volmdlr_tools.features.reverse_engineering package#

Turns a recognised feature back into the parameters it would have been built from: an axis, a depth, a profile wire, and — when the profile’s outline is one a sketch can draw directly — the shape it was recognised as, paired with the sketch frame it was measured in. Reconstruction draws that shape in preference to the wire, so a feature whose profile is recognised is rebuilt the way a modeller would have drawn it.

The shape vocabulary itself lives with the code that emits build123d, so that the kinds recognition can report and the kinds emission can draw are one list rather than two. See volmdlr_build.transpiler for ProfileShape and the kinds it names.

The definitions each reverser produces — CavityDefinition, InteractingFeatureDefinition, HoleDefinition — are documented with the features package that re-exports them. Documented here are the shared engines they are built by, which are not.

Compute engines#

Shared compute engines for reverse engineering.

These modules implement axis / depth / profile / entry / fillet computations that are consumed by the type-keyed reversers (PocketReverser, SlotReverser, StepReverser, HoleReverser). Each engine is a pure function taking face ID lists + an AttributedAdjacencyGraph — no anatomy dataclass required — so it works uniformly for every seed, whatever feature type it came from.

class volmdlr_tools.features.reverse_engineering.engines.BlendClassification(floor: list[int] = <factory>, entry: list[int] = <factory>, wall: list[int] = <factory>)#

Bases: object

Partition of blend face IDs by anatomical role.

entry: list[int]#
floor: list[int]#
wall: list[int]#
class volmdlr_tools.features.reverse_engineering.engines.ClassifiedProfile(shape: ProfileShape, frame: Frame3D)#

Bases: object

A classified profile shape paired with the frame it is expressed in.

The 2D parameters of shape are meaningless without the sketch frame they were measured in, so the two travel together.

Parameters:
  • shape – The classified parametric shape.

  • frame – Sketch frame the wire was projected into; its origin is the profile centroid and its third axis is the classification axis.

frame: Frame3D#
shape: ProfileShape#
class volmdlr_tools.features.reverse_engineering.engines.ProfileExtractor(aag: AttributedAdjacencyGraph)#

Bases: object

Extracts 2D cross-section profiles from 3D cavity geometry.

This class handles multiple extraction strategies: 1. Direct projection onto entry surface (for planar, perpendicular entries) 2. Projection onto synthetic plane (for non-planar or angled entries) 3. Translation of floor edges (for straight-walled cavities)

Parameters:

aag – The Attributed Adjacency Graph containing face and edge data.

cross_section_wire(*, floor_face_ids: list[int], wall_face_ids: list[int], fillet_floor_ids: list[int] = (), feature_face_ids: list[int] = ()) Wire#

Build the closed transverse cross-section (trapezoid) of a step.

The corners come from FACE-PLANE intersections, not local edges, so the section is correct even when other features (blind steps, notches) sit at the step’s corners. The floor∩wall plane line gives the virtual sharp corner and the sweep axis (this also unifies sharp and blended steps — the planes intersect through any fillet). The section is closed against the real stock face planes the floor and wall open into.

A round at that junction which reduces to a profile corner is drawn into the section, so sweeping it reproduces the round; one that does not stays a separate blend and the corner is left sharp.

Raises NotImplementedError for degenerate geometry (no usable junction, a transverse edge running along the sweep axis, or a missing stock plane); the orchestrator catches it and drops the section.

entry_boundary_wire(*, opposing_wall_ids: list[int], feature_face_ids: list[int], axis: Vector3D) Wire | None#

For through-slots: wall↔entry-neighbor shared edges projected at entry level.

extract_full_profile(anatomy: CavityAnatomy, axis: Vector3D, depth: float) tuple[Wire | None, list[Wire]]#

Extract the full 2D profile of a cavity: outer wire plus per-island inner wires.

Inner wires correspond to floor protrusions (islands). Only populated for cavities with a single non-blended planar floor (see _collect_floor_inner_loop_edges()); empty list otherwise.

Parameters:
  • anatomy – The cavity anatomy with classified faces.

  • axis – The cavity axis direction (unit vector).

  • depth – The cavity depth.

Returns:

Tuple of (outer_wire, list_of_inner_wires).

extract_profile(anatomy: CavityAnatomy, axis: Vector3D, depth: float) Wire | None#

