Skin

Pure-Python port of the surface generators from BOSL2’s skin.scad — every one builds a vnf you render with .polyhedron().

Coverage of BOSL2 skin.scad

BOSL2 function

Status

Notes

sweep(shape, transforms)

ported

sweep()

path_sweep(shape, path)

ported

path_sweep() — methods incremental / manual / natural, twist, scale (scalar / [x, y] / per-point / Nx2), open & closed paths, flat caps, user tangents, and the transforms=True mode

skin(profiles, slices)

ported

from_skin()direct and reindex methods

linear_sweep(region, h)

ported

linear_sweep() — single outline, with twist / scale / shift / caps

rotate_sweep(shape, angle)

ported

rotate_sweep()

spiral_sweep(poly, h, r)

ported

spiral_sweep() — without the lead-in taper options

path_sweep2d(shape, path)

ported

path_sweep2d() — 2-D shape along a 2-D path (mitre offset; local creases handled up to the path’s tightest radius)

rot_resample(rotlist, n)

ported

ported — resample a transform list along its screw motion, with rot_decode / rot_inverse in pybosl2.transforms

subdivide_and_slice / slice_profiles

ported

sweep()

skin() distance / tangent methods

not ported

use direct / reindex (they need the dynamic-programming vertex matcher)

sweep_attach(), anchors

not ported

need the BOSL2 attachment/anchor system

textures (texture(), tex_*)

not ported

the whole texturing engine

rounded / chamfered “fancy” caps

not ported

use flat caps, or a native end treatment

region shapes with holes

not ported

use a native linear_extrude / CSG for holed extrusions

rot_resample() / associate_vertices() helpers

not ported

only needed by the un-ported matching methods

API reference

Surface generators: sweep, path_sweep, skin, linear_sweep, rotate_sweep, spiral_sweep (BOSL2 skin.scad).

class pybosl2.skin.Sweepable[source]

Bases: object

Mixin adding sweep methods to Path2D and Path3D.

path_sweep(shape, method=SweepMethod.INCREMENTAL, normal=None, closed=False, twist=0.0, twist_by_length=True, scale=(1.0, 1.0), scale_by_length=True, symmetry=1, last_normal=None, tangent=None, uniform=True, relaxed=False, caps=CapType.BUTT, style=VNFStyle.MIN_EDGE)[source]

Sweep shape along this path.

method orients the cross section: “incremental” (rotation-minimizing frame), “manual” (using normal as a per-point normal list), or “natural” (the path’s own normal). twist (degrees) and scale (scalar, 2-vector, per-point vector, or Nx2) are interpolated along the path. See BOSL2 path_sweep() for the full semantics.

Parameters:
shape : PathLike

The cross-section to sweep.

method : SweepMethod

How cross-sections are matched or oriented along the sweep.

normal : Sequence[float] | Sequence[Sequence[float]] | None

The surface normal to use.

closed : bool

Treat the path or profile as closed.

twist : float

Total twist in degrees along the sweep.

twist_by_length : bool

Distribute the twist by arc length rather than evenly per section.

scale : Any

Scale applied along the sweep, from 1 at the start.

scale_by_length : bool

Distribute the scaling by arc length rather than evenly per section.

symmetry : int

Rotational symmetry of the profile, used to match its points up.

last_normal : Sequence[float] | None

The previous section’s normal, so the sweep does not flip.

tangent : Sequence[Sequence[float]] | None

The path’s direction at this point.

uniform : bool

Sample by arc length rather than by parameter.

relaxed : bool

Allow a less exact match where an exact one is not possible.

caps : CapsSpec

Close the two open ends of the sweep.

style : VNFStyle

How each grid cell is split into triangles.

Return type:

Solid

Examples

Sweeping a small square profile along a helical path into a solid:

import math
import numpy as np
from pybosl2 import Path3D

square = [[-3, -3], [3, -3], [3, 3], [-3, 3]]
helix = [[10 * math.cos(t), 10 * math.sin(t), t * 3] for t in np.linspace(0, 3 * math.pi, 40)]
Path3D(helix).path_sweep(square).show()
Loading 3-D preview…

