Feature Classification#

Internal documentation for the feature classification rule system.

Note

Audience: Developers extending the library with new feature types.

This document covers the internal rule-based classification system. For using feature recognition, see Feature Recognition.

Feature Classification System#

This module provides a rule-based feature classification system for categorizing sheet metal features. The system is based on the “Characteristics Matrix” approach described in Yang Yang et al. (2021).


Overview#

The classification system uses three types of rules to categorize features:

  1. Cut Feature Rules (Table 1): For holes, slots, notches, and corner reliefs

  2. Composite Feature Rules (Table 2): For bends, jogs, lances, and complex features

  3. Deform Feature Rules (Table 3): For emboss, louver, and deformation features

Each rule defines a set of geometric characteristics that must match for a feature to be classified as that type.


Core Components#

FeatureRuleRegistry#

The central registry containing all classification rules organized by category.

from volmdlr_tools.features.classification import FeatureRuleRegistry
Rule Categories#

Category

Attribute

Description

Cut Features

CUT_RULES

Holes, slots, notches, reliefs

Composite Features

COMPOSITE_RULES

Bends, jogs, lances

Deform Features

DEFORM_RULES

Emboss, louver

Example: Accessing Rules#
from volmdlr_tools.features.classification import FeatureRuleRegistry

# List all cut feature rules
for rule in FeatureRuleRegistry.CUT_RULES:
    print(f"{rule.name}: {rule.description}")

# List all composite feature rules
for rule in FeatureRuleRegistry.COMPOSITE_RULES:
    print(f"{rule.name}: {rule.description}")

RuleMatcher#

The matching engine that compares extracted properties against rule definitions.

from volmdlr_tools.features.classification import RuleMatcher
Value Matching#

The matcher supports three types of value specifications:

Type

Example

Matches

None (wildcard)

None

Any value

Exact value

4

Only the value 4

Range tuple

(2, 5)

Values 2, 3, 4, or 5

Open range

(None, 5)

Values up to 5

Open range

(2, None)

Values 2 and above

Methods#

matches_value(rule_value, actual_value) -> bool

Check if an actual value matches a rule specification.

matcher = RuleMatcher()

# Exact match
matcher.matches_value(4, 4)           # True
matcher.matches_value(4, 5)           # False

# Wildcard
matcher.matches_value(None, 42)       # True (any value)

# Range match
matcher.matches_value((2, 5), 3)      # True (in range)
matcher.matches_value((2, 5), 6)      # False (out of range)

# Open range
matcher.matches_value((None, 5), 3)   # True (unbounded lower)
matcher.matches_value((2, None), 10)  # True (unbounded upper)

matches_rule(rule, properties) -> float

Check if properties match a rule. Returns a confidence score (0.0 to 1.0).

from volmdlr_tools.features.classification import RuleMatcher, FeatureRuleRegistry

matcher = RuleMatcher()

# Properties extracted from a thickness face chain
properties = {
    "type_of_chain": "Closed",
    "total_faces": 4,
    "cylindrical_surfaces": 0,
    "planar_surfaces": 4,
    "parallel_planar_pairs": 2,
    "different_radius_values": 0,
}

# Check against rectangular hole rule
rule = FeatureRuleRegistry.CUT_RULES[1]  # RectangularHole
confidence = matcher.matches_rule(rule, properties)
print(f"Match confidence: {confidence:.2%}")

matches_deform_rule(rule, properties) -> bool

Check if properties match a deformation feature rule.

properties = {
    "has_interconnected_faces": True,
    "termination_criterion": 1,
    "inner_loop_shape": "Circular",
}

for rule in FeatureRuleRegistry.DEFORM_RULES:
    if matcher.matches_deform_rule(rule, properties):
        print(f"Matched: {rule.name}")

Rule Dataclasses#

CutFeatureRule#

For cut features (Table 1 in Yang Yang et al.).

from volmdlr_tools.features.classification import CutFeatureRule

@dataclass(frozen=True)
class CutFeatureRule:
    feature_class: type[Feature]       # The feature class to instantiate
    type_of_chain: str = None          # "Closed" | "Open"
    total_faces: RangeValue = None     # Number of faces in chain
    cylindrical_surfaces: RangeValue = None  # Count of cylindrical faces
    different_radius_values: RangeValue = None  # Unique radius count
    planar_surfaces: RangeValue = None  # Count of planar faces
    parallel_planar_pairs: RangeValue = None  # Parallel face pairs
    all_angles_concave: bool = None    # All angles are concave?
    is_fsf_neighbor: bool = None       # Adjacent to fan-shaped face?
    name: str = None                   # Rule identifier
    description: str = None            # Human-readable description

Example Cut Rules:

Rule

Chain

Faces

Cylindrical

Planar

Parallel Pairs

CircularHole

Closed

1-2

2

0

0

RectangularHole

Closed

4-8

0-4

4

2

Slot

