Spaces:
Sleeping
Sleeping
| """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__) | |
| 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, | |
| ) | |