Skip to content

Commit 4c3310f

Browse files
Krandeclaude
andcommitted
feat(cad): FEM/gmsh meshing is backend-agnostic (works under adacpp)
Rewrite import_into_gmsh_use_nativepointer to go through the active CAD backend instead of pythonocc directly: iterate sub-shapes via backend.solids/faces/edges, translate via backend.transform, and feed gmsh each sub-shape pointer via backend.to_topods_pointer. Under adacpp the pointer is to its OCCT 7.9.3 TopoDS_Shape — ABI-valid for gmsh's OCCT, so gmsh stays a pointer consumer with no kernel mixing. Add solids/edges/to_topods_pointer/make_wire to both backends; route Beam.line_occ through backend.make_wire (was raw pythonocc). Under ADAPY_CAD_BACKEND=adacpp: solid + shell + line FEM meshing all work (IPE300 solid mesh = 506 elems). tests/core under adacpp 614->638 pass. OccBackend unchanged (655 pass), ruff clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent cad7472 commit 4c3310f

3 files changed

Lines changed: 63 additions & 18 deletions

File tree

src/ada/api/beams/base_bm.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -308,11 +308,9 @@ def bbox(self) -> BoundingBox:
308308
return self._bbox
309309

310310
def line_occ(self):
311-
from ada.occ.utils import make_wire_from_points
311+
from ada.cad import active_backend
312312

313-
points = [self.n1.p, self.n2.p]
314-
315-
return make_wire_from_points(points)
313+
return active_backend().make_wire([self.n1.p, self.n2.p])
316314

317315
def shell_occ(self) -> ShapeHandle:
318316
from ada.occ.geom.cache import get_shell_occ

src/ada/cad/__init__.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ class CadBackend(Protocol):
5252
name: str
5353

5454
def build(self, geometry: "Geometry") -> ShapeHandle: ...
55+
def make_wire(self, points: "list") -> ShapeHandle: ...
5556
def make_box(self, dx: float, dy: float, dz: float) -> ShapeHandle: ...
5657
def make_cylinder(self, radius: float, height: float) -> ShapeHandle: ...
5758
def make_sphere(self, radius: float) -> ShapeHandle: ...
@@ -69,8 +70,11 @@ def serialize(self, shape: ShapeHandle) -> str: ...
6970
def is_valid(self, shape: ShapeHandle) -> bool: ...
7071
def volume(self, shape: ShapeHandle) -> float: ...
7172
def faces(self, shape: ShapeHandle) -> list[ShapeHandle]: ...
73+
def solids(self, shape: ShapeHandle) -> list[ShapeHandle]: ...
74+
def edges(self, shape: ShapeHandle) -> list[ShapeHandle]: ...
7275
def vertex_points(self, shape: ShapeHandle) -> list[tuple[float, float, float]]: ...
7376
def face_plane(self, face: ShapeHandle) -> "tuple[Point, Direction] | None": ...
77+
def to_topods_pointer(self, shape: ShapeHandle) -> int: ...
7478

7579

7680
class AdacppBackend:
@@ -180,6 +184,9 @@ def _encode_curve(self, curve) -> list[list[float]]:
180184
f"AdacppBackend.build: profile curve {type(curve).__name__!r} not yet ported to adacpp."
181185
)
182186

187+
def make_wire(self, points: "list") -> ShapeHandle:
188+
return self._cad.make_wire([[float(c) for c in self._xyz(p)] for p in points])
189+
183190
def make_box(self, dx: float, dy: float, dz: float) -> ShapeHandle:
184191
return self._cad.make_box(dx, dy, dz)
185192

@@ -260,6 +267,24 @@ def faces(self, shape: ShapeHandle) -> list[ShapeHandle]:
260267
raise NotImplementedError("adacpp.cad.faces is not available in this build")
261268
return list(fn(shape))
262269

270+
def solids(self, shape: ShapeHandle) -> list[ShapeHandle]:
271+
fn = getattr(self._cad, "solids", None)
272+
if fn is None:
273+
raise NotImplementedError("adacpp.cad.solids is not available in this build")
274+
return list(fn(shape))
275+
276+
def edges(self, shape: ShapeHandle) -> list[ShapeHandle]:
277+
fn = getattr(self._cad, "edges", None)
278+
if fn is None:
279+
raise NotImplementedError("adacpp.cad.edges is not available in this build")
280+
return list(fn(shape))
281+
282+
def to_topods_pointer(self, shape: ShapeHandle) -> int:
283+
fn = getattr(self._cad, "to_topods_pointer", None)
284+
if fn is None:
285+
raise NotImplementedError("adacpp.cad.to_topods_pointer is not available in this build")
286+
return fn(shape)
287+
263288
def vertex_points(self, shape: ShapeHandle) -> list[tuple[float, float, float]]:
264289
fn = getattr(self._cad, "vertex_points", None)
265290
if fn is None:
@@ -376,6 +401,11 @@ def build(self, geometry: "Geometry") -> ShapeHandle:
376401

