"""B-Rep face extraction: solid shells vs free surfaces.""" from __future__ import annotations import logging from dataclasses import dataclass from OCP.BRep import BRep_Tool from OCP.TopAbs import TopAbs_FACE, TopAbs_SHELL, TopAbs_SOLID from OCP.TopExp import TopExp_Explorer from OCP.TopoDS import TopoDS, TopoDS_Face, TopoDS_Shape logger = logging.getLogger(__name__) @dataclass class FaceExtractionResult: """Result of face extraction from a shape.""" faces: list[TopoDS_Face] is_solid: bool num_solids: int num_shells: int num_faces: int def _count_topology(shape: TopoDS_Shape, topo_type) -> int: """Count topology entities of a given type.""" count = 0 exp = TopExp_Explorer(shape, topo_type) while exp.More(): count += 1 exp.Next() return count def _has_surface_geometry(face: TopoDS_Face) -> bool: """Check that a face has valid underlying surface geometry.""" try: surface = BRep_Tool.Surface_s(face) return surface is not None except Exception: return False def extract_faces(shape: TopoDS_Shape) -> FaceExtractionResult: """Extract faces from a B-Rep shape. For solids (CATIA): extracts outer shell faces. For free surfaces (Alias): collects all faces directly. Args: shape: The TopoDS_Shape to extract faces from. Returns: FaceExtractionResult with the list of faces and metadata. """ num_solids = _count_topology(shape, TopAbs_SOLID) num_shells = _count_topology(shape, TopAbs_SHELL) is_solid = num_solids > 0 faces: list[TopoDS_Face] = [] seen_hashes: set[int] = set() if is_solid: logger.info( "Solid geometry detected (%d solid(s), %d shell(s)). " "Extracting outer shell faces.", num_solids, num_shells, ) solid_exp = TopExp_Explorer(shape, TopAbs_SOLID) while solid_exp.More(): shell_exp = TopExp_Explorer(solid_exp.Current(), TopAbs_FACE) while shell_exp.More(): face = TopoDS.Face_s(shell_exp.Current()) h = hash(face) if h not in seen_hashes and _has_surface_geometry(face): seen_hashes.add(h) faces.append(face) shell_exp.Next() solid_exp.Next() else: logger.info( "Free-surface geometry detected (%d shell(s)). " "Collecting all faces.", num_shells, ) face_exp = TopExp_Explorer(shape, TopAbs_FACE) while face_exp.More(): face = TopoDS.Face_s(face_exp.Current()) h = hash(face) if h not in seen_hashes and _has_surface_geometry(face): seen_hashes.add(h) faces.append(face) face_exp.Next() num_faces = len(faces) logger.info("Extracted %d unique faces (duplicates removed: %s).", num_faces, "solid" if is_solid else "surface") return FaceExtractionResult( faces=faces, is_solid=is_solid, num_solids=num_solids, num_shells=num_shells, num_faces=num_faces, )