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
+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)
%}
}