mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-21 14:23:53 +00:00
geom: expose a reusable kernel object to Python (prototype for #9417)
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).
This commit is contained in:
committed by
Thomas Krijnen
parent
e713fdb17e
commit
66bcddc277
@@ -549,6 +549,63 @@ def create_shape(
|
||||
)
|
||||
|
||||
|
||||
class kernel:
|
||||
"""A reusable geometry kernel bound to a (geometry library, file, settings) triple.
|
||||
|
||||
``ifcopenshell.geom.create_shape`` constructs a new geometry kernel on every
|
||||
call, which repeats the backend resolution (including plugin discovery for
|
||||
hybrid kernels) and discards the mapping and conversion caches afterwards.
|
||||
This class performs that construction once so that converting many products
|
||||
one by one reuses the same kernel, mapping and caches, similar to what
|
||||
``ifcopenshell.geom.iterator`` does internally.
|
||||
|
||||
The kernel is bound at construction: the settings are copied and the file
|
||||
reference is kept, so later changes to the settings object do not affect an
|
||||
existing kernel and instances passed to :meth:`create_shape` must belong to
|
||||
the bound file.
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
settings = ifcopenshell.geom.settings()
|
||||
k = ifcopenshell.geom.kernel(settings, ifc_file, geometry_library="hybrid-cgal-simple-opencascade")
|
||||
for product in ifc_file.by_type("IfcProduct"):
|
||||
if product.Representation:
|
||||
shape = k.create_shape(product)
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
settings: settings,
|
||||
file: file,
|
||||
geometry_library: GEOMETRY_LIBRARY = "opencascade",
|
||||
logger: Optional[ifcopenshell.logger] = None,
|
||||
):
|
||||
self.settings = settings
|
||||
self.file = file
|
||||
self.wrapped = ifcopenshell_wrapper.geometry_kernel(
|
||||
geometry_library, file, settings, *ifcopenshell.optional_logger_args(logger)
|
||||
)
|
||||
|
||||
def create_shape(
|
||||
self,
|
||||
inst: entity_instance,
|
||||
repr: Optional[entity_instance] = None,
|
||||
) -> Union[
|
||||
ShapeType, ShapeElementType, ifcopenshell_wrapper.transformation, utils.shape_tuple, TopoDS.TopoDS_Shape
|
||||
]:
|
||||
"""Identical to :func:`create_shape` but reuses this kernel across calls.
|
||||
|
||||
See :func:`create_shape` for the possible return types; the settings and
|
||||
geometry library bound at construction are used for every call.
|
||||
"""
|
||||
return wrap_shape_creation(
|
||||
self.settings,
|
||||
self.wrapped.create_shape(inst, repr) if repr else self.wrapped.create_shape(inst),
|
||||
)
|
||||
|
||||
|
||||
def map_shape(settings: settings, inst: entity_instance) -> ifcopenshell_wrapper.item:
|
||||
"""
|
||||
Returns an interpretation of the geometry encoded as per IfcOpenShell's taxonomy layer.
|
||||
|
||||
@@ -990,10 +990,7 @@ struct shape_rtti : public boost::static_visitor<PyObject*>
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
static std::variant<ifcopenshell::geom::element*, ifcopenshell::geom::representation*, ifcopenshell::geom::transformation*> helper_fn_create_shape(ifcopenshell::logger& logger, const std::string& geometry_library, ifcopenshell::geom::settings& settings, const express::base& instance, const express::base& representation = express::base()) {
|
||||
ifcopenshell::file* file = instance.file();
|
||||
|
||||
ifcopenshell::geom::converter kernel(ifcopenshell::geom::kernels::construct(file, geometry_library, settings, logger), file, settings, logger);
|
||||
static std::variant<ifcopenshell::geom::element*, ifcopenshell::geom::representation*, ifcopenshell::geom::transformation*> helper_fn_create_shape_with_converter(ifcopenshell::geom::converter& kernel, ifcopenshell::geom::settings& settings, const express::base& instance, const express::base& representation) {
|
||||
if (instance.declaration().is("IfcProduct")) {
|
||||
if (representation && !representation.declaration().is("IfcRepresentation")) {
|
||||
throw ifcopenshell::exception("Supplied representation not of type IfcRepresentation");
|
||||
@@ -1069,6 +1066,12 @@ struct shape_rtti : public boost::static_visitor<PyObject*>
|
||||
}
|
||||
return std::variant<ifcopenshell::geom::element*, ifcopenshell::geom::representation*, ifcopenshell::geom::transformation*>();
|
||||
}
|
||||
|
||||
static std::variant<ifcopenshell::geom::element*, ifcopenshell::geom::representation*, ifcopenshell::geom::transformation*> helper_fn_create_shape(ifcopenshell::logger& logger, const std::string& geometry_library, ifcopenshell::geom::settings& settings, const express::base& instance, const express::base& representation = express::base()) {
|
||||
ifcopenshell::file* file = instance.file();
|
||||
ifcopenshell::geom::converter kernel(ifcopenshell::geom::kernels::construct(file, geometry_library, settings, logger), file, settings, logger);
|
||||
return helper_fn_create_shape_with_converter(kernel, settings, instance, representation);
|
||||
}
|
||||
%}
|
||||
|
||||
%typemap(out) ifcopenshell::geom::taxonomy::item::ptr {
|
||||
@@ -1116,6 +1119,37 @@ ifcopenshell::geom::taxonomy::item::ptr try_upcast(PyObject* obj0, swig_type_inf
|
||||
}
|
||||
%}
|
||||
|
||||
%inline %{
|
||||
// Reusable geometry kernel handle bound to a (geometry_library, file,
|
||||
// settings) triple. Constructing it resolves the kernel once and reuses
|
||||
// the converter, mapping and caches for every create_shape call.
|
||||
class geometry_kernel {
|
||||
public:
|
||||
geometry_kernel(const std::string& geometry_library, ifcopenshell::file* file, ifcopenshell::geom::settings& settings, ifcopenshell::logger* logger = nullptr)
|
||||
: file_(file)
|
||||
, settings_(settings)
|
||||
, converter_(ifcopenshell::geom::kernels::construct(file, geometry_library, settings_, ifcopenshell::logger_or_root(logger)), file, settings_, ifcopenshell::logger_or_root(logger))
|
||||
{}
|
||||
|
||||
std::variant<ifcopenshell::geom::element*, ifcopenshell::geom::representation*, ifcopenshell::geom::transformation*> create_shape(const express::base& instance, const express::base& representation) {
|
||||
if (instance.file() != file_) {
|
||||
throw ifcopenshell::exception("Instance does not belong to the file this kernel was constructed for");
|
||||
}
|
||||
return helper_fn_create_shape_with_converter(converter_, settings_, instance, representation);
|
||||
}
|
||||
|
||||
// Manual definition of overload without representation argument
|
||||
std::variant<ifcopenshell::geom::element*, ifcopenshell::geom::representation*, ifcopenshell::geom::transformation*> create_shape(const express::base& instance) {
|
||||
return create_shape(instance, express::base());
|
||||
}
|
||||
|
||||
private:
|
||||
ifcopenshell::file* file_;
|
||||
ifcopenshell::geom::settings settings_;
|
||||
ifcopenshell::geom::converter converter_;
|
||||
};
|
||||
%}
|
||||
|
||||
// @todo bring back serialization OCCT -> IFC by means of opencascade_geometry_ifc_writer_registry
|
||||
|
||||
%template(OpaqueCoordinate_3) ifcopenshell::geom::opaque_coordinate<3>;
|
||||
|
||||
Reference in New Issue
Block a user