377402
return geom_to_occ_geom(geometry)
378403

404+
def make_wire(self, points: "list") -> ShapeHandle:
405+
from ada.occ.utils import make_wire_from_points
406+
407+
return make_wire_from_points(list(points))
408+
379409
def make_box(self, dx: float, dy: float, dz: float) -> ShapeHandle:
380410
# Centered axis-aligned box: matches adacpp.cad.make_box semantics
381411
# (corner at -d/2, opposite corner at +d/2). adapy's helper expects
@@ -454,6 +484,17 @@ def faces(self, shape: ShapeHandle) -> list[ShapeHandle]:
454484
# face, so callers iterate without re-entering the backend per element.
455485
return list(self._TopologyExplorer(shape).faces())
456486

487+
def solids(self, shape: ShapeHandle) -> list[ShapeHandle]:
488+
return list(self._TopologyExplorer(shape).solids())
489+
490+
def edges(self, shape: ShapeHandle) -> list[ShapeHandle]:
491+
return list(self._TopologyExplorer(shape).edges())
492+
493+
def to_topods_pointer(self, shape: ShapeHandle) -> int:
494+
# Under this backend a ShapeHandle IS a TopoDS_Shape; its SWIG pointer
495+
# is int(shape.this) — the value gmsh's importShapesNativePointer wants.
496+
return int(shape.this)
497+
457498
def vertex_points(self, shape: ShapeHandle) -> list[tuple[float, float, float]]:
458499
# Walk every vertex and return all coordinates as one list. The
459500
# per-vertex loop stays inside the backend (the abstraction boundary

src/ada/fem/meshing/concepts.py

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -398,36 +398,42 @@ def import_into_gmsh_using_step(
398398

399399

400400
def import_into_gmsh_use_nativepointer(obj: BackendGeom | Shape, geom_repr: GeomRepr, model: gmsh.model) -> List[tuple]:
401-
from OCC.Extend.TopologyUtils import TopologyExplorer
402-
401+
# gmsh consumes raw OCCT shapes via native pointers. We go through the
402+
# active CAD backend (sub-shape iteration + the to_topods_pointer bridge)
403+
# so this works on either backend without kernel mixing: under adacpp the
404+
# pointer is to its OCCT 7.9.x TopoDS_Shape, ABI-valid for gmsh's OCCT.
403405
from ada import Placement, PrimBox
404-
from ada.occ.utils import transform_shape
406+
from ada.cad import active_backend
405407

408+
backend = active_backend()
406409
abs_place = obj.placement.get_absolute_placement()
407410

408411
def transform_occ(geo):
409-
410412
if Placement() != abs_place:
411-
geo = transform_shape(geo, transform=abs_place)
413+
o = abs_place.origin
414+
mat = np.array(
415+
[[1.0, 0.0, 0.0, float(o[0])],
416+
[0.0, 1.0, 0.0, float(o[1])],
417+
[0.0, 0.0, 1.0, float(o[2])],
418+
[0.0, 0.0, 0.0, 1.0]]
419+
)
420+
geo = backend.transform(geo, mat, copy=True)
412421
return geo
413422

414-
ents = []
415423
if geom_repr == GeomRepr.SOLID:
416424
geom = transform_occ(obj.solid_occ())
417-
t = TopologyExplorer(geom)
418-
geom_iter = t.solids()
425+
sub_shapes = backend.solids(geom)
419426
elif geom_repr == GeomRepr.SHELL:
420427
geom = obj.shell_occ() if type(obj) not in (PrimBox,) else obj.solid_occ()
421428
geom = transform_occ(geom)
422-
t = TopologyExplorer(geom)
423-
geom_iter = t.faces()
429+
sub_shapes = backend.faces(geom)
424430
else:
425431
geom = transform_occ(obj.line_occ())
426-
t = TopologyExplorer(geom)
427-
geom_iter = t.edges()
432+
sub_shapes = backend.edges(geom)
428433

429-
for shp in geom_iter:
430-
ents += model.occ.importShapesNativePointer(int(shp.this))
434+
ents = []
435+
for shp in sub_shapes:
436+
ents += model.occ.importShapesNativePointer(backend.to_topods_pointer(shp))
431437

432438
if len(ents) == 0:
433439
raise ValueError("No entities found")

0 commit comments

Comments
 (0)