⬇ Download STL mesh

path_sweep_transforms(method=SweepMethod.INCREMENTAL, normal=None, closed=False, twist=0.0, twist_by_length=True, scale=(1.0, 1.0), scale_by_length=True, symmetry=1, last_normal=None, tangent=None, uniform=True, relaxed=False)[source]

Return the 4x4 transforms path_sweep() would place its cross sections with.

This used to be path_sweep(..., transforms=True), which made the return type depend on an argument: every caller of the ordinary case paid for the flag with a union they could not narrow, and the documented one-liner stopped type-checking. A flag that changes the return type is a second function, so here it is (SPEC S-19b, PLAN T-6d).

The parameters are path_sweep()’s, minus the ones that only affect the skin (caps, style) and the profile itself – a transform list does not have a profile.

Parameters:
method : SweepMethod

how the cross section is oriented along the path.

normal : Sequence[float] | Sequence[Sequence[float]] | None

per-point normals, for SweepMethod.MANUAL.

closed : bool

the path loops back on itself.

twist : float

degrees of twist along the path.

twist_by_length : bool

distribute the twist by arc length rather than by point index.

scale : Any

scalar, 2-vector, per-point vector or Nx2 scaling along the path.

scale_by_length : bool

distribute the scaling by arc length rather than by point index.

symmetry : int

rotational symmetry order of the profile.

last_normal : Sequence[float] | None

normal to land on at the far end.

tangent : Sequence[Sequence[float]] | None

explicit per-point tangents.

uniform : bool

resample the path uniformly first.

relaxed : bool

relax the frame rather than holding the normal exactly.

Returns:

One 4x4 matrix per cross section, as plain nested lists.

Return type:

list[list[list[float]]]

Examples

Placing your own geometry at each station along a path:

from pybosl2 import Path3D, cuboid

path = Path3D([[0, 0, 0], [0, 0, 10], [5, 0, 20]])
for matrix in path.path_sweep_transforms():
    cuboid([2, 2, 1]).multmatrix(matrix)
path_sweep2d(shape, closed=False, caps=CapType.BUTT, style=VNFStyle.MIN_EDGE)[source]

Sweep 2-D shape along this 2-D path.

For each point on the profile, the path is offset by its X coordinate and lifted to Z = Y, producing a stack of profiles that are skinned into the final surface. Closed paths are reversed automatically to maintain the same winding.

Parameters:
shape : PathLike

The cross-section to sweep.

closed : bool

Treat the path or profile as closed.

caps : CapsSpec

Close the two open ends of the sweep.

style : VNFStyle

How each grid cell is split into triangles.

Return type:

Solid

Examples

A rounded bar swept along a wavy 2-D path:

import math
from pybosl2 import Path2D

shape = [[-2, -2], [2, -2], [2, 2], [-2, 2]]
path = [[t, 8 * math.sin(t / 12)] for t in range(0, 90, 3)]
Path2D(path).path_sweep2d(shape).show()
Loading 3-D preview…

⬇ Download STL mesh

linear_sweep(height=None, twist=0.0, scale=1, shift=(0.0, 0.0), slices=None, center=False, caps=CapType.BUTT, style=VNFStyle.MIN_EDGE)[source]

Extrude this 2-D profile linearly with optional twist/scale/shift.

The profile is duplicated at slices positions along the Z axis; at each level the points are twisted (rotation around Z, degrees) and scaled (uniform scalar or 2-vector), then shifted in XY. The slices are skinned into a VNF.

Parameters:
height : float | None

Height of the extrusion.

twist : float

Total twist in degrees along the sweep.

scale : Any

Scale applied along the sweep, from 1 at the start.

shift : Sequence[float]

Offset of the far end from the near one, as [x, y].

slices : int | None

How many intermediate sections to insert between profiles.

center : bool

Centre the result on the origin.

caps : CapsSpec

Close the two open ends of the sweep.

