kumiki.pathcsg¶
Flat import path
kumiki/__init__.py re-exports everything on this page via from kumiki import *, so every name below is also available directly as kumiki.PathSegment -- you do not need to import from the submodule path shown in the heading above.
kumiki.pathcsg
¶
Path-based CSG: a closed 2D boundary built from a sequence of segments (lines, circular arcs, and room for more curve types later), plus PathExtrusion, the CutCSG primitive that extrudes a Path along its local Z axis.
Path generalizes ConvexPolygonExtrusion's plain point list to boundaries that aren't convex -- a cabriole leg's silhouette, for instance, is concave at the ankle -- at the cost of a more expensive containment test (general ray-casting instead of a half-plane scan) and a more expensive cap-triangulation path (see decompose_path_into_convex_pieces). Side faces that come from a non-planar segment (e.g. ArcSegment) never register a CSGFeature -- PathExtrusion. find_all_features simply has nothing to emit for them, the same graceful- omission behavior Cylinder's lateral surface and ConvexPolygonSimpleLoft's tapered side faces already have today.
PathSegment
dataclass
¶
Bases: ABC
One piece of a closed Path, living in some local 2D plane. A Path is a list of these, connected end-to-start; PathSegment itself doesn't know about neighbors or CSG at all -- Path and PathExtrusion own that.
is_planar
abstractmethod
¶
True if extruding this segment produces a flat side face -- i.e. it's eligible to ever carry a plane/feature. False (ArcSegment) means PathExtrusion.find_all_features simply has nothing to emit for it when it's named -- not a special case that needs handling, just an absence.
Source code in kumiki/pathcsg.py
ray_crossings
abstractmethod
¶
x-coordinates where this segment crosses height y.
inclusive=False (the default) uses a half-open [lower_y, upper_y) convention on each of this segment's own monotonic branches, so a ray through a vertex shared by two segments is counted by exactly one of them -- this mode is for Path.contains_point_2d's parity test.
inclusive=True uses closed intervals instead, so it reliably returns the true crossing(s) even exactly at a segment's own endpoint -- this mode is for decompose_path_into_convex_pieces, which needs the actual geometric position at a band edge, not parity-test ownership.
Source code in kumiki/pathcsg.py
v_extrema
abstractmethod
¶
v_extrema() -> List[Numeric]
Extra y-values, strictly between this segment's own endpoints' y values, where its x(y) relation has a local turning point (e.g. an arc passing through its circle's own north/south pole) and so isn't single-valued across its full span. Empty for segments whose x(y) is already monotonic (e.g. any StraightSegment). Used as extra band-boundary breakpoints by decompose_path_into_convex_pieces.
Source code in kumiki/pathcsg.py
closest_point
abstractmethod
¶
outward_local_normal
abstractmethod
¶
2D outward normal at point, assumed to lie on this segment and
assumed the Path is CCW-wound (Path.is_valid() checks this).
bounds
abstractmethod
¶
tessellate
abstractmethod
¶
tessellate(tolerance: float) -> List[V2]
Points approximating this segment for MESHING ONLY -- never used by
the analytic methods above. Excludes start (shared with the
previous segment's end), includes end.
Source code in kumiki/pathcsg.py
tessellate_with_extra_breaks
abstractmethod
¶
Like tessellate(), but also forces a vertex at every y-value in
other_breaks that falls strictly inside this segment's own y-range
-- even for a StraightSegment, which tessellate() alone never subdivides.
This exists because decompose_path_into_convex_pieces's band sweep can cut a v-band boundary through the MIDDLE of some other segment's y-range too (e.g. one segment's own extremum still splits a plain straight wall segment that happens to span that same height) -- so a cap piece can end up with a seam vertex that the wall ring, built from plain tessellate(), never placed. Path.tessellate_for_mesh uses this (with every OTHER segment's own breakpoints passed in) so the wall ring and the cap pieces agree on every seam, not just curved ones -- without it, a straight wall edge crossing a foreign breakpoint is a T-junction against the cap, same failure mode sample_interior's grid-sharing fixes for curved edges specifically.
Source code in kumiki/pathcsg.py
sample_interior
abstractmethod
¶
Points approximating this segment strictly BETWEEN point_lo and point_hi (both assumed to already lie on this segment), excluding both endpoints, ordered starting near point_lo and ending near point_hi, for meshing only. Used by decompose_path_into_convex_pieces to keep a curved band-edge curved in the output piece instead of flattening it to a chord. Empty for a StraightSegment (a chord between two of its own points IS the segment).
Must draw from the exact same per-tolerance sample grid tessellate() uses (see ArcSegment._angle_grid), not independently re-subdivide [point_lo, point_hi] -- otherwise a cap piece's curved edge and the wall mesh's tessellation of that same physical arc land on different points, which is a T-junction merge_vertices() can't repair.
Source code in kumiki/pathcsg.py
reverse
abstractmethod
¶
reverse() -> PathSegment
This same segment, geometrically identical, but traversed from
end to start instead of start to end. Used by FancyPath.reversed()
to flip a whole loop's winding direction (e.g. to satisfy is_valid()'s
CCW requirement when a loop was naturally built CW by construction).
Source code in kumiki/pathcsg.py
StraightSegment
dataclass
¶
Bases: PathSegment
A straight run of a path, between two points in the path's 2D plane.
Straight rather than Line, so it pairs with ArcSegment by what the segment DOES, and so it does not collide with geometry.LineSegment -- which is a different thing entirely: a bounded stretch of an infinite 3D line.
is_planar
¶
ray_crossings
¶
Source code in kumiki/pathcsg.py
closest_point
¶
Source code in kumiki/pathcsg.py
outward_local_normal
¶
bounds
¶
Source code in kumiki/pathcsg.py
tessellate_with_extra_breaks
¶
Source code in kumiki/pathcsg.py
sample_interior
¶
reverse
¶
reverse() -> StraightSegment
ArcSegment
dataclass
¶
Bases: PathSegment
Circular arc from start_angle, sweeping by sweep_angle: CCW (increasing
angle) if sweep_angle is positive, CW (decreasing angle) if negative --
abs(sweep_angle) must be in (0, 2*pi]. The sign is NOT optional styling:
for a fixed (start, end) point pair -- which a Path's connectivity
dictates, there's no freedom to swap them -- going CCW vs CW traces two
different arcs (one bulging to each side of the start->end chord), and
which one is a convex bulge vs a concave bite depends on that sign, not
on where center sits. (An earlier version of this docstring claimed the
bulge direction "falls out from center position alone, no sign needed" --
that's wrong; picking a concave arc from a fixed start to a fixed end
requires a CW sweep, which needs the sign.)
is_planar
¶
ray_crossings
¶
Source code in kumiki/pathcsg.py
closest_point
¶
Source code in kumiki/pathcsg.py
outward_local_normal
¶
Source code in kumiki/pathcsg.py
bounds
¶
Source code in kumiki/pathcsg.py
signed_area_contribution
¶
signed_area_contribution() -> Numeric
Source code in kumiki/pathcsg.py
tessellate_with_extra_breaks
¶
Source code in kumiki/pathcsg.py
sample_interior
¶
Source code in kumiki/pathcsg.py
reverse
¶
reverse() -> ArcSegment
Source code in kumiki/pathcsg.py
FancyPath
dataclass
¶
A closed loop: segments[i].end must equal segments[i+1].start, and segments[-1].end must equal segments[0].start. NOT validated at construction -- same trust-the-caller convention ConvexPolygonSimpleLoft already uses for its own non-convex-taper risk. is_valid() is opt-in.
is_valid
¶
Checks: >=1 segment, each segment connects end-to-start with the next (loop closes), signed_area() > 0 (CCW).
Does NOT check for self-intersection. A self-intersecting Path is undefined behavior for PathExtrusion (contains_point_2d's parity test and decompose_path_into_convex_pieces both assume a simple polygon), same as ConvexPolygonSimpleLoft's existing disclaimer for non-convex intermediate cross-sections -- avoiding self-intersection is the caller's responsibility, not something validated here.
Source code in kumiki/pathcsg.py
reversed
¶
reversed() -> FancyPath
This same closed loop, traversed in the opposite direction -- flips CW<->CCW (negates signed_area()). Handy for a loop that's naturally built in a fixed geometric order that comes out CW (e.g. one auto-generated end of it is dictated by construction rather than free to choose) but needs to satisfy is_valid()'s CCW requirement.
Source code in kumiki/pathcsg.py
bounds
¶
Source code in kumiki/pathcsg.py
locate_boundary_segment
¶
locate_boundary_segment(point: V2, eps: Optional[Numeric] = None) -> Optional[Tuple[int, PathSegment]]
First segment (in order) whose closest point to point is ~point,
i.e. the segment point lies on -- or None if point isn't on the
boundary at all. At a shared vertex between two segments this is an
arbitrary but consistent choice, same ambiguity RectangularPrism's
get_outward_normal already accepts at its own edges/corners.
Source code in kumiki/pathcsg.py
is_point_on_boundary_2d
¶
contains_point_2d
¶
Source code in kumiki/pathcsg.py
tessellate
¶
tessellate(tolerance: float) -> Profile
Path -> plain point list (Profile), for meshing only.
Does NOT account for decompose_path_into_convex_pieces's band seams (see tessellate_with_extra_breaks) -- a wall built from this alone can T-junction against that function's cap pieces. Use tessellate_for_mesh for an actual PathExtrusion's wall ring; this plain version is for anyone who wants a Path's outline on its own, with no cap to stay consistent with.
Source code in kumiki/pathcsg.py
tessellate_for_mesh
¶
tessellate_for_mesh(tolerance: float) -> Profile
Like tessellate(), but forces a vertex at every band-seam decompose_path_into_convex_pieces will introduce -- including on a straight segment that a foreign extremum cuts through the middle of -- so a PathExtrusion's wall ring and cap pieces always agree on every seam. Use this (not tessellate()) to build a PathExtrusion's wall mesh.
Source code in kumiki/pathcsg.py
SimplePathExtrusionFeature
dataclass
¶
Bases: CSGFeature
One side face (key = segment index) or end cap of a PathExtrusion.
A key pointing at a curved segment never matches any point: there is no planar face there to name. That is the graceful-fail behaviour, not a special case -- the feature simply stays unmatched.
feature_type
¶
feature_type() -> CSGFeatureType
locate
¶
locate(owner: CutCSG) -> Optional[LocatedGeometry]
Source code in kumiki/pathcsg.py
get_extent
¶
get_extent(owner: CutCSG) -> Optional[CSGFeatureExtent]
Source code in kumiki/pathcsg.py
test_point_unbounded
¶
Source code in kumiki/pathcsg.py
PathExtrusion
dataclass
¶
Bases: HasFeatures, CutCSG
Generalizes ConvexPolygonExtrusion to an arbitrary closed FancyPath (lines and arcs today, more segment types later) -- convexity is NOT required. Trades ConvexPolygonExtrusion's cheap half-plane containment test for FancyPath's general ray-casting one.
The path lives in the local XY plane at transform's position, extruded
out in -z by start_distance and +z by end_distance, matching
ConvexPolygonExtrusion's conventions exactly.
transform
class-attribute
instance-attribute
¶
__repr__
¶
contains_point
¶
Source code in kumiki/pathcsg.py
is_point_on_boundary
¶
Source code in kumiki/pathcsg.py
get_outward_normal
¶
get_outward_normal(point: V3, eps: Optional[Numeric] = None) -> Optional[Direction3D]
Source code in kumiki/pathcsg.py
get_aabb
¶
get_aabb() -> BoundingBox
Source code in kumiki/pathcsg.py
decompose_path_into_convex_pieces
¶
Like cutcsg.decompose_simple_polygon_into_convex_pieces, but sweeps
directly over a FancyPath's small number of segments instead of a
pre-tessellated point list -- the expensive exact-arithmetic part (which
v-bands exist, which segments are active in each, pairing crossings) runs
over O(segment count), not O(tessellated point count). A leg profile with
a handful of arcs tessellated finely might be 200-400 points; this runs
the symbolic sweep over more like 5-15 segments instead, since arcs are
only numerically sampled (at tolerance) AFTER the sweep has decided
which pieces exist -- not before.
Returns convex pieces as plain Profiles (points only): once a piece is
isolated, any curved edge within it has already been sampled to straight
sub-segments at tolerance, so pieces are ordinary polygons suitable for
a plain fan triangulation, exactly like ConvexPolygonExtrusion's cap
pieces. The corner points of each piece are still exact (computed via
ray_crossings(inclusive=True)); only the extra interior points along a
curved band-edge are float approximations -- consistent with this being
the analytic/mesh boundary, same role triangles.py plays for the rest of
CutCSG.
Source code in kumiki/pathcsg.py
718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 | |