Closed

4+

2

2

1

Notch

Open

1-3

-

-

-

CornerRelief

Open

4+

-

-

-

Relief

Open

3

-

-

-


CompositeFeatureRule#

For bend/composite features (Table 2 in Yang Yang et al.).

from volmdlr_tools.features.classification import CompositeFeatureRule

@dataclass(frozen=True)
class CompositeFeatureRule:
    feature_class: type[Feature]       # The feature class
    internal_chains: RangeValue = None  # Internal chain count
    boundary_subchains: RangeValue = None  # Boundary subchain count
    fan_shaped_faces: RangeValue = None  # Fan-shaped face count
    fan_shaped_pairs: RangeValue = None  # Paired fan-shaped faces
    share_common_face: bool = None      # Faces share common neighbor?
    opposite_normals: bool = None       # Face normals are opposite?
    d1_greater_d2: bool = None          # Width comparison
    name: str = None
    description: str = None

Example Composite Rules:

Rule

FSF Pairs

Common Face

Opposite Normals

Pattern

Bend

1

True

True

[0, 1]

Curl

1

True

True

path_dist=3

Hem

1

True

True

path_dist=5

Jog

2

True

True

[0, 0, 1, 1]


DeformFeatureRule#

For deformation features (Table 3 in Yang Yang et al.).

from volmdlr_tools.features.classification import DeformFeatureRule

@dataclass(frozen=True)
class DeformFeatureRule:
    feature_class: type[Feature]        # The feature class
    has_interconnected_faces: bool = None  # Has connected main faces?
    termination_criterion: int = None   # 1 or 2
    inner_loop_shape: str = None        # "Circular", "Rectangular", etc.
    name: str = None
    description: str = None

SheetMetalFeatureClassifier#

The high-level classifier that uses rules to categorize features.

from volmdlr_tools.features.extractors import SheetMetalFeatureClassifier
Usage#
from volmdlr_tools.features.extractors import SheetMetalFeatureClassifier
from volmdlr_tools.graph.faces import AttributedAdjacencyGraph

# Create classifier with AAG
classifier = SheetMetalFeatureClassifier(aag=my_aag)

# Classify a TFCGroup (thickness face chain group)
feature = classifier.classify(tfc_group)

if feature:
    print(f"Classified as: {feature.__class__.__name__}")
else:
    print("Could not classify feature")

Classification Workflow#

1. Extract Properties from TFCGroup#

# For cut features
properties = tfc_group.extract_cut_feature_properties()

# For composite features
properties = tfc_group.extract_composite_feature_properties()

2. Match Against Rules#

from volmdlr_tools.features.classification import RuleMatcher, FeatureRuleRegistry

matcher = RuleMatcher()

# Try cut rules first
best_match = None
best_confidence = 0.0

for rule in FeatureRuleRegistry.CUT_RULES:
    confidence = matcher.matches_rule(rule, properties)
    if confidence > best_confidence:
        best_confidence = confidence
        best_match = rule

if best_match and best_confidence > 0.5:
    print(f"Best match: {best_match.name} ({best_confidence:.0%})")

3. Instantiate Feature#

if best_match:
    feature = best_match.feature_class.from_faces_and_nodes(
        faces=tfc_group.faces,
        nodes=tfc_group.face_ids,
        name=best_match.name
    )

Extending the System#

Adding a New Feature Type#

  1. Create the feature class in volmdlr_tools/features/feature_types/sheet_metal/:

# In new_feature.py
from volmdlr_tools.features.feature_types.core import Feature

class MyNewFeature(Feature):
    """My custom sheet metal feature."""

    @property
    def category(self):
        return FeatureCategory.CUT  # or FORMING, etc.
  1. Add a rule to FeatureRuleRegistry:

# In registry.py
CUT_RULES = (
    # ... existing rules ...
    CutFeatureRule(
        feature_class=MyNewFeature,
        type_of_chain="Closed",
        total_faces=(3, 5),
        cylindrical_surfaces=1,
        name="MyNewFeature",
        description="Description of my new feature",
    ),
)
  1. Position the rule by specificity (more specific rules first).


Confidence Scoring#

The RuleMatcher.matches_rule() method returns a confidence score:

  • 1.0 (100%): All rule parameters matched

  • 0.0 (0%): An exclusive parameter didn’t match

  • 0.0 < x < 1.0: Partial match (ratio of matched parameters)

Exclusive Parameters (must match or confidence = 0):

  • type_of_chain

  • all_angles_concave

  • d1_greater_d2

  • parallel_planar_pairs

  • total_faces

  • fan_shaped_faces


FeatureCategory Enum#

from volmdlr_tools.features.constants import FeatureCategory

class FeatureCategory(Enum):
    BLEND = "blend"
    CAVITY = "cavity"
    CUT = "cut"
    FORMING = "forming"
    UNKNOWN = "unknown"

See Also#

See Also#