Extract the 2D outer profile of a cavity.

Parameters:
  • anatomy – The cavity anatomy with classified faces.

  • axis – The cavity axis direction (unit vector).

  • depth – The cavity depth.

Returns:

Wire representing the cavity outer profile, or None if extraction fails.

Raises:

NotImplementedError – For unsupported cavity geometries.

floor_wire(*, floor_face_ids: list[int], wall_face_ids: list[int], fillet_floor_ids: list[int], axis: Vector3D, depth: float) Wire | None#

Build a closed wire at entry level from floor↔wall (or fillet↔wall) edges.

outer_wire(*, floor_face_ids: list[int], axis: Vector3D, depth: float) Wire | None#

Project the outer wire of the floor face onto the entry-level plane.

wall_blends_from_profile(anatomy: CavityAnatomy) bool#

Determine if wall blends should be included in the profile extraction.

Parameters:

anatomy – The cavity anatomy with classified faces.

Returns:

True if wall blends should be included in the profile.

class volmdlr_tools.features.reverse_engineering.engines.ProfileMode(*values)#

Bases: Enum

Strategy for building the profile wire.

CROSS_SECTION = 4#
ENTRY_BOUNDARY = 3#
FLOOR_WIRE = 1#
OUTER_WIRE = 2#
class volmdlr_tools.features.reverse_engineering.engines.Venting(edges: tuple[int, ...] = (), at_entry: bool = False, at_exit: bool = False)#

Bases: object

Which boundaries of a cut the material has already ended at.

A boundary the material ends at does not determine where the cut stops, so the cut may be made to run past it; one with material beyond it does determine it, and must be reproduced exactly.

The two ends are named for the cut itself rather than for a frame’s parameterisation, so a frame anchored or signed the other way about cannot swap them: the entry is the mouth the tool goes in through, the exit is the far end of its travel — a blind cut’s floor, or the face a through cut breaks out of.

Parameters:
  • edges – Indices of the profile’s edges that vent, in the wire’s own order.

  • at_entry – Whether the mouth the tool goes in through leaves the material.

  • at_exit – Whether the far end of the travel leaves the material.

at_entry: bool = False#
at_exit: bool = False#
edges: tuple[int, ...] = ()#
volmdlr_tools.features.reverse_engineering.engines.blends_drawn_on_profile(profile: Wire | None, blend_face_ids: Iterable[int], aag: AttributedAdjacencyGraph) tuple[int, ...]#

Return which of a feature’s rounds its profile actually draws.

A profile is taken across one direction only, so it can carry a round only when that round turns about an axis the profile turns about too — a round standing square to the sweep leaves no arc in it at all. What settles it is the wire itself: the round is drawn when the wire holds an arc of the round’s radius turning about the round’s own axis. Reading the wire answers for every way a profile is built, and answers with what was built rather than with what could have been.

A round the profile does not draw is not swept with its feature and has to be applied to edges of its own instead.

Parameters:
  • profile – The profile wire the feature is built from, or None.

  • blend_face_ids – The feature’s own rounds, as face indices.

  • aag – The graph those indices address.

Returns:

The face indices whose round the profile draws, in ascending order.

volmdlr_tools.features.reverse_engineering.engines.classify_blends(*, blend_face_ids: list[int], floor_face_ids: list[int], entry_face_ids: list[int], aag: AttributedAdjacencyGraph) BlendClassification#

Classify each blend into FLOOR / ENTRY / CORNER.

A blend is FLOOR if it’s adjacent to any floor face with a smooth dihedral; ENTRY if adjacent to any entry face with a smooth dihedral; CORNER otherwise.

Precedence when a blend legitimately touches both floor and entry (corner blend at the pocket-floor edge): FLOOR wins. The blend is bucketed as FLOOR and the entry side is discarded.

Ambiguous blends — adjacent to floor and/or entry faces but failing the smoothness test on each — fall through to CORNER and are logged at WARNING level. CORNER from this path is a best-effort default; surfacing it loudly avoids the “silently miscategorised” failure mode.

volmdlr_tools.features.reverse_engineering.engines.classify_profile_wire(profile: Wire | None, axis: Vector3D) ClassifiedProfile | None#

Recognise a feature’s profile wire as a standard parametric shape, when it is one.

