2026-03-31 15:32:36 +02:00
|
|
|
/********************************************************************************
|
2013-03-24 10:48:39 +00:00
|
|
|
* *
|
|
|
|
|
* 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 <http://www.gnu.org/licenses/>. *
|
|
|
|
|
* *
|
|
|
|
|
********************************************************************************/
|
|
|
|
|
|
|
|
|
|
/********************************************************************************
|
|
|
|
|
* *
|
2022-08-27 07:53:17 -04:00
|
|
|
* This started as a brief example of how IfcOpenShell can be interfaced from *
|
2013-03-24 10:48:39 +00:00
|
|
|
* within a C++ context, it has since then evolved into a fullfledged command *
|
|
|
|
|
* line application that is able to convert geometry in an IFC files into *
|
2022-08-27 07:53:17 -04:00
|
|
|
* several tessellated and topological output formats. *
|
2013-03-24 10:48:39 +00:00
|
|
|
* *
|
|
|
|
|
********************************************************************************/
|
|
|
|
|
|
2024-06-10 20:10:53 +02:00
|
|
|
// windows stuff: defines max as a macro when including windows.h
|
|
|
|
|
// error C2589: '(': illegal token on right side of '::'
|
|
|
|
|
#define NOMINMAX
|
|
|
|
|
|
2026-04-18 15:46:21 +02:00
|
|
|
#include "../serializers/document_serializer_plugin.h"
|
|
|
|
|
#include "../serializers/geometry_serializer_plugin.h"
|
2015-07-26 14:26:13 +00:00
|
|
|
|
2023-03-21 20:15:01 +01:00
|
|
|
#include "../ifcgeom/IfcGeomFilter.h"
|
|
|
|
|
#include "../ifcgeom/Iterator.h"
|
|
|
|
|
#include "../ifcgeom/IfcGeomRenderStyles.h"
|
2025-09-26 14:24:49 +02:00
|
|
|
#include "../ifcgeom/hybrid_kernel.h"
|
2013-03-24 10:48:39 +00:00
|
|
|
|
2019-02-08 15:18:55 +01:00
|
|
|
#include "../ifcparse/utils.h"
|
|
|
|
|
|
2023-03-22 11:17:14 +01:00
|
|
|
#ifdef IFOPSH_WITH_OPENCASCADE
|
|
|
|
|
|
2016-01-25 13:30:33 +01:00
|
|
|
#include <Standard_Version.hxx>
|
2015-11-23 15:52:12 +02:00
|
|
|
|
2023-03-22 11:17:14 +01:00
|
|
|
#endif
|
|
|
|
|
|
2016-03-07 22:37:36 +02:00
|
|
|
#include <boost/program_options.hpp>
|
2026-01-04 10:40:02 +01:00
|
|
|
#include <boost/optional/optional_io.hpp>
|
2018-10-08 16:36:48 +03:00
|
|
|
#include <boost/make_shared.hpp>
|
2016-03-07 22:37:36 +02:00
|
|
|
|
2026-04-19 12:32:10 +02:00
|
|
|
#include <algorithm>
|
2016-03-07 22:37:36 +02:00
|
|
|
#include <fstream>
|
2026-04-19 12:32:10 +02:00
|
|
|
#include <iomanip>
|
|
|
|
|
#include <map>
|
2016-03-07 22:37:36 +02:00
|
|
|
#include <set>
|
2026-04-19 12:32:10 +02:00
|
|
|
#include <sstream>
|
2016-03-07 22:37:36 +02:00
|
|
|
#include <time.h>
|
|
|
|
|
|
2016-02-07 12:28:05 +02:00
|
|
|
#if USE_VLD
|
2016-02-07 00:30:08 +02:00
|
|
|
#include <vld.h>
|
|
|
|
|
#endif
|
|
|
|
|
|
2019-02-08 15:18:55 +01:00
|
|
|
#ifdef _MSC_VER
|
|
|
|
|
#include <io.h>
|
|
|
|
|
#include <fcntl.h>
|
2019-05-21 12:03:44 +01:00
|
|
|
#endif
|
2019-07-09 16:00:58 +02:00
|
|
|
|
2019-02-08 15:18:55 +01:00
|
|
|
#include <random>
|
2019-07-09 16:00:58 +02:00
|
|
|
#include <thread>
|
2019-02-08 15:18:55 +01:00
|
|
|
|
|
|
|
|
#if defined(_MSC_VER) && defined(_UNICODE)
|
|
|
|
|
typedef std::wstring path_t;
|
2020-08-16 16:17:38 +02:00
|
|
|
typedef std::wofstream ofstream_t;
|
2019-02-08 15:18:55 +01:00
|
|
|
static std::wostream& cout_ = std::wcout;
|
|
|
|
|
static std::wostream& cerr_ = std::wcerr;
|
|
|
|
|
#else
|
|
|
|
|
typedef std::string path_t;
|
2020-08-16 16:17:38 +02:00
|
|
|
typedef std::ofstream ofstream_t;
|
2019-02-08 15:18:55 +01:00
|
|
|
static std::ostream& cout_ = std::cout;
|
|
|
|
|
static std::ostream& cerr_ = std::cerr;
|
|
|
|
|
#endif
|
|
|
|
|
|
2019-05-28 12:48:40 +02:00
|
|
|
const std::string DEFAULT_EXTENSION = ".obj";
|
2016-03-20 12:48:20 +02:00
|
|
|
const std::string TEMP_FILE_EXTENSION = ".tmp";
|
2013-03-24 10:48:39 +00:00
|
|
|
|
2016-12-16 13:51:07 +02:00
|
|
|
namespace po = boost::program_options;
|
|
|
|
|
|
2026-04-19 12:32:10 +02:00
|
|
|
namespace {
|
|
|
|
|
|
|
|
|
|
struct serializer_usage_line {
|
|
|
|
|
std::string extensions;
|
|
|
|
|
std::string name;
|
|
|
|
|
std::string description;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
std::string join_extensions(const std::vector<std::string>& extensions) {
|
|
|
|
|
std::ostringstream stream;
|
|
|
|
|
for (std::size_t i = 0; i < extensions.size(); ++i) {
|
|
|
|
|
if (i != 0) {
|
|
|
|
|
stream << ", ";
|
|
|
|
|
}
|
|
|
|
|
stream << extensions[i];
|
|
|
|
|
}
|
|
|
|
|
return stream.str();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void print_serializer_section(const char* title, std::vector<serializer_usage_line> lines) {
|
|
|
|
|
if (lines.empty()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::size_t extensions_width = 0;
|
|
|
|
|
std::size_t name_width = 0;
|
|
|
|
|
for (const auto& line : lines) {
|
|
|
|
|
extensions_width = std::max(extensions_width, line.extensions.size());
|
|
|
|
|
name_width = std::max(name_width, line.name.size());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cout_ << title << "\n";
|
|
|
|
|
for (const auto& line : lines) {
|
|
|
|
|
const auto extensions = ifcopenshell::path::from_utf8(line.extensions);
|
|
|
|
|
const auto name = ifcopenshell::path::from_utf8(line.name);
|
|
|
|
|
const auto description = ifcopenshell::path::from_utf8(line.description);
|
|
|
|
|
cout_ << " "
|
|
|
|
|
<< std::left << std::setw(static_cast<int>(extensions_width + 2)) << extensions
|
|
|
|
|
<< std::setw(static_cast<int>(name_width + 2)) << name
|
|
|
|
|
<< description << "\n";
|
|
|
|
|
}
|
|
|
|
|
cout_ << "\n";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::vector<serializer_usage_line> geometry_serializer_usage_lines() {
|
|
|
|
|
auto serializers = ifcopenshell::serializers::geometry_serializer_registry_instance().serializers();
|
|
|
|
|
std::sort(serializers.begin(), serializers.end(), [](const auto& a, const auto& b) {
|
|
|
|
|
const auto& lhs = a.extensions.empty() ? a.format : a.extensions.front();
|
|
|
|
|
const auto& rhs = b.extensions.empty() ? b.format : b.extensions.front();
|
|
|
|
|
if (lhs != rhs) {
|
|
|
|
|
return lhs < rhs;
|
|
|
|
|
}
|
|
|
|
|
return a.format < b.format;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
std::vector<serializer_usage_line> lines;
|
|
|
|
|
lines.reserve(serializers.size());
|
|
|
|
|
for (const auto& info : serializers) {
|
|
|
|
|
lines.push_back({ join_extensions(info.extensions), info.name, info.description });
|
|
|
|
|
}
|
|
|
|
|
return lines;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::vector<serializer_usage_line> document_serializer_usage_lines() {
|
|
|
|
|
auto serializers = ifcopenshell::serializers::document_serializer_registry_instance().serializers();
|
|
|
|
|
std::sort(serializers.begin(), serializers.end(), [](const auto& a, const auto& b) {
|
|
|
|
|
if (a.format != b.format) {
|
|
|
|
|
return a.format < b.format;
|
|
|
|
|
}
|
|
|
|
|
return a.schema_name < b.schema_name;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
std::set<std::string> seen_formats;
|
|
|
|
|
std::vector<serializer_usage_line> lines;
|
|
|
|
|
for (const auto& info : serializers) {
|
|
|
|
|
if (!seen_formats.insert(info.format).second) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
lines.push_back({ "." + info.format, info.name, info.description });
|
|
|
|
|
}
|
|
|
|
|
return lines;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
2016-03-07 22:37:36 +02:00
|
|
|
void print_version()
|
|
|
|
|
{
|
2023-03-22 11:17:14 +01:00
|
|
|
cout_ << "IfcOpenShell IfcConvert " << IFCOPENSHELL_VERSION;
|
|
|
|
|
#ifdef IFOPSH_WITH_OPENCASCADE
|
|
|
|
|
cout_ << " (OCC " << OCC_VERSION_STRING_EXT << ")";
|
|
|
|
|
#endif
|
|
|
|
|
cout_ << "\n";
|
2013-03-24 10:48:39 +00:00
|
|
|
}
|
|
|
|
|
|
2016-03-11 10:05:33 +02:00
|
|
|
void print_usage(bool suggest_help = true)
|
2016-03-07 22:37:36 +02:00
|
|
|
{
|
2019-02-08 15:18:55 +01:00
|
|
|
cout_ << "Usage: IfcConvert [options] <input.ifc> [<output>]\n"
|
2016-03-07 22:37:36 +02:00
|
|
|
<< "\n"
|
2026-04-19 12:32:10 +02:00
|
|
|
<< "Converts (the geometry in) an IFC file into one of the following formats:\n\n";
|
|
|
|
|
print_serializer_section("Geometry serializers:", geometry_serializer_usage_lines());
|
|
|
|
|
print_serializer_section("Document serializers:", document_serializer_usage_lines());
|
|
|
|
|
print_serializer_section("Built-in:", { { ".ifc", "IFC-SPF", "Industry Foundation Classes." } });
|
|
|
|
|
cout_
|
2026-03-31 15:32:36 +02:00
|
|
|
<< "If no output filename given, <input>" << ifcopenshell::path::from_utf8(DEFAULT_EXTENSION) << " will be used as the output file.\n";
|
2016-03-11 10:05:33 +02:00
|
|
|
if (suggest_help) {
|
2019-02-08 15:18:55 +01:00
|
|
|
cout_ << "\nRun 'IfcConvert --help' for more information.";
|
2016-03-11 10:05:33 +02:00
|
|
|
}
|
2019-02-08 15:18:55 +01:00
|
|
|
cout_ << std::endl;
|
2016-03-07 22:37:36 +02:00
|
|
|
}
|
|
|
|
|
|
2017-02-21 16:04:05 +02:00
|
|
|
/// @todo Add help for single option
|
2016-12-16 13:51:07 +02:00
|
|
|
void print_options(const po::options_description& options)
|
2016-03-07 22:37:36 +02:00
|
|
|
{
|
2019-02-08 15:18:55 +01:00
|
|
|
#if defined(_MSC_VER) && defined(_UNICODE)
|
|
|
|
|
// See issue https://svn.boost.org/trac10/ticket/10952
|
|
|
|
|
std::ostringstream temp;
|
|
|
|
|
temp << options;
|
|
|
|
|
cout_ << "\n" << temp.str().c_str();
|
|
|
|
|
#else
|
|
|
|
|
cout_ << "\n" << options;
|
|
|
|
|
#endif
|
|
|
|
|
cout_ << std::endl;
|
2013-03-24 10:48:39 +00:00
|
|
|
}
|
|
|
|
|
|
2019-02-08 15:18:55 +01:00
|
|
|
template <typename T>
|
|
|
|
|
T change_extension(const T& fn, const T& ext) {
|
|
|
|
|
typename T::size_type dot = fn.find_last_of('.');
|
|
|
|
|
if (dot != T::npos) {
|
|
|
|
|
return fn.substr(0, dot) + ext;
|
2013-03-24 10:48:39 +00:00
|
|
|
} else {
|
2019-02-08 15:18:55 +01:00
|
|
|
return fn + ext;
|
2013-03-24 10:48:39 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-02-08 15:18:55 +01:00
|
|
|
bool file_exists(const std::string& filename) {
|
2026-03-31 15:32:36 +02:00
|
|
|
std::ifstream file(ifcopenshell::path::from_utf8(filename).c_str());
|
2016-03-18 22:14:05 +02:00
|
|
|
return file.good();
|
|
|
|
|
}
|
|
|
|
|
|
2019-02-08 15:18:55 +01:00
|
|
|
static std::basic_stringstream<path_t::value_type> log_stream;
|
2018-05-12 20:12:25 +02:00
|
|
|
void write_log(bool);
|
2026-03-31 15:32:36 +02:00
|
|
|
void fix_quantities(ifcopenshell::file&, bool, bool, bool);
|
2018-10-01 13:19:31 +03:00
|
|
|
std::string format_duration(time_t start, time_t end);
|
2013-12-31 14:10:38 +00:00
|
|
|
|
2017-02-24 16:46:14 +02:00
|
|
|
/// @todo make the filters non-global
|
2017-02-20 19:41:52 +02:00
|
|
|
IfcGeom::entity_filter entity_filter; // Entity filter is used always by default.
|
|
|
|
|
IfcGeom::layer_filter layer_filter;
|
2018-09-07 14:08:56 +02:00
|
|
|
IfcGeom::attribute_filter attribute_filter;
|
2017-02-20 19:41:52 +02:00
|
|
|
|
2016-12-16 13:51:07 +02:00
|
|
|
struct geom_filter
|
|
|
|
|
{
|
2017-03-01 23:51:57 +02:00
|
|
|
geom_filter(bool include, bool traverse) : type(UNUSED), include(include), traverse(traverse) {}
|
|
|
|
|
geom_filter() : type(UNUSED), include(false), traverse(false) {}
|
2017-02-16 19:49:46 +02:00
|
|
|
enum filter_type { UNUSED, ENTITY_TYPE, LAYER_NAME, ENTITY_ARG };
|
2016-12-16 13:51:07 +02:00
|
|
|
filter_type type;
|
2017-03-01 23:51:57 +02:00
|
|
|
bool include;
|
|
|
|
|
bool traverse;
|
2017-02-16 14:28:18 +02:00
|
|
|
std::string arg;
|
2016-12-16 13:51:07 +02:00
|
|
|
std::set<std::string> values;
|
|
|
|
|
};
|
|
|
|
|
// Specialized classes for knowing which type of filter we are validating within validate().
|
|
|
|
|
// Could not figure out easily how else to know it if using single type for both.
|
2017-03-01 23:51:57 +02:00
|
|
|
struct inclusion_filter : public geom_filter { inclusion_filter() : geom_filter(true, false) {} };
|
|
|
|
|
struct inclusion_traverse_filter : public geom_filter { inclusion_traverse_filter() : geom_filter(true, true) {} };
|
|
|
|
|
struct exclusion_filter : public geom_filter { exclusion_filter() : geom_filter(false, false) {} };
|
|
|
|
|
struct exclusion_traverse_filter : public geom_filter { exclusion_traverse_filter() : geom_filter(false, true) {} };
|
2016-12-16 13:51:07 +02:00
|
|
|
|
2017-05-19 18:10:20 +03:00
|
|
|
size_t read_filters_from_file(const std::string&, inclusion_filter&, inclusion_traverse_filter&, exclusion_filter&, exclusion_traverse_filter&);
|
|
|
|
|
void parse_filter(geom_filter &, const std::vector<std::string>&);
|
2026-01-04 10:40:02 +01:00
|
|
|
std::vector<ifcopenshell::geometry::filter_t> setup_filters(const std::vector<geom_filter>&, const std::string&);
|
2017-02-20 19:41:52 +02:00
|
|
|
|
2026-03-31 15:32:36 +02:00
|
|
|
bool init_input_file(const std::string& filename, ifcopenshell::file*& ifc_file, bool no_progress, bool mmap, bool bypass_properties=false);
|
2017-06-15 22:07:56 +03:00
|
|
|
|
2020-08-16 13:37:58 +02:00
|
|
|
// from https://stackoverflow.com/questions/31696328/boost-program-options-using-zero-parameter-options-multiple-times
|
|
|
|
|
struct verbosity_counter {
|
2020-08-17 18:02:59 +02:00
|
|
|
int count;
|
|
|
|
|
verbosity_counter(int c = 0) {
|
|
|
|
|
count = c;
|
|
|
|
|
}
|
2020-08-16 13:37:58 +02:00
|
|
|
};
|
|
|
|
|
|
2019-02-08 15:18:55 +01:00
|
|
|
#if defined(_MSC_VER) && defined(_UNICODE)
|
|
|
|
|
int wmain(int argc, wchar_t** argv) {
|
|
|
|
|
typedef po::wcommand_line_parser command_line_parser;
|
|
|
|
|
typedef wchar_t char_t;
|
|
|
|
|
|
|
|
|
|
_setmode(_fileno(stdout), _O_U16TEXT);
|
|
|
|
|
_setmode(_fileno(stderr), _O_U16TEXT);
|
|
|
|
|
#else
|
|
|
|
|
int main(int argc, char** argv) {
|
|
|
|
|
typedef po::command_line_parser command_line_parser;
|
|
|
|
|
typedef char char_t;
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
inclusion_filter include_filter;
|
|
|
|
|
inclusion_traverse_filter include_traverse_filter;
|
|
|
|
|
exclusion_filter exclude_filter;
|
|
|
|
|
exclusion_traverse_filter exclude_traverse_filter;
|
|
|
|
|
path_t filter_filename;
|
|
|
|
|
path_t default_material_filename;
|
2020-08-16 16:17:38 +02:00
|
|
|
path_t log_file;
|
2021-10-08 15:38:46 +02:00
|
|
|
path_t cache_file;
|
2018-05-12 20:12:25 +02:00
|
|
|
std::string log_format;
|
2023-03-21 20:15:01 +01:00
|
|
|
std::string geometry_kernel;
|
2026-04-19 12:32:10 +02:00
|
|
|
auto& document_serializer_registry = ifcopenshell::serializers::document_serializer_registry_instance();
|
|
|
|
|
auto& geometry_serializer_registry = ifcopenshell::serializers::geometry_serializer_registry_instance();
|
|
|
|
|
#ifdef WITH_HDF5
|
|
|
|
|
const bool supports_geometry_cache = geometry_serializer_registry.has(".h5");
|
|
|
|
|
#else
|
|
|
|
|
const bool supports_geometry_cache = false;
|
|
|
|
|
#endif
|
2019-02-08 15:18:55 +01:00
|
|
|
|
2016-12-16 13:51:07 +02:00
|
|
|
po::options_description generic_options("Command line options");
|
2020-08-16 13:37:58 +02:00
|
|
|
verbosity_counter vcounter;
|
2013-03-24 10:48:39 +00:00
|
|
|
generic_options.add_options()
|
2016-03-07 22:37:36 +02:00
|
|
|
("help,h", "display usage information")
|
2013-03-24 10:48:39 +00:00
|
|
|
("version", "display version information")
|
2021-11-26 11:33:00 -03:00
|
|
|
("verbose,v", po::value(&vcounter)->zero_tokens(), "more verbose log messages. Use twice (-vv) for debugging level.")
|
2018-05-12 20:12:25 +02:00
|
|
|
("quiet,q", "less status and progress output")
|
2018-08-13 12:54:52 +02:00
|
|
|
("stderr-progress", "output progress to stderr stream")
|
2018-05-12 20:12:25 +02:00
|
|
|
("yes,y", "answer 'yes' automatically to possible confirmation queries (e.g. overwriting an existing output file)")
|
|
|
|
|
("no-progress", "suppress possible progress bar type of prints that use carriage return")
|
2020-08-16 16:17:38 +02:00
|
|
|
("log-format", po::value<std::string>(&log_format), "log format: plain or json")
|
|
|
|
|
("log-file", new po::typed_value<path_t, char_t>(&log_file), "redirect log output to file");
|
2026-04-19 12:32:10 +02:00
|
|
|
if (supports_geometry_cache) {
|
|
|
|
|
generic_options.add_options()
|
|
|
|
|
("cache", "cache geometry creation. Use --cache-file to specify cache file path.");
|
|
|
|
|
}
|
2013-03-24 10:48:39 +00:00
|
|
|
|
2016-12-16 13:51:07 +02:00
|
|
|
po::options_description fileio_options;
|
2013-03-24 10:48:39 +00:00
|
|
|
fileio_options.add_options()
|
2017-07-23 14:52:04 +02:00
|
|
|
#ifdef USE_MMAP
|
|
|
|
|
("mmap", "use memory-mapped file for input")
|
|
|
|
|
#endif
|
2019-02-08 15:18:55 +01:00
|
|
|
("input-file", new po::typed_value<path_t, char_t>(0), "input IFC file")
|
2021-10-08 15:38:46 +02:00
|
|
|
("output-file", new po::typed_value<path_t, char_t>(0), "output geometry file")
|
2025-08-25 12:45:10 +02:00
|
|
|
("stream", "Use streaming conversion (currently supported with conversion to RocksDB)")
|
2021-10-08 15:38:46 +02:00
|
|
|
;
|
2026-04-19 12:32:10 +02:00
|
|
|
if (supports_geometry_cache) {
|
|
|
|
|
fileio_options.add_options()
|
|
|
|
|
("cache-file", new po::typed_value<path_t, char_t>(&cache_file), "geometry cache file");
|
|
|
|
|
}
|
2021-10-08 15:38:46 +02:00
|
|
|
|
2018-11-02 16:10:06 +01:00
|
|
|
po::options_description ifc_options("IFC options");
|
|
|
|
|
ifc_options.add_options()
|
|
|
|
|
("calculate-quantities", "Calculate or fix the physical quantity definitions "
|
|
|
|
|
"based on an interpretation of the geometry when exporting IFC");
|
2019-07-03 12:10:27 +02:00
|
|
|
|
2019-07-19 15:55:22 +02:00
|
|
|
int num_threads;
|
2020-02-13 19:23:28 +01:00
|
|
|
std::string offset_str, rotation_str;
|
2023-03-21 20:15:01 +01:00
|
|
|
|
|
|
|
|
std::string default_kernel;
|
2026-04-07 15:47:58 +02:00
|
|
|
#ifdef IFOPSH_WITH_MANIFOLD
|
|
|
|
|
default_kernel = "manifold";
|
|
|
|
|
#endif
|
2023-03-21 20:15:01 +01:00
|
|
|
#ifdef IFOPSH_WITH_CGAL
|
|
|
|
|
default_kernel = "cgal";
|
|
|
|
|
#endif
|
|
|
|
|
#ifdef IFOPSH_WITH_OPENCASCADE
|
|
|
|
|
default_kernel = "opencascade";
|
|
|
|
|
#endif
|
2023-10-06 13:27:47 +02:00
|
|
|
|
|
|
|
|
// none, convex-decomposition, minkowski-triangles or halfspace-snapping
|
|
|
|
|
std::string exterior_only_algo;
|
2023-11-15 10:32:20 +01:00
|
|
|
|
|
|
|
|
ifcopenshell::geometry::Settings geometry_settings;
|
2018-11-02 16:10:06 +01:00
|
|
|
|
|
|
|
|
po::options_description geom_options("Geometry options");
|
2013-03-24 10:48:39 +00:00
|
|
|
geom_options.add_options()
|
2023-03-21 20:15:01 +01:00
|
|
|
("kernel", po::value<std::string>(&geometry_kernel)->default_value(default_kernel),
|
2026-04-09 17:17:53 +02:00
|
|
|
"Geometry kernel to use (opencascade, cgal, cgal-simple, manifold, passthrough, hybrid-cgal-simple-opencascade, hybrid-passthrough-opencascade).")
|
2019-08-02 13:27:33 +02:00
|
|
|
("threads,j", po::value<int>(&num_threads)->default_value(1),
|
2019-07-03 12:10:27 +02:00
|
|
|
"Number of parallel processing threads for geometry interpretation.")
|
2020-02-13 19:23:28 +01:00
|
|
|
("center-model",
|
|
|
|
|
"Centers the elements by applying the center point of all placements as an offset."
|
|
|
|
|
"Can take several minutes on large models.")
|
2020-02-12 11:38:08 +01:00
|
|
|
("center-model-geometry",
|
|
|
|
|
"Centers the elements by applying the center point of all mesh vertices as an offset.")
|
2025-03-19 11:42:11 +01:00
|
|
|
("model-offset", po::value<std::string>(&offset_str),
|
|
|
|
|
"Applies an arbitrary offset of form 'x;y;z' to all placements.")
|
|
|
|
|
("model-rotation", po::value<std::string>(&rotation_str),
|
|
|
|
|
"Applies an arbitrary quaternion rotation of form 'x;y;z;w' to all placements.")
|
2017-12-20 12:40:15 +01:00
|
|
|
("include", po::value<inclusion_filter>(&include_filter)->multitoken(),
|
2018-09-07 14:08:56 +02:00
|
|
|
"Specifies that the instances that match a specific filtering criteria are to be included in the geometrical output:\n"
|
2017-12-20 12:40:15 +01:00
|
|
|
"1) 'entities': the following list of types should be included. SVG output defaults "
|
|
|
|
|
"to IfcSpace to be included. The entity names are handled case-insensitively.\n"
|
2018-09-07 14:08:56 +02:00
|
|
|
"2) 'layers': the instances that are assigned to presentation layers of which names "
|
2017-12-20 12:40:15 +01:00
|
|
|
"match the given values should be included.\n"
|
2018-09-07 14:08:56 +02:00
|
|
|
"3) 'attribute <AttributeName>': products whose value for <AttributeName> should be included\n. "
|
2017-12-20 12:40:15 +01:00
|
|
|
"Currently supported arguments are GlobalId, Name, Description, and Tag.\n\n"
|
|
|
|
|
"The values for 'layers' and 'arg' are handled case-sensitively (wildcards supported)."
|
|
|
|
|
"--include and --exclude cannot be placed right before input file argument and "
|
|
|
|
|
"only single of each argument supported for now. See also --exclude.")
|
|
|
|
|
("include+", po::value<inclusion_traverse_filter>(&include_traverse_filter)->multitoken(),
|
|
|
|
|
"Same as --include but applies filtering also to the decomposition and/or containment (IsDecomposedBy, "
|
|
|
|
|
"HasOpenings, FillsVoid, ContainedInStructure) of the filtered entity, e.g. --include+=arg Name \"Level 1\" "
|
|
|
|
|
"includes entity with name \"Level 1\" and all of its children. See --include for more information. ")
|
|
|
|
|
("exclude", po::value<exclusion_filter>(&exclude_filter)->multitoken(),
|
|
|
|
|
"Specifies that the entities that match a specific filtering criteria are to be excluded in the geometrical output."
|
|
|
|
|
"See --include for syntax and more details. The default value is '--exclude=entities IfcOpeningElement IfcSpace'.")
|
|
|
|
|
("exclude+", po::value<exclusion_traverse_filter>(&exclude_traverse_filter)->multitoken(),
|
|
|
|
|
"Same as --exclude but applies filtering also to the decomposition and/or containment "
|
|
|
|
|
"of the filtered entity. See --include+ for more details.")
|
2019-03-08 11:40:16 +01:00
|
|
|
("filter-file", new po::typed_value<path_t, char_t>(&filter_filename),
|
2017-12-20 12:40:15 +01:00
|
|
|
"Specifies a filter file that describes the used filtering criteria. Supported formats "
|
2018-09-07 14:42:15 +02:00
|
|
|
"are '--include=arg GlobalId ...' and 'include arg GlobalId ...'. Spaces and tabs can be used as delimiters."
|
2017-12-20 12:40:15 +01:00
|
|
|
"Multiple filters of same type with different values can be inserted on their own lines. "
|
2018-09-07 14:08:56 +02:00
|
|
|
"See --include, --include+, --exclude, and --exclude+ for more details.")
|
2019-02-08 15:18:55 +01:00
|
|
|
("default-material-file", new po::typed_value<path_t, char_t>(&default_material_filename),
|
2018-09-20 15:58:24 +02:00
|
|
|
"Specifies a material file that describes the material object types will have"
|
2018-12-10 11:33:54 +01:00
|
|
|
"if an object does not have any specified material in the IFC file.")
|
2023-10-06 13:27:47 +02:00
|
|
|
("exterior-only",
|
|
|
|
|
po::value<std::string>(&exterior_only_algo)->default_value("none")->implicit_value("minkowski-triangles"),
|
|
|
|
|
"Export only the exterior shell of the building found by geometric analysis. convex-decomposition, minkowski-triangles or halfspace-snapping")
|
2024-04-18 21:04:02 +02:00
|
|
|
("plan", "Specifies whether to include curves in the output result. Typically "
|
|
|
|
|
"these are representations of type Plan or Axis. Excluded by default.")
|
|
|
|
|
("model", "Specifies whether to include surfaces and solids in the output result. "
|
|
|
|
|
"Typically these are representations of type Body or Facetation. ")
|
2023-11-15 10:32:20 +01:00
|
|
|
;
|
|
|
|
|
|
|
|
|
|
geometry_settings.define_options(geom_options);
|
2016-03-07 22:37:36 +02:00
|
|
|
|
2020-02-13 19:23:28 +01:00
|
|
|
std::string bounds;
|
2017-01-13 12:10:50 +02:00
|
|
|
#ifdef HAVE_ICU
|
|
|
|
|
std::string unicode_mode;
|
|
|
|
|
#endif
|
2017-01-11 16:36:33 +02:00
|
|
|
short precision;
|
2020-07-11 13:39:09 +02:00
|
|
|
|
2023-11-15 10:32:20 +01:00
|
|
|
ifcopenshell::geometry::SerializerSettings serializer_settings;
|
|
|
|
|
|
2016-12-16 13:51:07 +02:00
|
|
|
po::options_description serializer_options("Serialization options");
|
2016-03-07 22:37:36 +02:00
|
|
|
serializer_options.add_options()
|
2017-01-13 12:10:50 +02:00
|
|
|
#ifdef HAVE_ICU
|
2016-12-16 13:51:07 +02:00
|
|
|
("unicode", po::value<std::string>(&unicode_mode),
|
2017-01-13 12:10:50 +02:00
|
|
|
"Specifies the Unicode handling behavior when parsing the IFC file. "
|
|
|
|
|
"Accepted values 'utf8' (the default) and 'escape'.")
|
|
|
|
|
#endif
|
2020-06-15 16:24:16 +02:00
|
|
|
;
|
2016-03-07 22:37:36 +02:00
|
|
|
|
2023-11-15 10:32:20 +01:00
|
|
|
serializer_settings.define_options(serializer_options);
|
|
|
|
|
|
2016-12-16 13:51:07 +02:00
|
|
|
po::options_description cmdline_options;
|
2018-11-02 16:24:32 +01:00
|
|
|
cmdline_options.add(generic_options).add(fileio_options).add(geom_options).add(ifc_options).add(serializer_options);
|
2013-03-24 10:48:39 +00:00
|
|
|
|
2016-12-16 13:51:07 +02:00
|
|
|
po::positional_options_description positional_options;
|
2013-03-24 10:48:39 +00:00
|
|
|
positional_options.add("input-file", 1);
|
|
|
|
|
positional_options.add("output-file", 1);
|
|
|
|
|
|
2016-12-16 13:51:07 +02:00
|
|
|
po::variables_map vmap;
|
|
|
|
|
try {
|
2019-02-08 15:18:55 +01:00
|
|
|
po::store(command_line_parser(argc, argv).
|
2016-12-16 13:51:07 +02:00
|
|
|
options(cmdline_options).positional(positional_options).run(), vmap);
|
|
|
|
|
} catch (const po::unknown_option& e) {
|
2026-03-31 15:32:36 +02:00
|
|
|
cerr_ << "[error] Unknown option '" << e.get_option_name().c_str() << "'\n\n";
|
2016-03-07 22:37:36 +02:00
|
|
|
print_usage();
|
2016-12-16 13:51:07 +02:00
|
|
|
return EXIT_FAILURE;
|
|
|
|
|
} catch (const po::error_with_option_name& e) {
|
2026-03-31 15:32:36 +02:00
|
|
|
cerr_ << "[error] Invalid usage of '" << e.get_option_name().c_str() << "': " << e.what() << "\n\n";
|
2016-12-16 13:51:07 +02:00
|
|
|
return EXIT_FAILURE;
|
2017-01-11 16:36:33 +02:00
|
|
|
} catch (const std::exception& e) {
|
2026-03-31 15:32:36 +02:00
|
|
|
cerr_ << "[error] " << e.what() << "\n\n";
|
2017-01-11 16:36:33 +02:00
|
|
|
print_usage();
|
2016-12-16 13:51:07 +02:00
|
|
|
return EXIT_FAILURE;
|
2017-02-20 19:41:52 +02:00
|
|
|
} catch (...) {
|
2026-03-31 15:32:36 +02:00
|
|
|
cerr_ << "[error] Unknown error parsing command line options\n\n";
|
2016-03-07 22:37:36 +02:00
|
|
|
print_usage();
|
2017-02-02 14:58:58 +02:00
|
|
|
return EXIT_FAILURE;
|
2017-02-22 17:39:32 +02:00
|
|
|
}
|
2016-12-16 13:51:07 +02:00
|
|
|
|
|
|
|
|
po::notify(vmap);
|
2013-03-24 10:48:39 +00:00
|
|
|
|
2017-07-23 14:52:04 +02:00
|
|
|
const bool mmap = vmap.count("mmap") != 0;
|
2017-05-23 20:06:35 +03:00
|
|
|
const bool no_progress = vmap.count("no-progress") != 0;
|
2018-05-12 20:12:25 +02:00
|
|
|
const bool quiet = vmap.count("quiet") != 0;
|
2018-08-13 12:54:52 +02:00
|
|
|
const bool stderr_progress = vmap.count("stderr-progress") != 0;
|
2023-11-15 10:32:20 +01:00
|
|
|
|
2018-05-12 20:12:25 +02:00
|
|
|
const bool center_model = vmap.count("center-model") != 0;
|
2020-02-12 11:38:08 +01:00
|
|
|
const bool center_model_geometry = vmap.count("center-model-geometry") != 0;
|
2025-03-19 11:42:11 +01:00
|
|
|
const bool model_offset = vmap.count("model-offset") != 0;
|
|
|
|
|
const bool model_rotation = vmap.count("model-rotation") != 0;
|
2018-05-12 20:12:25 +02:00
|
|
|
|
2019-02-08 15:18:55 +01:00
|
|
|
if (!quiet || vmap.count("version")) {
|
2018-05-12 20:12:25 +02:00
|
|
|
print_version();
|
|
|
|
|
}
|
|
|
|
|
|
2019-02-08 15:18:55 +01:00
|
|
|
if (vmap.count("version")) {
|
2018-05-12 20:12:25 +02:00
|
|
|
return EXIT_SUCCESS;
|
|
|
|
|
} else if (vmap.count("help")) {
|
|
|
|
|
print_usage(false);
|
|
|
|
|
print_options(generic_options.add(geom_options).add(serializer_options));
|
|
|
|
|
return EXIT_SUCCESS;
|
|
|
|
|
} else if (!vmap.count("input-file")) {
|
2026-03-31 15:32:36 +02:00
|
|
|
cerr_ << "[error] Input file not specified" << std::endl;
|
2018-05-12 20:12:25 +02:00
|
|
|
print_usage();
|
|
|
|
|
return EXIT_FAILURE;
|
|
|
|
|
}
|
2021-03-09 16:11:42 +01:00
|
|
|
|
2023-09-13 22:02:52 +02:00
|
|
|
if (num_threads <= 0) {
|
|
|
|
|
num_threads = std::thread::hardware_concurrency();
|
2026-03-31 15:32:36 +02:00
|
|
|
logger::notice("Using " + std::to_string(num_threads) + " threads");
|
2023-09-13 22:02:52 +02:00
|
|
|
}
|
2019-02-08 15:18:55 +01:00
|
|
|
|
|
|
|
|
if (vmap.count("log-format") == 1) {
|
|
|
|
|
boost::to_lower(log_format);
|
|
|
|
|
if (log_format == "plain") {
|
2026-03-31 15:32:36 +02:00
|
|
|
logger::output_format(logger::FMT_PLAIN);
|
2019-02-08 15:18:55 +01:00
|
|
|
} else if (log_format == "json") {
|
2026-03-31 15:32:36 +02:00
|
|
|
logger::output_format(logger::FMT_JSON);
|
2019-02-08 15:18:55 +01:00
|
|
|
} else {
|
2026-03-31 15:32:36 +02:00
|
|
|
cerr_ << "[error] --log-format should be either plain or json" << std::endl;
|
2019-02-08 15:18:55 +01:00
|
|
|
print_usage();
|
|
|
|
|
return EXIT_FAILURE;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!filter_filename.empty()) {
|
2026-03-31 15:32:36 +02:00
|
|
|
size_t num_filters = read_filters_from_file(ifcopenshell::path::to_utf8(filter_filename), include_filter, include_traverse_filter, exclude_filter, exclude_traverse_filter);
|
2019-02-08 15:18:55 +01:00
|
|
|
if (num_filters) {
|
2026-03-31 15:32:36 +02:00
|
|
|
logger::notice(boost::lexical_cast<std::string>(num_filters) + " filters read from specifified file.");
|
2019-02-08 15:18:55 +01:00
|
|
|
} else {
|
2026-03-31 15:32:36 +02:00
|
|
|
cerr_ << "[error] No filters read from specifified file.\n";
|
2019-02-08 15:18:55 +01:00
|
|
|
return EXIT_FAILURE;
|
|
|
|
|
}
|
|
|
|
|
}
|
2015-10-07 17:33:30 +02:00
|
|
|
|
2017-01-13 12:10:50 +02:00
|
|
|
#ifdef HAVE_ICU
|
|
|
|
|
if (!unicode_mode.empty()) {
|
|
|
|
|
if (unicode_mode == "utf8") {
|
2026-03-31 15:32:36 +02:00
|
|
|
ifcopenshell::runtime_character_decoder::mode = ifcopenshell::runtime_character_decoder::UTF8;
|
2017-01-13 12:10:50 +02:00
|
|
|
} else if (unicode_mode == "escape") {
|
2026-03-31 15:32:36 +02:00
|
|
|
ifcopenshell::runtime_character_decoder::mode = ifcopenshell::runtime_character_decoder::ESCAPE;
|
2017-01-13 12:10:50 +02:00
|
|
|
} else {
|
2026-03-31 15:32:36 +02:00
|
|
|
cerr_ << "[error] Invalid value for --unicode" << std::endl;
|
2017-01-13 12:10:50 +02:00
|
|
|
print_options(serializer_options);
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
2019-02-08 15:18:55 +01:00
|
|
|
if (!default_material_filename.empty()) {
|
|
|
|
|
try {
|
2026-03-31 15:32:36 +02:00
|
|
|
IfcGeom::set_default_style_file(ifcopenshell::path::to_utf8(default_material_filename));
|
2019-02-08 15:18:55 +01:00
|
|
|
} catch (const std::exception& e) {
|
2026-03-31 15:32:36 +02:00
|
|
|
cerr_ << "[error] Could not read default material file:" << std::endl;
|
2020-07-21 23:23:39 +02:00
|
|
|
cerr_ << e.what() << std::endl;
|
2019-02-08 15:18:55 +01:00
|
|
|
return EXIT_FAILURE;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const path_t input_filename = vmap["input-file"].as<path_t>();
|
2025-02-27 22:07:31 +01:00
|
|
|
/*
|
|
|
|
|
// todo also allow rocksdb dir
|
2026-03-31 15:32:36 +02:00
|
|
|
if (!file_exists(ifcopenshell::path::to_utf8(input_filename))) {
|
|
|
|
|
cerr_ << "[error] Input file '" << input_filename << "' does not exist" << std::endl;
|
2017-02-20 19:41:52 +02:00
|
|
|
return EXIT_FAILURE;
|
2025-02-27 22:07:31 +01:00
|
|
|
}*/
|
2016-03-18 22:33:03 +02:00
|
|
|
|
2013-03-24 10:48:39 +00:00
|
|
|
// If no output filename is specified a Wavefront OBJ file will be output
|
|
|
|
|
// to maintain backwards compatibility with the obsolete IfcObj executable.
|
2019-02-08 15:18:55 +01:00
|
|
|
const path_t output_filename = vmap.count("output-file") == 1
|
|
|
|
|
? vmap["output-file"].as<path_t>()
|
2026-03-31 15:32:36 +02:00
|
|
|
: change_extension(input_filename, ifcopenshell::path::from_utf8(DEFAULT_EXTENSION));
|
2013-03-24 10:48:39 +00:00
|
|
|
|
|
|
|
|
if (output_filename.size() < 5) {
|
2026-03-31 15:32:36 +02:00
|
|
|
cerr_ << "[error] Invalid or unsupported output file '" << output_filename << "' given" << std::endl;
|
2016-03-07 22:37:36 +02:00
|
|
|
print_usage();
|
2017-02-20 19:41:52 +02:00
|
|
|
return EXIT_FAILURE;
|
2013-03-24 10:48:39 +00:00
|
|
|
}
|
|
|
|
|
|
2026-03-31 15:32:36 +02:00
|
|
|
if (file_exists(ifcopenshell::path::to_utf8(output_filename)) && !vmap.count("yes")) {
|
2016-03-18 22:14:05 +02:00
|
|
|
std::string answer;
|
2025-08-12 17:30:35 +05:00
|
|
|
cout_ << "A file '" << output_filename << "' already exists. Overwrite the existing file? y/n" << std::endl;
|
2016-03-18 22:14:05 +02:00
|
|
|
std::cin >> answer;
|
|
|
|
|
if (!boost::iequals(answer, "yes") && !boost::iequals(answer, "y")) {
|
2017-02-20 19:41:52 +02:00
|
|
|
return EXIT_SUCCESS;
|
2016-03-18 22:14:05 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-16 16:17:38 +02:00
|
|
|
ofstream_t log_fs;
|
|
|
|
|
|
|
|
|
|
if (vmap.count("log-file")) {
|
|
|
|
|
log_fs.open(log_file.c_str(), std::ios::app);
|
2026-03-31 15:32:36 +02:00
|
|
|
logger::set_output(quiet ? nullptr : &cout_, &log_fs);
|
2020-08-16 16:17:38 +02:00
|
|
|
} else {
|
2026-03-31 15:32:36 +02:00
|
|
|
logger::set_output(quiet ? nullptr : &cout_, vcounter.count > 1 ? &cout_ : &log_stream);
|
2020-08-16 16:17:38 +02:00
|
|
|
}
|
|
|
|
|
|
2022-01-07 15:12:32 +01:00
|
|
|
switch (vcounter.count) {
|
|
|
|
|
case 0:
|
2026-03-31 15:32:36 +02:00
|
|
|
logger::verbosity(logger::LOG_ERROR);
|
2022-01-07 15:12:32 +01:00
|
|
|
break;
|
|
|
|
|
case 1:
|
2026-03-31 15:32:36 +02:00
|
|
|
logger::verbosity(logger::LOG_NOTICE);
|
2022-01-07 15:12:32 +01:00
|
|
|
break;
|
|
|
|
|
case 2:
|
2026-03-31 15:32:36 +02:00
|
|
|
logger::verbosity(logger::LOG_DEBUG);
|
2022-01-07 15:12:32 +01:00
|
|
|
break;
|
|
|
|
|
case 3:
|
2026-03-31 15:32:36 +02:00
|
|
|
logger::verbosity(logger::LOG_PERF);
|
2022-01-07 15:12:32 +01:00
|
|
|
break;
|
|
|
|
|
case 4:
|
2026-03-31 15:32:36 +02:00
|
|
|
logger::verbosity(logger::LOG_PERF);
|
|
|
|
|
logger::print_performance_stats_on_element(true);
|
2022-01-07 15:12:32 +01:00
|
|
|
break;
|
|
|
|
|
}
|
2015-02-06 13:07:50 +00:00
|
|
|
|
2026-03-31 15:32:36 +02:00
|
|
|
path_t output_temp_filename = output_filename + ifcopenshell::path::from_utf8(TEMP_FILE_EXTENSION);
|
2021-02-17 17:20:03 +01:00
|
|
|
|
2021-02-20 15:35:33 +01:00
|
|
|
std::vector<path_t> tokens;
|
2021-02-17 17:20:03 +01:00
|
|
|
split(tokens, output_filename, boost::is_any_of("."));
|
2023-05-17 16:18:50 +02:00
|
|
|
std::vector<path_t>::iterator tok_iter;
|
2021-02-20 15:35:33 +01:00
|
|
|
path_t ext = *(tokens.end() - 1);
|
|
|
|
|
path_t dot;
|
2021-02-17 17:20:03 +01:00
|
|
|
dot = '.';
|
|
|
|
|
path_t output_extension = dot + ext;
|
2017-02-03 19:15:37 +02:00
|
|
|
|
2019-02-08 15:18:55 +01:00
|
|
|
boost::to_lower(output_extension);
|
2026-04-18 15:46:21 +02:00
|
|
|
const auto output_extension_utf8 = ifcopenshell::path::to_utf8(output_extension);
|
2018-05-12 20:12:25 +02:00
|
|
|
|
2026-03-31 15:32:36 +02:00
|
|
|
ifcopenshell::file* ifc_file = 0;
|
2023-09-13 22:02:52 +02:00
|
|
|
|
|
|
|
|
boost::optional<std::list<IfcGeom::Element*>> elems_from_adaptor;
|
2019-03-08 11:40:16 +01:00
|
|
|
|
2026-04-19 12:32:10 +02:00
|
|
|
const path_t IFC = ifcopenshell::path::from_utf8(".ifc");
|
2026-04-18 15:46:21 +02:00
|
|
|
|
|
|
|
|
const auto* document_serializer_info = document_serializer_registry.find(output_extension_utf8);
|
|
|
|
|
if (document_serializer_info) {
|
2018-11-02 16:10:06 +01:00
|
|
|
int exit_code = EXIT_FAILURE;
|
|
|
|
|
try {
|
2026-04-18 15:46:21 +02:00
|
|
|
const bool use_input_filename = vmap.count("stream") && document_serializer_info->supports_input_filename;
|
|
|
|
|
if (!use_input_filename && !document_serializer_info->supports_ifc_file) {
|
|
|
|
|
throw ifcopenshell::exception("Selected document serializer requires --stream");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (use_input_filename || init_input_file(ifcopenshell::path::to_utf8(input_filename), ifc_file, no_progress || quiet, mmap)) {
|
|
|
|
|
if (!use_input_filename) {
|
|
|
|
|
document_serializer_info = document_serializer_registry.find(output_extension_utf8, ifc_file->schema()->name());
|
|
|
|
|
if (!document_serializer_info) {
|
|
|
|
|
throw ifcopenshell::exception("No document serializer registered for " + output_extension_utf8 + " and schema " + ifc_file->schema()->name());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-02-06 15:39:43 +01:00
|
|
|
time_t start, end;
|
|
|
|
|
time(&start);
|
2026-04-18 15:46:21 +02:00
|
|
|
|
|
|
|
|
ifcopenshell::serializers::document_serializer_context context;
|
|
|
|
|
context.file = ifc_file;
|
|
|
|
|
context.input_filename = ifcopenshell::path::to_utf8(input_filename);
|
|
|
|
|
context.output_filename = ifcopenshell::path::to_utf8(document_serializer_info->writes_final_output ? output_filename : output_temp_filename);
|
|
|
|
|
context.schema_name = ifc_file ? ifc_file->schema()->name() : document_serializer_info->schema_name;
|
|
|
|
|
context.stream = use_input_filename;
|
|
|
|
|
|
|
|
|
|
boost::shared_ptr<Serializer> serializer = document_serializer_registry.create(output_extension_utf8, context);
|
|
|
|
|
logger::status("Writing " + boost::to_upper_copy(document_serializer_info->format) + " output...");
|
|
|
|
|
serializer->finalize();
|
|
|
|
|
serializer.reset();
|
|
|
|
|
|
2019-02-06 15:39:43 +01:00
|
|
|
time(&end);
|
2026-03-31 15:32:36 +02:00
|
|
|
logger::status("Done! Conversion took " + format_duration(start, end));
|
2019-02-06 15:39:43 +01:00
|
|
|
|
2026-04-18 15:46:21 +02:00
|
|
|
if (!document_serializer_info->writes_final_output &&
|
|
|
|
|
!ifcopenshell::path::rename_file(ifcopenshell::path::to_utf8(output_temp_filename), ifcopenshell::path::to_utf8(output_filename))) {
|
|
|
|
|
throw ifcopenshell::exception(
|
|
|
|
|
"Unable to write output file '" + ifcopenshell::path::to_utf8(output_filename) +
|
|
|
|
|
"', see '" + ifcopenshell::path::to_utf8(output_temp_filename) + "' for the conversion result");
|
|
|
|
|
}
|
2018-11-02 16:10:06 +01:00
|
|
|
exit_code = EXIT_SUCCESS;
|
|
|
|
|
}
|
|
|
|
|
} catch (const std::exception& e) {
|
2026-03-31 15:32:36 +02:00
|
|
|
logger::error(e);
|
2018-03-16 16:04:48 +01:00
|
|
|
}
|
2018-11-02 16:10:06 +01:00
|
|
|
write_log(!quiet);
|
|
|
|
|
return exit_code;
|
2019-03-08 11:40:16 +01:00
|
|
|
} else if (output_extension == IFC) {
|
2018-11-02 16:10:06 +01:00
|
|
|
int exit_code = EXIT_FAILURE;
|
|
|
|
|
try {
|
2026-03-31 15:32:36 +02:00
|
|
|
if (init_input_file(ifcopenshell::path::to_utf8(input_filename), ifc_file, no_progress || quiet, mmap)) {
|
2018-10-01 13:19:31 +03:00
|
|
|
time_t start, end;
|
2019-03-08 11:40:16 +01:00
|
|
|
time(&start);
|
2018-11-02 16:10:06 +01:00
|
|
|
std::ofstream fs(output_filename.c_str());
|
|
|
|
|
if (fs.is_open()) {
|
|
|
|
|
if (vmap.count("calculate-quantities")) {
|
|
|
|
|
fix_quantities(*ifc_file, no_progress, quiet, stderr_progress);
|
|
|
|
|
}
|
|
|
|
|
fs << *ifc_file;
|
2018-12-10 11:33:54 +01:00
|
|
|
exit_code = EXIT_SUCCESS;
|
2018-11-02 16:10:06 +01:00
|
|
|
} else {
|
2026-03-31 15:32:36 +02:00
|
|
|
logger::error("Unable to open output file for writing");
|
2018-11-02 16:10:06 +01:00
|
|
|
}
|
2018-10-01 13:19:31 +03:00
|
|
|
time(&end);
|
2026-03-31 15:32:36 +02:00
|
|
|
logger::status("Done! Writing IFC took " + format_duration(start, end));
|
2018-11-02 16:10:06 +01:00
|
|
|
}
|
|
|
|
|
} catch (const std::exception& e) {
|
2026-03-31 15:32:36 +02:00
|
|
|
logger::error(e);
|
2018-11-02 16:10:06 +01:00
|
|
|
}
|
|
|
|
|
write_log(!quiet);
|
|
|
|
|
return exit_code;
|
|
|
|
|
}
|
2026-04-18 15:46:21 +02:00
|
|
|
|
|
|
|
|
const auto* geometry_serializer_info = geometry_serializer_registry.find(output_extension_utf8);
|
|
|
|
|
if (!geometry_serializer_info) {
|
|
|
|
|
cerr_ << "[error] Unknown output filename extension '" << output_extension << "'\n";
|
2025-02-25 21:29:54 +01:00
|
|
|
write_log(!quiet);
|
2026-04-18 15:46:21 +02:00
|
|
|
print_usage();
|
|
|
|
|
return EXIT_FAILURE;
|
2025-02-25 21:29:54 +01:00
|
|
|
}
|
2018-03-16 16:04:48 +01:00
|
|
|
|
2017-03-01 23:51:57 +02:00
|
|
|
/// @todo Clean up this filter code further.
|
|
|
|
|
std::vector<geom_filter> used_filters;
|
|
|
|
|
if (include_filter.type != geom_filter::UNUSED) { used_filters.push_back(include_filter); }
|
|
|
|
|
if (include_traverse_filter.type != geom_filter::UNUSED) { used_filters.push_back(include_traverse_filter); }
|
|
|
|
|
if (exclude_filter.type != geom_filter::UNUSED) { used_filters.push_back(exclude_filter); }
|
|
|
|
|
if (exclude_traverse_filter.type != geom_filter::UNUSED) { used_filters.push_back(exclude_traverse_filter); }
|
|
|
|
|
|
2026-03-31 15:32:36 +02:00
|
|
|
std::vector<ifcopenshell::geometry::filter_t> filter_funcs = setup_filters(used_filters, ifcopenshell::path::to_utf8(output_extension));
|
2017-03-01 23:51:57 +02:00
|
|
|
if (filter_funcs.empty()) {
|
2026-03-31 15:32:36 +02:00
|
|
|
cerr_ << "[error] Failed to set up geometry filters\n";
|
2017-02-20 19:41:52 +02:00
|
|
|
return EXIT_FAILURE;
|
2017-02-07 17:34:23 +02:00
|
|
|
}
|
|
|
|
|
|
2026-03-31 15:32:36 +02:00
|
|
|
if (!entity_filter.entity_names.empty()) { entity_filter.update_description(); logger::notice(entity_filter.description); }
|
|
|
|
|
if (!layer_filter.values.empty()) { layer_filter.update_description(); logger::notice(layer_filter.description); }
|
|
|
|
|
if (!attribute_filter.attribute_name.empty()) { attribute_filter.update_description(); logger::notice(attribute_filter.description); }
|
2017-02-16 19:49:46 +02:00
|
|
|
|
2026-04-18 15:46:21 +02:00
|
|
|
if (geometry_serializer_info && geometry_serializer_info->requires_ascii_temp_file) {
|
2019-05-17 16:52:15 +02:00
|
|
|
// These serializers do not support opening unicode paths. Therefore
|
2019-02-08 15:18:55 +01:00
|
|
|
// a random temp file is generated using only ASCII characters instead.
|
|
|
|
|
std::random_device rng;
|
2019-05-18 11:01:17 +02:00
|
|
|
std::uniform_int_distribution<int> index_dist('A', 'Z');
|
|
|
|
|
{
|
|
|
|
|
std::string v = ".ifcopenshell.";
|
2021-07-17 09:06:03 +02:00
|
|
|
output_temp_filename = path_t(v.begin(), v.end());
|
2019-05-18 11:01:17 +02:00
|
|
|
}
|
2019-02-08 15:18:55 +01:00
|
|
|
for (int i = 0; i < 8; ++i) {
|
2019-05-18 11:01:17 +02:00
|
|
|
output_temp_filename.push_back(static_cast<path_t::value_type>(index_dist(rng)));
|
|
|
|
|
}
|
|
|
|
|
{
|
2020-08-19 12:56:43 +02:00
|
|
|
std::string v = ".tmp";
|
2019-05-18 11:01:17 +02:00
|
|
|
output_temp_filename += path_t(v.begin(), v.end());
|
2019-02-08 15:18:55 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-18 22:14:22 +02:00
|
|
|
// The OS will clean up for us if there is a leak
|
|
|
|
|
geometry_settings.get<ifcopenshell::geometry::settings::OcctNoCleanTriangulation>().value = true;
|
|
|
|
|
|
|
|
|
|
if (geometry_settings.get<ifcopenshell::geometry::settings::PermissiveShapeReuse>().get()) {
|
|
|
|
|
geometry_settings.get<ifcopenshell::geometry::settings::NoParallelMapping>().value = true;
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-29 20:49:11 +02:00
|
|
|
if (vmap[ifcopenshell::geometry::settings::WeldVertices::name].defaulted()) {
|
2023-11-15 10:32:20 +01:00
|
|
|
geometry_settings.get<ifcopenshell::geometry::settings::WeldVertices>().value = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (geometry_settings.get<ifcopenshell::geometry::settings::ForceSpaceTransparency>().has()) {
|
2024-06-10 21:30:26 +02:00
|
|
|
IfcGeom::update_default_style("IfcSpace")->transparency = geometry_settings.get<ifcopenshell::geometry::settings::ForceSpaceTransparency>().get();
|
2020-09-27 10:43:11 +02:00
|
|
|
}
|
|
|
|
|
|
2026-04-18 15:46:21 +02:00
|
|
|
if (geometry_settings.get<ifcopenshell::geometry::settings::UseElementHierarchy>().get() &&
|
|
|
|
|
!geometry_serializer_info->supports_user_element_hierarchy) {
|
|
|
|
|
cerr_ << "[error] --use-element-hierarchy is not supported by the selected geometry serializer.\n";
|
|
|
|
|
write_log(!quiet);
|
|
|
|
|
print_usage();
|
|
|
|
|
ifcopenshell::path::delete_file(ifcopenshell::path::to_utf8(output_temp_filename));
|
|
|
|
|
return EXIT_FAILURE;
|
2024-09-27 16:32:41 +02:00
|
|
|
}
|
|
|
|
|
|
2026-04-18 15:46:21 +02:00
|
|
|
ifcopenshell::serializers::geometry_serializer_context serializer_context{
|
|
|
|
|
ifcopenshell::path::to_utf8(output_filename),
|
|
|
|
|
ifcopenshell::path::to_utf8(output_temp_filename),
|
|
|
|
|
geometry_settings,
|
|
|
|
|
serializer_settings
|
|
|
|
|
};
|
2024-12-01 11:20:37 +01:00
|
|
|
|
2018-10-08 16:36:48 +03:00
|
|
|
boost::shared_ptr<GeometrySerializer> serializer; /**< @todo use std::unique_ptr when possible */
|
2026-04-18 15:46:21 +02:00
|
|
|
try {
|
|
|
|
|
geometry_serializer_registry.configure(output_extension_utf8, serializer_context);
|
|
|
|
|
serializer = geometry_serializer_registry.create(output_extension_utf8, serializer_context);
|
|
|
|
|
} catch (const std::exception& e) {
|
|
|
|
|
cerr_ << "[error] " << e.what() << std::endl;
|
2018-05-12 20:12:25 +02:00
|
|
|
write_log(!quiet);
|
2026-04-18 15:46:21 +02:00
|
|
|
print_options(serializer_options);
|
|
|
|
|
ifcopenshell::path::delete_file(ifcopenshell::path::to_utf8(output_temp_filename));
|
2017-02-20 19:41:52 +02:00
|
|
|
return EXIT_FAILURE;
|
2013-03-24 10:48:39 +00:00
|
|
|
}
|
|
|
|
|
|
2016-03-17 23:25:53 +02:00
|
|
|
const bool is_tesselated = serializer->isTesselated(); // isTesselated() doesn't change at run-time
|
|
|
|
|
if (!is_tesselated) {
|
2023-11-15 10:32:20 +01:00
|
|
|
if (geometry_settings.get<ifcopenshell::geometry::settings::WeldVertices>().get()) {
|
2026-03-31 15:32:36 +02:00
|
|
|
logger::notice("Weld vertices setting ignored when writing non-tesselated output");
|
2015-02-23 22:01:21 +00:00
|
|
|
}
|
2023-11-15 10:32:20 +01:00
|
|
|
if (geometry_settings.get<ifcopenshell::geometry::settings::GenerateUvs>().get()) {
|
2026-03-31 15:32:36 +02:00
|
|
|
logger::notice("Generate UVs setting ignored when writing non-tesselated output");
|
2016-03-17 00:25:33 +02:00
|
|
|
}
|
2024-11-29 13:03:56 +01:00
|
|
|
if (center_model || center_model_geometry) {
|
2026-03-31 15:32:36 +02:00
|
|
|
logger::notice("Centering/offsetting model setting ignored when writing non-tesselated output");
|
2016-03-17 23:25:53 +02:00
|
|
|
}
|
|
|
|
|
|
2023-11-15 10:32:20 +01:00
|
|
|
geometry_settings.get<ifcopenshell::geometry::settings::IteratorOutput>().value = ifcopenshell::geometry::settings::NATIVE;
|
2015-02-23 22:01:21 +00:00
|
|
|
}
|
|
|
|
|
|
2013-03-24 10:48:39 +00:00
|
|
|
if (!serializer->ready()) {
|
2026-03-31 15:32:36 +02:00
|
|
|
ifcopenshell::path::delete_file(ifcopenshell::path::to_utf8(output_temp_filename));
|
2018-05-12 20:12:25 +02:00
|
|
|
write_log(!quiet);
|
2017-02-20 19:41:52 +02:00
|
|
|
return EXIT_FAILURE;
|
2013-03-24 10:48:39 +00:00
|
|
|
}
|
|
|
|
|
|
2014-03-31 14:03:06 +00:00
|
|
|
time_t start,end;
|
|
|
|
|
time(&start);
|
2017-04-11 17:26:30 +02:00
|
|
|
|
2025-10-24 12:06:59 +02:00
|
|
|
// @nb last argument true -> bypass_properties which are not read by any of the geometry serializers
|
2026-04-18 15:46:21 +02:00
|
|
|
// Document serializers and IFC are already special-cased above
|
2025-10-24 12:06:59 +02:00
|
|
|
// SVG requires properties for IfcAnnotation/DRAWING properties
|
2026-04-18 15:46:21 +02:00
|
|
|
if (!init_input_file(ifcopenshell::path::to_utf8(input_filename), ifc_file, no_progress || quiet, mmap, geometry_serializer_info->bypass_properties)) {
|
2018-10-08 10:39:27 +03:00
|
|
|
write_log(!quiet);
|
2019-06-04 17:06:13 +02:00
|
|
|
serializer.reset();
|
2026-03-31 15:32:36 +02:00
|
|
|
ifcopenshell::path::delete_file(ifcopenshell::path::to_utf8(output_temp_filename)); /**< @todo Windows Unicode support */
|
2017-06-15 22:07:56 +03:00
|
|
|
return EXIT_FAILURE;
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-16 16:17:38 +02:00
|
|
|
if (vmap.count("log-file")) {
|
2026-03-31 15:32:36 +02:00
|
|
|
logger::set_output(quiet ? nullptr : &cout_, &log_fs);
|
2020-08-16 16:17:38 +02:00
|
|
|
} else {
|
2026-03-31 15:32:36 +02:00
|
|
|
logger::set_output(quiet ? nullptr : &cout_, vcounter.count > 1 ? &cout_ : &log_stream);
|
2020-08-16 16:17:38 +02:00
|
|
|
}
|
2025-03-19 11:42:11 +01:00
|
|
|
|
|
|
|
|
if (model_rotation) {
|
|
|
|
|
std::vector<double> rotation(4);
|
|
|
|
|
int n = 0;
|
|
|
|
|
if (sscanf(rotation_str.c_str(), "%lf;%lf;%lf;%lf %n", &rotation[0], &rotation[1], &rotation[2], &rotation[3], &n) != 4 || n != rotation_str.size()) {
|
2026-03-31 15:32:36 +02:00
|
|
|
cerr_ << "[error] Invalid use of --model-rotation\n";
|
|
|
|
|
ifcopenshell::path::delete_file(ifcopenshell::path::to_utf8(output_temp_filename));
|
2025-03-19 11:42:11 +01:00
|
|
|
print_options(serializer_options);
|
|
|
|
|
return EXIT_FAILURE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::stringstream msg;
|
|
|
|
|
msg << "Using model rotation (" << rotation[0] << "," << rotation[1] << "," << rotation[2] << "," << rotation[3] << ")";
|
2026-03-31 15:32:36 +02:00
|
|
|
logger::notice(msg.str());
|
2025-03-19 11:42:11 +01:00
|
|
|
|
|
|
|
|
geometry_settings.get<ifcopenshell::geometry::settings::ModelRotation>().value = rotation;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (model_offset && (center_model || center_model_geometry)) {
|
2026-03-31 15:32:36 +02:00
|
|
|
logger::notice("--model-offset ignored with --center-model or --center-model-geometry");
|
2025-03-19 11:42:11 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (model_offset && !(center_model || center_model_geometry)) {
|
|
|
|
|
std::vector<double> offset(3);
|
|
|
|
|
int n = 0;
|
|
|
|
|
if (sscanf(offset_str.c_str(), "%lf;%lf;%lf %n", &offset[0], &offset[1], &offset[2], &n) != 3 || n != offset_str.size()) {
|
2026-03-31 15:32:36 +02:00
|
|
|
cerr_ << "[error] Invalid use of --model-offset\n";
|
|
|
|
|
ifcopenshell::path::delete_file(ifcopenshell::path::to_utf8(output_temp_filename));
|
2025-03-19 11:42:11 +01:00
|
|
|
print_options(serializer_options);
|
|
|
|
|
return EXIT_FAILURE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::stringstream msg;
|
|
|
|
|
msg << std::setprecision(std::numeric_limits<double>::max_digits10) << "Using model offset (" << offset[0] << "," << offset[1] << "," << offset[2] << ")";
|
2026-03-31 15:32:36 +02:00
|
|
|
logger::notice(msg.str());
|
2025-03-19 11:42:11 +01:00
|
|
|
|
|
|
|
|
geometry_settings.get<ifcopenshell::geometry::settings::ModelOffset>().value = offset;
|
|
|
|
|
}
|
2020-01-31 15:55:04 +01:00
|
|
|
|
2024-11-29 13:03:56 +01:00
|
|
|
if (is_tesselated && (center_model || center_model_geometry)) {
|
|
|
|
|
std::vector<double> offset(3);
|
2018-08-20 14:36:53 +03:00
|
|
|
|
2025-09-26 14:24:49 +02:00
|
|
|
IfcGeom::Iterator tmp_context_iterator(ifcopenshell::geometry::kernels::construct(ifc_file, geometry_kernel, geometry_settings), geometry_settings, ifc_file, filter_funcs, num_threads);
|
2020-02-12 11:38:08 +01:00
|
|
|
|
2024-11-29 13:03:56 +01:00
|
|
|
time_t start, end;
|
|
|
|
|
time(&start);
|
2026-03-31 15:32:36 +02:00
|
|
|
if (!quiet) logger::status("Computing bounds...");
|
2024-11-29 13:03:56 +01:00
|
|
|
|
|
|
|
|
if (center_model_geometry) {
|
|
|
|
|
if (!tmp_context_iterator.initialize()) {
|
|
|
|
|
/// @todo It would be nice to know and print separate error prints for a case where we found no entities
|
|
|
|
|
/// and for a case we found no entities that satisfy our filtering criteria.
|
2026-03-31 15:32:36 +02:00
|
|
|
logger::notice("No geometrical elements found or none successfully converted");
|
2024-11-29 13:03:56 +01:00
|
|
|
serializer.reset();
|
2026-03-31 15:32:36 +02:00
|
|
|
ifcopenshell::path::delete_file(ifcopenshell::path::to_utf8(output_temp_filename));
|
2024-11-29 13:03:56 +01:00
|
|
|
write_log(!quiet);
|
|
|
|
|
return EXIT_FAILURE;
|
2020-02-12 11:38:08 +01:00
|
|
|
}
|
2024-11-29 13:03:56 +01:00
|
|
|
}
|
2020-02-12 11:38:08 +01:00
|
|
|
|
2024-11-29 13:03:56 +01:00
|
|
|
tmp_context_iterator.compute_bounds(center_model_geometry);
|
2020-01-31 15:55:04 +01:00
|
|
|
|
2024-11-29 13:03:56 +01:00
|
|
|
time(&end);
|
2026-03-31 15:32:36 +02:00
|
|
|
if (!quiet) logger::status("Done ! Bounds computed in " + format_duration(start, end));
|
2018-08-20 14:36:53 +03:00
|
|
|
|
2024-11-29 13:03:56 +01:00
|
|
|
auto center = (tmp_context_iterator.bounds_min().ccomponents() + tmp_context_iterator.bounds_max().ccomponents()) * 0.5;
|
|
|
|
|
offset[0] = -center(0);
|
|
|
|
|
offset[1] = -center(1);
|
|
|
|
|
offset[2] = -center(2);
|
2017-01-12 15:47:44 +02:00
|
|
|
|
|
|
|
|
std::stringstream msg;
|
2025-03-19 11:42:11 +01:00
|
|
|
msg << std::setprecision (std::numeric_limits<double>::max_digits10) << "Using model offset (" << offset[0] << "," << offset[1] << "," << offset[2] << ")";
|
2026-03-31 15:32:36 +02:00
|
|
|
logger::notice(msg.str());
|
2024-11-29 13:03:56 +01:00
|
|
|
|
|
|
|
|
geometry_settings.get<ifcopenshell::geometry::settings::ModelOffset>().value = offset;
|
2017-01-12 15:47:44 +02:00
|
|
|
}
|
2024-04-18 21:04:02 +02:00
|
|
|
|
|
|
|
|
// backwards compatibility
|
|
|
|
|
if (vmap.count("plan") && vmap.count("model")) {
|
|
|
|
|
geometry_settings.get<ifcopenshell::geometry::settings::OutputDimensionality>().value = ifcopenshell::geometry::settings::CURVES_SURFACES_AND_SOLIDS;
|
|
|
|
|
} else if (vmap.count("model")) {
|
|
|
|
|
geometry_settings.get<ifcopenshell::geometry::settings::OutputDimensionality>().value = ifcopenshell::geometry::settings::SURFACES_AND_SOLIDS;
|
|
|
|
|
} else if (vmap.count("plan")) {
|
|
|
|
|
geometry_settings.get<ifcopenshell::geometry::settings::OutputDimensionality>().value = ifcopenshell::geometry::settings::CURVES;
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-13 22:02:52 +02:00
|
|
|
std::unique_ptr<IfcGeom::Iterator> context_iterator;
|
|
|
|
|
if (!elems_from_adaptor) {
|
2025-09-26 14:24:49 +02:00
|
|
|
context_iterator.reset(new IfcGeom::Iterator(ifcopenshell::geometry::kernels::construct(ifc_file, geometry_kernel, geometry_settings), geometry_settings, ifc_file, filter_funcs, num_threads));
|
2023-09-13 22:02:52 +02:00
|
|
|
}
|
2021-10-08 15:38:46 +02:00
|
|
|
|
2026-04-19 12:32:10 +02:00
|
|
|
#ifdef WITH_HDF5
|
2026-04-18 15:46:21 +02:00
|
|
|
boost::shared_ptr<GeometrySerializer> cache;
|
2026-04-19 12:32:10 +02:00
|
|
|
const bool use_cache = supports_geometry_cache && context_iterator && (vmap.count("cache-file") || vmap.count("cache"));
|
2026-04-18 15:46:21 +02:00
|
|
|
if (use_cache) {
|
2026-04-19 12:32:10 +02:00
|
|
|
const path_t CACHE = ifcopenshell::path::from_utf8(".cache");
|
|
|
|
|
const path_t HDF = ifcopenshell::path::from_utf8(".h5");
|
2021-10-08 15:38:46 +02:00
|
|
|
if (!vmap.count("cache-file")) {
|
|
|
|
|
cache_file = input_filename + CACHE + HDF;
|
|
|
|
|
}
|
2026-04-18 15:46:21 +02:00
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
ifcopenshell::serializers::geometry_serializer_context cache_context{
|
|
|
|
|
ifcopenshell::path::to_utf8(cache_file),
|
|
|
|
|
ifcopenshell::path::to_utf8(cache_file),
|
|
|
|
|
geometry_settings,
|
|
|
|
|
serializer_settings
|
|
|
|
|
};
|
|
|
|
|
cache = geometry_serializer_registry.create(".h5", cache_context);
|
|
|
|
|
} catch (const std::exception& e) {
|
|
|
|
|
cerr_ << "[error] Unable to initialize geometry cache: " << e.what() << std::endl;
|
|
|
|
|
serializer.reset();
|
|
|
|
|
ifcopenshell::path::delete_file(ifcopenshell::path::to_utf8(output_temp_filename));
|
|
|
|
|
write_log(!quiet);
|
|
|
|
|
return EXIT_FAILURE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!cache || !cache->ready()) {
|
|
|
|
|
cerr_ << "[error] Unable to initialize geometry cache serializer\n";
|
|
|
|
|
serializer.reset();
|
|
|
|
|
ifcopenshell::path::delete_file(ifcopenshell::path::to_utf8(output_temp_filename));
|
|
|
|
|
write_log(!quiet);
|
|
|
|
|
return EXIT_FAILURE;
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-13 22:02:52 +02:00
|
|
|
context_iterator->set_cache(cache.get());
|
2021-10-08 15:38:46 +02:00
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
2026-03-31 15:32:36 +02:00
|
|
|
logger::message(logger::LOG_PERF, "file geometry conversion");
|
2022-01-09 14:30:56 +01:00
|
|
|
|
2023-09-13 22:02:52 +02:00
|
|
|
if (context_iterator && !context_iterator->initialize()) {
|
2020-01-31 15:55:04 +01:00
|
|
|
/// @todo It would be nice to know and print separate error prints for a case where we found no entities
|
|
|
|
|
/// and for a case we found no entities that satisfy our filtering criteria.
|
2026-03-31 15:32:36 +02:00
|
|
|
logger::notice("No geometrical elements found or none successfully converted");
|
2020-01-31 15:55:04 +01:00
|
|
|
serializer.reset();
|
2026-03-31 15:32:36 +02:00
|
|
|
ifcopenshell::path::delete_file(ifcopenshell::path::to_utf8(output_temp_filename));
|
2020-01-31 15:55:04 +01:00
|
|
|
write_log(!quiet);
|
|
|
|
|
return EXIT_FAILURE;
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-13 22:02:52 +02:00
|
|
|
serializer->setFile(ifc_file);
|
2020-07-11 13:39:09 +02:00
|
|
|
|
2023-11-15 10:32:20 +01:00
|
|
|
if (context_iterator && geometry_settings.get<ifcopenshell::geometry::settings::ConvertBackUnits>().get()) {
|
2023-09-13 22:02:52 +02:00
|
|
|
serializer->setUnitNameAndMagnitude(context_iterator->unit_name(), static_cast<float>(context_iterator->unit_magnitude()));
|
2020-01-31 15:55:04 +01:00
|
|
|
} else {
|
|
|
|
|
serializer->setUnitNameAndMagnitude("METER", 1.0f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
serializer->writeHeader();
|
|
|
|
|
|
|
|
|
|
int old_progress = quiet ? 0 : -1;
|
|
|
|
|
|
2018-05-12 20:12:25 +02:00
|
|
|
if (!quiet) {
|
2026-03-31 15:32:36 +02:00
|
|
|
logger::status("Creating geometry...");
|
2018-05-12 20:12:25 +02:00
|
|
|
}
|
2013-03-24 10:48:39 +00:00
|
|
|
|
2015-02-06 13:07:50 +00:00
|
|
|
// The functions IfcGeom::Iterator::get() and IfcGeom::Iterator::next()
|
|
|
|
|
// wrap an iterator of all geometrical products in the Ifc file.
|
|
|
|
|
// IfcGeom::Iterator::get() returns an IfcGeom::TriangulationElement or
|
2023-03-21 20:15:01 +01:00
|
|
|
// -BRepElement pointer, based on current settings. (see Iterator.h
|
2015-02-06 13:07:50 +00:00
|
|
|
// for definition) IfcGeom::Iterator::next() is used to poll whether more
|
|
|
|
|
// geometrical entities are available. None of these functions throw
|
|
|
|
|
// exceptions, neither for parsing errors or geometrical errors. Upon
|
|
|
|
|
// calling next() the entity to be returned has already been processed, a
|
2017-02-03 19:15:37 +02:00
|
|
|
// non-null return value guarantees that a successfully processed product is
|
2015-02-06 13:07:50 +00:00
|
|
|
// available.
|
2016-03-27 21:17:51 +02:00
|
|
|
size_t num_created = 0;
|
2023-09-13 22:02:52 +02:00
|
|
|
|
|
|
|
|
std::list<IfcGeom::Element*>::const_iterator elems_from_adaptor_it;
|
|
|
|
|
if (elems_from_adaptor) {
|
|
|
|
|
elems_from_adaptor_it = elems_from_adaptor->begin();
|
|
|
|
|
}
|
2016-03-27 21:17:51 +02:00
|
|
|
|
2023-09-13 22:02:52 +02:00
|
|
|
while (true) {
|
2021-02-14 12:23:43 +01:00
|
|
|
|
2023-09-13 22:02:52 +02:00
|
|
|
IfcGeom::Element* geom_object = elems_from_adaptor ? *elems_from_adaptor_it : context_iterator->get();
|
2017-04-20 15:06:49 +02:00
|
|
|
|
2017-04-21 15:49:34 +02:00
|
|
|
if (is_tesselated)
|
2017-04-11 17:26:30 +02:00
|
|
|
{
|
2021-07-11 15:00:59 +02:00
|
|
|
serializer->write(static_cast<const IfcGeom::TriangulationElement*>(geom_object));
|
2017-04-11 17:26:30 +02:00
|
|
|
}
|
2017-04-12 14:38:54 +02:00
|
|
|
else
|
2017-04-11 17:26:30 +02:00
|
|
|
{
|
2021-07-11 15:00:59 +02:00
|
|
|
serializer->write(static_cast<const IfcGeom::BRepElement*>(geom_object));
|
2013-03-24 10:48:39 +00:00
|
|
|
}
|
2017-05-23 20:06:35 +03:00
|
|
|
|
|
|
|
|
if (!no_progress) {
|
2023-09-13 22:02:52 +02:00
|
|
|
int progress = context_iterator ? context_iterator->progress() : (int)std::distance(elems_from_adaptor->cbegin(), elems_from_adaptor_it) * 100 / elems_from_adaptor->size();
|
2018-05-12 20:12:25 +02:00
|
|
|
if (quiet) {
|
|
|
|
|
for (; old_progress < progress; ++old_progress) {
|
2019-07-09 16:00:58 +02:00
|
|
|
cout_ << ".";
|
2018-08-13 12:54:52 +02:00
|
|
|
if (stderr_progress)
|
2019-07-09 16:00:58 +02:00
|
|
|
cerr_ << ".";
|
2018-05-12 20:12:25 +02:00
|
|
|
}
|
2019-07-09 16:00:58 +02:00
|
|
|
cout_ << std::flush;
|
2018-08-13 12:54:52 +02:00
|
|
|
if (stderr_progress)
|
2019-07-09 16:00:58 +02:00
|
|
|
cerr_ << std::flush;
|
2020-08-16 13:37:58 +02:00
|
|
|
} else if (vcounter.count == 2) {
|
2026-03-31 15:32:36 +02:00
|
|
|
logger::message(logger::LOG_DEBUG, "Progress " + boost::lexical_cast<std::string>(progress));
|
2018-05-12 20:12:25 +02:00
|
|
|
} else {
|
2023-09-13 22:02:52 +02:00
|
|
|
progress = progress / 2;
|
2026-03-31 15:32:36 +02:00
|
|
|
if (old_progress != progress) logger::progress_bar(progress);
|
2018-05-12 20:12:25 +02:00
|
|
|
old_progress = progress;
|
|
|
|
|
}
|
2017-05-23 20:06:35 +03:00
|
|
|
}
|
2013-03-24 10:48:39 +00:00
|
|
|
|
2023-09-13 22:02:52 +02:00
|
|
|
++num_created;
|
|
|
|
|
if (context_iterator) {
|
|
|
|
|
if (!context_iterator->next()) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
++elems_from_adaptor_it;
|
|
|
|
|
if (elems_from_adaptor_it == elems_from_adaptor->end()) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-05-12 20:12:25 +02:00
|
|
|
if (!no_progress && quiet) {
|
|
|
|
|
for (; old_progress < 100; ++old_progress) {
|
2019-07-09 16:00:58 +02:00
|
|
|
cout_ << ".";
|
2018-08-13 12:54:52 +02:00
|
|
|
if (stderr_progress)
|
2019-07-09 16:00:58 +02:00
|
|
|
cerr_ << ".";
|
|
|
|
|
}
|
|
|
|
|
cout_ << std::flush;
|
|
|
|
|
if (stderr_progress) {
|
|
|
|
|
cerr_ << std::flush;
|
2018-05-12 20:12:25 +02:00
|
|
|
}
|
|
|
|
|
} else {
|
2019-07-09 16:00:58 +02:00
|
|
|
const std::string task = ((num_threads == 1) ? "creating" : "writing");
|
2026-03-31 15:32:36 +02:00
|
|
|
logger::status("\rDone " + task + " geometry (" + boost::lexical_cast<std::string>(num_created) +
|
2018-05-12 20:12:25 +02:00
|
|
|
" objects) ");
|
|
|
|
|
}
|
2016-03-07 22:37:36 +02:00
|
|
|
|
2016-03-27 21:17:51 +02:00
|
|
|
serializer->finalize();
|
2018-10-08 16:36:48 +03:00
|
|
|
// Make sure the dtor is explicitly run here (e.g. output files are closed before renaming them).
|
|
|
|
|
serializer.reset();
|
2013-03-24 10:48:39 +00:00
|
|
|
|
2026-03-31 15:32:36 +02:00
|
|
|
logger::message(logger::LOG_PERF, "done file geometry conversion");
|
2022-01-09 15:15:50 +01:00
|
|
|
|
2023-05-17 14:24:09 +02:00
|
|
|
bool successful;
|
2026-04-18 15:46:21 +02:00
|
|
|
if (geometry_serializer_info->writes_final_output) {
|
2023-05-17 14:24:09 +02:00
|
|
|
// No need to rename the file
|
|
|
|
|
successful = true;
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
// Renaming might fail (e.g. maybe the existing file was open in a viewer application)
|
|
|
|
|
// Do not remove the temp file as user can salvage the conversion result from it.
|
2026-03-31 15:32:36 +02:00
|
|
|
successful = ifcopenshell::path::rename_file(ifcopenshell::path::to_utf8(output_temp_filename), ifcopenshell::path::to_utf8(output_filename));
|
2023-05-17 14:24:09 +02:00
|
|
|
}
|
2023-05-07 22:46:05 +02:00
|
|
|
|
2016-03-28 11:47:02 +03:00
|
|
|
if (!successful) {
|
2019-02-08 15:18:55 +01:00
|
|
|
cerr_ << "Unable to write output file '" << output_filename << "', see '" <<
|
|
|
|
|
output_temp_filename << "' for the conversion result.";
|
2016-03-20 12:48:20 +02:00
|
|
|
}
|
2013-03-24 10:48:39 +00:00
|
|
|
|
2026-03-31 15:32:36 +02:00
|
|
|
if (geometry_settings.get<ifcopenshell::geometry::settings::ValidateQuantities>().get() && logger::max_severity() >= logger::LOG_ERROR) {
|
|
|
|
|
logger::error("Errors encountered during processing.");
|
2018-12-12 12:33:09 +01:00
|
|
|
successful = false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-31 15:32:36 +02:00
|
|
|
if (logger::verbosity() == logger::LOG_PERF) {
|
|
|
|
|
logger::print_performance_stats();
|
2022-01-09 15:15:50 +01:00
|
|
|
}
|
|
|
|
|
|
2018-05-12 20:12:25 +02:00
|
|
|
write_log(!quiet);
|
2013-03-24 10:48:39 +00:00
|
|
|
|
|
|
|
|
time(&end);
|
2015-02-06 13:07:50 +00:00
|
|
|
|
2018-10-01 13:19:31 +03:00
|
|
|
if (!quiet) {
|
2026-03-31 15:32:36 +02:00
|
|
|
logger::status("\nConversion took " + format_duration(start, end));
|
2018-10-01 13:19:31 +03:00
|
|
|
}
|
2015-02-06 13:07:50 +00:00
|
|
|
|
2017-02-22 16:34:09 +02:00
|
|
|
return successful ? EXIT_SUCCESS : EXIT_FAILURE;
|
2013-12-31 14:10:38 +00:00
|
|
|
}
|
|
|
|
|
|
2018-10-01 13:19:31 +03:00
|
|
|
std::string format_duration(time_t start, time_t end)
|
|
|
|
|
{
|
|
|
|
|
int seconds = (int)difftime(end, start);
|
|
|
|
|
std::stringstream ss;
|
|
|
|
|
int minutes = seconds / 60;
|
|
|
|
|
seconds = seconds % 60;
|
|
|
|
|
if (minutes > 0) {
|
|
|
|
|
ss << minutes << " minute";
|
|
|
|
|
if (minutes == 0 || minutes > 1) {
|
|
|
|
|
ss << "s";
|
|
|
|
|
}
|
|
|
|
|
ss << " ";
|
|
|
|
|
}
|
|
|
|
|
ss << seconds << " second";
|
|
|
|
|
if (seconds == 0 || seconds > 1) {
|
|
|
|
|
ss << "s";
|
|
|
|
|
}
|
|
|
|
|
return ss.str();
|
|
|
|
|
}
|
|
|
|
|
|
2018-05-12 20:12:25 +02:00
|
|
|
void write_log(bool header) {
|
2019-02-08 15:18:55 +01:00
|
|
|
path_t log = log_stream.str();
|
2013-12-31 14:10:38 +00:00
|
|
|
if (!log.empty()) {
|
2019-02-08 15:18:55 +01:00
|
|
|
if (header) {
|
|
|
|
|
cout_ << "\nLog:\n";
|
|
|
|
|
}
|
|
|
|
|
cout_ << log << std::endl;
|
2013-12-31 14:10:38 +00:00
|
|
|
}
|
2016-03-07 22:37:36 +02:00
|
|
|
}
|
2016-12-16 13:51:07 +02:00
|
|
|
|
2018-08-29 12:54:25 +02:00
|
|
|
#include <boost/algorithm/string/predicate.hpp>
|
|
|
|
|
|
2026-03-31 15:32:36 +02:00
|
|
|
bool init_input_file(const std::string& filename, ifcopenshell::file*& ifc_file, bool no_progress, bool mmap, bool bypass_properties) {
|
2018-10-01 13:19:31 +03:00
|
|
|
time_t start, end;
|
2017-12-20 12:40:15 +01:00
|
|
|
|
2026-03-31 15:32:36 +02:00
|
|
|
// Prevent file::Init() prints by setting output to null temporarily
|
|
|
|
|
if (no_progress) { logger::set_output(NULL, &log_stream); }
|
2017-06-15 22:07:56 +03:00
|
|
|
|
2018-10-01 13:19:31 +03:00
|
|
|
time(&start);
|
2018-08-29 12:54:25 +02:00
|
|
|
|
2025-10-24 12:06:59 +02:00
|
|
|
bool requires_init = false;
|
|
|
|
|
|
|
|
|
|
{
|
2026-03-31 15:32:36 +02:00
|
|
|
ifc_file = new ifcopenshell::file(ifcopenshell::uninitialized_tag{});
|
2025-10-24 12:06:59 +02:00
|
|
|
requires_init = true;
|
|
|
|
|
}
|
2021-09-04 15:38:07 +02:00
|
|
|
|
2025-11-14 19:12:39 +01:00
|
|
|
if (bypass_properties) {
|
|
|
|
|
ifc_file->bypass_type("IfcRelDefinesByProperties");
|
|
|
|
|
ifc_file->bypass_type("IfcPropertySetDefinition");
|
|
|
|
|
ifc_file->bypass_type("IfcProperty");
|
|
|
|
|
ifc_file->bypass_type("IfcMaterialProperties");
|
|
|
|
|
ifc_file->bypass_type("IfcProfileProperties");
|
|
|
|
|
ifc_file->bypass_type("IfcPhysicalQuantity");
|
|
|
|
|
}
|
2025-10-24 12:06:59 +02:00
|
|
|
|
2021-09-04 15:38:07 +02:00
|
|
|
#ifdef USE_MMAP
|
2025-10-24 12:06:59 +02:00
|
|
|
if (mmap) {
|
|
|
|
|
ifc_file->initialize(filename, mmap);
|
|
|
|
|
requires_init = false;
|
|
|
|
|
}
|
2021-09-04 15:38:07 +02:00
|
|
|
#else
|
2025-10-24 12:06:59 +02:00
|
|
|
(void)mmap;
|
2017-07-23 14:52:04 +02:00
|
|
|
#endif
|
2025-10-24 12:06:59 +02:00
|
|
|
if (requires_init) {
|
|
|
|
|
ifc_file->initialize(filename);
|
|
|
|
|
}
|
2021-09-04 15:38:07 +02:00
|
|
|
|
|
|
|
|
if (!ifc_file || !ifc_file->good()) {
|
2026-03-31 15:32:36 +02:00
|
|
|
logger::error("Unable to parse input file '" + filename + "'");
|
2017-06-15 22:07:56 +03:00
|
|
|
return false;
|
|
|
|
|
}
|
2018-10-01 13:19:31 +03:00
|
|
|
time(&end);
|
2017-06-15 22:07:56 +03:00
|
|
|
|
2026-03-31 15:32:36 +02:00
|
|
|
if (no_progress) { logger::set_output(&cout_, &log_stream); }
|
|
|
|
|
else { logger::status("Parsing input file took " + format_duration(start, end)); }
|
2017-06-15 22:07:56 +03:00
|
|
|
|
|
|
|
|
return true;
|
2017-12-20 12:40:15 +01:00
|
|
|
|
2017-06-15 22:07:56 +03:00
|
|
|
}
|
|
|
|
|
|
2017-05-19 18:10:20 +03:00
|
|
|
bool append_filter(const std::string& type, const std::vector<std::string>& values, geom_filter& filter)
|
|
|
|
|
{
|
|
|
|
|
geom_filter temp;
|
|
|
|
|
parse_filter(temp, values);
|
|
|
|
|
// Merge values only if type and arg match.
|
|
|
|
|
if ((filter.type != geom_filter::UNUSED && filter.type != temp.type) || (!filter.arg.empty() && filter.arg != temp.arg)) {
|
2026-03-31 15:32:36 +02:00
|
|
|
cerr_ << "[error] Multiple '" << type.c_str() << "' filters specified with different criteria\n";
|
2017-05-19 18:10:20 +03:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
filter.type = temp.type;
|
|
|
|
|
filter.values.insert(temp.values.begin(), temp.values.end());
|
|
|
|
|
filter.arg = temp.arg;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
size_t read_filters_from_file(
|
|
|
|
|
const std::string& filename,
|
|
|
|
|
inclusion_filter& include_filter,
|
|
|
|
|
inclusion_traverse_filter& include_traverse_filter,
|
|
|
|
|
exclusion_filter& exclude_filter,
|
|
|
|
|
exclusion_traverse_filter& exclude_traverse_filter)
|
|
|
|
|
{
|
2026-03-31 15:32:36 +02:00
|
|
|
std::ifstream filter_file(ifcopenshell::path::from_utf8(filename).c_str());
|
2019-02-08 15:18:55 +01:00
|
|
|
|
2017-05-19 18:10:20 +03:00
|
|
|
if (!filter_file.is_open()) {
|
2026-03-31 15:32:36 +02:00
|
|
|
cerr_ << "[error] Unable to open filter file '" << ifcopenshell::path::from_utf8(filename) << "' or the file does not exist.\n";
|
2017-05-19 18:10:20 +03:00
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
size_t line_number = 1, num_filters = 0;
|
|
|
|
|
for (std::string line; std::getline(filter_file, line); ++line_number) {
|
|
|
|
|
boost::trim(line);
|
|
|
|
|
if (line.empty()) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::vector<std::string> values;
|
|
|
|
|
boost::split(values, line, boost::is_any_of("\t "), boost::token_compress_on);
|
|
|
|
|
if (values.empty()) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string type = values.front();
|
|
|
|
|
values.erase(values.begin());
|
|
|
|
|
// Support both "--include=arg GlobalId 1VQ5n5$RrEbPk8le4ZCI81" and "include arg GlobalId 1VQ5n5$RrEbPk8le4ZCI81"
|
|
|
|
|
// and tolerate extraneous whitespace.
|
|
|
|
|
boost::trim_left_if(type, boost::is_any_of("-"));
|
|
|
|
|
size_t equal_pos = type.find('=');
|
|
|
|
|
if (equal_pos != std::string::npos) {
|
|
|
|
|
std::string value = type.substr(equal_pos + 1);
|
|
|
|
|
type = type.substr(0, equal_pos);
|
|
|
|
|
values.insert(values.begin(), value);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
if (type == "include") { if (append_filter("include", values, include_filter)) { ++num_filters; } }
|
|
|
|
|
else if (type == "include+") { if (append_filter("include+", values, include_traverse_filter)) { ++num_filters; } }
|
|
|
|
|
else if (type == "exclude") { if (append_filter("exclude", values, exclude_filter)) { ++num_filters; } }
|
|
|
|
|
else if (type == "exclude+") { if (append_filter("exclude+", values, exclude_traverse_filter)) { ++num_filters; } }
|
|
|
|
|
else {
|
2026-03-31 15:32:36 +02:00
|
|
|
cerr_ << "[error] Invalid filtering type at line " << boost::lexical_cast<path_t>(line_number) << "\n";
|
2017-05-19 18:10:20 +03:00
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
} catch(...) {
|
2026-03-31 15:32:36 +02:00
|
|
|
cerr_ << "[error] Unable to parse filter at line " << boost::lexical_cast<path_t>(line_number) << ".\n";
|
2017-05-19 18:10:20 +03:00
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return num_filters;
|
|
|
|
|
}
|
|
|
|
|
|
2017-02-16 14:28:18 +02:00
|
|
|
void parse_filter(geom_filter &filter, const std::vector<std::string>& values)
|
2016-12-16 13:51:07 +02:00
|
|
|
{
|
|
|
|
|
if (values.size() == 0) {
|
|
|
|
|
throw po::validation_error(po::validation_error::at_least_one_value_required);
|
|
|
|
|
}
|
|
|
|
|
std::string type = *values.begin();
|
|
|
|
|
if (type == "entities") {
|
2017-02-02 14:58:58 +02:00
|
|
|
filter.type = geom_filter::ENTITY_TYPE;
|
2017-02-03 11:02:24 +02:00
|
|
|
} else if (type == "layers") {
|
|
|
|
|
filter.type = geom_filter::LAYER_NAME;
|
2019-10-13 07:45:19 +02:00
|
|
|
} else if (type == "attribute" || type == "arg") {
|
2017-02-16 14:28:18 +02:00
|
|
|
filter.type = geom_filter::ENTITY_ARG;
|
|
|
|
|
filter.arg = *(values.begin() + 1);
|
2016-12-16 13:51:07 +02:00
|
|
|
} else {
|
|
|
|
|
throw po::validation_error(po::validation_error::invalid_option_value);
|
|
|
|
|
}
|
2017-02-16 14:28:18 +02:00
|
|
|
filter.values.insert(values.begin() + (filter.type == geom_filter::ENTITY_ARG ? 2 : 1), values.end());
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-16 13:37:58 +02:00
|
|
|
void validate(boost::any& v, const std::vector<std::string>& values, verbosity_counter*, long) {
|
|
|
|
|
if (v.empty()) v = verbosity_counter{ 1 };
|
|
|
|
|
else ++boost::any_cast<verbosity_counter&>(v).count;
|
|
|
|
|
}
|
|
|
|
|
|
2017-02-16 14:28:18 +02:00
|
|
|
void validate(boost::any& v, const std::vector<std::string>& values, inclusion_filter*, int)
|
|
|
|
|
{
|
2017-05-19 18:10:20 +03:00
|
|
|
/// @todo For now only single --include, --include+, --exclude, or --exclude+ supported. Support having multiple.
|
2017-02-16 14:28:18 +02:00
|
|
|
po::validators::check_first_occurrence(v);
|
|
|
|
|
inclusion_filter filter;
|
|
|
|
|
parse_filter(filter, values);
|
2016-12-16 13:51:07 +02:00
|
|
|
v = filter;
|
|
|
|
|
}
|
|
|
|
|
|
2017-03-01 23:51:57 +02:00
|
|
|
void validate(boost::any& v, const std::vector<std::string>& values, inclusion_traverse_filter*, int)
|
|
|
|
|
{
|
|
|
|
|
po::validators::check_first_occurrence(v);
|
|
|
|
|
inclusion_traverse_filter filter;
|
|
|
|
|
parse_filter(filter, values);
|
|
|
|
|
v = filter;
|
|
|
|
|
}
|
|
|
|
|
|
2016-12-16 13:51:07 +02:00
|
|
|
void validate(boost::any& v, const std::vector<std::string>& values, exclusion_filter*, int)
|
|
|
|
|
{
|
|
|
|
|
po::validators::check_first_occurrence(v);
|
|
|
|
|
exclusion_filter filter;
|
2017-02-16 14:28:18 +02:00
|
|
|
parse_filter(filter, values);
|
2016-12-16 13:51:07 +02:00
|
|
|
v = filter;
|
|
|
|
|
}
|
2017-02-20 19:41:52 +02:00
|
|
|
|
2017-03-01 23:51:57 +02:00
|
|
|
void validate(boost::any& v, const std::vector<std::string>& values, exclusion_traverse_filter*, int)
|
|
|
|
|
{
|
|
|
|
|
po::validators::check_first_occurrence(v);
|
|
|
|
|
exclusion_traverse_filter filter;
|
|
|
|
|
parse_filter(filter, values);
|
|
|
|
|
v = filter;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2017-02-21 16:04:05 +02:00
|
|
|
/// @todo Clean up this filter initialization code further.
|
2017-03-01 23:51:57 +02:00
|
|
|
/// @return References to the used filter functors, if none an error occurred.
|
2026-01-04 10:40:02 +01:00
|
|
|
std::vector<ifcopenshell::geometry::filter_t> setup_filters(const std::vector<geom_filter>& filters, const std::string& output_extension)
|
2017-02-20 19:41:52 +02:00
|
|
|
{
|
2026-01-04 10:40:02 +01:00
|
|
|
std::vector<ifcopenshell::geometry::filter_t> filter_funcs;
|
2018-09-07 14:08:56 +02:00
|
|
|
for(auto& f: filters) {
|
2017-03-01 23:51:57 +02:00
|
|
|
if (f.type == geom_filter::ENTITY_TYPE) {
|
|
|
|
|
entity_filter.include = f.include;
|
|
|
|
|
entity_filter.traverse = f.traverse;
|
2018-09-07 14:08:56 +02:00
|
|
|
entity_filter.entity_names = f.values;
|
2017-03-01 23:51:57 +02:00
|
|
|
} else if (f.type == geom_filter::LAYER_NAME) {
|
|
|
|
|
layer_filter.include = f.include;
|
|
|
|
|
layer_filter.traverse = f.traverse;
|
|
|
|
|
layer_filter.populate(f.values);
|
|
|
|
|
} else if (f.type == geom_filter::ENTITY_ARG) {
|
2018-09-07 14:08:56 +02:00
|
|
|
attribute_filter.include = f.include;
|
2024-07-21 02:41:43 +02:00
|
|
|
attribute_filter.traverse = attribute_filter.traverse_openings = f.traverse;
|
2018-09-07 14:08:56 +02:00
|
|
|
attribute_filter.attribute_name = f.arg;
|
|
|
|
|
attribute_filter.populate(f.values);
|
2017-02-20 19:41:52 +02:00
|
|
|
}
|
2017-03-01 23:51:57 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If no entity names are specified these are the defaults to skip from output
|
2018-09-07 14:08:56 +02:00
|
|
|
if (entity_filter.entity_names.empty()) {
|
|
|
|
|
std::set<std::string> entities;
|
|
|
|
|
entities.insert("IfcSpace");
|
|
|
|
|
if (output_extension == ".svg") {
|
|
|
|
|
entity_filter.include = true;
|
|
|
|
|
} else {
|
|
|
|
|
entities.insert("IfcOpeningElement");
|
2017-02-20 19:41:52 +02:00
|
|
|
}
|
2018-09-07 14:08:56 +02:00
|
|
|
entity_filter.entity_names = entities;
|
2017-02-20 19:41:52 +02:00
|
|
|
}
|
|
|
|
|
|
2017-03-01 23:51:57 +02:00
|
|
|
if (!layer_filter.values.empty()) { filter_funcs.push_back(boost::ref(layer_filter)); }
|
2018-09-07 14:08:56 +02:00
|
|
|
if (!entity_filter.entity_names.empty()) { filter_funcs.push_back(boost::ref(entity_filter)); }
|
|
|
|
|
if (!attribute_filter.values.empty()) { filter_funcs.push_back(boost::ref(attribute_filter)); }
|
2017-02-20 19:41:52 +02:00
|
|
|
|
2017-03-01 23:51:57 +02:00
|
|
|
return filter_funcs;
|
2017-02-20 19:41:52 +02:00
|
|
|
}
|
2018-11-02 16:10:06 +01:00
|
|
|
|
|
|
|
|
namespace latebound_access {
|
|
|
|
|
|
|
|
|
|
template <typename T>
|
2026-01-04 10:40:02 +01:00
|
|
|
void set(express::Base inst, const std::string& attr, T t);
|
2018-11-02 16:10:06 +01:00
|
|
|
|
|
|
|
|
template <typename T>
|
2026-03-31 15:32:36 +02:00
|
|
|
void set_enumeration(express::Base, const std::string&, const ifcopenshell::enumeration_type*, T) {}
|
2018-11-02 16:10:06 +01:00
|
|
|
|
|
|
|
|
template <>
|
2026-03-31 15:32:36 +02:00
|
|
|
void set_enumeration(express::Base inst, const std::string& attr, const ifcopenshell::enumeration_type* enum_type, std::string t) {
|
2018-11-02 16:10:06 +01:00
|
|
|
std::vector<std::string>::const_iterator it = std::find(
|
|
|
|
|
enum_type->enumeration_items().begin(),
|
|
|
|
|
enum_type->enumeration_items().end(),
|
|
|
|
|
t);
|
|
|
|
|
|
2026-03-31 15:32:36 +02:00
|
|
|
return set(inst, attr, enumeration_reference(enum_type, it - enum_type->enumeration_items().begin()));
|
2018-11-02 16:10:06 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template <typename T>
|
2026-01-04 10:40:02 +01:00
|
|
|
void set(express::Base inst, const std::string& attr, T t) {
|
|
|
|
|
auto decl = inst.declaration().as_entity();
|
2018-11-02 16:10:06 +01:00
|
|
|
auto i = decl->attribute_index(attr);
|
|
|
|
|
|
|
|
|
|
auto attr_type = decl->attribute_by_index(i)->type_of_attribute();
|
2026-03-31 15:32:36 +02:00
|
|
|
if (attr_type->as_named_type() && attr_type->as_named_type()->declared_type()->as_enumeration_type() && !std::is_same<T, enumeration_reference>::value) {
|
2018-11-02 16:10:06 +01:00
|
|
|
set_enumeration(inst, attr, attr_type->as_named_type()->declared_type()->as_enumeration_type(), t);
|
2019-06-18 14:28:57 +02:00
|
|
|
} else {
|
2026-01-04 10:40:02 +01:00
|
|
|
inst.set_attribute_value(i, t);
|
2018-11-02 16:10:06 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-31 15:32:36 +02:00
|
|
|
express::Base create(ifcopenshell::file& f, const std::string& entity) {
|
2018-11-02 16:10:06 +01:00
|
|
|
auto decl = f.schema()->declaration_by_name(entity);
|
2026-01-04 10:40:02 +01:00
|
|
|
return f.create(decl);
|
2018-11-02 16:10:06 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-31 15:32:36 +02:00
|
|
|
void fix_quantities(ifcopenshell::file& f, bool no_progress, bool quiet, bool stderr_progress) {
|
2018-11-02 16:10:06 +01:00
|
|
|
{
|
2026-01-04 10:40:02 +01:00
|
|
|
auto delete_reversed = [&f](const std::vector<express::Base>& insts) {
|
2018-11-02 16:10:06 +01:00
|
|
|
// Lists are traversed back to front as the list may be mutated when
|
|
|
|
|
// instances are removed from the grouping by type.
|
2026-01-04 10:40:02 +01:00
|
|
|
for (auto it = insts.end() - 1; it >= insts.begin(); --it) {
|
2026-03-31 15:32:36 +02:00
|
|
|
f.remove_entity(*it);
|
2018-11-02 16:10:06 +01:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Delete quantities
|
|
|
|
|
auto quantities = f.instances_by_type("IfcPhysicalQuantity");
|
2026-01-04 10:40:02 +01:00
|
|
|
for (auto it = quantities.end() - 1; it >= quantities.begin(); --it) {
|
|
|
|
|
if (!it->declaration().is("IfcPhysicalComplexQuantity")) {
|
2026-03-31 15:32:36 +02:00
|
|
|
f.remove_entity(*it);
|
2026-01-04 10:40:02 +01:00
|
|
|
}
|
|
|
|
|
}
|
2018-11-02 16:10:06 +01:00
|
|
|
|
|
|
|
|
// Delete complexes
|
|
|
|
|
delete_reversed(f.instances_by_type("IfcPhysicalComplexQuantity"));
|
|
|
|
|
|
|
|
|
|
auto element_quantities = f.instances_by_type("IfcElementQuantity");
|
|
|
|
|
|
|
|
|
|
// Capture relationship nodes
|
2026-01-04 10:40:02 +01:00
|
|
|
std::vector<express::Entity> relationships;
|
2018-11-02 16:10:06 +01:00
|
|
|
auto IfcRelDefinesByProperties = f.schema()->declaration_by_name("IfcRelDefinesByProperties");
|
2018-12-10 11:33:54 +01:00
|
|
|
|
2026-01-04 10:40:02 +01:00
|
|
|
for (auto& eq : element_quantities) {
|
2026-03-31 15:32:36 +02:00
|
|
|
auto rels = eq.file()->get_inverse(eq.id(), IfcRelDefinesByProperties, -1);
|
2026-01-04 10:40:02 +01:00
|
|
|
for (auto& rel : rels) {
|
|
|
|
|
relationships.push_back(rel);
|
|
|
|
|
}
|
2018-11-02 16:10:06 +01:00
|
|
|
}
|
|
|
|
|
|
2026-01-04 10:40:02 +01:00
|
|
|
// Delete element quantities
|
|
|
|
|
delete_reversed(element_quantities);
|
|
|
|
|
|
2018-11-02 16:10:06 +01:00
|
|
|
|
|
|
|
|
// Delete relationship nodes
|
|
|
|
|
for (auto& rel : relationships) {
|
2026-03-31 15:32:36 +02:00
|
|
|
f.remove_entity(rel);
|
2018-11-02 16:10:06 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-15 10:32:20 +01:00
|
|
|
ifcopenshell::geometry::Settings settings;
|
|
|
|
|
settings.get<ifcopenshell::geometry::settings::UseWorldCoords>().value = false;
|
|
|
|
|
settings.get<ifcopenshell::geometry::settings::WeldVertices>().value = false;
|
|
|
|
|
settings.get<ifcopenshell::geometry::settings::ReorientShells>().value = true;
|
|
|
|
|
settings.get<ifcopenshell::geometry::settings::ConvertBackUnits>().value = true;
|
|
|
|
|
settings.get<ifcopenshell::geometry::settings::IteratorOutput>().value = ifcopenshell::geometry::settings::NATIVE;
|
2018-11-02 16:10:06 +01:00
|
|
|
|
2025-09-26 14:24:49 +02:00
|
|
|
IfcGeom::Iterator context_iterator(ifcopenshell::geometry::kernels::construct(&f, "opencascade", settings), settings, &f, {}, 1);
|
2018-11-02 16:10:06 +01:00
|
|
|
|
|
|
|
|
if (!context_iterator.initialize()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
size_t num_created = 0;
|
|
|
|
|
int old_progress = quiet ? 0 : -1;
|
|
|
|
|
|
|
|
|
|
auto person = latebound_access::create(f, "IfcPerson");
|
|
|
|
|
latebound_access::set(person, "FamilyName", std::string("IfcOpenShell"));
|
|
|
|
|
latebound_access::set(person, "GivenName", std::string("IfcOpenShell"));
|
|
|
|
|
|
|
|
|
|
auto org = latebound_access::create(f, "IfcOrganization");
|
|
|
|
|
latebound_access::set(org, "Name", std::string("IfcOpenShell"));
|
|
|
|
|
|
|
|
|
|
auto pando = latebound_access::create(f, "IfcPersonAndOrganization");
|
|
|
|
|
latebound_access::set(pando, "ThePerson", person);
|
|
|
|
|
latebound_access::set(pando, "TheOrganization", org);
|
|
|
|
|
|
|
|
|
|
auto application = latebound_access::create(f, "IfcApplication");
|
|
|
|
|
latebound_access::set(application, "ApplicationDeveloper", org);
|
|
|
|
|
latebound_access::set(application, "Version", std::string(IFCOPENSHELL_VERSION));
|
|
|
|
|
latebound_access::set(application, "ApplicationFullName", std::string("IfcConvert"));
|
2025-09-30 18:11:33 +05:00
|
|
|
latebound_access::set(application, "ApplicationIdentifier", std::string("IfcConvert") + IFCOPENSHELL_VERSION);
|
2018-11-02 16:10:06 +01:00
|
|
|
|
|
|
|
|
auto ownerhist = latebound_access::create(f, "IfcOwnerHistory");
|
|
|
|
|
latebound_access::set(ownerhist, "OwningUser", pando);
|
|
|
|
|
latebound_access::set(ownerhist, "OwningApplication", application);
|
|
|
|
|
latebound_access::set(ownerhist, "ChangeAction", std::string("MODIFIED"));
|
|
|
|
|
latebound_access::set(ownerhist, "CreationDate", (int)time(0));
|
|
|
|
|
|
2026-01-04 10:40:02 +01:00
|
|
|
express::Base quantity;
|
|
|
|
|
std::vector<express::Base> objects;
|
2018-11-02 16:10:06 +01:00
|
|
|
boost::shared_ptr<IfcGeom::Representation::BRep> previous_geometry_pointer;
|
|
|
|
|
|
2018-12-12 12:33:09 +01:00
|
|
|
for (;; ++num_created) {
|
|
|
|
|
bool has_more = true;
|
|
|
|
|
if (num_created) {
|
|
|
|
|
has_more = context_iterator.next();
|
|
|
|
|
}
|
2021-07-11 15:00:59 +02:00
|
|
|
IfcGeom::BRepElement* geom_object = nullptr;
|
2018-12-12 12:33:09 +01:00
|
|
|
if (has_more) {
|
|
|
|
|
geom_object = context_iterator.get_native();
|
|
|
|
|
}
|
2018-11-02 16:10:06 +01:00
|
|
|
|
2018-12-12 12:33:09 +01:00
|
|
|
if (geom_object && geom_object->geometry_pointer() == previous_geometry_pointer) {
|
2023-03-21 20:15:01 +01:00
|
|
|
// @todo
|
2026-01-04 10:40:02 +01:00
|
|
|
objects.push_back(geom_object->product());
|
2018-11-02 16:10:06 +01:00
|
|
|
} else {
|
|
|
|
|
if (quantity) {
|
|
|
|
|
auto rel = latebound_access::create(f, "IfcRelDefinesByProperties");
|
|
|
|
|
latebound_access::set(rel, "OwnerHistory", ownerhist);
|
|
|
|
|
latebound_access::set(rel, "RelatedObjects", objects);
|
|
|
|
|
latebound_access::set(rel, "RelatingPropertyDefinition", quantity);
|
|
|
|
|
}
|
|
|
|
|
|
2018-12-12 12:33:09 +01:00
|
|
|
if (!geom_object) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-04 10:40:02 +01:00
|
|
|
std::vector<express::Base> quantities;
|
2018-11-02 16:10:06 +01:00
|
|
|
|
|
|
|
|
double a, b, c;
|
|
|
|
|
if (geom_object->geometry().calculate_surface_area(a)) {
|
|
|
|
|
auto quantity_area = latebound_access::create(f, "IfcQuantityArea");
|
|
|
|
|
latebound_access::set(quantity_area, "Name", std::string("Total Surface Area"));
|
|
|
|
|
latebound_access::set(quantity_area, "AreaValue", a);
|
2026-01-04 10:40:02 +01:00
|
|
|
quantities.push_back(quantity_area);
|
2018-11-02 16:10:06 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (geom_object->geometry().calculate_volume(a)) {
|
|
|
|
|
auto quantity_volume = latebound_access::create(f, "IfcQuantityVolume");
|
|
|
|
|
latebound_access::set(quantity_volume, "Name", std::string("Volume"));
|
|
|
|
|
latebound_access::set(quantity_volume, "VolumeValue", a);
|
2026-01-04 10:40:02 +01:00
|
|
|
quantities.push_back(quantity_volume);
|
2018-11-02 16:10:06 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (geom_object->calculate_projected_surface_area(a, b, c)) {
|
|
|
|
|
auto quantity_area = latebound_access::create(f, "IfcQuantityArea");
|
|
|
|
|
latebound_access::set(quantity_area, "Name", std::string("Footprint Area"));
|
|
|
|
|
latebound_access::set(quantity_area, "AreaValue", c);
|
2026-01-04 10:40:02 +01:00
|
|
|
quantities.push_back(quantity_area);
|
2018-11-02 16:10:06 +01:00
|
|
|
}
|
|
|
|
|
|
2018-12-12 12:33:09 +01:00
|
|
|
auto quantity_complex = latebound_access::create(f, "IfcPhysicalComplexQuantity");
|
|
|
|
|
latebound_access::set(quantity_complex, "Name", std::string("Shape Validation Properties"));
|
2026-01-04 10:40:02 +01:00
|
|
|
quantities.push_back(quantity_complex);
|
2018-12-10 11:33:54 +01:00
|
|
|
|
2026-01-04 10:40:02 +01:00
|
|
|
std::vector<express::Base> quantities_2;
|
2018-12-10 11:33:54 +01:00
|
|
|
|
2018-12-12 12:33:09 +01:00
|
|
|
for (auto& part : geom_object->geometry()) {
|
|
|
|
|
auto quantity_count = latebound_access::create(f, "IfcQuantityCount");
|
|
|
|
|
latebound_access::set(quantity_count, "Name", std::string("Surface Genus"));
|
|
|
|
|
latebound_access::set(quantity_count, "Description", '#' + boost::lexical_cast<std::string>(part.ItemId()));
|
2023-03-21 20:15:01 +01:00
|
|
|
latebound_access::set(quantity_count, "CountValue", part.Shape()->surface_genus());
|
2018-12-10 11:33:54 +01:00
|
|
|
|
2026-01-04 10:40:02 +01:00
|
|
|
quantities_2.push_back(quantity_count);
|
2018-12-10 11:33:54 +01:00
|
|
|
}
|
|
|
|
|
|
2018-12-12 12:33:09 +01:00
|
|
|
latebound_access::set(quantity_complex, "HasQuantities", quantities_2);
|
|
|
|
|
|
2026-01-04 10:40:02 +01:00
|
|
|
if (!quantities.empty()) {
|
2018-11-02 16:10:06 +01:00
|
|
|
quantity = latebound_access::create(f, "IfcElementQuantity");
|
|
|
|
|
latebound_access::set(quantity, "OwnerHistory", ownerhist);
|
|
|
|
|
latebound_access::set(quantity, "Quantities", quantities);
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-21 20:15:01 +01:00
|
|
|
// @todo
|
2026-01-04 10:40:02 +01:00
|
|
|
objects.push_back(geom_object->product());
|
2018-11-02 16:10:06 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
previous_geometry_pointer = geom_object->geometry_pointer();
|
|
|
|
|
|
|
|
|
|
if (!no_progress) {
|
|
|
|
|
if (quiet) {
|
|
|
|
|
const int progress = context_iterator.progress();
|
|
|
|
|
for (; old_progress < progress; ++old_progress) {
|
|
|
|
|
std::cout << ".";
|
|
|
|
|
if (stderr_progress)
|
2020-07-21 23:23:39 +02:00
|
|
|
cerr_ << ".";
|
2018-11-02 16:10:06 +01:00
|
|
|
}
|
|
|
|
|
std::cout << std::flush;
|
|
|
|
|
if (stderr_progress)
|
2020-07-21 23:23:39 +02:00
|
|
|
cerr_ << std::flush;
|
2018-11-02 16:10:06 +01:00
|
|
|
} else {
|
|
|
|
|
const int progress = context_iterator.progress() / 2;
|
2026-03-31 15:32:36 +02:00
|
|
|
if (old_progress != progress) logger::progress_bar(progress);
|
2018-11-02 16:10:06 +01:00
|
|
|
old_progress = progress;
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-12-12 12:33:09 +01:00
|
|
|
}
|
2018-12-10 11:33:54 +01:00
|
|
|
|
|
|
|
|
if (!no_progress && quiet) {
|
|
|
|
|
for (; old_progress < 100; ++old_progress) {
|
|
|
|
|
std::cout << ".";
|
|
|
|
|
if (stderr_progress)
|
2020-07-21 23:23:39 +02:00
|
|
|
cerr_ << ".";
|
2018-12-10 11:33:54 +01:00
|
|
|
}
|
|
|
|
|
std::cout << std::flush;
|
|
|
|
|
if (stderr_progress)
|
2020-07-21 23:23:39 +02:00
|
|
|
cerr_ << std::flush;
|
2018-12-10 11:33:54 +01:00
|
|
|
} else {
|
2026-03-31 15:32:36 +02:00
|
|
|
logger::status("\rDone writing quantities for " + boost::lexical_cast<std::string>(num_created) +
|
2018-12-10 11:33:54 +01:00
|
|
|
" objects ");
|
|
|
|
|
}
|
|
|
|
|
|
2018-11-02 16:10:06 +01:00
|
|
|
}
|