File size: 3,192 Bytes
182efca
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
"""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,
    )