Skip to content

Commit 1982073

Browse files
sawenzelclaude
andcommitted
Skip CAD leaves that are open faces instead of solids
This fixes a problem in o2-cad-to-tgeo with sheet bodies and adds a self-test. - A leaf made only of open faces encloses no volume, but was converted as a solid; its O2BVHSurfaceSolid then refused to close when geom.C was built. - Such a leaf is now skipped with a warning. - Open faces beside a closed body are dropped with a warning. - Closed shells without a solid around them are kept as volumes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent 136bb73 commit 1982073

1 file changed

Lines changed: 101 additions & 2 deletions

File tree

‎Detectors/CADSupport/tools/O2_CADtoTGeo.py‎

Lines changed: 101 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@
6666
)
6767
from OCC.Core.TopExp import TopExp_Explorer, topexp
6868
from OCC.Core.TopLoc import TopLoc_Location
69-
from OCC.Core.TopAbs import TopAbs_REVERSED, TopAbs_WIRE, TopAbs_EDGE, TopAbs_FACE, TopAbs_SOLID
69+
from OCC.Core.TopAbs import TopAbs_REVERSED, TopAbs_WIRE, TopAbs_EDGE, TopAbs_FACE, TopAbs_SHELL, TopAbs_SOLID
7070
from OCC.Core.TopTools import TopTools_IndexedMapOfShape
7171
from OCC.Core.TopoDS import topods
7272
from OCC.Extend.TopologyUtils import TopologyExplorer
@@ -2241,6 +2241,76 @@ def compound_of(*shapes):
22412241
"an empty leaf label is dropped with a warning and its siblings still convert",
22422242
detail or f"{len(logical_volumes)} volume(s), {len(occ)} placed")
22432243

2244+
# --- 4. faces that enclose no volume, as in the ALICE3 OTR stave -------------------------
2245+
from OCC.Core.BRepGProp import brepgprop
2246+
from OCC.Core.GProp import GProp_GProps
2247+
from OCC.Core.TopoDS import TopoDS_Shell
2248+
2249+
def volume_of(shape):
2250+
props = GProp_GProps()
2251+
brepgprop.VolumeProperties(shape, props)
2252+
return props.Mass()
2253+
2254+
def n_faces(shape):
2255+
exp = TopExp_Explorer(shape, TopAbs_FACE)
2256+
n = 0
2257+
while exp.More():
2258+
n += 1
2259+
exp.Next()
2260+
return n
2261+
2262+
def faces_of(shape):
2263+
exp = TopExp_Explorer(shape, TopAbs_FACE)
2264+
out = []
2265+
while exp.More():
2266+
out.append(exp.Current())
2267+
exp.Next()
2268+
return out
2269+
2270+
def one_face_shells(faces):
2271+
shells = []
2272+
for face in faces:
2273+
shell = TopoDS_Shell()
2274+
BRep_Builder().MakeShell(shell)
2275+
BRep_Builder().Add(shell, face)
2276+
shells.append(shell)
2277+
return compound_of(*shells)
2278+
2279+
box = BRepPrimAPI_MakeBox(2., 3., 4.)
2280+
_doc, st = _self_test_shape_tool()
2281+
sheets = st.AddShape(one_face_shells(faces_of(box.Shape())[:3]), False)
2282+
good = st.AddShape(BRepPrimAPI_MakeBox(3., 3., 3.).Shape(), False)
2283+
_self_test_assembly(st, [(sheets, gp_Trsf()), (good, _self_test_shift(dx=10.0))])
2284+
st.UpdateAssemblies()
2285+
_rep, occ = _self_test_convert(st)
2286+
report(len(logical_volumes) == 1 and len(occ) == 1,
2287+
"a leaf of open faces that enclose no volume is dropped, its sibling still converts",
2288+
f"{len(logical_volumes)} volume(s), {len(occ)} placed")
2289+
2290+
# --- 5. a closed shell with no solid around it still encloses a volume and is kept --------
2291+
_doc, st = _self_test_shape_tool()
2292+
shell_only = st.AddShape(box.Shell(), False)
2293+
_self_test_assembly(st, [(shell_only, gp_Trsf())])
2294+
st.UpdateAssemblies()
2295+
_rep, occ = _self_test_convert(st)
2296+
vol = volume_of(next(iter(def_shapes.values()))) if len(def_shapes) == 1 else 0.0
2297+
report(len(logical_volumes) == 1 and len(occ) == 1 and abs(vol - 24.0) < 1e-9,
2298+
"a closed shell without a solid is kept as one volume enclosing the right volume",
2299+
f"{len(logical_volumes)} volume(s), volume {vol:.6g} (expected 24)")
2300+
2301+
# --- 6. a solid with a stray face beside it keeps only the solid --------------------------
2302+
_doc, st = _self_test_shape_tool()
2303+
stray = compound_of(box.Shape(), faces_of(BRepPrimAPI_MakeBox(gp_Pnt(9., 0., 0.), 1., 1., 1.)
2304+
.Shape())[0])
2305+
part = st.AddShape(stray, False)
2306+
_self_test_assembly(st, [(part, gp_Trsf())])
2307+
st.UpdateAssemblies()
2308+
_rep, occ = _self_test_convert(st)
2309+
shape = next(iter(def_shapes.values())) if len(def_shapes) == 1 else None
2310+
report(shape is not None and len(occ) == 1 and n_faces(shape) == 6,
2311+
"a solid with a stray face keeps its 6 faces and drops the stray one",
2312+
f"{len(def_shapes)} volume(s), {n_faces(shape) if shape is not None else '-'} face(s)")
2313+
22442314
print(f"\n{tally.checks} checks, {tally.failures} failure(s)")
22452315
return tally.failures
22462316

