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

The colour to apply.

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:

ValueError – 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

pybosl2.paths.PathLike : TypeAlias = 'Path | Sequence[Sequence[float]] | NDArray[np.float64]'

Internal. The permissive form a body normalises from, never a public parameter type (SPEC C-7c, PLAN T-4a). A public parameter meaning an ordered set of points is typed Path2D, Path3D or Path and guarded with require_path() (SPEC C-7a).

This alias used to be documented as “anything an API that wants a polyline accepts”, and that sentence is how it reached 36 public signatures: the widest form read as the intended contract, so every new function copied it. A bare sequence carries no dimension, no open/closed flag and no winding, leaving each callee to re-derive all three – and they disagreed.

Normalise on the first line – np.asarray(x, dtype=float) or Path2D(x) – so the rest of the body works on one shape.

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

Bases: Enum

Method for subdividing a path.

LENGTH = 'length'
SEGMENT = 'segment'
pybosl2.paths.require_path(value, parameter, function, expect=None)[source]

Return value as a Path, refusing raw points and naming the wrapper (SPEC C-7a/b).

A bare sequence carries no dimension, no open/closed flag and no winding, so every function that accepted one had to re-derive all three – and they did not agree: the same list was a 2-D outline to one and a degenerate 3-D path to the next. Requiring the type moves that decision to the single place that can make it once, at construction.

Raw points are what a caller usually has – literals, a CSV, another library’s output – so the refusal names the wrapper to apply, picking Path2D or Path3D from the width of what was passed rather than making the caller work it out (SPEC C-7b).

Parameters:
value : object

the argument supplied for a polyline parameter.

parameter : str

the parameter’s name, so the message points at the argument that was wrong.

function : str

the function’s name, so it points at the call.

expect : type[Path] | None

the concrete type the parameter needs, when only one width will do – pass Path2D for a parameter typed Path2D, and leave it None only where the annotation is Path because both widths really are meant. A wrong width is not a type error a caller can see: Path2D and Path3D are siblings, so without this a Path3D satisfies a Path2D-typed parameter and flows into a formula that indexes columns 0 and 1 and drops z – a wrong answer rather than a refusal.

Returns:

value unchanged, when it is already a Path of the expected width.

Raises:

Bosl2ValueError – If value is not a Path, or is not an expect.

Return type:

Path

pybosl2.paths.require_paths(values, parameter, function, expect=None)[source]

Return values as a list of Path, refusing raw points elementwise (SPEC C-7a).

The sequence form of require_path(). The index of the offending element is part of the message, because a list of profiles where only one is raw is the usual way to get here and saying only “profiles must be Paths” leaves the caller to find which.

Parameters:
values : object

the argument supplied for a sequence-of-polylines parameter.

parameter : str

the parameter’s name.

function : str

the function’s name.

expect : type[Path] | None

the concrete type each element needs, as require_path() takes it.

Returns:

The paths as a list, unchanged.

Raises:

Bosl2ValueError – If values is not a sequence, or any element is not an expect.

Return type:

list[Path]

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

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:

ValueError – 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

Tangent directions, one per point, instead of deriving them.

closed : bool | None

Join the last point back to the first.

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]

Distance from the corner at which to cut.

closed : bool | None

Join the last point back to the first.

direction : bool

Which way to measure or travel.

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]

Where along the path to cut.

closed : bool

Join the last point back to the first.

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, refine=None)[source]

Insert points along the path.

Give one of points, refine or maxlen.

Parameters:
points : int | None

Target total number of points.

points_per_segment : Sequence[int] | None

Points to add per segment; needs method=SubdivideMethod.SEGMENT.

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.

method : SubdivideMethod

How to distribute the new points.

refine : float | None

Multiply the current point count by this, instead of giving points.

Returns:

A new Path2D with additional interpolated points.

Raises:

Bosl2ValueError – if points_per_segment is given without the segment method.

Return type:

Path2D

Examples

from pybosl2 import Path2D

pts = Path2D([[0, 0], [80, 0], [80, 60], [0, 60]])
result = pts.subdivide_path(points=24)
result.stroke(width=1).linear_extrude(height=4).show()
Loading 3-D preview…

⬇ Download STL mesh

resample_path(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_path(num_copies=20)
sampled.stroke(width=1).linear_extrude(height=2).show()
Loading 3-D preview…

⬇ Download STL mesh

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

Parameter at the start of the first segment.

u1 : float

Parameter along the first curve.

s2 : int

Parameter at the start of the second.

u2 : float

Parameter along the second.

closed : bool | None

Join the last point back to the first.

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). Omitted, the ambient use_defaults(fn=...) value applies; fn=0 opts back out to fa/fs.

fa : float | None

Minimum angle in degrees for circle fragments. Omitted, the ambient use_defaults(fa=...) value applies.

fs : float | None

Minimum size for circle fragments. Omitted, the ambient use_defaults(fs=...) value applies.

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:

ValueError – 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

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

rotate(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

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

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, on whichever backend is active.

Builds through the backend-neutral façade (SPEC A-10), so the same call produces a CSG outline by default and an SDF one inside use_backend("sdf"). It used to reach the CSG module directly and hand back a Bosl2Shape2D whichever backend was selected, which is the silent cross-backend result SPEC A-6 exists to prevent.

Returns:

A Flat, so the result chains straight into the 2-D operators (.fill(), .hull(), .offset()) and the extruders (.linear_extrude(...)).

Return type:

Flat

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:

Flat

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 Flat with its holes closed.

Return type:

Flat

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=None, fn=None, fa=None, fs=None)[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 | None

Segments per quadrant for round joins; resolved from the radius and the ambient facet controls when omitted.

fn : int | None

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

fa : float | None

Minimum fragment angle for the round joins. Omitted, the ambient use_defaults(fa=...) value applies.

fs : float | None

Minimum fragment size for the round joins. Omitted, the ambient use_defaults(fs=...) value applies.

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. Omitted, the ambient use_defaults(fn=...) value applies; fn=0 opts back out to fa/fs.

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. Omitted, the ambient use_defaults(fn=...) value applies; fn=0 opts back out to fa/fs.

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). Omitted, the ambient use_defaults(res=...) value applies.

