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"
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 ( )
{
2026-04-21 16:18:59 +02:00
// @todo print plug-in versions
cout_ < < " IfcOpenShell IfcConvert " < < IFCOPENSHELL_VERSION < < " \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_ < < " \n Run '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-07-09 13:30:48 +02:00
void fix_quantities ( ifcopenshell : : file & , bool , bool , bool , logger & logger = : : logger : : root ( ) ) ;
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-07-09 13:30:48 +02:00
bool init_input_file ( const std : : string & filename , ifcopenshell : : file * & ifc_file , bool no_progress , bool mmap , bool bypass_properties = false , logger & logger = : : logger : : root ( ) ) ;
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
2026-07-09 13:30:48 +02:00
logger logger ;
2019-02-08 15:18:55 +01:00
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 ;
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 ( ) ;
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 " )
2026-07-10 20:38:15 +03:00
( " fail-on-error " , " return a non-zero exit code when one or more errors were logged during "
" geometry conversion (e.g. an element failed to convert). By default IfcConvert exits "
" successfully as long as an output file could be written, even if some elements were "
" silently dropped. Enable this flag so scripts and CI can detect partial conversions. " )
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 " ) ;
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 " )
2026-05-08 10:57:58 +02:00
( " stream " , " Use streaming conversion when supported (RocksDB uses it automatically) " )
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 ;
2026-07-10 20:38:15 +03:00
const bool fail_on_error = vmap . count ( " fail-on-error " ) ! = 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-07-09 13:30:48 +02:00
logger . notice ( " SYS " , 7 , " 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-07-09 13:30:48 +02:00
logger . output_format ( : : logger : : FMT_PLAIN ) ;
2019-02-08 15:18:55 +01:00
} else if ( log_format = = " json " ) {
2026-07-09 13:30:48 +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-07-09 13:30:48 +02:00
logger . notice ( " SYS " , 8 , 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-07-09 13:30:48 +02:00
logger . set_output ( quiet ? nullptr : & cout_ , & log_fs ) ;
2020-08-16 16:17:38 +02:00
} else {
2026-07-09 13:30:48 +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-07-09 13:30:48 +02:00
logger . verbosity ( : : logger : : LOG_ERROR ) ;
2022-01-07 15:12:32 +01:00
break ;
case 1 :
2026-07-09 13:30:48 +02:00
logger . verbosity ( : : logger : : LOG_NOTICE ) ;
2022-01-07 15:12:32 +01:00
break ;
case 2 :
2026-07-09 13:30:48 +02:00
logger . verbosity ( : : logger : : LOG_DEBUG ) ;
2022-01-07 15:12:32 +01:00
break ;
case 3 :
2026-07-09 13:30:48 +02:00
logger . verbosity ( : : logger : : LOG_PERF ) ;
2022-01-07 15:12:32 +01:00
break ;
case 4 :
2026-07-09 13:30:48 +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
2026-05-06 21:17:25 +02:00
auto run_document_serializer = [ & ] ( const ifcopenshell : : serializers : : document_serializer_info * document_serializer_info ) {
2018-11-02 16:10:06 +01:00
int exit_code = EXIT_FAILURE ;
try {
2026-05-08 10:57:58 +02:00
const bool use_input_filename = document_serializer_info - > supports_input_filename & &
( vmap . count ( " stream " ) | | ! document_serializer_info - > supports_ifc_file ) ;
2026-04-18 15:46:21 +02:00
if ( ! use_input_filename & & ! document_serializer_info - > supports_ifc_file ) {
throw ifcopenshell : : exception ( " Selected document serializer requires --stream " ) ;
}
2026-05-06 21:17:25 +02:00
if ( use_input_filename | | ifc_file | | init_input_file ( ifcopenshell : : path : : to_utf8 ( input_filename ) , ifc_file , no_progress | | quiet , mmap ) ) {
2026-04-18 15:46:21 +02:00
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 ;
2026-05-08 10:57:58 +02:00
context . file = use_input_filename ? nullptr : ifc_file ;
2026-04-18 15:46:21 +02:00
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 ) ;
2026-05-08 10:57:58 +02:00
if ( serializer - > is_streaming ( ) ! = use_input_filename ) {
throw ifcopenshell : : exception ( " Selected document serializer streaming mode does not match its registry metadata " ) ;
}
2026-07-09 13:21:39 +02:00
logger . status ( " Writing " + boost : : to_upper_copy ( document_serializer_info - > format ) + " output... " ) ;
2026-04-18 15:46:21 +02:00
serializer - > finalize ( ) ;
serializer . reset ( ) ;
2019-02-06 15:39:43 +01:00
time ( & end ) ;
2026-07-09 13:21:39 +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-07-09 13:30:48 +02:00
logger . error ( " SYS " , 9 , e ) ;
2018-03-16 16:04:48 +01:00
}
2018-11-02 16:10:06 +01:00
write_log ( ! quiet ) ;
return exit_code ;
2026-05-06 21:17:25 +02:00
} ;
const auto * document_serializer_info = document_serializer_registry . find ( output_extension_utf8 ) ;
if ( document_serializer_info ) {
return run_document_serializer ( document_serializer_info ) ;
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-07-09 13:21:39 +02:00
if ( init_input_file ( ifcopenshell : : path : : to_utf8 ( input_filename ) , ifc_file , no_progress | | quiet , mmap , false , logger ) ) {
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 " ) ) {
2026-06-10 18:30:54 +02:00
fix_quantities ( * ifc_file , no_progress , quiet , stderr_progress , logger ) ;
2018-11-02 16:10:06 +01:00
}
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-07-09 13:30:48 +02:00
logger . error ( " SYS " , 10 , " 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-07-09 13:30:48 +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-07-09 13:30:48 +02:00
logger . error ( " SYS " , 11 , 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 ) {
2026-05-06 21:17:25 +02:00
if ( init_input_file ( ifcopenshell : : path : : to_utf8 ( input_filename ) , ifc_file , no_progress | | quiet , mmap ) ) {
document_serializer_info = document_serializer_registry . find ( output_extension_utf8 , ifc_file - > schema ( ) - > name ( ) ) ;
if ( document_serializer_info ) {
return run_document_serializer ( document_serializer_info ) ;
}
}
2026-04-18 15:46:21 +02:00
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-07-09 13:30:48 +02:00
if ( ! entity_filter . entity_names . empty ( ) ) { entity_filter . update_description ( ) ; logger . notice ( " SYS " , 13 , entity_filter . description ) ; }
if ( ! layer_filter . values . empty ( ) ) { layer_filter . update_description ( ) ; logger . notice ( " SYS " , 14 , layer_filter . description ) ; }
if ( ! attribute_filter . attribute_name . empty ( ) ) { attribute_filter . update_description ( ) ; logger . notice ( " SYS " , 15 , 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-07-09 13:30:48 +02:00
logger . notice ( " SYS " , 16 , " 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-07-09 13:30:48 +02:00
logger . notice ( " SYS " , 17 , " 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-07-09 13:30:48 +02:00
logger . notice ( " SYS " , 18 , " 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-07-10 15:37:44 +03:00
logger . error ( " SYS " , 25 , " Unable to open output file ' " + ifcopenshell : : path : : to_utf8 ( output_filename ) + " ' for writing; check that the directory exists and is writable " ) ;
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-07-09 13:21:39 +02:00
if ( ! init_input_file ( ifcopenshell : : path : : to_utf8 ( input_filename ) , ifc_file , no_progress | | quiet , mmap , geometry_serializer_info - > bypass_properties , logger ) ) {
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-07-09 13:30:48 +02:00
logger . set_output ( quiet ? nullptr : & cout_ , & log_fs ) ;
2020-08-16 16:17:38 +02:00
} else {
2026-07-09 13:30:48 +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-07-09 13:30:48 +02:00
logger . notice ( " SYS " , 19 , 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-07-09 13:30:48 +02:00
logger . notice ( " GEO " , 22 , " --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-07-09 13:30:48 +02:00
logger . notice ( " SYS " , 20 , 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
2026-07-09 13:30:48 +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 , logger ) ;
2020-02-12 11:38:08 +01:00
2024-11-29 13:03:56 +01:00
time_t start , end ;
time ( & start ) ;
2026-07-09 13:30:48 +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-07-09 13:30:48 +02:00
logger . notice ( " GEO " , 23 , " 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-07-09 13:30:48 +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-07-09 13:30:48 +02:00
logger . notice ( " SYS " , 21 , 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 ) {
2026-07-09 13:30:48 +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 , logger ) ) ;
2023-09-13 22:02:52 +02:00
}
2021-10-08 15:38:46 +02:00
2026-07-09 13:30:48 +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-07-09 13:30:48 +02:00
logger . notice ( " GEO " , 25 , " 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 ;
}
2026-07-24 19:47:11 +05: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-07-09 13:30:48 +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-07-09 13:30:48 +02:00
logger . message ( : : logger : : LOG_DEBUG , " SYS " , 23 , " 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-07-09 13:30:48 +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-07-09 13:30:48 +02:00
logger . status ( " \r Done " + 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-07-09 13:30:48 +02:00
logger . message ( : : logger : : LOG_PERF , " GEO " , 26 , " 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-07-09 13:30:48 +02:00
if ( geometry_settings . get < ifcopenshell : : geometry : : settings : : ValidateQuantities > ( ) . get ( ) & & logger . max_severity ( ) > = : : logger : : LOG_ERROR ) {
logger . error ( " SYS " , 24 , " Errors encountered during processing. " ) ;
2018-12-12 12:33:09 +01:00
successful = false ;
}
2026-07-10 20:38:15 +03:00
if ( fail_on_error & & logger . max_severity ( ) > = : : logger : : LOG_ERROR ) {
logger . error ( " SYS " , 26 , " Errors encountered during processing, failing due to --fail-on-error. " ) ;
successful = false ;
}
2026-07-09 13:30:48 +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-07-09 13:30:48 +02:00
logger . status ( " \n Conversion 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_ < < " \n Log: \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-07-09 13:30:48 +02:00
bool init_input_file ( const std : : string & filename , ifcopenshell : : file * & ifc_file , bool no_progress , bool mmap , bool bypass_properties , logger & logger ) {
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
2026-07-09 13:21:39 +02:00
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-07-09 13:21:39 +02:00
ifc_file = new ifcopenshell : : file ( ifcopenshell : : uninitialized_tag { } , logger ) ;
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-07-09 13:30:48 +02:00
logger . error ( " SYN " , 1 , " 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-07-09 13:30:48 +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-07-09 13:30:48 +02:00
void fix_quantities ( ifcopenshell : : file & f , bool no_progress , bool quiet , bool stderr_progress , logger & logger ) {
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
2026-07-09 13:30:48 +02:00
IfcGeom : : Iterator context_iterator ( ifcopenshell : : geometry : : kernels : : construct ( & f , " opencascade " , settings ) , settings , & f , { } , 1 , logger ) ;
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 " ) ) ;
2026-07-19 12:58:23 +03:00
latebound_access : : set ( ownerhist , " CreationDate " , ( int64_t ) time ( 0 ) ) ;
2018-11-02 16:10:06 +01:00
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 ( ) ) ) ;
2026-07-19 12:58:23 +03:00
latebound_access : : set ( quantity_count , " CountValue " , ( int64_t ) 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-07-09 13:30:48 +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-07-09 13:30:48 +02:00
logger . status ( " \r Done 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
}