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: object

A point along a path where it was cut, with the index of the next segment.

Returned by cut_points() and related methods. When requested with direction=True, the direction and normal attributes are populated; otherwise they are None.

Parameters:
point : Point

next_index : int

direction : ndarray | None

normal : ndarray | None

point : Point
next_index : int
direction : ndarray | None
normal : ndarray | None
property is_directed : bool

True if direction and normal vectors are present.

class pybosl2.paths.Path(points=None, closed=False)[source]

Bases: ABC

Dimension-agnostic numeric path operations shared by Path2D and Path3D.

Abstract base class. Subclasses must provide _points (numpy.ndarray) and closed (bool).

Parameters:
points : Sequence[Sequence[float]] | None

closed : bool

Return type:

Self

closed : bool
color(c)[source]

Return a copy of this path with the given Color.

Parameters:
c : Color

Return type:

Self

abstractmethod copy()[source]

Return a shallow copy of this path.

Return type:

Self

property array : ndarray

The points as an (N, D) numpy array.

abstractmethod segment_lengths(closed=None)[source]

Length of each segment of the path, as an ndarray.

Parameters:
closed : bool | None

Override the instance’s closed flag; uses self.closed by default.

Returns:

An ndarray of segment lengths.

Return type:

NDArray[np.float64]

abstractmethod perimeter()[source]

Total length along the path.

Returns:

The total path length as a float.

Return type:

float

abstractmethod length_fractions(closed=None)[source]

Distance fraction of each point in the path (0 at start, 1 at end).

Parameters:
closed : bool | None

Override the instance’s closed flag; uses self.closed by default.

Returns:

An ndarray of cumulative length fractions, from 0 to 1.

Return type:

NDArray[np.float64]

abstractmethod closest_point(pt, closed=None)[source]

Return the closest point on the path to pt.

Parameters:
pt : Point | Sequence[float]

The query point as Point or [x, y, z].

closed : bool | None

Override the instance’s closed flag; uses self.closed by default.

Returns:

A Point of the closest point on the path.

Return type:

Point

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.

Parameters:
closed : bool | None

Override the instance’s closed flag; uses self.closed by default.

uniform : bool

If True, estimate the derivative assuming equally spaced points. If False, sample it at the true (non-uniform) segment lengths, which tracks a path whose points are unevenly spaced far better.

Returns:

An ndarray of unit tangent vectors, one per path point.

Raises:

AssertionError – If two adjacent points coincide, leaving a zero-length tangent.

Return type:

NDArray[np.float64]

abstractmethod tangents(closed=None, uniform=True)[source]

Return normalized tangent vector at each point of the path, as an ndarray.

Parameters:
closed : bool | None

Override the instance’s closed flag; uses self.closed by default.

uniform : bool

If True, use uniform parameter spacing; if False, weight by segment lengths.

Returns:

An ndarray of unit tangent vectors, one per path point.

Return type:

list[Point]

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.

Parameters:
tangents : list[Point] | None

Optional pre-computed tangent vectors; computed automatically if None.

closed : bool | None

Override the instance’s closed flag; uses self.closed by default.

Returns:

An ndarray of unit normal vectors, one per path point.

Return type:

list[Point]

abstractmethod curvature(closed=None)[source]

Numeric curvature estimate of the path at each point, as an ndarray.

Parameters:
closed : bool | None

Override the instance’s closed flag; uses self.closed by default.

Returns:

An ndarray of curvature values, one per path point.

Return type:

NDArray[np.float64]

abstractmethod torsion(closed=None)[source]

Numeric torsion estimate of the path at each point, as an ndarray.

Parameters:
closed : bool | None

Override the instance’s closed flag; uses self.closed by default.

Returns:

An ndarray of torsion values, one per path point.

Return type:

NDArray[np.float64]

abstractmethod cut(cutdist, closed=None)[source]

Cut path into subpaths at the given ascending list of distances (or a single distance).

Parameters:
cutdist : float | Sequence[float]

A single distance or a list of ascending distances from the start.

closed : bool | None

Override the instance’s closed flag; uses self.closed by default.

Returns:

A list of subpath point lists.

Return type:

list[Any]

abstractmethod cut_getpaths(cutlist, closed)[source]

Reconstruct sub-paths from the output of cut_points().

Parameters:
cutlist : list[CutPoint]

Output from cut_points(), a list of CutPoint entries.

closed : bool

Whether the path is closed.

Returns:

A list of subpath point lists.

Return type:

Sequence[Path]

