Paths¶
The object form of BOSL2’s path maths. Path2D is a 2-D outline (a list of
[x, y] points) carrying every paths.scad operation as a chained method;
Path3D is its 3-D sibling (a list of [x, y, z] points), used by the 3-D
generators like helix(). Path3D reuses the same numeric kernels and
carries only the operations that make sense in 3-D – measurement (length, tangents,
normals(), curvature, torsion()), resampling
and cutting, and the 3-D transforms (translate/move, the six directional moves including
up/down, scale, rotate, mirror) – with path2d() to
drop to the XY plane when you need the inherently-2-D operations (polygon, offset, area).
Abstract Path base class for 2-D and 3-D path types.
Concrete math helpers live in pybosl2._path_math.
-
class pybosl2.paths.CutPoint(point, next_index, direction=
None, normal=None)[source]¶ Bases:
objectA point along a path where it was cut, with the index of the next segment.
Returned by
cut_points()and related methods. When requested withdirection=True, the direction and normal attributes are populated; otherwise they areNone.- point : Point¶
- next_index : int¶
- property is_directed : bool¶
True if direction and normal vectors are present.
-
class pybosl2.paths.Path(points=
None, closed=False)[source]¶ Bases:
ABCDimension-agnostic numeric path operations shared by
Path2DandPath3D.Abstract base class. Subclasses must provide
_points(numpy.ndarray) andclosed(bool).- closed : bool¶
-
abstractmethod segment_lengths(closed=
None)[source]¶ Length of each segment of the path, as an ndarray.
-
abstractmethod length_fractions(closed=
None)[source]¶ Distance fraction of each point in the path (0 at start, 1 at end).
-
tangent_array(closed=
None, uniform=True)[source]¶ Return the unit tangent at every point of the path, as an (N, D) array (BOSL2 path_tangents).
The shared implementation behind
tangents()for both dimensions. Always returns one tangent per path point, never one per segment.A path of fewer than two points has no direction to derive – there is no neighbour to difference against. Rather than raise, each such point is given +x (
[1, 0]/[1, 0, 0]). That is a CONVENTION, not a measurement: it is arbitrary, inherited from the original implementation, and kept only so callers get a usable unit vector and a predictable(N, D)shape. Do not read meaning into the direction, and do not change it casually –normals()rotates whatever comes back, so anything downstream of a one-point path moves with it.
-
abstractmethod tangents(closed=
None, uniform=True)[source]¶ Return normalized tangent vector at each point of the path, as an ndarray.
-
abstractmethod normals(tangents=
None, closed=None)[source]¶ Return normal vector (perpendicular to tangent, in the plane of the curve) at each point.
For 2-D paths this is a 90-degree rotation of the tangent. For 3-D paths it is the principal normal estimated via the triple-product cross.
-
abstractmethod curvature(closed=
None)[source]¶ Numeric curvature estimate of the path at each point, as an ndarray.
-
abstractmethod torsion(closed=
None)[source]¶ Numeric torsion estimate of the path at each point, as an ndarray.
-
abstractmethod cut(cutdist, closed=
None)[source]¶ Cut path into subpaths at the given ascending list of distances (or a single distance).
- abstractmethod cut_getpaths(cutlist, closed)[source]¶
Reconstruct sub-paths from the output of cut_points().
-
abstractmethod cut_points(cutdist, closed=
None, direction=False)[source]¶ Cut path at given distance(s) from start.
Returns a list of
CutPointentries (or :class:`` if direction is True).
-
abstractmethod cut_points_recurse(dists, closed=
False)[source]¶ Walk the path accumulating distance until each cut distance is reached.
-
abstractmethod cut_single(dist, closed=
False, ind=0, eps=1e-07)[source]¶ Find the single cut point at distance dist from segment ind.
-
abstractmethod cuts_path_normals(cuts, closed=
False)[source]¶ Compute normals at each cut point from the path geometry.
-
abstractmethod plane(ind, i, closed=
False)[source]¶ Find the local plane defined by point ind, ind-1, and the nearest non-collinear point.
-
abstractmethod cuts_dir(cuts, closed=
False, eps=0.01)[source]¶ Compute direction vectors at each cut point (blended from adjacent segments).
-
abstractmethod subdivide_path(points=
None, points_per_segment=None, maxlen=None, exact=True, closed=None, method=SubdivideMethod.LENGTH)[source]¶ Subdivide the path into evenly spaced points.
- Parameters:¶
- points : int | None¶
Target total number of points.
- points_per_segment : Sequence[int] | None¶
Number of points to add to each segment index.
- maxlen : float | None¶
Maximum allowed segment length.
- exact : bool¶
If False, favor uniform sampling — point count may differ.
- closed : bool | None¶
Override the instance’s closed flag.
- method : SubdivideMethod¶
Subdivision method —
LENGTH(uniform along path) orSEGMENT(per segment).
- Returns:¶
A new path with the subdivided points.
- Return type:¶
-
abstractmethod resample_path(num_copies=
None, spacing=None, closed=None)[source]¶ Uniformly resample path to num_copies points, or to a spacing near spacing.
-
abstractmethod select(s1, u1, s2, u2, closed=
None)[source]¶ Portion of path from the u1 fraction of segment s1 to the u2 fraction of segment s2.
-
abstractmethod stroke(width=
1, closed=None, endcaps=CapType.ROUND, endcap1=CapType.ROUND, endcap2=CapType.ROUND, joints=CapType.ROUND)[source]¶ Render the path as a stroked polygon outline (2-D) or solid tube (3-D).
- Parameters:¶
- width : float¶
Stroke line width.
- closed : bool | None¶
Override the instance’s closed flag.
- endcaps : CapType | CapSpec¶
Default endcap style for both ends.
- endcap1 : CapType | CapSpec¶
Start endcap style (overrides endcaps).
- endcap2 : CapType | CapSpec¶
End endcap style (overrides endcaps).
- joints : CapType | CapSpec¶
Joint style at vertices.
- Returns:¶
A
Path2Dfor 2-D strokes,Bosl2Solidfor 3-D.- Return type:¶
Any
-
abstractmethod dashed_stroke(dashpat=
None, closed=None, fit=True, mindash=0.5)[source]¶ Break the path into dashed segments and stroke them.
-
abstractmethod merge_collinear(closed=
None, eps=1e-09)[source]¶ Remove sequential collinear points and return a new path.
- class pybosl2.paths.SubdivideMethod(*values)[source]¶
Bases:
EnumMethod for subdividing a path.
-
LENGTH =
'length'¶
-
SEGMENT =
'segment'¶
-
LENGTH =
2-D path operations: area, offset, polygon containment, round_corners, linear_extrude, and more.
The Path2D class extends Path with 2-D-specific
operations (polygon, area, offset, round_corners()) while inheriting
the dimension-agnostic measurements from Path.
-
class pybosl2.path2d.Path2D(points=
None, closed=False)[source] Bases:
Path,Distributable,Extrudable,Sweepable,RoundableA 2-D path (formerly
Path2D): a list of [x, y] points, with every path operation as a method.Every place that already treats a path as a plain point list (indexing, iteration,
len(), equality with a plain list, and crossing the nativepolygon()/FFI boundary) keeps working, so this is a drop-in for the raw lists the toolkit passes around, while giving the chained object form for new code:Path2D([[0, 0], [80, 0], [80, 60], [0, 60]]).offset(radius=-2).round_corners(radius=1).polygon()Every method returns a NEW Path2D (or list/array) – nothing mutates in place, so a path can be reused as the base for several derived outlines.
- Parameters:¶
- points : Sequence[Sequence[float]] | NDArray[np.float64]¶
the [x, y] points (anything array-like; numpy scalars are converted to float)
- closed : bool¶
whether the path is a closed polygon – default False, an open polyline, matching BOSL2, where a path is open unless a function is told otherwise. Pass
closed=Truefor a polygon: it adds the segment from the last point back to the first to the length, the tangents, and anything derived from them. Note thatpolygon()andarea()treat the outline as closed either way.
Examples
A box outline inset by the wall thickness and with rounded corners, extruded into a plate:
from pybosl2 import Path2D outline = Path2D([[0, 0], [80, 0], [80, 60], [0, 60]]) plate = outline.offset(radius=-3).round_corners(radius=5).polygon().linear_extrude(height=4) plate.show()Loading 3-D preview…- property array : ndarray
The points as an (N, 2) numpy array, for doing your own vectorised maths.
- Returns:¶
An (N, 2) float64 numpy array.
- property to_list : list[list[float]]
The points as a list of
[x, y]plain-Python-float pairs.- Returns:¶
A list of
[x, y]pairs.
-
classmethod from_list(lst, closed=
False)[source] Create a Path2D from a plain list of
[x, y]coordinate pairs.
-
segment_lengths(closed=
None)[source] Length of each segment of the path, as an ndarray.
An open path of N points has N-1 segments; a closed one has N, the extra being the closing segment from the last point back to the first.
The short cases follow from that rule rather than being special-cased away:
An EMPTY path has no segments either way – there are no points to join.
A SINGLE point is where open and closed differ. Open, it has no segments. Closed, the closing segment joins the point to ITSELF, so the result is one segment of length 0 – not zero segments:
Path2D([[1, 2]]).segment_lengths() # array([]) Path2D([[1, 2]], closed=True).segment_lengths() # array([0.])This keeps
len(segment_lengths(closed=True)) == len(path), whichtangent_array()relies on when it samples the non-uniform derivative.
-
length_fractions(closed=
None)[source] Distance fraction of each point in the path (0 at start, 1 at end).
-
closest_point(pt, closed=
None)[source] Return the closest point on the path to pt.
Uses Shapely projection for 2-D accuracy.
-
tangents(closed=
None, uniform=True)[source] Return the normalized tangent vector at each point of the path (BOSL2 path_tangents).
There is always exactly one tangent per path point – not one per segment. A path of fewer than two points has nothing to differentiate, so each of its points gets
+xby convention; seetangent_array().
-
normals(tangents=
None, closed=None)[source] Perpendicular unit normal at each point (90° rotation of tangent).
-
curvature(closed=
None)[source] Numeric curvature estimate at each point of the path (BOSL2 path_curvature).
There is one value per path point, matching
tangents().
-
cut(cutdist, closed=
None)[source] Cut path into subpaths at the given ascending list of distances (or a single distance).
-
cut_points(cutdist, closed=
None, direction=False)[source] Cut path at given distance(s) from start.
If direction is True, each CutPoint includes direction and normal.
-
cut_points_recurse(dists, closed=
False)[source] Walk the path accumulating distance until each cut distance is reached.
-
cut_single(dist, closed=
False, ind=0, eps=1e-07)[source] Find the single cut point at distance dist.
-
cuts_path_normals(cuts, closed=
False)[source] Compute normals at each cut point from the path geometry.
Uses the Shapely line to find the local tangent at each cut location, then returns the perpendicular (normal) vector.
-
subdivide_path(points=
None, points_per_segment=None, maxlen=None, exact=True, closed=None, method=SubdivideMethod.LENGTH)[source] Subdivide the path into more points.
-
resample_path(num_copies=
None, spacing=None, closed=None)[source] Resample the path with evenly spaced points.
-
select(s1, u1, s2, u2, closed=
None)[source] Portion of path from the u1 fraction of segment s1 to the u2 fraction of segment s2.
- bounds()[source]
Axis-aligned bounding box with pre-computed width and length.
Returns a
Bounds2Dnamed tuple withmin_x,min_y,max_x,max_y,width, andlengthfields.
- is_clockwise()[source]
Return True if the polygon winds clockwise (negative signed area).
- Return type:¶
bool
- perimeter()[source]
Total length along the path, including the closing segment when it is closed.
- contains(point)[source]
Return True if point is inside the closed polygon (on the boundary counts as inside).
Containment is only meaningful for a closed polygon, so an open path (
closed=False) always returns False rather than testing.- Parameters:¶
- point : Sequence[float]¶
An
[x, y]coordinate to test for containment.
- Returns:¶
True if the point is inside or on the boundary of the polygon.
- Return type:¶
bool
Examples
from pybosl2 import Path2D rect = Path2D([[0, 0], [80, 0], [80, 60], [0, 60]]) result = rect.contains([40, 30]) print("inside:", result) rect.stroke(width=1).linear_extrude(height=1).show()
- property is_closed : bool
Return True if the first and last points of the path coincide.
-
offset(radius=
None, delta=None, chamfer=False, fn=None, fa=None, fs=None, same_length=False)[source] Offset by radius (rounded joins) or delta (sharp/chamfered).
Prefer
.polygon().offset(...)(native, Manifold-side) when you only need geometry; this is for when the result is needed as points.The result is a simple (non-self-intersecting) polygon: where the offset folds back over the original outline – corner arcs colliding on a detailed outline, or mitres inverting when a shape is shrunk past its own width – the folded points are dropped rather than left in. An offset that would break the outline into separate pieces still comes back as one path, since a
Path2Dholds a single outline; use aRegionif the pieces matter.- Parameters:¶
- radius : float | None¶
Offset distance with rounded joins (positive grows, negative shrinks).
- delta : float | None¶
Offset distance with sharp/chamfered joins (mutually exclusive with radius).
- chamfer : bool¶
If True, use chamfered rather than sharp joins when delta is given.
- fn : int | None¶
Number of facets for rounded sections (overrides fa/fs).
- fa : float | None¶
Minimum angle in degrees for circle fragments.
- fs : float | None¶
Minimum size for circle fragments.
- same_length : bool¶
Return one point per input point (
deltaoffsets only), for callers likepath_sweep2d()that need the two paths to correspond point-for-point (BOSL2offset(..., same_length=true)). Since repairing a fold means dropping points, this mode skips the repair and returns the raw corner construction – do not use it for outlines you intend to keep.
- Returns:¶
A new offset
Path2D.- Raises:¶
AssertionError – If not exactly one of radius/delta is given, if the path is open, if same_length is combined with rounded or chamfered joins, or if the offset collapsed the outline entirely (shrinking a shape by more than its own half-width leaves nothing).
- Return type:¶
Path2D
Examples
from pybosl2 import Path2D outline = Path2D([[0, 0], [80, 0], [80, 60], [0, 60]]) inset = outline.offset(radius=-3) inset.polygon().linear_extrude(height=4).show()Loading 3-D preview…
- close()[source]
Append the start point if the path is not already closed.
Returns a new Path2D with the first point appended to the end, making it a closed polygon. Has no effect if the path is already closed.
Examples
from pybosl2 import Path2D pts = Path2D([[0, 0], [80, 0], [80, 60], [0, 60]], closed=False) result = pts.close() result.stroke(width=2).linear_extrude(height=4).show()Loading 3-D preview…- Return type:¶
Path2D
- cleanup()[source]
Drop a duplicate closing point if present.
If the first and last points coincide this returns a new Path2D with the duplicate removed, turning the path into an open one.
Examples
from pybosl2 import Path2D pts = Path2D([[0, 0], [80, 0], [80, 60], [0, 60], [0, 0]]) result = pts.cleanup() result.stroke(width=2).linear_extrude(height=4).show()Loading 3-D preview…- Return type:¶
Path2D
- reverse()[source]
Return the same outline wound the other way.
Returns a new Path2D with all points in reverse order, flipping the winding direction (clockwise becomes counter-clockwise and vice-versa).
Examples
from pybosl2 import Path2D rect = Path2D([[0, 0], [80, 0], [80, 60], [0, 60]]) result = rect.reverse() result.stroke(width=2).linear_extrude(height=4).show()Loading 3-D preview…- Return type:¶
Path2D
- deduplicated()[source]
Drop consecutive repeated points (
_deduplicate()).Examples
from pybosl2 import Path2D pts = Path2D([[0, 0], [20, 0], [20, 0], [40, 0], [40, 30], [40, 30], [80, 60]]) result = pts.deduplicated() result.stroke(width=2).linear_extrude(height=4).show()Loading 3-D preview…- Return type:¶
Path2D
-
subdivide(num_copies=
None, refine=None, maxlen=None, exact=True, closed=None)[source] Insert points along the path.
Give exactly one of num_copies, refine or maxlen.
- Parameters:¶
- num_copies : int | None¶
Target total number of points.
- refine : float | None¶
Multiply the current point count by this.
- maxlen : float | None¶
Cap on the spacing between points.
- exact : bool¶
Hit the target count exactly rather than approximately.
- closed : bool | None¶
Override the instance’s closed flag.
- Returns:¶
A new
Path2Dwith additional interpolated points.- Return type:¶
Path2D
Examples
from pybosl2 import Path2D pts = Path2D([[0, 0], [80, 0], [80, 60], [0, 60]]) result = pts.subdivide(num_copies=24) result.stroke(width=1).linear_extrude(height=4).show()Loading 3-D preview…
-
resample(num_copies=
None, spacing=None, closed=None)[source] Resample to evenly spaced points.
Give exactly one of num_copies or spacing.
Examples
from pybosl2 import Path2D pts = Path2D([[0, 0], [80, 0], [80, 60], [0, 60]]) sampled = pts.resample(num_copies=20) sampled.stroke(width=1).linear_extrude(height=2).show()Loading 3-D preview…
-
split_at_self_crossings(eps=
1e-09)[source] Split this 2-D path into subpaths wherever it crosses itself.
-
polygon_parts(nonzero=
False, eps=1e-09)[source] Split a possibly self-intersecting polygon into non-intersecting simple polygons.
- move(v)
Translate every point by v (2-D; a 1-vector shifts X only).
- rotate(a)
Rotate every point by a degrees about the origin (Z axis).
- fwd(y)
Translate by y along -Y (BOSL2 fwd()).
- to_region()[source]
Convert this path into a single-outline Region.
Returns a
Regioncontaining just this path as its only outline. Useful as a gateway to 2-D Boolean operations (union, intersection, difference) on polygons.
-
to_bezier(closed=
False, tangents=None, uniform=False, size=None, relsize=None)[source] Cubic bezier PATH through every point of this path (BOSL2 path_to_bezpath).
Delegates to
pybosl2.beziers.create_bezier().- Parameters:¶
- closed : bool¶
Whether the resulting bezier path should be closed.
- tangents : Path2D | None¶
Optional pre-computed tangent vectors for each point.
- uniform : bool¶
If True, use uniform parameterisation; see
tangents().- size : float | None¶
Absolute size of the tangent handles.
- relsize : float | None¶
Relative size of the tangent handles as a fraction of segment length.
- Returns:¶
A
Bezierpath through the given points.- Return type:¶
Examples
from pybosl2 import Path2D pts = Path2D([[0, 0], [40, 30], [80, 0], [120, 30]]) curve = pts.to_bezier(size=10).path_curve() curve.stroke(width=2).linear_extrude(height=3).show()Loading 3-D preview…
- polygon()[source]
Return this path as 2-D geometry (crosses the FFI as plain floats).
- Returns:¶
A
Bosl2Shape2D, so the result chains straight into the 2-D operators (.fill(),.hull(),.offset()) and the extruders (.linear_extrude(...)).- Raises:¶
UnsupportedByBackendError – under
use_backend("sdf").Use linear_extrude() instead; it works on both backends. –
- Return type:¶
Bosl2Shape2D
Examples
from pybosl2 import Path2D shape = Path2D([[0, 0], [80, 0], [80, 60], [0, 60]]) shape.polygon().linear_extrude(height=5).show()Loading 3-D preview…
- geometry()[source]
2-D geometry of this path.
The name
Regionalso exposes this, so a caller that may hold either a Path2D or a Region can ask for geometry without checking which it got.- Return type:¶
Bosl2Shape2D
- fill()[source]
Return this path as 2-D geometry with every hole filled in – only the outermost outline survives.
(OpenSCAD
fill()). For a self-intersecting path this closes up the interior loops thatpolygon()would leave as holes.
- minkowski_sum(other)[source]
Return the 2-D Minkowski sum of this closed path and other.
Adds other (a closed 2‑D polygon) to every point of this path, producing the swept outline as a single closed
Path2D. Equivalent to OpenSCAD’sminkowski()for 2‑D paths.Uses shapely to compute the convex hull of the translated copies of other centred at each vertex of this path. For convex shapes the result is exact; for non‑convex shapes it is a conservative approximation.
-
minkowski_sum_circle(radius, join=
MinkowskiJoin.ROUND, mitre_limit=5.0, single_sided=False, quad_segs=16)[source] Return the Minkowski sum of this closed path with a circle of radius.
Uses shapely
buffer()for an efficient offset with configurable corner style. Positive radius dilates (outline grows); negative erodes (shrinks).Corner join styles: *
MinkowskiJoin.ROUND— smooth radiused corners (default) *MinkowskiJoin.MITRE— sharp mitered corners (clipped at mitre_limit) *MinkowskiJoin.BEVEL— flat chamfered cornersSet single_sided to
Truefor a one‑sided dilation. quad_segs controls the segment count per quadrant for round joins (default 16).- Parameters:¶
- radius : float¶
The buffer radius (positive = dilate, negative = erode).
- join : MinkowskiJoin¶
Corner join style (default
MinkowskiJoin.ROUND).- mitre_limit : float¶
Maximum mitre extension ratio (
MinkowskiJoin.MITREonly).- single_sided : bool¶
If
True, dilate on one side of the outline only.- quad_segs : int¶
Segments per quadrant for round joins (default 16).
- Returns:¶
A new closed
Path2D.- Return type:¶
Path2D
Examples
Round join (default):
from pybosl2 import Path2D, MinkowskiJoin base = Path2D([[0, 0], [30, 0], [30, 20], [0, 20]]) base.minkowski_sum_circle(radius=5, join=MinkowskiJoin.ROUND) \ .polygon().linear_extrude(height=3).show()Loading 3-D preview…Sharp mitered corners:
from pybosl2 import Path2D, MinkowskiJoin base = Path2D([[0, 0], [30, 0], [30, 20], [0, 20]]) base.minkowski_sum_circle(radius=5, join=MinkowskiJoin.MITRE) \ .polygon().linear_extrude(height=3).show()Loading 3-D preview…Flat bevel (chamfered) corners:
from pybosl2 import Path2D, MinkowskiJoin base = Path2D([[0, 0], [30, 0], [30, 20], [0, 20]]) base.minkowski_sum_circle(radius=5, join=MinkowskiJoin.BEVEL) \ .polygon().linear_extrude(height=3).show()Loading 3-D preview…
-
classmethod circle2d(radius=
10, fn=64)[source] Create a closed
Path2Dapproximating a circle of radius.Uses fn uniform segments around the origin.
-
classmethod ellipse2d(rx=
10, ry=5, fn=64)[source] Create a closed
Path2Dapproximating an ellipse.Uses fn uniform parametric segments with semi‑axes rx and ry centred at the origin.
- hull(*others)[source]
Return the 2-D convex hull of this path and all the given closed paths and regions.
Uses shapely to compute the convex hull of the union of all input geometries and returns the hull as a single closed
Path2D.
- union(*others)[source]
Return the 2-D union of this closed path with others.
Converts all paths to
Polygonobjects, computes the Boolean union, and returns the result as a single closedPath2D. Requires all paths to be closed.
- intersection(*others)[source]
Return the 2-D intersection of this closed path with others.
Converts all paths to
Polygonobjects, computes the Boolean intersection, and returns the common area as a single closedPath2D.
- difference(other)[source]
Return the 2-D difference: self minus other.
Subtracts other from this path using shapely Boolean difference. Requires both paths to be closed.
- symmetric_difference(other)[source]
Return the 2-D symmetric difference (XOR) of this path and other.
Returns the area in either path but not both. Requires both paths to be closed.
-
linear_extrude(height, center=
None, twist=None, scale=None, slices=None, convexity=None, rounding_top=None, rounding_bottom=None, res=None, fn=None, fa=None, fs=None)[source] Extrude this path height along +Z into a 3-D solid.
The extrusion uses whichever backend is active: a
Bosl2Solidunder the default CSG backend, aPyShapeunderuse_backend("sdf"):plate = Path2D(pts).linear_extrude(height=4) # -> Bosl2Solid with use_backend("sdf"): field = Path2D(pts).linear_extrude(height=4) # -> PyShapeThe extra options differ by backend, since each realizes the extrusion its own way: the CSG backend takes the native
center/twist/scale/slices/convexity(seelinear_extrude()); the SDF backend takescenterplusrounding_top/rounding_bottom/res, and rejects the profile-shearing ones.- Parameters:¶
- height : float¶
The extrusion height along +Z.
- center : bool | None¶
Centre the result on z=0 rather than starting at z=0.
- twist : float | None¶
Degrees to rotate the top face relative to the bottom (CSG only).
- scale : float | Sequence[float] | None¶
Scale of the top face, a scalar or
[x, y](CSG only).- slices : int | None¶
Number of intermediate layers (CSG only).
- convexity : int | None¶
Rendering hint for self-overlapping cross-sections (CSG only).
- rounding_top : float | None¶
Rim roundover at the top (SDF only).
- rounding_bottom : float | None¶
Rim roundover at the bottom (SDF only).
- res : int | None¶
Field resolution (SDF only).
- fn : int | None¶
Arc smoothness override (CSG only).
- fa : float | None¶
Arc smoothness override (CSG only).
- fs : float | None¶
Arc smoothness override (CSG only).
- Return type:¶
Examples
from pybosl2 import Path2D plate = Path2D([[0, 0], [80, 0], [80, 60], [0, 60]]) plate.linear_extrude(height=4).show()Loading 3-D preview…
-
rotate_extrude(angle=
360.0, convexity=None, fn=None, fa=None, fs=None)[source] Revolve this path about the Y axis into a 3-D solid.
See
rotate_extrude().- Parameters:¶
- Returns:¶
A
Bosl2Solid.- Raises:¶
pybosl2.exceptions.UnsupportedByBackendError – under
use_backend("sdf")–the SDF backend has no revolve; sweep the profile instead via –
pybosl2._sdf.shapes3d.path_sweep()` –
- Return type:¶
Bosl2Solid
-
debug_polygon(size=
1, vertices=True)[source] Return a debug view of this polygon.
The filled outline (as a thin flat solid) with each vertex labelled by its index in red (BOSL2 debug_polygon()). Set size for the label size.
-
stroke(width=
1, closed=None, endcaps=None, endcap1=None, endcap2=None, joints=CapType.ROUND)[source] Render this 2-D path as a stroked polygon outline.
-
dashed_stroke(dashpat=
None, closed=None, fit=True, mindash=0.5)[source] Break this 2-D path into dashed polygon outlines.
Returns a
Regionof dash polygons.
-
merge_collinear(closed=
None, eps=1e-09)[source] Remove sequential collinear points and return a new path.
-
deduplicate(closed=
None, eps=1e-09)[source] Remove duplicate consecutive points and return a new path.
-
is_path_simple(closed=
None, eps=1e-09)[source] Return True if the 2D path has no self-intersections (repeated points are not intersections).
-
to_bezcornerpath(parm=
None, closed=None, fn=0, fs=2.0)[source] Replace straight corners with continuous-curvature beziers (BOSL2 path_to_bezcornerpath).
- Parameters:¶
- parm : float | list[float] | None¶
Distance from corner to control point (scalar for all corners,
[d, k]for asymmetrical, or per-corner list).NoneorFalseleaves a corner sharp.- closed : bool | None¶
Override the path’s closed flag.
- fn : int¶
Number of facets per bezier corner (0 = auto from fs).
- fs : float¶
Maximum facet size.
- Returns:¶
A new
Path2Dwith bezier-rounded corners.- Return type:¶
Path2D
- class pybosl2.path2d.MinkowskiJoin(*values)[source]
Bases:
EnumCorner join style for
Path2D.minkowski_sum_circle().ROUNDCircular arc joins (default). Smooth, radiused corners.
MITRESharp mitered joins, clipped at mitre_limit.
BEVELFlat chamfered joins.
-
ROUND =
1
-
MITRE =
2
-
BEVEL =
3
3-D path operations: tangents, normals, curvature, torsion, resampling, cutting, and 3-D transforms.
The Path3D class extends Path with 3-D measurements
and transforms while omitting inherently 2-D operations (polygon, area, offset).
-
class pybosl2.path3d.Path3D(points=
None, closed=False)[source] Bases:
Path,Distributable,Extrudable,Sweepable,RoundableA 3-D path: a list of
[x, y, z]points, with the path operations that make sense in 3-D.The 3-D counterpart of
Path2D. LikePath2D, every method returns a NEW object. It carries the dimension-independent measurements (length, segment lengths, tangents,normals(), curvature,torsion()), resampling/subdividing/cutting, and the 3-D transforms (translate/move,right/left/back/forward/up/down,scale,mirror,rotate). The inherently-2-D operations ofPath2D(polygon,area,offset,round_corners, point-in-polygon) are intentionally absent; usepath2d()to drop to the XY plane when you want them.- Parameters:¶
- points : Sequence[Sequence[float]] | NDArray[np.float64]¶
the
[x, y, z]points (anything array-like; numpy scalars are converted to float)- closed : bool¶
whether the path is a closed loop – default False, an open polyline, matching BOSL2, where a path is open unless a function is told otherwise. Pass
closed=Truefor a loop: it adds the segment from the last point back to the first to the length, the tangents, and anything derived from them.
Examples
A helix resampled to fewer points and swept into a coil:
from pybosl2.path3d import Path3D coil = Path3D.helix(turns=3, height=60, radius=20).resample(num_copies=120) coil.stroke(width=4).show()Loading 3-D preview…-
classmethod helix(length=
None, height=None, turns=None, angle=None, radius=None, radius1=None, radius2=None, diameter=None, diameter1=None, diameter2=None)[source] Return a 3-D helical path on a (possibly conical) surface – BOSL2’s
helix().Returned as a
Path3D(the 3-D path object), so it carries the 3-D transforms/measurements and feeds straight into stroke orpath_sweep. Give exactly two of length/height (length), turns, and angle; the third is derived. Positive turns is right-handed, negative left-handed. Start/end radii may differ for a conical helix (a flat spiral isheight=0with a turn count).- Parameters:¶
- length : float | None¶
Height of the helix (0 for a flat spiral).
- height : float | None¶
Height of the helix (0 for a flat spiral).
- turns : float | None¶
Number of turns (positive = right-handed).
- angle : float | None¶
Helix angle in degrees (measured at the base radius).
- radius : float | None¶
Radius for a constant-radius helix.
- radius1 : float | None¶
Bottom radius.
- radius2 : float | None¶
Top radius.
- diameter : float | None¶
Diameter for a constant-radius helix.
- diameter1 : float | None¶
Bottom diameter.
- diameter2 : float | None¶
Top diameter.
- Return type:¶
Path3D
Examples
A 2.5-turn helix drawn as a tube:
from pybosl2 import Path3D Path3D.helix(turns=2.5, height=100, radius=30).stroke(width=3).show()Loading 3-D preview…
- property array : ndarray
The points as an (N, 3) numpy array, for doing your own vectorised maths.
- Returns:¶
An (N, 3) float64 numpy array.
- property to_list : list[list[float]]
The points as a list of
[x, y, z]plain-Python-float triples.- Returns:¶
A list of
[x, y, z]triples.
-
classmethod from_list(lst, closed=
False)[source] Create a Path3D from a plain list of
[x, y, z]coordinate triples.
-
segment_lengths(closed=
None)[source] Length of each segment of the path, as an ndarray.
An open path of N points has N-1 segments; a closed one has N, the extra being the closing segment from the last point back to the first.
The short cases follow from that rule rather than being special-cased away:
An EMPTY path has no segments either way – there are no points to join.
A SINGLE point is where open and closed differ. Open, it has no segments. Closed, the closing segment joins the point to ITSELF, so the result is one segment of length 0 – not zero segments:
Path3D([[1, 2, 3]]).segment_lengths() # array([]) Path3D([[1, 2, 3]], closed=True).segment_lengths() # array([0.])This keeps
len(segment_lengths(closed=True)) == len(path), whichtangent_array()relies on when it samples the non-uniform derivative.
-
length_fractions(closed=
None)[source] Distance fraction of each point in the path (0 at start, 1 at end).
-
tangents(closed=
None, uniform=True)[source] Return the normalized tangent vector at each point of the path (BOSL2 path_tangents).
There is always exactly one tangent per path point – not one per segment. A path of fewer than two points has nothing to differentiate, so each of its points gets
+xby convention; seetangent_array().
-
normals(tangents=
None, closed=None)[source] Return normal vector (perpendicular to tangent, in the plane of the curve) at each point.
For 2-D paths this is a 90-degree rotation of the tangent. For 3-D paths it is the principal normal estimated via the triple-product cross.
-
cut(cutdist, closed=
None)[source] Cut path into subpaths at the given ascending list of distances (or a single distance).
- Parameters:¶
- Returns:¶
A list of
Path3Dsubpaths.- Raises:¶
AssertionError – If the first cut distance is not positive or the last cut distance exceeds the path length.
- Return type:¶
list[‘Path3D’]
Examples
Splitting a path into two segments and stroking each:
from pybosl2 import Path3D path3d = Path3D([[0, 0, 0], [30, 0, 0], [30, 20, 0], [0, 20, 0]]) pieces = path3d.cut(15) pieces[0].stroke(width=1).show()Loading 3-D preview…
-
cut_points(cutdist, closed=
None, direction=False)[source] Cut path at given distance(s) from start.
Returns a list of
CutPointentries, with optional direction and normal data when direction is True.
-
cut_points_recurse(dists, closed=
False)[source] Walk the path accumulating distance until each cut distance is reached.
-
cut_single(dist, closed=
False, ind=0, eps=1e-07)[source] Find the single cut point at distance dist from segment ind.
-
cuts_path_normals(cuts, closed=
False)[source] Compute normal vectors at each cut point from the local path geometry.
For each cut point, the normal is derived from the local plane of three consecutive path points. When the points are collinear or a plane cannot be determined, a perpendicular vector in the XY plane is used instead.
-
plane(ind, i, closed=
False)[source] Find the local plane defined by point ind, ind-1, and the nearest non-collinear point.
-
cuts_dir(cuts, closed=
False, eps=0.01)[source] Compute direction vectors at each cut point (blended from adjacent segments).
-
subdivide_path(points=
None, points_per_segment=None, maxlen=None, exact=True, closed=None, method=SubdivideMethod.LENGTH)[source] Subdivide the path into evenly spaced points.
- Parameters:¶
- points : int | None¶
Target total number of points.
- points_per_segment : Sequence[int] | None¶
Number of points to add to each segment index.
- maxlen : float | None¶
Maximum allowed segment length.
- exact : bool¶
If False, favor uniform sampling — point count may differ.
- closed : bool | None¶
Override the instance’s closed flag.
- method : SubdivideMethod¶
LENGTH(uniform) orSEGMENT(per segment).
- Returns:¶
A new
Path3Dwith the subdivided points.- Raises:¶
AssertionError – If more than one of points, points_per_segment, and maxlen is given, or if points_per_segment is given without
SEGMENTmethod.- Return type:¶
Path3D
Examples
Subdividing a helix into 200 evenly spaced points and stroking it:
from pybosl2.path3d import Path3D coil = Path3D.helix(turns=3, height=60, radius=20).subdivide_path(points=200) coil.stroke(width=4).show()Loading 3-D preview…
-
resample_path(num_copies=
None, spacing=None, closed=None)[source] Uniformly resample path to num_copies points, or to a spacing near spacing.
- Parameters:¶
- Returns:¶
A new
Path3Dwith the uniformly resampled points.- Raises:¶
AssertionError – If both or neither of num_copies and spacing are given.
- Return type:¶
Path3D
Examples
Resampling a helix to 120 evenly spaced points:
from pybosl2.path3d import Path3D coil = Path3D.helix(turns=3, height=60, radius=20).resample_path(num_copies=120) coil.stroke(width=4).show()Loading 3-D preview…
-
select(s1, u1, s2, u2, closed=
None)[source] Extract a portion of the path from one segment to another.
Returns the sub-path starting at the u1 fraction of segment s1 and ending at the u2 fraction of segment s2. Segments indices out of range are clamped, and partial endpoint fractions include the interpolated point.
Examples
Selecting the middle portion of a 3-D path:
from pybosl2 import Path3D path3d = Path3D([[0, 0, 0], [30, 0, 0], [30, 20, 0], [0, 20, 0]]) mid = path3d.select(0, 0.5, 2, 0.5) mid.stroke(width=1).show()Loading 3-D preview…
- bounds()[source]
Compute the axis-aligned bounding box with pre-computed width, length, and height.
- perimeter()[source]
Total length along the path.
- is_closed()[source]
Check whether the first and last points of the path coincide.
- close()[source]
Append the start point if the path is not already closed.
Returns a new Path3D with the first point appended to the end, making it a closed loop. Has no effect if already closed.
Examples
Closing an open path into a loop:
from pybosl2 import Path3D path3d = Path3D([[0, 0, 0], [30, 0, 0], [30, 20, 0]], closed=False) loop = path3d.close() loop.stroke(width=1).show()Loading 3-D preview…
- cleanup()[source]
Drop a duplicate closing point if present.
If the first and last points coincide this returns a new Path3D with the duplicate removed, turning the path into an open one.
Examples
Converting a closed loop to an open path:
from pybosl2 import Path3D path3d = Path3D([[0, 0, 0], [30, 0, 0], [30, 20, 0], [0, 0, 0]]) result = path3d.cleanup() result.stroke(width=1).show()Loading 3-D preview…
- reverse()[source]
Return the same path wound in the opposite direction.
Returns a new Path3D with all points in reverse order.
Examples
Reversing the direction of a 3-D path:
from pybosl2 import Path3D path3d = Path3D([[0, 0, 0], [30, 0, 0], [30, 20, 0], [0, 20, 0]]) result = path3d.reverse() result.stroke(width=1).show()Loading 3-D preview…
-
merge_collinear(closed=
None, eps=1e-09)[source] Remove sequential collinear points and return a new path.
Examples
Removing a redundant middle point from a straight segment:
from pybosl2 import Path3D path3d = Path3D([[0, 0, 0], [15, 0, 0], [30, 0, 0], [30, 20, 0], [0, 20, 0]]) result = path3d.merge_collinear() result.stroke(width=1).show()Loading 3-D preview…
-
deduplicate(closed=
None, eps=1e-09)[source] Remove duplicate consecutive points and return a new path.
Examples
Cleaning up a path with repeated consecutive points:
from pybosl2 import Path3D path3d = Path3D([[0, 0, 0], [30, 0, 0], [30, 0, 0], [30, 20, 0], [0, 20, 0]]) result = path3d.deduplicate() result.stroke(width=1).show()Loading 3-D preview…
- deduplicated()[source]
Drop consecutive repeated points.
-
subdivide(num_copies=
None, refine=None, maxlen=None, exact=True, closed=None)[source] Insert points along the path.
Give exactly one of num_copies, refine or maxlen.
- Parameters:¶
- num_copies : int | None¶
Target total number of points.
- refine : float | None¶
Multiply the current point count by this.
- maxlen : float | None¶
Cap on the spacing between points.
- exact : bool¶
Hit the target count exactly rather than approximately.
- closed : bool | None¶
Override the instance’s closed flag.
- Returns:¶
A new
Path3Dwith additional interpolated points.- Return type:¶
Path3D
Examples
Subdividing a 3-D path using the num_copies parameter:
from pybosl2 import Path3D path3d = Path3D([[0, 0, 0], [30, 0, 0], [30, 20, 0], [0, 20, 0]]) result = path3d.subdivide(num_copies=100) result.stroke(width=1).show()Loading 3-D preview…
-
resample(num_copies=
None, spacing=None, closed=None)[source] Resample to evenly spaced points.
Give exactly one of num_copies or spacing.
Examples
Resampling a 3-D path to 50 evenly spaced points:
from pybosl2 import Path3D path3d = Path3D([[0, 0, 0], [30, 0, 0], [30, 20, 0], [0, 20, 0]]) result = path3d.resample(num_copies=50) result.stroke(width=1).show()Loading 3-D preview…
- translate(v)[source]
Translate every point by v (a shorter vector pads with zeros).
- Parameters:¶
- v : Sequence[float]¶
A 3-D translation vector
[dx, dy, dz]; shorter vectors pad with zeros.
- Returns:¶
A new translated
Path3D.- Return type:¶
Path3D
Examples
from pybosl2 import Path3D path3d = Path3D([[0, 0, 0], [30, 0, 0], [30, 20, 0], [0, 20, 0]]) result = path3d.translate([10, 5, 15]) result.stroke(width=2).show()Loading 3-D preview…
- move(v)
Translate every point by v (a shorter vector pads with zeros).
- Parameters:¶
- v : Sequence[float]¶
A 3-D translation vector
[dx, dy, dz]; shorter vectors pad with zeros.
- Returns:¶
A new translated
Path3D.- Return type:¶
Path3D
Examples
from pybosl2 import Path3D path3d = Path3D([[0, 0, 0], [30, 0, 0], [30, 20, 0], [0, 20, 0]]) result = path3d.translate([10, 5, 15]) result.stroke(width=2).show()Loading 3-D preview…
- scale(v)[source]
Scale every point by a scalar or a per-axis
[sx, sy, sz]factor.- Parameters:¶
- v : float | Sequence[float]¶
A uniform scalar or a per-axis
[sx, sy, sz]scale factor.
- Returns:¶
A new scaled
Path3D.- Return type:¶
Path3D
Examples
from pybosl2 import Path3D path3d = Path3D([[0, 0, 0], [30, 0, 0], [30, 20, 0], [0, 20, 0]]) result = path3d.scale(2) result.stroke(width=2).show()Loading 3-D preview…
-
rotate(a, v=
None)[source] Rotate the points.
rotate(angle, axis)spins about axis;rotate(angle)about +Z;rotate([rx, ry, rz])applies the OpenSCAD X-then-Y-then-Z Euler rotation.Examples
from pybosl2 import Path3D path3d = Path3D([[0, 0, 0], [30, 0, 0], [30, 20, 0], [0, 20, 0]]) result = path3d.rotate(45, v=[0, 0, 1]) result.stroke(width=2).show()Loading 3-D preview…
-
rot(a, v=
None) Rotate the points.
rotate(angle, axis)spins about axis;rotate(angle)about +Z;rotate([rx, ry, rz])applies the OpenSCAD X-then-Y-then-Z Euler rotation.Examples
from pybosl2 import Path3D path3d = Path3D([[0, 0, 0], [30, 0, 0], [30, 20, 0], [0, 20, 0]]) result = path3d.rotate(45, v=[0, 0, 1]) result.stroke(width=2).show()Loading 3-D preview…
- mirror(v)[source]
Reflect every point across the plane through the origin with normal v.
- Parameters:¶
- v : Sequence[float]¶
The normal vector of the reflection plane through the origin.
- Returns:¶
A new mirrored
Path3D.- Return type:¶
Path3D
Examples
from pybosl2 import Path3D path3d = Path3D([[0, 0, 0], [30, 0, 0], [30, 20, 0], [0, 20, 0]]) result = path3d.mirror([1, 0, 0]) result.stroke(width=2).show()Loading 3-D preview…
- fwd(y)
Translate by y along -Y (BOSL2 fwd()).
- path2d()[source]
Drop the Z coordinate, giving a 2-D
Path2D(the XY projection).Useful when a 3-D sweep path needs 2-D operations like
contains()orpolygon().Examples
from pybosl2.path3d import Path3D sweep_path = Path3D.helix(turns=3, height=60, radius=20) flat = sweep_path.path2d() flat.stroke(width=2).linear_extrude(height=1).show()Loading 3-D preview…- Return type:¶
Path2D
-
stroke(width=
1, closed=None, endcaps=CapType.ROUND, endcap1=None, endcap2=None, joints=CapType.ROUND)[source] Render this 3-D path as a solid tube.
Converts the path into a tubular 3-D solid with the given width, using rounded endcaps and joints by default.
- Parameters:¶
- width : float¶
Thickness of the tube.
- closed : bool | None¶
Override the instance’s closed flag; uses
self.closedby default.- endcaps : CapType | CapSpec¶
Cap style for both ends (unused when explicit endcaps are given).
- endcap1 : CapType | CapSpec | None¶
Cap style for the start of the path.
- endcap2 : CapType | CapSpec | None¶
Cap style for the end of the path.
- joints : CapType | CapSpec¶
Joint style between segments (unused when explicit endcaps are given).
- Returns:¶
A
Bosl2Solidrepresenting the tubular stroke.- Return type:¶
Bosl2Solid
Examples
A simple path stroked as a tube:
from pybosl2 import Path3D path3d = Path3D([[0, 0, 0], [30, 0, 0], [30, 20, 10], [0, 20, 0]]) path3d.stroke(width=2).show()Loading 3-D preview…
-
dashed_stroke(dashpat=
None, closed=None, fit=True, mindash=0.5)[source] Render this 3-D path as dashed tube segments, unioned together.
Breaks the path into individual solid dashes based on the given dash pattern.
- Parameters:¶
- dashpat : Sequence[float] | None¶
Alternating dash/gap lengths. Defaults to
[3, 2](3-unit dashes, 2-unit gaps) when None.- closed : bool | None¶
Override the instance’s closed flag; uses
self.closedby default.- fit : bool¶
If True, adjust the pattern so dashes fit evenly along the path.
- mindash : float¶
Minimum dash length when fit is True.
- Returns:¶
A
Bosl2Solidof unioned dash segments.- Return type:¶
Bosl2Solid
Examples
A dashed stroke along a 3-D path:
from pybosl2 import Path3D path3d = Path3D([[0, 0, 0], [30, 0, 0], [30, 20, 10], [0, 20, 0]]) path3d.dashed_stroke(dashpat=[5, 2]).show()Loading 3-D preview…