Wire up serializer plug-ins in python

This commit is contained in:
Thomas Krijnen
2026-05-08 10:58:09 +02:00
parent 18b79a4360
commit bfea57c617
3 changed files with 113 additions and 67 deletions
@@ -20,6 +20,7 @@
from __future__ import annotations from __future__ import annotations
from collections.abc import Generator, Iterable from collections.abc import Generator, Iterable
from os import PathLike, fspath
from typing import TYPE_CHECKING, Any, Literal, Optional, TypeVar, Union, cast, overload from typing import TYPE_CHECKING, Any, Literal, Optional, TypeVar, Union, cast, overload
from .. import ifcopenshell_wrapper, open from .. import ifcopenshell_wrapper, open
@@ -630,69 +631,84 @@ def make_shape_function(fn):
# tesselate = make_shape_function(ifcopenshell_wrapper.tesselate) # tesselate = make_shape_function(ifcopenshell_wrapper.tesselate)
def transform_string(v: Union[str, serializers.buffer]) -> serializers.buffer: class _serializer_factory:
if isinstance(v, str): def __init__(self, name: str, extension: str):
return ifcopenshell_wrapper.buffer(v) self.name = name
return v self.extension = extension
self.__name__ = name
def __call__(self, out_filename: Union[str, PathLike[str]], *args: Any) -> ifcopenshell_wrapper.GeometrySerializer:
if self.name == "obj" and len(args) == 3:
output_filename = args[0]
output_temp_filename = out_filename
geometry_settings, serializer_settings = args[1], args[2]
elif len(args) == 2:
output_filename = out_filename
output_temp_filename = out_filename
geometry_settings, serializer_settings = args
else:
obj_signature = " or (out_filename, mtl_filename, geometry_settings, serializer_settings)"
raise TypeError(
f"serializers.{self.name}() expects (out_filename, geometry_settings, serializer_settings)"
+ (obj_signature if self.name == "obj" else "")
)
output_filename = self._path(output_filename)
output_temp_filename = self._path(output_temp_filename)
return ifcopenshell_wrapper.create_geometry_serializer(
self.extension, output_filename, output_temp_filename, geometry_settings, serializer_settings
)
def _path(self, value: Union[str, PathLike[str]]) -> str:
try:
path = fspath(value)
except TypeError:
raise TypeError(f"serializers.{self.name}() requires a filesystem path") from None
if not isinstance(path, str):
raise TypeError(f"serializers.{self.name}() requires a text filesystem path")
return path
class serializers: class _serializers_meta(type):
# Python does not have automatic casts. The C++ serializers accept a stream_or_filename _extensions = {
# which in C++ can be automatically constructed from a filename string. In Python we "obj": "obj",
# have to implement this cast/construction explicitly by transform_string. "svg": "svg",
@staticmethod "ttl": "ttl",
def obj( "gltf": "glb",
out_filename: Union[str, serializers.buffer], "glb": "glb",
mtl_filename: Union[str, serializers.buffer], "hdf": "h5",
geometry_settings: settings, "hdf5": "h5",
settings: serializer_settings, "h5": "h5",
) -> ifcopenshell_wrapper.WaveFrontOBJSerializer: "collada": "dae",
out_filename = transform_string(out_filename) "dae": "dae",
mtl_filename = transform_string(mtl_filename) "stp": "stp",
return ifcopenshell_wrapper.WaveFrontOBJSerializer(out_filename, mtl_filename, geometry_settings, settings) "step": "stp",
"igs": "igs",
"iges": "igs",
"usd": "usd",
"usda": "usd",
"usdc": "usd",
}
@staticmethod def __getattr__(cls, name: str) -> _serializer_factory:
def svg( extension = cls._extensions.get(name)
out_filename: Union[str, serializers.buffer], geometry_settings: settings, settings: serializer_settings if extension is None:
) -> ifcopenshell_wrapper.SvgSerializer: raise AttributeError(f"type object 'serializers' has no attribute '{name}'")
out_filename = transform_string(out_filename) factory = _serializer_factory(name, extension)
return ifcopenshell_wrapper.SvgSerializer(out_filename, geometry_settings, settings) setattr(cls, name, factory)
return factory
# Hdf- Xml- and glTF- serializers don't support writing to a buffer, only to filename def __dir__(cls) -> list[str]:
# so no wrap_buffer_creation() for these serializers return sorted(set(super().__dir__()) | set(cls._extensions))
# xml = ifcopenshell_wrapper.XmlSerializer
class serializers(metaclass=_serializers_meta):
buffer = ifcopenshell_wrapper.buffer buffer = ifcopenshell_wrapper.buffer
# gltf, hdf5, collada and json availability depend on IfcOpenShell configuration settings
try:
gltf = ifcopenshell_wrapper.GltfSerializer
except:
pass
try:
hdf5 = ifcopenshell_wrapper.HdfSerializer
except:
pass
try:
collada = ifcopenshell_wrapper.ColladaSerializer
except:
pass
try:
json = ifcopenshell_wrapper.JsonSerializer
except:
pass
# ttl is always available since it doesn't depend on any C++ libraries,
# just people might be using an outdated binary
if hasattr(ifcopenshell_wrapper, "TtlWktSerializer"):
@staticmethod
def ttl(
out_filename: Union[str, serializers.buffer], geometry_settings: settings, settings: serializer_settings
) -> ifcopenshell_wrapper.SvgSerializer:
out_filename = transform_string(out_filename)
return ifcopenshell_wrapper.TtlWktSerializer(out_filename, geometry_settings, settings)
@classmethod @classmethod
def guess_from_extension(cls, filepath: str): def guess_from_extension(cls, filepath: str):
ext = filepath.split(".")[-1] ext = filepath.rsplit(".", 1)[-1].lower()
mapping = { mapping = {
"glb": "gltf", "glb": "gltf",
"hdf": "hdf5", "hdf": "hdf5",
@@ -701,8 +717,14 @@ class serializers:
"obj": "obj", "obj": "obj",
"svg": "svg", "svg": "svg",
"ttl": "ttl", "ttl": "ttl",
"xml": "xml",
"dae": "collada", "dae": "collada",
"stp": "stp",
"step": "step",
"igs": "igs",
"iges": "iges",
"usd": "usd",
"usda": "usda",
"usdc": "usdc",
} }
serializer_name = mapping.get(ext) serializer_name = mapping.get(ext)
if not serializer_name: if not serializer_name:
@@ -1710,6 +1710,13 @@ def construct_iterator_with_include_exclude_id(geometry_library, settings, file,
def convert_loop_to_function_item(loop): ... def convert_loop_to_function_item(loop): ...
def create_box(*args): ... def create_box(*args): ...
def create_epeck(*args): ... def create_epeck(*args): ...
def create_geometry_serializer(
extension: str,
output_filename: str,
output_temp_filename: str,
geometry_settings: Settings,
serializer_settings: SerializerSettings,
) -> GeometrySerializer: ...
def create_shape(*args): ... def create_shape(*args): ...
def flatten(deep): ... def flatten(deep): ...
def get_feature(x): ... def get_feature(x): ...
+28 -11
View File
@@ -634,17 +634,34 @@ struct ShapeRTTI : public boost::static_visitor<PyObject*>
$result = std::visit(ShapeRTTI(), (std::variant<IfcGeom::Element*, IfcGeom::Representation::Representation*, IfcGeom::Transformation*>) $1); $result = std::visit(ShapeRTTI(), (std::variant<IfcGeom::Element*, IfcGeom::Representation::Representation*, IfcGeom::Transformation*>) $1);
} }
%newobject construct_iterator; %newobject construct_iterator;
%newobject construct_iterator_with_include_exclude; %newobject construct_iterator_with_include_exclude;
%newobject construct_iterator_with_include_exclude_globalid; %newobject construct_iterator_with_include_exclude_globalid;
%newobject construct_iterator_with_include_exclude_id; %newobject construct_iterator_with_include_exclude_id;
%newobject create_geometry_serializer;
// I couldn't get the vector<string> typemap to be applied when %extending Iterator constructor.
// anyway it does not matter as SWIG generates C code without actual constructors // I couldn't get the vector<string> typemap to be applied when %extending Iterator constructor.
%inline %{ // anyway it does not matter as SWIG generates C code without actual constructors
IfcGeom::Iterator* construct_iterator(const std::string& geometry_library, ifcopenshell::geometry::Settings settings, ifcopenshell::file* file, int num_threads) { %inline %{
return new IfcGeom::Iterator(ifcopenshell::geometry::kernels::construct(file, geometry_library, settings), settings, file, num_threads); GeometrySerializer* create_geometry_serializer(
} const std::string& extension,
const std::string& output_filename,
const std::string& output_temp_filename,
ifcopenshell::geometry::Settings& geometry_settings,
const ifcopenshell::geometry::SerializerSettings& serializer_settings
) {
return new PythonPluginGeometrySerializer(
extension,
output_filename,
output_temp_filename,
geometry_settings,
serializer_settings
);
}
IfcGeom::Iterator* construct_iterator(const std::string& geometry_library, ifcopenshell::geometry::Settings settings, ifcopenshell::file* file, int num_threads) {
return new IfcGeom::Iterator(ifcopenshell::geometry::kernels::construct(file, geometry_library, settings), settings, file, num_threads);
}
IfcGeom::Iterator* construct_iterator_with_include_exclude(const std::string& geometry_library, ifcopenshell::geometry::Settings settings, ifcopenshell::file* file, std::vector<std::string> elems, bool include, int num_threads) { IfcGeom::Iterator* construct_iterator_with_include_exclude(const std::string& geometry_library, ifcopenshell::geometry::Settings settings, ifcopenshell::file* file, std::vector<std::string> elems, bool include, int num_threads) {
std::set<std::string> elems_set(elems.begin(), elems.end()); std::set<std::string> elems_set(elems.begin(), elems.end());