abstractmethod cut_points(cutdist, closed=None, direction=False)[source]

Cut path at given distance(s) from start.

Returns a list of CutPoint entries (or :class:`` if direction is True).

Parameters:
cutdist : float | Sequence[float]

A single distance or a list of ascending distances from the start.

closed : bool | None

Override the instance’s closed flag; uses self.closed by default.

direction : bool

If True, also include direction and normal at each cut point.

Returns:

class:`` entries, one per cut distance.

Return type:

A list of CutPoint or

abstractmethod cut_points_recurse(dists, closed=False)[source]

Walk the path accumulating distance until each cut distance is reached.

Parameters:
dists : Sequence[float]

Ordered list of distances from the start at which to cut.

closed : bool

Whether the path is closed.

Returns:

A list of CutPoint entries, one per cut distance.

Return type:

list[CutPoint]

abstractmethod cut_single(dist, closed=False, ind=0, eps=1e-07)[source]

Find the single cut point at distance dist from segment ind.

Parameters:
dist : float

Distance along the path from the given segment index.

closed : bool

Whether the path is closed.

ind : int

The segment index to start searching from.

eps : float

Epsilon for distance comparison.

Returns:

A CutPoint with the cut point and its next segment index.

Return type:

CutPoint

abstractmethod cuts_path_normals(cuts, closed=False)[source]

Compute normals at each cut point from the path geometry.

Parameters:
cuts : list[CutPoint]

List of cut entries from cut_points().

closed : bool

Whether the path is closed.

Returns:

A list of normal vectors, one per cut point.

Return type:

list[Point]

abstractmethod plane(ind, i, closed=False)[source]

Find the local plane defined by point ind, ind-1, and the nearest non-collinear point.

Parameters:
ind : int

Index of the first point defining the plane.

i : int

Index of the search start for the third non-collinear point.

closed : bool

Whether the path is closed.

Returns:

A 2x3 ndarray of two basis vectors defining the local plane, or None if no non-collinear point is found.

Return type:

list[Point]

abstractmethod cuts_dir(cuts, closed=False, eps=0.01)[source]

Compute direction vectors at each cut point (blended from adjacent segments).

Parameters:
cuts : list[CutPoint]

List of cut entries from cut_points().

closed : bool

Whether the path is closed.

eps : float

Epsilon for numerical comparisons.

Returns:

A list of direction vectors, one per cut point.

Return type:

list[Point]

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) or SEGMENT (per segment).

Returns:

A new path with the subdivided points.

Return type:

Path

abstractmethod resample_path(num_copies=None, spacing=None, closed=None)[source]

Uniformly resample path to num_copies points, or to a spacing near spacing.

Parameters:
num_copies : int | None

Target number of points.

spacing : float | None

Approximate spacing between points.

closed : bool | None

Override the instance’s closed flag; uses self.closed by default.

Returns:

A list of uniformly resampled path points.

Return type:

Path

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.

Parameters:
s1 : int

Starting segment index.

u1 : float

Fraction along segment s1 (0 to 1).

s2 : int

Ending segment index.

u2 : float

Fraction along segment s2 (0 to 1).

closed : bool | None

Override the instance’s closed flag; uses self.closed by default.

Returns:

A list of points representing the selected portion of the path.

Return type:

Path

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 Path2D for 2-D strokes, Bosl2Solid for 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.

Parameters:
dashpat : Sequence[float] | None

Dash pattern [line_len, space_len, …].

closed : bool | None

Override the instance’s closed flag.

fit : bool

Scale the pattern to fit a whole number of repeats.

mindash : float

Drop a trailing dash shorter than this.

Returns:

A Region for 2-D, Bosl2Solid for 3-D.

Return type:

Any

abstractmethod merge_collinear(closed=None, eps=1e-09)[source]

Remove sequential collinear points and return a new path.

Parameters:
closed : bool | None

Override the instance’s closed flag.

eps : float

Epsilon for collinearity comparison.

Returns:

A new path with collinear points removed.

Return type:

Path

abstractmethod deduplicate(closed=None, eps=1e-09)[source]

Remove duplicate consecutive points and return a new path.

Parameters:
closed : bool | None

Override the instance’s closed flag.

eps : float

Epsilon for distance comparison.

Returns:

A new path with duplicate points removed.

Return type:

Path

class pybosl2.paths.SubdivideMethod(*values)[source]

Bases: Enum

Method for subdividing a path.

LENGTH = 'length'
SEGMENT = 'segment'

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, Roundable

A 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 native polygon()/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=True for 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 that polygon() and area() 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…