style : VNFStyle

How each grid cell is split into triangles.

Return type:

Solid

Examples

A twisting, tapering square column:

from pybosl2 import Path2D

square = [[-10, -10], [10, -10], [10, 10], [-10, 10]]
Path2D(square).linear_sweep(height=40, twist=120, scale=0.4).show()
Loading 3-D preview…

⬇ Download STL mesh

rotate_sweep(angle=360.0, caps=CapType.BUTT, _closed=None, style=VNFStyle.MIN_EDGE, start=0.0)[source]

Revolve this 2-D profile around the Z axis.

The profile is swept through angle degrees (default 360) around Z, starting at start degrees. When angle < 360 the profile is capped at both ends.

Parameters:
angle : float

The angle in degrees.

caps : CapsSpec

Close the two open ends of the sweep.

_closed : bool | None

Internal closed flag.

style : VNFStyle

How each grid cell is split into triangles.

start : float

Where along the path to begin.

Return type:

Solid

Examples

Revolving a rounded profile into a spool:

from pybosl2 import Path2D

profile = [[4, -10], [12, -10], [12, -6], [7, -2], [7, 2], [12, 6], [12, 10], [4, 10]]
Path2D(profile).rotate_sweep(angle=360).show()
Loading 3-D preview…

⬇ Download STL mesh

spiral_sweep(height, radius=None, turns=1.0, radius1=None, radius2=None, diameter=None, diameter1=None, diameter2=None, center=True, style=VNFStyle.MIN_EDGE, fn=None, fa=None, fs=None)[source]

Sweep this 2-D profile along a helix.

The profile follows a helical path of height and radius (or separate start/end radii) over turns revolutions. Unlike rotate_sweep, the profile also gains height, producing a coil.

Parameters:
height : float

Overall height of the coil.

radius : float | None

Helix radius; use radius1/radius2 for a taper.

turns : float

Number of revolutions.

radius1 : float | None

Radius at the start.

radius2 : float | None

Radius at the end.

diameter : float | None

Helix diameter, instead of radius.

diameter1 : float | None

Diameter at the start.

diameter2 : float | None

Diameter at the end.

center : bool

Centre the coil on the origin.

style : VNFStyle

Quad-subdivision style for the mesh.

fn : int | None

Fixed fragment count per turn; ambient default when omitted. Omitted, the ambient use_defaults(fn=...) value applies; fn=0 opts back out to fa/fs.

fa : float | None

Minimum fragment angle per turn. Omitted, the ambient use_defaults(fa=...) value applies.

fs : float | None

Minimum fragment size per turn. Omitted, the ambient use_defaults(fs=...) value applies.

Returns:

The swept coil.

Return type:

Solid

Examples

A rectangular-section coil spring:

from pybosl2 import Path2D

section = [[-1.2, -1.2], [1.2, -1.2], [1.2, 1.2], [-1.2, 1.2]]
Path2D(section).spiral_sweep(height=40, radius=12, turns=5).show()
Loading 3-D preview…

⬇ Download STL mesh

sweep(transforms, closed=False, caps=CapType.BUTT, style=VNFStyle.MIN_EDGE)[source]

Apply each 4x4 transform to this 2-D shape and skin the resulting profiles into a VNF.

or Bosl2Solid (BOSL2 sweep()).

Parameters:
transforms : Sequence[Sequence[Sequence[float]]]

The matrices to place each section with, instead of deriving them.

closed : bool

Treat the path or profile as closed.

caps : CapsSpec

Close the two open ends of the sweep.

style : VNFStyle

How each grid cell is split into triangles.

Return type:

Solid

pybosl2.skin.path3d(path)[source]

Pad a 2-D (or 3-D) point list to 3-D with z=0.

path stays array-like rather than becoming a Path (SPEC C-7a’s normalizer carve-out, PLAN T-4d). The name says polyline, but half its callers hand it something that is not one: this also pads tangent and derivative vectors to three components – _path_sweep(tangent=) and Bezier.sweep() both do – and no point type describes a list of direction vectors. It is a “pad each row to three floats” utility that a polyline merely happens to be a common input for.

