/******************************************************************************** * * * 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 . * * * ********************************************************************************/ /******************************************************************************** * * * Implementations of the various conversion functions defined in IfcGeom.h * * * ********************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if OCC_VERSION_HEX >= 0x70200 #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if OCC_VERSION_HEX >= 0x70200 #include #endif #include "../ifcparse/macros.h" #include "../ifcparse/IfcSIPrefix.h" #include "../ifcparse/IfcFile.h" #include "../ifcgeom/IfcGeom.h" #include "../ifcgeom_schema_agnostic/IfcGeomTree.h" #include "../ifcgeom_schema_agnostic/boolean_utils.h" #include "../ifcgeom_schema_agnostic/wire_utils.h" #include "../ifcgeom_schema_agnostic/base_utils.h" #include "../ifcgeom_schema_agnostic/layerset.h" #include #include #if OCC_VERSION_HEX < 0x60900 #ifdef _MSC_VER #pragma message("warning: You are linking against Open CASCADE version " OCC_VERSION_COMPLETE ". Version 6.9.0 introduces various improvements with relation to boolean operations. You are advised to upgrade.") #else #warning "You are linking against an older version of Open CASCADE. Version 6.9.0 introduces various improvements with relation to boolean operations. You are advised to upgrade." #endif #endif namespace { struct MAKE_TYPE_NAME(factory_t) { IfcGeom::Kernel* operator()(IfcParse::IfcFile* file) const { IfcGeom::MAKE_TYPE_NAME(Kernel)* k = new IfcGeom::MAKE_TYPE_NAME(Kernel); if (file) { double unit_magnitude = 1.; // Set unit information from file IfcSchema::IfcProject::list::ptr projects = file->instances_by_type(); if (projects->size() == 1) { IfcSchema::IfcProject* project = *projects->begin(); std::pair unit_info = k->initializeUnits(project->UnitsInContext()); unit_magnitude = unit_info.second; } else { Logger::Warning("A single IfcProject is expected (encountered " + boost::lexical_cast(projects->size()) + "); unable to read unit information."); } // Set precision from file double lowest_precision_encountered = std::numeric_limits::infinity(); bool any_precision_encountered = false; IfcSchema::IfcGeometricRepresentationContext::list::it it; IfcSchema::IfcGeometricRepresentationContext::list::ptr contexts = file->instances_by_type_excl_subtypes(); for (it = contexts->begin(); it != contexts->end(); ++it) { IfcSchema::IfcGeometricRepresentationContext* context = *it; if (context->Precision() && (*context->Precision() * unit_magnitude * 10.) < lowest_precision_encountered) { // Some arbitrary factor that has proven to work better for the models in the set of test files. lowest_precision_encountered = *context->Precision() * unit_magnitude * 10.; any_precision_encountered = true; } } double precision_to_set = 1.e-5; if (any_precision_encountered) { if (lowest_precision_encountered < 1.e-7) { Logger::Message(Logger::LOG_WARNING, "Precision lower than 0.0000001 meter not enforced"); precision_to_set = 1.e-7; } else { precision_to_set = lowest_precision_encountered; } } k->setValue(IfcGeom::Kernel::GV_PRECISION, precision_to_set); } return k; } }; } void MAKE_INIT_FN(KernelImplementation_)(IfcGeom::impl::KernelFactoryImplementation* mapping) { static const std::string schema_name = STRINGIFY(IfcSchema); MAKE_TYPE_NAME(factory_t) factory; mapping->bind(schema_name, factory); } #define Kernel MAKE_TYPE_NAME(Kernel) void IfcGeom::Kernel::set_offset(const std::array &p_offset) { offset = gp_Vec(p_offset[0], p_offset[1], p_offset[2]); offset_and_rotation = util::combine_offset_and_rotation(offset, rotation); } void IfcGeom::Kernel::set_rotation(const std::array &p_rotation) { rotation = gp_Quaternion(p_rotation[0], p_rotation[1], p_rotation[2], p_rotation[3]); offset_and_rotation = util::combine_offset_and_rotation(offset, rotation); } namespace { struct opening_sorter { bool operator()(const std::pair& a, const std::pair& b) const { return a.first > b.first; } }; } bool IfcGeom::Kernel::convert_openings(const IfcSchema::IfcProduct* entity, const IfcSchema::IfcRelVoidsElement::list::ptr& openings, const IfcGeom::IfcRepresentationShapeItems& entity_shapes, const gp_Trsf& entity_trsf, IfcGeom::IfcRepresentationShapeItems& cut_shapes) { util::boolean_settings bst; bst.attempt_2d = getValue(GV_BOOLEAN_ATTEMPT_2D) > 0.; bst.debug = getValue(GV_DEBUG_BOOLEAN) > 0.; bst.precision = getValue(GV_PRECISION); std::vector< std::pair > opening_vector; for (IfcSchema::IfcRelVoidsElement::list::it it = openings->begin(); it != openings->end(); ++it) { IfcSchema::IfcRelVoidsElement* v = *it; IfcSchema::IfcFeatureElementSubtraction* fes = v->RelatedOpeningElement(); if (fes->declaration().is(IfcSchema::IfcOpeningElement::Class())) { if (!fes->Representation()) continue; /* // Not yet implemented and tested, process opening placement up to parent wall // placement so that the matrix inverse can be eliminated. // @todo property check and handle the decomposition into parts (where element // carying geom and opening are in different branches). // @todo properly check whether opening correctly references wall placement // and fallback to matrix inverse when not the case. auto relative = entity; { auto ds = relative->Decomposes(); if (ds->size() == 1) { relative = (*ds->begin())->RelatingObject()->as(); } } set_conversion_placement_rel_to_instance(relative); */ // Convert the IfcRepresentation of the IfcOpeningElement gp_Trsf opening_trsf; if (fes->ObjectPlacement()) { try { convert(fes->ObjectPlacement(), opening_trsf); } catch (const std::exception& e) { Logger::Error(e); } catch (...) { Logger::Error("Failed to construct placement"); } } // set_conversion_placement_rel_to_instance(nullptr); // Move the opening into the coordinate system of the IfcProduct opening_trsf.PreMultiply(entity_trsf.Inverted()); IfcSchema::IfcProductRepresentation* prodrep = fes->Representation(); IfcSchema::IfcRepresentation::list::ptr reps = prodrep->Representations(); IfcGeom::IfcRepresentationShapeItems opening_shapes; for (IfcSchema::IfcRepresentation::list::it it2 = reps->begin(); it2 != reps->end(); ++it2) { if (IfcParse::traverse((*it2))->as()->size()) { continue; } convert_shapes(*it2, opening_shapes); } for (unsigned int i = 0; i < opening_shapes.size(); ++i) { TopoDS_Shape opening_shape_solid; const TopoDS_Shape& opening_shape_unlocated = util::ensure_fit_for_subtraction(opening_shapes[i].Shape(), opening_shape_solid, getValue(GV_PRECISION)); gp_GTrsf gtrsf = opening_shapes[i].Placement(); gtrsf.PreMultiply(opening_trsf); TopoDS_Shape opening_shape = util::apply_transformation(opening_shape_unlocated, gtrsf); opening_vector.push_back(std::make_pair(util::min_edge_length(opening_shape), opening_shape)); } } } std::sort(opening_vector.begin(), opening_vector.end(), opening_sorter()); // Iterate over the shapes of the IfcProduct for (IfcGeom::IfcRepresentationShapeItems::const_iterator it3 = entity_shapes.begin(); it3 != entity_shapes.end(); ++it3) { TopoDS_Compound C; BRep_Builder B; B.MakeCompound(C); TopoDS_Shape combined_result; std::list parts; bool is_multiple = it3->Shape().ShapeType() == TopAbs_COMPOUND && TopoDS_Iterator(it3->Shape()).More() && util::is_nested_compound_of_solid(it3->Shape()); if (is_multiple) { TopoDS_Iterator sit(it3->Shape()); for (; sit.More(); sit.Next()) { parts.push_back(sit.Value()); } } else { parts.push_back(it3->Shape()); } for (auto& entity_part : parts) { bool is_manifold = util::is_manifold(entity_part); if (!is_manifold) { Logger::Warning("Non-manifold first operand"); } TopoDS_Shape entity_part_result; for (int as_shell = 0; as_shell < 2; ++as_shell) { TopoDS_Shape entity_shape_solid; TopoDS_Shape entity_shape_unlocated; if (as_shell) { entity_shape_unlocated = entity_part; } else { entity_shape_unlocated = util::ensure_fit_for_subtraction(entity_part, entity_shape_solid, getValue(GV_PRECISION)); } const gp_GTrsf& entity_shape_gtrsf = it3->Placement(); if (entity_shape_gtrsf.Form() == gp_Other) { Logger::Message(Logger::LOG_WARNING, "Applying non uniform transformation to:", entity); } TopoDS_Shape entity_shape = util::apply_transformation(entity_shape_unlocated, entity_shape_gtrsf); TopoDS_Shape result = entity_shape; auto it = opening_vector.begin(); auto jt = it; for (;; ++it) { if (it == opening_vector.end() || jt->first / it->first > 10.) { TopTools_ListOfShape opening_list; for (auto kt = jt; kt < it; ++kt) { opening_list.Append(kt->second); } TopoDS_Shape intermediate_result; if (util::boolean_operation(bst, result, opening_list, BOPAlgo_CUT, intermediate_result)) { result = intermediate_result; } else { Logger::Message(Logger::LOG_ERROR, "Opening subtraction failed for " + boost::lexical_cast(std::distance(jt, it)) + " openings", entity); } jt = it; } if (it == opening_vector.end()) { break; } } int result_n_faces = util::count(result, TopAbs_FACE); if (!is_manifold && as_shell == 0 && result_n_faces == 0) { // If we have a non-manifold first operand and our first attempt // on a Solid-Solid subtraction yielded a empty result (no faces) // or a strange result, a larger number of faces with the original input // included. Then retry (another iteration on the for-loop on as-shell) // where we keep the first operand as is (a compound of faces probably, // unless --orient-shells was activated in which case we're already lost). if (!is_manifold) { Logger::Warning("Retrying boolean operation on individual faces"); } continue; } entity_part_result = result; // For manifold first operands we're not even going to try if processing // as loose faces gives a better result. break; } if (is_multiple) { B.Add(C, entity_part_result); } else { combined_result = entity_part_result; } } if (is_multiple) { combined_result = C; } cut_shapes.push_back(IfcGeom::IfcRepresentationShapeItem(it3->ItemId(), combined_result, it3->StylePtr())); } return true; } void IfcGeom::Kernel::setValue(GeomValue var, double value) { switch (var) { case GV_DEFLECTION_TOLERANCE: deflection_tolerance = value; break; case GV_LENGTH_UNIT: ifc_length_unit = value; break; case GV_PLANEANGLE_UNIT: ifc_planeangle_unit = value; break; case GV_PRECISION: modelling_precision = value; break; case GV_DIMENSIONALITY: dimensionality = value; break; case GV_MAX_FACES_TO_ORIENT: max_faces_to_orient = value; break; case GV_LAYERSET_FIRST: layerset_first = value; break; case GV_DISABLE_BOOLEAN_RESULT: disable_boolean_result = value; break; case GV_NO_WIRE_INTERSECTION_CHECK: no_wire_intersection_check = value; break; case GV_PRECISION_FACTOR: precision_factor = value; break; case GV_NO_WIRE_INTERSECTION_TOLERANCE: no_wire_intersection_tolerance = value; break; case GV_DEBUG_BOOLEAN: boolean_debug_setting = value; break; case GV_BOOLEAN_ATTEMPT_2D: boolean_attempt_2d = value; break; default: throw std::runtime_error("Invalid setting"); } } double IfcGeom::Kernel::getValue(GeomValue var) const { switch (var) { case GV_DEFLECTION_TOLERANCE: return deflection_tolerance; case GV_MINIMAL_FACE_AREA: // Considering a right-angled triangle, this about the smallest // area you can obtain without the vertices being confused. return modelling_precision * modelling_precision / 20.; case GV_POINT_EQUALITY_TOLERANCE: return modelling_precision; case GV_LENGTH_UNIT: return ifc_length_unit; case GV_PLANEANGLE_UNIT: return ifc_planeangle_unit; case GV_PRECISION: return modelling_precision; case GV_DIMENSIONALITY: return dimensionality; case GV_MAX_FACES_TO_ORIENT: return max_faces_to_orient; case GV_LAYERSET_FIRST: return layerset_first; case GV_DISABLE_BOOLEAN_RESULT: return disable_boolean_result; case GV_NO_WIRE_INTERSECTION_CHECK: return no_wire_intersection_check; case GV_PRECISION_FACTOR: return precision_factor; case GV_NO_WIRE_INTERSECTION_TOLERANCE: return no_wire_intersection_tolerance; case GV_DEBUG_BOOLEAN: return boolean_debug_setting; case GV_BOOLEAN_ATTEMPT_2D: return boolean_attempt_2d; } throw std::runtime_error("Invalid setting"); } IfcSchema::IfcRelVoidsElement::list::ptr IfcGeom::Kernel::find_openings(IfcSchema::IfcProduct* product) { std::vector rs; if ( product->declaration().is(IfcSchema::IfcElement::Class()) && !product->declaration().is(IfcSchema::IfcOpeningElement::Class()) ) { IfcSchema::IfcElement* element = (IfcSchema::IfcElement*)product; auto rels = element->HasOpenings(); rs.insert(rs.end(), rels->begin(), rels->end()); } // Is the IfcElement a decomposition of an IfcElement with any IfcOpeningElements? IfcSchema::IfcObjectDefinition* obdef = product->as(); for (;;) { auto decomposes = obdef->Decomposes(); if (decomposes->size() != 1) break; IfcSchema::IfcObjectDefinition* rel_obdef = (*decomposes->begin())->RelatingObject(); if ( rel_obdef->declaration().is(IfcSchema::IfcElement::Class()) && !rel_obdef->declaration().is(IfcSchema::IfcOpeningElement::Class()) ) { IfcSchema::IfcElement* element = (IfcSchema::IfcElement*)rel_obdef; auto rels = element->HasOpenings(); rs.insert(rs.end(), rels->begin(), rels->end()); } obdef = rel_obdef; } // Filter openings in Reference view, solely marked as Reference. IfcSchema::IfcRelVoidsElement::list::ptr openings(new IfcSchema::IfcRelVoidsElement::list); std::for_each(rs.begin(), rs.end(), [&openings](IfcSchema::IfcRelVoidsElement* rel) { if (rel->RelatedOpeningElement()->ObjectPlacement() && rel->RelatedOpeningElement()->Representation()) { auto reps = rel->RelatedOpeningElement()->Representation()->Representations(); if (!(reps->size() == 1 && (*reps->begin())->RepresentationIdentifier().get_value_or("") == "Reference")) { openings->push(rel); } } }); return openings; } const IfcSchema::IfcMaterial* IfcGeom::Kernel::get_single_material_association(const IfcSchema::IfcProduct* product) { IfcSchema::IfcMaterial* single_material = 0; IfcSchema::IfcRelAssociatesMaterial::list::ptr associated_materials = product->HasAssociations()->as(); if (associated_materials->size() == 1) { IfcSchema::IfcMaterialSelect* associated_material = (*associated_materials->begin())->RelatingMaterial(); single_material = associated_material->as(); // NB: IfcMaterialLayerSets are also considered, regardless of --enable-layerset-slicing. Picking // the first material (in accordance with other viewers) when layerset-slicing is disabled. if (!single_material && associated_material->as()) { IfcSchema::IfcMaterialLayerSet* layerset = associated_material->as()->ForLayerSet(); if (getValue(GV_LAYERSET_FIRST) > 0.0 ? layerset->MaterialLayers()->size() >= 1 : layerset->MaterialLayers()->size() == 1) { IfcSchema::IfcMaterialLayer* layer = (*layerset->MaterialLayers()->begin()); if (layer->Material()) { single_material = layer->Material(); } } } } return single_material; } IfcGeom::BRepElement* IfcGeom::Kernel::create_brep_for_representation_and_product( const IteratorSettings& settings, IfcSchema::IfcRepresentation* representation, IfcSchema::IfcProduct* product) { std::stringstream representation_id_builder; representation_id_builder << representation->data().id(); IfcGeom::Representation::BRep* shape; IfcGeom::IfcRepresentationShapeItems shapes, shapes2; if ( !convert_shapes(representation, shapes) ) { return 0; } if (settings.get(IteratorSettings::APPLY_LAYERSETS)) { TopoDS_Shape merge; if (util::flatten_shape_list(shapes, merge, false, getValue(GV_PRECISION))) { if (util::count(merge, TopAbs_FACE) > 0) { std::vector thickness; std::vector layers; std::vector< std::vector > folded_layers; std::vector> styles; if (convert_layerset(product, layers, styles, thickness)) { IfcSchema::IfcRelAssociates::list::ptr associations = product->HasAssociations(); for (IfcSchema::IfcRelAssociates::list::it it = associations->begin(); it != associations->end(); ++it) { IfcSchema::IfcRelAssociatesMaterial* associates_material = (**it).as(); if (associates_material) { unsigned layerset_id = associates_material->RelatingMaterial()->data().id(); representation_id_builder << "-layerset-" << layerset_id; break; } } if (styles.size() > 1) { // If there's only a single layer there is no need to manipulate geometries. bool success = true; if (product->as() && fold_layers(product->as(), shapes, layers, thickness, folded_layers)) { if (util::apply_folded_layerset(shapes, folded_layers, styles, shapes2, getValue(GV_PRECISION))) { std::swap(shapes, shapes2); success = true; } } else { if (util::apply_layerset(shapes, layers, styles, shapes2, getValue(GV_PRECISION))) { std::swap(shapes, shapes2); success = true; } } if (!success) { Logger::Error("Failed processing layerset"); } } } } } } bool material_style_applied = false; const IfcSchema::IfcMaterial* single_material = get_single_material_association(product); if (single_material) { auto s = get_style(single_material); for (IfcGeom::IfcRepresentationShapeItems::iterator it = shapes.begin(); it != shapes.end(); ++it) { if (!it->hasStyle() && s) { it->setStyle(s); material_style_applied = true; } } } else { bool some_items_without_style = false; for (IfcGeom::IfcRepresentationShapeItems::iterator it = shapes.begin(); it != shapes.end(); ++it) { if (!it->hasStyle() && util::count(it->Shape(), TopAbs_FACE)) { some_items_without_style = true; break; } } if (some_items_without_style) { Logger::Warning("No material and surface styles for:", product); } } if (material_style_applied) { representation_id_builder << "-material-" << single_material->data().id(); } if (settings.force_space_transparency() >= 0. && product->declaration().is("IfcSpace")) { for (auto& s : shapes) { if (s.hasStyle()) { for (auto& p : style_cache) { if (p.second == s.StylePtr()) { std::const_pointer_cast(p.second)->Transparency() = settings.force_space_transparency(); } } } } } int parent_id = -1; try { IfcUtil::IfcBaseEntity* parent_object = get_decomposing_entity(product); if (parent_object && parent_object->as()) { parent_id = parent_object->data().id(); } } catch (const std::exception& e) { Logger::Error(e); } const std::string name = product->Name().get_value_or(""); const std::string guid = product->GlobalId(); gp_Trsf trsf; try { if (product->ObjectPlacement()) { convert(product->ObjectPlacement(), trsf); } } catch (const std::exception& e) { Logger::Error(e); } catch (...) { Logger::Error("Failed to construct placement"); } // Does the IfcElement have any IfcOpenings? // Note that openings for IfcOpeningElements are not processed IfcSchema::IfcRelVoidsElement::list::ptr openings = find_openings(product); const std::string product_type = product->declaration().name(); ElementSettings element_settings(settings, getValue(GV_LENGTH_UNIT), product_type); if (!settings.get(IfcGeom::IteratorSettings::DISABLE_OPENING_SUBTRACTIONS) && openings && openings->size()) { representation_id_builder << "-openings"; for (IfcSchema::IfcRelVoidsElement::list::it it = openings->begin(); it != openings->end(); ++it) { representation_id_builder << "-" << (*it)->data().id(); } IfcGeom::IfcRepresentationShapeItems opened_shapes; bool caught_error = false; try { convert_openings(product,openings,shapes,trsf,opened_shapes); } catch (const std::exception& e) { Logger::Message(Logger::LOG_ERROR, std::string("Error processing openings for: ") + e.what() + ":", product); caught_error = true; } catch(...) { Logger::Message(Logger::LOG_ERROR,"Error processing openings for:",product); } if (caught_error && opened_shapes.size() < shapes.size()) { opened_shapes = shapes; } if (settings.get(IteratorSettings::USE_WORLD_COORDS)) { for ( IfcGeom::IfcRepresentationShapeItems::iterator it = opened_shapes.begin(); it != opened_shapes.end(); ++ it ) { it->prepend(trsf); } trsf = gp_Trsf(); representation_id_builder << "-world-coords"; } shape = new IfcGeom::Representation::BRep(element_settings, representation_id_builder.str(), opened_shapes); } else if (settings.get(IteratorSettings::USE_WORLD_COORDS)) { for ( IfcGeom::IfcRepresentationShapeItems::iterator it = shapes.begin(); it != shapes.end(); ++ it ) { it->prepend(trsf); } trsf = gp_Trsf(); representation_id_builder << "-world-coords"; shape = new IfcGeom::Representation::BRep(element_settings, representation_id_builder.str(), shapes); } else { shape = new IfcGeom::Representation::BRep(element_settings, representation_id_builder.str(), shapes); } std::string context_string = ""; if (representation->RepresentationIdentifier()) { context_string = *representation->RepresentationIdentifier(); } else if (representation->ContextOfItems()->ContextType()) { context_string = *representation->ContextOfItems()->ContextType(); } auto elem = new BRepElement( product->data().id(), parent_id, name, product_type, guid, context_string, trsf, boost::shared_ptr(shape), product ); if (settings.get(IteratorSettings::VALIDATE_QUANTITIES)) { auto rels = product->IsDefinedBy(); for (auto& rel : *rels) { if (rel->as()) { auto pdef = rel->as()->RelatingPropertyDefinition(); if (pdef->as()) { std::string organization_name; try { // A couple of files are not according to the schema here. organization_name = pdef->as()->OwnerHistory()->OwningApplication()->ApplicationDeveloper()->Name(); } catch (...) {} if (organization_name == "IfcOpenShell") { auto qs = pdef->as()->Quantities(); for (auto& q : *qs) { if (q->as() && q->Name() == "Total Surface Area") { double a_calc; double a_file = q->as()->AreaValue(); if (elem->geometry().calculate_surface_area(a_calc)) { double diff = std::abs(a_calc - a_file); if (diff / std::sqrt(a_file) > getValue(GV_PRECISION)) { Logger::Error("Validation of surface area failed for:", product); } else { Logger::Notice("Validation of surface area succeeded for:", product); } } else { Logger::Error("Validation of surface area failed for:", product); } } else if (q->as() && q->Name() == "Volume") { double v_calc; double v_file = q->as()->VolumeValue(); if (elem->geometry().calculate_volume(v_calc)) { double diff = std::abs(v_calc - v_file); if (diff / std::sqrt(v_file) > getValue(GV_PRECISION)) { Logger::Error("Validation of volume failed for:", product); } else { Logger::Notice("Validation of volume succeeded for:", product); } } else { Logger::Error("Validation of volume failed for:", product); } } else if (q->as() && q->Name() == "Shape Validation Properties") { auto qs2 = q->as()->HasQuantities(); bool all_succeeded = qs2->size() > 0; for (auto& q2 : *qs2) { if (q2->as() && q2->Name() == "Surface Genus" && q2->Description()) { int item_id = boost::lexical_cast((*q2->Description()).substr(1)); int genus = (int) q2->as()->CountValue(); for (auto& part : elem->geometry()) { if (part.ItemId() == item_id) { if (util::surface_genus(part.Shape()) != genus) { all_succeeded = false; } } } } } if (!all_succeeded) { Logger::Error("Validation of surface genus failed for:", product); } else { Logger::Notice("Validation of surface genus succeeded for:", product); } } } } } } } } return elem; } IfcSchema::IfcRepresentation* IfcGeom::Kernel::representation_mapped_to(const IfcSchema::IfcRepresentation* representation) { IfcSchema::IfcRepresentation* representation_mapped_to = 0; try { IfcSchema::IfcRepresentationItem::list::ptr items = representation->Items(); if (items->size() == 1) { IfcSchema::IfcRepresentationItem* item = *items->begin(); if (item->declaration().is(IfcSchema::IfcMappedItem::Class())) { if (item->StyledByItem()->size() == 0) { IfcSchema::IfcMappedItem* mapped_item = item->as(); if (is_identity_transform(mapped_item->MappingTarget())) { IfcSchema::IfcRepresentationMap* map = mapped_item->MappingSource(); if (is_identity_transform(map->MappingOrigin())) { representation_mapped_to = map->MappedRepresentation(); } } } } } } catch (const IfcParse::IfcException& e) { Logger::Error(e); // @todo reset representation_mapped_to to zero? } return representation_mapped_to; } IfcSchema::IfcProduct::list::ptr IfcGeom::Kernel::products_represented_by(const IfcSchema::IfcRepresentation* representation) { IfcSchema::IfcProduct::list::ptr products(new IfcSchema::IfcProduct::list); IfcSchema::IfcProductRepresentation::list::ptr prodreps = representation->OfProductRepresentation(); for (IfcSchema::IfcProductRepresentation::list::it it = prodreps->begin(); it != prodreps->end(); ++it) { // http://buildingsmart-tech.org/ifc/IFC2x3/TC1/html/ifcrepresentationresource/lexical/ifcproductrepresentation.htm // IFC2x Edition 3 NOTE Users should not instantiate the entity IfcProductRepresentation from IFC2x Edition 3 onwards. // It will be changed into an ABSTRACT supertype in future releases of IFC. // IfcProductRepresentation also lacks the INVERSE relation to IfcProduct // Let's find the IfcProducts that reference the IfcProductRepresentation anyway products->push((*it)->data().getInverse((&IfcSchema::IfcProduct::Class()), -1)->as()); } IfcSchema::IfcRepresentationMap::list::ptr maps = representation->RepresentationMap(); if (products->size() && maps->size()) { Logger::Warning("Representation used by IfcRepresentationMap and IfcProductDefinitionShape", representation); } if (prodreps->size() > 1) { Logger::Warning("Multiple IfcProductDefinitionShapes for representation", representation); } if (maps->size() > 1) { Logger::Warning("Multiple IfcRepresentationMaps for representation", representation); } if (maps->size() == 1) { IfcSchema::IfcRepresentationMap* map = *maps->begin(); if (is_identity_transform(map->MappingOrigin())) { IfcSchema::IfcMappedItem::list::ptr items = map->MapUsage(); for (IfcSchema::IfcMappedItem::list::it it = items->begin(); it != items->end(); ++it) { IfcSchema::IfcMappedItem* item = *it; if (item->StyledByItem()->size() != 0) continue; if (!is_identity_transform(item->MappingTarget())) { continue; } IfcSchema::IfcRepresentation::list::ptr reps = item->data().getInverse((&IfcSchema::IfcRepresentation::Class()), -1)->as(); for (IfcSchema::IfcRepresentation::list::it jt = reps->begin(); jt != reps->end(); ++jt) { IfcSchema::IfcRepresentation* rep = *jt; if (rep->Items()->size() != 1) continue; IfcSchema::IfcProductRepresentation::list::ptr prodreps_mapped = rep->OfProductRepresentation(); for (IfcSchema::IfcProductRepresentation::list::it kt = prodreps_mapped->begin(); kt != prodreps_mapped->end(); ++kt) { IfcSchema::IfcProduct::list::ptr ps = (*kt)->data().getInverse((&IfcSchema::IfcProduct::Class()), -1)->as(); products->push(ps); } } } } } return products; } IfcGeom::BRepElement* IfcGeom::Kernel::create_brep_for_processed_representation( const IteratorSettings& /*settings*/, IfcSchema::IfcRepresentation* representation, IfcSchema::IfcProduct* product, IfcGeom::BRepElement* brep) { int parent_id = -1; try { IfcUtil::IfcBaseEntity* parent_object = get_decomposing_entity(product); if (parent_object && parent_object->as()) { parent_id = parent_object->data().id(); } } catch (const std::exception& e) { Logger::Error(e); } const std::string name = product->Name().get_value_or(""); const std::string guid = product->GlobalId(); gp_Trsf trsf; try { if (product->ObjectPlacement()) { convert(product->ObjectPlacement(), trsf); } } catch (const std::exception& e) { Logger::Error(e); } catch (...) { Logger::Error("Failed to construct placement"); } std::string context_string = ""; if (representation->RepresentationIdentifier()) { context_string = *representation->RepresentationIdentifier(); } else if (representation->ContextOfItems()->ContextType()) { context_string = *representation->ContextOfItems()->ContextType(); } const std::string product_type = product->declaration().name(); return new BRepElement( product->data().id(), parent_id, name, product_type, guid, context_string, trsf, brep->geometry_pointer(), product ); } std::pair IfcGeom::Kernel::initializeUnits(IfcSchema::IfcUnitAssignment* unit_assignment) { // Set default units, set length to meters, angles to undefined setValue(IfcGeom::Kernel::GV_LENGTH_UNIT, 1.0); setValue(IfcGeom::Kernel::GV_PLANEANGLE_UNIT, -1.0); std::string unit_name = "METER"; double unit_magnitude = 1.; bool length_unit_encountered = false, angle_unit_encountered = false; try { aggregate_of_instance::ptr units = unit_assignment->Units(); if (!units || !units->size()) { Logger::Warning("No unit information found"); } else { for (aggregate_of_instance::it it = units->begin(); it != units->end(); ++it) { IfcUtil::IfcBaseClass* base = *it; if (base->declaration().is(IfcSchema::IfcNamedUnit::Class())) { IfcSchema::IfcNamedUnit* named_unit = base->as(); if (named_unit->UnitType() == IfcSchema::IfcUnitEnum::IfcUnit_LENGTHUNIT || named_unit->UnitType() == IfcSchema::IfcUnitEnum::IfcUnit_PLANEANGLEUNIT) { std::string current_unit_name; const double current_unit_magnitude = IfcParse::get_SI_equivalent(named_unit); if (current_unit_magnitude != 0.) { if (named_unit->declaration().is(IfcSchema::IfcConversionBasedUnit::Class())) { IfcSchema::IfcConversionBasedUnit* u = (IfcSchema::IfcConversionBasedUnit*)base; current_unit_name = u->Name(); } else if (named_unit->declaration().is(IfcSchema::IfcSIUnit::Class())) { IfcSchema::IfcSIUnit* si_unit = named_unit->as(); if (si_unit->Prefix()) { current_unit_name = IfcSchema::IfcSIPrefix::ToString(*si_unit->Prefix()) + unit_name; } current_unit_name += IfcSchema::IfcSIUnitName::ToString(si_unit->Name()); } if (named_unit->UnitType() == IfcSchema::IfcUnitEnum::IfcUnit_LENGTHUNIT) { unit_name = current_unit_name; unit_magnitude = current_unit_magnitude; setValue(IfcGeom::Kernel::GV_LENGTH_UNIT, current_unit_magnitude); length_unit_encountered = true; } else { setValue(IfcGeom::Kernel::GV_PLANEANGLE_UNIT, current_unit_magnitude); angle_unit_encountered = true; } } } } } } } catch (const IfcParse::IfcException& ex) { std::stringstream ss; ss << "Failed to determine unit information '" << ex.what() << "'"; Logger::Message(Logger::LOG_ERROR, ss.str()); } if (!length_unit_encountered) { Logger::Warning("No length unit encountered"); } if (!angle_unit_encountered) { Logger::Warning("No plane angle unit encountered"); } return std::pair(unit_name, unit_magnitude); } bool IfcGeom::Kernel::convert_layerset(const IfcSchema::IfcProduct* product, std::vector& surfaces, std::vector>& styles, std::vector& thicknesses) { IfcSchema::IfcMaterialLayerSetUsage* usage = 0; Handle_Geom_Surface reference_surface; IfcSchema::IfcRelAssociates::list::ptr associations = product->HasAssociations(); for (IfcSchema::IfcRelAssociates::list::it it = associations->begin(); it != associations->end(); ++it) { IfcSchema::IfcRelAssociatesMaterial* associates_material = (**it).as(); if (associates_material) { usage = associates_material->RelatingMaterial()->as(); break; } } if (!usage) { return false; } IfcSchema::IfcRepresentation* body_representation = find_representation(product, "Body"); if (!body_representation) { Logger::Warning("No body representation for product", product); return false; } if (product->declaration().is(IfcSchema::IfcWall::Class())) { IfcSchema::IfcRepresentation* axis_representation = find_representation(product, "Axis"); if (!axis_representation) { Logger::Message(Logger::LOG_WARNING, "No axis representation for:", product); return false; } IfcRepresentationShapeItems axis_items; { Kernel temp = *this; temp.setValue(GV_DIMENSIONALITY, -1.); temp.convert_shapes(axis_representation, axis_items); } TopoDS_Shape axis_shape; util::flatten_shape_list(axis_items, axis_shape, false, getValue(GV_PRECISION)); TopExp_Explorer exp(axis_shape, TopAbs_EDGE); TopoDS_Edge axis_edge; int edge_count = 0; if (exp.More()) { axis_edge = TopoDS::Edge(exp.Current()); ++ edge_count; } else { Logger::Message(Logger::LOG_WARNING, "No edge found in axis representation:", product); return false; } double u1, u2; Handle_Geom_Curve axis_curve = BRep_Tool::Curve(axis_edge, u1, u2); if (true) { /**< @todo Why always true? */ if (axis_curve->DynamicType() == STANDARD_TYPE(Geom_Line)) { Handle_Geom_Line axis_line = Handle_Geom_Line::DownCast(axis_curve); // @todo note that this creates an offset into the wrong order, the cross product arguments should be // reversed. This causes some inversions later on, e.g. if(positive) { reverse(); } reference_surface = new Geom_Plane(axis_line->Lin().Location(), axis_line->Lin().Direction() ^ gp::DZ()); } else if (axis_curve->DynamicType() == STANDARD_TYPE(Geom_Circle)) { // @todo note that in this branch this inversion does not seem to take place. Handle_Geom_Circle axis_line = Handle_Geom_Circle::DownCast(axis_curve); reference_surface = new Geom_CylindricalSurface(axis_line->Position(), axis_line->Radius()); } else { Logger::Message(Logger::LOG_ERROR, "Unsupported underlying curve of Axis representation:", product); return false; } } else { // Unfortunately this does not work when its intersection // is calculated later on when the layerset is applied. reference_surface = new Geom_SurfaceOfLinearExtrusion(axis_curve, gp::DZ()); } } else { IfcSchema::IfcExtrudedAreaSolid::list::ptr extrusions = IfcParse::traverse(body_representation)->as(); if (extrusions->size() != 1) { Logger::Message(Logger::LOG_WARNING, "No single extrusion found in body representation for:", product); return false; } IfcSchema::IfcExtrudedAreaSolid* extrusion = *extrusions->begin(); gp_Trsf extrusion_position; bool has_position = true; #ifdef SCHEMA_IfcSweptAreaSolid_Position_IS_OPTIONAL has_position = extrusion->Position() != nullptr; #endif if (has_position) { if (!convert(extrusion->Position(), extrusion_position)) { Logger::Message(Logger::LOG_ERROR, "Failed to convert placement for extrusion of:", product); return false; } } gp_Dir extrusion_direction; if (!convert(extrusion->ExtrudedDirection(), extrusion_direction)) { Logger::Message(Logger::LOG_ERROR, "Failed to convert direction for extrusion of:", product); return false; } reference_surface = new Geom_Plane(extrusion_position.TranslationPart(), extrusion_direction); } const IfcSchema::IfcMaterialLayerSet* layerset = usage->ForLayerSet(); const bool positive = usage->DirectionSense() == IfcSchema::IfcDirectionSenseEnum::IfcDirectionSense_POSITIVE; double offset = usage->OffsetFromReferenceLine() * getValue(GV_LENGTH_UNIT); IfcSchema::IfcMaterialLayer::list::ptr material_layers = layerset->MaterialLayers(); surfaces.push_back(new Geom_OffsetSurface(reference_surface, -offset)); for (IfcSchema::IfcMaterialLayer::list::it it = material_layers->begin(); it != material_layers->end(); ++it) { styles.push_back(get_style((*it)->Material())); double thickness = (*it)->LayerThickness() * getValue(GV_LENGTH_UNIT); thicknesses.push_back(thickness); if (!positive) { thickness *= -1; } offset += thickness; if (fabs(offset) < 1.e-7) { surfaces.push_back(reference_surface); } else { surfaces.push_back(new Geom_OffsetSurface(reference_surface, -offset)); } } if (positive) { std::reverse(thicknesses.begin(), thicknesses.end()); std::reverse(styles.begin(), styles.end()); std::reverse(surfaces.begin(), surfaces.end()); } return true; } bool IfcGeom::Kernel::find_wall_end_points(const IfcSchema::IfcWall* wall, gp_Pnt& start, gp_Pnt& end) { IfcSchema::IfcRepresentation* axis_representation = find_representation(wall, "Axis"); if (!axis_representation) { return false; } IfcRepresentationShapeItems items; { Kernel temp = *this; temp.setValue(GV_DIMENSIONALITY, -1.); temp.convert_shapes(axis_representation, items); } TopoDS_Vertex a, b; for (IfcRepresentationShapeItems::const_iterator it = items.begin(); it != items.end(); ++it) { TopExp_Explorer exp(it->Shape(), TopAbs_VERTEX); for (; exp.More(); exp.Next()) { b = TopoDS::Vertex(exp.Current()); if (a.IsNull()) { a = b; } } } if (a.IsNull() || b.IsNull()) { return false; } start = BRep_Tool::Pnt(a); end = BRep_Tool::Pnt(b); return true; } bool IfcGeom::Kernel::fold_layers(const IfcSchema::IfcWall* wall, const IfcRepresentationShapeItems& items, const std::vector& surfaces, const std::vector& thicknesses, std::vector< std::vector >& result) { /* * @todo isn't it easier to do this based on the non-folded surfaces of * the connected walls and fold both pairs of layersets simultaneously? */ bool folds_made = false; IfcSchema::IfcRelConnectsPathElements::list::ptr connections(new IfcSchema::IfcRelConnectsPathElements::list); connections->push(wall->ConnectedFrom()->as()); connections->push( wall->ConnectedTo()->as()); typedef std::vector surfaces_t; typedef std::pair curve_on_surface; typedef std::vector curves_on_surfaces_t; typedef std::vector< std::pair< std::pair, const IfcSchema::IfcProduct*> > endpoint_connections_t; typedef std::vector< std::vector > result_t; endpoint_connections_t endpoint_connections; // Find the semantic connections ot other wall elements when they are not connected 'AT_PATH' because // in that latter case no folds need to be made. for (IfcSchema::IfcRelConnectsPathElements::list::it it = connections->begin(); it != connections->end(); ++it) { IfcSchema::IfcRelConnectsPathElements* connection = *it; IfcSchema::IfcConnectionTypeEnum::Value own_type = connection->RelatedElement() == wall ? connection->RelatedConnectionType() : connection->RelatingConnectionType(); IfcSchema::IfcConnectionTypeEnum::Value other_type = connection->RelatedElement() == wall ? connection->RelatingConnectionType() : connection->RelatedConnectionType(); if (other_type != IfcSchema::IfcConnectionTypeEnum::IfcConnectionType_ATPATH && (own_type == IfcSchema::IfcConnectionTypeEnum::IfcConnectionType_ATEND || own_type == IfcSchema::IfcConnectionTypeEnum::IfcConnectionType_ATSTART)) { IfcSchema::IfcElement* other = connection->RelatedElement() == wall ? connection->RelatingElement() : connection->RelatedElement(); if (other->as()) { endpoint_connections.push_back(std::make_pair(std::make_pair(own_type, other_type), other)); } } } if (endpoint_connections.size() == 0) { return false; } // Count how many connections are made AT_START and AT_END respectively int connection_type_count[2] = {0,0}; for (endpoint_connections_t::const_iterator it = endpoint_connections.begin(); it != endpoint_connections.end(); ++it) { const int idx = it->first.first == IfcSchema::IfcConnectionTypeEnum::IfcConnectionType_ATSTART; connection_type_count[idx] ++; } gp_Trsf local; if (wall->ObjectPlacement()) { if (!convert(wall->ObjectPlacement(), local)) { return false; } } local.Invert(); { // Copy the unfolded surfaces result.resize(surfaces.size()); std::vector< std::vector >::iterator result_it = result.begin() + 1; std::vector::const_iterator input_it = surfaces.begin() + 1; for(; input_it != surfaces.end() - 1; ++result_it, ++input_it) { result_it->push_back(*input_it); } } const double total_thickness = std::accumulate(thicknesses.begin(), thicknesses.end(), 0.); gp_Pnt own_axis_start, own_axis_end; find_wall_end_points(wall, own_axis_start, own_axis_end); // Sometimes duplicate IfcRelConnectsPathElements exist. These are detected // and the counts of connections are decremented accordingly. for (int idx = 0; idx < 2; ++idx) { if (connection_type_count[idx] <= 1) { continue; } /* IfcSchema::IfcConnectionTypeEnum::Value connection_type = idx == 1 ? IfcSchema::IfcConnectionTypeEnum::IfcConnectionType_ATSTART : IfcSchema::IfcConnectionTypeEnum::IfcConnectionType_ATEND; */ std::set others; endpoint_connections_t::iterator it = endpoint_connections.begin(); while (it != endpoint_connections.end()) { const IfcSchema::IfcProduct* other = it->second; if (others.find(other) != others.end()) { it = endpoint_connections.erase(it); --connection_type_count[idx]; } else { others.insert(other); ++it; } } } // Check whether the end points are of the wall are really ~1 LayerThickness away from each other /* for (endpoint_connections_t::const_iterator it = endpoint_connections.begin(); it != endpoint_connections.end(); ++it) { IfcSchema::IfcConnectionTypeEnum::Value own_type = it->first.first; IfcSchema::IfcConnectionTypeEnum::Value other_type = it->first.second; gp_Pnt other_axis_start, other_axis_end; find_wall_end_points(it->second->as(), other_axis_start, other_axis_end); gp_Trsf other; if (!convert(it->second->ObjectPlacement(), other)) { continue; } other.Transforms(other_axis_start.ChangeCoord()); local.Transforms(other_axis_start.ChangeCoord()); other.Transforms(other_axis_end.ChangeCoord()); local.Transforms(other_axis_end.ChangeCoord()); const gp_Pnt& a = own_type == IfcSchema::IfcConnectionTypeEnum::IfcConnectionType_ATSTART ? own_axis_start : own_axis_end; const gp_Pnt& b = other_type == IfcSchema::IfcConnectionTypeEnum::IfcConnectionType_ATSTART ? other_axis_start : other_axis_end; const double d = a.Distance(b); } */ const double length_required = endpoint_connections.size() * total_thickness; // @todo this is not precisely the distance in case of curved walls. Also, it's safer // to first reproject the body onto the axis to get the precise curve parametrization // range. It's only a safeguard though, so can probably be approximated. const double axis_length = own_axis_start.Distance(own_axis_end); if (length_required > axis_length) { Logger::Warning("The wall axis is not long enough to accomodate the fold points"); return false; } for (endpoint_connections_t::const_iterator it = endpoint_connections.begin(); it != endpoint_connections.end(); ++it) { IfcSchema::IfcConnectionTypeEnum::Value connection_type = it->first.first; // If more than one wall connects to this start/end -point assume layers do not need to be folded const int idx = connection_type == IfcSchema::IfcConnectionTypeEnum::IfcConnectionType_ATSTART; if (connection_type_count[idx] > 1) continue; // Pick the corresponding point from the axis const gp_Pnt& own_end_point = connection_type == IfcSchema::IfcConnectionTypeEnum::IfcConnectionType_ATEND ? own_axis_end : own_axis_start; const IfcSchema::IfcProduct* other_wall = it->second; gp_Trsf other; if (other_wall->ObjectPlacement()) { if (!convert(other_wall->ObjectPlacement(), other)) { Logger::Error("Failed to convert placement", other_wall); continue; } } IfcSchema::IfcRepresentation* axis_representation = find_representation(other_wall, "Axis"); if (!axis_representation) { Logger::Warning("Joined wall has no axis representation", other_wall); continue; } IfcRepresentationShapeItems axis_items; { Kernel temp = *this; temp.setValue(GV_DIMENSIONALITY, -1.); temp.convert_shapes(axis_representation, axis_items); } TopoDS_Shape axis_shape; util::flatten_shape_list(axis_items, axis_shape, false, getValue(GV_PRECISION)); // local and other are IfcLocalPlacements and therefore have a unit // scale factor that can be applied by means of TopoDS_Shape::Move() axis_shape.Move(other); axis_shape.Move(local); TopoDS_Shape body_shape; util::flatten_shape_list(items, body_shape, false, getValue(GV_PRECISION)); // Create a single paremetric range over a single curve // that represents the entire 1d domain of the other wall // Sometimes there are multiple edges in the Axis shape // but it is assumed these are colinear. Handle_Geom_Curve other_axis_curve; double axis_u1, axis_u2; { TopExp_Explorer exp(axis_shape, TopAbs_EDGE); if (!exp.More()) { return false; } TopoDS_Edge axis_edge = TopoDS::Edge(exp.Current()); other_axis_curve = BRep_Tool::Curve(axis_edge, axis_u1, axis_u2); gp_Pnt other_a_1, other_a_2; other_axis_curve->D0(axis_u1, other_a_1); other_axis_curve->D0(axis_u2, other_a_2); if (axis_u2 < axis_u1) { std::swap(axis_u1, axis_u2); } exp.Next(); for (; exp.More(); exp.Next()) { TopoDS_Edge axis_edge2 = TopoDS::Edge(exp.Current()); TopExp_Explorer exp2(axis_edge2, TopAbs_VERTEX); for (; exp2.More(); exp2.Next()) { gp_Pnt p = BRep_Tool::Pnt(TopoDS::Vertex(exp2.Current())); gp_Pnt pp; double u, d; if (util::project(other_axis_curve, p, pp, u, d)) { if (u < axis_u1) axis_u1 = u; if (u > axis_u2) axis_u2 = u; } } } } double layer_offset = 0; std::vector::const_iterator thickness = thicknesses.begin(); result_t::iterator result_vector = result.begin() + 1; // nb The first layer is never folded, because it corresponds // to one of the longitudonal faces of the wall. Hence the +1 for (surfaces_t::const_iterator jt = surfaces.begin() + 1; jt != surfaces.end() - 1; ++jt, ++result_vector) { layer_offset += *thickness++; bool found_intersection = false, parallel = false; boost::optional point_outside_param_range; const Handle_Geom_Surface& surface = *jt; // Find the intersection point between the layerset surface // and the other axis curve. If it's within the parametric // range of the other wall it means the walls are connected // with an angle. GeomAPI_IntCS intersections(other_axis_curve, surface); if (intersections.IsDone() && intersections.NbPoints() == 1) { const gp_Pnt& p = intersections.Point(1); double u, v, w; intersections.Parameters(1, u, v, w); gp_Pnt Pc, Ps; gp_Vec Vc, Vs1, Vs2; other_axis_curve->D1(w, Pc, Vc); surface->D1(u, v, Ps, Vs1, Vs2); Vs1.Cross(Vs2); if (Vs1.IsNormal(Vc, 1.e-5)) { Logger::Warning("Connected walls are parallel"); parallel = true; } else if (w < axis_u1 || w > axis_u2) { point_outside_param_range = p; } else { // Found an intersection. Layer end point is covered by connecting wall found_intersection = true; break; } } if (!parallel && !found_intersection && point_outside_param_range) { /* Is there a bug in Open Cascade related to the intersection of offset surfaces constructed from linear extrusions? Handle_Geom_Surface xy = new Geom_Plane(gp::Origin(), gp::DZ()); // Handle_Geom_Surface yz = new Geom_Plane(gp::Origin(), gp::DX()); // Handle_Geom_Surface yz2 = new Geom_OffsetSurface(yz, 1.); Handle_Geom_Curve ln = new Geom_Line(gp::Origin(), gp::DX()); Handle_Geom_Surface yz = new Geom_SurfaceOfLinearExtrusion(ln, gp::DZ()); Handle_Geom_Surface yz2 = new Geom_OffsetSurface(yz, 1.); intersect(xy, yz2); */ Handle_Geom_Surface plane = new Geom_Plane(*point_outside_param_range, gp::DZ()); // vertical edges at wall end point face. curves_on_surfaces_t layer_ends; util::intersect(surface, body_shape, layer_ends); Handle_Geom_Curve layer_body_intersection; Handle_Geom_Surface body_surface; double mind = std::numeric_limits::infinity(); for (curves_on_surfaces_t::const_iterator kt = layer_ends.begin(); kt != layer_ends.end(); ++kt) { gp_Pnt p; gp_Vec v; double u, d; kt->second->D1(0., p, v); if (ALMOST_THE_SAME(0., v.Dot(gp::DZ()))) { // Filter horizontal curves continue; } // Find vertical wall end point edge closest to end point associated with semantic connection if (util::project(kt->second, own_end_point, p, u, d)) { // In addition to closest, there is a length threshold based on thickness. // @todo ideally, first, the point closest to end-point is selected, and // after that the parallel check is performed. But threshold probably // functions good enough. if (d < total_thickness * 3 && d < mind) { GeomAdaptor_Curve GAC(other_axis_curve); GeomAdaptor_Surface GAS(kt->first); Extrema_ExtCS x(GAC, GAS, getValue(GV_PRECISION), getValue(GV_PRECISION)); if (x.IsParallel()) { body_surface = kt->first; layer_body_intersection = kt->second; mind = d; } } } } if (body_surface.IsNull()) { continue; } // Intersect vertical edge with ground plane for point. GeomAPI_IntCS intersection2(layer_body_intersection, plane); if (intersection2.IsDone() && intersection2.NbPoints() == 1) { const gp_Pnt& layer_end_point = intersection2.Point(1); // Intersect layerset surface with ground plane GeomAPI_IntSS intersection3(surface, plane, 1.e-7); if (intersection3.IsDone() && intersection3.NbLines() == 1) { Handle_Geom_Curve layer_line = intersection3.Line(1); GeomAdaptor_Curve layer_line_adaptor(layer_line); ShapeAnalysis_Curve sac; gp_Pnt layer_end_point_projected; double layer_end_point_param; sac.Project(layer_line, layer_end_point, 1e-3, layer_end_point_projected, layer_end_point_param, false); // Move point inwards by distance from other layerset GCPnts_AbscissaPoint dst(layer_line_adaptor, layer_offset, layer_end_point_param); if (dst.IsDone()) { // Convert parameter to point gp_Pnt layer_fold_point; layer_line->D0(dst.Parameter(), layer_fold_point); GeomAPI_IntSS intersection4(body_surface, plane, 1.e-7); if (intersection4.IsDone() && intersection4.NbLines() == 1) { Handle_Geom_Curve body_trim_curve = intersection4.Line(1); ShapeAnalysis_Curve sac2; gp_Pnt layer_fold_point_projected; double layer_fold_point_param; sac2.Project(body_trim_curve, layer_fold_point, 1.e-7, layer_fold_point_projected, layer_fold_point_param, false); Handle_Geom_Curve fold_curve = new Geom_OffsetCurve(body_trim_curve->Reversed(), layer_fold_point_projected.Distance(layer_fold_point), gp::DZ()); Handle_Geom_Surface fold_surface = new Geom_SurfaceOfLinearExtrusion(fold_curve, gp::DZ()); result_vector->push_back(fold_surface); folds_made = true; } } } } } } } return folds_made; } IfcSchema::IfcRepresentation* IfcGeom::Kernel::find_representation(const IfcSchema::IfcProduct* product, const std::string& identifier) { if (!product->Representation()) return 0; IfcSchema::IfcProductRepresentation* prod_rep = product->Representation(); IfcSchema::IfcRepresentation::list::ptr reps = prod_rep->Representations(); for (IfcSchema::IfcRepresentation::list::it it = reps->begin(); it != reps->end(); ++it) { if ((**it).RepresentationIdentifier() && (*(**it).RepresentationIdentifier()) == identifier) { return *it; } } return 0; } const IfcSchema::IfcRepresentationItem* IfcGeom::Kernel::find_item_carrying_style(const IfcSchema::IfcRepresentationItem* item) { if (item->StyledByItem()->size()) { return item; } while (item->declaration().is(IfcSchema::IfcBooleanResult::Class())) { // All instantiations of IfcBooleanOperand (type of FirstOperand) are subtypes of // IfcGeometricRepresentationItem item = item->as()->FirstOperand()->as(); if (item && item->StyledByItem()->size()) { return item; } } // TODO: Ideally this would be done for other entities (such as IfcCsgSolid) as well. // But neither are these very prevalent, nor does the current IfcOpenShell style // mechanism enable to conveniently style subshapes, which would be necessary for // distinctly styled union operands. return item; } bool IfcGeom::Kernel::is_identity_transform(IfcUtil::IfcBaseInterface* l) { IfcSchema::IfcAxis2Placement2D* ax2d; IfcSchema::IfcAxis2Placement3D* ax3d; IfcSchema::IfcCartesianTransformationOperator2D* op2d; IfcSchema::IfcCartesianTransformationOperator3D* op3d; IfcSchema::IfcCartesianTransformationOperator2DnonUniform* op2dnonu; IfcSchema::IfcCartesianTransformationOperator3DnonUniform* op3dnonu; if((op2dnonu = l->as()) != 0) { gp_GTrsf2d gtrsf2d; convert(op2dnonu, gtrsf2d); return gtrsf2d.Form() == gp_Identity; } else if ((op2d = l->as()) != 0) { gp_Trsf2d trsf2d; convert(op2d, trsf2d); return trsf2d.Form() == gp_Identity; } else if((op3dnonu = l->as()) != 0) { gp_GTrsf gtrsf; convert(op3dnonu, gtrsf); return gtrsf.Form() == gp_Identity; } else if ((op3d = l->as()) != 0) { gp_Trsf trsf; convert(op3d, trsf); return trsf.Form() == gp_Identity; } else if((ax2d = l->as()) != 0) { gp_Trsf2d trsf2d; convert(ax2d, trsf2d); return trsf2d.Form() == gp_Identity; } else if ((ax3d = l->as()) != 0) { gp_Trsf trsf; convert(ax3d, trsf); return trsf.Form() == gp_Identity; } else { throw IfcParse::IfcException("Invalid valuation for IfcAxis2Placement / IfcCartesianTransformationOperator"); } } void IfcGeom::Kernel::set_conversion_placement_rel_to_type(const IfcParse::declaration* type) { placement_rel_to_type_ = type; } void IfcGeom::Kernel::set_conversion_placement_rel_to_instance(const IfcUtil::IfcBaseEntity* instance) { placement_rel_to_instance_ = instance; } namespace { bool process_colour(IfcSchema::IfcColourRgb* colour, double* rgb) { if (colour != 0) { rgb[0] = colour->Red(); rgb[1] = colour->Green(); rgb[2] = colour->Blue(); } return colour != 0; } bool process_colour(IfcSchema::IfcNormalisedRatioMeasure* factor, double* rgb) { if (factor != 0) { const double f = *factor; rgb[0] = rgb[1] = rgb[2] = f; } return factor != 0; } bool process_colour(IfcSchema::IfcColourOrFactor* colour_or_factor, double* rgb) { if (colour_or_factor == 0) { return false; } else if (colour_or_factor->declaration().is(IfcSchema::IfcColourRgb::Class())) { return process_colour(static_cast(colour_or_factor), rgb); } else if (colour_or_factor->declaration().is(IfcSchema::IfcNormalisedRatioMeasure::Class())) { return process_colour(static_cast(colour_or_factor), rgb); } else { return false; } } } #define Kernel MAKE_TYPE_NAME(Kernel) std::shared_ptr IfcGeom::Kernel::internalize_surface_style(const std::pair& shading_styles) { if (shading_styles.second == 0) { return 0; } int surface_style_id = shading_styles.first->data().id(); auto it = style_cache.find(surface_style_id); if (it != style_cache.end()) { return it->second; } IfcSchema::IfcSurfaceStyle* style = shading_styles.first->as(); IfcSchema::IfcSurfaceStyleShading* shading = shading_styles.second->as(); std::shared_ptr surface_style_ptr; if (style->Name()) { surface_style_ptr.reset(new SurfaceStyle(surface_style_id, *style->Name())); } else { surface_style_ptr.reset(new SurfaceStyle(surface_style_id)); } std::shared_ptr surface_style_ptr_const = std::const_pointer_cast(surface_style_ptr); SurfaceStyle& surface_style = *surface_style_ptr; double rgb[3]; if (process_colour(shading->SurfaceColour(), rgb)) { surface_style.Diffuse().reset(SurfaceStyle::ColorComponent(rgb[0], rgb[1], rgb[2])); } if (shading_styles.second->declaration().is(IfcSchema::IfcSurfaceStyleRendering::Class())) { IfcSchema::IfcSurfaceStyleRendering* rendering_style = static_cast(shading_styles.second); if (rendering_style->DiffuseColour() && process_colour(rendering_style->DiffuseColour(), rgb)) { SurfaceStyle::ColorComponent diffuse = surface_style.Diffuse().get_value_or(SurfaceStyle::ColorComponent(1, 1, 1)); surface_style.Diffuse().reset(SurfaceStyle::ColorComponent(diffuse.R() * rgb[0], diffuse.G() * rgb[1], diffuse.B() * rgb[2])); } if (rendering_style->DiffuseTransmissionColour()) { // Not supported } if (rendering_style->ReflectionColour()) { // Not supported } if (rendering_style->SpecularColour() && process_colour(rendering_style->SpecularColour(), rgb)) { surface_style.Specular().reset(SurfaceStyle::ColorComponent(rgb[0], rgb[1], rgb[2])); } if (rendering_style->SpecularHighlight()) { IfcSchema::IfcSpecularHighlightSelect* highlight = rendering_style->SpecularHighlight(); if (highlight->declaration().is(IfcSchema::IfcSpecularRoughness::Class())) { double roughness = *((IfcSchema::IfcSpecularRoughness*)highlight); if (roughness >= 1e-9) { surface_style.Specularity().reset(1.0 / roughness); } } else if (highlight->declaration().is(IfcSchema::IfcSpecularExponent::Class())) { surface_style.Specularity().reset(*((IfcSchema::IfcSpecularExponent*)highlight)); } } if (rendering_style->TransmissionColour()) { // Not supported } if (rendering_style->Transparency()) { const double d = *rendering_style->Transparency(); surface_style.Transparency().reset(d); } } return style_cache[surface_style_id] = surface_style_ptr_const; } std::shared_ptr IfcGeom::Kernel::get_style(const IfcSchema::IfcRepresentationItem* item) { return internalize_surface_style(get_surface_style(item)); } std::shared_ptr IfcGeom::Kernel::get_style(const IfcSchema::IfcMaterial* material) { IfcSchema::IfcMaterialDefinitionRepresentation::list::ptr defs = material->HasRepresentation(); for (IfcSchema::IfcMaterialDefinitionRepresentation::list::it jt = defs->begin(); jt != defs->end(); ++jt) { IfcSchema::IfcRepresentation::list::ptr reps = (*jt)->Representations(); IfcSchema::IfcStyledItem::list::ptr styles(new IfcSchema::IfcStyledItem::list); for (IfcSchema::IfcRepresentation::list::it it = reps->begin(); it != reps->end(); ++it) { styles->push((**it).Items()->as()); } for (IfcSchema::IfcStyledItem::list::it it = styles->begin(); it != styles->end(); ++it) { const std::pair ss = get_surface_style(*it); if (ss.second) { return internalize_surface_style(ss); } } } auto material_style = std::make_shared(material->data().id(), material->Name()); return style_cache[material->data().id()] = material_style; }