⬇ Download STL mesh

copy()[source]

Return a shallow copy of this path.

Return type:

Path2D

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.

Parameters:
lst : Sequence[Any]

A sequence of [x, y] coordinate pairs.

closed : bool

Whether the path is a closed polygon.

Returns:

A new Path2D instance.

Return type:

Path2D

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), which tangent_array() relies on when it samples the non-uniform derivative.

Parameters:
closed : bool | None

Override the instance’s closed flag; uses self.closed by default.

Returns:

An ndarray of segment lengths, one per segment.

Return type:

NDArray[np.float64]

length_fractions(closed=None)[source]

Distance fraction of each point in the path (0 at start, 1 at end).

Parameters:
closed : bool | None

Override the instance’s closed flag.

Returns:

An ndarray of cumulative length fractions, from 0 to 1.

Return type:

NDArray[np.float64]

closest_point(pt, closed=None)[source]

Return the closest point on the path to pt.

Uses Shapely projection for 2-D accuracy.

Parameters:
pt : Point | Sequence[float]

The query point as Point or [x, y].

closed : bool | None

Override the instance’s closed flag; uses self.closed by default.

Returns:

A Point of the closest point on the path.

Return type:

Point

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 +x by convention; see tangent_array().

Parameters:
closed : bool | None

Override the instance’s closed flag; uses self.closed by default.

uniform : bool

If True, estimate the derivative assuming equally spaced points. If False, sample it at the true segment lengths, which follows an unevenly spaced path much more closely.

Returns:

A list of unit tangent vectors, one per path point.

Raises:

AssertionError – If two adjacent points coincide, leaving a zero-length tangent.

Return type:

list[Point]

normals(tangents=None, closed=None)[source]

Perpendicular unit normal at each point (90° rotation of tangent).

Parameters:
tangents : list[Point] | None

closed : bool | None

Return type:

list[Point]

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().

Parameters:
closed : bool | None

Override the instance’s closed flag; uses self.closed by default.

Returns:

An ndarray of curvature values, one per path point.

Return type:

NDArray[np.float64]

torsion(closed=None)[source]

Numeric torsion estimate (always 0 for 2-D paths).

Parameters:
closed : bool | None

Override the instance’s closed flag.

Returns:

An ndarray of torsion values (all zeros for 2-D).

Return type:

NDArray[np.float64]

cut(cutdist, closed=None)[source]

Cut path into subpaths at the given ascending list of distances (or a single distance).

Parameters:
cutdist : float | Sequence[float]

A single distance or a list of ascending distances from the start.

closed : bool | None

Override the instance’s closed flag; uses self.closed by default.

Returns:

A list of Path2D subpaths.

Return type:

list[Path2D]

cut_getpaths(cutlist, closed=False)[source]

Reconstruct sub-paths from the output of cut_points().

Parameters:
cutlist : list[CutPoint]

Output from cut_points(), a list of CutPoint entries.

closed : bool

Whether the path is closed.

Returns:

A list of Path2D subpaths.

Return type:

list[Path2D]

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.

Parameters:
cutdist : float | Sequence[float]

closed : bool | None

direction : bool

Return type:

list[CutPoint]

cut_points_recurse(dists, closed=False)[source]

Walk the path accumulating distance until each cut distance is reached.

Parameters:
dists : Sequence[float]

Ordered list of distances from the start at which to cut.

closed : bool

Whether the path is closed.

Returns:

A list of CutPoint entries, one per cut distance.

Return type:

list[CutPoint]

cut_single(dist, closed=False, ind=0, eps=1e-07)[source]

Find the single cut point at distance dist.

Parameters:
dist : float

Distance along the path from the given segment index.

closed : bool

Whether the path is closed.

ind : int

The segment index to start searching from.

eps : float

Epsilon for distance comparison.

Returns:

A CutPoint with the cut point and its next segment index.

Return type:

CutPoint

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.

Parameters:
cuts : list[CutPoint]

closed : bool

Return type:

list[Point]

plane(ind, i, closed=False)[source]

Local plane at path point (always XY for 2-D).

Parameters:
ind : int

Index of the first point defining the plane.

i : int

Index of the search start for the third non-collinear point.

closed : bool

Whether the path is closed.

Returns:

Two basis vectors defining the XY plane.

Return type:

list[Point]

cuts_dir(cuts, closed=False, eps=0.01)[source]

Compute direction vectors at each cut point.

Parameters:
cuts : list[CutPoint]

List of cut entries from cut_points().

