diff --git a/src/ifcconvert/IfcConvert.cpp b/src/ifcconvert/IfcConvert.cpp index 847f47ba95..af0baf5cd6 100644 --- a/src/ifcconvert/IfcConvert.cpp +++ b/src/ifcconvert/IfcConvert.cpp @@ -175,9 +175,9 @@ int main(int argc, char** argv) { "Specifies whether to enable the slicing of products according " "to their associated IfcMaterialLayerSet.") ("include", - "Specifies that the entities listed after --entities or --names are to be included") + "Specifies that the entities and/or names listed after --entities and/or --names are to be included") ("exclude", - "Specifies that the entities listed after --entities or --names are to be excluded") + "Specifies that the entities and/or names listed after --entities and/or --names are to be excluded") ("entities", boost::program_options::value< std::vector >(&entity_vector)->multitoken(), "A list of entities that should be included in or excluded from the " "geometrical output, depending on whether --exclude or --include is specified. " @@ -196,7 +196,11 @@ int main(int argc, char** argv) { "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."); + "Not guaranteed to work properly if used with --weld-vertices.") + ("traverse", + "Applies --include or --exclude also to the decomposition and/or containment (IsDecomposedBy, " + "HasOpenings, FillsVoid, ContainedInStructure) of the filtered entity, e.g. " + "--include --traverse --names \"Level 1\" includes entity with name \"Level 1\" and all of its children."); std::string bounds; boost::program_options::options_description serializer_options("Serialization options"); @@ -266,7 +270,8 @@ int main(int argc, char** argv) { const bool merge_boolean_operands = vmap.count("merge-boolean-operands") != 0; #endif const bool disable_opening_subtractions = vmap.count("disable-opening-subtractions") != 0; - bool include_entities = vmap.count("include") != 0; + bool include_entities = vmap.count("include") != 0 && !entity_vector.empty(); + const bool include_names = vmap.count("include") != 0 && !names.empty(); 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; @@ -276,6 +281,7 @@ int main(int argc, char** argv) { const bool no_normals = vmap.count("no-normals") != 0 ; bool center_model = vmap.count("center-model") != 0 ; const bool generate_uvs = vmap.count("generate-uvs") != 0 ; + const bool traverse = vmap.count("traverse") != 0; const bool deflection_tolerance_specified = vmap.count("deflection-tolerance") != 0 ; int bounding_width = -1, bounding_height = -1; @@ -377,6 +383,7 @@ int main(int argc, char** argv) { settings.set(IfcGeom::IteratorSettings::NO_NORMALS, no_normals); settings.set(IfcGeom::IteratorSettings::CENTER_MODEL, center_model); settings.set(IfcGeom::IteratorSettings::GENERATE_UVS, generate_uvs); + settings.set(IfcGeom::IteratorSettings::TRAVERSE, traverse); if (deflection_tolerance_specified) { settings.set_deflection_tolerance(deflection_tolerance); } @@ -432,16 +439,20 @@ int main(int argc, char** argv) { try { if (include_entities) { context_iterator.includeEntities(entities); - context_iterator.include_entity_names(names); } else { context_iterator.excludeEntities(entities); - context_iterator.exclude_entity_names(names); } } catch (const IfcParse::IfcException& e) { std::cout << "[Error] " << e.what() << std::endl; return 1; } + if (include_names) { + context_iterator.include_entity_names(names); + } else { + context_iterator.exclude_entity_names(names); + } + if (!serializer->ready()) { Logger::Message(Logger::LOG_ERROR, "Unable to open output '" + output_filename + "' file for writing"); write_log(); @@ -452,6 +463,8 @@ int main(int argc, char** argv) { time(&start); if (!context_iterator.initialize()) { + /// @todo It would be nice to know and print separate error prints for a case where we failed to parse + /// the file and for a case where we found no entities that satisfy our filtering criteria. Logger::Message(Logger::LOG_ERROR, "Unable to parse input file '" + input_filename + "' or no geometrical entities found"); write_log(); return 1; @@ -516,7 +529,7 @@ int main(int argc, char** argv) { // 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::Message(Logger::LOG_ERROR, "Unable to write output file '" + output_filename + ""); + Logger::Message(Logger::LOG_ERROR, "Unable to write output file '" + output_filename + "'"); } write_log(); diff --git a/src/ifcgeom/IfcGeomIterator.h b/src/ifcgeom/IfcGeomIterator.h index 90e65cd1c5..b9c50730f1 100644 --- a/src/ifcgeom/IfcGeomIterator.h +++ b/src/ifcgeom/IfcGeomIterator.h @@ -138,6 +138,7 @@ namespace IfcGeom { std::set names_to_include_or_exclude; // regex containing a name or a wildcard expression std::set entities_to_include_or_exclude; bool include_entities_in_processing; + bool include_names_in_processing_; void populate_set(const std::set& include_or_ignore) { entities_to_include_or_exclude.clear(); @@ -335,7 +336,7 @@ namespace IfcGeom { names_to_include_or_exclude.clear(); foreach(const std::string &name, names) names_to_include_or_exclude.insert(wildcard_string_to_regex(name)); - include_entities_in_processing = true; + include_names_in_processing_ = true; } /// @note Arbitrary names or wildcard expressions are handled case-sensitively. @@ -344,7 +345,7 @@ namespace IfcGeom { names_to_include_or_exclude.clear(); foreach(const std::string &name, names) names_to_include_or_exclude.insert(wildcard_string_to_regex(name)); - include_entities_in_processing = false; + include_names_in_processing_ = false; } static boost::regex wildcard_string_to_regex(std::string str) @@ -530,27 +531,64 @@ namespace IfcGeom { } } - // Filter the products based on the set of entities being included or excluded for - // processing. The set is iterated over to able to filter on subtypes. - for ( IfcSchema::IfcProduct::list::it jt = unfiltered_products->begin(); jt != unfiltered_products->end(); ++jt ) { - bool found = false; - for (std::set::const_iterator kt = entities_to_include_or_exclude.begin(); kt != entities_to_include_or_exclude.end(); ++kt) { - if ((*jt)->is(*kt)) { - found = true; - break; - } - } - - foreach(const boost::regex& r, names_to_include_or_exclude) { - if (boost::regex_match((*jt)->Name(), r)) { - found = true; + // Filter the products based on the set of entities and/or names being included or excluded for processing. + // If traversal requested, traverse to the parents to see if they satisfy the criteria. E.g. we might be looking for + // children of a storey named "Level 20", or children of entities that have no representation, e.g. IfcCurtainWall. + const bool traverse = settings.get(IteratorSettings::TRAVERSE); + for (IfcSchema::IfcProduct::list::it jt = unfiltered_products->begin(); jt != unfiltered_products->end(); ++jt) { + IfcSchema::IfcProduct* prod = *jt; + bool type_found = false; + // The set is iterated over to able to filter on subtypes. + foreach(IfcSchema::Type::Enum type, entities_to_include_or_exclude) { + if (prod->is(type)) { + type_found = true; break; } } - - if (found == include_entities_in_processing) { - ifcproducts->push(*jt); - } + + if (!type_found && traverse) { + foreach(IfcSchema::Type::Enum type, entities_to_include_or_exclude) { + IfcSchema::IfcProduct* parent, * current = prod; + while ((parent = static_cast(kernel.get_decomposing_entity(current))) != 0) { + if (parent->is(type)) { + type_found = true; + break; + } + current = parent; + } + if (type_found) { + break; + } + } + } + + bool name_found = false; + foreach(const boost::regex& r, names_to_include_or_exclude) { + if (prod->hasName() && boost::regex_match(prod->Name(), r)) { + name_found = true; + break; + } + } + + if (!name_found && traverse) { + foreach(const boost::regex& r, names_to_include_or_exclude) { + IfcSchema::IfcProduct* parent, *current = prod; + while ((parent = static_cast(kernel.get_decomposing_entity(current))) != 0) { + if (parent->hasName() && boost::regex_match(parent->Name(), r)) { + name_found = true; + break; + } + current = parent; + } + if (name_found) { + break; + } + } + } + + if (type_found == include_entities_in_processing && name_found == include_names_in_processing_) { + ifcproducts->push(prod); + } } ifcproduct_iterator = ifcproducts->begin(); @@ -704,7 +742,8 @@ namespace IfcGeom { // Upon initialisation, the (empty) set of entity names, // should be excluded, or no products would be processed. include_entities_in_processing = false; - + include_names_in_processing_ = false; + unit_name = "METER"; unit_magnitude = 1.f; diff --git a/src/ifcgeom/IfcGeomIteratorSettings.h b/src/ifcgeom/IfcGeomIteratorSettings.h index 6c3608dc6b..e3fcd353b9 100644 --- a/src/ifcgeom/IfcGeomIteratorSettings.h +++ b/src/ifcgeom/IfcGeomIteratorSettings.h @@ -83,15 +83,19 @@ namespace IfcGeom /// Applicable for OBJ and DAE output. USE_MATERIAL_NAMES = 1 << 14, /// Centers the models upon serialization by the applying the center point of - /// the scene bounds as an offset. Applicable only for DAE output currently. + /// the scene bounds as an offset. Applicable for OBJ and DAE output currently. CENTER_MODEL = 1 << 15, /// Generates UVs by using simple box projection. Requires normals. - /// Applicable only for DAE output currently. + /// Applicable for OBJ and DAE output. GENERATE_UVS = 1 << 16, /// Specifies whether to slice representations according to associated IfcLayerSets. APPLY_LAYERSETS = 1 << 17, + /// Marks that include/exclude filtering should be applied also to the decomposition + /// and/or containment (IsDecomposedBy, HasOpenings, FillsVoid, ContainedInStructure) + /// of the filtered entity. + TRAVERSE = 1 << 18, /// Number of different setting flags. - NUM_SETTINGS = 17 + NUM_SETTINGS = 18 }; /// Used to store logical OR combination of setting flags. typedef unsigned SettingField;