Merge remote-tracking branch 'origin/v0.8.0' into ifcviewer-wgpu

This commit is contained in:
Thomas Krijnen
2026-07-09 13:21:39 +02:00
373 changed files with 22411 additions and 4242 deletions
+91 -9
View File
@@ -17,6 +17,23 @@
# #
################################################################################
cmake_minimum_required(VERSION 3.21)
if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
project(IfcOpenShellPython LANGUAGES CXX)
set(IFCOPENSHELL_IFCWRAP_STANDALONE ON)
else()
set(IFCOPENSHELL_IFCWRAP_STANDALONE OFF)
endif()
if(NOT DEFINED CMAKE_CXX_STANDARD)
set(CMAKE_CXX_STANDARD 17)
endif()
if(CMAKE_CXX_STANDARD LESS 17)
message(FATAL_ERROR "C++17 or newer is required.")
endif()
set(CMAKE_CXX_STANDARD_REQUIRED ON)
if(POLICY CMP0148) # 3.27
cmake_policy(SET CMP0148 OLD)
endif()
@@ -24,9 +41,38 @@ if(POLICY CMP0177) # 3.31
cmake_policy(SET CMP0177 OLD)
endif()
FIND_PACKAGE(SWIG)
IF(NOT SWIG_FOUND)
MESSAGE(
set(_ifcwrap_feature_definitions "")
if(IFCOPENSHELL_IFCWRAP_STANDALONE)
get_filename_component(_ifcwrap_repo_root "${CMAKE_CURRENT_SOURCE_DIR}/../.." ABSOLUTE)
list(PREPEND CMAKE_MODULE_PATH "${_ifcwrap_repo_root}/cmake")
find_package(IfcOpenShell REQUIRED)
set(WITH_OPENCASCADE "${IFCOPENSHELL_WITH_OPENCASCADE}")
set(WITH_CGAL "${IFCOPENSHELL_WITH_CGAL}")
set(SWIG_DEFINES "")
if(IFCOPENSHELL_WITH_OPENCASCADE)
list(APPEND SWIG_DEFINES -DIFOPSH_WITH_OPENCASCADE)
list(APPEND _ifcwrap_feature_definitions IFOPSH_WITH_OPENCASCADE)
endif()
if(IFCOPENSHELL_WITH_CGAL)
list(APPEND SWIG_DEFINES -DIFOPSH_WITH_CGAL)
list(APPEND _ifcwrap_feature_definitions IFOPSH_WITH_CGAL)
endif()
if(IFCOPENSHELL_IFCXML)
list(APPEND SWIG_DEFINES -DWITH_IFCXML)
list(APPEND _ifcwrap_feature_definitions WITH_IFCXML)
endif()
if(IFCOPENSHELL_WITH_ROCKSDB)
list(APPEND SWIG_DEFINES -DIFOPSH_WITH_ROCKSDB)
list(APPEND _ifcwrap_feature_definitions IFOPSH_WITH_ROCKSDB)
endif()
endif()
find_package(SWIG)
if(NOT SWIG_FOUND)
message(
FATAL_ERROR
"BUILD_IFCPYTHON enabled, but unable to find SWIG. Disable BUILD_IFCPYTHON or fix SWIG paths to proceed. "
"Likely SWIG_EXECUTABLE is missing (current value - '${SWIG_EXECUTABLE}')."
@@ -64,7 +110,19 @@ INCLUDE_DIRECTORIES(BEFORE ${CMAKE_CURRENT_SOURCE_DIR})
SET(CMAKE_SWIG_FLAGS ${SWIG_DEFINES})
SET_SOURCE_FILES_PROPERTIES(IfcPython.i PROPERTIES CPLUSPLUS ON)
if(WITH_CGAL)
if(IFCOPENSHELL_IFCWRAP_STANDALONE)
if(TARGET IfcOpenShell::svgfill)
set(LIBSVGFILL IfcOpenShell::svgfill)
else()
message(FATAL_ERROR "IfcOpenShell was built with CGAL, but the exported svgfill target was not found.")
endif()
else()
set(LIBSVGFILL svgfill)
endif()
endif()
set_source_files_properties(IfcPython.i PROPERTIES CPLUSPLUS ON)
# Rebuild on changes in other .i files.
SET_PROPERTY(
SOURCE IfcPython.i
@@ -122,8 +180,23 @@ endif()
if(WITH_ROCKSDB)
target_link_libraries(ifcopenshell_wrapper PRIVATE document_serializer_rdb)
endif()
SET_PROPERTY(TARGET ifcopenshell_wrapper PROPERTY SWIG_DEPENDS ${IFCOPENSHELL_LIBRARIES})
if (WASM_BUILD)
if(_ifcwrap_feature_definitions)
target_compile_definitions(ifcopenshell_wrapper PRIVATE ${_ifcwrap_feature_definitions})
endif()
if(IFCOPENSHELL_IFCWRAP_STANDALONE)
set(_ifcwrap_ifcopenshell_libraries ${IFCOPENSHELL_LIBRARIES})
set(_ifcwrap_geometry_libraries ${IFCOPENSHELL_GEOMETRY_LIBRARIES})
set(_ifcwrap_cgal_libraries "")
set(_ifcwrap_swig_depends "")
else()
set(_ifcwrap_ifcopenshell_libraries ${IFCOPENSHELL_LIBRARIES})
set(_ifcwrap_geometry_libraries IfcGeom ${kernel_libraries})
set(_ifcwrap_cgal_libraries ${CGAL_LIBRARIES})
set(_ifcwrap_swig_depends ${IFCOPENSHELL_LIBRARIES})
endif()
set_property(TARGET ifcopenshell_wrapper PROPERTY SWIG_DEPENDS ${_ifcwrap_swig_depends})
if(WASM_BUILD)
# SIDE_MODULE=1 - add to .so all symbols from linked archives (default used by pyodide).
# Since currently libIfcGeom.a seems to be linked twice it results in duplicated symbols and compilation errors.
# Possibly in the future we can clean up linked libs and try `=1`.
@@ -147,7 +220,7 @@ target_link_libraries(ifcopenshell_wrapper PRIVATE IfcGeom IfcParse ${Boost_LIBR
if(schema_libraries OR kernel_libraries OR tree_libraries OR mapping_libraries OR geometry_serializer_libraries OR document_serializer_libraries OR linework_processing_libraries)
add_dependencies(ifcopenshell_wrapper ${schema_libraries} ${kernel_libraries} ${tree_libraries} ${mapping_libraries} ${geometry_serializer_libraries} ${document_serializer_libraries} ${linework_processing_libraries})
endif()
if(NOT WIN32)
if((NOT WIN32) AND BUILD_SHARED_LIBS AND COMMAND SET_INSTALL_RPATHS)
SET_INSTALL_RPATHS(ifcopenshell_wrapper "${IFCDIRS};${OCC_LIBRARY_DIR}")
endif()
@@ -194,8 +267,17 @@ IF(Python_Interpreter_FOUND OR PYTHON_MODULE_INSTALL_DIR)
MESSAGE(WARNING "Unable to locate Python site-package directory, unable to install the Python wrapper")
ELSE()
message(STATUS "Python wrapper will be installed to '${python_package_dir}'.")
FILE(GLOB_RECURSE sourcefiles
"${CMAKE_CURRENT_SOURCE_DIR}/../ifcopenshell-python/ifcopenshell/*"
file(GLOB_RECURSE sourcefiles "${CMAKE_CURRENT_SOURCE_DIR}/../ifcopenshell-python/ifcopenshell/*")
foreach(file ${sourcefiles})
file(RELATIVE_PATH relative "${CMAKE_CURRENT_SOURCE_DIR}/../ifcopenshell-python/ifcopenshell/" "${file}")
get_filename_component(dir "${relative}" DIRECTORY)
if(NOT IS_DIRECTORY "${file}")
install(FILES "${file}" DESTINATION "${python_package_dir}/ifcopenshell/${dir}")
endif()
endforeach()
install(
FILES "${CMAKE_CURRENT_BINARY_DIR}/ifcopenshell_wrapper.py"
DESTINATION "${python_package_dir}/ifcopenshell"
)
FOREACH(file ${sourcefiles})
FILE(RELATIVE_PATH relative "${CMAKE_CURRENT_SOURCE_DIR}/../ifcopenshell-python/ifcopenshell/" "${file}")
+305 -217
View File
@@ -81,16 +81,9 @@
%newobject IfcGeom::ConversionResultShape::moved;
%newobject IfcGeom::ConversionResultShape::wrap_in_compound;
%newobject IfcGeom::ConversionResultShape::area;
%newobject IfcGeom::ConversionResultShape::volume;
%newobject IfcGeom::ConversionResultShape::length;
%newobject nary_union;
%newobject IfcGeom::OpaqueNumber::operator+;
%newobject IfcGeom::OpaqueNumber::operator-;
%newobject IfcGeom::OpaqueNumber::operator*;
%newobject IfcGeom::OpaqueNumber::operator/;
%inline %{
template <typename T>
@@ -260,149 +253,149 @@ namespace {
%include "../ifcgeom/ConversionSettings.h"
%include "../ifcgeom/IfcGeomElement.h"
%include "../ifcgeom/IfcGeomRepresentation.h"
%include "../ifcgeom/Iterator.h"
%include "../ifcgeom/GeometrySerializer.h"
%include "../ifcgeom/taxonomy.h"
%include "../ifcgeom/function_item_evaluator.h"
%{
#include "../serializers/geometry_serializer_plugin.h"
class PythonPluginGeometrySerializer : public GeometrySerializer {
public:
PythonPluginGeometrySerializer(
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
)
: GeometrySerializer(geometry_settings, serializer_settings)
{
ifcopenshell::serializers::geometry_serializer_context context{
output_filename,
output_temp_filename.empty() ? output_filename : output_temp_filename,
geometry_settings,
serializer_settings
};
initialize_serializer(extension, context);
}
PythonPluginGeometrySerializer(
const std::string& extension,
const stream_or_filename& output_filename,
const stream_or_filename& output_temp_filename,
ifcopenshell::geometry::Settings& geometry_settings,
const ifcopenshell::geometry::SerializerSettings& serializer_settings
)
: GeometrySerializer(geometry_settings, serializer_settings)
{
const auto output_filename_string = output_filename.filename().value_or("");
const auto output_temp_filename_string = output_temp_filename.filename().value_or(output_filename_string);
ifcopenshell::serializers::geometry_serializer_context context{
output_filename_string,
output_temp_filename_string,
geometry_settings,
serializer_settings,
&output_filename,
&output_temp_filename
};
initialize_serializer(extension, context);
}
private:
void initialize_serializer(
const std::string& extension,
ifcopenshell::serializers::geometry_serializer_context& context
) {
auto& registry = ifcopenshell::serializers::geometry_serializer_registry_instance();
registry.configure(extension, context);
geometry_settings_ = context.geometry_settings;
serializer_ = registry.create(extension, context);
}
public:
bool ready() override {
return serializer_->ready();
}
bool is_streaming() const override {
return serializer_->is_streaming();
}
void writeHeader() override {
serializer_->writeHeader();
}
void finalize() override {
serializer_->finalize();
}
void setFile(ifcopenshell::file* file) override {
serializer_->setFile(file);
}
bool isTesselated() const override {
return serializer_->isTesselated();
}
void write(const IfcGeom::TriangulationElement* element) override {
serializer_->write(element);
}
void write(const IfcGeom::BRepElement* element) override {
serializer_->write(element);
}
void setUnitNameAndMagnitude(const std::string& name, float magnitude) override {
serializer_->setUnitNameAndMagnitude(name, magnitude);
}
IfcGeom::Element* read(
ifcopenshell::file& file,
const std::string& guid,
const std::string& representation_id,
read_type rt = READ_BREP
) override {
return serializer_->read(file, guid, representation_id, rt);
}
std::string object_id(const IfcGeom::Element* element) override {
return serializer_->object_id(element);
}
private:
boost::shared_ptr<GeometrySerializer> serializer_;
};
%}
%extend GeometrySerializer {
bool ready() {
return $self->ready();
}
bool is_streaming() const {
return $self->is_streaming();
}
void writeHeader() {
$self->writeHeader();
}
void finalize() {
$self->finalize();
}
void setFile(ifcopenshell::file* file) {
$self->setFile(file);
}
}
%extend ifcopenshell::geometry::taxonomy::style {
size_t instance_id() const {
%include "../ifcgeom/Iterator.h"
%include "../ifcgeom/GeometrySerializer.h"
%include "../ifcgeom/taxonomy.h"
%include "../ifcgeom/function_item_evaluator.h"
%{
#include "../serializers/geometry_serializer_plugin.h"
class PythonPluginGeometrySerializer : public GeometrySerializer {
public:
PythonPluginGeometrySerializer(
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
)
: GeometrySerializer(geometry_settings, serializer_settings)
{
ifcopenshell::serializers::geometry_serializer_context context{
output_filename,
output_temp_filename.empty() ? output_filename : output_temp_filename,
geometry_settings,
serializer_settings
};
initialize_serializer(extension, context);
}
PythonPluginGeometrySerializer(
const std::string& extension,
const stream_or_filename& output_filename,
const stream_or_filename& output_temp_filename,
ifcopenshell::geometry::Settings& geometry_settings,
const ifcopenshell::geometry::SerializerSettings& serializer_settings
)
: GeometrySerializer(geometry_settings, serializer_settings)
{
const auto output_filename_string = output_filename.filename().value_or("");
const auto output_temp_filename_string = output_temp_filename.filename().value_or(output_filename_string);
ifcopenshell::serializers::geometry_serializer_context context{
output_filename_string,
output_temp_filename_string,
geometry_settings,
serializer_settings,
&output_filename,
&output_temp_filename
};
initialize_serializer(extension, context);
}
private:
void initialize_serializer(
const std::string& extension,
ifcopenshell::serializers::geometry_serializer_context& context
) {
auto& registry = ifcopenshell::serializers::geometry_serializer_registry_instance();
registry.configure(extension, context);
geometry_settings_ = context.geometry_settings;
serializer_ = registry.create(extension, context);
}
public:
bool ready() override {
return serializer_->ready();
}
bool is_streaming() const override {
return serializer_->is_streaming();
}
void writeHeader() override {
serializer_->writeHeader();
}
void finalize() override {
serializer_->finalize();
}
void setFile(ifcopenshell::file* file) override {
serializer_->setFile(file);
}
bool isTesselated() const override {
return serializer_->isTesselated();
}
void write(const IfcGeom::TriangulationElement* element) override {
serializer_->write(element);
}
void write(const IfcGeom::BRepElement* element) override {
serializer_->write(element);
}
void setUnitNameAndMagnitude(const std::string& name, float magnitude) override {
serializer_->setUnitNameAndMagnitude(name, magnitude);
}
IfcGeom::Element* read(
ifcopenshell::file& file,
const std::string& guid,
const std::string& representation_id,
read_type rt = READ_BREP
) override {
return serializer_->read(file, guid, representation_id, rt);
}
std::string object_id(const IfcGeom::Element* element) override {
return serializer_->object_id(element);
}
private:
boost::shared_ptr<GeometrySerializer> serializer_;
};
%}
%extend GeometrySerializer {
bool ready() {
return $self->ready();
}
bool is_streaming() const {
return $self->is_streaming();
}
void writeHeader() {
$self->writeHeader();
}
void finalize() {
$self->finalize();
}
void setFile(ifcopenshell::file* file) {
$self->setFile(file);
}
}
%extend ifcopenshell::geometry::taxonomy::style {
size_t instance_id() const {
if (!self->instance) {
return 0;
}
@@ -515,16 +508,16 @@ assign_matrix_access(revolve);
void set_(const std::string& name, const std::set<int>& val) {
return $self->set(name, val);
}
void set_(const std::string& name, const std::vector<double>& val) {
return $self->set(name, val);
}
void set_(const std::string& name, const std::vector<std::string>& val) {
// Python sequences cannot distinguish std::vector<std::string> from std::set<std::string>.
if ($self->get_type(name) == "std::set<std::string>") {
return $self->set(name, std::set<std::string>(val.begin(), val.end()));
}
return $self->set(name, val);
}
void set_(const std::string& name, const std::vector<double>& val) {
return $self->set(name, val);
}
void set_(const std::string& name, const std::vector<std::string>& val) {
// Python sequences cannot distinguish std::vector<std::string> from std::set<std::string>.
if ($self->get_type(name) == "std::set<std::string>") {
return $self->set(name, std::set<std::string>(val.begin(), val.end()));
}
return $self->set(name, val);
}
ifcopenshell::geometry::Settings::value_variant_t get_(const std::string& name) {
return $self->get(name);
}
@@ -671,70 +664,76 @@ struct ShapeRTTI : public boost::static_visitor<PyObject*>
$result = std::visit(ShapeRTTI(), (std::variant<IfcGeom::Element*, IfcGeom::Representation::Representation*, IfcGeom::Transformation*>) $1);
}
%newobject construct_iterator;
%newobject construct_iterator_with_include_exclude;
%newobject construct_iterator_with_include_exclude_globalid;
%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
%inline %{
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
);
}
GeometrySerializer* create_geometry_serializer(
const std::string& extension,
const stream_or_filename& output_filename,
const stream_or_filename& 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);
}
%newobject construct_iterator;
%newobject construct_iterator_with_include_exclude;
%newobject construct_iterator_with_include_exclude_globalid;
%newobject construct_iterator_with_include_exclude_id;
%newobject create_geometry_serializer;
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());
IfcGeom::entity_filter ef{ include, false, elems_set };
return new IfcGeom::Iterator(ifcopenshell::geometry::kernels::construct(file, geometry_library, settings), settings, file, {ef}, num_threads);
// 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
%inline %{
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_with_include_exclude_globalid(const std::string& geometry_library, ifcopenshell::geometry::Settings settings, ifcopenshell::file* file, std::vector<std::string> elems, bool include, int num_threads) {
GeometrySerializer* create_geometry_serializer(
const std::string& extension,
const stream_or_filename& output_filename,
const stream_or_filename& 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);
}
// 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
IfcGeom::Iterator* construct_iterator(const std::string& geometry_library, ifcopenshell::geometry::Settings settings, ifcopenshell::file* file, int num_threads, Logger& logger = Logger::Root()) {
return new IfcGeom::Iterator(ifcopenshell::geometry::kernels::construct(file, geometry_library, settings, logger), settings, file, num_threads, logger);
}
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, Logger& logger = Logger::Root()) {
std::set<std::string> elems_set(elems.begin(), elems.end());
IfcGeom::entity_filter ef{ include, false, elems_set };
return new IfcGeom::Iterator(ifcopenshell::geometry::kernels::construct(file, geometry_library, settings, logger), settings, file, {ef}, num_threads, logger);
}
IfcGeom::Iterator* construct_iterator_with_include_exclude_globalid(const std::string& geometry_library, ifcopenshell::geometry::Settings settings, ifcopenshell::file* file, std::vector<std::string> elems, bool include, int num_threads, Logger& logger = Logger::Root()) {
std::set<std::string> elems_set(elems.begin(), elems.end());
IfcGeom::attribute_filter af;
af.attribute_name = "GlobalId";
af.populate(elems_set);
af.include = include;
return new IfcGeom::Iterator(ifcopenshell::geometry::kernels::construct(file, geometry_library, settings), settings, file, {af}, num_threads);
return new IfcGeom::Iterator(ifcopenshell::geometry::kernels::construct(file, geometry_library, settings, logger), settings, file, {af}, num_threads, logger);
}
IfcGeom::Iterator* construct_iterator_with_include_exclude_id(const std::string& geometry_library, ifcopenshell::geometry::Settings settings, ifcopenshell::file* file, std::vector<int> elems, bool include, int num_threads) {
IfcGeom::Iterator* construct_iterator_with_include_exclude_id(const std::string& geometry_library, ifcopenshell::geometry::Settings settings, ifcopenshell::file* file, std::vector<int> elems, bool include, int num_threads, Logger& logger = Logger::Root()) {
std::set<int> elems_set(elems.begin(), elems.end());
IfcGeom::instance_id_filter af(include, false, elems_set);
return new IfcGeom::Iterator(ifcopenshell::geometry::kernels::construct(file, geometry_library, settings), settings, file, {af}, num_threads);
return new IfcGeom::Iterator(ifcopenshell::geometry::kernels::construct(file, geometry_library, settings, logger), settings, file, {af}, num_threads, logger);
}
%}
@@ -956,7 +955,7 @@ struct ShapeRTTI : public boost::static_visitor<PyObject*>
return oss.str();
}
static std::variant<IfcGeom::Element*, IfcGeom::Representation::Representation*, IfcGeom::Transformation*> helper_fn_create_shape(const std::string& geometry_library, ifcopenshell::geometry::Settings& st, const express::Base& instance, const express::Base& representation = express::Base()) {
static std::variant<IfcGeom::Element*, IfcGeom::Representation::Representation*, IfcGeom::Transformation*> helper_fn_create_shape(Logger& logger, const std::string& geometry_library, ifcopenshell::geometry::Settings& st, const express::Base& instance, const express::Base& representation = express::Base()) {
ifcopenshell::file* file = instance.file();
ifcopenshell::geometry::Converter kernel(ifcopenshell::geometry::kernels::construct(file, geometry_library, st), file, st);
@@ -1070,12 +1069,12 @@ ifcopenshell::geometry::taxonomy::item::ptr try_upcast(PyObject* obj0, swig_type
%}
%inline %{
static std::variant<IfcGeom::Element*, IfcGeom::Representation::Representation*, IfcGeom::Transformation*> create_shape(ifcopenshell::geometry::Settings& settings, const express::Base& instance, const express::Base& representation, const char* const geometry_library="opencascade") {
static std::variant<IfcGeom::Element*, IfcGeom::Representation::Representation*, IfcGeom::Transformation*> create_shape(ifcopenshell::geometry::Settings& settings, const express::Base& instance, const express::Base& representation, const char* const geometry_library="opencascade", Logger& logger = Logger::Root()) {
return helper_fn_create_shape(geometry_library, settings, instance, representation);
}
// Manual definition of overload without representation argument
static std::variant<IfcGeom::Element*, IfcGeom::Representation::Representation*, IfcGeom::Transformation*> create_shape(ifcopenshell::geometry::Settings& settings, const express::Base& instance, const char* const geometry_library="opencascade") {
static std::variant<IfcGeom::Element*, IfcGeom::Representation::Representation*, IfcGeom::Transformation*> create_shape(ifcopenshell::geometry::Settings& settings, const express::Base& instance, const char* const geometry_library="opencascade", Logger& logger = Logger::Root()) {
return create_shape(settings, instance, express::Base(), geometry_library);
}
%}
@@ -1209,6 +1208,95 @@ ifcopenshell::geometry::taxonomy::item::ptr try_upcast(PyObject* obj0, swig_type
%template(svg_loop) std::vector<std::array<double, 2>>;
%template(svg_loops) std::vector<std::vector<std::array<double, 2>>>;
%extend IfcGeom::OpaqueCoordinate {
%pythoncode %{
__len__ = size
def __iter__(self):
yield from (self.get(i) for i in range(len(self)))
%}
}
%extend IfcGeom::OpaqueNumber {
%pythoncode %{
__abs__ = abs
%}
}
%template(OpaqueCoordinate_3) IfcGeom::OpaqueCoordinate<3>;
%template(OpaqueCoordinate_4) IfcGeom::OpaqueCoordinate<4>;
%inline %{
IfcGeom::OpaqueNumber create_epeck(int i) {
return ifcopenshell::geometry::NumberEpeck(i);
}
IfcGeom::OpaqueNumber create_epeck(double d) {
return ifcopenshell::geometry::NumberEpeck(d);
}
IfcGeom::OpaqueNumber create_epeck(const std::string& s) {
return ifcopenshell::geometry::NumberEpeck(typename CGAL::Epeck::FT::ET(s));
}
%}
%inline %{
IfcGeom::ConversionResultShape* nary_union(PyObject* sequence) {
std::vector<const CGAL::Nef_polyhedron_3<CGAL::Epeck>*> nefs;
for(Py_ssize_t i = 0; i < PySequence_Size(sequence); ++i) {
PyObject* element = PySequence_GetItem(sequence, i);
void* argp1 = nullptr;
auto res1 = SWIG_ConvertPtr(element, &argp1, SWIGTYPE_p_IfcGeom__ConversionResultShape, 0);
if (SWIG_IsOK(res1)) {
auto arg1 = reinterpret_cast<IfcGeom::ConversionResultShape*>(argp1);
auto cgs = dynamic_cast<ifcopenshell::geometry::CgalShape*>(arg1);
if (cgs) {
nefs.push_back(&cgs->nef());
}
}
}
ifcopenshell::geometry::CgalShape* shp;
Py_BEGIN_ALLOW_THREADS;
CGAL::Nef_nary_union_3< CGAL::Nef_polyhedron_3<CGAL::Epeck> > accum;
for (auto& n : nefs) {
accum.add_polyhedron(*n);
}
shp = new ifcopenshell::geometry::CgalShape(accum.get_union());
Py_END_ALLOW_THREADS;
return shp;
}
%}
%extend IfcGeom::ConversionResultShape {
std::string serialize_obj() {
std::ostringstream result;
auto cgs = dynamic_cast<ifcopenshell::geometry::CgalShape*>($self);
if (cgs) {
write_to_obj(cgs->nef(), result, std::numeric_limits<size_t>::max());
}
return result.str();
}
void convex_tag(bool b) {
auto cgs = dynamic_cast<ifcopenshell::geometry::CgalShape*>($self);
if (cgs) {
cgs->convex_tag() = b;
}
}
std::string serialize() {
std::string result;
ifcopenshell::geometry::taxonomy::matrix4 iden;
$self->Serialize(iden, result);
return result;
}
ConversionResultShape* solid_mt() {
IfcGeom::ConversionResultShape* r;
Py_BEGIN_ALLOW_THREADS;
r = $self->solid();
Py_END_ALLOW_THREADS;
return r;
}
}
%naturalvar svgfill::polygon_2::boundary;
%naturalvar svgfill::polygon_2::inner_boundaries;
%naturalvar svgfill::polygon_2::point_inside;
@@ -1243,9 +1331,9 @@ ifcopenshell::geometry::taxonomy::item::ptr try_upcast(PyObject* obj0, swig_type
}
}
std::vector<svgfill::polygon_2> arrange_polygons(svgfill::arrange_polygon_settings settings, const std::vector<svgfill::polygon_2>& polygons) {
std::vector<svgfill::polygon_2> arrange_polygons(svgfill::arrange_polygon_settings settings, const std::vector<svgfill::polygon_2>& polygons, Logger& logger = Logger::Root()) {
std::vector<svgfill::polygon_2> r;
if (svgfill::arrange_polygons(settings, polygons, r)) {
if (svgfill::arrange_polygons(settings, polygons, r, logger)) {
return r;
} else {
throw std::runtime_error("Failed to arrange polygons");
+83 -15
View File
@@ -112,6 +112,47 @@ PyObject* get_feature(const std::string& x) {
%{
#include <fstream>
#include <random>
// Atomic IFC/STEP write (issue #4797): serialize to a temporary file next to
// the destination, then atomically rename it onto the destination. If the
// process is interrupted mid-write, the destination is never truncated or
// left with dangling STEP references; at most a stray temp file remains, which
// the caller can safely ignore. Keeping the temp in the same directory means
// the rename stays on a single filesystem and is therefore atomic. The temp
// path never leaks into the FILE_NAME header, which is derived from the model
// header, not the output path.
template <typename T>
static void helper_fn_atomic_write(T& file_obj, const std::string& fn) {
std::random_device rd;
const std::string temp_fn = fn + "." + std::to_string(rd()) + ".tmp";
{
// Same open mode as a plain write so the bytes are identical.
std::ofstream f(IfcUtil::path::from_utf8(temp_fn).c_str());
if (!f.good()) {
// The temp file could not be created (e.g. directory not
// writable). Nothing was touched; report as a normal write error.
throw std::runtime_error("Failed to write to path: '" + fn + "', check folder and file permissions.");
}
f << file_obj;
f.flush();
if (!f.good()) {
// Serialization failed (e.g. disk full). Clean up the partial temp
// and abort. The existing destination is left intact.
f.close();
IfcUtil::path::delete_file(temp_fn);
throw std::runtime_error("Failed to write to path: '" + fn + "', the file may be incomplete.");
}
// The ofstream destructor at the end of this scope closes the stream.
// On Windows the file must be closed before it can be renamed.
}
if (!IfcUtil::path::atomic_rename_file(temp_fn, fn)) {
IfcUtil::path::delete_file(temp_fn);
throw std::runtime_error("Failed to write to path: '" + fn + "', could not replace the existing file.");
}
}
static const std::string& helper_fn_declaration_get_name(const ifcopenshell::declaration* decl) {
return decl->name();
}
@@ -221,11 +262,10 @@ private:
}
void _write(const std::string& fn) {
std::ofstream f(ifcopenshell::path::from_utf8(fn).c_str());
if (!f.good()) {
throw std::runtime_error("Failed to write to path: '" + fn + "', check folder and file permissions.");
}
f << (*$self);
// Atomic write: serialize to a temp file next to the target, then
// atomically rename it into place, so an interrupted write can never
// corrupt the destination (issue #4797).
helper_fn_atomic_write(*$self, fn);
}
std::string to_string() {
@@ -947,6 +987,7 @@ object = _old_object
%include "../ifcparse/schema.h"
%include "../serializers/RocksDbSerializer.h"
%include "../ifcparse/IfcLogger.h"
// The file* returned by open() is to be freed by SWIG/Python
%newobject open;
@@ -954,10 +995,10 @@ object = _old_object
%newobject stream_from_string;
%inline %{
ifcopenshell::file* open(const std::string& fn, bool readonly=false) {
ifcopenshell::file* open(const std::string& fn, bool readonly=false, Logger& logger=Logger::Root()) {
ifcopenshell::file* f;
Py_BEGIN_ALLOW_THREADS;
f = new ifcopenshell::file(fn, ifcopenshell::FT_AUTODETECT, readonly);
f = new ifcopenshell::file(fn, ifcopenshell::FT_AUTODETECT, readonly, logger);
Py_END_ALLOW_THREADS;
return f;
}
@@ -1115,7 +1156,7 @@ object = _old_object
static std::stringstream ifcopenshell_log_stream;
%}
%init %{
logger::set_output(0, &ifcopenshell_log_stream);
Logger::Root().SetOutput(0, &ifcopenshell_log_stream);
%}
%inline %{
std::string get_log() {
@@ -1124,20 +1165,20 @@ object = _old_object
return log;
}
void turn_on_detailed_logging() {
logger::set_output(&std::cout, &std::cout);
logger::verbosity(logger::LOG_DEBUG);
Logger::Root().SetOutput(&std::cout, &std::cout);
Logger::Root().Verbosity(Logger::LOG_DEBUG);
}
void turn_off_detailed_logging() {
logger::set_output(0, &ifcopenshell_log_stream);
logger::verbosity(logger::LOG_WARNING);
Logger::Root().SetOutput(0, &ifcopenshell_log_stream);
Logger::Root().Verbosity(Logger::LOG_WARNING);
}
void set_log_format_json() {
ifcopenshell_log_stream.str("");
logger::output_format(logger::FMT_JSON);
Logger::Root().OutputFormat(Logger::FMT_JSON);
}
void set_log_format_text() {
ifcopenshell_log_stream.str("");
logger::output_format(logger::FMT_PLAIN);
Logger::Root().OutputFormat(Logger::FMT_PLAIN);
}
%}
@@ -1447,4 +1488,31 @@ object = _old_object
return d;
}
}
%extend Logger {
%pythoncode %{
def __iter__(self):
return iter(self.log_messages())
%}
}
%extend log_message {
std::string severity_string() const {
static const char* const severity_strings[] = {"PERF", "DEBUG", "NOTICE", "WARNING", "ERROR"};
return severity_strings[(int)$self->severity];
}
%pythoncode %{
severity_string = property(severity_string)
def to_dict(self):
keys = ("timestamp", "severity", "code", "message", "instance", "product")
return dict(zip(keys, self.to_tuple()))
def to_tuple(self):
return self.timestamp, self.severity_string, self.code, self.message, self.instance, self.product
def __eq__(self, other):
return type(self) == type(other) and self.to_tuple() == other.to_tuple()
def __hash__(self):
return hash(self.to_tuple())
def __repr__(self):
return "<log_message '[%s] [%s] %s'>" % (self.severity_string, self.code, self.message)
%}
}
-1
View File
@@ -59,7 +59,6 @@
%}
%template(DoubleArray3) std::array<double, 3>;
%ignore IfcGeom::NumberNativeDouble;
%ignore ifcopenshell::geometry::Converter;
// Not relevant for python: new_IfcBaseClass() calls instantiate()
+14 -3
View File
@@ -57,9 +57,19 @@
if (PySequence_Size(aggregate) == -1) return false;
for(Py_ssize_t i = 0; i < PySequence_Size(aggregate); ++i) {
PyObject* element = PySequence_GetItem(aggregate, i);
// This is equivalent to the PyFloat_CheckExact macro. This means
// that direct instances of int, float, str, etc. need to be used.
bool b = element->ob_type == type_obj;
// Accept the exact type or, for the numeric types, a subclass such
// as a numpy scalar (numpy.float64 subclasses float), so that numpy
// arrays can be assigned. The REAL vs INTEGER distinction is kept: a
// float is not accepted where an int is expected and vice versa, and
// bool (a subclass of int) is still rejected for INTEGER. See #5873.
bool b;
if (type_obj == static_cast<void*>(&PyFloat_Type)) {
b = PyFloat_Check(element);
} else if (type_obj == static_cast<void*>(&PyLong_Type)) {
b = PyLong_Check(element) && !PyBool_Check(element);
} else {
b = element->ob_type == type_obj;
}
Py_DECREF(element);
if (!b) {
return false;
@@ -249,6 +259,7 @@
PyObject* pythonize(const ifcopenshell::inverse_attribute* t) { return SWIG_NewPointerObj(SWIG_as_voidptr(t), SWIGTYPE_p_ifcopenshell__inverse_attribute, 0); }
PyObject* pythonize(const ifcopenshell::entity* t) { return SWIG_NewPointerObj(SWIG_as_voidptr(t), SWIGTYPE_p_ifcopenshell__entity, 0); }
PyObject* pythonize(const ifcopenshell::declaration* t) { return SWIG_NewPointerObj(SWIG_as_voidptr(t), declaration_type_to_swig(t), 0); }
PyObject* pythonize(const log_message& t) { return SWIG_NewPointerObj(SWIG_as_voidptr(&t), SWIGTYPE_p_log_message, 0); }
// @nb ownership
PyObject* pythonize(const IfcGeom::ConversionResultShape* t) { return SWIG_NewPointerObj(SWIG_as_voidptr(t), SWIGTYPE_p_IfcGeom__ConversionResultShape, SWIG_POINTER_OWN); }
// PyObject* pythonize(const IfcGeom::ConversionResultShape* t) { return SWIG_NewPointerObj(SWIG_as_voidptr(t), SWIGTYPE_p_IfcGeom__ConversionResultShape, 0); }
+18
View File
@@ -356,3 +356,21 @@ CREATE_OPTIONAL_TYPEMAP_IN(std::string, string, str)
CREATE_SET_TYPEMAP_IN(int)
CREATE_SET_TYPEMAP_IN(std::string)
// for the logger codes
%typemap(typecheck, precedence=SWIG_TYPECHECK_STRING) SWIGTYPE &code_prefix {
$1 = PyUnicode_Check($input);
}
%typemap(in) SWIGTYPE &code_prefix (char tmp[4]) {
Py_ssize_t len = 0;
const char *s = PyUnicode_AsUTF8AndSize($input, &len);
if (!s || len != 3) {
SWIG_exception_fail(SWIG_ValueError, "Expected Python str of length 3");
}
memcpy(tmp, s, 3);
tmp[3] = '\0';
$1 = &tmp;
}
+1
View File
@@ -95,6 +95,7 @@ CREATE_VECTOR_TYPEMAP_OUT(ifcopenshell::inverse_attribute const *)
CREATE_VECTOR_TYPEMAP_OUT(ifcopenshell::entity const *)
CREATE_VECTOR_TYPEMAP_OUT(ifcopenshell::declaration const *)
CREATE_VECTOR_TYPEMAP_OUT(IfcGeom::ConversionResultShape *)
CREATE_VECTOR_TYPEMAP_OUT(log_message)
%typemap(out) ifcopenshell::geometry::Settings::value_variant_t {
pythonizing_visitor vis;