diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt index 57bd7fafa1..c7f175fce7 100644 --- a/cmake/CMakeLists.txt +++ b/cmake/CMakeLists.txt @@ -425,6 +425,10 @@ IF(MSVC) # Disable overeager and false positives causing C4458 ("declaration of 'indentifier' hides class member"), at least for now. ADD_DEFINITIONS(-wd4458) ENDIF() + # Enforce standards-conformance on VS > 2015, older Boost versions fail to compile with this + if (MSVC_VERSION GREATER 1900 AND (Boost_MAJOR_VERSION GREATER 1 OR Boost_MINOR_VERSION GREATER 66)) + add_definitions(-permissive-) + endif() # Link against the static VC runtime # TODO Make this configurable IF("$ENV{CONDA_BUILD}" STREQUAL "") diff --git a/nix/build-all.py b/nix/build-all.py index 6ecd4e991a..1061994f75 100644 --- a/nix/build-all.py +++ b/nix/build-all.py @@ -273,7 +273,8 @@ CMAKE_VERSION_2=CMAKE_VERSION[:CMAKE_VERSION.rindex('.')] OCE_LOCATION="https://github.com/tpaviot/oce/archive/OCE-%s.tar.gz" % (OCE_VERSION,) BOOST_LOCATION="http://downloads.sourceforge.net/project/boost/boost/%s/boost_%s.tar.bz2" % (BOOST_VERSION, BOOST_VERSION_UNDERSCORE) OPENCOLLADA_LOCATION="https://github.com/KhronosGroup/OpenCOLLADA.git" -OPENCOLLADA_COMMIT="f99d59e73e565a41715eaebc00c7664e1ee5e628" +#OPENCOLLADA_COMMIT="f99d59e73e565a41715eaebc00c7664e1ee5e628" +OPENCOLLADA_COMMIT="v1.6.63" # Helper functions diff --git a/src/ifcconvert/IfcConvert.cpp b/src/ifcconvert/IfcConvert.cpp index 0bf0978bcb..dead7c14c8 100644 --- a/src/ifcconvert/IfcConvert.cpp +++ b/src/ifcconvert/IfcConvert.cpp @@ -35,6 +35,7 @@ #include "../ifcgeom_schema_agnostic/IfcGeomFilter.h" #include "../ifcgeom_schema_agnostic/IfcGeomIterator.h" +#include "../ifcgeom_schema_agnostic/IfcGeomRenderStyles.h" #include @@ -43,6 +44,7 @@ #endif #include +#include #include #include @@ -177,6 +179,7 @@ int main(int argc, char** argv) exclusion_filter exclude_filter; exclusion_traverse_filter exclude_traverse_filter; std::string filter_filename; + std::string default_material_filename; po::options_description geom_options("Geometry options"); geom_options.add_options() @@ -257,8 +260,10 @@ 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.") + ("default-material-file", po::value(&default_material_filename), + "Specifies a material file that describes the material object types will have" + "if an object does not have any specified material in the IFC file."); std::string bounds, offset_str; #ifdef HAVE_ICU @@ -488,7 +493,18 @@ int main(int argc, char** argv) return EXIT_FAILURE; } } - + + if (!default_material_filename.empty()) { + try { + IfcGeom::set_default_style_file(default_material_filename); + } catch (const std::exception& e) { + std::cerr << "[Error] Could not read default material file " << default_material_filename << ":" << std::endl; + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + } + + /// @todo Clean up this filter code further. std::vector used_filters; if (include_filter.type != geom_filter::UNUSED) { used_filters.push_back(include_filter); } @@ -535,7 +551,7 @@ int main(int argc, char** argv) settings.set_deflection_tolerance(deflection_tolerance); settings.precision = precision; - GeometrySerializer* serializer; + boost::shared_ptr serializer; /**< @todo use std::unique_ptr when possible */ 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"); @@ -543,28 +559,28 @@ int main(int argc, char** argv) 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); + serializer = boost::make_shared(output_temp_filename, mtl_filename, settings); #ifdef WITH_OPENCOLLADA } else if (output_extension == ".dae") { - serializer = new ColladaSerializer(output_temp_filename, settings); + serializer = boost::make_shared(output_temp_filename, settings); #endif } else if (output_extension == ".stp") { - serializer = new StepSerializer(output_temp_filename, settings); + serializer = boost::make_shared(output_temp_filename, settings); } else if (output_extension == ".igs") { #if OCC_VERSION_HEX < 0x60900 // According to https://tracker.dev.opencascade.org/view.php?id=25689 something has been fixed in 6.9.0 IGESControl_Controller::Init(); // work around Open Cascade bug #endif - serializer = new IgesSerializer(output_temp_filename, settings); + serializer = boost::make_shared(output_temp_filename, settings); } else if (output_extension == ".svg") { settings.set(IfcGeom::IteratorSettings::DISABLE_TRIANGULATION, true); - serializer = new SvgSerializer(output_temp_filename, settings); + serializer = boost::make_shared(output_temp_filename, settings); if (vmap.count("section-height") != 0) { Logger::Notice("Overriding section height"); - static_cast(serializer)->setSectionHeight(section_height); + static_cast(serializer.get())->setSectionHeight(section_height); } if (bounding_width.is_initialized() && bounding_height.is_initialized()) { - static_cast(serializer)->setBoundingRectangle(bounding_width.get(), bounding_height.get()); + static_cast(serializer.get())->setBoundingRectangle(bounding_width.get(), bounding_height.get()); } } else { std::cerr << "[Error] Unknown output filename extension '" + output_extension + "'\n"; @@ -573,13 +589,11 @@ int main(int argc, char** argv) return EXIT_FAILURE; } - // NOTE After this point, make sure to delete serializer upon application exit. - if (use_element_hierarchy && output_extension != ".dae") { std::cerr << "[Error] --use-element-hierarchy can be used only with .dae output.\n"; + /// @todo Lots of duplicate error-and-exit code. write_log(!quiet); print_usage(); - delete serializer; std::remove(output_temp_filename.c_str()); /**< @todo Windows Unicode support */ return EXIT_FAILURE; } @@ -600,7 +614,6 @@ int main(int argc, char** argv) } if (!serializer->ready()) { - delete serializer; std::remove(output_temp_filename.c_str()); /**< @todo Windows Unicode support */ write_log(!quiet); return EXIT_FAILURE; @@ -610,6 +623,8 @@ int main(int argc, char** argv) time(&start); if (!init_input_file(input_filename, ifc_file, no_progress || quiet, mmap)) { + write_log(!quiet); + std::remove(output_temp_filename.c_str()); /**< @todo Windows Unicode support */ return EXIT_FAILURE; } @@ -618,7 +633,6 @@ int main(int argc, char** argv) /// @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"); - delete serializer; std::remove(output_temp_filename.c_str()); /**< @todo Windows Unicode support */ write_log(!quiet); return EXIT_FAILURE; @@ -641,7 +655,6 @@ int main(int argc, char** argv) if (center_model) { if (site_local_placement || building_local_placement) { Logger::Error("Cannot use --center-model together with --{site,building}-local-placement"); - delete serializer; return EXIT_FAILURE; } @@ -658,7 +671,6 @@ int main(int argc, char** argv) } 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"; - delete serializer; std::remove(output_temp_filename.c_str()); /**< @todo Windows Unicode support */ print_options(serializer_options); return EXIT_FAILURE; @@ -732,7 +744,8 @@ int main(int argc, char** argv) } serializer->finalize(); - delete serializer; + // Make sure the dtor is explicitly run here (e.g. output files are closed before renaming them). + serializer.reset(); // 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. diff --git a/src/ifcgeom/IfcGeomFunctions.cpp b/src/ifcgeom/IfcGeomFunctions.cpp index 5db4491b62..d402489db3 100644 --- a/src/ifcgeom/IfcGeomFunctions.cpp +++ b/src/ifcgeom/IfcGeomFunctions.cpp @@ -387,7 +387,18 @@ bool IfcGeom::Kernel::create_solid_from_faces(const TopTools_ListOfShape& face_l try { builder.Perform(); shape = builder.SewedShape(); - valid_shell = BRepCheck_Analyzer(shape).IsValid() != 0 && count(shape, TopAbs_SHELL) > 0; + + { + BRepCheck_Analyzer ana(shape); + if (!ana.IsValid()) { + ShapeFix_Shape sfs(shape); + sfs.Perform(); + shape = sfs.Shape(); + } + } + + BRepCheck_Analyzer ana(shape); + valid_shell = ana.IsValid() != 0 && count(shape, TopAbs_SHELL) > 0; } catch (const Standard_Failure& e) { if (e.GetMessageString() && strlen(e.GetMessageString())) { Logger::Error(e.GetMessageString()); diff --git a/src/ifcgeom/IfcGeomIteratorImplementation.h b/src/ifcgeom/IfcGeomIteratorImplementation.h index b1bf97a544..e275db1c39 100644 --- a/src/ifcgeom/IfcGeomIteratorImplementation.h +++ b/src/ifcgeom/IfcGeomIteratorImplementation.h @@ -270,10 +270,15 @@ namespace IfcGeom { } if (representations->size() == 0) { - Logger::Message(Logger::LOG_ERROR, "No geometries found"); - return false; + Logger::Message(Logger::LOG_ERROR, "No representations encountered in relevant contexts, using all"); + representations = ifc_file->instances_by_type(); } + if (representations->size() == 0) { + Logger::Message(Logger::LOG_ERROR, "No representations encountered, aborting"); + return false; + } + representation_iterator = representations->begin(); ifcproducts.reset(); @@ -402,19 +407,30 @@ namespace IfcGeom { for (;;) { IfcSchema::IfcRepresentation* representation; - // Have we reached the end of our list of representations? if ( representation_iterator == representations->end() ) { representations.reset(); - return 0; + return 0; // reached the end of our list of representations } representation = *representation_iterator; - // Has the list of IfcProducts for this representation been initialized? if (!ifcproducts) { + // Init. the list of filtered IfcProducts for this representation ifcproducts = IfcSchema::IfcProduct::list::ptr(new IfcSchema::IfcProduct::list); IfcSchema::IfcProduct::list::ptr unfiltered_products = kernel.products_represented_by(representation); + // Include only the desired products for processing. + for (IfcSchema::IfcProduct::list::it jt = unfiltered_products->begin(); jt != unfiltered_products->end(); ++jt) { + IfcSchema::IfcProduct* prod = *jt; + if (boost::all(filters_, filter_match(prod))) { + ifcproducts->push(prod); + } + } - geometry_reuse_ok_for_current_representation_ = reuse_ok_(unfiltered_products); + if (ifcproducts->size() == 0) { + _nextShape(); + continue; + } + + geometry_reuse_ok_for_current_representation_ = reuse_ok_(ifcproducts); IfcSchema::IfcRepresentationMap::list::ptr maps = representation->RepresentationMap(); @@ -432,13 +448,12 @@ namespace IfcGeom { } } + // Check if this represenation has (or will be) processed as part its mapped representation bool representation_processed_as_mapped_item = false; - - IfcSchema::IfcRepresentation* representation_mapped_to = kernel.representation_mapped_to(representation); + IfcSchema::IfcRepresentation* representation_mapped_to = kernel.representation_mapped_to(representation); if (representation_mapped_to) { - // Check if this representation has (or will be) processed as part its mapped representation - representation_processed_as_mapped_item = ok_mapped_representations->contains(representation_mapped_to) || - reuse_ok_(kernel.products_represented_by(representation_mapped_to)); + representation_processed_as_mapped_item = geometry_reuse_ok_for_current_representation_ || + ok_mapped_representations->contains(representation_mapped_to); } if (representation_processed_as_mapped_item) { @@ -447,14 +462,6 @@ namespace IfcGeom { continue; } - // Filter the products based on the set of entities and/or names being included or excluded for processing. - for (IfcSchema::IfcProduct::list::it jt = unfiltered_products->begin(); jt != unfiltered_products->end(); ++jt) { - IfcSchema::IfcProduct* prod = *jt; - if (boost::all(filters_, filter_match(prod))) { - ifcproducts->push(prod); - } - } - ifcproduct_iterator = ifcproducts->begin(); } diff --git a/src/ifcgeom_schema_agnostic/IfcGeomRenderStyles.h b/src/ifcgeom_schema_agnostic/IfcGeomRenderStyles.h index 7ba1440207..804e1caa12 100644 --- a/src/ifcgeom_schema_agnostic/IfcGeomRenderStyles.h +++ b/src/ifcgeom_schema_agnostic/IfcGeomRenderStyles.h @@ -94,7 +94,8 @@ namespace IfcGeom { boost::optional& Specularity() { return specularity; } }; - IFC_GEOM_API const SurfaceStyle* get_default_style(const std::string& ifc_type); + IFC_GEOM_API const SurfaceStyle* get_default_style(const std::string& ifc_type); + IFC_GEOM_API void set_default_style_file(const std::string& json_file); } #endif diff --git a/src/ifcgeom_schema_agnostic/SurfaceStyle.cpp b/src/ifcgeom_schema_agnostic/SurfaceStyle.cpp index e59f30e575..c3db9c3756 100644 --- a/src/ifcgeom_schema_agnostic/SurfaceStyle.cpp +++ b/src/ifcgeom_schema_agnostic/SurfaceStyle.cpp @@ -1,7 +1,12 @@ #include "../ifcgeom_schema_agnostic/IfcGeomRenderStyles.h" +#include +#include + #include +namespace pt = boost::property_tree; + static std::map default_materials; static IfcGeom::SurfaceStyle default_material; static bool default_materials_initialized = false; @@ -44,12 +49,72 @@ void InitDefaultMaterials() { default_materials_initialized = true; } +boost::optional read_colour_component(const boost::optional list) { + if (!list) { + return boost::none; + } + double rgb[3]; + int i = 0; + for (pt::ptree::value_type &colour : list.get()) { + if (3 <= i) { + throw std::runtime_error("rgb array over 3 elements large"); + } + rgb[i] = colour.second.get_value(); + i++; + } + if (i != 3) { + throw std::runtime_error("rgb array less than 3 elements large (was " + std::to_string(i) + ")"); + } + return IfcGeom::SurfaceStyle::ColorComponent(rgb[0], rgb[1], rgb[2]); +} + +void IfcGeom::set_default_style_file(const std::string& json_file) { + if (!default_materials_initialized) InitDefaultMaterials(); + default_materials.clear(); + + pt::ptree root; + pt::read_json(json_file, root); + + for (pt::ptree::value_type &material_pair : root) { + std::string name = material_pair.first; + default_materials.insert(std::make_pair(name, IfcGeom::SurfaceStyle(name))); + + pt::ptree material = material_pair.second; + boost::optional diffuse = material.get_child_optional("diffuse"); + default_materials[name].Diffuse() = read_colour_component(diffuse); + + boost::optional specular = material.get_child_optional("specular"); + default_materials[name].Specular() = read_colour_component(specular); + + if (material.get_child_optional("specular-roughness")) { + default_materials[name].Specularity().reset(1.0 / material.get("specular-roughness")); + } + if (material.get_child_optional("transparency")) { + default_materials[name].Transparency() = material.get("transparency"); + } + } + + // Is "*" present? If yes, remove it and make it the default style. + std::map::const_iterator it = default_materials.find("*"); + if (it != default_materials.end()) { + IfcGeom::SurfaceStyle star = it->second; + default_material.Diffuse() = star.Diffuse(); + default_material.Specular() = star.Specular(); + default_material.Specularity() = star.Specularity(); + default_material.Transparency() = star.Transparency(); + default_materials.erase(it); + } +} + const IfcGeom::SurfaceStyle* IfcGeom::get_default_style(const std::string& s) { if (!default_materials_initialized) InitDefaultMaterials(); std::map::const_iterator it = default_materials.find(s); if (it == default_materials.end()) { default_materials.insert(std::make_pair(s, IfcGeom::SurfaceStyle(s))); - default_materials[s].Diffuse().reset(*default_material.Diffuse()); + default_materials[s].Diffuse() = default_material.Diffuse(); + default_materials[s].Specular() = default_material.Specular(); + default_materials[s].Specularity() = default_material.Specularity(); + default_materials[s].Transparency() = default_material.Transparency(); it = default_materials.find(s); } const IfcGeom::SurfaceStyle& surface_style = it->second; diff --git a/src/ifcparse/IfcParse.cpp b/src/ifcparse/IfcParse.cpp index 810227b616..0e9f40ad07 100644 --- a/src/ifcparse/IfcParse.cpp +++ b/src/ifcparse/IfcParse.cpp @@ -1320,8 +1320,6 @@ void IfcFile::initialize_(IfcParse::IfcSpfStream* s) { return; } - good_ = true; - tokens = new IfcSpfLexer(stream, this); _header.file(this); _header.tryRead(); @@ -1339,18 +1337,16 @@ void IfcFile::initialize_(IfcParse::IfcSpfStream* s) { } catch (const IfcParse::IfcException& e) { Logger::Error(e); } - - if (schema_ == 0 && schemas.front().substr(0, 4) == "IFC2") { - Logger::Message(Logger::LOG_ERROR, schemas.front() + " not supported, using IFC2X3 instead"); - schema_ = IfcParse::schema_by_name("IFC2X3"); - } } if (schema_ == 0) { - schema_ = IfcParse::schema_by_name("IFC4"); - Logger::Message(Logger::LOG_ERROR, "Unable to deduce schema version from header identifiers, defaulting to IFC4"); + Logger::Message(Logger::LOG_ERROR, "No support for file schema encountered (" + + boost::algorithm::join(schemas, ", ") + ")"); + return; } + good_ = true; + ifcroot_type_ = schema_->declaration_by_name("IfcRoot"); boost::circular_buffer token_stream(3, Token());