The wire is projected into the sketch frame anchored at its own centroid and facing along axis, then matched against the shapes a sketch can draw directly. A missing profile, and one whose outline is free-form, both give None: neither is an error, and the reconstruction falls back to drawing the wire itself.

Parameters:
  • profile – The profile wire, or None.

  • axis – Normal of the plane the profile lies in. Its direction is preserved as the returned frame’s third axis, so a consumer whose own frame faces the other way must re-express the shape rather than read it directly. Note this is the plane’s normal and not necessarily the direction material is removed along — a reverser passes whichever axis it built the profile against.

Returns:

The matched shape paired with the frame it was measured in, or None.

Raises:

ValueError – When profile is a wire with no vertices. That is malformed input from further upstream rather than an unrecognisable shape, and returning None for it would file the defect under “free-form profile” and lose it.

volmdlr_tools.features.reverse_engineering.engines.classify_venting(*, profile: Wire, frame: Frame3D, depth: float, solid: Solid | Shell, probe: float) Venting#

Say which of a cut’s boundaries the material has already ended at.

Each boundary is answered by stepping a short way past it and asking whether that lands in material. The profile is read from the middle of the travel rather than from its own plane: it is positioned at the start of the travel, which is itself a boundary the cut may leave the material at, so a probe taken there reports on the end rather than on the side it was meant to test.

An end is checked by probing a point drawn toward the profile’s own centre from several places around its boundary and asking whether every one of them still lands in material. That assumes the centre itself lies inside the profile, which holds for an outline that bulges outward everywhere; an outline with a notch cut into it can fail the assumption, and an end past the notch may then be read as having left the material when it has not.

Parameters:
  • profile – The cut’s closed profile wire.

  • frame – Sketch frame anchored at the mouth the tool goes in through, its third axis pointing the way the cut travels from there into the material.

  • depth – How far the cut travels along that axis, so that walking the depth from the origin arrives at the far end of the travel.

  • solid – The shape being reconstructed, which the probes are put to.

  • probe – How far past a boundary to step. Well under the clearance, so a wall thinner than the clearance is still read as the boundary it is.

Returns:

The boundaries the material has already ended at.

volmdlr_tools.features.reverse_engineering.engines.clearance_for(solid: Solid | Shell, relative: float = 0.001) float#

How far past a boundary the material has ended at a cut should be made to run.

Measured against the shape rather than fixed, because the same number cannot serve parts modelled at different scales: one large enough to clear a surface on a part spanning half a metre would swallow a part spanning ten millimetres whole.

Parameters:
  • solid – The shape being reconstructed.

  • relative – The fraction of the shape’s diagonal to use.

Returns:

The clearance, in the shape’s own units.

volmdlr_tools.features.reverse_engineering.engines.compute_access_direction(origin: Point3D, axis: Vector3D, feature_face_ids: list[int], aag: AttributedAdjacencyGraph) Vector3D#

Access the planar non-feature neighbor of the origin.

volmdlr_tools.features.reverse_engineering.engines.compute_axis(*, floor_face_ids: list[int], wall_face_ids: list[int], feature_face_ids: list[int], aag: AttributedAdjacencyGraph) Vector3D#

Compute the machining-depth axis for a cut feature.

Strategy:
  1. If the feature has a planar floor, use its outward normal.

  2. Otherwise (ThroughSlot / ThroughHole), find a planar entry-level neighbor of the walls and use its normal.

Raises:

NotImplementedError – If no axis can be determined (no planar floor and no planar entry-level neighbor).

volmdlr_tools.features.reverse_engineering.engines.compute_depth(*, floor_face_ids: list[int], wall_face_ids: list[int], feature_face_ids: list[int], axis: Vector3D, aag: AttributedAdjacencyGraph) float#

Compute the feature depth along axis.

Strategy:
  1. If floor faces exist: project floor vertices (min) and entry-level neighbors of walls (max) onto axis; depth = max - min.

  2. Fall back to wall-vertex span along axis (ThroughSlot case).

Always returns a non-negative float.

volmdlr_tools.features.reverse_engineering.engines.compute_sweep_axis(*, floor_face_ids: list[int], wall_face_ids: list[int], aag: AttributedAdjacencyGraph) Vector3D | None#

