volmdlr_tools.parametrics package#
The parametric descriptions recognition produces: the kinds a sketch can draw and the parameters of each, the projection a wire is read through, and the measurements that fit an arrangement to a set of positions.
Everything here reads geometry: points, wires and the frames they are projected onto. Nothing in it knows how a feature is recognised or how code for a shape is written, which is what lets recognition and the code that redraws a shape read one definition rather than each carrying its own.
Submodules#
volmdlr_tools.parametrics.profiles module#
Classification of closed planar wires into parametric profile shapes.
A closed planar outline is walked once and matched against the shapes a sketch can
draw directly, giving a ProfileShape that names the kind and carries its
parameters — or None when the outline is not one of them.
Five kinds are recognised, tried in this order:
rectangle— four sides, right corners, at any rotation.regular_polygon— equal sides on a common circle, at any rotation.slot— two parallel sides capped by tangent half circles.rectangle_rounded— a rectangle whose four corners carry one fillet radius.fillet_polyline— any other closed outline whose corners all carry one radius.
Precedence matters where two kinds could both match: a rectangle is reported as a
rectangle rather than as a four-sided regular polygon, and a rounded rectangle as
rectangle_rounded rather than as a fillet_polyline that happens to be
rectangular. An outline containing a spline, a whole circle, corners of differing
radii, or sides that do not meet end to end is not one of these kinds and yields
None, which is an ordinary answer rather than a failure.
The kind names and the meaning of each field are the contract with the code that draws each kind as a single sketch primitive, so they are not free to change on their own.
This is the one place that vocabulary is defined. Emission reads a shape off a payload and recognition puts one there, so both sides name the same kinds and the same fields, and a second copy of them could only drift: a kind added to one and not the other would be advertised by a classifier that can never return it, or returned by one whose consumer cannot draw it.
Classification works on a wire already projected onto its sketch frame, as
wire_edge_tuples() produces:
project first, then call classify_edge_tuples(). A caller holding projected
edges already needs only the second.
- class volmdlr_tools.parametrics.profiles.ProfileShape(kind: Literal['rectangle', 'regular_polygon', 'slot', 'rectangle_rounded', 'fillet_polyline'], center: tuple[float, float], width: float | None = None, height: float | None = None, radius: float | None = None, side_count: int | None = None, rotation_deg: float = 0.0, corner_radius: float | None = None, points: tuple[tuple[float, float], ...] | None = None)#
Bases:
objectOne recognised profile shape, in sketch-plane coordinates.
Which fields carry a value depends on
kind:kind
fields that carry a value
rectanglecenter,width,height,rotation_degregular_polygoncenter,radius,side_count,rotation_degslotcenter,width,height,rotation_degrectangle_roundedcenter,width,height,rotation_deg,corner_radiusfillet_polylinecenter,points,corner_radius- Parameters:
kind – Which of the five shapes this is.
center –
(cx, cy)the shape is positioned by. For every kind butfillet_polylinethis is the shape’s own centre; forfillet_polylineit is the mean ofpoints, which is not the outline’s centroid unless the outline happens to be symmetric.fillet_polylineis drawn frompointsalone, so itscenterpositions nothing and is only a handle for grouping.width – Extent along the rotation direction. For
slot, the overall length including both caps.height – Extent across the rotation direction. For
slot, the width across the caps, which is twice the cap radius.radius – Distance from
centerto a vertex of a regular polygon.side_count – Number of sides of a regular polygon.
rotation_deg – Rotation of the shape about
center, in degrees. Canonical in[0, 90)forrectangleandrectangle_rounded, whose quarter turns are the same shape with width and height exchanged; in[0, 180)forslot, whose half turns are the same shape; and in[0, 360)forregular_polygon, measured to its first vertex.corner_radius – The one radius every rounded corner carries.
points – The corners a
fillet_polylineis drawn through, in order. These are the sharp corners the outline would have had unrounded, not points on the outline itself.
- center: tuple[float, float]#
- corner_radius: float | None = None#
- height: float | None = None#
- kind: Literal['rectangle', 'regular_polygon', 'slot', 'rectangle_rounded', 'fillet_polyline']#
- points: tuple[tuple[float, float], ...] | None = None#
- radius: float | None = None#
- rotation_deg: float = 0.0#
- side_count: int | None = None#
- width: float | None = None#
- volmdlr_tools.parametrics.profiles.classify_edge_tuples(edges_2d: list[tuple[str, dict]]) ProfileShape | None#
Return the shape
edges_2ddraws, orNonewhen it is not one of the five.- Parameters:
edges_2d – Ordered
(edge_type, data)tuples, aswire_edge_tuples()produces."line"carriesstartandend;"arc"carriesstart,midandend, and carriesradiuswhen it projected as a circle rather than an ellipse. Any other edge type — a whole"circle", or an"unsupported"curve — means the outline is not one of these shapes.- Returns:
The recognised shape, or
None.
- volmdlr_tools.parametrics.profiles.reframe_profile_shape(shape: ProfileShape, from_frame: Frame3D, to_frame: Frame3D) ProfileShape#
Re-express a shape measured in one sketch frame in another frame.
centerandpointsare carried through 3D;rotation_degis carried by transporting the direction it names. Rectangle-family kinds keep rotation canonical by exchanging width and height, so a shape square to its frame stays square to the new one after a quarter-turn change of frame.The two frames must describe the same plane, or planes parallel to it: only the in-plane position and direction are mapped, while
width,height,radiusandcorner_radiusare carried across untouched. A frame whose third axis is not parallel to the original’s would need those re-measured, which this cannot do, so the caller is responsible for not asking.
volmdlr_tools.parametrics.projection module#
Describe the edges of a wire as the primitives a sketch draws.
One projection, used wherever a wire is read on a plane — whether it is about to be drawn or about to be classified. Two projections would let the same wire be measured one way for recognition and another for emission, and a profile that classified as one shape but drew as another would be the result.
- volmdlr_tools.parametrics.projection.project_edge(ocp_edge: object, frame: Frame3D) list[tuple[str, dict]]#
Describe one edge of a shape as the sketch primitives that draw it.
One edge usually gives one primitive, and a B-spline gives one per Bézier segment. Every wire walked for a sketch comes through here, so no kind of edge can be handled in one sketch and misread in another.
- Returns:
The primitives, empty when the edge contributes nothing to the sketch.
- volmdlr_tools.parametrics.projection.wire_edge_tuples(wire: object, frame: Frame3D) list[tuple[str, dict]]#
Walk a wire in order and describe each edge on
frameas a sketch primitive.The projection every sketch is read through, so a wire is described the same way wherever it is read — whether it is about to be drawn or about to be classified by
classify_edge_tuples(). It lives besideproject_edge(), which does the per-edge work.An edge whose two ends coincide once projected is dropped: it draws nothing, and left in it would read as an extra vertex. The threshold is the classifier’s own
TOL_DIST, so an edge too short for the classifier to tell apart from a point never reaches it.- Parameters:
wire – A volmdlr wire.
frame – Sketch-plane frame the edges are projected onto.
- Returns:
Ordered
(edge_type, data)tuples.
volmdlr_tools.parametrics.pattern_math module#
Fitting a set of points to the arrangement that repeats them.
A circle through scattered centres, the shortest spacing that explains a ring, the two directions a grid runs in: the measurements that turn a set of positions into a pattern. Plain coordinates in, parameters out — nothing here knows what sits at those positions, which is what lets both the code that finds patterns and the code that redraws them measure the same way.
- volmdlr_tools.parametrics.pattern_math.compute_grid_coords(pts_2d: ndarray, origin: ndarray, u: ndarray, v: ndarray, tol_fraction: float = 0.01) list[tuple[int, int]] | None#
Check all points lie on integer grid coordinates.
- Parameters:
pts_2d – Nx2 array of 2D points.
origin – 2D origin point.
u – First basis vector.
v – Second basis vector.
tol_fraction – Relative tolerance for grid-snap check.
- Returns:
List of (i, j) integer coordinates, or None if any point is off-grid.
- volmdlr_tools.parametrics.pattern_math.find_fundamental_spacing(deltas: ndarray, angle_tol: float = 2.0) float | None#
Find the fundamental angular spacing from consecutive angle deltas.
All deltas must be positive integer multiples of the fundamental spacing.
The candidate fundamental is the mode of the deltas (the mean of the most-populated tolerance bin), not the min. The mode is robust against a single near-coincident pair producing a tiny outlier delta that would otherwise hijack a min-based pick and cause every real delta to fail the integer-ratio test.
- Parameters:
deltas – Array of consecutive angular differences (degrees).
angle_tol – Tolerance in degrees for angle comparisons.
- Returns:
Fundamental spacing in degrees, or None if no regular pattern.
- volmdlr_tools.parametrics.pattern_math.find_grid_basis(pts_2d: ndarray, collinear_tol: float = 0.01) tuple[ndarray, ndarray, int] | None#
Find two non-collinear basis vectors from shortest pairwise distances.
- Parameters:
pts_2d – Nx2 array of 2D points.
collinear_tol – Relative cross-product threshold for collinearity.
- Returns:
(u, v, origin_index) or None.
- volmdlr_tools.parametrics.pattern_math.fit_circle_kasa(xs: ndarray, ys: ndarray) tuple[float, float, float] | None#
Algebraic circle fit (Kasa method).
Near-collinear input makes the algebraic solver return a geometrically valid but practically useless huge-radius circle. The radius/bbox guard below rejects those fits before they reach a downstream consistency check (which would pass — the points really do lie on that huge circle).
- Parameters:
xs – X coordinates of points.
ys – Y coordinates of points.
- Returns:
(cx, cy, r) or None on failure / degenerate fit.
Module contents#
The parametric descriptions recognition produces and emission consumes.
A recognised profile has to be named in terms the code that redraws it can use, and a repeated feature has to be described by where its copies sit. Both sides therefore need one vocabulary, and this package is where it is defined — not beside either of them, so that neither can extend it alone and leave the other unable to read the result.
Everything here reads geometry: points, wires and the frames they are projected onto. Nothing in it knows how a feature is recognised or how code for a shape is written, which is what lets both sides import it.
- class volmdlr_tools.parametrics.ProfileShape(kind: Literal['rectangle', 'regular_polygon', 'slot', 'rectangle_rounded', 'fillet_polyline'], center: tuple[float, float], width: float | None = None, height: float | None = None, radius: float | None = None, side_count: int | None = None, rotation_deg: float = 0.0, corner_radius: float | None = None, points: tuple[tuple[float, float], ...] | None = None)#
Bases:
objectOne recognised profile shape, in sketch-plane coordinates.
Which fields carry a value depends on
kind:kind
fields that carry a value
rectanglecenter,width,height,rotation_degregular_polygoncenter,radius,side_count,rotation_degslotcenter,width,height,rotation_degrectangle_roundedcenter,width,height,rotation_deg,corner_radiusfillet_polylinecenter,points,corner_radius- Parameters:
kind – Which of the five shapes this is.
center –
(cx, cy)the shape is positioned by. For every kind butfillet_polylinethis is the shape’s own centre; forfillet_polylineit is the mean ofpoints, which is not the outline’s centroid unless the outline happens to be symmetric.fillet_polylineis drawn frompointsalone, so itscenterpositions nothing and is only a handle for grouping.width – Extent along the rotation direction. For
slot, the overall length including both caps.height – Extent across the rotation direction. For
slot, the width across the caps, which is twice the cap radius.radius – Distance from
centerto a vertex of a regular polygon.side_count – Number of sides of a regular polygon.
rotation_deg – Rotation of the shape about
center, in degrees. Canonical in[0, 90)forrectangleandrectangle_rounded, whose quarter turns are the same shape with width and height exchanged; in[0, 180)forslot, whose half turns are the same shape; and in[0, 360)forregular_polygon, measured to its first vertex.corner_radius – The one radius every rounded corner carries.
points – The corners a
fillet_polylineis drawn through, in order. These are the sharp corners the outline would have had unrounded, not points on the outline itself.
- center: tuple[float, float]#
- corner_radius: float | None = None#
- height: float | None = None#
- kind: Literal['rectangle', 'regular_polygon', 'slot', 'rectangle_rounded', 'fillet_polyline']#
- points: tuple[tuple[float, float], ...] | None = None#
- radius: float | None = None#
- rotation_deg: float = 0.0#
- side_count: int | None = None#
- width: float | None = None#
- volmdlr_tools.parametrics.classify_edge_tuples(edges_2d: list[tuple[str, dict]]) ProfileShape | None#
Return the shape
edges_2ddraws, orNonewhen it is not one of the five.- Parameters:
edges_2d – Ordered
(edge_type, data)tuples, aswire_edge_tuples()produces."line"carriesstartandend;"arc"carriesstart,midandend, and carriesradiuswhen it projected as a circle rather than an ellipse. Any other edge type — a whole"circle", or an"unsupported"curve — means the outline is not one of these shapes.- Returns:
The recognised shape, or
None.
- volmdlr_tools.parametrics.compute_grid_coords(pts_2d: ndarray, origin: ndarray, u: ndarray, v: ndarray, tol_fraction: float = 0.01) list[tuple[int, int]] | None#
Check all points lie on integer grid coordinates.
- Parameters:
pts_2d – Nx2 array of 2D points.
origin – 2D origin point.
u – First basis vector.
v – Second basis vector.
tol_fraction – Relative tolerance for grid-snap check.
- Returns:
List of (i, j) integer coordinates, or None if any point is off-grid.
- volmdlr_tools.parametrics.find_fundamental_spacing(deltas: ndarray, angle_tol: float = 2.0) float | None#
Find the fundamental angular spacing from consecutive angle deltas.
All deltas must be positive integer multiples of the fundamental spacing.
The candidate fundamental is the mode of the deltas (the mean of the most-populated tolerance bin), not the min. The mode is robust against a single near-coincident pair producing a tiny outlier delta that would otherwise hijack a min-based pick and cause every real delta to fail the integer-ratio test.
- Parameters:
deltas – Array of consecutive angular differences (degrees).
angle_tol – Tolerance in degrees for angle comparisons.
- Returns:
Fundamental spacing in degrees, or None if no regular pattern.
- volmdlr_tools.parametrics.find_grid_basis(pts_2d: ndarray, collinear_tol: float = 0.01) tuple[ndarray, ndarray, int] | None#
Find two non-collinear basis vectors from shortest pairwise distances.
- Parameters:
pts_2d – Nx2 array of 2D points.
collinear_tol – Relative cross-product threshold for collinearity.
- Returns:
(u, v, origin_index) or None.
- volmdlr_tools.parametrics.fit_circle_kasa(xs: ndarray, ys: ndarray) tuple[float, float, float] | None#
Algebraic circle fit (Kasa method).
Near-collinear input makes the algebraic solver return a geometrically valid but practically useless huge-radius circle. The radius/bbox guard below rejects those fits before they reach a downstream consistency check (which would pass — the points really do lie on that huge circle).
- Parameters:
xs – X coordinates of points.
ys – Y coordinates of points.
- Returns:
(cx, cy, r) or None on failure / degenerate fit.
- volmdlr_tools.parametrics.reframe_profile_shape(shape: ProfileShape, from_frame: Frame3D, to_frame: Frame3D) ProfileShape#
Re-express a shape measured in one sketch frame in another frame.
centerandpointsare carried through 3D;rotation_degis carried by transporting the direction it names. Rectangle-family kinds keep rotation canonical by exchanging width and height, so a shape square to its frame stays square to the new one after a quarter-turn change of frame.The two frames must describe the same plane, or planes parallel to it: only the in-plane position and direction are mapped, while
width,height,radiusandcorner_radiusare carried across untouched. A frame whose third axis is not parallel to the original’s would need those re-measured, which this cannot do, so the caller is responsible for not asking.
- volmdlr_tools.parametrics.wire_edge_tuples(wire: object, frame: Frame3D) list[tuple[str, dict]]#
Walk a wire in order and describe each edge on
frameas a sketch primitive.The projection every sketch is read through, so a wire is described the same way wherever it is read — whether it is about to be drawn or about to be classified by
classify_edge_tuples(). It lives besideproject_edge(), which does the per-edge work.An edge whose two ends coincide once projected is dropped: it draws nothing, and left in it would read as an extra vertex. The threshold is the classifier’s own
TOL_DIST, so an edge too short for the classifier to tell apart from a point never reaches it.- Parameters:
wire – A volmdlr wire.
frame – Sketch-plane frame the edges are projected onto.
- Returns:
Ordered
(edge_type, data)tuples.