The coordinates are converted to plain Python floats, not left as whatever the input held: a numpy row in would otherwise leak np.float64 scalars out of an annotation that promises float, and those raise SystemError/TypeError at the native FFI boundary (see the note in pybosl2/paths.py).

Parameters:
path : Sequence[Sequence[float]] | Path | np.ndarray | Sequence[np.ndarray]

The path to sweep along.

Return type:

list[list[float]]

pybosl2.skin.clockwise_polygon(poly)[source]

poly wound clockwise (reversed if its signed area is positive/CCW).

Returns a Path2D, not a bare point list: rewinding an outline yields an outline, and handing back the raw points would drop the type its caller just supplied (PLAN T-4, SPEC C-9). The old signature said list[Sequence[float]] while actually returning numpy rows, which only type-checked because a # type: ignore sat on the call.

Parameters:
poly : Path2D

The polygon to operate on.

Return type:

Path2D

pybosl2.skin.frame_map(x=None, y=None, z=None)[source]

Return the 4x4 rotation whose columns are the given orthonormal axes.

Give any two of x/y/z (as 3-vectors); the third is filled in by the cross product.

Parameters:
x : Sequence[float] | None

The X coordinate.

y : Sequence[float] | None

The Y coordinate.

z : Sequence[float] | None

The Z coordinate.

Return type:

ndarray

pybosl2.skin.slice_profiles(profiles, slices, closed=False)[source]

Interpolate slices extra profiles between each consecutive pair.

slices is a count (or a per-segment list). The profiles must all be equal-length point lists; the interpolation is vertex-by-vertex.

Parameters:
profiles : Sequence[Path2D | Path3D]

The cross-sections to skin between, in order.

slices : int

How many intermediate sections to insert between profiles.

closed : bool

Treat the path or profile as closed.

Return type:

list[list[list[float]]]

pybosl2.skin.subdivide_and_slice(profiles, slices, numpoints=None, method=ResampleMethod.LENGTH, closed=False)[source]

Resample every profile up to numpoints then interpolate slices between them.

numpoints defaults to the largest profile’s length; “lcm” uses the least common multiple of the profile lengths. Returns the stacked list of (equal-length) profiles.

Parameters:
profiles : Sequence[Path2D | Path3D]

The cross-sections to skin between, in order.

slices : int

How many intermediate sections to insert between profiles.

numpoints : int | str | None

How many points to resample each profile to.

method : ResampleMethod

How cross-sections are matched or oriented along the sweep.

closed : bool

Treat the path or profile as closed.

Raises:

Bosl2ValueError – If profiles is not a sequence of Path2D/Path3D.

Return type:

list[list[list[float]]]

class pybosl2.skin.OSType(*values)[source]

Bases: StrEnum

Offset sweep profile type.

CIRCLE = 'circle'
SMOOTH = 'smooth'
TEARDROP = 'teardrop'
CHAMFER = 'chamfer'
FLAT = 'flat'
PROFILE = 'profile'
pybosl2.skin.os_circle(radius=None, height=None, extra=0.0)[source]

Circular roundover/flare profile for offset_sweep() (BOSL2 os_circle()).

Describes the treatment applied to one rim of the extruded shape:

  • radius > 0 — inward roundover: the rim is eased in (material is removed from the corner, yielding a convex fillet).

  • radius < 0 — outward flare: extra material is added outside the wall at the rim (a concave cove).

  • radius == 0 — square / no treatment (same as passing None to offset_sweep()).

Parameters:
radius : float | None

Roundover radius (positive = roundover, negative = flare).

height : float | None

Height of the rim treatment; defaults to abs(radius). Should be less than half the extrusion height.

extra : float

Extra extension beyond the nominal arc (useful to close tiny gaps from floating-point rounding; default 0).

Returns:

A descriptor OSProfile consumed by offset_sweep().

Return type:

OSProfile

