lomit's picture
Sync from forma-3d-review@b6d4687f5d0f2e5303758c97095ea7e38e740723
182efca verified
"""Exterior/interior part classification for assembly review.
Classifies parts based on name-pattern matching against configurable
exterior patterns. Everything that does not match is classified as interior.
"""
from __future__ import annotations
import logging
from dataclasses import dataclass
from config.settings import SplitterSettings
from src.loader.assembly_tree import AssemblyNode
logger = logging.getLogger(__name__)
@dataclass
class ClassificationResult:
"""Result of exterior/interior classification for a part."""
node_id: str
name: str
classification: str # "exterior" or "interior"
score: int # 1 = matched exterior pattern, 0 = no match
reasons: list[str]
def classify_parts(
root: AssemblyNode,
settings: SplitterSettings | None = None,
) -> list[ClassificationResult]:
"""Classify all leaf parts as exterior or interior.
Uses simple case-insensitive substring matching against the configured
exterior_patterns. Any part whose name matches at least one pattern
is classified as "exterior"; everything else is "interior".
Args:
root: Assembly tree root node.
settings: Splitter settings with exterior_patterns.
Returns:
List of ClassificationResult for each leaf part.
"""
if settings is None:
settings = SplitterSettings()
patterns = [p.lower() for p in settings.exterior_patterns]
results: list[ClassificationResult] = []
for leaf in root.iter_leaves():
if leaf.shape is None or leaf.shape.IsNull():
continue
name_lower = leaf.name.lower()
matched = [p for p in patterns if p in name_lower]
if matched:
classification = "exterior"
score = 1
reasons = [f"name matches exterior pattern(s): {', '.join(matched)}"]
else:
classification = "interior"
score = 0
reasons = ["no exterior pattern matched"]
leaf.classification = classification
result = ClassificationResult(
node_id=leaf.id,
name=leaf.name,
classification=classification,
score=score,
reasons=reasons,
)
results.append(result)
logger.debug("Part '%s': -> %s (%s)", leaf.name, classification, "; ".join(reasons))
ext_count = sum(1 for r in results if r.classification == "exterior")
int_count = sum(1 for r in results if r.classification == "interior")
logger.info("Classification: %d exterior, %d interior", ext_count, int_count)
return results