Longitudinal sweep direction of a step: the floor-wall edge tangent.

Unlike compute_axis() (the floor normal), this is the direction the step’s cross-section is swept along. Because the floor-wall edge lies in the floor plane, this axis is perpendicular to the floor normal — which lets the cross-section profile carry the true floor/wall dihedral angle.

Returns None when there is no planar floor/wall pair sharing a linear edge, signalling the caller to fall back to the floor-normal axis.

volmdlr_tools.features.reverse_engineering.engines.compute_sweep_length(*, feature_face_ids: list[int], axis: Vector3D, aag: AttributedAdjacencyGraph) float#

Longitudinal span of the feature faces along axis — a step’s sweep length.

For the section-sweep step model the cross-section profile is swept along the floor-wall edge; this returns how far (max - min vertex projection along the sweep axis). Always non-negative.

volmdlr_tools.features.reverse_engineering.engines.extract_profile(*, floor_face_ids: list[int], wall_face_ids: list[int], fillet_floor_ids: list[int], feature_face_ids: list[int], axis: Vector3D, depth: float, aag: AttributedAdjacencyGraph, mode: ProfileMode = ProfileMode.FLOOR_WIRE) Wire | None#

Build the cross-section profile wire for a feature in the requested mode.

Thin wrapper that instantiates ProfileExtractor and dispatches to the matching mode method. The kernel lives on the class — both this function and direct callers of ProfileExtractor route through the same code path so the two cannot drift.

Returns None when the underlying geometry can’t be projected into a usable wire (the reverser should propagate the None).

volmdlr_tools.features.reverse_engineering.engines.profile_frame(origin: Point3D, axis: Vector3D) Frame3D#

Sketch-plane frame for a profile at origin, facing along axis.

The frame travels with whatever is expressed in it — a classified profile, a cut payload — so a consumer reads the frame it was handed instead of building its own. That only works because the two in-plane directions follow from axis alone: the same axis always gives the same frame, so a shape measured against one frame and drawn against another agrees on where its width runs.

Parameters:
  • origin – Where the sketch plane is anchored.

  • axis – Normal of the sketch plane. Need not be normalized. Its direction is kept as the frame’s third axis, so a caller wanting the plane faced the other way passes the reversed axis.

Returns:

A right-handed frame whose third axis runs along axis.

volmdlr_tools.features.reverse_engineering.engines.profile_origin(profile: Wire | None) Point3D | None#

Centroid of a profile wire’s vertices, or None when there is no profile.

Where a profile’s sketch is anchored. Both the definition that records a profile and the classification measured against it read this, so they anchor on the same point.

volmdlr_tools.features.reverse_engineering.engines.sweep_is_prismatic(*, feature_face_ids: list[int], sweep_axis: Vector3D, aag: AttributedAdjacencyGraph) bool#

Whether a step’s faces really form a prism along sweep_axis.

Sweeping a cross-section reproduces a step only where the cross-section is the same all the way along. Each planar face of a prism either runs along the sweep, with its normal square to the direction, or squares it off — and a prism is squared off only at its two ends. A stepped face breaks that: it is squared off once per step, part way along, and there the section it would be swept with changes. Sweeping any one of those sections removes material the part still has, and does it silently.

Curved faces are not judged here. A rounding that runs along the sweep is part of the cross-section, and one that runs across it already stops the cross-section from being built at all.

Parameters:
  • feature_face_ids – Every face of the step.

  • sweep_axis – The direction the cross-section would be swept along.

  • aag – The graph the faces belong to.

Returns:

True when sweeping a cross-section reproduces the step.

volmdlr_tools.features.reverse_engineering.engines.synthesize_entry_face(wall_face_ids: list[int], feature_face_ids: Iterable[int], aag: AttributedAdjacencyGraph, *, prefer_planar: bool = True) int | None#

Walk walls → AAG neighbors → return first non-feature face adjacent to walls.

Parameters:
  • wall_face_ids – Wall face IDs of the feature.

  • feature_face_ids – All face IDs belonging to the feature (to exclude).

  • aag – Adjacency graph.

  • prefer_planar – If True, prefer planar faces over non-planar.

Returns:

A non-feature face ID adjacent to at least one wall, or None.