pybosl2.skin.os_smooth(cut=None, radius=None, curvature=0.5, extra=0.0)[source]

Continuous curvature (Bézier) profile for offset_sweep() (BOSL2 os_smooth()).

Uses a 4th-order Bézier curve to ease the transition between flat and curved edges, avoiding sudden changes in curvature.

Parameters:
cut : float | None

Depth of the roundover/flare.

radius : float | None

Alternative to cut (aliases it).

curvature : float

Smoothness/curvature match parameter between 0 and 1 (default 0.5).

extra : float

Extra extension beyond the nominal curve (default 0).

Returns:

A descriptor OSProfile consumed by offset_sweep().

Return type:

OSProfile

pybosl2.skin.os_teardrop(radius=None, height=None, cut=None, max_angle=45.0, extra=0.0)[source]

Teardrop profile for offset_sweep() to avoid overhangs in 3D printing (BOSL2 os_teardrop()).

Transitions from a 1/8th circle into a straight line at max_angle degrees relative to the vertical wall, allowing support-free printing.

Parameters:
radius : float | None

Radius of the circular portion.

height : float | None

Total height of the treatment (defaults to abs(radius)).

cut : float | None

Alternative to radius (aliases it).

max_angle : float

Curvature transition angle relative to the wall (default 45.0).

extra : float

Extra extension beyond the nominal curve (default 0).

Returns:

A descriptor OSProfile consumed by offset_sweep().

Return type:

OSProfile

pybosl2.skin.os_chamfer(width=None, height=None, angle=None, cut=None, extra=0.0)[source]

Chamfer/bevel profile for offset_sweep() (BOSL2 os_chamfer()).

Creates a flat bevel transition.

Parameters:
width : float | None

Horizontal width of the chamfer.

height : float | None

Vertical height of the chamfer (defaults to width).

angle : float | None

Bevel angle in degrees. If given, overrides width.

cut : float | None

Bevel size (aliases both width and height).

extra : float

Extra extension beyond the nominal bevel (default 0).

Returns:

A descriptor OSProfile consumed by offset_sweep().

Return type:

OSProfile

pybosl2.skin.os_flat()[source]

Flat end cap profile descriptor representing no treatment (BOSL2 os_flat()).

Return type:

OSProfile

pybosl2.skin.os_profile(profile, extra=0.0)[source]

Return a custom offset sweep profile descriptor (BOSL2 os_profile()).

Accepts a list of 2D points [[x, y], …] defining the profile: - x is the inward radial offset (meaning delta = -x). - y is the height z.

Parameters:
profile : Path2D

Sequence of [x, y] points. The first must be [0, 0].

extra : float

Extra extension (default 0).

Returns:

A descriptor OSProfile consumed by offset_sweep().

Return type:

OSProfile

pybosl2.skin.rot_resample(rotlist, num_copies, twist=None, scale=None, smoothlen=1, long=False, turns=0, closed=False, method=ResampleMethod.LENGTH)[source]

Resample a list of 4x4 transforms to uniform screw-motion spacing.

Interpolates between successive transforms along their screw motion (via rot_decode()), optionally adding twist and scale (smoothed over smoothlen). Handy for regularizing the transform list from Sweepable.path_sweep_transforms() before handing it to sweep().

Parameters:
rotlist : Sequence[Sequence[float]]

list of 4x4 transform matrices

num_copies : int | Sequence[int]

number of output samples (method=”length”) or samples per gap (method=”count”)

twist : float | Sequence[float] | None

extra twist in degrees (scalar or per-gap list)

scale : float | Sequence[float] | None

extra scale (scalar or per-gap list, multiplied cumulatively)

smoothlen : int

odd window length for smoothing the twist/scale (default 1 = none)

long : bool

take the >180-degree rotation at a gap (scalar or per-gap list)

turns : float

extra full turns to add at a gap (scalar or per-gap list)

closed : bool

the transform list forms a loop (default False)

method : ResampleMethod

“length” (uniform screw-distance) or “count” (fixed samples per gap)

Return type:

list[list[list[float]]]