Feature Reverse Engineering#

Overview#

Feature reverse engineering turns recognised features into parametric definitions — the data needed to re-cut each feature on a defeatured solid. For every feature it determines:

  • Profile: the cross-section contour (Wire)

  • Depth: the distance the feature was cut to

  • Axis: the machining direction

  • Origin: the point the profile plane is placed at

  • Blends: the fillets belonging to the feature, with their radii

  • Anatomy: which face plays which role (entry, wall, floor, blends)

  • Parent: which other feature this one was cut into, if any

The single entry point is FeatureReverseEngineer, in volmdlr_tools.features.reverse_engineering:

from volmdlr.model import VolumeModel

from volmdlr_tools.features.reverse_engineering import FeatureReverseEngineer
from volmdlr_tools.graph.faces import AttributedAdjacencyGraph

solid = VolumeModel.from_step("part.step").primitives[0]
aag = AttributedAdjacencyGraph(shape=solid, allow_smooth=False)
aag.calculate_faces_angles()

plan = FeatureReverseEngineer(aag).reverse_engineer_all()
print(plan.summary())

for index, definition in enumerate(plan.definitions):
    print(index, definition.feature_type, definition.depth, definition.parent_index)
One part resolved into 26 typed feature definitions

A single part resolved into 26 definitions: 15 pockets (blue), 3 blind slots (orange), 3 through holes (red, exiting the underside) and 5 blind holes (green, in the pocket floors). Grey is the surrounding stock, which no feature owns. Each coloured face set is one definition’s faces_to_defeature().#

A single already-extracted feature can be reversed on its own, without re-running recognition:

definition = FeatureReverseEngineer(aag).reverse_engineer(my_pocket)

reverse_engineer returns None when the feature type has no reverser mapped, or when the seed adapter declines the feature.

Face Nomenclature#

Every reverser classifies the feature’s faces by their anatomical role before measuring anything. The vocabulary is shared across feature kinds:

Face Roles#

Role

Description

Entry

The face the feature opens through — typically a stock surface. It bounds the feature but is not part of the material removed.

Wall

Structural faces forming the sides, connecting the entry face to the floor.

Floor

The face at the bottom. Through features have none.

Entry Blend

Fillet faces at the transition between the entry face and the walls.

Floor Blend

Fillet faces at the transition between the walls and the floor.

Wall Blend

Fillet faces between adjacent walls, such as corner fillets.

Faces of a cavity coloured by anatomical role

Faces coloured by role: entry, walls, floor and the blend faces between them.#

Architecture#

Recognition produces features; reverse engineering measures them. The two stay separate, joined by a seed:

InteractingFeatureExtractor        recognise features on the AAG
           │
           ▼
seed_from_feature                  adapt one Feature -> FeatureSeed
           │                       (classifies faces into roles)
           ▼
FeatureReverseEngineer             dispatch on FeatureType
           │
           ├── PocketReverser      -> CavityDefinition
           ├── SlotReverser        -> InteractingFeatureDefinition
           ├── StepReverser        -> InteractingFeatureDefinition
           └── HoleReverser        -> HoleDefinition
           │
           ▼
FeatureDecompositionPlan           ordered definitions + parent wiring

The dispatch table is validated at construction: every feature type the orchestrator promises to reverse must have a reverser registered, so adding a new feature type cannot silently drop features at runtime.

Reversers#

One reverser per feature-type family. Each consumes a FeatureSeed and returns a definition, delegating all measurement to the shared engines:

Reverser

Feature types

Returns

PocketReverser

POCKET, OPEN_POCKET, PASSAGE

CavityDefinition

SlotReverser

BLIND_SLOT, THROUGH_SLOT

InteractingFeatureDefinition

StepReverser

STEP, BLIND_STEP

InteractingFeatureDefinition

HoleReverser

HOLE and its variants (blind, through, counterbore, countersink, stepped, taper)

HoleDefinition

Add a new feature type by writing a reverser and registering it — never by extending an existing one to cover a second type.

Shared Engines#

The engines carry the geometry. Each takes face-ID lists plus the adjacency graph and returns a measurement, so it works the same way for every feature type:

Engine

Responsibility

engines.axis

Machining axis from a planar floor, or from a planar entry neighbour when the feature has no floor. Also the sweep axis and access direction.

engines.depth

Floor-to-entry distance along the axis, with a parallel-plane and wall-vertex fallback. Also the sweep length.

engines.entry

Synthesises the entry face when the seed carries none, by walking from the walls out to a non-feature neighbour.

engines.blends

Sorts the feature’s blends into entry, wall and floor roles.

engines.profile

Extracts the cross-section wire, in one of several modes depending on whether the profile is taken from the floor loop, the entry loop or a planar section.

Definitions#

Three definition types, one per reverser family. They differ in what they carry — a hole knows its radius, a slot knows how it is swept — but agree on the parts the downstream pipeline relies on:

CavityDefinition

InteractingFeatureDefinition

HoleDefinition

Meaning

feature_type

Which kind of feature this is

profile / origin / axis / depth

The swept cross-section and where to sweep it

classified_profile

The profile recognised as a standard parametric shape, paired with the sketch frame it was measured in; None when the profile is free-form. Reconstruction draws this shape in preference to the profile wire.

anatomy

CavityAnatomy

InteractingFeatureAnatomy

HoleAnatomy

Face-to-role mapping

blends

The feature’s fillets

parent_index

Index of the feature this one was cut into; None for a root feature

faces_to_defeature()

Faces to hand to defeaturing

faces_to_defeature() is the shared contract with the defeaturing side: it returns the feature’s own faces and excludes the entry faces, which belong to the surrounding stock and must survive defeaturing — they are what closes the opening on the original stock surface.

Cavity definitions expose their fillet radii directly, so a caller can ask about the fillets without walking the blend list:

definition.has_entry_blend        # True when an entry fillet was found
definition.entry_fillet_radius    # its radius, or None
definition.has_floor_blend
definition.floor_fillet_radius

The Plan#

reverse_engineer_all returns a FeatureDecompositionPlan: the definitions in a stable order, with counts per definition kind and a one-line summary().

Two properties matter when consuming it:

  • Indices are stable. parent_index refers to positions in plan.definitions, so the list must not be reordered in place.

  • The order is not a build order. Sort explicitly — children before parents to defeature, parents before children to reconstruct — using the helpers in the reconstruction module.

Features whose type has no reverser are dropped from the plan rather than being carried as half-measured definitions. When that happens it is logged, so a part that loses features to an unrecognised type is visible rather than silently smaller.

Demo#

scripts/features/demo_interacting_feature_reverse_engineering.py runs the whole pipeline over a set of STEP files and displays each recognised feature, coloured by role. Its output on those files is pinned by tests/features/test_reverse_engineering_orchestrator.py, so the script doubles as the regression baseline: if the pipeline changes what it produces, that test fails and the ground truth has to be updated deliberately.