fn : int | None

Arc smoothness override (CSG only). Omitted, the ambient use_defaults(fn=...) value applies; fn=0 opts back out to fa/fs.

fa : float | None

Arc smoothness override (CSG only). Omitted, the ambient use_defaults(fa=...) value applies.

fs : float | None

Arc smoothness override (CSG only). Omitted, the ambient use_defaults(fs=...) value applies.

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. Omitted, the ambient use_defaults(fn=...) value applies; fn=0 opts back out to fa/fs.

fa : float | None

Arc smoothness override. Omitted, the ambient use_defaults(fa=...) value applies.

fs : float | None

Arc smoothness override. Omitted, the ambient use_defaults(fs=...) value applies.

Returns:

A Bosl2Solid.

Returns:

The revolved solid, built by whichever backend is active. A revolve is exact in a distance field – the solid’s field is this profile’s own field read at (hypot(x, y), z) – so unlike most 2-D work it is not CSG-only.

Return type:

Solid

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 filled stroked outline (SPEC S-23).

A stroke is the area the pen covers, so it comes back as a Region – outlines-with-holes – which is what the rest of the family already returned (dashed_stroke(), stroke()) and what the 3-D twin returns the solid equivalent of. It used to hand back a Path2D, which described the boundary rather than the mark and left one member of one family answering a different kind of thing from the other three.

Parameters:
width : float

Stroke width.

closed : bool | None

Treat the path as closed (default: the path’s own flag).

endcaps : CapType | CapSpec | None

Cap style for both ends (default: round).

endcap1 : CapType | CapSpec | None

Cap style for the first end, overriding endcaps.

endcap2 : CapType | CapSpec | None

Cap style for the last end, overriding endcaps.

joints : CapType | CapSpec

Style for the corners between segments (default: round).

Returns:

The stroked area as a Region.

Return type:

Region

Examples

from pybosl2 import Path2D

trace = Path2D([[0, 0], [30, 0], [30, 20]])
trace.stroke(width=3).linear_extrude(height=2).show()
Loading 3-D preview…

⬇ Download STL mesh

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

The dash pattern, as alternating on and off lengths.

closed : bool | None

Join the last point back to the first.

fit : bool

Stretch the pattern so a whole number of dashes fits the path.

mindash : float

Shortest dash to keep; anything shorter is dropped.

Return type:

Region

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

All self-intersection points of the path.

Parameters:
closed : bool | None

Join the last point back to the first.

eps : float

Tolerance for the comparison.

Returns:

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

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

Examples

from pybosl2 import Path2D

pts = Path2D([[0, 0], [20, 0], [20, 0], [40, 0], [40, 30], [40, 30], [80, 60]])
result = pts.deduplicate()
result.stroke(width=2).linear_extrude(height=4).show()
Loading 3-D preview…

⬇ Download STL mesh

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

static polygon_area(poly, signed=False)[source]

Area of a 2-D polygon (shoelace formula).

Parameters:
poly : Path2D

The outline, as a Path2D – the shoelace formula is planar (SPEC C-7a).

signed : bool

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

Raises:

Bosl2ValueError – If poly is not a Path2D.

Return type:

float

static point_in_polygon(point, poly, nonzero=False, eps=1e-09)[source]

Whether point is inside 2-D polygon poly: 1 inside, -1 outside, 0 boundary.

Parameters:
point : Point

The Point to test.

poly : Path2D

The Path2D defining the polygon boundary.

nonzero : bool

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

eps : float

Epsilon for numerical comparisons.

Return type:

int

static is_closed_path(path, eps=1e-09)[source]

Return True if the first and last points of path coincide.

Takes a Path rather than a concrete width: whether the ends meet is the same question in 2-D and 3-D (SPEC C-7a).

Parameters:
path : Path

A path to check for closure.

eps : float

Epsilon for numerical comparison.

Raises:

Bosl2ValueError – If path is not a Path.

Return type:

bool

static close_path(path, eps=1e-09)[source]

Append the start point to path if it isn’t already closed.

Parameters:
path : Path

A path to close, at either width.

eps : float

Epsilon for numerical comparison.

Raises:

Bosl2ValueError – If path is not a Path.

Return type:

list[Any]

static cleanup_path(path, eps=1e-09)[source]

Drop the last point of path if it coincides with the first.

Parameters:
path : Path

A path to clean up, at either width.

eps : float

Epsilon for numerical comparison.

Raises:

Bosl2ValueError – If path is not a Path.

Return type:

list[Any]

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). Omitted, the ambient use_defaults(fn=...) value applies; fn=0 opts back out to fa/fs.

fs : float

Maximum facet size. Omitted, the ambient use_defaults(fs=...) value applies.

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

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_path(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, fn=None, fa=None, fs=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.

fn : int | None

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

fa : float | None

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

fs : float | None

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

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:

ValueError – 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:

ValueError – 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, refine=None)[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).

refine : float | None

Multiply the current point count by this, instead of giving points.

Returns:

A new Path3D with the subdivided points.

Raises:

ValueError – 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:

ValueError – 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

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

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

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

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