closed : bool

Whether the path is closed.

eps : float

Epsilon for numerical comparisons.

Returns:

A list of Vector direction vectors, one per cut point.

Return type:

list[Point]

subdivide_path(points=None, points_per_segment=None, maxlen=None, exact=True, closed=None, method=SubdivideMethod.LENGTH)[source]

Subdivide the path into more points.

Parameters:
points : int | None

points_per_segment : Sequence[int] | None

maxlen : float | None

exact : bool

closed : bool | None

method : SubdivideMethod

Return type:

Path2D

resample_path(num_copies=None, spacing=None, closed=None)[source]

Resample the path with evenly spaced points.

Parameters:
num_copies : int | None

spacing : float | None

closed : bool | None

Return type:

Path2D

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.

Parameters:
s1 : int

u1 : float

s2 : int

u2 : float

closed : bool | None

Return type:

Path2D

bounds()[source]

Axis-aligned bounding box with pre-computed width and length.

Returns:

A Bounds2D named tuple.

Return type:

Bounds2D

Returns a Bounds2D named tuple with min_x, min_y, max_x, max_y, width, and length fields.

area(signed=False)[source]

Enclosed area; signed keeps the sign (negative == clockwise).

Parameters:
signed : bool

If True, preserve the sign so negative indicates clockwise winding.

Returns:

The enclosed area as a float.

Return type:

float

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.

Returns:

The total path length as a float.

Return type:

float

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.

is_simple()[source]

Return True if the path does not self-intersect.

Return type:

bool

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 Path2D holds a single outline; use a Region if 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 (delta offsets only), for callers like path_sweep2d() that need the two paths to correspond point-for-point (BOSL2 offset(..., 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…

⬇ Download STL mesh

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…

⬇ Download STL mesh

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…

⬇ Download STL mesh

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…

⬇ Download STL mesh

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…

⬇ Download STL mesh

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 Path2D with 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…

⬇ Download STL mesh

resample(num_copies=None, spacing=None, closed=None)[source]

Resample to evenly spaced points.

Give exactly one of num_copies or spacing.

Parameters:
num_copies : int | None

Target number of points.

spacing : float | None

Approximate spacing between points.

closed : bool | None

Override the instance’s closed flag.

Returns:

A new Path2D with uniformly resampled points.

Return type:

Path2D

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…

⬇ Download STL mesh

split_at_self_crossings(eps=1e-09)[source]

Split this 2-D path into subpaths wherever it crosses itself.

Parameters:
eps : float

Epsilon for numerical comparisons.

Returns:

A list of Path2D subpaths split at each self-crossing.

Return type:

list[Path2D]

polygon_parts(nonzero=False, eps=1e-09)[source]

Split a possibly self-intersecting polygon into non-intersecting simple polygons.

Parameters:
nonzero : bool

If True, use non-zero winding rule instead of even-odd.

eps : float

Epsilon for numerical comparisons.

Returns:

A list of non-intersecting simple Path2D polygon parts.

Return type:

list[Path2D]

translate(v)[source]

Translate every point by v (2-D; a 1-vector shifts X only).

Parameters:
v : Sequence[float]

A 2-D translation vector [dx, dy]; a 1-vector shifts X only.

Returns:

A new translated Path2D.

Return type:

Path2D

move(v)

Translate every point by v (2-D; a 1-vector shifts X only).

Parameters:
v : Sequence[float]

A 2-D translation vector [dx, dy]; a 1-vector shifts X only.

Returns:

A new translated Path2D.

Return type:

Path2D

rot(a)[source]

Rotate every point by a degrees about the origin (Z axis).

Parameters:
a : float

Rotation angle in degrees.

Returns:

A new rotated Path2D.

Return type:

Path2D

rotate(a)

Rotate every point by a degrees about the origin (Z axis).

Parameters:
a : float

Rotation angle in degrees.

Returns:

A new rotated Path2D.

Return type:

Path2D

mirror(v)[source]

Reflect every point across the line through the origin with normal v.

Parameters:
v : Sequence[float]

The normal vector of the reflection line through the origin.

Returns:

A new mirrored Path2D.

Return type:

Path2D

yflip(y=0.0)[source]

Reflect every point across the horizontal line Y=*y* (default: the X axis).

Parameters:
y : float

The Y coordinate of the horizontal reflection line.

Returns:

A new flipped Path2D.

Return type:

Path2D

right(x)[source]

Translate by x along +X.

Parameters:
x : float

Distance to translate along +X.

Returns:

A new Path2D shifted right.

Return type:

Path2D

left(x)[source]

Translate by x along -X.

Parameters:
x : float

Distance to translate along -X.

Returns:

A new Path2D shifted left.

Return type:

Path2D

back(y)[source]

Translate by y along +Y.

Parameters:
y : float

Distance to translate along +Y.

Returns:

A new Path2D shifted back.

Return type:

Path2D

forward(y)[source]

Translate by y along -Y (BOSL2 fwd()).

Parameters:
y : float

Distance to translate along -Y.

Returns:

A new Path2D shifted forward.

Return type:

Path2D

fwd(y)

Translate by y along -Y (BOSL2 fwd()).

Parameters:
y : float

Distance to translate along -Y.

Returns:

A new Path2D shifted forward.

Return type:

Path2D

to_region()[source]

Convert this path into a single-outline Region.

Returns a Region containing just this path as its only outline. Useful as a gateway to 2-D Boolean operations (union, intersection, difference) on polygons.

Return type:

Region

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 Bezier path through the given points.

Return type:

Bezier

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…

⬇ Download STL mesh

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:
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…

⬇ Download STL mesh

geometry()[source]

2-D geometry of this path.

The name Region also 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 that polygon() would leave as holes.

Returns:

A Bosl2Shape2D (csg backend only).

Return type:

Bosl2Shape2D

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’s minkowski() 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.

Parameters:
other : Path2D

A closed Path2D to sweep along this path.

Returns:

The Minkowski sum as a new closed Path2D.

Return type:

Path2D

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 corners

Set single_sided to True for 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.MITRE only).

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…

⬇ Download STL mesh

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…

⬇ Download STL mesh

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…

⬇ Download STL mesh

classmethod circle2d(radius=10, fn=64)[source]

Create a closed Path2D approximating a circle of radius.

Uses fn uniform segments around the origin.

Parameters:
radius : float

Circle radius.

fn : int

Number of polygon segments.

Returns:

A closed Path2D.

Return type:

Path2D

classmethod ellipse2d(rx=10, ry=5, fn=64)[source]

Create a closed Path2D approximating an ellipse.

Uses fn uniform parametric segments with semi‑axes rx and ry centred at the origin.

Parameters:
rx : float

Semi‑axis in the X direction.

ry : float

Semi‑axis in the Y direction.

fn : int

Number of polygon segments.

Returns:

A closed Path2D.

Return type:

Path2D

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.

Parameters:
others : Path2D | Region

The closed paths or regions to hull together.

Returns:

A single closed Path2D of the convex hull outline.

Return type:

Path2D

union(*others)[source]

Return the 2-D union of this closed path with others.

Converts all paths to Polygon objects, computes the Boolean union, and returns the result as a single closed Path2D. Requires all paths to be closed.

Parameters:
others : Path2D

One or more closed Path2D objects to union with.

Returns:

A new closed Path2D of the union outline.

Raises:

ValueError – If the result is not a single valid polygon.

Return type:

Path2D

intersection(*others)[source]

Return the 2-D intersection of this closed path with others.

Converts all paths to Polygon objects, computes the Boolean intersection, and returns the common area as a single closed Path2D.

Parameters:
others : Path2D

One or more closed Path2D objects to intersect with.

Returns:

A new closed Path2D of the intersection outline, or an empty Path2D if the result is empty.

Return type:

Path2D

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.

Parameters:
other : Path2D

A closed Path2D to subtract from this one.

Returns:

A new closed Path2D of the difference outline.

Raises:

ValueError – If the result is not a single valid polygon.

Return type:

Path2D

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.

Parameters:
other : Path2D

A closed Path2D to XOR with.

Returns:

A new closed Path2D of the XOR outline.

Return type:

Path2D

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 Bosl2Solid under the default CSG backend, a PyShape under use_backend("sdf"):

plate = Path2D(pts).linear_extrude(height=4)          # -> Bosl2Solid
with use_backend("sdf"):
    field = Path2D(pts).linear_extrude(height=4)      # -> PyShape

The extra options differ by backend, since each realizes the extrusion its own way: the CSG backend takes the native center/twist/scale/slices/convexity (see linear_extrude()); the SDF backend takes center plus rounding_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:

Solid

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…

⬇ Download STL mesh

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:
angle : float

The sweep angle in degrees (default 360 for a full revolution).

convexity : int | None

Rendering hint for self-overlapping cross-sections.

fn : int | None

Arc smoothness override.

fa : float | None

Arc smoothness override.

fs : float | None

Arc smoothness override.

Returns:

A Bosl2Solid.

Raises:
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.

Parameters:
size : float

Label size for the vertex indices.

vertices : bool

If False, show only the filled outline without labels.

Returns:

A Bosl2Solid.

Return type:

Any

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.

Parameters:
width : float

closed : bool | None

endcaps : CapType | CapSpec | None

endcap1 : CapType | CapSpec | None

endcap2 : CapType | CapSpec | None

joints : CapType | CapSpec

Return type:

Path2D

dashed_stroke(dashpat=None, closed=None, fit=True, mindash=0.5)[source]

Break this 2-D path into dashed polygon outlines.

Returns a Region of dash polygons.

Parameters:
dashpat : Sequence[float] | None

closed : bool | None

fit : bool

mindash : float

Return type:

Region

self_intersections(closed=None, eps=1e-09)[source]

All self-intersection points of the path.

Returns:

A list of SelfIntersection entries with .point, .seg1, .prop1, .seg2, and .prop2 fields.

Parameters:
closed : bool | None

eps : float

Return type:

list[SelfIntersection]

merge_collinear(closed=None, eps=1e-09)[source]

Remove sequential collinear points and return a new path.

Parameters:
closed : bool | None

Override the instance’s closed flag.

eps : float

Epsilon for collinearity comparison.

Returns:

A new Path2D with collinear points removed.

Return type:

Path2D

deduplicate(closed=None, eps=1e-09)[source]

Remove duplicate consecutive points and return a new path.

Parameters:
closed : bool | None

Override the instance’s closed flag.

eps : float

Epsilon for distance comparison.

Returns:

A new Path2D with duplicate points removed.

Return type:

Path2D

is_path_simple(closed=None, eps=1e-09)[source]

Return True if the 2D path has no self-intersections (repeated points are not intersections).

Parameters:
closed : bool | None

Override the instance’s closed flag; uses self.closed by default.

eps : float

Epsilon for numerical comparisons.

Return type:

bool

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). None or False leaves 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 Path2D with bezier-rounded corners.

