mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-22 14:17:59 +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:
|
def map_shape(settings: settings, inst: entity_instance) -> ifcopenshell_wrapper.item:
|
||||||
"""
|
"""
|
||||||
Returns an interpretation of the geometry encoded as per IfcOpenShell's taxonomy layer.
|
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();
|
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()) {
|
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) {
|
||||||
ifcopenshell::file* file = instance.file();
|
|
||||||
|
|
||||||
ifcopenshell::geom::converter kernel(ifcopenshell::geom::kernels::construct(file, geometry_library, settings, logger), file, settings, logger);
|
|
||||||
if (instance.declaration().is("IfcProduct")) {
|
if (instance.declaration().is("IfcProduct")) {
|
||||||
if (representation && !representation.declaration().is("IfcRepresentation")) {
|
if (representation && !representation.declaration().is("IfcRepresentation")) {
|
||||||
throw ifcopenshell::exception("Supplied representation not of type 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*>();
|
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 {
|
%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
|
// @todo bring back serialization OCCT -> IFC by means of opencascade_geometry_ifc_writer_registry
|
||||||
|
|
||||||
%template(OpaqueCoordinate_3) ifcopenshell::geom::opaque_coordinate<3>;
|
%template(OpaqueCoordinate_3) ifcopenshell::geom::opaque_coordinate<3>;
|
||||||
|
|||||||
Reference in New Issue
Block a user