volmdlr_tools.features.reverse_engineering.engines.venting_and_clearance(profile: Wire | None, origin: Point3D | None, access_direction: Vector3D | None, depth: float, solid: Solid | Shell) tuple[Venting | None, float]#

Measure which of a cut’s boundaries the material has already ended at, and by how much.

Every reverser of a swept cut asks the same two questions in the same way, so they are asked here once. A cut with no profile, no origin, or no access direction cannot be classified — there is nothing to probe outwards from — and reports no answer rather than a wrong one. The probe steps an order of magnitude less than the clearance, so a bounding wall thinner than the clearance is still read as bounding.

Parameters:
  • profile – The cut’s closed profile wire, when one was recovered.

  • origin – A point on the plane the cut was made from.

  • access_direction – The way the tool went in, when it could be determined.

  • depth – How far the cut travels from the mouth.

  • solid – The part the cut was made in.

Returns:

The classification, or None when it could not be made, paired with the clearance for this part.

Profile extraction and shape recognition#

Profile extraction.

Build a cross-section Wire from a feature’s face set. Supports three modes that cover pocket, slot, through-slot, and step semantics.

Modes:
FLOOR_WIRE — floor↔wall junction edges projected onto the entry-level

plane (floor_centroid + depth·axis). Used by pocket, open-pocket, and blind-slot.

OUTER_WIRE — outer wire of the floor face projected onto the entry-level

plane. Used by step / blind-step.

ENTRY_BOUNDARY — edges shared between opposing walls and their non-feature

neighbors, projected onto a plane at entry level. Used by through-slot.

CROSS_SECTION — transverse L-wire (floor + wall free edges) in the plane

perpendicular to the floor-wall edge. Its corner angle is the true floor/wall dihedral, so sweeping it along that edge reconstructs steps at any angle. Used by step / blind-step.

Once a profile has been built, classify_profile_wire() recognises whether its outline is one of the shapes a sketch can draw directly, and returns a ClassifiedProfile — the shape paired with the sketch frame it was measured in, which travel together because the shape’s 2D parameters mean nothing without it. Reconstruction draws that shape in preference to the wire, so a feature whose profile is recognised is rebuilt the way a modeller would have drawn it.

class volmdlr_tools.features.reverse_engineering.engines.profile.ClassifiedProfile(shape: ProfileShape, frame: Frame3D)#

Bases: object

A classified profile shape paired with the frame it is expressed in.

The 2D parameters of shape are meaningless without the sketch frame they were measured in, so the two travel together.

Parameters:
  • shape – The classified parametric shape.

  • frame – Sketch frame the wire was projected into; its origin is the profile centroid and its third axis is the classification axis.

frame: Frame3D#
shape: ProfileShape#
class volmdlr_tools.features.reverse_engineering.engines.profile.ProfileExtractor(aag: AttributedAdjacencyGraph)#

Bases: object

Extracts 2D cross-section profiles from 3D cavity geometry.

This class handles multiple extraction strategies: 1. Direct projection onto entry surface (for planar, perpendicular entries) 2. Projection onto synthetic plane (for non-planar or angled entries) 3. Translation of floor edges (for straight-walled cavities)

Parameters:

aag – The Attributed Adjacency Graph containing face and edge data.

cross_section_wire(*, floor_face_ids: list[int], wall_face_ids: list[int], fillet_floor_ids: list[int] = (), feature_face_ids: list[int] = ()) Wire#

Build the closed transverse cross-section (trapezoid) of a step.

The corners come from FACE-PLANE intersections, not local edges, so the section is correct even when other features (blind steps, notches) sit at the step’s corners. The floor∩wall plane line gives the virtual sharp corner and the sweep axis (this also unifies sharp and blended steps — the planes intersect through any fillet). The section is closed against the real stock face planes the floor and wall open into.

A round at that junction which reduces to a profile corner is drawn into the section, so sweeping it reproduces the round; one that does not stays a separate blend and the corner is left sharp.

Raises NotImplementedError for degenerate geometry (no usable junction, a transverse edge running along the sweep axis, or a missing stock plane); the orchestrator catches it and drops the section.

entry_boundary_wire(*, opposing_wall_ids: list[int], feature_face_ids: list[int], axis: Vector3D) Wire | None#

For through-slots: wall↔entry-neighbor shared edges projected at entry level.