Return type:

Path2D

class pybosl2.path2d.MinkowskiJoin(*values)[source]

Bases: Enum

Corner join style for Path2D.minkowski_sum_circle().

ROUND

Circular arc joins (default). Smooth, radiused corners.

MITRE

Sharp mitered joins, clipped at mitre_limit.

BEVEL

Flat 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, Roundable

A 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. Like Path2D, 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 of Path2D (polygon, area, offset, round_corners, point-in-polygon) are intentionally absent; use path2d() 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=True for 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…

⬇ Download STL mesh

copy()[source]

Return a shallow copy of this path.

Return type:

Path3D

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 or path_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 is height=0 with 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…

⬇ Download STL mesh

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.

Parameters:
lst : Sequence[Any]

A sequence of [x, y, z] coordinate triples.

closed : bool

Whether the path is a closed loop.

Returns:

A new Path3D instance.

Return type:

Path3D

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), which tangent_array() relies on when it samples the non-uniform derivative.

Parameters:
closed : bool | None

Override the instance’s closed flag; uses self.closed by default.

Returns:

An ndarray of segment lengths, one per segment.

Return type:

NDArray[np.float64]

length_fractions(closed=None)[source]

Distance fraction of each point in the path (0 at start, 1 at end).

Parameters:
closed : bool | None

Override the instance’s closed flag; uses self.closed by default.

Returns:

An ndarray of cumulative length fractions, from 0 to 1.

Return type:

NDArray[np.float64]

closest_point(pt, closed=None)[source]

Return the closest point on the path to pt.

Parameters:
pt : Point | Sequence[float]

The query point as Point or [x, y, z].

closed : bool | None

Override the instance’s closed flag; uses self.closed by default.

Returns:

A Point of the closest point on the path.

Return type:

Point

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 +x by convention; see tangent_array().

Parameters:
closed : bool | None

Override the instance’s closed flag; uses self.closed by default.

uniform : bool

If True, estimate the derivative assuming equally spaced points. If False, sample it at the true segment lengths, which follows an unevenly spaced path much more closely.

Returns:

A list of unit tangent vectors, one per path point.

Raises:

AssertionError – If two adjacent points coincide, leaving a zero-length tangent.

Return type:

list[Point]

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.

Parameters:
tangents : list[Point] | None

Optional pre-computed tangent vectors; computed automatically if None.

closed : bool | None

Override the instance’s closed flag; uses self.closed by default.

Returns:

A list of unit normal vectors, one per path point.

