/********************************************************************************
* *
* 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 . *
* *
********************************************************************************/
/********************************************************************************
* *
* This started as a brief example of how IfcOpenShell can be interfaced from *
* 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 *
* several tesselated and topological output formats. *
* *
********************************************************************************/
#include "../ifcconvert/ColladaSerializer.h"
#include "../ifcconvert/IgesSerializer.h"
#include "../ifcconvert/StepSerializer.h"
#include "../ifcconvert/WavefrontObjSerializer.h"
#include "../ifcconvert/XmlSerializer.h"
#include "../ifcconvert/SvgSerializer.h"
#include "../ifcgeom/IfcGeomIterator.h"
#include
#include
#include
#include
#include
#include
#include
#if USE_VLD
#include
#endif
const std::string DEFAULT_EXTENSION = "obj";
const std::string TEMP_FILE_EXTENSION = ".tmp";
namespace po = boost::program_options;
void print_version()
{
std::cout << "IfcOpenShell " << IfcSchema::Identifier << " IfcConvert " << IFCOPENSHELL_VERSION << std::endl;
}
void print_usage(bool suggest_help = true)
{
std::cout << "Usage: IfcConvert [options] []\n"
<< "\n"
<< "Converts the geometry in an IFC file into one of the following formats:\n"
<< " .obj WaveFront OBJ (a .mtl file is also created)\n"
#ifdef WITH_OPENCOLLADA
<< " .dae Collada Digital Assets Exchange\n"
#endif
<< " .stp STEP Standard for the Exchange of Product Data\n"
<< " .igs IGES Initial Graphics Exchange Specification\n"
<< " .xml XML Property definitions and decomposition tree\n"
<< " .svg SVG Scalable Vector Graphics (2D floor plan)\n"
<< "\n"
<< "If no output filename given, ." + DEFAULT_EXTENSION + " will be used as the output file.\n";
if (suggest_help) {
std::cout << "\nRun 'IfcConvert --help' for more information.";
}
std::cout << std::endl;
}
/// @todo Add help for single option
void print_options(const po::options_description& options)
{
std::cout << "\n" << options;
std::cout << std::endl;
}
std::string change_extension(const std::string& fn, const std::string& ext) {
std::string::size_type dot = fn.find_last_of('.');
if (dot != std::string::npos) {
return fn.substr(0,dot+1) + ext;
} else {
return fn + "." + ext;
}
}
bool file_exists(const std::string& filename)
{
/// @todo Windows Unicode support
std::ifstream file(filename.c_str());
return file.good();
}
bool rename_file(const std::string& old_filename, const std::string& new_filename)
{
// Whether or not rename() replaces an existing file is implementation-specific,
// so remove() possible existing file always.
/// @todo Windows Unicode support
std::remove(new_filename.c_str());
return std::rename(old_filename.c_str(), new_filename.c_str()) == 0;
}
static std::stringstream log_stream;
void write_log();
/// @todo make the filters non-global
IfcGeom::entity_filter entity_filter; // Entity filter is used always by default.
IfcGeom::layer_filter layer_filter;
const std::string NAME_ARG = "Name", GUID_ARG = "GlobalId", DESC_ARG = "Description", TAG_ARG = "Tag";
boost::array supported_args = { { NAME_ARG, GUID_ARG, DESC_ARG, TAG_ARG } };
IfcGeom::string_arg_filter guid_filter(IfcSchema::Type::IfcRoot, 0); // IfcRoot.GlobalId
// Note: skipping IfcRoot OwnerHistory, argument index 1
IfcGeom::string_arg_filter name_filter(IfcSchema::Type::IfcRoot, 2); // IfcRoot.Name
IfcGeom::string_arg_filter desc_filter(IfcSchema::Type::IfcRoot, 3); // IfcRoot.Description
IfcGeom::string_arg_filter tag_filter(IfcSchema::Type::IfcProxy, 8, IfcSchema::Type::IfcElement, 7); // IfcProxy.Tag & IfcElement.Tag
struct geom_filter
{
geom_filter(bool include, bool traverse) : type(UNUSED), include(include), traverse(traverse) {}
geom_filter() : type(UNUSED), include(false), traverse(false) {}
enum filter_type { UNUSED, ENTITY_TYPE, LAYER_NAME, ENTITY_ARG };
filter_type type;
bool include;
bool traverse;
std::string arg;
std::set 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.
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) {} };
std::vector setup_filters(const std::vector&, const std::string&);
int main(int argc, char** argv)
{
po::options_description generic_options("Command line options");
generic_options.add_options()
("help,h", "display usage information")
("version", "display version information")
("verbose,v", "more verbose output")
("yes,y", "answer 'yes' automatically to possible confirmation queries (e.g. overwriting an existing output file)");
po::options_description fileio_options;
fileio_options.add_options()
("input-file", po::value(), "input IFC file")
("output-file", po::value(), "output geometry file");
double deflection_tolerance;
inclusion_filter include_filter;
inclusion_traverse_filter include_traverse_filter;
exclusion_filter exclude_filter;
exclusion_traverse_filter exclude_traverse_filter;
po::options_description geom_options("Geometry options");
geom_options.add_options()
("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. "
"Included by default.")
("weld-vertices",
"Specifies whether vertices are welded, meaning that the coordinates "
"vector will only contain unique xyz-triplets. This results in a "
"manifold mesh which is useful for modelling applications, but might "
"result in unwanted shading artefacts in rendering applications.")
("use-world-coords",
"Specifies whether to apply the local placements of building elements "
"directly to the coordinates of the representation mesh rather than "
"to represent the local placement in the 4x3 matrix, which will in that "
"case be the identity matrix.")
("convert-back-units",
"Specifies whether to convert back geometrical output back to the "
"unit of measure in which it is defined in the IFC file. Default is "
"to use meters.")
("sew-shells",
"Specifies whether to sew the faces of IfcConnectedFaceSets together. "
"This is a potentially time consuming operation, but guarantees a "
"consistent orientation of surface normals, even if the faces are not "
"properly oriented in the IFC file.")
#if OCC_VERSION_HEX < 0x60900
// In Open CASCADE version prior to 6.9.0 boolean operations with multiple
// arguments where not introduced yet and a work-around was implemented to
// subtract multiple openings as a single compound. This hack is obsolete
// for newer versions of Open CASCADE.
("merge-boolean-operands",
"Specifies whether to merge all IfcOpeningElement operands into a single "
"operand before applying the subtraction operation. This may "
"introduce a performance improvement at the risk of failing, in "
"which case the subtraction is applied one-by-one.")
#endif
("disable-opening-subtractions",
"Specifies whether to disable the boolean subtraction of "
"IfcOpeningElement Representations from their RelatingElements.")
("enable-layerset-slicing",
"Specifies whether to enable the slicing of products according "
"to their associated IfcMaterialLayerSet.")
("include", po::value(&include_filter)->multitoken(),
"Specifies that the entities that match a specific filtering criteria are to be included in the geometrical output:\n"
"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"
"2) 'layers': the entities that are assigned to presentation layers of which names "
"match the given values should be included.\n"
"3) 'arg ': the following list of values for that specific argument should be included. "
"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(&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(&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(&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.")
("no-normals",
"Disables computation of normals. Saves time and file size and is useful "
"in instances where you're going to recompute normals for the exported "
"model in other modelling application in any case.")
("deflection-tolerance", po::value(&deflection_tolerance)->default_value(1e-3),
"Sets the deflection tolerance of the mesher, 1e-3 by default if not specified.")
("generate-uvs",
"Generates UVs (texture coordinates) by using simple box projection. Requires normals. "
"Not guaranteed to work properly if used with --weld-vertices.");
std::string bounds, offset_str;
#ifdef HAVE_ICU
std::string unicode_mode;
#endif
short precision;
po::options_description serializer_options("Serialization options");
serializer_options.add_options()
#ifdef HAVE_ICU
("unicode", po::value(&unicode_mode),
"Specifies the Unicode handling behavior when parsing the IFC file. "
"Accepted values 'utf8' (the default) and 'escape'.")
#endif
("bounds", po::value(&bounds),
"Specifies the bounding rectangle, for example 512x512, to which the "
"output will be scaled. Only used when converting to SVG.")
("use-element-names",
"Use entity names instead of unique IDs for naming elements upon serialization. "
"Applicable for OBJ, DAE, and SVG output.")
("use-element-guids",
"Use entity GUIDs instead of unique IDs for naming elements upon serialization. "
"Applicable for OBJ, DAE, and SVG output.")
("use-material-names",
"Use material names instead of unique IDs for naming materials upon serialization. "
"Applicable for OBJ and DAE output.")
("center-model",
"Centers the elements upon serialization by applying the center point of "
"all placements as an offset. Applicable for OBJ and DAE output.")
("model-offset", po::value(&offset_str),
"Applies an arbitrary offset of form 'x;y;z' to all placements. Applicable for OBJ and DAE output.")
("precision", po::value(&precision)->default_value(SerializerSettings::DEFAULT_PRECISION),
"Sets the precision to be used to format floating-point values, 15 by default. "
"Use a negative value to use the system's default precision (should be 6 typically). "
"Applicable for OBJ and DAE output. For DAE output, value >= 15 means that up to 16 decimals are used, "
" and any other value means that 6 or 7 decimals are used.");
po::options_description cmdline_options;
cmdline_options.add(generic_options).add(fileio_options).add(geom_options).add(serializer_options);
po::positional_options_description positional_options;
positional_options.add("input-file", 1);
positional_options.add("output-file", 1);
po::variables_map vmap;
try {
po::store(po::command_line_parser(argc, argv).
options(cmdline_options).positional(positional_options).run(), vmap);
} catch (const po::unknown_option& e) {
std::cerr << "[Error] Unknown option '" << e.get_option_name() << "'\n\n";
print_usage();
return EXIT_FAILURE;
} catch (const po::error_with_option_name& e) {
std::cerr << "[Error] Invalid usage of '" << e.get_option_name() << "': " << e.what() << "\n\n";
return EXIT_FAILURE;
} catch (const std::exception& e) {
std::cerr << "[Error] " << e.what() << "\n\n";
print_usage();
return EXIT_FAILURE;
} catch (...) {
print_usage();
return EXIT_FAILURE;
}
po::notify(vmap);
print_version();
if (vmap.count("version")) {
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")) {
std::cerr << "[Error] Input file not specified" << std::endl;
print_usage();
return EXIT_FAILURE;
}
const bool verbose = vmap.count("verbose") != 0;
const bool weld_vertices = vmap.count("weld-vertices") != 0;
const bool use_world_coords = vmap.count("use-world-coords") != 0;
const bool convert_back_units = vmap.count("convert-back-units") != 0;
const bool sew_shells = vmap.count("sew-shells") != 0;
#if OCC_VERSION_HEX < 0x60900
const bool merge_boolean_operands = vmap.count("merge-boolean-operands") != 0;
#endif
const bool disable_opening_subtractions = vmap.count("disable-opening-subtractions") != 0;
const bool include_plan = vmap.count("plan") != 0;
const bool include_model = vmap.count("model") != 0 || (!include_plan);
const bool enable_layerset_slicing = vmap.count("enable-layerset-slicing") != 0;
const bool use_element_names = vmap.count("use-element-names") != 0;
const bool use_element_guids = vmap.count("use-element-guids") != 0 ;
const bool use_material_names = vmap.count("use-material-names") != 0;
const bool no_normals = vmap.count("no-normals") != 0 ;
const bool center_model = vmap.count("center-model") != 0 ;
const bool model_offset = vmap.count("model-offset") != 0 ;
const bool generate_uvs = vmap.count("generate-uvs") != 0 ;
#ifdef HAVE_ICU
if (!unicode_mode.empty()) {
if (unicode_mode == "utf8") {
IfcParse::IfcCharacterDecoder::mode = IfcParse::IfcCharacterDecoder::UTF8;
} else if (unicode_mode == "escape") {
IfcParse::IfcCharacterDecoder::mode = IfcParse::IfcCharacterDecoder::JSON;
} else {
std::cerr << "[Error] Invalid value for --unicode" << std::endl;
print_options(serializer_options);
return 1;
}
}
#endif
int bounding_width = -1, bounding_height = -1;
if (vmap.count("bounds") == 1) {
int w, h;
if (sscanf(bounds.c_str(), "%ux%u", &w, &h) == 2 && w > 0 && h > 0) {
bounding_width = w;
bounding_height = h;
} else {
std::cerr << "[Error] Invalid use of --bounds" << std::endl;
print_options(serializer_options);
return EXIT_FAILURE;
}
}
const std::string input_filename = vmap["input-file"].as();
if (!file_exists(input_filename)) {
std::cerr << "[Error] Input file '" << input_filename << "' does not exist" << std::endl;
return EXIT_FAILURE;
}
// If no output filename is specified a Wavefront OBJ file will be output
// to maintain backwards compatibility with the obsolete IfcObj executable.
const std::string output_filename = vmap.count("output-file") == 1
? vmap["output-file"].as()
: change_extension(input_filename, DEFAULT_EXTENSION);
if (output_filename.size() < 5) {
std::cerr << "[Error] Invalid or unsupported output file '" << output_filename << "' given" << std::endl;
print_usage();
return EXIT_FAILURE;
}
if (file_exists(output_filename) && !vmap.count("yes")) {
std::string answer;
std::cout << "A file '" << output_filename << "' already exists. Overwrite the existing file?" << std::endl;
std::cin >> answer;
if (!boost::iequals(answer, "yes") && !boost::iequals(answer, "y")) {
return EXIT_SUCCESS;
}
}
std::string output_temp_filename = output_filename + TEMP_FILE_EXTENSION;
std::string output_extension = output_filename.substr(output_filename.size()-4);
boost::to_lower(output_extension);
Logger::SetOutput(&std::cout, &log_stream);
Logger::Verbosity(verbose ? Logger::LOG_NOTICE : Logger::LOG_ERROR);
IfcParse::IfcFile ifc_file;
if (!ifc_file.Init(input_filename)) {
Logger::Error("Unable to parse input file '" + input_filename + "'");
return EXIT_FAILURE;
}
if (output_extension == ".xml") {
int exit_code = EXIT_FAILURE;
try {
XmlSerializer s(output_temp_filename);
s.setFile(&ifc_file);
Logger::Status("Writing XML output...");
s.finalize();
Logger::Status("Done!");
rename_file(output_temp_filename, output_filename);
exit_code = EXIT_SUCCESS;
} catch (...) {}
write_log();
return exit_code;
}
/// @todo Clean up this filter code further.
std::vector 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); }
std::vector filter_funcs = setup_filters(used_filters, output_extension);
if (filter_funcs.empty()) {
std::cerr << "[Error] Failed to set up geometry filters\n";
return EXIT_FAILURE;
}
if (!entity_filter.values.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 (!guid_filter.values.empty()) { guid_filter.update_description(); Logger::Notice(guid_filter.description); }
if (!name_filter.values.empty()) { name_filter.update_description(); Logger::Notice(name_filter.description); }
if (!desc_filter.values.empty()) { desc_filter.update_description(); Logger::Notice(desc_filter.description); }
if (!tag_filter.values.empty()) { tag_filter.update_description(); Logger::Notice(tag_filter.description); }
SerializerSettings settings;
/// @todo Make APPLY_DEFAULT_MATERIALS configurable? Quickly tested setting this to false and using obj exporter caused the program to crash and burn.
settings.set(IfcGeom::IteratorSettings::APPLY_DEFAULT_MATERIALS, true);
settings.set(IfcGeom::IteratorSettings::USE_WORLD_COORDS, use_world_coords);
settings.set(IfcGeom::IteratorSettings::WELD_VERTICES, weld_vertices);
settings.set(IfcGeom::IteratorSettings::SEW_SHELLS, sew_shells);
settings.set(IfcGeom::IteratorSettings::CONVERT_BACK_UNITS, convert_back_units);
#if OCC_VERSION_HEX < 0x60900
settings.set(IfcGeom::IteratorSettings::FASTER_BOOLEANS, merge_boolean_operands);
#endif
settings.set(IfcGeom::IteratorSettings::DISABLE_OPENING_SUBTRACTIONS, disable_opening_subtractions);
settings.set(IfcGeom::IteratorSettings::INCLUDE_CURVES, include_plan);
settings.set(IfcGeom::IteratorSettings::EXCLUDE_SOLIDS_AND_SURFACES, !include_model);
settings.set(IfcGeom::IteratorSettings::APPLY_LAYERSETS, enable_layerset_slicing);
settings.set(IfcGeom::IteratorSettings::NO_NORMALS, no_normals);
settings.set(IfcGeom::IteratorSettings::GENERATE_UVS, generate_uvs);
settings.set(SerializerSettings::USE_ELEMENT_NAMES, use_element_names);
settings.set(SerializerSettings::USE_ELEMENT_GUIDS, use_element_guids);
settings.set(SerializerSettings::USE_MATERIAL_NAMES, use_material_names);
settings.set_deflection_tolerance(deflection_tolerance);
settings.precision = precision;
GeometrySerializer* serializer;
if (output_extension == ".obj") {
// Do not use temp file for MTL as it's such a small file.
const std::string mtl_filename = change_extension(output_filename, "mtl");
if (!use_world_coords) {
Logger::Notice("Using world coords when writing WaveFront OBJ files");
settings.set(IfcGeom::IteratorSettings::USE_WORLD_COORDS, true);
}
serializer = new WaveFrontOBJSerializer(output_temp_filename, mtl_filename, settings);
#ifdef WITH_OPENCOLLADA
} else if (output_extension == ".dae") {
serializer = new ColladaSerializer(output_temp_filename, settings);
#endif
} else if (output_extension == ".stp") {
serializer = new StepSerializer(output_temp_filename, settings);
} else if (output_extension == ".igs") {
IGESControl_Controller::Init(); // work around Open Cascade bug
serializer = new IgesSerializer(output_temp_filename, settings);
} else if (output_extension == ".svg") {
settings.set(IfcGeom::IteratorSettings::DISABLE_TRIANGULATION, true);
serializer = new SvgSerializer(output_temp_filename, settings);
if (bounding_width && bounding_height) {
static_cast(serializer)->setBoundingRectangle(bounding_width, bounding_height);
}
} else {
Logger::Error("Unknown output filename extension '" + output_extension + "'");
write_log();
print_usage();
return EXIT_FAILURE;
}
const bool is_tesselated = serializer->isTesselated(); // isTesselated() doesn't change at run-time
if (!is_tesselated) {
if (weld_vertices) {
Logger::Notice("Weld vertices setting ignored when writing non-tesselated output");
}
if (generate_uvs) {
Logger::Notice("Generate UVs setting ignored when writing non-tesselated output");
}
if (center_model || model_offset) {
Logger::Notice("Centering/offsetting model setting ignored when writing non-tesselated output");
}
settings.set(IfcGeom::IteratorSettings::DISABLE_TRIANGULATION, true);
}
if (!serializer->ready()) {
write_log();
return EXIT_FAILURE;
}
time_t start,end;
time(&start);
IfcGeom::Iterator context_iterator(settings, &ifc_file, filter_funcs);
if (!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.
Logger::Error("No geometrical entities found");
write_log();
return EXIT_FAILURE;
}
serializer->setFile(context_iterator.getFile());
if (convert_back_units) {
serializer->setUnitNameAndMagnitude(context_iterator.getUnitName(), static_cast(context_iterator.getUnitMagnitude()));
} else {
serializer->setUnitNameAndMagnitude("METER", 1.0f);
}
serializer->writeHeader();
int old_progress = -1;
if (center_model || model_offset) {
double* offset = serializer->settings().offset;
if (center_model) {
gp_XYZ center = (context_iterator.bounds_min() + context_iterator.bounds_max()) * 0.5;
offset[0] = -center.X();
offset[1] = -center.Y();
offset[2] = -center.Z();
} else {
if (sscanf(offset_str.c_str(), "%lf;%lf;%lf", &offset[0], &offset[1], &offset[2]) != 3) {
std::cerr << "[Error] Invalid use of --model-offset\n";
print_options(serializer_options);
return 1;
}
}
std::stringstream msg;
msg << "Using model offset (" << offset[0] << "," << offset[1] << "," << offset[2] << ")";
Logger::Notice(msg.str());
}
Logger::Status("Creating geometry...");
// 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
// -BRepElement pointer, based on current settings. (see IfcGeomIterator.h
// 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
// non-null return value guarantees that a successfully processed product is
// available.
size_t num_created = 0;
do {
IfcGeom::Element *geom_object = context_iterator.get();
if (is_tesselated) {
serializer->write(static_cast*>(geom_object));
} else {
serializer->write(static_cast*>(geom_object));
}
const int progress = context_iterator.progress() / 2;
if (old_progress != progress) Logger::ProgressBar(progress);
old_progress = progress;
} while (++num_created, context_iterator.next());
Logger::Status("\rDone creating geometry (" + boost::lexical_cast(num_created) +
" objects) ");
serializer->finalize();
delete serializer;
// 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.
bool successful = rename_file(output_temp_filename, output_filename);
if (!successful) {
Logger::Error("Unable to write output file '" + output_filename + "'");
}
write_log();
time(&end);
int seconds = (int)difftime(end, start);
std::stringstream msg;
int minutes = seconds / 60;
seconds = seconds % 60;
msg << "\nConversion took";
if (minutes > 0) {
msg << " " << minutes << " minute";
if (minutes > 1) {
msg << "s";
}
}
msg << " " << seconds << " second";
if (seconds > 1) {
msg << "s";
}
Logger::Status(msg.str());
return successful ? EXIT_SUCCESS : EXIT_FAILURE;
}
void write_log() {
std::string log = log_stream.str();
if (!log.empty()) {
std::cout << "\nLog:\n" << log << std::endl;
}
}
void parse_filter(geom_filter &filter, const std::vector& values)
{
if (values.size() == 0) {
throw po::validation_error(po::validation_error::at_least_one_value_required);
}
std::string type = *values.begin();
if (type == "entities") {
filter.type = geom_filter::ENTITY_TYPE;
} else if (type == "layers") {
filter.type = geom_filter::LAYER_NAME;
} else if (type == "arg") {
filter.type = geom_filter::ENTITY_ARG;
filter.arg = *(values.begin() + 1);
if (std::find(supported_args.begin(), supported_args.end(), filter.arg) == supported_args.end()) {
throw po::validation_error(po::validation_error::invalid_option_value);
}
} else {
throw po::validation_error(po::validation_error::invalid_option_value);
}
filter.values.insert(values.begin() + (filter.type == geom_filter::ENTITY_ARG ? 2 : 1), values.end());
}
void validate(boost::any& v, const std::vector& values, inclusion_filter*, int)
{
/// @todo For now only single --include or --exclude supported. Support having multiple.
po::validators::check_first_occurrence(v);
inclusion_filter filter;
parse_filter(filter, values);
v = filter;
}
void validate(boost::any& v, const std::vector& values, inclusion_traverse_filter*, int)
{
po::validators::check_first_occurrence(v);
inclusion_traverse_filter filter;
parse_filter(filter, values);
v = filter;
}
void validate(boost::any& v, const std::vector& values, exclusion_filter*, int)
{
po::validators::check_first_occurrence(v);
exclusion_filter filter;
parse_filter(filter, values);
v = filter;
}
void validate(boost::any& v, const std::vector& values, exclusion_traverse_filter*, int)
{
po::validators::check_first_occurrence(v);
exclusion_traverse_filter filter;
parse_filter(filter, values);
v = filter;
}
/// @todo Clean up this filter initialization code further.
/// @return References to the used filter functors, if none an error occurred.
std::vector setup_filters(const std::vector& filters, const std::string& output_extension)
{
std::vector filter_funcs;
foreach(const geom_filter& f, filters) {
if (f.type == geom_filter::ENTITY_TYPE) {
entity_filter.include = f.include;
entity_filter.traverse = f.traverse;
try {
entity_filter.populate(f.values);
} catch (const IfcParse::IfcException& e) {
std::cerr << "[Error] " << e.what() << std::endl;
return std::vector();
}
} 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) {
if (f.arg == GUID_ARG) {
guid_filter.include = f.include;
guid_filter.traverse = f.traverse;
guid_filter.populate(f.values);
} else if (f.arg == NAME_ARG) {
name_filter.include = f.include;
name_filter.traverse = f.traverse;
name_filter.populate(f.values);
} else if (f.arg == DESC_ARG) {
desc_filter.include = f.include;
desc_filter.traverse = f.traverse;
desc_filter.populate(f.values);
} else if (f.arg == TAG_ARG) {
tag_filter.include = f.include;
tag_filter.traverse = f.traverse;
tag_filter.populate(f.values);
}
}
}
// If no entity names are specified these are the defaults to skip from output
if (entity_filter.values.empty()) {
try {
std::set entities;
entities.insert("IfcSpace");
if (output_extension == ".svg") {
entity_filter.include = true;
} else {
entities.insert("IfcOpeningElement");
}
entity_filter.populate(entities);
} catch (const IfcParse::IfcException& e) {
std::cerr << "[Error] " << e.what() << std::endl;
return std::vector();
}
}
if (!layer_filter.values.empty()) { filter_funcs.push_back(boost::ref(layer_filter)); }
if (!entity_filter.values.empty()) { filter_funcs.push_back(boost::ref(entity_filter)); }
if (!guid_filter.values.empty()) { filter_funcs.push_back(boost::ref(guid_filter)); }
if (!name_filter.values.empty()) { filter_funcs.push_back(boost::ref(name_filter)); }
if (!desc_filter.values.empty()) { filter_funcs.push_back(boost::ref(desc_filter)); }
if (!tag_filter.values.empty()) { filter_funcs.push_back(boost::ref(tag_filter)); }
return filter_funcs;
}