extract_full_profile(anatomy: CavityAnatomy, axis: Vector3D, depth: float) tuple[Wire | None, list[Wire]]#

Extract the full 2D profile of a cavity: outer wire plus per-island inner wires.

Inner wires correspond to floor protrusions (islands). Only populated for cavities with a single non-blended planar floor (see _collect_floor_inner_loop_edges()); empty list otherwise.

Parameters:
  • anatomy – The cavity anatomy with classified faces.

  • axis – The cavity axis direction (unit vector).

  • depth – The cavity depth.

Returns:

Tuple of (outer_wire, list_of_inner_wires).

extract_profile(anatomy: CavityAnatomy, axis: Vector3D, depth: float) Wire | None#

Extract the 2D outer profile of a cavity.

Parameters:
  • anatomy – The cavity anatomy with classified faces.

  • axis – The cavity axis direction (unit vector).

  • depth – The cavity depth.

Returns:

Wire representing the cavity outer profile, or None if extraction fails.

Raises:

NotImplementedError – For unsupported cavity geometries.

floor_wire(*, floor_face_ids: list[int], wall_face_ids: list[int], fillet_floor_ids: list[int], axis: Vector3D, depth: float) Wire | None#

Build a closed wire at entry level from floor↔wall (or fillet↔wall) edges.

outer_wire(*, floor_face_ids: list[int], axis: Vector3D, depth: float) Wire | None#

Project the outer wire of the floor face onto the entry-level plane.

wall_blends_from_profile(anatomy: CavityAnatomy) bool#

Determine if wall blends should be included in the profile extraction.

Parameters:

anatomy – The cavity anatomy with classified faces.

Returns:

True if wall blends should be included in the profile.

class volmdlr_tools.features.reverse_engineering.engines.profile.ProfileMode(*values)#

Bases: Enum

Strategy for building the profile wire.

CROSS_SECTION = 4#
ENTRY_BOUNDARY = 3#
FLOOR_WIRE = 1#
OUTER_WIRE = 2#
class volmdlr_tools.features.reverse_engineering.engines.profile.Venting(edges: tuple[int, ...] = (), at_entry: bool = False, at_exit: bool = False)#

Bases: object

Which boundaries of a cut the material has already ended at.

A boundary the material ends at does not determine where the cut stops, so the cut may be made to run past it; one with material beyond it does determine it, and must be reproduced exactly.

The two ends are named for the cut itself rather than for a frame’s parameterisation, so a frame anchored or signed the other way about cannot swap them: the entry is the mouth the tool goes in through, the exit is the far end of its travel — a blind cut’s floor, or the face a through cut breaks out of.

Parameters:
  • edges – Indices of the profile’s edges that vent, in the wire’s own order.

  • at_entry – Whether the mouth the tool goes in through leaves the material.

  • at_exit – Whether the far end of the travel leaves the material.

at_entry: bool = False#
at_exit: bool = False#
edges: tuple[int, ...] = ()#
volmdlr_tools.features.reverse_engineering.engines.profile.blends_drawn_on_profile(profile: Wire | None, blend_face_ids: Iterable[int], aag: AttributedAdjacencyGraph) tuple[int, ...]#

Return which of a feature’s rounds its profile actually draws.

A profile is taken across one direction only, so it can carry a round only when that round turns about an axis the profile turns about too — a round standing square to the sweep leaves no arc in it at all. What settles it is the wire itself: the round is drawn when the wire holds an arc of the round’s radius turning about the round’s own axis. Reading the wire answers for every way a profile is built, and answers with what was built rather than with what could have been.

A round the profile does not draw is not swept with its feature and has to be applied to edges of its own instead.

Parameters:
  • profile – The profile wire the feature is built from, or None.

  • blend_face_ids – The feature’s own rounds, as face indices.

  • aag – The graph those indices address.

Returns:

The face indices whose round the profile draws, in ascending order.

volmdlr_tools.features.reverse_engineering.engines.profile.classify_profile_wire(profile: Wire | None, axis: Vector3D) ClassifiedProfile | None#

Recognise a feature’s profile wire as a standard parametric shape, when it is one.

The wire is projected into the sketch frame anchored at its own centroid and facing along axis, then matched against the shapes a sketch can draw directly. A missing profile, and one whose outline is free-form, both give None: neither is an error, and the reconstruction falls back to drawing the wire itself.