Return type:

list[Point]

curvature(closed=None)[source]

Numeric curvature estimate of the path at each point, as an ndarray.

Parameters:
closed : bool | None

Override the instance’s closed flag; uses self.closed by default.

Returns:

An ndarray of curvature values, one per path point.

Return type:

NDArray[np.float64]

torsion(closed=None)[source]

Numeric torsion estimate of the path at each point, as an ndarray.

Parameters:
closed : bool | None

Override the instance’s closed flag; uses self.closed by default.

Returns:

An ndarray of torsion values, one per path point.

Return type:

NDArray[np.float64]

cut(cutdist, closed=None)[source]

Cut path into subpaths at the given ascending list of distances (or a single distance).

Parameters:
cutdist : float | Sequence[float]

A single distance or a list of ascending distances from the start.

closed : bool | None

Override the instance’s closed flag; uses self.closed by default.

Returns:

A list of Path3D subpaths.

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…

⬇ Download STL mesh

cut_getpaths(cutlist, closed)[source]

Reconstruct sub-paths from the output of cut_points().

Parameters:
cutlist : list[CutPoint]

Output from cut_points(), a list of CutPoint entries.

closed : bool

Whether the path is closed.

Returns:

A list of Path3D subpaths.

Return type:

list[Path3D]

cut_points(cutdist, closed=None, direction=False)[source]

Cut path at given distance(s) from start.

Returns a list of CutPoint entries, with optional direction and normal data when direction is True.

Parameters:
cutdist : float | Sequence[float]

A single distance or a list of ascending distances from the start.

closed : bool | None

Override the instance’s closed flag; uses self.closed by default.

direction : bool

If True, also include direction and normal at each cut point.

Returns:

A list of CutPoint entries, one per cut distance.

Return type:

list[CutPoint]

cut_points_recurse(dists, closed=False)[source]

Walk the path accumulating distance until each cut distance is reached.

Parameters:
dists : Sequence[float]

Ordered list of distances from the start at which to cut.

closed : bool

Whether the path is closed.

Returns:

A list of CutPoint entries, one per cut distance.

Return type:

list[CutPoint]

cut_single(dist, closed=False, ind=0, eps=1e-07)[source]

Find the single cut point at distance dist from segment ind.

Parameters:
dist : float

Distance along the path from the given segment index.

closed : bool

Whether the path is closed.

ind : int

The segment index to start searching from.

eps : float

Epsilon for distance comparison.

Returns:

A CutPoint with the cut point and its next segment index.

Return type:

CutPoint

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.

Parameters:
cuts : list[CutPoint]

List of cut entries from cut_points().

closed : bool

Whether the path is closed.

Returns:

A list of Vector normal vectors, one per cut point.

Return type:

list[Point]

plane(ind, i, closed=False)[source]

Find the local plane defined by point ind, ind-1, and the nearest non-collinear point.

Parameters:
ind : int

Index of the first point defining the plane.

i : int

Index of the search start for the third non-collinear point.

closed : bool

Whether the path is closed.

Returns:

A list of two Vector basis vectors defining the local plane.

Return type:

list[Point]

cuts_dir(cuts, closed=False, eps=0.01)[source]

Compute direction vectors at each cut point (blended from adjacent segments).

Parameters:
cuts : list[CutPoint]

List of cut entries from cut_points().

closed : bool

Whether the path is closed.

eps : float

Epsilon for numerical comparisons.

Returns:

A list of Vector direction vectors, one per cut point.

Return type:

list[Point]

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) or SEGMENT (per segment).

Returns:

A new Path3D with 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 SEGMENT method.

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…

⬇ Download STL mesh

resample_path(num_copies=None, spacing=None, closed=None)[source]

Uniformly resample path to num_copies points, or to a spacing near spacing.

Parameters:
num_copies : int | None

Target number of points.

spacing : float | None

Approximate spacing between points.

closed : bool | None

Override the instance’s closed flag; uses self.closed by default.

Returns:

A new Path3D with 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…

⬇ Download STL mesh

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.

Parameters:
s1 : int

Starting segment index.

u1 : float

Fraction (0 to 1) along the starting segment.

s2 : int

Ending segment index.

u2 : float

Fraction (0 to 1) along the ending segment.

closed : bool | None

Override the instance’s closed flag; uses self.closed by default.

Returns:

A new Path3D containing the selected sub-path.

Return type:

Path3D

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…

⬇ Download STL mesh

bounds()[source]

Compute the axis-aligned bounding box with pre-computed width, length, and height.

