diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt index cbc6704179..bd7730cd15 100644 --- a/cmake/CMakeLists.txt +++ b/cmake/CMakeLists.txt @@ -562,6 +562,8 @@ if(NOT Boost_VERSION LESS 105800) add_definitions(-DBOOST_OPTIONAL_USE_OLD_DEFINITION_OF_NONE) endif() +add_subdirectory(../src/plugin plugin) + add_subdirectory(../src/ifcparse ifcparse) set(IFCOPENSHELL_LIBRARIES IfcParse) diff --git a/src/ifcgeom/AbstractKernel.h b/src/ifcgeom/AbstractKernel.h index e950f665e3..eeb8614351 100644 --- a/src/ifcgeom/AbstractKernel.h +++ b/src/ifcgeom/AbstractKernel.h @@ -28,6 +28,8 @@ #include "../ifcgeom/ConversionSettings.h" #include "../ifcgeom/abstract_mapping.h" +#include + static const double ALMOST_ZERO = 1.e-9; template @@ -79,6 +81,12 @@ namespace ifcopenshell { const std::string& geometry_library() const { return geometry_library_; } + virtual std::string_view backend_id() const { + return geometry_library_; + } + virtual bool accepts(const IfcGeom::ConversionResultShape& shape) const { + return shape.backend_id() == backend_id(); + } virtual bool supports_boolean_operations() const = 0; @@ -238,4 +246,4 @@ namespace { }; } -#endif \ No newline at end of file +#endif diff --git a/src/ifcgeom/CMakeLists.txt b/src/ifcgeom/CMakeLists.txt index 143caf2159..7e560fb424 100644 --- a/src/ifcgeom/CMakeLists.txt +++ b/src/ifcgeom/CMakeLists.txt @@ -14,6 +14,7 @@ file(GLOB SCHEMA_AGNOSTIC_CPP_FILES *.cpp) set(SCHEMA_AGNOSTIC_FILES ${SCHEMA_AGNOSTIC_H_FILES} ${SCHEMA_AGNOSTIC_CPP_FILES}) add_library(IfcGeom ${SCHEMA_AGNOSTIC_FILES}) +add_library(geometry ALIAS IfcGeom) set_target_properties(IfcGeom PROPERTIES COMPILE_FLAGS -DIFC_GEOM_EXPORTS VERSION "${PROJECT_VERSION}" SOVERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}") if(UNIX) @@ -23,11 +24,11 @@ endif() find_package(Eigen3 REQUIRED) if(WASM_BUILD) - target_link_libraries(IfcGeom ${kernel_libraries} ${mapping_libraries} ${CMAKE_THREAD_LIBS_INIT} "Eigen3::Eigen") + target_link_libraries(IfcGeom plugin ${kernel_libraries} ${mapping_libraries} ${CMAKE_THREAD_LIBS_INIT} "Eigen3::Eigen") else() - target_link_libraries(IfcGeom IfcParse ${kernel_libraries} ${mapping_libraries} ${CMAKE_THREAD_LIBS_INIT} "Eigen3::Eigen") + target_link_libraries(IfcGeom plugin IfcParse ${kernel_libraries} ${mapping_libraries} ${CMAKE_THREAD_LIBS_INIT} "Eigen3::Eigen") endif() install(FILES ${SCHEMA_AGNOSTIC_H_FILES} DESTINATION ${INCLUDEDIR}/ifcgeom -) \ No newline at end of file +) diff --git a/src/ifcgeom/ConversionResult.h b/src/ifcgeom/ConversionResult.h index 43b25669a2..98b66afc3e 100644 --- a/src/ifcgeom/ConversionResult.h +++ b/src/ifcgeom/ConversionResult.h @@ -25,6 +25,7 @@ #include "../ifcgeom/taxonomy.h" #include +#include #include #include @@ -253,6 +254,7 @@ namespace IfcGeom { class IFC_GEOM_API ConversionResultShape { public: + virtual std::string_view backend_id() const = 0; virtual void Triangulate(ifcopenshell::geometry::Settings settings, const ifcopenshell::geometry::taxonomy::matrix4& place, Representation::Triangulation* t, int item_id, int surface_style_id) const = 0; IfcGeom::Representation::Triangulation* Triangulate(const ifcopenshell::geometry::Settings& settings) const; virtual void Serialize(const ifcopenshell::geometry::taxonomy::matrix4& place, std::string&) const = 0; diff --git a/src/ifcgeom/GeometrySerializer.h b/src/ifcgeom/GeometrySerializer.h index b50394eecd..603b48a261 100644 --- a/src/ifcgeom/GeometrySerializer.h +++ b/src/ifcgeom/GeometrySerializer.h @@ -103,7 +103,7 @@ class SerializerSettings : public SettingsContainer < } } -class stream_or_filename { +class IFC_GEOM_API stream_or_filename { private: std::shared_ptr ofs_; std::shared_ptr oss_; @@ -139,7 +139,7 @@ public: } }; -class GeometrySerializer : public Serializer { +class IFC_GEOM_API GeometrySerializer : public Serializer { public: enum read_type { READ_BREP, READ_TRIANGULATION }; @@ -175,7 +175,7 @@ protected: ifcopenshell::geometry::SerializerSettings settings_; }; -class WriteOnlyGeometrySerializer : public GeometrySerializer { +class IFC_GEOM_API WriteOnlyGeometrySerializer : public GeometrySerializer { public: WriteOnlyGeometrySerializer(const ifcopenshell::geometry::Settings& geometry_settings, const ifcopenshell::geometry::SerializerSettings& settings) : GeometrySerializer(geometry_settings, settings) {} diff --git a/src/ifcgeom/Serialization/CMakeLists.txt b/src/ifcgeom/Serialization/CMakeLists.txt index 9031c08148..454de685a6 100644 --- a/src/ifcgeom/Serialization/CMakeLists.txt +++ b/src/ifcgeom/Serialization/CMakeLists.txt @@ -1,8 +1,9 @@ add_subdirectory(schema) -add_library(geometry_serializer STATIC Serialization.cpp) +add_library(geometry_serializer Serialization.cpp) +add_library(opencascade_geometry_ifc_writer ALIAS geometry_serializer) set_target_properties(geometry_serializer PROPERTIES COMPILE_FLAGS "-DIFC_GEOMSERIALIZATION_EXPORTS") -target_link_libraries(geometry_serializer ${geometry_serializer_libraries} IfcParse) +target_link_libraries(geometry_serializer plugin ${geometry_serializer_libraries} IfcParse) set(IFCOPENSHELL_LIBRARIES ${IFCOPENSHELL_LIBRARIES} "geometry_serializer" ${geometry_serializer_libraries}) set(IFCOPENSHELL_LIBRARIES ${IFCOPENSHELL_LIBRARIES} PARENT_SCOPE) diff --git a/src/ifcgeom/Serialization/Serialization.cpp b/src/ifcgeom/Serialization/Serialization.cpp index 04fa3b2e09..7c1c4d0022 100644 --- a/src/ifcgeom/Serialization/Serialization.cpp +++ b/src/ifcgeom/Serialization/Serialization.cpp @@ -1,59 +1,108 @@ #include "Serialization.h" -#include - -#include -#include -#include - +#include "../../ifcgeom/kernels/opencascade/OpenCascadeConversionResult.h" #include "../../ifcparse/file.h" -#define EXTERNAL_DEFS_1(r, data, elem) \ - express::Base BOOST_PP_CAT(tesselate_Ifc, elem)(ifcopenshell::file&, const TopoDS_Shape& shape, double deflection); +#include +#include +#include -#define EXTERNAL_DEFS_2(r, data, elem) \ - express::Base BOOST_PP_CAT(serialise_Ifc, elem)(ifcopenshell::file&, const TopoDS_Shape& shape, bool advanced); +#include -#define CONDITIONAL_CALL(r, data, elem) \ - if (schema_name_lower == BOOST_PP_STRINGIZE(BOOST_PP_CAT(elem,))) { \ - return BOOST_PP_CAT(METHOD_NAME, elem)(f, shape, arg_2); \ +namespace { + std::string writer_schema_key(const std::string& schema_name) { + return boost::to_upper_copy(schema_name); } -BOOST_PP_SEQ_FOR_EACH(EXTERNAL_DEFS_1, , SCHEMA_SEQ); -BOOST_PP_SEQ_FOR_EACH(EXTERNAL_DEFS_2, , SCHEMA_SEQ); + ifcopenshell::plugin::module builtin_writer_module(const std::string& schema_name) { + ifcopenshell::plugin::metadata metadata; + metadata.kind_ = ifcopenshell::plugin::kind::opencascade_geometry_ifc_writer; + metadata.id = "geometry.ifc_writer." + boost::to_lower_copy(schema_name); + metadata.schema = schema_name; + return ifcopenshell::plugin::module::builtin(metadata); + } -#define METHOD_NAME tesselate_Ifc + const ifcopenshell::geometry::OpenCascadeShape& require_opencascade_shape(const IfcGeom::ConversionResultShape& shape) { + const auto* oc_shape = dynamic_cast(&shape); + if (!oc_shape) { + throw ifcopenshell::exception("OpenCascade geometry IFC writer requires an opencascade conversion result shape"); + } + return *oc_shape; + } -express::Base IfcGeom::tesselate(ifcopenshell::file& f, const TopoDS_Shape& shape, double arg_2) { - auto schema_name = f.schema()->name(); + template + express::Base serialise_adapter(ifcopenshell::file& f, const IfcGeom::ConversionResultShape& shape, bool advanced) { + return Fn(f, require_opencascade_shape(shape).shape(), advanced); + } - // @todo an ugly hack to guarantee schemas are initialised. - // @todo is this still needed? parsing a file should have initialized the schemas already. - try { - ifcopenshell::schema_by_name("IFC2X3"); - } catch (ifcopenshell::exception&) {} - - const std::string schema_name_lower = boost::to_lower_copy(schema_name.substr(3)); - - BOOST_PP_SEQ_FOR_EACH(CONDITIONAL_CALL, , SCHEMA_SEQ); - - throw ifcopenshell::exception("No geometry serialization available for " + schema_name); + template + express::Base tesselate_adapter(ifcopenshell::file& f, const IfcGeom::ConversionResultShape& shape, double deflection) { + return Fn(f, require_opencascade_shape(shape).shape(), deflection); + } } -#undef METHOD_NAME -#define METHOD_NAME serialise_Ifc +#define EXTERNAL_TESSELATE(r, data, elem) \ + express::Base BOOST_PP_CAT(tesselate_Ifc, elem)(ifcopenshell::file&, const TopoDS_Shape& shape, double deflection); -express::Base IfcGeom::serialise(ifcopenshell::file& f, const TopoDS_Shape& shape, bool arg_2) { - auto schema_name = f.schema()->name(); +#define EXTERNAL_SERIALISE(r, data, elem) \ + express::Base BOOST_PP_CAT(serialise_Ifc, elem)(ifcopenshell::file&, const TopoDS_Shape& shape, bool advanced); - // @todo an ugly hack to guarantee schemas are initialised. - try { - ifcopenshell::schema_by_name("IFC2X3"); - } catch (ifcopenshell::exception&) {} +#define BIND_WRITER(r, data, elem) \ + registry.bind(BOOST_PP_STRINGIZE(BOOST_PP_CAT(Ifc, elem)), \ + &serialise_adapter, \ + &tesselate_adapter, \ + builtin_writer_module(BOOST_PP_STRINGIZE(BOOST_PP_CAT(Ifc, elem)))); - const std::string schema_name_lower = boost::to_lower_copy(schema_name.substr(3)); +BOOST_PP_SEQ_FOR_EACH(EXTERNAL_TESSELATE, , SCHEMA_SEQ) +BOOST_PP_SEQ_FOR_EACH(EXTERNAL_SERIALISE, , SCHEMA_SEQ) - BOOST_PP_SEQ_FOR_EACH(CONDITIONAL_CALL, , SCHEMA_SEQ); +IfcGeom::opencascade_geometry_ifc_writer_registry& IfcGeom::opencascade_geometry_ifc_writer_registry_instance() { + static opencascade_geometry_ifc_writer_registry registry; + static std::once_flag once; + std::call_once(once, []() { + BOOST_PP_SEQ_FOR_EACH(BIND_WRITER, , SCHEMA_SEQ) + }); + return registry; +} - throw ifcopenshell::exception("No geometry serialization available for " + schema_name); -} \ No newline at end of file +void IfcGeom::opencascade_geometry_ifc_writer_registry::bind(const std::string& schema_name, serialise_fn serialise, tesselate_fn tesselate, const ifcopenshell::plugin::module& module) { + entry entry; + entry.serialise_ = serialise; + entry.tesselate_ = tesselate; + entry.module_ = module; + entries_[writer_schema_key(schema_name)] = entry; +} + +express::Base IfcGeom::opencascade_geometry_ifc_writer_registry::tesselate(ifcopenshell::file& f, const ConversionResultShape& shape, double deflection) const { + const auto iter = entries_.find(writer_schema_key(f.schema()->name())); + if (iter == entries_.end()) { + throw ifcopenshell::exception("No geometry serialization available for " + f.schema()->name()); + } + return iter->second.tesselate_(f, shape, deflection); +} + +express::Base IfcGeom::opencascade_geometry_ifc_writer_registry::serialise(ifcopenshell::file& f, const ConversionResultShape& shape, bool advanced) const { + const auto iter = entries_.find(writer_schema_key(f.schema()->name())); + if (iter == entries_.end()) { + throw ifcopenshell::exception("No geometry serialization available for " + f.schema()->name()); + } + return iter->second.serialise_(f, shape, advanced); +} + +express::Base IfcGeom::tesselate(ifcopenshell::file& f, const ConversionResultShape& shape, double deflection) { + return opencascade_geometry_ifc_writer_registry_instance().tesselate(f, shape, deflection); +} + +express::Base IfcGeom::serialise(ifcopenshell::file& f, const ConversionResultShape& shape, bool advanced) { + return opencascade_geometry_ifc_writer_registry_instance().serialise(f, shape, advanced); +} + +express::Base IfcGeom::tesselate(ifcopenshell::file& f, const TopoDS_Shape& shape, double deflection) { + ifcopenshell::geometry::OpenCascadeShape converted(shape); + return tesselate(f, converted, deflection); +} + +express::Base IfcGeom::serialise(ifcopenshell::file& f, const TopoDS_Shape& shape, bool advanced) { + ifcopenshell::geometry::OpenCascadeShape converted(shape); + return serialise(f, converted, advanced); +} diff --git a/src/ifcgeom/Serialization/Serialization.h b/src/ifcgeom/Serialization/Serialization.h index 243465715a..72a2232405 100644 --- a/src/ifcgeom/Serialization/Serialization.h +++ b/src/ifcgeom/Serialization/Serialization.h @@ -1,12 +1,49 @@ +#ifndef IFC_GEOM_SERIALIZATION_H +#define IFC_GEOM_SERIALIZATION_H + #include "../../ifcparse/express.h" +#include "../../plugin/plugin.h" +#include "../../ifcgeom/ConversionResult.h" #include "ifc_geomserialization_api.h" -#include +#include +#include #include +class TopoDS_Shape; + +namespace ifcopenshell { + class file; +} + namespace IfcGeom { +class IFC_GEOMSERIALIZATION_API opencascade_geometry_ifc_writer_registry { +public: + typedef boost::function3 tesselate_fn; + typedef boost::function3 serialise_fn; + + void bind(const std::string& schema_name, serialise_fn serialise, tesselate_fn tesselate, const ifcopenshell::plugin::module& module = ifcopenshell::plugin::module()); + express::Base tesselate(ifcopenshell::file& f, const ConversionResultShape& shape, double deflection) const; + express::Base serialise(ifcopenshell::file& f, const ConversionResultShape& shape, bool advanced) const; + +private: + struct entry { + serialise_fn serialise_; + tesselate_fn tesselate_; + ifcopenshell::plugin::module module_; + }; + + std::map entries_; +}; + +IFC_GEOMSERIALIZATION_API opencascade_geometry_ifc_writer_registry& opencascade_geometry_ifc_writer_registry_instance(); + +IFC_GEOMSERIALIZATION_API express::Base tesselate(ifcopenshell::file& f, const ConversionResultShape& shape, double deflection); +IFC_GEOMSERIALIZATION_API express::Base serialise(ifcopenshell::file& f, const ConversionResultShape& shape, bool advanced); IFC_GEOMSERIALIZATION_API express::Base tesselate(ifcopenshell::file& f, const TopoDS_Shape& shape, double deflection); IFC_GEOMSERIALIZATION_API express::Base serialise(ifcopenshell::file& f, const TopoDS_Shape& shape, bool advanced); } // namespace IfcGeom + +#endif diff --git a/src/ifcgeom/Serializer.h b/src/ifcgeom/Serializer.h index 3d011bd675..3db0069951 100644 --- a/src/ifcgeom/Serializer.h +++ b/src/ifcgeom/Serializer.h @@ -20,9 +20,10 @@ #ifndef SERIALIZER_H #define SERIALIZER_H +#include "ifc_geom_api.h" #include "../ifcparse/file.h" -class Serializer { +class IFC_GEOM_API Serializer { public: virtual ~Serializer() {} @@ -32,4 +33,4 @@ public: virtual void setFile(ifcopenshell::file*) = 0; }; -#endif \ No newline at end of file +#endif diff --git a/src/ifcgeom/abstract_mapping.cpp b/src/ifcgeom/abstract_mapping.cpp index 99d433e125..de26611c55 100644 --- a/src/ifcgeom/abstract_mapping.cpp +++ b/src/ifcgeom/abstract_mapping.cpp @@ -4,47 +4,80 @@ #include #include +#include #ifndef SCHEMA_SEQ static_assert(false, "A boost preprocessor sequence of schema identifiers is needed for this file to compile."); #endif +// Declares the schema-based external kernel initialization routines: +// - extern void init_MappingImplementation_Ifc2x3(IfcGeom::impl::mapping_registry*); +// - ... +#define EXTERNAL_DEFS(r, data, elem) \ + extern void BOOST_PP_CAT(init_MappingImplementation_Ifc, elem)(ifcopenshell::geometry::impl::mapping_registry*); + +// Declares the schema-based external iterator initialization routines: +// - init_MappingImplementation_Ifc2x3(this); +// - ... +#define CALL_DEFS(r, data, elem) \ + BOOST_PP_CAT(init_MappingImplementation_Ifc, elem)(®istry); + +BOOST_PP_SEQ_FOR_EACH(EXTERNAL_DEFS, , SCHEMA_SEQ) + +namespace { + std::string mapping_key(const std::string& schema_name) { + return boost::to_lower_copy(schema_name); + } + + ifcopenshell::plugin::module builtin_mapping_module(const std::string& schema_name) { + ifcopenshell::plugin::metadata metadata; + metadata.kind_ = ifcopenshell::plugin::kind::mapping; + metadata.id = "geometry.mapping." + boost::to_lower_copy(schema_name); + metadata.schema = schema_name; + return ifcopenshell::plugin::module::builtin(metadata); + } + +} + +ifcopenshell::geometry::impl::mapping_registry& ifcopenshell::geometry::impl::mapping_registry_instance() { + static mapping_registry registry; + static std::once_flag once; + std::call_once(once, []() { + BOOST_PP_SEQ_FOR_EACH(CALL_DEFS, , SCHEMA_SEQ) + }); + return registry; +} + +void ifcopenshell::geometry::impl::mapping_registry::bind(const std::string& schema_name, ifcopenshell::geometry::impl::mapping_fn fn, const plugin::module& module) { + auto& entry = entries_[mapping_key(schema_name)]; + entry.fn_ = fn; + entry.module_ = module.meta().id.empty() ? builtin_mapping_module(schema_name) : module; +} + +ifcopenshell::geometry::abstract_mapping* ifcopenshell::geometry::impl::mapping_registry::construct(ifcopenshell::file* file, Settings& s) { + const std::string schema_name_lower = boost::to_lower_copy(file->schema()->name()); + const auto it = entries_.find(schema_name_lower); + if (it == entries_.end()) { + throw ifcopenshell::exception("No geometry mapping registered for " + schema_name_lower); + } + auto new_mapping = it->second.fn_(file, s); + new_mapping->initialize_settings(); + return new_mapping; +} + ifcopenshell::geometry::impl::MappingFactoryImplementation& ifcopenshell::geometry::impl::mapping_implementations() { static MappingFactoryImplementation impl; return impl; } -// Declares the schema-based external kernel initialization routines: -// - extern void init_MappingImplementation_Ifc2x3(IfcGeom::impl::MappingFactoryImplementation*); -// - ... -#define EXTERNAL_DEFS(r, data, elem) \ - extern void BOOST_PP_CAT(init_MappingImplementation_Ifc, elem)(ifcopenshell::geometry::impl::MappingFactoryImplementation*); - -// Declares the schema-based external iterator initialization routines: -// - init_MappingImplementation_Ifc2x3(this); -// - ... -#define CALL_DEFS(r, data, elem) \ - BOOST_PP_CAT(init_MappingImplementation_Ifc, elem)(this); - -BOOST_PP_SEQ_FOR_EACH(EXTERNAL_DEFS, , SCHEMA_SEQ) - ifcopenshell::geometry::impl::MappingFactoryImplementation::MappingFactoryImplementation() { - BOOST_PP_SEQ_FOR_EACH(CALL_DEFS, , SCHEMA_SEQ) + mapping_registry_instance(); } void ifcopenshell::geometry::impl::MappingFactoryImplementation::bind(const std::string& schema_name, ifcopenshell::geometry::impl::mapping_fn fn) { - const std::string schema_name_lower = boost::to_lower_copy(schema_name); - this->insert(std::make_pair(schema_name_lower, fn)); + mapping_registry_instance().bind(schema_name, fn, builtin_mapping_module(schema_name)); } ifcopenshell::geometry::abstract_mapping* ifcopenshell::geometry::impl::MappingFactoryImplementation::construct(ifcopenshell::file* file, Settings& s) { - const std::string schema_name_lower = boost::to_lower_copy(file->schema()->name()); - std::map::const_iterator it; - it = this->find(schema_name_lower); - if (it == end()) { - throw ifcopenshell::exception("No geometry mapping registered for " + schema_name_lower); - } - auto new_mapping = it->second(file, s); - new_mapping->initialize_settings(); - return new_mapping; + return mapping_registry_instance().construct(file, s); } diff --git a/src/ifcgeom/abstract_mapping.h b/src/ifcgeom/abstract_mapping.h index cecd3acf0f..e49f4851c2 100644 --- a/src/ifcgeom/abstract_mapping.h +++ b/src/ifcgeom/abstract_mapping.h @@ -23,6 +23,7 @@ #include "../ifcparse/express.h" #include "../ifcgeom/taxonomy.h" #include "../ifcgeom/ConversionSettings.h" +#include "../plugin/plugin.h" #include @@ -79,7 +80,23 @@ namespace geometry { namespace impl { typedef boost::function2 mapping_fn; - class IFC_GEOM_API MappingFactoryImplementation : public std::map { + class IFC_GEOM_API mapping_registry { + public: + void bind(const std::string& schema_name, mapping_fn fn, const plugin::module& module = plugin::module()); + abstract_mapping* construct(ifcopenshell::file* file, Settings& settings); + + private: + struct entry { + mapping_fn fn_; + plugin::module module_; + }; + + std::map entries_; + }; + + IFC_GEOM_API mapping_registry& mapping_registry_instance(); + + class IFC_GEOM_API MappingFactoryImplementation { public: MappingFactoryImplementation(); void bind(const std::string& schema_name, mapping_fn); diff --git a/src/ifcgeom/hybrid_kernel.h b/src/ifcgeom/hybrid_kernel.h index cb1613baf0..c529ba9d86 100644 --- a/src/ifcgeom/hybrid_kernel.h +++ b/src/ifcgeom/hybrid_kernel.h @@ -21,52 +21,7 @@ #define HYBRID_KERNEL_H #include "AbstractKernel.h" - -#ifdef IFOPSH_WITH_OPENCASCADE -#include "../ifcgeom/kernels/opencascade/OpenCascadeKernel.h" -#undef Handle -#endif - -#ifdef IFOPSH_WITH_CGAL -#include "../ifcgeom/kernels/cgal/CgalKernel.h" -#undef CGAL_KERNEL_H -#undef CGALCONVERSIONRESULT_H -#define IFOPSH_SIMPLE_KERNEL -#include "../ifcgeom/kernels/cgal/CgalKernel.h" -#undef CgalKernel -#endif - -#ifdef IFOPSH_WITH_MANIFOLD -#include "../ifcgeom/kernels/manifold/ManifoldKernel.h" -#endif -#include "../ifcgeom/kernels/passthrough/PassthroughKernel.h" - -namespace { - inline bool is_valid_for_kernel(const ifcopenshell::geometry::kernels::AbstractKernel* k, const IfcGeom::ConversionResult& shp) { -#ifdef IFOPSH_WITH_OPENCASCADE - if (k->geometry_library() == "opencascade") { - return dynamic_cast(shp.Shape().get()) != nullptr; - } -#endif -#ifdef IFOPSH_WITH_CGAL - if (k->geometry_library() == "cgal-simple") { - return dynamic_cast(shp.Shape().get()) != nullptr; - } - if (k->geometry_library() == "cgal") { - return dynamic_cast(shp.Shape().get()) != nullptr; - } -#endif -#ifdef IFOPSH_WITH_MANIFOLD - if (k->geometry_library() == "manifold") { - return dynamic_cast(shp.Shape().get()) != nullptr; - } -#endif - if (k->geometry_library() == "passthrough") { - return dynamic_cast(shp.Shape().get()) != nullptr; - } - return false; - } -} +#include "kernel_registry.h" namespace ifcopenshell { namespace geometry { @@ -154,7 +109,7 @@ namespace ifcopenshell { for (auto& k : kernels_) { bool is_valid = true; for (auto& s : entity_shapes) { - if (!is_valid_for_kernel(k.get(), s)) { + if (!k->accepts(*s.Shape())) { is_valid = false; break; } @@ -182,91 +137,6 @@ namespace ifcopenshell { return new HybridKernel(geometry_library(), file_, const_cast(settings()), std::move(ks)); } }; - - inline std::unique_ptr construct(ifcopenshell::file* file, const std::string& geometry_library, Settings& conv_settings) { - std::string geometry_library_lower = boost::to_lower_copy(geometry_library); - -#ifdef IFOPSH_WITH_OPENCASCADE - if (geometry_library_lower == "opencascade") { - return std::make_unique(conv_settings); - } -#endif - -#ifdef IFOPSH_WITH_CGAL - if (geometry_library_lower == "cgal") { - return std::make_unique(conv_settings); - } - - if (geometry_library_lower == "cgal-simple") { - return std::make_unique(conv_settings); - } -#endif - -#ifdef IFOPSH_WITH_MANIFOLD - if (geometry_library_lower == "manifold") { - return std::make_unique(conv_settings); - } -#endif - if (geometry_library_lower == "passthrough") { - return std::make_unique(conv_settings); - } - - if (geometry_library_lower.rfind("hybrid-", 0) == 0) { - geometry_library_lower = geometry_library_lower.substr(strlen("hybrid")); - std::vector> kernels; - while (!geometry_library_lower.empty()) { - if (geometry_library_lower.find("-", 0) == 0) { - geometry_library_lower = geometry_library_lower.substr(strlen("-")); - } else { - throw ifcopenshell::exception("Invalid hybrid kernel " + geometry_library); - } - auto n = kernels.size(); -#ifdef IFOPSH_WITH_OPENCASCADE - if (geometry_library_lower.find("opencascade", 0) == 0) { - kernels.emplace_back(new IfcGeom::OpenCascadeKernel(conv_settings)); - geometry_library_lower = geometry_library_lower.substr(strlen("opencascade")); - } -#endif - -#ifdef IFOPSH_WITH_CGAL - if (geometry_library_lower.find("cgal-simple", 0) == 0) { - kernels.emplace_back(new SimpleCgalKernel(conv_settings)); - geometry_library_lower = geometry_library_lower.substr(strlen("cgal-simple")); - } - - if (geometry_library_lower.find("cgal", 0) == 0) { - kernels.emplace_back(new CgalKernel(conv_settings)); - geometry_library_lower = geometry_library_lower.substr(strlen("cgal")); - } -#endif -#ifdef IFOPSH_WITH_MANIFOLD - if (geometry_library_lower.find("manifold", 0) == 0) { - kernels.emplace_back(new ManifoldKernel(conv_settings)); - geometry_library_lower = geometry_library_lower.substr(strlen("manifold")); - } -#endif - if (geometry_library_lower.find("passthrough", 0) == 0) { - kernels.emplace_back(new PassthroughKernel(conv_settings)); - geometry_library_lower = geometry_library_lower.substr(strlen("passthrough")); - } - if (kernels.size() != n + 1) { - throw ifcopenshell::exception("Invalid hybrid kernel " + geometry_library); - } - } - - for (auto it = kernels.begin(); it != kernels.end(); ++it) { - (**it).propagate_exceptions = it == kernels.begin(); - (**it).partial_success_is_success = it == kernels.end() - 1; - } - - if (!kernels.empty()) { - return std::make_unique(geometry_library, file, conv_settings, std::move(kernels)); - } - } - - throw ifcopenshell::exception("No geometry kernel registered for " + geometry_library); - } - } } } diff --git a/src/ifcgeom/kernel_registry.cpp b/src/ifcgeom/kernel_registry.cpp new file mode 100644 index 0000000000..7fc8d04817 --- /dev/null +++ b/src/ifcgeom/kernel_registry.cpp @@ -0,0 +1,174 @@ +/******************************************************************************** + * * + * This file is part of IfcOpenShell. * + * * + * IfcOpenShell is free software: you can redistribute it and/or modify * + * it under the terms of the Lesser GNU General Public License as published by * + * the Free Software Foundation, either version 3.0 of the License, or * + * (at your option) any later version. * + * * + * IfcOpenShell is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * Lesser GNU General Public License for more details. * + * * + * You should have received a copy of the Lesser GNU General Public License * + * along with this program. If not, see . * + * * + ********************************************************************************/ + +#include "kernel_registry.h" + +#include "../ifcgeom/hybrid_kernel.h" +#include "../ifcparse/file.h" + +#include + +#include +#include + +#ifdef IFOPSH_WITH_OPENCASCADE +#include "../ifcgeom/kernels/opencascade/OpenCascadeKernel.h" +#undef Handle +#endif + +#ifdef IFOPSH_WITH_CGAL +#include "../ifcgeom/kernels/cgal/CgalKernel.h" +#undef CGAL_KERNEL_H +#undef CGALCONVERSIONRESULT_H +#define IFOPSH_SIMPLE_KERNEL +#include "../ifcgeom/kernels/cgal/CgalKernel.h" +#undef CgalKernel +#undef IFOPSH_SIMPLE_KERNEL +#endif + +#ifdef IFOPSH_WITH_MANIFOLD +#include "../ifcgeom/kernels/manifold/ManifoldKernel.h" +#endif +#include "../ifcgeom/kernels/passthrough/PassthroughKernel.h" + +namespace { + std::string kernel_key(const std::string& backend_id) { + return boost::to_lower_copy(backend_id); + } + + ifcopenshell::plugin::module builtin_kernel_module(const std::string& backend_id) { + ifcopenshell::plugin::metadata metadata; + metadata.kind_ = ifcopenshell::plugin::kind::kernel; + metadata.id = "geometry.kernel." + kernel_key(backend_id); + return ifcopenshell::plugin::module::builtin(metadata); + } + + void register_builtin_kernels(ifcopenshell::geometry::kernels::kernel_registry& registry) { +#ifdef IFOPSH_WITH_OPENCASCADE + ifcopenshell::geometry::kernels::kernel_info opencascade_info; + opencascade_info.backend_id = "opencascade"; + opencascade_info.supports_boolean_operations = true; + registry.bind(opencascade_info, [](ifcopenshell::file*, ifcopenshell::geometry::Settings& settings) { return new IfcGeom::OpenCascadeKernel(settings); }, builtin_kernel_module("opencascade")); +#endif +#ifdef IFOPSH_WITH_CGAL + ifcopenshell::geometry::kernels::kernel_info cgal_info; + cgal_info.backend_id = "cgal"; + cgal_info.supports_boolean_operations = true; + registry.bind(cgal_info, [](ifcopenshell::file*, ifcopenshell::geometry::Settings& settings) { return new ifcopenshell::geometry::kernels::CgalKernel(settings); }, builtin_kernel_module("cgal")); + + ifcopenshell::geometry::kernels::kernel_info cgal_simple_info; + cgal_simple_info.backend_id = "cgal-simple"; + cgal_simple_info.supports_boolean_operations = false; + registry.bind(cgal_simple_info, [](ifcopenshell::file*, ifcopenshell::geometry::Settings& settings) { return new ifcopenshell::geometry::kernels::SimpleCgalKernel(settings); }, builtin_kernel_module("cgal-simple")); +#endif +#ifdef IFOPSH_WITH_MANIFOLD + ifcopenshell::geometry::kernels::kernel_info manifold_info; + manifold_info.backend_id = "manifold"; + manifold_info.supports_boolean_operations = true; + registry.bind(manifold_info, [](ifcopenshell::file*, ifcopenshell::geometry::Settings& settings) { return new ifcopenshell::geometry::kernels::ManifoldKernel(settings); }, builtin_kernel_module("manifold")); +#endif + ifcopenshell::geometry::kernels::kernel_info passthrough_info; + passthrough_info.backend_id = "passthrough"; + passthrough_info.supports_boolean_operations = false; + registry.bind(passthrough_info, [](ifcopenshell::file*, ifcopenshell::geometry::Settings& settings) { return new ifcopenshell::geometry::kernels::PassthroughKernel(settings); }, builtin_kernel_module("passthrough")); + } + +} + +void ifcopenshell::geometry::kernels::kernel_registry::bind(const kernel_info& info, create_fn create, const plugin::module& module) { + entry entry; + entry.info_ = info; + entry.create_ = create; + entry.module_ = module; + entries_[kernel_key(info.backend_id)] = entry; +} + +bool ifcopenshell::geometry::kernels::kernel_registry::has(const std::string& backend_id) const { + return entries_.find(kernel_key(backend_id)) != entries_.end(); +} + +std::unique_ptr ifcopenshell::geometry::kernels::kernel_registry::create(const std::string& backend_id, ifcopenshell::file* file, Settings& settings) const { + const auto iter = entries_.find(kernel_key(backend_id)); + if (iter == entries_.end()) { + throw ifcopenshell::exception("No geometry kernel registered for " + backend_id); + } + return std::unique_ptr(iter->second.create_(file, settings)); +} + +std::vector ifcopenshell::geometry::kernels::kernel_registry::kernels() const { + std::vector result; + for (const auto& pair : entries_) { + result.push_back(pair.second.info_); + } + return result; +} + +ifcopenshell::geometry::kernels::kernel_registry& ifcopenshell::geometry::kernels::kernel_registry_instance() { + static kernel_registry registry; + static std::once_flag once; + std::call_once(once, register_builtin_kernels, std::ref(registry)); + return registry; +} + +std::unique_ptr ifcopenshell::geometry::kernels::construct(ifcopenshell::file* file, const std::string& geometry_library, Settings& settings) { + auto geometry_library_lower = boost::to_lower_copy(geometry_library); + auto& registry = kernel_registry_instance(); + + if (registry.has(geometry_library_lower)) { + return registry.create(geometry_library_lower, file, settings); + } + + if (geometry_library_lower.rfind("hybrid-", 0) == 0) { + geometry_library_lower = geometry_library_lower.substr(strlen("hybrid")); + std::vector> kernels; + while (!geometry_library_lower.empty()) { + if (geometry_library_lower.find("-", 0) == 0) { + geometry_library_lower = geometry_library_lower.substr(strlen("-")); + } else { + throw ifcopenshell::exception("Invalid hybrid kernel " + geometry_library); + } + + std::string matched_backend_id; + for (const auto& info : registry.kernels()) { + const auto backend_id = kernel_key(info.backend_id); + if (geometry_library_lower.rfind(backend_id, 0) == 0 && backend_id.size() > matched_backend_id.size()) { + matched_backend_id = backend_id; + } + } + + if (matched_backend_id.empty()) { + throw ifcopenshell::exception("Invalid hybrid kernel " + geometry_library); + } + + kernels.push_back(registry.create(matched_backend_id, file, settings)); + geometry_library_lower = geometry_library_lower.substr(matched_backend_id.size()); + } + + for (auto it = kernels.begin(); it != kernels.end(); ++it) { + (**it).propagate_exceptions = it == kernels.begin(); + (**it).partial_success_is_success = it == kernels.end() - 1; + } + + if (!kernels.empty()) { + return std::make_unique(geometry_library, file, settings, std::move(kernels)); + } + } + + throw ifcopenshell::exception("No geometry kernel registered for " + geometry_library); +} diff --git a/src/ifcgeom/kernel_registry.h b/src/ifcgeom/kernel_registry.h new file mode 100644 index 0000000000..cab91a0ec6 --- /dev/null +++ b/src/ifcgeom/kernel_registry.h @@ -0,0 +1,70 @@ +/******************************************************************************** + * * + * This file is part of IfcOpenShell. * + * * + * IfcOpenShell is free software: you can redistribute it and/or modify * + * it under the terms of the Lesser GNU General Public License as published by * + * the Free Software Foundation, either version 3.0 of the License, or * + * (at your option) any later version. * + * * + * IfcOpenShell is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * Lesser GNU General Public License for more details. * + * * + * You should have received a copy of the Lesser GNU General Public License * + * along with this program. If not, see . * + * * + ********************************************************************************/ + +#ifndef KERNEL_REGISTRY_H +#define KERNEL_REGISTRY_H + +#include "../ifcgeom/AbstractKernel.h" +#include "../plugin/plugin.h" + +#include + +#include +#include +#include +#include + +namespace ifcopenshell { + class file; + + namespace geometry { + namespace kernels { + + struct IFC_GEOM_API kernel_info { + std::string backend_id; + bool supports_boolean_operations = false; + }; + + class IFC_GEOM_API kernel_registry { + public: + typedef boost::function2 create_fn; + + void bind(const kernel_info& info, create_fn create, const plugin::module& module = plugin::module()); + bool has(const std::string& backend_id) const; + std::unique_ptr create(const std::string& backend_id, ifcopenshell::file* file, Settings& settings) const; + std::vector kernels() const; + + private: + struct entry { + kernel_info info_; + create_fn create_; + plugin::module module_; + }; + + std::map entries_; + }; + + IFC_GEOM_API kernel_registry& kernel_registry_instance(); + IFC_GEOM_API std::unique_ptr construct(ifcopenshell::file* file, const std::string& geometry_library, Settings& settings); + + } + } +} + +#endif diff --git a/src/ifcgeom/kernels/cgal/CgalConversionResult.h b/src/ifcgeom/kernels/cgal/CgalConversionResult.h index cb46ccaf98..2f3e48d479 100644 --- a/src/ifcgeom/kernels/cgal/CgalConversionResult.h +++ b/src/ifcgeom/kernels/cgal/CgalConversionResult.h @@ -208,6 +208,13 @@ namespace ifcopenshell { namespace geometry { operator const cgal_shape_t& () const { to_poly(); return *shape_; } const cgal_shape_t& poly() const { to_poly(); return *shape_; } + virtual std::string_view backend_id() const { +#ifdef IFOPSH_SIMPLE_KERNEL + return "cgal-simple"; +#else + return "cgal"; +#endif + } virtual void Triangulate(ifcopenshell::geometry::Settings settings, const ifcopenshell::geometry::taxonomy::matrix4& place, IfcGeom::Representation::Triangulation* t, int item_id, int surface_style_id) const; virtual void Serialize(const ifcopenshell::geometry::taxonomy::matrix4& place, std::string&) const; @@ -284,6 +291,13 @@ namespace ifcopenshell { namespace geometry { shape_.reset(new halfspace_tree_plane(shape)); planes_.push_back(shape); } + virtual std::string_view backend_id() const { +#ifdef IFOPSH_SIMPLE_KERNEL + return "cgal-simple"; +#else + return "cgal"; +#endif + } virtual void Triangulate(ifcopenshell::geometry::Settings settings, const ifcopenshell::geometry::taxonomy::matrix4& place, IfcGeom::Representation::Triangulation* t, int item_id, int surface_style_id) const; virtual void Serialize(const ifcopenshell::geometry::taxonomy::matrix4& place, std::string&) const; diff --git a/src/ifcgeom/kernels/manifold/ManifoldConversionResult.h b/src/ifcgeom/kernels/manifold/ManifoldConversionResult.h index 423da07ab6..9250dbfd53 100644 --- a/src/ifcgeom/kernels/manifold/ManifoldConversionResult.h +++ b/src/ifcgeom/kernels/manifold/ManifoldConversionResult.h @@ -26,6 +26,7 @@ public: const std::vector& parts() const { return parts_; } std::optional as_manifold() const; + virtual std::string_view backend_id() const { return "manifold"; } virtual void Triangulate(ifcopenshell::geometry::Settings settings, const ifcopenshell::geometry::taxonomy::matrix4& place, IfcGeom::Representation::Triangulation* t, int item_id, int surface_style_id) const; virtual void Serialize(const ifcopenshell::geometry::taxonomy::matrix4& place, std::string&) const; diff --git a/src/ifcgeom/kernels/opencascade/OpenCascadeConversionResult.h b/src/ifcgeom/kernels/opencascade/OpenCascadeConversionResult.h index 7d47366d35..001aaa656b 100644 --- a/src/ifcgeom/kernels/opencascade/OpenCascadeConversionResult.h +++ b/src/ifcgeom/kernels/opencascade/OpenCascadeConversionResult.h @@ -52,6 +52,7 @@ namespace ifcopenshell { const TopoDS_Shape& shape() const { return shape_; } operator const TopoDS_Shape& () { return shape_; } + virtual std::string_view backend_id() const { return "opencascade"; } virtual void Triangulate(ifcopenshell::geometry::Settings settings, const ifcopenshell::geometry::taxonomy::matrix4& place, IfcGeom::Representation::Triangulation* t, int item_id, int surface_style_id) const; virtual void Serialize(const ifcopenshell::geometry::taxonomy::matrix4& place, std::string&) const; @@ -113,4 +114,4 @@ namespace ifcopenshell { } } -#endif \ No newline at end of file +#endif diff --git a/src/ifcgeom/kernels/passthrough/PassthroughConversionResult.h b/src/ifcgeom/kernels/passthrough/PassthroughConversionResult.h index 1bf48611d3..47af9a3076 100644 --- a/src/ifcgeom/kernels/passthrough/PassthroughConversionResult.h +++ b/src/ifcgeom/kernels/passthrough/PassthroughConversionResult.h @@ -22,6 +22,7 @@ public: explicit PassthroughShape(std::vector&& parts); const std::vector& parts() const { return parts_; } + virtual std::string_view backend_id() const { return "passthrough"; } virtual void Triangulate(ifcopenshell::geometry::Settings settings, const ifcopenshell::geometry::taxonomy::matrix4& place, IfcGeom::Representation::Triangulation* t, int item_id, int surface_style_id) const; virtual void Serialize(const ifcopenshell::geometry::taxonomy::matrix4& place, std::string&) const; diff --git a/src/ifcgeom/mapping/mapping.cpp b/src/ifcgeom/mapping/mapping.cpp index 7b6449bf81..206d2e239c 100644 --- a/src/ifcgeom/mapping/mapping.cpp +++ b/src/ifcgeom/mapping/mapping.cpp @@ -39,7 +39,7 @@ namespace { }; } -void MAKE_INIT_FN(MappingImplementation)(ifcopenshell::geometry::impl::MappingFactoryImplementation* mapping) { +void MAKE_INIT_FN(MappingImplementation)(ifcopenshell::geometry::impl::mapping_registry* mapping) { static const std::string schema_name = STRINGIFY(IfcSchema); POSTFIX_SCHEMA(factory_t) factory; mapping->bind(schema_name, factory); diff --git a/src/ifcgeom/taxonomy.cpp b/src/ifcgeom/taxonomy.cpp index 5b74ac9dee..8de4b59725 100644 --- a/src/ifcgeom/taxonomy.cpp +++ b/src/ifcgeom/taxonomy.cpp @@ -590,7 +590,7 @@ const std::string& ifcopenshell::geometry::taxonomy::kind_to_string(kinds k) { return values[k]; } -std::atomic_uint32_t item::counter_(0); +IFC_GEOM_API std::atomic_uint32_t item::counter_(0); void ifcopenshell::geometry::taxonomy::item::print(std::ostream& o, int indent) const { o << std::string(indent, ' ') << kind_to_string(kind()) << std::endl; diff --git a/src/ifcparse/CMakeLists.txt b/src/ifcparse/CMakeLists.txt index 02f1fc979f..b9b9f50dbf 100644 --- a/src/ifcparse/CMakeLists.txt +++ b/src/ifcparse/CMakeLists.txt @@ -34,6 +34,7 @@ list(APPEND IFCPARSE_CPP_FILES set(IFCPARSE_FILES ${IFCPARSE_CPP_FILES} ${IFCPARSE_H_FILES}) add_library(IfcParse ${IFCPARSE_FILES}) +add_library(parse ALIAS IfcParse) set_target_properties(IfcParse PROPERTIES COMPILE_FLAGS -DIFC_PARSE_EXPORTS VERSION "${PROJECT_VERSION}" SOVERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}") if(IFCXML_SUPPORT) @@ -49,9 +50,9 @@ if(IFCXML_SUPPORT) endif() if(WASM_BUILD) - target_link_libraries(IfcParse ${BCRYPT_LIBRARIES}) + target_link_libraries(IfcParse plugin ${BCRYPT_LIBRARIES}) else() - target_link_libraries(IfcParse ${Boost_LIBRARIES} ${BCRYPT_LIBRARIES}) + target_link_libraries(IfcParse plugin ${Boost_LIBRARIES} ${BCRYPT_LIBRARIES}) endif() # CMake installation targets diff --git a/src/ifcparse/buildinfo.cpp b/src/ifcparse/buildinfo.cpp index 0785712e2e..98581fae03 100644 --- a/src/ifcparse/buildinfo.cpp +++ b/src/ifcparse/buildinfo.cpp @@ -25,11 +25,12 @@ ********************************************************************************/ #include +#include "ifc_parse_api.h" #include "macros.h" #if defined(IFCOPENSHELL_BRANCH) && defined(IFCOPENSHELL_COMMIT) -const char *IFCOPENSHELL_VERSION = STRINGIFY(IFCOPENSHELL_BRANCH) "-" STRINGIFY(IFCOPENSHELL_COMMIT); +IFC_PARSE_API const char *IFCOPENSHELL_VERSION = STRINGIFY(IFCOPENSHELL_BRANCH) "-" STRINGIFY(IFCOPENSHELL_COMMIT); #else -const char *IFCOPENSHELL_VERSION = "0.8.0"; +IFC_PARSE_API const char *IFCOPENSHELL_VERSION = "0.8.0"; #endif diff --git a/src/ifcparse/parse.h b/src/ifcparse/parse.h index c6ee71a7ac..ee0a23755d 100644 --- a/src/ifcparse/parse.h +++ b/src/ifcparse/parse.h @@ -43,7 +43,7 @@ #include -extern const char *IFCOPENSHELL_VERSION; +extern IFC_PARSE_API const char *IFCOPENSHELL_VERSION; namespace ifcopenshell { @@ -59,6 +59,9 @@ class IFC_PARSE_API spf_lexer { mutable size_t pool_index = 0; public: + spf_lexer(const spf_lexer&) = delete; + spf_lexer& operator=(const spf_lexer&) = delete; + std::string& get_temp_string() const; void reset_pool() const { pool_index = 0; diff --git a/src/ifcparse/schema.cpp b/src/ifcparse/schema.cpp index 5e94b8ea8e..1b43215069 100644 --- a/src/ifcparse/schema.cpp +++ b/src/ifcparse/schema.cpp @@ -22,6 +22,7 @@ #include "express.h" #include +#include #ifdef HAS_SCHEMA_2x3 #include "schemas/Ifc2x3.h" @@ -128,7 +129,60 @@ ifcopenshell::entity::~entity() { delete inverse_attribute; } } -static std::map schemas; +namespace { + std::string schema_key(const std::string& schema_name) { + return boost::to_upper_copy(schema_name); + } + + ifcopenshell::plugin::module builtin_schema_module(const std::string& schema_name) { + ifcopenshell::plugin::metadata metadata; + metadata.kind_ = ifcopenshell::plugin::kind::parse_schema; + metadata.id = "parse.schema." + boost::to_lower_copy(schema_name); + metadata.schema = schema_name; + return ifcopenshell::plugin::module::builtin(metadata); + } + + void register_builtin_schemas(ifcopenshell::schema_registry& registry) { + registry.bind("HEADER_SECTION_SCHEMA", &Header_section_schema::get_schema, &Header_section_schema::clear_schema, builtin_schema_module("HEADER_SECTION_SCHEMA")); +#ifdef HAS_SCHEMA_2x3 + registry.bind("IFC2X3", &Ifc2x3::get_schema, &Ifc2x3::clear_schema, builtin_schema_module("IFC2X3")); +#endif +#ifdef HAS_SCHEMA_4 + registry.bind("IFC4", &Ifc4::get_schema, &Ifc4::clear_schema, builtin_schema_module("IFC4")); +#endif +#ifdef HAS_SCHEMA_4x1 + registry.bind("IFC4X1", &Ifc4x1::get_schema, &Ifc4x1::clear_schema, builtin_schema_module("IFC4X1")); +#endif +#ifdef HAS_SCHEMA_4x2 + registry.bind("IFC4X2", &Ifc4x2::get_schema, &Ifc4x2::clear_schema, builtin_schema_module("IFC4X2")); +#endif +#ifdef HAS_SCHEMA_4x3_rc1 + registry.bind("IFC4X3_RC1", &Ifc4x3_rc1::get_schema, &Ifc4x3_rc1::clear_schema, builtin_schema_module("IFC4X3_RC1")); +#endif +#ifdef HAS_SCHEMA_4x3_rc2 + registry.bind("IFC4X3_RC2", &Ifc4x3_rc2::get_schema, &Ifc4x3_rc2::clear_schema, builtin_schema_module("IFC4X3_RC2")); +#endif +#ifdef HAS_SCHEMA_4x3_rc3 + registry.bind("IFC4X3_RC3", &Ifc4x3_rc3::get_schema, &Ifc4x3_rc3::clear_schema, builtin_schema_module("IFC4X3_RC3")); +#endif +#ifdef HAS_SCHEMA_4x3_rc4 + registry.bind("IFC4X3_RC4", &Ifc4x3_rc4::get_schema, &Ifc4x3_rc4::clear_schema, builtin_schema_module("IFC4X3_RC4")); +#endif +#ifdef HAS_SCHEMA_4x3 + registry.bind("IFC4X3", &Ifc4x3::get_schema, &Ifc4x3::clear_schema, builtin_schema_module("IFC4X3")); +#endif +#ifdef HAS_SCHEMA_4x3_tc1 + registry.bind("IFC4X3_TC1", &Ifc4x3_tc1::get_schema, &Ifc4x3_tc1::clear_schema, builtin_schema_module("IFC4X3_TC1")); +#endif +#ifdef HAS_SCHEMA_4x3_add1 + registry.bind("IFC4X3_ADD1", &Ifc4x3_add1::get_schema, &Ifc4x3_add1::clear_schema, builtin_schema_module("IFC4X3_ADD1")); +#endif +#ifdef HAS_SCHEMA_4x3_add2 + registry.bind("IFC4X3_ADD2", &Ifc4x3_add2::get_schema, &Ifc4x3_add2::clear_schema, builtin_schema_module("IFC4X3_ADD2")); +#endif + } + +} ifcopenshell::schema_definition::schema_definition(const std::string& name, const std::vector& declarations) : name_(name) @@ -151,7 +205,7 @@ ifcopenshell::schema_definition::schema_definition(const std::string& name, cons entities_.push_back((**it).as_entity()); } } - schemas[name_] = this; + register_schema(this); } ifcopenshell::schema_definition::~schema_definition() { @@ -161,111 +215,67 @@ ifcopenshell::schema_definition::~schema_definition() { } void ifcopenshell::register_schema(schema_definition* schema) { - schemas.insert({boost::to_upper_copy(schema->name()), schema}); + schema_registry_instance().bind(schema); +} + +ifcopenshell::schema_registry& ifcopenshell::schema_registry_instance() { + static schema_registry registry; + static std::once_flag once; + std::call_once(once, register_builtin_schemas, std::ref(registry)); + return registry; +} + +void ifcopenshell::schema_registry::bind(const std::string& schema_name, get_schema_fn get, clear_schema_fn clear, const plugin::module& module) { + auto& entry = entries_[schema_key(schema_name)]; + entry.get_ = get; + entry.clear_ = clear; + entry.module_ = module; +} + +void ifcopenshell::schema_registry::bind(schema_definition* schema) { + auto& entry = entries_[schema_key(schema->name())]; + entry.schema_ = schema; +} + +const ifcopenshell::schema_definition* ifcopenshell::schema_registry::get(const std::string& schema_name) { + auto iter = entries_.find(schema_key(schema_name)); + if (iter == entries_.end()) { + throw ifcopenshell::exception("No schema named " + schema_name); + } + if (!iter->second.schema_ && iter->second.get_) { + iter->second.schema_ = &iter->second.get_(); + } + if (!iter->second.schema_) { + throw ifcopenshell::exception("No schema named " + schema_name); + } + return iter->second.schema_; +} + +std::vector ifcopenshell::schema_registry::names() const { + std::vector names; + for (const auto& pair : entries_) { + names.push_back(pair.first); + } + return names; +} + +void ifcopenshell::schema_registry::clear() { + for (auto& pair : entries_) { + if (pair.second.clear_) { + pair.second.clear_(); + } + pair.second.schema_ = nullptr; + } } const ifcopenshell::schema_definition* ifcopenshell::schema_by_name(const std::string& name) { - // TODO: initialize automatically somehow - Header_section_schema::get_schema(); -#ifdef HAS_SCHEMA_2x3 - Ifc2x3::get_schema(); -#endif -#ifdef HAS_SCHEMA_4 - Ifc4::get_schema(); -#endif -#ifdef HAS_SCHEMA_4x1 - Ifc4x1::get_schema(); -#endif -#ifdef HAS_SCHEMA_4x2 - Ifc4x2::get_schema(); -#endif -#ifdef HAS_SCHEMA_4x3_rc1 - Ifc4x3_rc1::get_schema(); -#endif -#ifdef HAS_SCHEMA_4x3_rc2 - Ifc4x3_rc2::get_schema(); -#endif -#ifdef HAS_SCHEMA_4x3_rc3 - Ifc4x3_rc3::get_schema(); -#endif -#ifdef HAS_SCHEMA_4x3_rc4 - Ifc4x3_rc4::get_schema(); -#endif -#ifdef HAS_SCHEMA_4x3 - Ifc4x3::get_schema(); -#endif -#ifdef HAS_SCHEMA_4x3_tc1 - Ifc4x3_tc1::get_schema(); -#endif -#ifdef HAS_SCHEMA_4x3_add1 - Ifc4x3_add1::get_schema(); -#endif -#ifdef HAS_SCHEMA_4x3_add2 - Ifc4x3_add2::get_schema(); -#endif - - std::map::const_iterator iter = schemas.find(boost::to_upper_copy(name)); - if (iter == schemas.end()) { - throw ifcopenshell::exception("No schema named " + name); - } - return iter->second; + return schema_registry_instance().get(name); } std::vector ifcopenshell::schema_names() { - // Load schema modules - try { - ifcopenshell::schema_by_name("IFC2X3"); - } catch (ifcopenshell::exception&) { - } - - // Populate vector with map keys - std::vector return_value; - for (auto& pair : schemas) { - return_value.push_back(pair.first); - } - - return return_value; + return schema_registry_instance().names(); } void ifcopenshell::clear_schemas() { -#ifdef HAS_SCHEMA_2x3 - Ifc2x3::clear_schema(); -#endif -#ifdef HAS_SCHEMA_4 - Ifc4::clear_schema(); -#endif -#ifdef HAS_SCHEMA_4x1 - Ifc4x1::clear_schema(); -#endif -#ifdef HAS_SCHEMA_4x2 - Ifc4x2::clear_schema(); -#endif -#ifdef HAS_SCHEMA_4x3_rc1 - Ifc4x3_rc1::clear_schema(); -#endif -#ifdef HAS_SCHEMA_4x3_rc2 - Ifc4x3_rc2::clear_schema(); -#endif -#ifdef HAS_SCHEMA_4x3_rc3 - Ifc4x3_rc3::clear_schema(); -#endif -#ifdef HAS_SCHEMA_4x3_rc4 - Ifc4x3_rc4::clear_schema(); -#endif -#ifdef HAS_SCHEMA_4x3 - Ifc4x3::clear_schema(); -#endif -#ifdef HAS_SCHEMA_4x3_tc1 - Ifc4x3_tc1::clear_schema(); -#endif -#ifdef HAS_SCHEMA_4x3_add1 - Ifc4x3_add1::clear_schema(); -#endif -#ifdef HAS_SCHEMA_4x3_add2 - Ifc4x3_add2::clear_schema(); -#endif - Header_section_schema::clear_schema(); - - // Schemas are owned by their respective modules in a unique_ptr, so just clear the map - schemas.clear(); + schema_registry_instance().clear(); } diff --git a/src/ifcparse/schema.h b/src/ifcparse/schema.h index 1cbc6d05c6..3b9209e729 100644 --- a/src/ifcparse/schema.h +++ b/src/ifcparse/schema.h @@ -21,11 +21,13 @@ #define IFCSCHEMA_H #include "exception.h" +#include "../plugin/plugin.h" #include #include #include #include +#include #include #include #include @@ -497,6 +499,30 @@ class IFC_PARSE_API schema_definition { const std::string& name() const { return name_; } }; +class IFC_PARSE_API schema_registry { + public: + typedef const schema_definition& (*get_schema_fn)(); + typedef void (*clear_schema_fn)(); + + void bind(const std::string& schema_name, get_schema_fn get, clear_schema_fn clear, const plugin::module& module = plugin::module()); + void bind(schema_definition* schema); + const schema_definition* get(const std::string& schema_name); + std::vector names() const; + void clear(); + + private: + struct entry { + const schema_definition* schema_ = nullptr; + get_schema_fn get_ = nullptr; + clear_schema_fn clear_ = nullptr; + plugin::module module_; + }; + + std::map entries_; +}; + +IFC_PARSE_API schema_registry& schema_registry_instance(); + IFC_PARSE_API const schema_definition* schema_by_name(const std::string& schema_name); IFC_PARSE_API std::vector schema_names(); diff --git a/src/plugin/CMakeLists.txt b/src/plugin/CMakeLists.txt new file mode 100644 index 0000000000..363c4a58f6 --- /dev/null +++ b/src/plugin/CMakeLists.txt @@ -0,0 +1,22 @@ +file(GLOB PLUGIN_H_FILES *.h) +file(GLOB PLUGIN_CPP_FILES *.cpp) + +add_library(plugin ${PLUGIN_CPP_FILES} ${PLUGIN_H_FILES}) +set_target_properties(plugin PROPERTIES + COMPILE_FLAGS "-DPLUGIN_EXPORTS" + VERSION "${PROJECT_VERSION}" + SOVERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}" +) + +target_compile_definitions(plugin PRIVATE + BOOST_DLL_USE_STD_FS + PLUGIN_IFCOPENSHELL_VERSION="${PROJECT_VERSION}" +) + +target_link_libraries(plugin PRIVATE ${Boost_LIBRARIES}) + +install(TARGETS plugin) + +install(FILES ${PLUGIN_H_FILES} + DESTINATION ${INCLUDEDIR}/plugin +) diff --git a/src/plugin/plugin.cpp b/src/plugin/plugin.cpp new file mode 100644 index 0000000000..a12dcbd943 --- /dev/null +++ b/src/plugin/plugin.cpp @@ -0,0 +1,154 @@ +/******************************************************************************** + * * + * This file is part of IfcOpenShell. * + * * + * IfcOpenShell is free software: you can redistribute it and/or modify * + * it under the terms of the Lesser GNU General Public License as published by * + * the Free Software Foundation, either version 3.0 of the License, or * + * (at your option) any later version. * + * * + * IfcOpenShell is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * Lesser GNU General Public License for more details. * + * * + * You should have received a copy of the Lesser GNU General Public License * + * along with this program. If not, see . * + * * + ********************************************************************************/ + +#include "plugin.h" + +#include + +#include +#include + +namespace { + using plugin_abi_fn = ifcopenshell::plugin::abi_info(*)(); + using plugin_metadata_fn = ifcopenshell::plugin::metadata(*)(); + + std::string compiler_id() { +#if defined(_MSC_VER) + return "msvc"; +#elif defined(__clang__) + return "clang"; +#elif defined(__GNUC__) + return "gcc"; +#else + return "unknown"; +#endif + } + + std::string compiler_version() { +#if defined(_MSC_FULL_VER) + return std::to_string(_MSC_FULL_VER); +#elif defined(__clang_major__) && defined(__clang_minor__) && defined(__clang_patchlevel__) + return std::to_string(__clang_major__) + "." + std::to_string(__clang_minor__) + "." + std::to_string(__clang_patchlevel__); +#elif defined(__GNUC__) && defined(__GNUC_MINOR__) && defined(__GNUC_PATCHLEVEL__) + return std::to_string(__GNUC__) + "." + std::to_string(__GNUC_MINOR__) + "." + std::to_string(__GNUC_PATCHLEVEL__); +#else + return "unknown"; +#endif + } + + bool is_debug_build() { +#if defined(NDEBUG) + return false; +#else + return true; +#endif + } +} + +struct ifcopenshell::plugin::module::data { + metadata metadata_; + std::filesystem::path path_; + std::shared_ptr library_; +}; + +ifcopenshell::plugin::module::module() + : data_(std::make_shared()) +{} + +ifcopenshell::plugin::module::module(const metadata& metadata) + : data_(std::make_shared()) +{ + data_->metadata_ = metadata; +} + +ifcopenshell::plugin::module::module(std::shared_ptr data) + : data_(std::move(data)) +{} + +ifcopenshell::plugin::module ifcopenshell::plugin::module::builtin(const metadata& metadata) { + return module(metadata); +} + +const ifcopenshell::plugin::metadata& ifcopenshell::plugin::module::meta() const { + return data_->metadata_; +} + +const std::filesystem::path& ifcopenshell::plugin::module::path() const { + return data_->path_; +} + +bool ifcopenshell::plugin::module::is_dynamic() const { + return data_->library_ != nullptr; +} + +bool ifcopenshell::plugin::module::is_loaded() const { + return data_->library_ && data_->library_->is_loaded(); +} + +ifcopenshell::plugin::manager::manager() = default; + +void ifcopenshell::plugin::manager::add_search_path(const std::filesystem::path& path) { + search_paths_.push_back(path); +} + +const std::vector& ifcopenshell::plugin::manager::search_paths() const { + return search_paths_; +} + +ifcopenshell::plugin::module ifcopenshell::plugin::manager::load(const std::filesystem::path& path) const { + auto library = std::make_shared(path, boost::dll::load_mode::default_mode); + auto abi = library->get_alias("ifcopenshell_plugin_abi_v1")(); + validate_abi(abi); + auto metadata = library->get_alias("ifcopenshell_plugin_metadata_v1")(); + + auto data = std::make_shared(); + data->metadata_ = metadata; + data->path_ = path; + data->library_ = std::move(library); + return module(data); +} + +ifcopenshell::plugin::abi_info ifcopenshell::plugin::host_abi() { + abi_info abi; + abi.debug_build = is_debug_build(); + abi.compiler_id = compiler_id(); + abi.compiler_version = compiler_version(); + abi.ifcopenshell_version = PLUGIN_IFCOPENSHELL_VERSION; + return abi; +} + +void ifcopenshell::plugin::validate_abi(const abi_info& abi) { + const auto host = host_abi(); + if (abi.plugin_api_version == host.plugin_api_version && + abi.pointer_size == host.pointer_size && + abi.debug_build == host.debug_build && + abi.compiler_id == host.compiler_id && + abi.compiler_version == host.compiler_version) { + return; + } + + std::ostringstream stream; + stream << "Incompatible plugin ABI"; + stream << " (plugin api " << abi.plugin_api_version << ", host api " << host.plugin_api_version << ")"; + stream << " (plugin compiler " << abi.compiler_id << " " << abi.compiler_version; + stream << ", host compiler " << host.compiler_id << " " << host.compiler_version << ")"; + stream << " (plugin pointer size " << abi.pointer_size << ", host pointer size " << host.pointer_size << ")"; + stream << " (plugin debug " << abi.debug_build << ", host debug " << host.debug_build << ")"; + throw std::runtime_error(stream.str()); +} diff --git a/src/plugin/plugin.h b/src/plugin/plugin.h new file mode 100644 index 0000000000..7ada44fe63 --- /dev/null +++ b/src/plugin/plugin.h @@ -0,0 +1,99 @@ +/******************************************************************************** + * * + * This file is part of IfcOpenShell. * + * * + * IfcOpenShell is free software: you can redistribute it and/or modify * + * it under the terms of the Lesser GNU General Public License as published by * + * the Free Software Foundation, either version 3.0 of the License, or * + * (at your option) any later version. * + * * + * IfcOpenShell is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * Lesser GNU General Public License for more details. * + * * + * You should have received a copy of the Lesser GNU General Public License * + * along with this program. If not, see . * + * * + ********************************************************************************/ + +#ifndef IFCOPENSHELL_PLUGIN_H +#define IFCOPENSHELL_PLUGIN_H + +#include "plugin_api.h" + +#include +#include +#include +#include +#include + +namespace ifcopenshell { +namespace plugin { + +enum class kind { + parse_schema, + mapping, + kernel, + document_serializer, + geometry_serializer, + opencascade_geometry_ifc_writer +}; + +struct PLUGIN_API abi_info { + uint32_t plugin_api_version = 1; + uint32_t pointer_size = sizeof(void*); + bool debug_build = false; + std::string compiler_id; + std::string compiler_version; + std::string ifcopenshell_version; +}; + +struct PLUGIN_API metadata { + kind kind_ = kind::kernel; + std::string id; + std::string schema; + std::string format; +}; + +class PLUGIN_API module { +public: + module(); + explicit module(const metadata& metadata); + + static module builtin(const metadata& metadata); + + const metadata& meta() const; + const std::filesystem::path& path() const; + bool is_dynamic() const; + bool is_loaded() const; + +private: + struct data; + std::shared_ptr data_; + + explicit module(std::shared_ptr data); + + friend class manager; +}; + +class PLUGIN_API manager { +public: + manager(); + + void add_search_path(const std::filesystem::path& path); + const std::vector& search_paths() const; + + module load(const std::filesystem::path& path) const; + +private: + std::vector search_paths_; +}; + +PLUGIN_API abi_info host_abi(); +PLUGIN_API void validate_abi(const abi_info& abi); + +} +} + +#endif diff --git a/src/plugin/plugin_api.h b/src/plugin/plugin_api.h new file mode 100644 index 0000000000..ca87e9ada5 --- /dev/null +++ b/src/plugin/plugin_api.h @@ -0,0 +1,37 @@ +/******************************************************************************** + * * + * This file is part of IfcOpenShell. * + * * + * IfcOpenShell is free software: you can redistribute it and/or modify * + * it under the terms of the Lesser GNU General Public License as published by * + * the Free Software Foundation, either version 3.0 of the License, or * + * (at your option) any later version. * + * * + * IfcOpenShell is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * Lesser GNU General Public License for more details. * + * * + * You should have received a copy of the Lesser GNU General Public License * + * along with this program. If not, see . * + * * + ********************************************************************************/ + +#ifndef PLUGIN_API_H +#define PLUGIN_API_H + +#ifdef IFC_SHARED_BUILD +#ifdef _WIN32 +#ifdef PLUGIN_EXPORTS +#define PLUGIN_API __declspec(dllexport) +#else +#define PLUGIN_API __declspec(dllimport) +#endif +#else +#define PLUGIN_API __attribute__((visibility("default"))) +#endif +#else +#define PLUGIN_API +#endif + +#endif diff --git a/src/serializers/CMakeLists.txt b/src/serializers/CMakeLists.txt index c575958a70..b5e8ad8ae0 100644 --- a/src/serializers/CMakeLists.txt +++ b/src/serializers/CMakeLists.txt @@ -5,6 +5,7 @@ file(GLOB SERIALIZERS_CPP_FILES *.cpp) set(SERIALIZERS_FILES ${SERIALIZERS_H_FILES} ${SERIALIZERS_CPP_FILES}) add_library(Serializers ${SERIALIZERS_FILES}) +add_library(serialization ALIAS Serializers) set_target_properties(Serializers PROPERTIES COMPILE_FLAGS "-DSERIALIZERS_EXPORTS" VERSION "${PROJECT_VERSION}" SOVERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}") set(SERIALIZER_SCHEMA_LIBRARIES Serializers ${SERIALIZER_SCHEMA_LIBRARIES} PARENT_SCOPE) @@ -20,7 +21,7 @@ endif() target_link_libraries(Serializers PRIVATE ${SERIALIZER_SCHEMA_LIBRARIES} ${OPENCOLLADA_LIBRARIES} ${USD_LIBRARIES} - IfcGeom ${OpenCASCADE_LIBRARIES} ${kernel_libraries} IfcParse + IfcGeom ${OpenCASCADE_LIBRARIES} IfcParse ) install(TARGETS Serializers) diff --git a/src/serializers/schema_dependent/CMakeLists.txt b/src/serializers/schema_dependent/CMakeLists.txt index c3c2fc42d8..25ec31c9b3 100644 --- a/src/serializers/schema_dependent/CMakeLists.txt +++ b/src/serializers/schema_dependent/CMakeLists.txt @@ -5,7 +5,7 @@ set(SERIALIZERS_S_FILES ${SERIALIZERS_S_H_FILES} ${SERIALIZERS_S_CPP_FILES}) foreach(schema ${SCHEMA_VERSIONS}) add_library(Serializers_ifc${schema} OBJECT ${SERIALIZERS_S_FILES}) set(SERIALIZER_SCHEMA_LIBRARIES ${SERIALIZER_SCHEMA_LIBRARIES} Serializers_ifc${schema}) - set_target_properties(Serializers_ifc${schema} PROPERTIES COMPILE_FLAGS "-DIFC_GEOM_EXPORTS -DIfcSchema=Ifc${schema}") + set_target_properties(Serializers_ifc${schema} PROPERTIES COMPILE_FLAGS "-DSERIALIZERS_EXPORTS -DIfcSchema=Ifc${schema}") if(WASM_BUILD) target_link_libraries(Serializers_ifc${schema} ${HDF5_LIBRARIES}) else()