diff --git a/src/Mod/Part/App/CMakeLists.txt b/src/Mod/Part/App/CMakeLists.txt index ab4cd149a9..dd95907b49 100644 --- a/src/Mod/Part/App/CMakeLists.txt +++ b/src/Mod/Part/App/CMakeLists.txt @@ -554,6 +554,8 @@ SET(Part_SRCS FaceMakerCheese.h FaceMakerBullseye.cpp FaceMakerBullseye.h + WireJoiner.cpp + WireJoiner.h ) if(FREECAD_USE_PCH) diff --git a/src/Mod/Part/App/WireJoiner.cpp b/src/Mod/Part/App/WireJoiner.cpp new file mode 100644 index 0000000000..80baa650b3 --- /dev/null +++ b/src/Mod/Part/App/WireJoiner.cpp @@ -0,0 +1,2436 @@ +/**************************************************************************** + * Copyright (c) 2022 Zheng Lei (realthunder) * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library 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 * + * GNU Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ****************************************************************************/ + +#include "PreCompiled.h" +#include +#include + +#ifndef _PreComp_ +# 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 +#endif + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "WireJoiner.h" + +#include "Geometry.h" +#include "PartFeature.h" +#include "TopoShapeOpCode.h" +#include "TopoShapeMapper.h" + +namespace bg = boost::geometry; +namespace bgi = boost::geometry::index; + +using RParameters = bgi::linear<16>; + +BOOST_GEOMETRY_REGISTER_POINT_3D_GET_SET( + gp_Pnt,double,bg::cs::cartesian,X,Y,Z,SetX,SetY,SetZ) + +FC_LOG_LEVEL_INIT("WireJoiner",true, true) + +using namespace Part; + +static inline void getEndPoints(const TopoDS_Edge &e, gp_Pnt &p1, gp_Pnt &p2) { + p1 = BRep_Tool::Pnt(TopExp::FirstVertex(e)); + p2 = BRep_Tool::Pnt(TopExp::LastVertex(e)); +} + +static inline void getEndPoints(const TopoDS_Wire &wire, gp_Pnt &p1, gp_Pnt &p2) { + BRepTools_WireExplorer xp(wire); + p1 = BRep_Tool::Pnt(TopoDS::Vertex(xp.CurrentVertex())); + for(;xp.More();xp.Next()); + p2 = BRep_Tool::Pnt(TopoDS::Vertex(xp.CurrentVertex())); +} + +static void _assertCheck(int line, bool cond, const char *msg) +{ + if (!cond) { + _FC_ERR(__FILE__, line, "Assert failed: " << msg); + throw Base::RuntimeError("Assertion failed"); + } +} + +#define assertCheck(cond) _assertCheck(__LINE__, cond, #cond) + +class WireJoiner::WireJoinerP { +public: + double myTol = Precision::Confusion(); + double myTol2 = myTol * myTol; + double myAngularTol = Precision::Angular(); + bool doSplitEdge = true; + bool doMergeEdge = true; + bool doOutline = false; + bool doTightBound = true; + + std::string catchObject; + int catchIteration; + int iteration = 0; + + typedef bg::model::box Box; + + bool checkBBox(const Bnd_Box &box) + { + if (box.IsVoid()) + return false; + Standard_Real xMin, yMin, zMin, xMax, yMax, zMax; + box.Get(xMin, yMin, zMin, xMax, yMax, zMax); + return zMax - zMin <= myTol; + } + + WireJoinerP() + { + auto hParam = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/WireJoiner"); + catchObject = hParam->GetASCII("ObjectName"); + catchIteration = hParam->GetInt("Iteration", 0); + } + + bool getBBox(const TopoDS_Shape &e, Bnd_Box &bound) { + BRepBndLib::AddOptimal(e,bound,Standard_False); + if (bound.IsVoid()) { + if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) + FC_WARN("failed to get bound of edge"); + return false; + } + if (!checkBBox(bound)) + showShape(e, "invalid"); + if (bound.SquareExtent() < myTol2) + return false; + bound.Enlarge(myTol); + return true; + } + + bool getBBox(const TopoDS_Shape &e, Box &box) { + Bnd_Box bound; + if (!getBBox(e, bound)) + return false; + Standard_Real xMin, yMin, zMin, xMax, yMax, zMax; + bound.Get(xMin, yMin, zMin, xMax, yMax, zMax); + box = Box(gp_Pnt(xMin,yMin,zMin), gp_Pnt(xMax,yMax,zMax)); + return true; + } + + struct WireInfo; + struct EdgeSet; + + struct EdgeInfo { + TopoDS_Edge edge; + TopoDS_Wire superEdge; + mutable TopoDS_Shape edgeReversed; + mutable TopoDS_Shape superEdgeReversed; + gp_Pnt p1; + gp_Pnt p2; + gp_Pnt mid; + Box box; + int iStart[2]; // adjacent list index start for p1 and p2 + int iEnd[2]; // adjacent list index end + int iteration; + int iteration2; + bool queryBBox; + std::shared_ptr wireInfo; + std::shared_ptr wireInfo2; // an edge can be shared by at most two tight bound wires. + std::unique_ptr geo; + Standard_Real firstParam; + Standard_Real lastParam; + Handle_Geom_Curve curve; + GeomAbs_CurveType type; + bool isLinear; + + EdgeInfo(const TopoDS_Edge &e, + const gp_Pnt &pt1, + const gp_Pnt &pt2, + const Box &bound, + bool bbox, + bool isLinear) + :edge(e),p1(pt1),p2(pt2),box(bound),queryBBox(bbox),isLinear(isLinear) + { + curve = BRep_Tool::Curve(e, firstParam, lastParam); + type = GeomAdaptor_Curve(curve).GetType(); + assertCheck(!curve.IsNull()); + GeomLProp_CLProps prop(curve,(firstParam+lastParam)*0.5,0,Precision::Confusion()); + mid = prop.Value(); + + iteration = 0; + reset(); + } + Geometry *geometry() { + if (!geo) + geo = Geometry::fromShape(edge, /*silent*/true); + return geo.get(); + } + void reset() { + wireInfo.reset(); + wireInfo2.reset(); + if (iteration >= 0) + iteration = 0; + iteration2 = 0; + iStart[0] = iStart[1] = iEnd[0] = iEnd[1] = -1; + } + const TopoDS_Shape &shape(bool forward=true) const + { + if (superEdge.IsNull()) { + if (forward) + return edge; + if (edgeReversed.IsNull()) + edgeReversed = edge.Reversed(); + return edgeReversed; + } + if (forward) + return superEdge; + if (superEdgeReversed.IsNull()) + superEdgeReversed = superEdge.Reversed(); + return superEdgeReversed; + } + TopoDS_Wire wire() const + { + auto s = shape(); + if (s.ShapeType() == TopAbs_WIRE) + return TopoDS::Wire(s); + return BRepBuilderAPI_MakeWire(TopoDS::Edge(s)).Wire(); + } + }; + + template + struct VectorSet { + void sort() + { + if (!sorted) { + sorted = true; + std::sort(data.begin(), data.end()); + } + } + bool contains(const T &v) + { + if (!sorted) { + if (data.size() < 30) + return std::find(data.begin(), data.end(), v) != data.end(); + sort(); + } + auto it = std::lower_bound(data.begin(), data.end(), v); + return it!=data.end() && *it == v; + } + bool intersects(const VectorSet &other) + { + if (other.size() < size()) + return other.intersects(*this); + else if (!sorted) { + for (const auto &v : data) { + if (other.contains(v)) + return true; + } + } else { + other.sort(); + auto it = other.data.begin(); + for (const auto &v : data) { + it = std::lower_bound(it, other.data.end(), v); + if (it == other.data.end()) + return false; + if (*it == v) + return true; + } + } + return false; + } + void insert(const T &v) + { + if (sorted) + data.insert(std::upper_bound(data.begin(), data.end(), v), v); + else + data.push_back(v); + } + bool insertUnique(const T &v) + { + if (sorted) { + auto it = std::lower_bound(data.begin(), data.end(), v); + if (it != data.end() && *it == v) + return false; + data.insert(it, v); + return true; + } + + if (contains(v)) + return false; + data.push_back(v); + return true; + } + void erase(const T &v) + { + if (!sorted) + data.erase(std::remove(data.begin(), data.end(), v), data.end()); + else { + auto it = std::lower_bound(data.begin(), data.end(), v); + auto itEnd = it; + while (itEnd!=data.end() && *itEnd==v) + ++itEnd; + data.erase(it, itEnd); + } + if (data.size() < 20) + sorted = false; + } + void clear() + { + data.clear(); + sorted = false; + } + std::size_t size() const + { + return data.size(); + } + bool empty() const + { + return data.empty(); + } + private: + bool sorted = false; + std::vector data; + }; + + Handle(BRepTools_History) aHistory = new BRepTools_History; + + typedef std::list Edges; + Edges edges; + + std::map edgesTable; + + struct VertexInfo { + Edges::iterator it; + bool start; + VertexInfo() + {} + VertexInfo(Edges::iterator it, bool start) + :it(it),start(start) + {} + VertexInfo reversed() const { + return VertexInfo(it, !start); + } + bool operator==(const VertexInfo &other) const { + return it==other.it && start==other.start; + } + bool operator<(const VertexInfo &other) const { + auto a = edgeInfo(); + auto b = other.edgeInfo(); + if (a < b) + return true; + if (a > b) + return false; + return start < other.start; + } + const gp_Pnt &pt() const { + return start?it->p1:it->p2; + } + gp_Pnt &pt() { + return start?it->p1:it->p2; + } + const gp_Pnt &ptOther() const { + return start?it->p2:it->p1; + } + gp_Pnt &ptOther() { + return start?it->p2:it->p1; + } + TopoDS_Vertex vertex() const { + return start ? TopExp::FirstVertex(edge()) : TopExp::LastVertex(edge()); + } + TopoDS_Vertex otherVertex() const { + return start ? TopExp::LastVertex(edge()) : TopExp::FirstVertex(edge()); + } + EdgeInfo *edgeInfo() const { + return &(*it); + } + const TopoDS_Edge &edge() const { + return it->edge; + } + }; + + struct StackInfo { + size_t iStart; + size_t iEnd; + size_t iCurrent; + StackInfo(size_t idx=0):iStart(idx),iEnd(idx),iCurrent(idx){} + }; + + std::vector stack; + std::vector vertexStack; + std::vector tmpVertices; + std::vector adjacentList; + + struct WireInfo { + std::vector vertices; + mutable std::vector sorted; + TopoDS_Wire wire; + TopoDS_Face face; + mutable Bnd_Box box; + bool done = false; + bool purge = false; + + void sort() const + { + if (sorted.size() == vertices.size()) + return; + assertCheck(sorted.size() < vertices.size()); + sorted.reserve(vertices.size()); + for (int i=(int)sorted.size(); i<(int)vertices.size(); ++i) + sorted.push_back(i); + std::sort(sorted.begin(), sorted.end(), [&](int a, int b) { + return vertices[a] < vertices[b]; + }); + } + int find(const VertexInfo &info) const + { + if (vertices.size() < 20) { + auto it = std::find(vertices.begin(), vertices.end(), info); + if (it == vertices.end()) + return 0; + return it - vertices.begin() + 1; + } + sort(); + auto it = std::lower_bound(sorted.begin(), sorted.end(), info, + [&](int idx, const VertexInfo &v) {return vertices[idx]edgeInfo() == info) + return it - vertices.begin() + 1; + } + return 0; + } + sort(); + auto it = std::lower_bound(sorted.begin(), sorted.end(), info, + [&](int idx, const EdgeInfo *v) {return vertices[idx].edgeInfo() { + }; + EdgeSet edgeSet; + + struct WireSet: VectorSet { + }; + WireSet wireSet; + + const Bnd_Box &getWireBound(const WireInfo &wireInfo) + { + if (wireInfo.box.IsVoid()) { + for (auto &v : wireInfo.vertices) + BRepBndLib::Add(v.it->shape(),wireInfo.box); + wireInfo.box.Enlarge(myTol); + } + return wireInfo.box; + } + + bool initWireInfo(WireInfo &wireInfo) + { + if (!wireInfo.face.IsNull()) + return true; + getWireBound(wireInfo); + if (wireInfo.wire.IsNull()) { + wireData->Clear(); + for (auto &v : wireInfo.vertices) + wireData->Add(v.it->shape(v.start)); + wireInfo.wire = makeCleanWire(); + } + + if (!BRep_Tool::IsClosed(wireInfo.wire)) { + showShape(wireInfo.wire, "FailedToClose"); + FC_ERR("Wire not closed"); + for (auto &v : wireInfo.vertices) { + showShape(v.edgeInfo(), v.start ? "failed" : "failed_r", iteration); + } + return false; + } + + BRepBuilderAPI_MakeFace mkFace(wireInfo.wire); + if (!mkFace.IsDone()) { + FC_ERR("Failed to create face for wire"); + showShape(wireInfo.wire, "FailedFace"); + return false; + } + wireInfo.face = mkFace.Face(); + return true; + } + + bool isInside(const WireInfo &wireInfo, gp_Pnt &pt) + { + if (getWireBound(wireInfo).IsOut(pt)) + return false; + BRepClass_FaceClassifier fc(wireInfo.face, pt, myTol); + return fc.State() == TopAbs_IN; + } + + bool isOutside(const WireInfo &wireInfo, gp_Pnt &pt) + { + if (getWireBound(wireInfo).IsOut(pt)) + return false; + BRepClass_FaceClassifier fc(wireInfo.face, pt, myTol); + return fc.State() == TopAbs_OUT; + } + + struct PntGetter + { + typedef const gp_Pnt& result_type; + result_type operator()(const VertexInfo &v) const { + return v.pt(); + } + }; + + bgi::rtree vmap; + + struct BoxGetter + { + typedef const Box& result_type; + result_type operator()(Edges::iterator it) const { + return it->box; + } + }; + bgi::rtree boxMap; + + BRep_Builder builder; + TopoDS_Compound compound; + + std::unordered_set sourceEdges; + std::vector sourceEdgeArray; + TopoDS_Compound openWireCompound; + + Handle(ShapeExtend_WireData) wireData = new ShapeExtend_WireData(); + + void clear() + { + aHistory->Clear(); + iteration = 0; + boxMap.clear(); + vmap.clear(); + edges.clear(); + edgeSet.clear(); + wireSet.clear(); + adjacentList.clear(); + stack.clear(); + tmpVertices.clear(); + vertexStack.clear(); + builder.MakeCompound(compound); + openWireCompound.Nullify(); + } + + Edges::iterator remove(Edges::iterator it) + { + if (it->queryBBox) + boxMap.remove(it); + vmap.remove(VertexInfo(it,true)); + vmap.remove(VertexInfo(it,false)); + return edges.erase(it); + } + + void remove(EdgeInfo *info) + { + if (edgesTable.empty()) { + for (auto it=edges.begin(); it!=edges.end(); ++it) + edgesTable[&(*it)] = it; + } + auto it = edgesTable.find(info); + if (it != edgesTable.end()) { + remove(it->second); + edgesTable.erase(it); + } + } + + void add(Edges::iterator it) + { + vmap.insert(VertexInfo(it,true)); + vmap.insert(VertexInfo(it,false)); + if (it->queryBBox) + boxMap.insert(it); + showShape(it->edge, "add"); + } + + int add(const TopoDS_Edge &e, bool queryBBox=false) + { + auto it = edges.begin(); + return add(e, queryBBox, it); + } + + int add(const TopoDS_Edge &e, bool queryBBox, Edges::iterator &it) + { + Box bbox; + if (!getBBox(e, bbox)) { + showShape(e, "small"); + aHistory->Remove(e); + return 0; + } + return add(e, queryBBox, bbox, it) ? 1 : -1; + } + + bool add(const TopoDS_Edge &e, bool queryBBox, const Box &bbox, Edges::iterator &it) + { + gp_Pnt p1,p2; + getEndPoints(e,p1,p2); + TopoDS_Vertex v1, v2; + TopoDS_Edge ev1, ev2; + double tol = myTol2; + // search for duplicate edges + showShape(e, "addcheck"); + bool isLinear = TopoShape(e).isLinearEdge(); + std::unique_ptr geo; + + for (auto vit=vmap.qbegin(bgi::nearest(p1,INT_MAX));vit!=vmap.qend();++vit) { + auto &vinfo = *vit; + if (canShowShape()) + FC_MSG("addcheck " << vinfo.edge().HashCode(INT_MAX)); + double d1 = vinfo.pt().SquareDistance(p1); + if (d1 >= tol) + break; + if (v1.IsNull()) { + ev1 = vinfo.edge(); + v1 = vinfo.vertex(); + } + double d2 = vinfo.ptOther().SquareDistance(p2); + if (d2 < tol) { + if (v2.IsNull()) { + ev2 = vinfo.edge(); + v2 = vinfo.otherVertex(); + } + if (isLinear && vinfo.edgeInfo()->isLinear) { + showShape(e, "duplicate"); + aHistory->Remove(e); + return false; + } + else if (auto geoEdge = vinfo.edgeInfo()->geometry()) { + if (!geo) + geo = Geometry::fromShape(e, /*silent*/true); + if (geo && geo->isSame(*geoEdge, myTol, myAngularTol)) { + showShape(e, "duplicate"); + aHistory->Remove(e); + return false; + } + } + } + } + if (v2.IsNull()) { + for (auto vit=vmap.qbegin(bgi::nearest(p2,1));vit!=vmap.qend();++vit) { + auto &vinfo = *vit; + double d1 = vinfo.pt().SquareDistance(p2); + if (d1 < tol) { + v2 = vit->vertex(); + ev2 = vit->edge(); + } + } + } + + // Make sure coincident vertices are actually the same TopoDS_Vertex, + // which is crucial for the OCC internal shape hierarchy structure. We + // achieve this by making a temp wire and let OCC do the hard work of + // replacing the vertex. + auto connectEdge = [&](TopoDS_Edge &e, + const TopoDS_Vertex &v, + const TopoDS_Edge &eOther, + const TopoDS_Vertex &vOther) + { + if (vOther.IsNull()) + return; + if (v.IsSame(vOther)) + return; + double tol = std::max(BRep_Tool::Pnt(v).Distance(BRep_Tool::Pnt(vOther)), + BRep_Tool::Tolerance(vOther)); + if (tol >= BRep_Tool::Tolerance(v)) { + ShapeFix_ShapeTolerance fix; + fix.SetTolerance(v, std::max(tol*0.5, myTol), TopAbs_VERTEX); + } + BRepBuilderAPI_MakeWire mkWire(eOther); + mkWire.Add(e); + auto newEdge = mkWire.Edge(); + TopoDS_Vertex vFirst = TopExp::FirstVertex(newEdge); + TopoDS_Vertex vLast = TopExp::LastVertex(newEdge); + assertCheck(vLast.IsSame(vOther) || vFirst.IsSame(vOther)); + e = newEdge; + }; + + TopoDS_Edge edge = e; + TopoDS_Vertex vFirst = TopExp::FirstVertex(e); + TopoDS_Vertex vLast = TopExp::LastVertex(e); + connectEdge(edge, vFirst, ev1, v1); + connectEdge(edge, vLast, ev2, v2); + if (!edge.IsSame(e)) { + auto itSource = sourceEdges.find(e); + if (itSource != sourceEdges.end()) { + TopoShape newEdge = *itSource; + newEdge.setShape(edge, false); + sourceEdges.erase(itSource); + sourceEdges.insert(newEdge); + } + getEndPoints(edge,p1,p2); + // Shall we also update bbox? + } + it = edges.emplace(it,edge,p1,p2,bbox,queryBBox,isLinear); + add(it); + return true; + } + + void add(const TopoDS_Shape &shape, bool queryBBox=false) + { + for (TopExp_Explorer xp(shape,TopAbs_EDGE); xp.More(); xp.Next()) + add(TopoDS::Edge(xp.Current()),queryBBox); + } + + //This algorithm tries to join connected edges into wires + // + //tol*tol>Precision::SquareConfusion() can be used to join points that are + //close but do not coincide with a line segment. The close points may be + //the results of rounding issue. + // + void join() + { + double tol = myTol2; + while (edges.size()) { + auto it = edges.begin(); + BRepBuilderAPI_MakeWire mkWire; + mkWire.Add(it->edge); + gp_Pnt pstart(it->p1),pend(it->p2); + remove(it); + + bool done = false; + for (int idx=0;!done&&idx<2;++idx) { + while (edges.size()) { + std::vector ret; + ret.reserve(1); + const gp_Pnt &pt = idx==0?pstart:pend; + vmap.query(bgi::nearest(pt,1),std::back_inserter(ret)); + assertCheck(ret.size()==1); + double d = ret[0].pt().SquareDistance(pt); + if (d > tol) break; + + const auto &info = *ret[0].it; + bool start = ret[0].start; + if (d > Precision::SquareConfusion()) { + // insert a filling edge to solve the tolerance problem + const gp_Pnt &pt = ret[idx].pt(); + if (idx) + mkWire.Add(BRepBuilderAPI_MakeEdge(pend,pt).Edge()); + else + mkWire.Add(BRepBuilderAPI_MakeEdge(pt,pstart).Edge()); + } + + if (idx==1 && start) { + pend = info.p2; + mkWire.Add(info.edge); + }else if (idx==0 && !start) { + pstart = info.p1; + mkWire.Add(info.edge); + }else if (idx==0 && start) { + pstart = info.p2; + mkWire.Add(TopoDS::Edge(info.edge.Reversed())); + }else { + pend = info.p1; + mkWire.Add(TopoDS::Edge(info.edge.Reversed())); + } + remove(ret[0].it); + if (pstart.SquareDistance(pend)<=Precision::SquareConfusion()){ + done = true; + break; + } + } + } + builder.Add(compound,mkWire.Wire()); + } + } + + struct IntersectInfo { + double param; + TopoDS_Shape intersectShape; + gp_Pnt point; + IntersectInfo(double p, const gp_Pnt &pt, const TopoDS_Shape &s) + :param(p), intersectShape(s), point(pt) + {} + bool operator<(const IntersectInfo &other) const { + return param < other.param; + } + }; + + void checkSelfIntersection(const EdgeInfo &info, std::set ¶ms) + { + // Early return if checking for self intersection (only for non linear spline curves) + if (info.type <= GeomAbs_Parabola || info.isLinear) + return; + IntRes2d_SequenceOfIntersectionPoint points2d; + TColgp_SequenceOfPnt points3d; + TColStd_SequenceOfReal errors; + TopoDS_Wire wire; + BRepBuilderAPI_MakeWire mkWire(info.edge); + if (!mkWire.IsDone()) + return; + if (!BRep_Tool::IsClosed(mkWire.Wire())) { + BRepBuilderAPI_MakeEdge mkEdge(info.p1, info.p2); + if (!mkEdge.IsDone()) + return; + mkWire.Add(mkEdge.Edge()); + } + wire = mkWire.Wire(); + BRepBuilderAPI_MakeFace mkFace(wire); + if (!mkFace.IsDone()) + return; + TopoDS_Face face = mkFace.Face(); + ShapeAnalysis_Wire analysis(wire, face, myTol); + analysis.CheckSelfIntersectingEdge(1, points2d, points3d); + assertCheck(points2d.Length() == points3d.Length()); + for (int i=1; i<=points2d.Length(); ++i) { + params.emplace(points2d(i).ParamOnFirst(), points3d(i), info.edge); + params.emplace(points2d(i).ParamOnSecond(), points3d(i), info.edge); + } + } + + void checkIntersection(const EdgeInfo &info, + const EdgeInfo &other, + std::set ¶ms1, + std::set ¶ms2) + { + gp_Pln pln; + bool planar = TopoShape(info.edge).findPlane(pln); + if (!planar) { + TopoDS_Compound comp; + builder.MakeCompound(comp); + builder.Add(comp, info.edge); + builder.Add(comp, other.edge); + planar = TopoShape(comp).findPlane(pln); + if (!planar) { + BRepExtrema_DistShapeShape extss(info.edge, other.edge); + extss.Perform(); + if (extss.IsDone() && extss.NbSolution() > 0) + if (!extss.IsDone() || extss.NbSolution()<=0 || extss.Value()>=myTol) + return; + for (int i=1; i<=extss.NbSolution(); ++i) { + Standard_Real p; + auto s1 = extss.SupportOnShape1(i); + auto s2 = extss.SupportOnShape2(i); + if (s1.ShapeType() == TopAbs_EDGE) { + extss.ParOnEdgeS1(i,p); + pushIntersection(params1, p, extss.PointOnShape1(i), other.edge); + } + if (s2.ShapeType() == TopAbs_EDGE) { + extss.ParOnEdgeS2(i,p); + pushIntersection(params2, p, extss.PointOnShape2(i), info.edge); + } + } + return; + } + } + // BRepExtrema_DistShapeShape has trouble finding all solutions for a + // spline curve. ShapeAnalysis_Wire is better. Besides, it can check + // for self intersection. It's slightly more troublesome to use, as it + // requires to build a face for the wire, so we only use it for planar + // cases. + + IntRes2d_SequenceOfIntersectionPoint points2d; + TColgp_SequenceOfPnt points3d; + TColStd_SequenceOfReal errors; + TopoDS_Wire wire; + int idx; + BRepBuilderAPI_MakeWire mkWire(info.edge); + mkWire.Add(other.edge); + if (mkWire.IsDone()) + idx = 2; + else if (mkWire.Error() == BRepBuilderAPI_DisconnectedWire) { + idx = 3; + BRepBuilderAPI_MakeEdge mkEdge(info.p1, other.p1); + if (!mkEdge.IsDone()) { + if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) + FC_WARN("Failed to build edge for checking intersection"); + return; + } + mkWire.Add(mkEdge.Edge()); + mkWire.Add(other.edge); + } + + if (!mkWire.IsDone()) { + if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) + FC_WARN("Failed to build wire for checking intersection"); + return; + } + wire = mkWire.Wire(); + if (!BRep_Tool::IsClosed(wire)) { + gp_Pnt p1, p2; + getEndPoints(wire, p1, p2); + BRepBuilderAPI_MakeEdge mkEdge(p1, p2); + if (!mkEdge.IsDone()) { + if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) + FC_WARN("Failed to build edge for checking intersection"); + return; + } + mkWire.Add(mkEdge.Edge()); + } + + BRepBuilderAPI_MakeFace mkFace(wire); + if (!mkFace.IsDone()) { + if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) + FC_WARN("Failed to build face for checking intersection"); + return; + } + TopoDS_Face face = mkFace.Face(); + ShapeAnalysis_Wire analysis(wire, face, myTol); + analysis.CheckIntersectingEdges(1, idx, points2d, points3d, errors); + assertCheck(points2d.Length() == points3d.Length()); + for (int i=1; i<=points2d.Length(); ++i) { + pushIntersection(params1, points2d(i).ParamOnFirst(), points3d(i), other.edge); + pushIntersection(params2, points2d(i).ParamOnSecond(), points3d(i), info.edge); + } + } + + void pushIntersection(std::set ¶ms, double param, const gp_Pnt &pt, const TopoDS_Shape &shape) + { + IntersectInfo info(param, pt, shape); + auto it = params.upper_bound(info); + if (it != params.end()) { + if (it->point.SquareDistance(pt) < myTol2) + return; + } + if (it != params.begin()) { + auto itPrev = it; + --itPrev; + if (itPrev->point.SquareDistance(pt) < myTol2) + return; + } + params.insert(it, info); + return; + } + + struct SplitInfo { + TopoDS_Edge edge; + TopoDS_Shape intersectShape; + Box bbox; + }; + + // Try splitting any edges that intersects other edge + void splitEdges() + { + std::unordered_map> intersects; + + int i=0; + for (auto &info : edges) + info.iteration = ++i; + + std::unique_ptr seq( + new Base::SequencerLauncher("Splitting edges", edges.size())); + + i = 0; + for (auto it=edges.begin(); it!=edges.end();++it) { + seq->next(true); + ++i; + auto &info = *it; + auto ¶ms = intersects[&info]; + checkSelfIntersection(info, params); + + for (auto vit=boxMap.qbegin(bgi::intersects(info.box)); vit!=boxMap.qend(); ++vit) { + const auto &other = *(*vit); + if (other.iteration <= i) { + // means the edge is before us, and we've already checked intersection + continue; + } + checkIntersection(info, other, params, intersects[&other]); + } + } + + i=0; + std::vector splitted; + for (auto it=edges.begin(); it!=edges.end(); ) { + ++i; + auto iter = intersects.find(&(*it)); + if (iter == intersects.end()) { + ++it; + continue; + } + auto &info = *it; + auto ¶ms = iter->second; + if (params.empty()) { + ++it; + continue; + } + + auto itParam = params.begin(); + if (itParam->point.SquareDistance(info.p1) < myTol2) + params.erase(itParam); + params.emplace(info.firstParam, info.p1, TopoDS_Shape()); + itParam = params.end(); + --itParam; + if (itParam->point.SquareDistance(info.p2) < myTol2) + params.erase(itParam); + params.emplace(info.lastParam, info.p2, TopoDS_Shape()); + + if (params.size() <= 2) { + ++it; + continue; + } + + splitted.clear(); + itParam = params.begin(); + for (auto itPrevParam=itParam++; itParam!=params.end(); ++itParam) { + const auto &intersectShape = itParam->intersectShape.IsNull() + ? itPrevParam->intersectShape : itParam->intersectShape; + if (intersectShape.IsNull()) + break; + + // Using points cause MakeEdge failure for some reason. Using + // parameters is better. + // + const gp_Pnt &p1 = itPrevParam->point; + const gp_Pnt &p2 = itParam->point; + const Standard_Real ¶m1 = itPrevParam->param; + const Standard_Real ¶m2 = itParam->param; + + BRepBuilderAPI_MakeEdge mkEdge(info.curve, param1, param2); + if (mkEdge.IsDone()) { + splitted.emplace_back(); + auto &entry = splitted.back(); + entry.edge = mkEdge.Edge(); + entry.intersectShape = intersectShape; + if (getBBox(entry.edge, entry.bbox)) + itPrevParam = itParam; + else + splitted.pop_back(); + } + else if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) { + FC_WARN("edge split failed " + << std::setprecision(16) + << FC_XYZ(p1) << FC_XYZ(p2) + << ": " << mkEdge.Error()); + } + } + if (splitted.size() <= 1) { + ++it; + continue; + } + + showShape(info.edge, "remove"); + auto removedEdge = info.edge; + it = remove(it); + for (const auto &v : splitted) { + if (!add(v.edge, false, v.bbox, it)) + continue; + auto &newInfo = *it++; + aHistory->AddModified(v.intersectShape, newInfo.edge); + // if (v.intersectShape != removedEdge) + // aHistory->AddModified(removedEdge, newInfo.edge); + showShape(newInfo.edge, "split"); + } + } + } + + void findSuperEdges() + { + std::unique_ptr seq( + new Base::SequencerLauncher("Combining edges", edges.size())); + + std::deque vertices; + + ++iteration; + + // Join edges (let's call it super edge) that are connected to only one + // other edges (count == 2 counts this and the other edge) on one of + // its vertices to save traverse time. + for (auto it=edges.begin(); it!=edges.end(); ++it) { + seq->next(true); + auto &info = *it; + if (info.iteration == iteration || info.iteration < 0) + continue; + info.iteration = iteration; + // showShape(&info, "scheck"); + + vertices.clear(); + vertices.emplace_back(it, true); + edgeSet.clear(); + + bool done = false; + for (int k=0; k<2; ++k) { // search in both direction + auto begin = k==1 ? vertices.back().reversed() : vertices.front(); + while (true) { + auto currentVertex = k==1 ? vertices.front() : vertices.back(); + auto current = currentVertex.edgeInfo(); + // showShape(current, "siter", k); + int idx = (currentVertex.start?1:0)^k; + EdgeInfo *found = nullptr; + for (int i=current->iStart[idx]; iiEnd[idx]; ++i) { + const auto &v = adjacentList[i]; + auto next = v.edgeInfo(); + if (next->iteration < 0 // skipped + || next == current) // skip self (see how adjacent list is built) + continue; + if (v == begin) { + // closed + done = true; + break; + } + if (found // more than one branch + || edgeSet.contains(next)) // or, self intersect + { + // if (found) { + // showShape(found, "branch_a", k); + // showShape(next, "branch_b", k); + // } else { + // showShape(next, "insect", k); + // } + found = nullptr; + break; + } + found = next; + currentVertex = v; + } + if (done || !found) + break; + // showShape(found, "snext", k); + if (k==1) { + edgeSet.insert(current); + vertices.push_front(currentVertex.reversed()); + } else { + edgeSet.insert(found); + vertices.push_back(currentVertex); + } + } + if (done) + break; + } + + if (vertices.size() <= 1) + continue; + + wireData->Clear(); + Bnd_Box bbox; + for (const auto &v : vertices) { + auto current = v.edgeInfo(); + bbox.Add(current->box.min_corner()); + bbox.Add(current->box.max_corner()); + wireData->Add(current->shape(v.start)); + showShape(current, "edge"); + current->iteration = -1; + } + auto first = vertices.front().edgeInfo(); + first->superEdge = makeCleanWire(false); + first->superEdgeReversed.Nullify(); + if (BRep_Tool::IsClosed(first->superEdge)) { + first->iteration = -2; + showShape(first, "super_done"); + } else { + first->iteration = iteration; + showShape(first, "super"); + auto &vFirst = vertices.front(); + auto &vLast = vertices.back(); + auto last = vLast.edgeInfo(); + vFirst.ptOther() = vLast.ptOther(); + int idx = vFirst.start ? 1 : 0; + first->iStart[idx] = last->iStart[vLast.start?1:0]; + first->iEnd[idx] = last->iEnd[vLast.start?1:0]; + + for (int i=first->iStart[idx];iiEnd[idx];++i) { + auto &v = adjacentList[i]; + if (v.it == vLast.it) { + v.it = vFirst.it; + v.start = !vFirst.start; + } + } + bbox.Enlarge(myTol); + first->box = Box(bbox.CornerMin(), bbox.CornerMax()); + } + } + } + + void buildAdjacentList() + { + builder.MakeCompound(compound); + + for (auto &info : edges) + info.reset(); + + adjacentList.clear(); + + // populate adjacent list + for (auto &info : edges) { + if (info.iteration == -2) { +#if OCC_VERSION_HEX >= 0x070000 + assertCheck(BRep_Tool::IsClosed(info.shape())); +#endif + showShape(&info,"closed"); + if (!doTightBound) + builder.Add(compound,info.wire()); + continue; + } else if (info.iteration < 0) + continue; + + if (info.p1.SquareDistance(info.p2)<=myTol2) { + if (!doTightBound) + builder.Add(compound,info.wire()); + info.iteration = -2; + continue; + } + + gp_Pnt pt[2]; + pt[0] = info.p1; + pt[1] = info.p2; + for (int i=0;i<2;++i) { + if (info.iStart[i]>=0) + continue; + info.iEnd[i] = info.iStart[i] = (int)adjacentList.size(); + + for (auto vit=vmap.qbegin(bgi::nearest(pt[i],INT_MAX));vit!=vmap.qend();++vit) { + auto &vinfo = *vit; + if (vinfo.pt().SquareDistance(pt[i]) > myTol2) + break; + + // We must push ourself too, because the adjacency + // information is shared among all connected edges. + // + // if (&(*vinfo.it) == &info) + // continue; + + if (vinfo.it->iteration < 0) + continue; + + adjacentList.push_back(vinfo); + ++info.iEnd[i]; + } + + // copy the adjacent indices to all connected edges + for (int j=info.iStart[i];jiteration >= 0 && other != &info) + break; + } + if (i == info.iEnd[k]) { + // If merge or tight bound, then repeat until no edges + // can be skipped. + done = !doMergeEdge & !doTightBound; + info.iteration = -3; + showShape(&info, "skip"); + break; + } + } + } + } + } + + // This algorithm tries to find a set of closed wires that includes as many + // edges (added by calling add() ) as possible. One edge may be included + // in more than one closed wires if it connects to more than one edges. + void findClosedWires(bool tightBound=false) + { + std::unique_ptr seq( + new Base::SequencerLauncher("Finding wires", edges.size())); + + for (auto &info : edges) { + info.wireInfo.reset(); + info.wireInfo2.reset(); + } + + for (auto it=edges.begin(); it!=edges.end(); ++it) { + VertexInfo beginVertex(it, true); + auto &beginInfo = *it; + seq->next(true); + ++iteration; + if (beginInfo.iteration < 0 || beginInfo.wireInfo) + continue; + + VertexInfo currentVertex(it, true); + EdgeInfo *currentInfo = &beginInfo; + showShape(currentInfo, "begin"); + stack.clear(); + vertexStack.clear(); + edgeSet.clear(); + + TopoDS_Wire wire = _findClosedWires(beginVertex, currentVertex); + if (wire.IsNull()) + continue; + + if (tightBound) { + assert(!beginInfo.wireInfo); + beginInfo.wireInfo.reset(new WireInfo); + beginInfo.wireInfo->vertices.emplace_back(it, true); + beginInfo.wireInfo->wire = wire; + } + for (auto &r : stack) { + const auto &v = vertexStack[r.iCurrent]; + auto &info = *v.it; + if (tightBound) + beginInfo.wireInfo->vertices.push_back(v); + if (!info.wireInfo) { + info.wireInfo = beginInfo.wireInfo; + // showShape(&info, "visited"); + } + } + showShape(wire,"joined"); + if (!tightBound) + builder.Add(compound, wire); + } + } + + void checkStack() + { +#if 0 + if (stack.size() <= 1) + return; + std::vector edges; + auto &r = stack[stack.size()-2]; + for (int i=r.iStart;ivertices.front().edgeInfo()->wireInfo.get() == w); + } + } + + TopoDS_Wire _findClosedWires(VertexInfo beginVertex, + VertexInfo currentVertex, + std::shared_ptr wireInfo = std::shared_ptr(), + int *idxVertex = nullptr, + int *stackPos = nullptr) + { + Base::SequencerBase::Instance().checkAbort(); + EdgeInfo &beginInfo = *beginVertex.it; + + EdgeInfo *currentInfo = currentVertex.edgeInfo(); + int currentIdx = currentVertex.start ? 1 : 0; + currentInfo->iteration = iteration; + + gp_Pnt pstart = beginVertex.pt(); + gp_Pnt pend = currentVertex.ptOther(); + + auto stackEnd = stack.size(); + checkStack(); + + // pstart and pend is the start and end vertex of the current wire + while (true) { + // push a new stack entry + stack.emplace_back(vertexStack.size()); + auto &r = stack.back(); + showShape(currentInfo, "check", iteration); + + bool proceed = true; + + // The loop below is to find all edges connected to pend, and save them into stack.back() + auto size = vertexStack.size(); + for (int i=currentInfo->iStart[currentIdx];iiEnd[currentIdx];++i) { + auto &vinfo = adjacentList[i]; + auto &info = *vinfo.it; + if (info.iteration < 0 || currentInfo == &info) + continue; + + bool abort = false; + if (!wireSet.empty() && wireSet.contains(info.wireInfo.get())) { + showShape(&info, "wired", iteration); + if (wireInfo) + wireInfo->purge = true; + abort = true; + } + + if (edgeSet.contains(&info)) { + showShape(&info, "intersect", iteration); + // This means the current edge connects to an + // existing edge in the middle of the stack. + // skip the current edge. + r.iEnd = r.iStart; + vertexStack.resize(size); + break; + } + + if (abort || currentInfo->wireInfo2) { + if (wireInfo) + wireInfo->purge = true; + continue; + } + + if (info.iteration == iteration) + continue; + info.iteration = iteration; + + if (wireInfo) { + // We may be called by findTightBound() with an existing wire + // to try to find a new wire by splitting the current one. So + // check if we've iterated to some edge in the existing wire. + if (int idx = wireInfo->find(vinfo)) { + vertexStack.push_back(adjacentList[i]); + r.iCurrent = r.iEnd++; + --idx; + proceed = false; + if (idxVertex) + *idxVertex = idx; + if (stackPos) + *stackPos = (int)stack.size()-2; + + auto info = wireInfo->vertices[idx].edgeInfo(); + showShape(info, "merge", iteration); + + if (info != &beginInfo) { + while (true) { + if (++idx == (int)wireInfo->vertices.size()) + idx = 0; + info = wireInfo->vertices[idx].edgeInfo(); + if (info == &beginInfo) + break; + stack.emplace_back(vertexStack.size()); + vertexStack.push_back(wireInfo->vertices[idx]); + ++stack.back().iEnd; + checkStack(); + } + } + break; + } + + if (wireInfo->find(VertexInfo(vinfo.it, !vinfo.start))) { + showShape(&info, "rintersect", iteration); + // Only used when exhausting tight bound. + wireInfo->purge = true; + continue; + } + + if (isOutside(*wireInfo, info.mid)) { + showShape(&info, "outside", iteration); + continue; + } + } + vertexStack.push_back(adjacentList[i]); + ++r.iEnd; + } + checkStack(); + + if (proceed) { + while (true) { + auto &r = stack.back(); + if (r.iCurrentwireInfo.get()); + break; + } + vertexStack.erase(vertexStack.begin()+r.iStart,vertexStack.end()); + + stack.pop_back(); + if (stack.size() == stackEnd) { + // If stack reaches the end, it means this wire is open. + return TopoDS_Wire(); + } + + auto &lastInfo = *vertexStack[stack.back().iCurrent].it; + edgeSet.erase(&lastInfo); + wireSet.erase(lastInfo.wireInfo.get()); + showShape(&lastInfo, "pop", iteration); + ++stack.back().iCurrent; + } + + if (pstart.SquareDistance(pend) > myTol2) { + // if the wire is not closed yet, continue search for the + // next connected edge + continue; + } + if (wireInfo) { + if (idxVertex) + *idxVertex = (int)wireInfo->vertices.size(); + if (stackPos) + *stackPos = (int)stack.size()-1; + } + } + + wireData->Clear(); + wireData->Add(beginInfo.shape(beginVertex.start)); + for (auto &r : stack) { + const auto &v = vertexStack[r.iCurrent]; + auto &info = *v.it; + wireData->Add(info.shape(v.start)); + } + TopoDS_Wire wire = makeCleanWire(); + if (!BRep_Tool::IsClosed(wire)) { + FC_WARN("failed to close some wire in iteration " << iteration); + showShape(wire,"_FailedToClose", iteration); + showShape(beginInfo.shape(beginVertex.start), "failed", iteration); + for (auto &r : stack) { + const auto &v = vertexStack[r.iCurrent]; + auto &info = *v.it; + showShape(info.shape(v.start), v.start ? "failed" : "failed_r", iteration); + } + assertCheck(false); + continue; + } + return wire; + } + } + + void findTightBound() + { + // Assumption: all edges lies on a common manifold surface + // + // Definition of 'Tight Bound': a wire that cannot be splitted into + // smaller wires by any intersecting edges internal to the wire. + // + // The idea of the searching algorithm is simple. The initial condition + // here is that we've found a closed wire for each edge. To find the + // tight bound, for each wire, check wire edge branches (using the + // adjacent list built earlier), and split the wire whenever possible. + + std::unique_ptr seq( + new Base::SequencerLauncher("Finding tight bound", edges.size())); + + int iteration2 = iteration; + for (auto &info : edges) { + ++iteration; + seq->next(true); + if (info.iteration < 0 || !info.wireInfo) + continue; + + ++iteration2; + while(!info.wireInfo->done) { + auto wireInfo = info.wireInfo; + checkWireInfo(*wireInfo); + const auto &wireVertices = wireInfo->vertices; + auto beginVertex = wireVertices.front(); + auto &beginInfo = *beginVertex.it; + initWireInfo(*wireInfo); + showShape(wireInfo->wire, "iwire", iteration); + for (auto &v : wireVertices) + v.it->iteration2 = iteration2; + + stack.clear(); + vertexStack.clear(); + edgeSet.clear(); + + std::shared_ptr newWire; + gp_Pnt pstart = beginVertex.pt(); + + int idxV = 0; + while (true) { + int idx = wireVertices[idxV].start ? 1 : 0; + auto current = wireVertices[idxV].edgeInfo(); + showShape(current, "current", iteration); + + for (int n=current->iStart[idx]; niEnd[idx]; ++n) { + const auto ¤tVertex = adjacentList[n]; + auto next = currentVertex.edgeInfo(); + if (next == current || next->iteration2 == iteration2 || next->iteration<0) + continue; + + showShape(next, "tcheck", iteration); + + if (!isInside(*wireInfo, next->mid)) { + showShape(next, "ninside", iteration); + next->iteration2 = iteration2; + continue; + } + + edgeSet.insert(next); + stack.emplace_back(vertexStack.size()); + ++stack.back().iEnd; + vertexStack.push_back(currentVertex); + checkStack(); + + int idxEnd = (int)wireVertices.size(); + int stackStart = (int)stack.size()-1; + int stackPos = (int)stack.size()-1; + + TopoDS_Wire wire; + if (pstart.SquareDistance(currentVertex.ptOther()) > myTol2) { + wire = _findClosedWires(beginVertex, currentVertex, beginInfo.wireInfo, &idxEnd, &stackPos); + if (wire.IsNull()) { + vertexStack.pop_back(); + stack.pop_back(); + edgeSet.erase(next); + continue; + } + } + + newWire.reset(new WireInfo); + auto &newWireVertices = newWire->vertices; + newWireVertices.push_back(beginVertex); + for (auto &r : stack) { + const auto &v = vertexStack[r.iCurrent]; + newWireVertices.push_back(v); + } + if (!wire.IsNull()) + newWire->wire = wire; + else if (!initWireInfo(*newWire)) { + newWire.reset(); + vertexStack.pop_back(); + stack.pop_back(); + edgeSet.erase(next); + continue; + } + for (auto &v : newWire->vertices) { + if (v.edgeInfo()->wireInfo == wireInfo) + v.edgeInfo()->wireInfo = newWire; + } + beginInfo.wireInfo = newWire; + showShape(*newWire, "nwire", iteration); + + std::shared_ptr splitWire; + if (idxEnd == 0) + idxEnd = (int)wireVertices.size(); + ++idxV; + assertCheck(idxV<=idxEnd); + int idxStart = idxV; + for (int idx=idxV; idx!=idxEnd; ++idx) { + auto info = wireVertices[idx].edgeInfo(); + if (info == &beginInfo) { + showShape(*wireInfo, "exception", iteration, true); + showShape(info, "exception", iteration, true); + assertCheck(info != &beginInfo); + } + if (info->wireInfo == wireInfo) { + if (!splitWire) { + idxStart = idx; + splitWire.reset(new WireInfo); + } + info->wireInfo = splitWire; + } + } + if (splitWire) { + auto &splitEdges = splitWire->vertices; + gp_Pnt pstart, pt; + bool first = true; + for (int idx=idxStart; idx!=idxEnd; ++idx) { + auto &v = wireVertices[idx]; + if (first) { + first = false; + pstart = v.pt(); + } else + assertCheck(pt.SquareDistance(v.pt()) < myTol2); + pt = v.ptOther(); + splitEdges.push_back(v); + } + for (int i=stackPos; i>=stackStart; --i) { + const auto &v = vertexStack[stack[i].iCurrent]; + assertCheck(pt.SquareDistance(v.ptOther()) < myTol2); + pt = v.pt(); + // The edges in the stack are the ones to slice + // the wire in half. We construct a new wire + // that includes the original beginning edge in + // the loop above. And this loop contains the + // other half. Note that the slicing edges + // should run in the oppsite direction, hence reversed + splitEdges.push_back(v.reversed()); + } + for (int idx=idxV; idx!=idxStart; ++idx) { + auto &v = wireVertices[idx]; + assertCheck(pt.SquareDistance(v.pt()) < myTol2); + pt = v.ptOther(); + splitEdges.push_back(v); + } + assertCheck(pt.SquareDistance(pstart) < myTol2); + showShape(*splitWire, "swire", iteration); + } + + checkWireInfo(*newWire); + break; + } + if (newWire) { + ++iteration; + break; + } + + if (++idxV == (int)wireVertices.size()) + break; + stack.emplace_back(vertexStack.size()); + ++stack.back().iEnd; + vertexStack.push_back(wireVertices[idxV]); + edgeSet.insert(wireVertices[idxV].edgeInfo()); + checkStack(); + } + + if (!newWire) { + showShape(*beginInfo.wireInfo, "done", iteration); + beginInfo.wireInfo->done = true; + // If a wire is done, make sure all edges of this wire is + // marked as done. This can also prevent duplicated wires. + for (auto &v : beginInfo.wireInfo->vertices) { + auto info = v.edgeInfo(); + if (!info->wireInfo) { + info->wireInfo = beginInfo.wireInfo; + continue; + } + else if (info->wireInfo->done) + continue; + auto otherWire = info->wireInfo; + auto &otherWireVertices = info->wireInfo->vertices; + if (info == otherWireVertices.front().edgeInfo()) { + // About to change the first edge of the other wireInfo. + // Try to find a new first edge for it. + tmpVertices.clear(); + auto it = otherWireVertices.begin(); + tmpVertices.push_back(*it); + for (++it;it!=otherWireVertices.end();++it) { + if (it->edgeInfo()->wireInfo == otherWire) + break; + tmpVertices.push_back(*it); + } + if (tmpVertices.size() != otherWireVertices.size()) { + otherWireVertices.erase(otherWireVertices.begin(), it); + otherWireVertices.insert(otherWireVertices.end(), + tmpVertices.begin(), tmpVertices.end()); + } + } + assertCheck(info != &beginInfo); + info->wireInfo = beginInfo.wireInfo; + checkWireInfo(*otherWire); + } + checkWireInfo(*beginInfo.wireInfo); + } + } + } + } + + void exhaustTightBound() + { + // findTightBound() function will find a tight bound wire for each + // edge. Now we try to find all possible tight bound wires, relying on + // the important fact that an edge can be shared by at most two tight + // bound wires. + + std::unique_ptr seq( + new Base::SequencerLauncher("Exhaust tight bound", edges.size())); + + for (auto &info : edges) { + if (info.iteration < 0 || !info.wireInfo || !info.wireInfo->done) + continue; + for (auto &v : info.wireInfo->vertices) { + auto edgeInfo = v.edgeInfo(); + if (edgeInfo->wireInfo != info.wireInfo) + edgeInfo->wireInfo2 = info.wireInfo; + } + } + + int iteration2 = iteration; + for (auto &info : edges) { + ++iteration; + seq->next(true); + if (info.iteration < 0 + || !info.wireInfo + || !info.wireInfo->done) + { + if (info.wireInfo) + showShape(*info.wireInfo, "iskip"); + else + showShape(&info, "iskip"); + continue; + } + + if (info.wireInfo2 && info.wireInfo2->done) { + showShape(*info.wireInfo, "idone"); + continue; + } + + showShape(*info.wireInfo, "iwire2", iteration); + showShape(&info, "begin2", iteration); + + int idx = info.wireInfo->find(&info); + assertCheck(idx > 0); + const auto &vertices = info.wireInfo->vertices; + --idx; + int nextIdx = idx == (int)vertices.size()-1 ? 0 : idx + 1; + int prevIdx = idx == 0 ? (int)vertices.size()-1 : idx - 1; + int count = prevIdx == nextIdx ? 1 : 2; + for (int n=0; niteration<0 + || !next->wireInfo + || !next->wireInfo->done + || next->wireInfo2) + continue; + + showShape(next, "n2", iteration); + + stack.clear(); + stack.emplace_back(); + ++stack.back().iEnd; + vertexStack.clear(); + vertexStack.push_back(currentVertex); + + edgeSet.clear(); + edgeSet.insert(next); + wireSet.clear(); + wireSet.insert(next->wireInfo.get()); + + TopoDS_Wire wire; + if (pstart.SquareDistance(currentVertex.ptOther()) > myTol2) { + wire = _findClosedWires(beginVertex, currentVertex); + if (wire.IsNull()) + continue; + } + + std::shared_ptr wireInfo(new WireInfo); + wireInfo->vertices.push_back(beginVertex); + for (auto &r : stack) { + const auto &v = vertexStack[r.iCurrent]; + wireInfo->vertices.push_back(v); + } + if (!wire.IsNull()) + wireInfo->wire = wire; + else if (!initWireInfo(*wireInfo)) + continue; + + showShape(*wireInfo, "nw2", iteration); + + ++iteration; + ++iteration2; + + while (wireInfo && !wireInfo->done) { + showShape(next, "next2", iteration); + + vertexStack.resize(1); + stack.resize(1); + edgeSet.clear(); + edgeSet.insert(next); + wireSet.clear(); + wireSet.insert(next->wireInfo.get()); + + const auto &wireVertices = wireInfo->vertices; + initWireInfo(*wireInfo); + for (auto &v : wireVertices) + v.it->iteration2 = iteration2; + + std::shared_ptr newWire; + + int idxV = 1; + while (true) { + int idx = wireVertices[idxV].start ? 1 : 0; + auto current = wireVertices[idxV].edgeInfo(); + + for (int n=current->iStart[idx]; niEnd[idx]; ++n) { + const auto ¤tVertex = adjacentList[n]; + auto next = currentVertex.edgeInfo(); + if (next == current || next->iteration2 == iteration2 || next->iteration<0) + continue; + + showShape(next, "check2", iteration); + + if (!isInside(*wireInfo, next->mid)) { + showShape(next, "ninside2", iteration); + next->iteration2 = iteration2; + continue; + } + + edgeSet.insert(next); + stack.emplace_back(vertexStack.size()); + ++stack.back().iEnd; + vertexStack.push_back(currentVertex); + checkStack(); + + TopoDS_Wire wire; + if (pstart.SquareDistance(currentVertex.ptOther()) > myTol2) { + wire = _findClosedWires(beginVertex, currentVertex, wireInfo); + if (wire.IsNull()) { + vertexStack.pop_back(); + stack.pop_back(); + edgeSet.erase(next); + wireSet.erase(next->wireInfo.get()); + continue; + } + } + + newWire.reset(new WireInfo); + auto &newWireVertices = newWire->vertices; + newWireVertices.push_back(beginVertex); + for (auto &r : stack) { + const auto &v = vertexStack[r.iCurrent]; + newWireVertices.push_back(v); + } + if (!wire.IsNull()) + newWire->wire = wire; + else if (!initWireInfo(*newWire)) { + newWire.reset(); + vertexStack.pop_back(); + stack.pop_back(); + edgeSet.erase(next); + wireSet.erase(next->wireInfo.get()); + continue; + } + for (auto &v : newWire->vertices) { + if (v.edgeInfo()->wireInfo == wireInfo) + v.edgeInfo()->wireInfo = newWire; + } + showShape(*newWire, "nwire2", iteration); + checkWireInfo(*newWire); + break; + } + + if (newWire) { + ++iteration; + wireInfo = newWire; + break; + } + + if (++idxV == (int)wireVertices.size()) { + if (wireInfo->purge) { + showShape(*wireInfo, "discard2", iteration); + wireInfo.reset(); + } else { + wireInfo->done = true; + showShape(*wireInfo, "done2", iteration); + } + break; + } + stack.emplace_back(vertexStack.size()); + ++stack.back().iEnd; + vertexStack.push_back(wireVertices[idxV]); + edgeSet.insert(wireVertices[idxV].edgeInfo()); + checkStack(); + } + } + + if (wireInfo && wireInfo->done) { + for (auto &v : wireInfo->vertices) { + auto edgeInfo = v.edgeInfo(); + assertCheck(edgeInfo->wireInfo != nullptr); + if (edgeInfo->wireInfo->isSame(*wireInfo)) { + wireInfo = edgeInfo->wireInfo; + break; + } + } + for (auto &v : wireInfo->vertices) { + auto edgeInfo = v.edgeInfo(); + if (!edgeInfo->wireInfo2 && edgeInfo->wireInfo != wireInfo) + edgeInfo->wireInfo2 = wireInfo; + } + assertCheck(info.wireInfo2 == wireInfo); + assertCheck(info.wireInfo2 != info.wireInfo); + showShape(*wireInfo, "exhaust"); + break; + } + } + } + } + wireSet.clear(); + } + + TopoDS_Wire makeCleanWire(bool fixGap=true) + { + // Make a clean wire with sorted, oriented, connected, etc edges + TopoDS_Wire result; + std::vector inputEdges; + + if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_TRACE)) { + for (int i=1; i<=wireData->NbEdges(); ++i) + inputEdges.emplace_back(wireData->Edge(i)); + } + + ShapeFix_Wire fixer; + fixer.SetContext(new ShapeBuild_ReShape); + fixer.Load(wireData); + fixer.SetMaxTolerance(myTol); + fixer.ClosedWireMode() = Standard_True; + fixer.Perform(); + // fixer.FixReorder(); + // fixer.FixConnected(); + + if (fixGap) { + // Gap fixing may change vertex, but we need all concident vertexes + // to be the same one. + // + // fixer.FixGap3d(1, Standard_True); + } + + fixer.FixClosed(); + + result = fixer.Wire(); + auto newHistory = fixer.Context()->History(); + + if (FC_LOG_INSTANCE.level()>FC_LOGLEVEL_TRACE+1) { + FC_MSG("init:"); + for (const auto &s : sourceEdges) + FC_MSG(s.getShape().TShape().get() << ", " << s.getShape().HashCode(INT_MAX)); + printHistory(aHistory, sourceEdges); + printHistory(newHistory, inputEdges); + } + + aHistory->Merge(newHistory); + + if (FC_LOG_INSTANCE.level()>FC_LOGLEVEL_TRACE+1) { + printHistory(aHistory, sourceEdges); + FC_MSG("final:"); + for (int i=1; i<=wireData->NbEdges(); ++i) { + auto s = wireData->Edge(i); + FC_MSG(s.TShape().get() << ", " << s.HashCode(INT_MAX)); + } + } + return result; + } + + template + void printHistory(Handle(BRepTools_History) hist, const T &input) + { + FC_MSG("\nHistory:\n"); + for (const auto &s : input) { + for(TopTools_ListIteratorOfListOfShape it(hist->Modified(s.getShape())); it.More(); it.Next()) { + FC_MSG(s.getShape().TShape().get() << ", " << s.getShape().HashCode(INT_MAX) + << " -> " << it.Value().TShape().get() << ", " << it.Value().HashCode(INT_MAX)); + } + } + } + + bool canShowShape(int idx=-1, bool forced=false) + { + if (idx < 0 || catchIteration == 0 || catchIteration > idx) { + if (!forced && FC_LOG_INSTANCE.level()<=FC_LOGLEVEL_TRACE) + return false; + } + return true; + } + + void showShape(const EdgeInfo *info, const char *name, int idx=-1, bool forced=false) + { + if (!canShowShape(idx, forced)) + return; + showShape(info->shape(), name, idx, forced); + } + + void showShape(WireInfo &wireInfo, const char *name, int idx=-1, bool forced=false) + { + if (!canShowShape(idx, forced)) + return; + if (wireInfo.wire.IsNull()) + initWireInfo(wireInfo); + showShape(wireInfo.wire, name, idx, forced); + } + + void showShape(const TopoDS_Shape &s, const char *name, int idx=-1, bool forced=false) + { + if (!canShowShape(idx, forced)) + return; + std::string _name; + if (idx >= 0) { + _name = name; + _name += "_"; + _name += std::to_string(idx); + _name += "_"; + name = _name.c_str(); + } + auto obj = Feature::create(s, name); + FC_MSG(obj->getNameInDocument() << " " << ShapeHasher()(s)); + if (catchObject == obj->getNameInDocument()) + FC_MSG("found"); + return; + } + + void build() + { + clear(); + sourceEdges.clear(); + sourceEdges.insert(sourceEdgeArray.begin(), sourceEdgeArray.end()); + for (const auto &e : sourceEdgeArray) + add(TopoDS::Edge(e.getShape()), true); + + if (doTightBound || doSplitEdge) + splitEdges(); + + buildAdjacentList(); + + if (!doTightBound && !doOutline) + findClosedWires(); + else { + findClosedWires(true); + findTightBound(); + exhaustTightBound(); + bool done = !doOutline; + while(!done) { + ++iteration; + done = true; + std::unordered_map counter; + std::unordered_set wires; + for (auto &info : edges) { + if (info.iteration == -2) + continue; + if (info.iteration < 0 || !info.wireInfo || !info.wireInfo->done) { + if (info.iteration >= 0) { + info.iteration = -1; + done = false; + showShape(&info, "removed", iteration); + aHistory->Remove(info.edge); + } + continue; + } + if (info.wireInfo2 && wires.insert(info.wireInfo2.get()).second) { + for (auto &v : info.wireInfo2->vertices) { + if (++counter[v.edgeInfo()] == 2) { + v.edgeInfo()->iteration = -1; + done = false; + showShape(v.edgeInfo(), "removed2", iteration); + aHistory->Remove(info.edge); + } + } + } + if (!wires.insert(info.wireInfo.get()).second) + continue; + for (auto &v : info.wireInfo->vertices) { + if (++counter[v.edgeInfo()] == 2) { + v.edgeInfo()->iteration = -1; + done = false; + showShape(v.edgeInfo(), "removed1", iteration); + aHistory->Remove(info.edge); + } + } + } + findClosedWires(true); + findTightBound(); + } + + builder.MakeCompound(compound); + wireSet.clear(); + for (auto &info : edges) { + if (info.iteration == -2) { + if (!info.wireInfo) { + builder.Add(compound, info.wire()); + continue; + } + addWire(info.wireInfo); + addWire(info.wireInfo2); + } + else if (info.iteration >= 0) { + addWire(info.wireInfo2); + addWire(info.wireInfo); + } + } + wireSet.clear(); + } + + // TODO: We choose to put open wires in a separated shape from the final + // result shape, so the history may contains some entries that are not + // presented in the final result, which will cause warning message when + // generating topo naming in TopoShape::makESHAPE(). We've lowered log + // message level to suppress the warning for the moment. The right way + // to solve the problem is to reconstruct the history and filter out + // those entries. + + bool hasOpenEdge = false; + for (const auto &info : edges) { + if (info.iteration == -3 || (!info.wireInfo && info.iteration>=0)) { + if (!hasOpenEdge) { + hasOpenEdge = true; + builder.MakeCompound(openWireCompound); + } + builder.Add(openWireCompound, info.wire()); + } + } + } + + void addWire(std::shared_ptr &wireInfo) + { + if (!wireInfo || !wireInfo->done || !wireSet.insertUnique(wireInfo.get())) + return; + initWireInfo(*wireInfo); + builder.Add(compound, wireInfo->wire); + } + + bool getOpenWires(TopoShape &shape, const char *op, bool noOriginal) { + if (openWireCompound.IsNull()) { + shape.setShape(TopoShape()); + return false; + } + auto comp = openWireCompound; + if (noOriginal) { + TopoShape source(-1); + source.makeElementCompound(sourceEdgeArray); + auto wires = TopoShape(openWireCompound, -1).getSubTopoShapes(TopAbs_WIRE); + bool touched = false; + for (auto it=wires.begin(); it!=wires.end();) { + bool purge = true; + for (const auto &e : it->getSubShapes(TopAbs_EDGE)) { + if (source.findSubShapesWithSharedVertex(TopoShape(e, -1)).empty()) { + purge = false; + break; + } + } + if (purge) { + it = wires.erase(it); + touched = true; + } else + ++it; + } + if (touched) { + if (wires.empty()) { + shape.setShape(TopoShape()); + return false; + } + comp = TopoDS::Compound(TopoShape(-1).makeElementCompound(wires).getShape()); + } + } + shape.makeShapeWithElementMap(comp, + MapperHistory(aHistory), + {sourceEdges.begin(), sourceEdges.end()}, + op); + return true; + } + + bool getResultWires(TopoShape &shape, const char *op) { + if (compound.IsNull()) { + shape.setShape(TopoShape()); + return false; + } + shape.makeShapeWithElementMap(compound, + MapperHistory(aHistory), + {sourceEdges.begin(), sourceEdges.end()}, + op); + return true; + } +}; + + +WireJoiner::WireJoiner() + :pimpl(new WireJoinerP) +{ +} + +WireJoiner::~WireJoiner() +{ +} + +void WireJoiner::addShape(const TopoShape &shape) +{ + NotDone(); + for (auto &e : shape.getSubTopoShapes(TopAbs_EDGE)) + pimpl->sourceEdgeArray.push_back(e); +} + +void WireJoiner::addShape(const std::vector &shapes) +{ + NotDone(); + for (const auto &shape : shapes) { + for (auto &e : shape.getSubTopoShapes(TopAbs_EDGE)) + pimpl->sourceEdgeArray.push_back(e); + } +} + +void WireJoiner::addShape(const std::vector &shapes) +{ + NotDone(); + for (const auto &shape : shapes) { + for (TopExp_Explorer xp(shape,TopAbs_EDGE); xp.More(); xp.Next()) + pimpl->sourceEdgeArray.emplace_back(TopoDS::Edge(xp.Current()), -1); + } +} + +void WireJoiner::setOutline(bool enable) +{ + if (enable != pimpl->doOutline) { + NotDone(); + pimpl->doOutline = enable; + } +} + +void WireJoiner::setTightBound(bool enable) +{ + if (enable != pimpl->doTightBound) { + NotDone(); + pimpl->doTightBound = enable; + } +} + +void WireJoiner::setSplitEdges(bool enable) +{ + if (enable != pimpl->doSplitEdge) { + NotDone(); + pimpl->doSplitEdge = enable; + } +} + +void WireJoiner::setMergeEdges(bool enable) +{ + if (enable != pimpl->doSplitEdge) { + NotDone(); + pimpl->doMergeEdge = enable; + } +} + +void WireJoiner::setTolerance(double tol, double atol) +{ + if (tol >= 0 && tol != pimpl->myTol) { + NotDone(); + pimpl->myTol = tol; + pimpl->myTol2 = tol * tol; + } + if (atol >= 0 && atol != pimpl->myAngularTol) { + NotDone(); + pimpl->myAngularTol = atol; + } +} + +#if OCC_VERSION_HEX < 0x070600 +void WireJoiner::Build() +#else +void WireJoiner::Build(const Message_ProgressRange&) +#endif +{ + if (IsDone()) + return; + pimpl->build(); + if (TopoShape(pimpl->compound).countSubShapes(TopAbs_SHAPE) > 0) + myShape = pimpl->compound; + else + myShape.Nullify(); + Done(); +} + +bool WireJoiner::getOpenWires(TopoShape &shape, const char *op, bool noOriginal) +{ + Build(); + return pimpl->getOpenWires(shape, op, noOriginal); +} + +bool WireJoiner::getResultWires(TopoShape &shape, const char *op) +{ + Build(); + return pimpl->getResultWires(shape, op); +} + +const TopTools_ListOfShape& WireJoiner::Generated (const TopoDS_Shape& S) +{ + Build(); + return pimpl->aHistory->Generated(S); +} + +const TopTools_ListOfShape& WireJoiner::Modified (const TopoDS_Shape& S) +{ + Build(); + return pimpl->aHistory->Modified(S); +} + +Standard_Boolean WireJoiner::IsDeleted (const TopoDS_Shape& S) +{ + Build(); + return pimpl->aHistory->IsRemoved(S); +} diff --git a/src/Mod/Part/App/WireJoiner.h b/src/Mod/Part/App/WireJoiner.h new file mode 100644 index 0000000000..948af08d0f --- /dev/null +++ b/src/Mod/Part/App/WireJoiner.h @@ -0,0 +1,67 @@ +/**************************************************************************** + * Copyright (c) 2022 Zheng Lei (realthunder) * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library 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 * + * GNU Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ****************************************************************************/ + +#ifndef PART_WIRE_JOINER_H +#define PART_WIRE_JOINER_H + +#include +#include +#include +#include "TopoShape.h" + +namespace Part{ + +class PartExport WireJoiner: public BRepBuilderAPI_MakeShape { +public: + WireJoiner(); + virtual ~WireJoiner(); + + void addShape(const TopoShape &shape); + void addShape(const std::vector &shapes); + void addShape(const std::vector &shapes); + + void setOutline(bool enable=true); + void setTightBound(bool enable=true); + void setSplitEdges(bool enable=true); + void setMergeEdges(bool enable=true); + void setTolerance(double tolerance, double angularTol=0.0); + + bool getOpenWires(TopoShape &shape, const char *op="", bool noOriginal=true); + bool getResultWires(TopoShape &shape, const char *op=""); + +#if OCC_VERSION_HEX < 0x070600 + void Build() override; +#else + void Build(const Message_ProgressRange& theRange = Message_ProgressRange()) override; +#endif + const TopTools_ListOfShape& Modified (const TopoDS_Shape& S) override; + const TopTools_ListOfShape& Generated (const TopoDS_Shape& S) override; + Standard_Boolean IsDeleted (const TopoDS_Shape& S) override; + +private: + class WireJoinerP; + std::unique_ptr pimpl; +}; + +} // namespace Part + +#endif // PART_WIRE_JOINER_H