diff --git a/src/ifcgeom/IfcGeomWires.cpp b/src/ifcgeom/IfcGeomWires.cpp index e1e2ed4781..99d0332c0c 100644 --- a/src/ifcgeom/IfcGeomWires.cpp +++ b/src/ifcgeom/IfcGeomWires.cpp @@ -85,9 +85,161 @@ #include #include +#include + #include "../ifcgeom/IfcGeom.h" +namespace { + // Returns the other vertex of an edge + TopoDS_Vertex other(const TopoDS_Edge& e, const TopoDS_Vertex& v) { + TopoDS_Vertex a, b; + TopExp::Vertices(e, a, b); + return v.IsSame(b) ? a : b; + } + + // Returns new wire with the edge replaced by a linear edge with the vertex v moved to p + TopoDS_Wire adjust(const TopoDS_Wire& w, const TopoDS_Edge& e, const TopoDS_Vertex& v, const gp_Pnt& p) { + gp_Pnt p1 = p; + gp_Pnt p2 = BRep_Tool::Pnt(other(e, v)); + + if (e.Orientation() == TopAbs_REVERSED) { + std::swap(p1, p2); + } + + // Already asserted this is a replacement for a linear edge + TopoDS_Edge new_edge = BRepBuilderAPI_MakeEdge(p1, p2).Edge(); + new_edge.Orientation(e.Orientation()); + + ShapeBuild_ReShape reshape; + reshape.Replace(e, new_edge); + + return TopoDS::Wire(reshape.Apply(w)); + } + + // A wrapper around BRepBuilderAPI_MakeWire that makes sure segments are connected either by moving end points or by adding intermediate segments + class wire_builder { + private: + BRepBuilderAPI_MakeWire mw_; + double p_; + bool skip_next_; + const IfcUtil::IfcBaseClass* inst_; + + public: + wire_builder(double p, const IfcUtil::IfcBaseClass* inst = 0) : p_(p), skip_next_(false), inst_(inst) {} + + void operator()(const TopoDS_Shape& a) { + if (skip_next_) { + // tfk: not ideal, adjusting both start and end points not supported now. + skip_next_ = false; + return; + } + + mw_.Add(TopoDS::Wire(a)); + } + + void operator()(const TopoDS_Shape& a, const TopoDS_Shape& b, bool last) { + if (skip_next_) { + // tfk: not ideal, adjusting both start and end points not supported now. + skip_next_ = false; + return; + } + + const TopoDS_Wire& w1 = TopoDS::Wire(a); + const TopoDS_Wire& w2 = TopoDS::Wire(b); + + TopoDS_Vertex w11, w12, w21, w22; + TopExp::Vertices(w1, w11, w12); + TopExp::Vertices(w2, w21, w22); + + gp_Pnt p1 = BRep_Tool::Pnt(w12); + gp_Pnt p2 = BRep_Tool::Pnt(w21); + + double dist = p1.Distance(p2); + TopTools_IndexedDataMapOfShapeListOfShape wmap1, wmap2; + + // Distance is within 2p, this is fine + if (dist < 2. * p_) { + mw_.Add(w1); + goto check; + } + + // Distance is too large for attempting to move end points, add intermediate edge + if (dist > 1000. * p_) { + mw_.Add(w1); + mw_.Add(BRepBuilderAPI_MakeEdge(p1, p2)); + Logger::Message(Logger::LOG_ERROR, "Added additional segment to close gap with length " + boost::lexical_cast(dist) + " to:", inst_->entity); + goto check; + } + + // Find edges connected to end- and begin vertex + TopExp::MapShapesAndAncestors(w1, TopAbs_VERTEX, TopAbs_EDGE, wmap1); + TopExp::MapShapesAndAncestors(w2, TopAbs_VERTEX, TopAbs_EDGE, wmap2); + + const TopTools_ListOfShape& last_edges = wmap1.FindFromKey(w12); + const TopTools_ListOfShape& first_edges = wmap2.FindFromKey(w21); + + double _, __; + if (last_edges.Extent() == 1 && first_edges.Extent() == 1) { + Handle(Geom_Curve) c1 = BRep_Tool::Curve(TopoDS::Edge(last_edges.First()), _, __); + Handle(Geom_Curve) c2 = BRep_Tool::Curve(TopoDS::Edge(first_edges.First()), _, __); + + const bool is_line1 = c1->DynamicType() == STANDARD_TYPE(Geom_Line); + const bool is_line2 = c2->DynamicType() == STANDARD_TYPE(Geom_Line); + + // Adjust the segment that is linear + if (is_line1) { + mw_.Add(adjust(w1, TopoDS::Edge(last_edges.First()), w12, p2)); + Logger::Message(Logger::LOG_ERROR, "Adjusted edge end-point with distance " + boost::lexical_cast(dist) + " on:", inst_->entity); + } else if (is_line2 && !last) { + // tfk: not ideal, begin point of first edge cannot be adjusted now for cyclic wires + mw_.Add(w1); + mw_.Add(adjust(w1, TopoDS::Edge(last_edges.First()), w12, p2)); + skip_next_ = true; + Logger::Message(Logger::LOG_ERROR, "Adjusted edge end-point with distance " + boost::lexical_cast(dist) + " on:", inst_->entity); + } else { + // If both aren't linear an edge is added + mw_.Add(w1); + mw_.Add(BRepBuilderAPI_MakeEdge(p1, p2)); + Logger::Message(Logger::LOG_ERROR, "Added additional segment to close gap with length " + boost::lexical_cast(dist) + " to:", inst_->entity); + } + } else { + Logger::Error("Internal error, inconsistent wire segments", inst_->entity); + mw_.Add(w1); + } + + check: + if (mw_.Error() == BRepBuilderAPI_NonManifoldWire) { + Logger::Error("Non-manifold curve segments:", inst_->entity); + } else if (mw_.Error() == BRepBuilderAPI_DisconnectedWire) { + Logger::Error("Failed to join curve segments:", inst_->entity); + } + } + + const TopoDS_Wire& wire() { return mw_.Wire(); } + }; + + template + void shape_pair_enumerate(TopTools_ListIteratorOfListOfShape& it, Fn& fn, bool closed) { + bool is_first = true; + TopoDS_Shape first, previous, current; + for (; it.More(); it.Next(), is_first = false) { + current = it.Value(); + if (is_first) { + first = current; + } else { + fn(previous, current, false); + } + previous = current; + } + if (closed) { + fn(current, first, true); + } else { + fn(current); + } + } +} + bool IfcGeom::Kernel::convert(const IfcSchema::IfcCompositeCurve* l, TopoDS_Wire& wire) { if ( getValue(GV_PLANEANGLE_UNIT)<0 ) { Logger::Message(Logger::LOG_WARNING,"Creating a composite curve without unit information:",l->entity); @@ -165,109 +317,45 @@ bool IfcGeom::Kernel::convert(const IfcSchema::IfcCompositeCurve* l, TopoDS_Wire return use_radians || use_degrees; } - BRepBuilderAPI_MakeWire w; - TopoDS_Vertex wire_first_vertex, wire_last_vertex, edge_first_vertex, edge_last_vertex; + IfcSchema::IfcCompositeCurveSegment::list::ptr segments = l->Segments(); - const double precision_sq_2 = 2 * getValue(GV_PRECISION) * getValue(GV_PRECISION); + TopTools_ListOfShape converted_segments; - for(IfcSchema::IfcCompositeCurveSegment::list::it it = segments->begin(); it != segments->end(); ++it) { - + for (IfcSchema::IfcCompositeCurveSegment::list::it it = segments->begin(); it != segments->end(); ++it) { + IfcSchema::IfcCurve* curve = (*it)->ParentCurve(); TopoDS_Wire segment; - + if (!convert_wire(curve, segment)) { Logger::Message(Logger::LOG_ERROR, "Failed to convert curve:", curve->entity); continue; } - + if (!(*it)->SameSense()) { segment.Reverse(); } - + ShapeFix_ShapeTolerance FTol; FTol.SetTolerance(segment, getValue(GV_PRECISION), TopAbs_WIRE); - - TopExp::Vertices(segment, edge_first_vertex, edge_last_vertex); - if (it == segments->begin()) { - wire_first_vertex = edge_first_vertex; - } else { - gp_Pnt first = BRep_Tool::Pnt(edge_first_vertex); - gp_Pnt last = BRep_Tool::Pnt(wire_last_vertex); + converted_segments.Append(segment); - Standard_Real distance = first.SquareDistance(last); - if (distance > precision_sq_2) { - w.Add(BRepBuilderAPI_MakeEdge(wire_last_vertex, edge_first_vertex)); - - Logger::Message(Logger::LOG_ERROR, "Closed gap on:", l->entity); - } - } - - w.Add(segment); - - if ( w.Error() != BRepBuilderAPI_WireDone ) { - if (w.Error() == BRepBuilderAPI_NonManifoldWire) { - - Logger::Message(Logger::LOG_ERROR, "Non-manifold curve segments:", l->entity); - - } else if (w.Error() == BRepBuilderAPI_DisconnectedWire) { - - Logger::Message(Logger::LOG_ERROR, "Failed to join curve segments:", l->entity); - - gp_Pnt p1, p2; - int precision = 4; - double d = 0.; - - if (!wire_last_vertex.IsNull()) { - p1 = BRep_Tool::Pnt(wire_last_vertex); - } - if (!edge_first_vertex.IsNull()) { - p2 = BRep_Tool::Pnt(edge_first_vertex); - } - if (!wire_last_vertex.IsNull() && !edge_first_vertex.IsNull()) { - d = p1.Distance(p2); - precision = ceil(-log10(d)) + 3; - } - - if (!wire_last_vertex.IsNull()) { - std::stringstream ss; - ss << std::setprecision(precision) << "Last vertex at (" << p1.X() << " " << p1.Y() << " " << p1.Z() << ")"; - Logger::Message(Logger::LOG_NOTICE, ss.str()); - } - - if (!edge_first_vertex.IsNull()) { - std::stringstream ss; - ss << std::setprecision(precision) << "Segment starts at (" << p2.X() << " " << p2.Y() << " " << p2.Z() << ")"; - if (d > 0.) { - ss << ", distance " << d << " > precision " << std::fixed << getValue(GV_PRECISION) / 10.; - } - ss << " for:"; - Logger::Message(Logger::LOG_NOTICE, ss.str(), (*it)->entity); - } - } - - return false; - } - - wire_last_vertex = edge_last_vertex; } + BRepBuilderAPI_MakeWire w; + TopoDS_Vertex wire_first_vertex, wire_last_vertex, edge_first_vertex, edge_last_vertex; + + const double precision_sq_2 = 2 * getValue(GV_PRECISION) * getValue(GV_PRECISION); + + TopTools_ListIteratorOfListOfShape it(converted_segments); + IfcEntityList::ptr profile = l->entity->getInverse(IfcSchema::Type::IfcProfileDef, -1); + const bool force_close = profile && profile->size() > 0; - if (profile && profile->size() > 0) { - gp_Pnt first = BRep_Tool::Pnt(edge_last_vertex); - gp_Pnt last = BRep_Tool::Pnt(wire_first_vertex); - - Standard_Real distance = first.SquareDistance(last); - if (distance > precision_sq_2) { - w.Add(BRepBuilderAPI_MakeEdge(edge_last_vertex, wire_first_vertex)); - - Logger::Message(Logger::LOG_ERROR, "Closed gap on:", l->entity); - } - } - - wire = w.Wire(); + wire_builder bld(getValue(GV_PRECISION), l); + shape_pair_enumerate(it, bld, force_close); + wire = bld.wire(); return true; }