Returns:

A Bounds3D enclosing all path points.

Return type:

Bounds3D

perimeter()[source]

Total length along the path.

Returns:

The total path length as a float.

Return type:

float

is_closed()[source]

Check whether the first and last points of the path coincide.

Returns:

True if the path endpoints are coincident, False otherwise.

Return type:

bool

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.

Returns:

A new Path3D guaranteed to form a closed loop.

Return type:

Path3D

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…

⬇ Download STL mesh

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.

Returns:

A new Path3D with the duplicate end point removed.

Return type:

Path3D

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…

⬇ Download STL mesh

reverse()[source]

Return the same path wound in the opposite direction.

Returns a new Path3D with all points in reverse order.

Returns:

A new Path3D with reversed point order.

Return type:

Path3D

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…

⬇ Download STL mesh

merge_collinear(closed=None, eps=1e-09)[source]

Remove sequential collinear points and return a new path.

Parameters:
closed : bool | None

Override the instance’s closed flag.

eps : float

Epsilon for collinearity comparison.

Returns:

A new Path3D with collinear points removed.

Return type:

Path3D

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…

⬇ Download STL mesh

deduplicate(closed=None, eps=1e-09)[source]

Remove duplicate consecutive points and return a new path.

Parameters:
closed : bool | None

Override the instance’s closed flag.

eps : float

Epsilon for distance comparison.

Returns:

A new Path3D with duplicate points removed.

Return type:

Path3D

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…

⬇ Download STL mesh

deduplicated()[source]

Drop consecutive repeated points.

Returns:

A new Path3D with duplicate points removed.

Return type:

Path3D

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 Path3D with 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…

⬇ Download STL mesh

resample(num_copies=None, spacing=None, closed=None)[source]

Resample to evenly spaced points.

Give exactly one of num_copies or spacing.

Parameters:
num_copies : int | None

Target number of points.

spacing : float | None

Approximate spacing between points.

closed : bool | None

Override the instance’s closed flag.

Returns:

A new Path3D with uniformly resampled points.

Return type:

Path3D

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…

⬇ Download STL mesh

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…

⬇ Download STL mesh

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…

⬇ Download STL mesh

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…

⬇ Download STL mesh

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.

Parameters:
a : float | Sequence[float]

A single angle in degrees, or [rx, ry, rz] Euler angles.

v : Sequence[float] | None

An optional rotation axis vector; if None and a is scalar, rotates about +Z.

Returns:

A new rotated 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.rotate(45, v=[0, 0, 1])
result.stroke(width=2).show()
Loading 3-D preview…

⬇ Download STL mesh

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.

Parameters:
a : float | Sequence[float]

A single angle in degrees, or [rx, ry, rz] Euler angles.

v : Sequence[float] | None

An optional rotation axis vector; if None and a is scalar, rotates about +Z.

Returns:

A new rotated 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.rotate(45, v=[0, 0, 1])
result.stroke(width=2).show()
Loading 3-D preview…

⬇ Download STL mesh

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…

⬇ Download STL mesh

right(x)[source]

Translate by x along +X.

Parameters:
x : float

Distance to translate along +X.

Returns:

A new Path3D shifted right.

Return type:

Path3D

left(x)[source]

Translate by x along -X.

Parameters:
x : float

Distance to translate along -X.

Returns:

A new Path3D shifted left.

Return type:

Path3D

back(y)[source]

Translate by y along +Y.

Parameters:
y : float

Distance to translate along +Y.

Returns:

A new Path3D shifted back.

Return type:

Path3D

forward(y)[source]

Translate by y along -Y (BOSL2 fwd()).

Parameters:
y : float

Distance to translate along -Y.

Returns:

A new Path3D shifted forward.

Return type:

Path3D

fwd(y)

Translate by y along -Y (BOSL2 fwd()).

Parameters:
y : float

Distance to translate along -Y.

Returns:

A new Path3D shifted forward.

Return type:

Path3D

up(z)[source]

Translate by z along +Z.

Parameters:
z : float

Distance to translate along +Z.

Returns:

A new Path3D shifted up.

Return type:

Path3D

down(z)[source]

Translate by z along -Z.

Parameters:
z : float

Distance to translate along -Z.

Returns:

A new Path3D shifted down.

Return type:

Path3D

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() or polygon().

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…

⬇ Download STL mesh

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.closed by 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 Bosl2Solid representing 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…

⬇ Download STL mesh

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.closed by 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 Bosl2Solid of 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…

⬇ Download STL mesh