Parameters:
  • profile – The profile wire, or None.

  • axis – Normal of the plane the profile lies in. Its direction is preserved as the returned frame’s third axis, so a consumer whose own frame faces the other way must re-express the shape rather than read it directly. Note this is the plane’s normal and not necessarily the direction material is removed along — a reverser passes whichever axis it built the profile against.

Returns:

The matched shape paired with the frame it was measured in, or None.

Raises:

ValueError – When profile is a wire with no vertices. That is malformed input from further upstream rather than an unrecognisable shape, and returning None for it would file the defect under “free-form profile” and lose it.

volmdlr_tools.features.reverse_engineering.engines.profile.classify_venting(*, profile: Wire, frame: Frame3D, depth: float, solid: Solid | Shell, probe: float) Venting#

Say which of a cut’s boundaries the material has already ended at.

Each boundary is answered by stepping a short way past it and asking whether that lands in material. The profile is read from the middle of the travel rather than from its own plane: it is positioned at the start of the travel, which is itself a boundary the cut may leave the material at, so a probe taken there reports on the end rather than on the side it was meant to test.

An end is checked by probing a point drawn toward the profile’s own centre from several places around its boundary and asking whether every one of them still lands in material. That assumes the centre itself lies inside the profile, which holds for an outline that bulges outward everywhere; an outline with a notch cut into it can fail the assumption, and an end past the notch may then be read as having left the material when it has not.

Parameters:
  • profile – The cut’s closed profile wire.

  • frame – Sketch frame anchored at the mouth the tool goes in through, its third axis pointing the way the cut travels from there into the material.

  • depth – How far the cut travels along that axis, so that walking the depth from the origin arrives at the far end of the travel.

  • solid – The shape being reconstructed, which the probes are put to.

  • probe – How far past a boundary to step. Well under the clearance, so a wall thinner than the clearance is still read as the boundary it is.

Returns:

The boundaries the material has already ended at.

volmdlr_tools.features.reverse_engineering.engines.profile.clearance_for(solid: Solid | Shell, relative: float = 0.001) float#

How far past a boundary the material has ended at a cut should be made to run.

Measured against the shape rather than fixed, because the same number cannot serve parts modelled at different scales: one large enough to clear a surface on a part spanning half a metre would swallow a part spanning ten millimetres whole.

Parameters:
  • solid – The shape being reconstructed.

  • relative – The fraction of the shape’s diagonal to use.

Returns:

The clearance, in the shape’s own units.

volmdlr_tools.features.reverse_engineering.engines.profile.extract_profile(*, floor_face_ids: list[int], wall_face_ids: list[int], fillet_floor_ids: list[int], feature_face_ids: list[int], axis: Vector3D, depth: float, aag: AttributedAdjacencyGraph, mode: ProfileMode = ProfileMode.FLOOR_WIRE) Wire | None#

Build the cross-section profile wire for a feature in the requested mode.

Thin wrapper that instantiates ProfileExtractor and dispatches to the matching mode method. The kernel lives on the class — both this function and direct callers of ProfileExtractor route through the same code path so the two cannot drift.

Returns None when the underlying geometry can’t be projected into a usable wire (the reverser should propagate the None).

volmdlr_tools.features.reverse_engineering.engines.profile.venting_and_clearance(profile: Wire | None, origin: Point3D | None, access_direction: Vector3D | None, depth: float, solid: Solid | Shell) tuple[Venting | None, float]#

Measure which of a cut’s boundaries the material has already ended at, and by how much.

Every reverser of a swept cut asks the same two questions in the same way, so they are asked here once. A cut with no profile, no origin, or no access direction cannot be classified — there is nothing to probe outwards from — and reports no answer rather than a wrong one. The probe steps an order of magnitude less than the clearance, so a bounding wall thinner than the clearance is still read as bounding.

Parameters:
  • profile – The cut’s closed profile wire, when one was recovered.

  • origin – A point on the plane the cut was made from.

  • access_direction – The way the tool went in, when it could be determined.

  • depth – How far the cut travels from the mouth.

  • solid – The part the cut was made in.

Returns:

The classification, or None when it could not be made, paired with the clearance for this part.