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 (flatten_shape_list(shapes, merge, false)) {
if (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 (apply_folded_layerset(shapes, folded_layers, styles, shapes2)) {
std::swap(shapes, shapes2);
success = true;
}
} else {
if (apply_layerset(shapes, layers, styles, shapes2)) {
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) {
const IfcGeom::SurfaceStyle* 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() && 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.Style()) {
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->hasName() ? product->Name() : "";
const std::string guid = product->GlobalId();
gp_Trsf trsf;
try {
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 {
#if OCC_VERSION_HEX < 0x60900
const bool faster_booleans = settings.get(IteratorSettings::FASTER_BOOLEANS);
#else
const bool faster_booleans = true;
#endif
if (faster_booleans) {
bool success = convert_openings_fast(product,openings,shapes,trsf,opened_shapes);
#if OCC_VERSION_HEX < 0x60900
if (!success) {
opened_shapes.clear();
convert_openings(product,openings,shapes,trsf,opened_shapes);
}
#else
(void)success;
#endif
} else {
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->hasRepresentationIdentifier()) {
context_string = representation->RepresentationIdentifier();
} else if (representation->ContextOfItems()->hasContextType()) {
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->hasDescription()) {
int item_id = boost::lexical_cast(q2->Description().substr(1));
int genus = q2->as()->CountValue();
for (auto& part : elem->geometry()) {
if (part.ItemId() == item_id) {
if (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;
}
template
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->hasName() ? product->Name() : "";
const std::string guid = product->GlobalId();
gp_Trsf trsf;
try {
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->hasRepresentationIdentifier()) {
context_string = representation->RepresentationIdentifier();
} else if (representation->ContextOfItems()->hasContextType()) {
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
);
}
template IFC_GEOM_API IfcGeom::BRepElement* IfcGeom::Kernel::create_brep_for_representation_and_product(
const IteratorSettings& settings, IfcSchema::IfcRepresentation* representation, IfcSchema::IfcProduct* product);
template IFC_GEOM_API IfcGeom::BRepElement* IfcGeom::Kernel::create_brep_for_representation_and_product(
const IteratorSettings& settings, IfcSchema::IfcRepresentation* representation, IfcSchema::IfcProduct* product);
template IFC_GEOM_API IfcGeom::BRepElement* IfcGeom::Kernel::create_brep_for_representation_and_product(
const IteratorSettings& settings, IfcSchema::IfcRepresentation* representation, IfcSchema::IfcProduct* product);
template IFC_GEOM_API IfcGeom::BRepElement* IfcGeom::Kernel::create_brep_for_processed_representation(
const IteratorSettings& settings, IfcSchema::IfcRepresentation* representation, IfcSchema::IfcProduct* product, IfcGeom::BRepElement* brep);
template IFC_GEOM_API IfcGeom::BRepElement* IfcGeom::Kernel::create_brep_for_processed_representation(
const IteratorSettings& settings, IfcSchema::IfcRepresentation* representation, IfcSchema::IfcProduct* product, IfcGeom::BRepElement* brep);
template IFC_GEOM_API IfcGeom::BRepElement* IfcGeom::Kernel::create_brep_for_processed_representation(
const IteratorSettings& settings, IfcSchema::IfcRepresentation* representation, IfcSchema::IfcProduct* product, IfcGeom::BRepElement* brep);
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 {
IfcEntityList::ptr units = unit_assignment->Units();
if (!units || !units->size()) {
Logger::Warning("No unit information found");
} else {
for (IfcEntityList::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->hasPrefix()) {
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;
flatten_shape_list(axis_items, axis_shape, false);
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->hasPosition();
#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;
}
const Handle_Geom_Curve IfcGeom::Kernel::intersect(const Handle_Geom_Surface& a, const Handle_Geom_Surface& b) {
GeomAPI_IntSS x(a, b, 1.e-7);
if (x.IsDone() && x.NbLines() == 1) {
return x.Line(1);
} else {
return Handle_Geom_Curve();
}
}
const Handle_Geom_Curve IfcGeom::Kernel::intersect(const Handle_Geom_Surface& a, const TopoDS_Face& b) {
return intersect(a, BRep_Tool::Surface(b));
}
const Handle_Geom_Curve IfcGeom::Kernel::intersect(const TopoDS_Face& a, const Handle_Geom_Surface& b) {
return intersect(BRep_Tool::Surface(a), b);
}
bool IfcGeom::Kernel::intersect(const Handle_Geom_Curve& a, const Handle_Geom_Surface& b, gp_Pnt& p) {
GeomAPI_IntCS x(a, b);
if (x.IsDone() && x.NbPoints() == 1) {
p = x.Point(1);
return true;
} else {
return false;
}
}
bool IfcGeom::Kernel::intersect(const Handle_Geom_Curve& a, const TopoDS_Face& b, gp_Pnt &c) {
return intersect(a, BRep_Tool::Surface(b), c);
}
bool IfcGeom::Kernel::intersect(const Handle_Geom_Curve& a, const TopoDS_Shape& b, std::vector& out) {
TopExp_Explorer exp(b, TopAbs_FACE);
gp_Pnt p;
for (; exp.More(); exp.Next()) {
if (intersect(a, TopoDS::Face(exp.Current()), p)) {
out.push_back(p);
}
}
return !out.empty();
}
bool IfcGeom::Kernel::intersect(const Handle_Geom_Surface& a, const TopoDS_Shape& b, std::vector< std::pair >& out) {
TopExp_Explorer exp(b, TopAbs_FACE);
for (; exp.More(); exp.Next()) {
const TopoDS_Face& f = TopoDS::Face(exp.Current());
const Handle_Geom_Surface& s = BRep_Tool::Surface(f);
Handle_Geom_Curve crv = intersect(a, s);
if (!crv.IsNull()) {
out.push_back(std::make_pair(s, crv));
}
}
return !out.empty();
}
bool IfcGeom::Kernel::closest(const gp_Pnt& a, const std::vector& b, gp_Pnt& c) {
double minimal_distance = std::numeric_limits::infinity();
for (std::vector::const_iterator it = b.begin(); it != b.end(); ++it) {
const double d = a.Distance(*it);
if (d < minimal_distance) {
minimal_distance = d;
c = *it;
}
}
return minimal_distance != std::numeric_limits::infinity();
}
bool IfcGeom::Kernel::project(const Handle_Geom_Curve& crv, const gp_Pnt& pt, gp_Pnt& p, double& u, double& d) {
ShapeAnalysis_Curve sac;
sac.Project(crv, pt, 1e-3, p, u, false);
d = pt.Distance(p);
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 (!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 (!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;
flatten_shape_list(axis_items, axis_shape, false);
// 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;
flatten_shape_list(items, body_shape, false);
// 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 (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;
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 (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;
}
namespace {
void subshapes(const TopoDS_Shape& in, std::list& out) {
TopoDS_Iterator sit(in);
for (; sit.More(); sit.Next()) {
out.push_back(sit.Value());
}
}
#if OCC_VERSION_HEX >= 0x70200
bool split(IfcGeom::Kernel&, const TopoDS_Shape& input, const TopTools_ListOfShape& operands, double eps, std::vector& slices) {
if (operands.Extent() < 2) {
// Needs to have at least two cutting surfaces for the ordering based on surface containment to work.
return false;
}
BRepAlgoAPI_Splitter split;
TopTools_ListOfShape input_list;
input_list.Append(input);
split.SetArguments(input_list);
split.SetTools(operands);
split.SetNonDestructive(true);
split.SetFuzzyValue(eps);
split.Build();
if (!split.IsDone()) {
return false;
} else {
std::map surfaces;
// NB 1, since first surface has been excluded
int i = 1;
for (TopTools_ListIteratorOfListOfShape it(operands); it.More(); it.Next(), ++i) {
TopExp_Explorer exp(it.Value(), TopAbs_FACE);
for (; exp.More(); exp.Next()) {
surfaces.insert(std::make_pair(BRep_Tool::Surface(TopoDS::Face(exp.Current())).get(), i));
}
}
auto result_shape = split.Shape();
std::list subs;
subshapes(result_shape, subs);
if (subs.size() == 1 && operands.Size() - 2 > (int)subs.size() && (subs.front().ShapeType() == TopAbs_COMPSOLID || subs.front().ShapeType() == TopAbs_COMPOUND)) {
auto s = subs.front();
subs.clear();
subshapes(s, subs);
}
// Initialize storage
slices.resize(subs.size());
for (auto& s : subs) {
// Iterate over the faces of solid to find correspondence to original
// splitting surfaces. For the outmost slices, there will be a single
// corresponding surface, because the outmost surfaces that align with
// the body geometry have not been added as operands. For intermediate
// slices, two surface indices should be find that should be next to
// each other in the array of input surfaces.
TopExp_Explorer exp(s, TopAbs_FACE);
int min = std::numeric_limits::max();
int max = std::numeric_limits::min();
for (; exp.More(); exp.Next()) {
auto ssrf = BRep_Tool::Surface(TopoDS::Face(exp.Current()));
auto it = surfaces.find(ssrf.get());
if (it != surfaces.end()) {
if (it->second < min) {
min = it->second;
}
if (it->second > max) {
max = it->second;
}
}
}
int idx = std::numeric_limits::max();
if (min != std::numeric_limits::max()) {
if (min == 1 && max == 1) {
idx = 0;
} else if (min + 1 == max || min == max) {
idx = min;
}
}
if (idx < (int) slices.size()) {
if (slices[idx].IsNull()) {
slices[idx] = s;
continue;
}
}
Logger::Error("Unable to map layer geometry to material index");
return false;
}
}
return true;
}
#else
bool split(IfcGeom::Kernel& k, const TopoDS_Shape& input, const TopTools_ListOfShape& operands, double, std::vector& slices) {
TopTools_ListIteratorOfListOfShape it(operands);
TopoDS_Shape i = input;
for (; it.More(); it.Next()) {
const TopoDS_Shape& s = it.Value();
TopoDS_Shape a, b;
Handle(Geom_Surface) surf;
if (s.ShapeType() == TopAbs_FACE) {
surf = BRep_Tool::Surface(TopoDS::Face(s));
}
if ((s.ShapeType() == TopAbs_FACE && k.split_solid_by_surface(i, surf, a, b)) ||
(s.ShapeType() == TopAbs_SHELL && k.split_solid_by_shell(i, s, a, b)))
{
slices.push_back(b);
i = a;
} else {
return false;
}
}
slices.push_back(i);
return true;
}
#endif
}
bool IfcGeom::Kernel::apply_folded_layerset(const IfcRepresentationShapeItems& items, const std::vector< std::vector >& surfaces, const std::vector& styles, IfcRepresentationShapeItems& result) {
Bnd_Box bb;
TopoDS_Shape input;
flatten_shape_list(items, input, false);
typedef std::vector< std::vector > folded_surfaces_t;
typedef std::vector< std::pair< TopoDS_Face, std::pair > > faces_with_mass_t;
TopTools_ListOfShape shells;
for (folded_surfaces_t::const_iterator it = surfaces.begin(); it != surfaces.end(); ++it) {
if (it->empty()) {
continue;
} else if (it->size() == 1) {
const Handle_Geom_Surface& surface = (*it)[0];
double u1, v1, u2, v2;
if (!project(surface, input, u1, v1, u2, v2)) {
continue;
}
shells.Append(BRepBuilderAPI_MakeShell(surface, u1, v1, u2, v2).Shell());
} else {
faces_with_mass_t solids;
for (folded_surfaces_t::value_type::const_iterator jt = it->begin(); jt != it->end(); ++jt) {
const Handle_Geom_Surface& surface = *jt;
double u1, v1, u2, v2;
if (!project(surface, input, u1, v1, u2, v2)) {
continue;
}
TopoDS_Face face = BRepBuilderAPI_MakeFace(surface, u1, u2, v1, v2, 1.e-7).Face();
gp_Pnt p, p1, p2; gp_Vec vu, vv, n;
surface->D1((u1+u2)/2., (v1+v2)/2., p, vu, vv);
n = vu ^ vv;
p1 = p.Translated( n);
p2 = p.Translated(-n);
solids.push_back(std::make_pair(face, std::make_pair(p1, p2)));
}
if (solids.empty()) {
continue;
}
faces_with_mass_t::iterator jt = solids.begin();
TopoDS_Face& A = jt->first;
TopoDS_Shape An = BRepPrimAPI_MakeHalfSpace(A, jt->second.second).Solid();
for (++jt; jt != solids.end(); ++jt) {
TopoDS_Face& B = jt->first;
TopoDS_Shape Bn = BRepPrimAPI_MakeHalfSpace(B, jt->second.second).Solid();
TopoDS_Shape a = BRepAlgoAPI_Cut(A, Bn);
if (count(a, TopAbs_FACE) == 1) {
A = TopoDS::Face(TopExp_Explorer(a, TopAbs_FACE).Current());
}
TopoDS_Shape b = BRepAlgoAPI_Cut(B, An);
if (count(b, TopAbs_FACE) == 1) {
B = TopoDS::Face(TopExp_Explorer(b, TopAbs_FACE).Current());
}
}
BRepOffsetAPI_Sewing builder;
for (faces_with_mass_t::const_iterator kt = solids.begin(); kt != solids.end(); ++kt) {
builder.Add(kt->first);
}
builder.Perform();
TopoDS_Shape s = builder.SewedShape();
if (s.ShapeType() == TopAbs_SHELL) {
shells.Append(TopoDS::Shell(s));
} else {
Logger::Error("Expected shell type in layerset processing");
return false;
}
}
}
if (shells.Extent() == 0) {
return false;
} else if (shells.Extent() == 1) {
for (IfcRepresentationShapeItems::const_iterator it = items.begin(); it != items.end(); ++it) {
TopoDS_Shape a,b;
if (split_solid_by_shell(it->Shape(), shells.First(), a, b)) {
result.push_back(IfcRepresentationShapeItem(it->ItemId(), it->Placement(), b, styles[0] ? styles[0] : &it->Style()));
result.push_back(IfcRepresentationShapeItem(it->ItemId(), it->Placement(), a, styles[1] ? styles[1] : &it->Style()));
} else {
continue;
}
}
return true;
} else {
for (IfcRepresentationShapeItems::const_iterator it = items.begin(); it != items.end(); ++it) {
const TopoDS_Shape& s = it->Shape();
TopoDS_Solid sld;
ensure_fit_for_subtraction(s, sld);
std::vector slices;
if (split(*this, it->Shape(), shells, getValue(GV_PRECISION), slices) && slices.size() == styles.size()) {
for (size_t i = 0; i < slices.size(); ++i) {
result.push_back(IfcRepresentationShapeItem(it->ItemId(), it->Placement(), slices[i], styles[i] ? styles[i] : &it->Style()));
}
} else {
return false;
}
}
return true;
}
}
bool IfcGeom::Kernel::apply_layerset(const IfcRepresentationShapeItems& items, const std::vector& surfaces, const std::vector& styles, IfcRepresentationShapeItems& result) {
if (surfaces.size() < 3) {
return false;
} else if (surfaces.size() == 3) {
for (IfcRepresentationShapeItems::const_iterator it = items.begin(); it != items.end(); ++it) {
TopoDS_Shape a,b;
if (split_solid_by_surface(it->Shape(), surfaces[1], a, b)) {
result.push_back(IfcRepresentationShapeItem(it->ItemId(), it->Placement(), b, styles[0] ? styles[0] : &it->Style()));
result.push_back(IfcRepresentationShapeItem(it->ItemId(), it->Placement(), a, styles[1] ? styles[1] : &it->Style()));
} else {
continue;
}
}
return true;
} else {
/*
// Determine whether sequence of surfaces is consistent with surface normal, so that
// layer operations are applied in the correct order. This seems to be always the case.
Bnd_Box bb;
for (IfcRepresentationShapeItems::const_iterator it = items.begin(); it != items.end(); ++it) {
BRepBndLib::Add(it->Shape(), bb);
}
double x1, y1, z1, x2, y2, z2;
bb.Get(x1, y1, z1, x2, y2, z2);
gp_Pnt p1(x1, y1, z1);
gp_Pnt p2(x2, y2, z2);
gp_Pnt avg = (p1.XYZ() + p2.XYZ()) / 2.;
ShapeAnalysis_Surface sas1(surfaces[0]);
ShapeAnalysis_Surface sas2(surfaces[1]);
const gp_Pnt2d uv = sas1.ValueOfUV(avg, 1e-3);
gp_Pnt ps1, ps2, mass;
gp_Vec du1, dv1, du2, dv2;
surfaces[0]->D1(uv.X(), uv.Y(), ps1, du1, dv1);
const gp_Vec n1 = dv1.XYZ() ^ du1.XYZ();
const bool reversed = gp_Dir(ps2.XYZ() - ps1.XYZ()).Dot(n1) < 0.;
surfaces[surfaces.size() - 1]->D0(uv.X(), uv.Y(), mass);
mass.ChangeCoord() += n1.XYZ();
*/
for (IfcRepresentationShapeItems::const_iterator it = items.begin(); it != items.end(); ++it) {
const TopoDS_Shape& s = it->Shape();
TopoDS_Solid sld;
ensure_fit_for_subtraction(s, sld);
TopTools_ListOfShape operands;
for (unsigned i = 1; i < surfaces.size() - 1; ++i) {
double u1, v1, u2, v2;
if (!project(surfaces[i], sld, u1, v1, u2, v2)) {
return false;
}
TopoDS_Face face = BRepBuilderAPI_MakeFace(surfaces[i], u1, u2, v1, v2, 1.e-7).Face();
operands.Append(face);
}
std::vector slices;
if (split(*this, it->Shape(), operands, getValue(GV_PRECISION), slices) && slices.size() == styles.size()) {
for (size_t i = 0; i < slices.size(); ++i) {
result.push_back(IfcRepresentationShapeItem(it->ItemId(), it->Placement(), slices[i], styles[i] ? styles[i] : &it->Style()));
}
} else {
return false;
}
}
return true;
}
}
IfcSchema::IfcRepresentation* IfcGeom::Kernel::find_representation(const IfcSchema::IfcProduct* product, const std::string& identifier) {
if (!product->hasRepresentation()) 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).hasRepresentationIdentifier() && (**it).RepresentationIdentifier() == identifier) {
return *it;
}
}
return 0;
}
bool IfcGeom::Kernel::split_solid_by_surface(const TopoDS_Shape& input, const Handle_Geom_Surface& surface, TopoDS_Shape& front, TopoDS_Shape& back) {
// Use an unbounded surface, that isolate part of the input shape,
// to split this shape into two parts. Make sure that the addition
// of the two result volumes matches that of the input.
double u1, v1, u2, v2;
if (!project(surface, input, u1, v1, u2, v2)) {
return false;
}
TopoDS_Face face = BRepBuilderAPI_MakeFace(surface, u1, u2, v1, v2, 1.e-7).Face();
gp_Pnt p, p1, p2; gp_Vec vu, vv, n;
surface->D1((u1+u2)/2., (v1+v2)/2., p, vu, vv);
n = vu ^ vv;
p1 = p.Translated(-n);
TopoDS_Solid solid = BRepPrimAPI_MakeHalfSpace(face, p1).Solid();
const bool b = split_solid_by_shell(input, solid, front, back);
return b;
}
bool IfcGeom::Kernel::split_solid_by_shell(const TopoDS_Shape& input, const TopoDS_Shape& shell, TopoDS_Shape& front, TopoDS_Shape& back) {
// Use a shell, typically one or more connected faces, that isolate part
// of the input shape, to split this shape into two parts. Make sure that
// the addition of the two result volumes matches that of the input.
TopoDS_Solid solid;
if (shell.ShapeType() == TopAbs_SHELL) {
solid = BRepBuilderAPI_MakeSolid(TopoDS::Shell(shell)).Solid();
} else if (shell.ShapeType() == TopAbs_SOLID) {
solid = TopoDS::Solid(shell);
} else {
return false;
}
apply_tolerance(solid, getValue(GV_PRECISION));
#if OCC_VERSION_HEX >= 0x70300
TopTools_ListOfShape shapes;
#else
BOPCol_ListOfShape shapes;
#endif
shapes.Append(input);
shapes.Append(solid);
BOPAlgo_PaveFiller filler(new NCollection_IncAllocator); // TODO: Does this need to be freed?
filler.SetArguments(shapes);
filler.Perform();
front = BRepAlgoAPI_Cut(input, solid, filler);
back = BRepAlgoAPI_Common(input, solid, filler);
bool is_null[2];
for (int i = 0; i < 2; ++i) {
TopoDS_Shape& shape = i == 0 ? front : back;
const bool result_is_null = is_null[i] = shape.IsNull() != 0;
if (result_is_null) {
continue;
}
try {
ShapeFix_Shape fix(shape);
if (fix.Perform()) {
shape = fix.Shape();
}
} catch (const Standard_Failure& e) {
if (e.GetMessageString() && strlen(e.GetMessageString())) {
Logger::Error(e.GetMessageString());
} else {
Logger::Error("Unknown error performing fixes");
}
} catch (...) {
Logger::Error("Unknown error performing fixes");
}
BRepCheck_Analyzer analyser(shape);
bool is_valid = analyser.IsValid() != 0;
if (!is_valid) {
return false;
}
}
if (is_null[0] || is_null[1]) {
Logger::Message(Logger::LOG_ERROR, "Null result obtained from layerset slicing");
if (is_null[0] && is_null[1]) {
return false;
}
}
const double ab = shape_volume(input);
const double a = shape_volume(front);
const double b = shape_volume(back);
return ALMOST_THE_SAME(ab, a+b, 1.e-3);
}
bool IfcGeom::Kernel::project(const Handle_Geom_Surface& srf, const TopoDS_Shape& shp, double& u1, double& v1, double& u2, double& v2, double widen) {
// @todo std::unique_ptr for C++11
ShapeAnalysis_Surface* sas = 0;
Handle(Geom_Plane) pln;
if (srf->DynamicType() == STANDARD_TYPE(Geom_Plane)) {
// Optimize projection for specific cases
pln = Handle(Geom_Plane)::DownCast(srf);
} else if (srf->DynamicType() == STANDARD_TYPE(Geom_OffsetSurface) && Handle(Geom_OffsetSurface)::DownCast(srf)->BasisSurface()->DynamicType() == STANDARD_TYPE(Geom_Plane)) {
// For an offset planar surface the projected UV coords are the same as the basis surface
pln = Handle(Geom_Plane)::DownCast(Handle(Geom_OffsetSurface)::DownCast(srf)->BasisSurface());
} else {
sas = new ShapeAnalysis_Surface(srf);
}
u1 = v1 = +std::numeric_limits::infinity();
u2 = v2 = -std::numeric_limits::infinity();
gp_Pnt median;
int vertex_count = 0;
for (TopExp_Explorer exp(shp, TopAbs_VERTEX); exp.More(); exp.Next(), ++vertex_count) {
gp_Pnt p = BRep_Tool::Pnt(TopoDS::Vertex(exp.Current()));
median.ChangeCoord() += p.XYZ();
gp_Pnt2d uv;
if (sas) {
uv = sas->ValueOfUV(p, 1e-3);
} else {
gp_Vec d = p.XYZ() - pln->Position().Location().XYZ();
uv.SetX(d.Dot(pln->Position().XDirection()));
uv.SetY(d.Dot(pln->Position().YDirection()));
}
if (uv.X() < u1) u1 = uv.X();
if (uv.Y() < v1) v1 = uv.Y();
if (uv.X() > u2) u2 = uv.X();
if (uv.Y() > v2) v2 = uv.Y();
}
if (vertex_count > 0) {
// Add a little bit of resolution so that the median is shifted towards the mass
// of the curve. This helps to find the parameter ordering for conic surfaces.
for (TopExp_Explorer exp(shp, TopAbs_EDGE); exp.More(); exp.Next(), ++vertex_count) {
const TopoDS_Edge& e = TopoDS::Edge(exp.Current());
double a, b;
Handle_Geom_Curve crv = BRep_Tool::Curve(e, a, b);
gp_Pnt p;
crv->D0((a + b) / 2., p);
median.ChangeCoord() += p.XYZ();
}
median.ChangeCoord().Divide(vertex_count);
gp_Pnt2d uv;
if (sas) {
uv = sas->ValueOfUV(median, 1e-3);
} else {
gp_Vec d = median.XYZ() - pln->Position().Location().XYZ();
uv.SetX(d.Dot(pln->Position().XDirection()));
uv.SetY(d.Dot(pln->Position().YDirection()));
}
if (uv.X() < u1 || uv.X() > u2) {
std::swap(u1, u2);
}
u1 -= widen;
u2 += widen;
v1 -= widen;
v2 += widen;
}
delete sas;
return vertex_count > 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 = (IfcSchema::IfcGeometricRepresentationItem*) ((IfcSchema::IfcBooleanResult*) item)->FirstOperand();
if (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::IfcBaseClass* 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");
}
}
bool IfcGeom::Kernel::approximate_plane_through_wire(const TopoDS_Wire& wire, gp_Pln& plane, double eps) {
// Newell's Method is used for the normal calculation
// as a simple edge cross product can give opposite results
// for a concave face boundary.
// Reference: Graphics Gems III p. 231
const double eps_ = eps < 1. ? getValue(GV_PRECISION) : eps;
const double eps2 = eps_ * eps_;
double x = 0, y = 0, z = 0;
gp_Pnt current, previous, first;
gp_XYZ center;
int n = 0;
BRepTools_WireExplorer exp(wire);
for (;; exp.Next()) {
const bool has_more = exp.More() != 0;
if (has_more) {
const TopoDS_Vertex& v = exp.CurrentVertex();
current = BRep_Tool::Pnt(v);
center += current.XYZ();
} else {
current = first;
}
if (n) {
const double& xn = previous.X();
const double& yn = previous.Y();
const double& zn = previous.Z();
const double& xn1 = current.X();
const double& yn1 = current.Y();
const double& zn1 = current.Z();
x += (yn - yn1)*(zn + zn1);
y += (xn + xn1)*(zn - zn1);
z += (xn - xn1)*(yn + yn1);
} else {
first = current;
}
if (!has_more) {
break;
}
previous = current;
++n;
}
if (n < 3) {
return false;
}
plane = gp_Pln(center / n, gp_Dir(x, y, z));
exp.Init(wire);
for (; exp.More(); exp.Next()) {
const TopoDS_Vertex& v = exp.CurrentVertex();
current = BRep_Tool::Pnt(v);
if (plane.SquareDistance(current) > eps2) {
return false;
}
}
return true;
}
bool IfcGeom::Kernel::flatten_wire(TopoDS_Wire& wire) {
gp_Pln pln;
if (!approximate_plane_through_wire(wire, pln)) {
return false;
}
TopoDS_Face face = BRepBuilderAPI_MakeFace(pln).Face();
BRepAlgo_NormalProjection proj(face);
proj.Add(wire);
proj.Build();
if (!proj.IsDone()) {
return false;
}
TopTools_ListOfShape list;
proj.BuildWire(list);
if (list.Extent() != 1) {
return false;
}
wire = TopoDS::Wire(list.First());
return true;
}
bool IfcGeom::Kernel::triangulate_wire(const std::vector& wires, TopTools_ListOfShape& faces) {
// This is a bit of a precarious approach, but seems to work for the
// versions of OCCT tested for. OCCT has a Delaunay triangulation function
// BRepMesh_Delaun, but it is notoriously hard to interpret the results
// (due to the Bowyer-Watson super triangle perhaps?). Therefore
// alternatively we use the regular OCCT incremental mesher on a new face
// created from the UV coordinates of the original wire. Pray to our gods
// that the vertex coordinates are unaffected by the meshing algorithm and
// map them back to 3d coordinates when iterating over the mesh triangles.
// In addition, to maintain a manifold shell, we need to make sure that
// every edge from the input wire is used exactly once in the list of
// resulting faces. And that other internal edges are used twice.
typedef std::pair uv_node;
gp_Pln pln;
if (!approximate_plane_through_wire(wires.front(), pln, std::numeric_limits::infinity())) {
return false;
}
const gp_XYZ& udir = pln.Position().XDirection().XYZ();
const gp_XYZ& vdir = pln.Position().YDirection().XYZ();
const gp_XYZ& pnt = pln.Position().Location().XYZ();
std::map mapping;
std::map, TopoDS_Edge> existing_edges, new_edges;
std::unique_ptr mf;
for (auto it = wires.begin(); it != wires.end(); ++it) {
const TopoDS_Wire& wire = *it;
BRepTools_WireExplorer exp(wire);
BRepBuilderAPI_MakePolygon mp;
// Add UV coordinates to a newly created polygon
for (; exp.More(); exp.Next()) {
// Project onto plane
const TopoDS_Vertex& V = exp.CurrentVertex();
gp_Pnt p = BRep_Tool::Pnt(V);
double u = (p.XYZ() - pnt).Dot(udir);
double v = (p.XYZ() - pnt).Dot(vdir);
mp.Add(gp_Pnt(u, v, 0.));
mapping.insert(std::make_pair(std::make_pair(u, v), V));
// Store existing edges in a map so that triangles can
// actually reference the preexisting edges.
const TopoDS_Edge& e = exp.Current();
TopoDS_Vertex V0, V1;
TopExp::Vertices(e, V0, V1, true);
gp_Pnt p0 = BRep_Tool::Pnt(V0);
gp_Pnt p1 = BRep_Tool::Pnt(V1);
double u0 = (p0.XYZ() - pnt).Dot(udir);
double v0 = (p0.XYZ() - pnt).Dot(vdir);
double u1 = (p1.XYZ() - pnt).Dot(udir);
double v1 = (p1.XYZ() - pnt).Dot(vdir);
uv_node uv0 = std::make_pair(u0, v0);
uv_node uv1 = std::make_pair(u1, v1);
existing_edges.insert(std::make_pair(std::make_pair(uv0, uv1), e));
existing_edges.insert(std::make_pair(std::make_pair(uv1, uv0), TopoDS::Edge(e.Reversed())));
}
// Not closed by default
mp.Close();
if (mf) {
if (it - 1 == wires.begin()) {
// @todo is this necessary?
TopoDS_Face f = mf->Face();
mf->Init(f);
}
mf->Add(mp.Wire());
} else {
mf.reset(new BRepBuilderAPI_MakeFace(mp.Wire()));
}
}
const TopoDS_Face& face = mf->Face();
// Create a triangular mesh from the face
BRepMesh_IncrementalMesh(face, Precision::Confusion());
int n123[3];
TopLoc_Location loc;
Handle_Poly_Triangulation tri = BRep_Tool::Triangulation(face, loc);
if (!tri.IsNull()) {
const TColgp_Array1OfPnt& nodes = tri->Nodes();
const Poly_Array1OfTriangle& triangles = tri->Triangles();
for (int i = 1; i <= triangles.Length(); ++i) {
if (face.Orientation() == TopAbs_REVERSED)
triangles(i).Get(n123[2], n123[1], n123[0]);
else triangles(i).Get(n123[0], n123[1], n123[2]);
// Create polygons from the mesh vertices
BRepBuilderAPI_MakeWire mp2;
for (int j = 0; j < 3; ++j) {
uv_node uvnodes[2];
TopoDS_Vertex vs[2];
for (int k = 0; k < 2; ++k) {
const gp_Pnt& uv = nodes.Value(n123[(j + k) % 3]);
uvnodes[k] = std::make_pair(uv.X(), uv.Y());
auto it = mapping.find(uvnodes[k]);
if (it == mapping.end()) {
Logger::Error("Internal error: unable to unproject uv-mesh");
return false;
}
vs[k] = it->second;
}
auto it = existing_edges.find(std::make_pair(uvnodes[0], uvnodes[1]));
if (it != existing_edges.end()) {
// This is a boundary edge, reuse existing edge from wire
mp2.Add(it->second);
} else {
auto jt = new_edges.find(std::make_pair(uvnodes[0], uvnodes[1]));
if (jt != new_edges.end()) {
// We have already added the reverse as part of another
// triangle, reuse this edge.
mp2.Add(TopoDS::Edge(jt->second));
} else {
// This is a new internal edge. Register the reverse
// for reuse later. We need to be sure to reuse vertices
// for the edge construction because otherwise the wire
// builder will use geometrical proximity for vertex
// connections in which case the edge will be copied
// and no longer partner with other edges from the shell.
TopoDS_Edge ne = BRepBuilderAPI_MakeEdge(vs[0], vs[1]);
mp2.Add(ne);
// Store the reverse to be picked up later.
new_edges.insert(std::make_pair(std::make_pair(uvnodes[1], uvnodes[0]), TopoDS::Edge(ne.Reversed())));
}
}
}
BRepBuilderAPI_MakeFace mft(mp2.Wire());
if (mft.IsDone()) {
TopoDS_Face triangle_face = mft.Face();
TopoDS_Iterator jt(triangle_face, false);
for (; jt.More(); jt.Next()) {
const TopoDS_Wire& w = TopoDS::Wire(jt.Value());
if (w.Orientation() != wires.front().Orientation()) {
triangle_face.Reverse();
}
}
faces.Append(triangle_face);
} else {
Logger::Error("Internal error: missing face");
return false;
}
}
}
TopTools_IndexedDataMapOfShapeListOfShape mape, mapn;
for (auto& wire : wires) {
TopExp::MapShapesAndAncestors(wire, TopAbs_EDGE, TopAbs_WIRE, mape);
}
TopTools_ListIteratorOfListOfShape it(faces);
for (; it.More(); it.Next()) {
TopExp::MapShapesAndAncestors(it.Value(), TopAbs_EDGE, TopAbs_WIRE, mapn);
}
// Validation
for (int i = 1; i <= mape.Extent(); ++i) {
#if OCC_VERSION_HEX >= 0x70000
TopTools_ListOfShape val;
if (!mapn.FindFromKey(mape.FindKey(i), val)) {
#else
bool contains = false;
try {
TopTools_ListOfShape val = mapn.FindFromKey(mape.FindKey(i));
contains = true;
} catch (Standard_NoSuchObject&) {}
if (!contains) {
#endif
// All existing edges need to exist in the new faces
Logger::Error("Internal error, missing edge from triangulation");
if (faceset_helper_ != nullptr) {
faceset_helper_->non_manifold() = true;
}
}
}
for (int i = 1; i <= mapn.Extent(); ++i) {
const TopoDS_Shape& v = mapn.FindKey(i);
int n = mapn.FindFromIndex(i).Extent();
// Existing edges are boundaries with use 1
// New edges are internal with use 2
if (n != (mape.Contains(v) ? 1 : 2)) {
Logger::Error("Internal error, non-manifold result from triangulation");
if (faceset_helper_ != nullptr) {
faceset_helper_->non_manifold() = true;
}
}
}
return true;
}
TopoDS_Shape IfcGeom::Kernel::apply_transformation(const TopoDS_Shape& s, const gp_Trsf& t) {
if (t.Form() == gp_Identity) {
return s;
} else {
/// @todo set to 1. and exactly 1. or use epsilon?
if (t.ScaleFactor() != 1.) {
return BRepBuilderAPI_Transform(s, t, true);
} else {
return s.Moved(t);
}
}
}
TopoDS_Shape IfcGeom::Kernel::apply_transformation(const TopoDS_Shape& s, const gp_GTrsf& t) {
if (t.Form() == gp_Other) {
return BRepBuilderAPI_GTransform(s, t, true);
} else {
return apply_transformation(s, t.Trsf());
}
}
namespace {
/*
* A small helper utility to wrap around a numeric range
*/
class bounded_int {
private:
int i;
size_t n;
public:
bounded_int(int i, size_t n) : i(i), n(n) {}
bounded_int& operator--() {
--i;
if (i == -1) {
i = n - 1;
}
return *this;
}
bounded_int& operator++() {
++i;
if (i == (int) n) {
i = 0;
}
return *this;
}
operator int() { return i; }
};
}
bool IfcGeom::Kernel::wire_intersections(const TopoDS_Wire& wire, TopTools_ListOfShape& wires) {
if (getValue(GV_NO_WIRE_INTERSECTION_CHECK) > 0.) {
return false;
}
if (!wire.Closed()) {
wires.Append(wire);
return false;
}
int n = count(wire, TopAbs_EDGE);
if (n < 3) {
wires.Append(wire);
return false;
}
// Note: initialize empty
Handle(ShapeExtend_WireData) wd = new ShapeExtend_WireData();
// ... to be sure to get consecutive edges
BRepTools_WireExplorer exp(wire);
IfcGeom::impl::tree tree;
int edge_idx = 0;
for (; exp.More(); exp.Next()) {
wd->Add(exp.Current());
if (n > 64) {
// tfk: indices in tree are 0-based vd 1-based in wiredata
tree.add(edge_idx++, exp.Current());
}
}
if (wd->NbEdges() != n) {
// If the number of edges differs, BRepTools_WireExplorer did not
// reach every edge, probably due to loops exactly at vertex locations.
// This is not supported by this algorithm which only elimates loops
// due to edge crossings.
throw geometry_exception("Invalid loop");
}
bool intersected = false;
// tfk: Extrema on infinite curves proved to be more robust.
// TopoDS_Face face = BRepBuilderAPI_MakeFace(wire, true).Face();
// ShapeAnalysis_Wire saw(wd, face, getValue(GV_PRECISION));
double eps = 0;
if (getValue(GV_NO_WIRE_INTERSECTION_TOLERANCE) < 0.) {
eps = faceset_helper_
// eps is added to both ends of the parametric domain, so 3. is chosen to be on the safe side here.
? (faceset_helper_->epsilon() / 3.)
// @todo re-evaluate 2. here for the reasons above:
: (std::min)(min_edge_length(wire) / 2., getValue(GV_PRECISION) * 10.);
}
for (int i = 2; i < n; ++i) {
std::vector