ifcopenshell.geom.create_shape constructs a geometry kernel, converter and
mapping on every call and discards them afterwards. For hybrid kernels the
construction alone rescans the plugin directory per component, which #9417
addresses on the C++ side by caching the resolved component ids. This
prototypes the alternative aothms suggested in the #9417 review: reuse the
constructed kernel itself from Python.
The wrapper gains a geometry_kernel class holding the converter (and through
it the kernel, mapping and conversion caches) built once per
(geometry_library, file, settings) triple, with create_shape(instance[,
representation]) delegating to the same helper the free create_shape uses,
now split so both paths share one body. Python gains
ifcopenshell.geom.kernel(settings, file, geometry_library) mirroring the
iterator constructor signature. The binding is fixed at construction:
settings are copied the way converter already copies them, and a guard
rejects instances from a different file because the mapping is file-bound.
On PGSuper_Import_Model.ifc (322 products, single-threaded, arm64 Linux),
hybrid-cgal-simple-opencascade drops from 71.97 ms/call with per-call
create_shape to 23.35 ms/call through the reused kernel, matching the
iterator (22.74 ms/call) without needing the #9417 cache; plain opencascade
is unaffected (23.30 vs 22.28 ms/call).
Return independent geometry copies with unique ownership, preserve parent lifetimes, and teach the Python wrapper to own derived results. Keep serializer inputs non-owning and replace Collada's deferred object with copied triangulation elements.\n\nGenerated with the assistance of an AI coding tool.
Apply the rename manifest, normalize serializer filenames to the classes they define, and update includes and CMake source lists.
Generated with the assistance of an AI coding tool.
It's ignoring underscore prefixed functions as not actually used.
Removing underscore to keep it happy without adding new exceptions.
(cherry picked from commit 88c8bd032f)
create_shape() returns a Python-owned Element (SWIG_POINTER_OWN in the
boost::variant out typemap). Its .geometry property calls Element::geometry(),
which returns a reference into the element's boost::shared_ptr<Representation>
_geometry member. SWIG wraps that reference as a non-owning pointer, so the
returned Triangulation/BRep/Serialization proxy does not keep the element alive.
When a caller keeps only .geometry (e.g. create_shape(s, e).geometry) and drops
the parent element, Python garbage-collects the element, destroying its
shared_ptr and freeing the underlying representation. Subsequent reads of
verts/faces then return freed memory: empty or implausible float/int garbage,
non-deterministically depending on GC and allocator timing. This is silent data
corruption, not a crash, and has bitten users since 2020.
Fix: in the TriangulationElement/SerializedElement/BRepElement pythoncode, wrap
the geometry getter so the returned geometry stores a backreference to its
owning element (result._parent = self). This makes the parent's lifetime at
least as long as the geometry's, automatically and transparently, so no caller
has to remember to hold the element. This is aothms's suggested backreference,
applied generically in the binding rather than left as a workaround.
Reproduced deterministically (washBasin fixture): before, verts len 0 vs 133500
across repeated GC-pressure runs; after, 133500 every run for all three element
types. test_create_shape passes; no regressions.
Note: tree.select_ray()'s ray_intersection_result (2024 follow-up in #1124) is a
separate ownership mechanism (std::vector element reference + std::array member
pointer) and is left as follow-up scope.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
(cherry picked from commit 824c1fc280)