@@ -3611,6 +3681,24 @@ def solid_bodies_of(shape) -> list:
36113681
return out
36123682

36133683

3684+
def shells_outside_solids(shape) -> Tuple[list, int]:
3685+
"""The closed shells a shape carries outside any solid, and the count of its open faces."""
3686+
closed, n_open = [], 0
3687+
exp = TopExp_Explorer(shape, TopAbs_SHELL, TopAbs_SOLID)
3688+
while exp.More():
3689+
shell = exp.Current()
3690+
if BRep_Tool.IsClosed(shell):
3691+
closed.append(shell)
3692+
else:
3693+
n_open += sum(1 for _ in TopologyExplorer(shell).faces())
3694+
exp.Next()
3695+
exp = TopExp_Explorer(shape, TopAbs_FACE, TopAbs_SHELL)
3696+
while exp.More():
3697+
n_open += 1
3698+
exp.Next()
3699+
return closed, n_open
3700+
3701+
36143702
def _register_leaf_shape(def_key: str, shape, meshparam, scale_to_cm: float,
36153703
clip_enabled: bool, clip_box, clip_box_shape,
36163704
world_trsf, def_lid: str) -> bool:
@@ -3793,7 +3881,18 @@ def expand_definition(
37933881
return def_key
37943882

37953883
shape = shape_tool.GetShape(def_label)
3796-
bodies = solid_bodies_of(shape)
3884+
closed_shells, n_open = shells_outside_solids(shape)
3885+
bodies = solid_bodies_of(shape) + closed_shells
3886+
if n_open:
3887+
# Open faces enclose no volume: a sheet body, not something to fill with material.
3888+
if not bodies:
3889+
print(f"WARNING: CAD leaf {def_lid} ('{nm}') carries only {n_open} open face(s) "
3890+
f"that enclose no volume; skipping it.")
3891+
return None
3892+
print(f"WARNING: CAD leaf {def_lid} ('{nm}') carries {n_open} open face(s) beside "
3893+
f"its {len(bodies)} closed bod(y/ies); dropping the faces.")
3894+
if len(bodies) == 1:
3895+
shape = bodies[0]
37973896

37983897
# A leaf label may hold several bodies; each becomes a volume the label places once.
37993898
if len(bodies) > 1:

0 commit comments

Comments
 (0)