diff --git a/src/Mod/TechDraw/App/AppTechDrawPy.cpp b/src/Mod/TechDraw/App/AppTechDrawPy.cpp index 2c8919ca02..592836ea9a 100644 --- a/src/Mod/TechDraw/App/AppTechDrawPy.cpp +++ b/src/Mod/TechDraw/App/AppTechDrawPy.cpp @@ -33,6 +33,7 @@ #include #include #include +#include #endif #include @@ -264,26 +265,28 @@ private: Py::List result; + std::vector closedEdges; + edgeList = DrawProjectSplit::scrubEdges(edgeList, closedEdges); + + std::vector sortedWires; try { EdgeWalker eWalker; - eWalker.loadEdges(edgeList); - bool success = eWalker.perform(); - if (success) { - std::vector rWires = eWalker.getResultNoDups(); - std::vector sortedWires = eWalker.sortStrip(rWires, biggie); //false==>do not include biggest wires - for (auto& w:sortedWires) { - PyObject* wire = new TopoShapeWirePy(new TopoShape(w)); - result.append(Py::asObject(wire)); - } - } - else { - Base::Console().Warning("edgeWalker: input is not planar graph. Wire detection not done\n"); - } + sortedWires = eWalker.execute(edgeList, biggie); } catch (Base::Exception &e) { e.setPyException(); throw Py::Exception(); } + + if(sortedWires.empty()) { + Base::Console().Warning("ATDP::edgeWalker: Wire detection failed\n"); + return Py::None(); + } else { + for (auto& w:sortedWires) { + PyObject* wire = new TopoShapeWirePy(new TopoShape(w)); + result.append(Py::asObject(wire)); + } + } return result; } @@ -313,30 +316,29 @@ private: } if (edgeList.empty()) { - Base::Console().Log("LOG - findOuterWire: input is empty\n"); + Base::Console().Message("ATDP::findOuterWire: input is empty\n"); return Py::None(); } + std::vector closedEdges; + edgeList = DrawProjectSplit::scrubEdges(edgeList, closedEdges); + PyObject* outerWire = nullptr; - bool success = false; + std::vector sortedWires; try { EdgeWalker eWalker; - eWalker.loadEdges(edgeList); - success = eWalker.perform(); - if (success) { - std::vector rWires = eWalker.getResultNoDups(); - std::vector sortedWires = eWalker.sortStrip(rWires, true); - outerWire = new TopoShapeWirePy(new TopoShape(*sortedWires.begin())); - } else { - Base::Console().Warning("findOuterWire: input is not planar graph. Wire detection not done\n"); - } + sortedWires = eWalker.execute(edgeList); } catch (Base::Exception &e) { e.setPyException(); throw Py::Exception(); } - if (!success) { + + if(sortedWires.empty()) { + Base::Console().Warning("ATDP::findOuterWire: Outline wire detection failed\n"); return Py::None(); + } else { + outerWire = new TopoShapeWirePy(new TopoShape(*sortedWires.begin())); } return Py::asObject(outerWire); } @@ -382,28 +384,25 @@ private: return Py::None(); } + std::vector closedEdges; + edgeList = DrawProjectSplit::scrubEdges(edgeList, closedEdges); + PyObject* outerWire = nullptr; - bool success = false; + std::vector sortedWires; try { EdgeWalker eWalker; - eWalker.loadEdges(edgeList); - if(eWalker.perform()) { - std::vector rWires = eWalker.getResultNoDups(); - std::vector sortedWires = eWalker.sortStrip(rWires, true); - if(!sortedWires.empty()) { - outerWire = new TopoShapeWirePy(new TopoShape(*sortedWires.begin())); - success = true; - } - } else { - Base::Console().Warning("ATDP::findShapeOutline: input is not planar graph. Wire detection not done\n"); - } + sortedWires = eWalker.execute(edgeList); } catch (Base::Exception &e) { e.setPyException(); throw Py::Exception(); } - if (!success) { + + if(sortedWires.empty()) { + Base::Console().Warning("ATDP::findShapeOutline: Outline wire detection failed\n"); return Py::None(); + } else { + outerWire = new TopoShapeWirePy(new TopoShape(*sortedWires.begin())); } return Py::asObject(outerWire); } diff --git a/src/Mod/TechDraw/App/CMakeLists.txt b/src/Mod/TechDraw/App/CMakeLists.txt index adc4cd507a..6f37a674d5 100644 --- a/src/Mod/TechDraw/App/CMakeLists.txt +++ b/src/Mod/TechDraw/App/CMakeLists.txt @@ -166,6 +166,7 @@ SET(TechDraw_SRCS TechDrawExport.h ProjectionAlgos.cpp ProjectionAlgos.h + EWTOLERANCE.h ) SET(Geometry_SRCS diff --git a/src/Mod/TechDraw/App/DrawProjectSplit.cpp b/src/Mod/TechDraw/App/DrawProjectSplit.cpp index d661412f8a..d538fb3296 100644 --- a/src/Mod/TechDraw/App/DrawProjectSplit.cpp +++ b/src/Mod/TechDraw/App/DrawProjectSplit.cpp @@ -29,18 +29,24 @@ #include #include #include +#include +#include #include +#include #include #include #include #include #include #include +#include +#include #include #include #include #include #include +#include #include #include #include @@ -50,6 +56,7 @@ #include #include #include +#include #include #include #include @@ -78,12 +85,8 @@ #include "DrawUtil.h" #include "Geometry.h" #include "GeometryObject.h" +#include "EWTOLERANCE.h" #include "DrawProjectSplit.h" -#include "DrawHatch.h" -#include "EdgeWalker.h" - - -//#include // generated from DrawProjectSplitPy.xml using namespace TechDraw; using namespace std; @@ -101,48 +104,55 @@ DrawProjectSplit::~DrawProjectSplit() { } +//make a projection of shape and return the edges +//used by python outline routines std::vector DrawProjectSplit::getEdgesForWalker(TopoDS_Shape shape, double scale, Base::Vector3d direction) { - std::vector result; + std::vector edgesIn; if (shape.IsNull()) { - return result; + return edgesIn; } BRepBuilderAPI_Copy BuilderCopy(shape); TopoDS_Shape copyShape = BuilderCopy.Shape(); - gp_Pnt inputCenter(0, 0,0); + gp_Pnt inputCenter(0, 0, 0); TopoDS_Shape scaledShape; scaledShape = TechDraw::scaleShape(copyShape, - scale); -// gp_Ax2 viewAxis = TechDraw::getViewAxis(Base::Vector3d(0.0, 0.0, 0.0), direction); - gp_Ax2 viewAxis = TechDraw::legacyViewAxis1(Base::Vector3d(0.0, 0.0, 0.0), direction); + scale); + gp_Ax2 viewAxis = TechDraw::legacyViewAxis1(Base::Vector3d(0.0, 0.0, 0.0), direction, false); TechDraw::GeometryObject* go = buildGeometryObject(scaledShape, viewAxis); - result = getEdges(go); + const std::vector& goEdges = go->getVisibleFaceEdges(false, false); + for (auto& e: goEdges){ + edgesIn.push_back(e->occEdge); + } + + std::vector nonZero; + for (auto& e: edgesIn) { //drop any zero edges (shouldn't be any by now!!!) + if (!DrawUtil::isZeroEdge(e, 2.0 * EWTOLERANCE)) { + nonZero.push_back(e); + } else { + Base::Console().Message("DPS::getEdgesForWalker found ZeroEdge!\n"); + } + } delete go; - return result; + return nonZero; } - +//project the shape using viewAxis (coordinate system) and return a geometry object TechDraw::GeometryObject* DrawProjectSplit::buildGeometryObject(TopoDS_Shape shape, const gp_Ax2& viewAxis) { TechDraw::GeometryObject* geometryObject = new TechDraw::GeometryObject("DrawProjectSplit", nullptr); if (geometryObject->usePolygonHLR()){ - geometryObject->projectShapeWithPolygonAlgo(shape, - viewAxis); + geometryObject->projectShapeWithPolygonAlgo(shape, viewAxis); } else{ - geometryObject->projectShape(shape, - viewAxis); + //note that this runs in the main thread, unlike DrawViewPart + geometryObject->projectShape(shape, viewAxis); } - - geometryObject->extractGeometry(TechDraw::ecHARD, //always show the hard&outline visible lines - true); - geometryObject->extractGeometry(TechDraw::ecOUTLINE, - true); return geometryObject; } @@ -159,10 +169,8 @@ std::vector DrawProjectSplit::getEdges(TechDraw::GeometryObject* ge std::vector faceEdges; std::vector nonZero; for (auto& e:origEdges) { //drop any zero edges (shouldn't be any by now!!!) - if (!DrawUtil::isZeroEdge(e)) { + if (!DrawUtil::isZeroEdge(e, 2.0 * EWTOLERANCE)) { nonZero.push_back(e); - } else { - Base::Console().Message("INFO - DPS::getEdges found ZeroEdge!\n"); } } faceEdges = nonZero; @@ -180,11 +188,9 @@ std::vector DrawProjectSplit::getEdges(TechDraw::GeometryObject* ge BRepBndLib::AddOptimal(*itOuter, sOuter); sOuter.SetGap(0.1); if (sOuter.IsVoid()) { - Base::Console().Message("DPS::Extract Faces - outer Bnd_Box is void\n"); continue; } if (DrawUtil::isZeroEdge(*itOuter)) { - Base::Console().Message("DPS::extractFaces - outerEdge: %d is ZeroEdge\n", iOuter); //this is not finding ZeroEdges continue; //skip zero length edges. shouldn't happen ;) } int iInner = 0; @@ -201,7 +207,6 @@ std::vector DrawProjectSplit::getEdges(TechDraw::GeometryObject* ge BRepBndLib::AddOptimal(*itInner, sInner); sInner.SetGap(0.1); if (sInner.IsVoid()) { - Base::Console().Log("INFO - DPS::Extract Faces - inner Bnd_Box is void\n"); continue; } if (sOuter.IsOut(sInner)) { //bboxes of edges don't intersect, don't bother @@ -233,10 +238,9 @@ std::vector DrawProjectSplit::getEdges(TechDraw::GeometryObject* ge sorted.erase(last, sorted.end()); //remove dupls std::vector newEdges = splitEdges(faceEdges, sorted); - if (newEdges.empty()) { - Base::Console().Log("LOG - DPS::extractFaces - no newEdges\n"); + if (!newEdges.empty()) { + newEdges = removeDuplicateEdges(newEdges); } - newEdges = removeDuplicateEdges(newEdges); return newEdges; } @@ -253,9 +257,7 @@ bool DrawProjectSplit::isOnEdge(TopoDS_Edge e, TopoDS_Vertex v, double& param, b Bnd_Box sBox; BRepBndLib::AddOptimal(e, sBox); sBox.SetGap(0.1); - if (sBox.IsVoid()) { - Base::Console().Message("DPS::isOnEdge - Bnd_Box is void\n"); - } else { + if (!sBox.IsVoid()) { gp_Pnt pt = BRep_Tool::Pnt(v); if (sBox.IsOut(pt)) { outOfBox = true; @@ -370,7 +372,7 @@ std::vector DrawProjectSplit::split1Edge(TopoDS_Edge e, std::vector } } catch (Standard_Failure&) { - Base::Console().Message("LOG - DPS::split1Edge failed building edge segment\n"); + Base::Console().Message("DPS::split1Edge failed building edge segment\n"); } } return result; @@ -445,12 +447,12 @@ std::vector DrawProjectSplit::removeDuplicateEdges(std::vector DrawProjectSplit::scrubEdges(const std::vector& origEdges, + std::vector &closedEdges) +{ +// Base::Console().Message("DPS::scrubEdges() - edges in: %d\n", origEdges.size()); + //make a copy of the input edges so the loose tolerances of face finding are + //not applied to the real edge geometry. See TopoDS_Shape::TShape(). + std::vector copyEdges; + bool copyGeometry = true; + bool copyMesh = false; + for (const auto& tdEdge: origEdges) { + if (!DrawUtil::isZeroEdge(tdEdge->occEdge, 2.0 * EWTOLERANCE)) { + BRepBuilderAPI_Copy copier(tdEdge->occEdge, copyGeometry, copyMesh); + copyEdges.push_back(TopoDS::Edge(copier.Shape())); + } + } + return scrubEdges(copyEdges, closedEdges); +} + +//origEdges should be a copy of the original edges since the underlying TShape will be modified. +std::vector DrawProjectSplit::scrubEdges(std::vector& origEdges, + std::vector &closedEdges) +{ + //HLR usually delivers overlapping edges. We need to refine edge overlaps + //into non-overlapping pieces + std::vector noOverlaps; + noOverlaps = DrawProjectSplit::removeOverlapEdges(origEdges); + + //HLR algo does not provide all edge intersections. + //need to split edges at intersection points. + std::vector splitEdges; + splitEdges = DrawProjectSplit::splitIntersectingEdges(noOverlaps); + + //separate any closed edges (ex circle) from the edge pile so as not to confuse + //the edge walker later. Closed edges are added back in the caller after + //EdgeWalker finds the faces using the open edges + std::vector openEdges; + for (auto& edge : splitEdges) { + if (BRep_Tool::IsClosed(edge)) { + closedEdges.push_back(edge); + } else { + openEdges.push_back(edge); + } + } + + //find all the unique vertices and count how many edges terminate at each, then + //remove edges that can't be part of a closed region since they are not connected at both ends + vertexMap verts = DrawProjectSplit::getUniqueVertexes(openEdges); + std::vector cleanEdges; + cleanEdges = DrawProjectSplit::pruneUnconnected(verts, openEdges); + + return cleanEdges; +} + +//extract a map of unique vertexes based on start and end point of each edge in +//the input vector and count the usage of each unique vertex +vertexMap DrawProjectSplit::getUniqueVertexes(std::vector inEdges) +{ + vertexMap verts; + //count the occurrences of each vertex in the pile + for (auto& edge: inEdges) { + gp_Pnt p = BRep_Tool::Pnt(TopExp::FirstVertex(edge)); + Base::Vector3d v0(p.X(), p.Y(), p.Z()); + vertexMap::iterator it0(verts.find(v0)); + if (it0 != verts.end()) { + it0->second++; + } else { + verts[v0] = 1; + } + p = BRep_Tool::Pnt(TopExp::LastVertex(edge)); + Base::Vector3d v1(p.X(), p.Y(), p.Z()); + vertexMap::iterator it1(verts.find(v1)); + if (it1 != verts.end()) { + it1->second++; + } else { + verts[v1] = 1; + } + } + return verts; +} + +//if an edge is not connected at both ends, then it can't be part of a face boundary +//and unconnected edges may confuse up the edge walker. +//note: closed edges have been removed by this point for later handling +std::vector DrawProjectSplit::pruneUnconnected(vertexMap verts, + std::vector edges) +{ +// Base::Console().Message("DPS::pruneUnconnected() - edges in: %d\n", edges.size()); + //check if edge ends are used at least twice => edge is joined to another edge + std::vector newPile; + std::vector deadEnds; + for (auto& edge: edges) { + gp_Pnt p = BRep_Tool::Pnt(TopExp::FirstVertex(edge)); + Base::Vector3d v0(p.X(), p.Y(), p.Z()); + int count0 = 0; + vertexMap::iterator it0(verts.find(v0)); + if (it0 != verts.end()) { + count0 = it0->second; + } + p = BRep_Tool::Pnt(TopExp::LastVertex(edge)); + Base::Vector3d v1(p.X(), p.Y(), p.Z()); + int count1 = 0; + vertexMap::iterator it1(verts.find(v1)); + if (it1 != verts.end()) { + count1 = it1->second; + } + if ((count0 > 1) && (count1 > 1)) { //connected at both ends + newPile.push_back(edge); + } else if ((count0 == 1) && (count1 == 1)) { + //completely disconnected edge. just drop it. + continue; + } else { + //only connected at 1 end + deadEnds.push_back(edge); //could separate dead ends here + } + } + + return newPile; +} + +bool DrawProjectSplit::sameEndPoints(TopoDS_Edge& e1, TopoDS_Edge& e2) +{ + bool result = false; + TopoDS_Vertex first1 = TopExp::FirstVertex(e1); + TopoDS_Vertex last1 = TopExp::LastVertex(e1); + TopoDS_Vertex first2 = TopExp::FirstVertex(e2); + TopoDS_Vertex last2 = TopExp::LastVertex(e2); + + if (DrawUtil::vertexEqual(first1, first2) && + DrawUtil::vertexEqual(last1, last2) ) { + result = true; + } else if (DrawUtil::vertexEqual(first1, last2) && + DrawUtil::vertexEqual(last1, first2) ) { + result = true; + } + + return result; +} + +#define e0ISSUBSET 0 +#define e1ISSUBSET 1 +#define EDGEOVERLAP 2 +#define NOTASUBSET 3 + +//eliminate edges that overlap another edge +std::vector DrawProjectSplit::removeOverlapEdges(std::vector inEdges) +{ +// Base::Console().Message("DPS::removeOverlapEdges() - %d edges in\n", inEdges.size()); + std::vector outEdges; + std::vector overlapEdges; + std::vector skipThisEdge(inEdges.size(), false); + int edgeCount = inEdges.size(); + int ie0 = 0; + for (; ie0 < edgeCount; ie0++) { + if (skipThisEdge.at(ie0)) { + continue; + } + int ie1 = ie0 + 1; + for (; ie1 < edgeCount; ie1++) { + if (skipThisEdge.at(ie1)) { + continue; + } + int rc = isSubset(inEdges.at(ie0), inEdges.at(ie1)); + if (rc == e0ISSUBSET) { + skipThisEdge.at(ie0) = true; + break; //stop checking ie0 + } else if (rc == e1ISSUBSET) { + skipThisEdge.at(ie1) = true; + } else if (rc == EDGEOVERLAP) { + skipThisEdge.at(ie0) = true; + skipThisEdge.at(ie1) = true; + std::vector olap = fuseEdges(inEdges.at(ie0), inEdges.at(ie1)); + if (!olap.empty()) { + overlapEdges.insert(overlapEdges.end(), olap.begin(), olap.end()); + } + break; //stop checking ie0 + } + } //inner loop + } //outer loop + + int iOut = 0; + for (auto& e: inEdges) { + if (!skipThisEdge.at(iOut)) { + outEdges.push_back(e); + } + iOut++; + } + + if (!overlapEdges.empty()) { + outEdges.insert(outEdges.end(), overlapEdges.begin(), overlapEdges.end()); + } + return outEdges; +} + +//determine if edge0 & edge1 are superimposed, and classify the type of overlap +int DrawProjectSplit::isSubset(TopoDS_Edge& edge0, TopoDS_Edge& edge1) +{ + if (!boxesIntersect(edge0, edge1)) { + return NOTASUBSET; //boxes don't intersect, so edges do not overlap + } + + //bboxes of edges intersect + BRepAlgoAPI_Common anOp; + anOp.SetFuzzyValue (FUZZYADJUST * EWTOLERANCE); + TopTools_ListOfShape anArg1, anArg2; + anArg1.Append (edge0); + anArg2.Append (edge1); + anOp.SetArguments (anArg1); + anOp.SetTools (anArg2); + anOp.Build(); + TopoDS_Shape aRes = anOp.Shape(); //always a compound + if (aRes.IsNull()) { + return NOTASUBSET; //no common segment + } + std::vector commonEdgeList; + TopExp_Explorer edges(aRes, TopAbs_EDGE); + for (int i = 1; edges.More(); edges.Next(), i++) { + commonEdgeList.push_back(TopoDS::Edge(edges.Current())); + } + if (commonEdgeList.empty()) { + return NOTASUBSET; + } + //we're only going to deal with the situation where the common of the edges + //is a single edge. A really odd pair of edges could have >1 edge in their + //common. + TopoDS_Edge commonEdge = commonEdgeList.at(0); + if (sameEndPoints(edge1, commonEdge)) { + return e1ISSUBSET; //e1 is a subset of e0 + } + if (sameEndPoints(edge0, commonEdge)) { + return e0ISSUBSET; //e0 is a subset of e1 + } + // edge0 is not a subset of edge1, nor is edge1 a subset of edge0, but they have a common segment + return EDGEOVERLAP; +} + +//edge0 and edge1 overlap, so we need to make 3 edges - part of edge0, common segment, part of edge1 +std::vector DrawProjectSplit::fuseEdges(TopoDS_Edge &edge0, TopoDS_Edge &edge1) +{ + std::vector edgeList; + BRepAlgoAPI_Fuse anOp; + anOp.SetFuzzyValue (FUZZYADJUST * EWTOLERANCE); + TopTools_ListOfShape anArg1, anArg2; + anArg1.Append (edge0); + anArg2.Append (edge1); + anOp.SetArguments (anArg1); + anOp.SetTools (anArg2); + anOp.Build(); + TopoDS_Shape aRes = anOp.Shape(); //always a compound + if (aRes.IsNull()) { + return edgeList; //empty result + } + TopExp_Explorer edges(aRes, TopAbs_EDGE); + for (int i = 1; edges.More(); edges.Next(), i++) { + edgeList.push_back(TopoDS::Edge(edges.Current())); + } + return edgeList; +} + +//split edges that intersect into pieces. +std::vector DrawProjectSplit::splitIntersectingEdges(std::vector& inEdges) +{ +// Base::Console().Message("DPS::splitIntersectingEdges() - edges in: %d\n", inEdges.size()); + std::vector outEdges; + std::vector skipThisEdge(inEdges.size(), false); + int edgeCount = inEdges.size(); + int iEdge0 = 0; + for (; iEdge0 < edgeCount; iEdge0++) { //all but last one + if (skipThisEdge.at(iEdge0)) { + continue; + } + int iEdge1 = iEdge0 + 1; + bool outerEdgeSplit = false; + for (; iEdge1 < edgeCount; iEdge1++) { + if (skipThisEdge.at(iEdge1)) { + continue; + } + + if (boxesIntersect(inEdges.at(iEdge0), inEdges.at(iEdge1))) { + std::vector intersectEdges = fuseEdges(inEdges.at(iEdge0), inEdges.at(iEdge1)); + if (intersectEdges.empty()) { + //don't think this can happen. fusion of disjoint edges is 2 edges. + //maybe an error? + continue; //next inner edge + } + + if (intersectEdges.size() == 1) { + //one edge is a subset of the other. + if (sameEndPoints(inEdges.at(iEdge0), intersectEdges.front())) { + //we got the outer edge back so mark the inner edge + skipThisEdge.at(iEdge1); + } else if (sameEndPoints(inEdges.at(iEdge1), intersectEdges.front())) { + //we got the inner edge back so mark the outer edge and go to the next outer edge + skipThisEdge.at(iEdge0); + break; //next outer edge + } else { + //not sure what this means? bad geometry? + } + + } else if (intersectEdges.size() == 2) { + //got the input edges back, so no intersection. carry on with next inner edge + continue; //next inner edge + + } else if (intersectEdges.size() == 3) { + //we have split 1 edge at a vertex of the other edge + //check if outer edge is the one split + bool innerEdgeSplit = false; + for (auto& interEdge : intersectEdges) { + if (!sameEndPoints(inEdges.at(iEdge0), interEdge) && + !sameEndPoints(inEdges.at(iEdge1), interEdge)) { + //interEdge does not match either outer or inner edge, + //so this is a piece of the split edge and we need to add it + //to end of list + inEdges.push_back(interEdge); + skipThisEdge.push_back(false); + edgeCount++; + } + if (sameEndPoints(inEdges.at(iEdge0), interEdge)) { + //outer edge is in output, so it was not split. + //therefore the inner edge was split and we should skip it in the future + //the two pieces of the split edge will have been added to edgesToKeep + //in the previous if + innerEdgeSplit = true; + skipThisEdge.at(iEdge1) = true; + } else if (sameEndPoints(inEdges.at(iEdge1), interEdge)) { + //inner edge is in output, so it was not split. + //therefore the outer edge was split and we should skip it in the future. + outerEdgeSplit = true; + skipThisEdge.at(iEdge0) = true; + } + } + if (!innerEdgeSplit && !outerEdgeSplit) { + //neither edge found in output, so this was a partial overlap, so + //both edges are replaced by the 3 split pieces + //Q: why does this happen if we have run pruneOverlaps before this??? + skipThisEdge.at(iEdge0) = true; + skipThisEdge.at(iEdge1) = true; + outerEdgeSplit = true; + } + if (outerEdgeSplit) { + //we can't use the outer edge any more, so we should exit the inner loop + break; + } + + } else if (intersectEdges.size() == 4) { + //we have split both edges at a single intersection + skipThisEdge.at(iEdge0) = true; + skipThisEdge.at(iEdge1) = true; + inEdges.insert(inEdges.end(), intersectEdges.begin(), intersectEdges.end()); + skipThisEdge.insert(skipThisEdge.end(), { false, false, false, false}); + edgeCount += 4; + outerEdgeSplit = true; + break; + + } else { + //this means multiple intersections of the 2 edges. we don't handle that yet. + continue; //next inner edge? + } + + } else { + //bboxes of edges do not intersect, so edges do not intersect + } + } //inner loop boundary + + if (!outerEdgeSplit) { + //outer edge[iEdge0] was not split, so add it to the output and mark it as used + outEdges.push_back(inEdges.at(iEdge0)); + skipThisEdge.at(iEdge0) = true; //superfluous? + } + } //outer loop boundary + + if (!skipThisEdge.back()) { + //last entry has not been split, so add it to output + outEdges.push_back(inEdges.back()); + } + + return outEdges; +} + +bool DrawProjectSplit::boxesIntersect(TopoDS_Edge& edge0, TopoDS_Edge& edge1) +{ + Bnd_Box box0, box1; + BRepBndLib::Add(edge0, box0); + box0.SetGap(0.1); //generous + BRepBndLib::Add(edge1, box1); + box1.SetGap(0.1); + if (box0.IsOut(box1)) { + return false; //boxes don't intersect + } + return true; +} + +//this is an aid to debugging and isn't used in normal processing. +void DrawProjectSplit::dumpVertexMap(vertexMap verts) +{ + Base::Console().Message("DPS::dumpVertexMap - %d verts\n", verts.size()); + int iVert = 0; + for (auto& item : verts) { + Base::Console().Message("%d: %s - %d\n",iVert, + DrawUtil::formatVector(item.first).c_str(), item.second); + iVert++; + } +} diff --git a/src/Mod/TechDraw/App/DrawProjectSplit.h b/src/Mod/TechDraw/App/DrawProjectSplit.h index 5320ca9b35..390bfd20b4 100644 --- a/src/Mod/TechDraw/App/DrawProjectSplit.h +++ b/src/Mod/TechDraw/App/DrawProjectSplit.h @@ -32,6 +32,9 @@ #include +#include "DrawUtil.h" +#include "Geometry.h" + class gp_Pnt; class gp_Ax2; @@ -44,6 +47,10 @@ class BaseGeom; namespace TechDraw { + +//magic number for finding parameter of point on curve +#define PARAM_MAX_DIST 0.000001 + struct splitPoint { int i; Base::Vector3d v; @@ -69,6 +76,21 @@ public: static bool edgeEqual(const edgeSortItem& e1, const edgeSortItem& e2); std::string dump(); }; + +using vertexMap = std::map; + +class edgeVectorEntry { +public: + edgeVectorEntry(TopoDS_Edge e, bool flag) { + edge = e; + validFlag = flag; + } + ~edgeVectorEntry() = default; + + TopoDS_Edge edge; + bool validFlag; +}; + class TechDrawExport DrawProjectSplit { public: @@ -89,6 +111,28 @@ public: static std::vector removeDuplicateEdges(std::vector& inEdges); static std::vector sortEdges(std::vector& e, bool ascend); + + //routines for revised face finding approach + static std::vector scrubEdges(const std::vector &origEdges, + std::vector& closedEdges); + static std::vector scrubEdges(std::vector& origEdges, + std::vector& closedEdges); + static vertexMap getUniqueVertexes(std::vector inEdges); + static std::vector pruneUnconnected(vertexMap verts, + std::vector edges); + static std::vector removeOverlapEdges(std::vector inEdges); + static std::vector splitIntersectingEdges(std::vector& inEdges); + + static bool sameEndPoints(TopoDS_Edge& e1, + TopoDS_Edge& e2); + static int isSubset(TopoDS_Edge &e0, + TopoDS_Edge &e1); + static std::vector fuseEdges(TopoDS_Edge& e0, + TopoDS_Edge& e1); + static bool boxesIntersect(TopoDS_Edge& e0, + TopoDS_Edge& e1); + static void dumpVertexMap(vertexMap verts); + protected: static std::vector getEdges(TechDraw::GeometryObject* geometryObject); diff --git a/src/Mod/TechDraw/App/DrawUtil.cpp b/src/Mod/TechDraw/App/DrawUtil.cpp index 30af767fbc..4f7607605c 100644 --- a/src/Mod/TechDraw/App/DrawUtil.cpp +++ b/src/Mod/TechDraw/App/DrawUtil.cpp @@ -75,6 +75,9 @@ #include #include + +#include "EWTOLERANCE.h" +#include "GeometryObject.h" #include "DrawUtil.h" using namespace TechDraw; @@ -155,9 +158,8 @@ bool DrawUtil::isZeroEdge(TopoDS_Edge e, double tolerance) TopoDS_Vertex vEnd = TopExp::LastVertex(e); if (isSamePoint(vStart, vEnd, tolerance)) { //closed edge will have same V's but non-zero length - GProp_GProps props; - BRepGProp::LinearProperties(e, props); - double len = props.Mass(); + BRepAdaptor_Curve adapt(e); + double len = GCPnts_AbscissaPoint::Length(adapt, Precision::Confusion()); if (len > tolerance) { return false; } @@ -206,47 +208,61 @@ double DrawUtil::angleWithX(TopoDS_Edge e, TopoDS_Vertex v, double tolerance) { double param = 0; - //find tangent @ v - double adjust = 1.0; //occ tangent points in direction of curve. at lastVert we need to reverse it. BRepAdaptor_Curve adapt(e); if (isFirstVert(e, v,tolerance)) { param = adapt.FirstParameter(); } else if (isLastVert(e, v,tolerance)) { param = adapt.LastParameter(); - adjust = -1; } else { //TARFU Base::Console().Message("Error: DU::angleWithX - v is neither first nor last \n"); - //must be able to get non-terminal point parm from curve/ + } + gp_Pnt paramPoint; + gp_Vec derivative; + const Handle(Geom_Curve) c = adapt.Curve().Curve(); + c->D1(param, paramPoint, derivative); + double angle = atan2(derivative.Y(), derivative.X()); + if (angle < 0) { //map from [-PI:PI] to [0:2PI] + angle += 2.0 * M_PI; + } + return angle; +} + +//! find angle of incidence at First/LastVertex +double DrawUtil::incidenceAngleAtVertex(TopoDS_Edge e, TopoDS_Vertex v, double tolerance) +{ + double incidenceAngle = 0; + + BRepAdaptor_Curve adapt(e); + double paramRange = adapt.LastParameter() - adapt.FirstParameter(); + double paramOffset = paramRange / 100.0; + double vertexParam; + Base::Vector3d anglePoint = DrawUtil::vertex2Vector(v); + Base::Vector3d offsetPoint, incidenceVec; + int noTangents = 0; + if (isFirstVert(e,v,tolerance)) { + vertexParam = adapt.FirstParameter(); + BRepLProp_CLProps prop(adapt, vertexParam + paramOffset, noTangents, Precision::Confusion()); + const gp_Pnt& gOffsetPoint = prop.Value(); + offsetPoint = Base::Vector3d(gOffsetPoint.X(), gOffsetPoint.Y(), gOffsetPoint.Z()); + } else if (isLastVert(e,v,tolerance)) { + vertexParam = adapt.LastParameter(); + BRepLProp_CLProps prop(adapt, vertexParam - paramOffset, noTangents, Precision::Confusion()); + const gp_Pnt& gOffsetPoint = prop.Value(); + offsetPoint = Base::Vector3d(gOffsetPoint.X(), gOffsetPoint.Y(), gOffsetPoint.Z()); + } else { + //TARFU +// Base::Console().Message("DU::incidenceAngle - v is neither first nor last \n"); + } + incidenceVec = anglePoint - offsetPoint; + incidenceAngle = atan2(incidenceVec.y, incidenceVec.x); + + //map to [0:2PI] + if (incidenceAngle < 0.0) { + incidenceAngle = M_2PI + incidenceAngle; } - Base::Vector3d uVec(0.0, 0.0, 0.0); - gp_Dir uDir; - BRepLProp_CLProps prop(adapt, param, 2,tolerance); - if (prop.IsTangentDefined()) { - prop.Tangent(uDir); - uVec = Base::Vector3d(uDir.X(), uDir.Y(), uDir.Z()) * adjust; - } else { - //this bit is a little sketchy - gp_Pnt gstart = BRep_Tool::Pnt(TopExp::FirstVertex(e)); - Base::Vector3d start(gstart.X(), gstart.Y(), gstart.Z()); - gp_Pnt gend = BRep_Tool::Pnt(TopExp::LastVertex(e)); - Base::Vector3d end(gend.X(), gend.Y(), gend.Z()); - if (isFirstVert(e, v,tolerance)) { - uVec = end - start; - } else if (isLastVert(e, v,tolerance)) { - uVec = end - start; - } else { - gp_Pnt errPnt = BRep_Tool::Pnt(v); - Base::Console().Warning("angleWithX: Tangent not defined at (%.3f, %.3f, %.3f)\n", errPnt.X(), errPnt.Y(), errPnt.Z()); - //throw ?????? - } - } - double result = atan2(uVec.y, uVec.x); - if (result < 0) { //map from [-PI:PI] to [0:2PI] - result += 2.0 * M_PI; - } - return result; + return incidenceAngle; } bool DrawUtil::isFirstVert(TopoDS_Edge e, TopoDS_Vertex v, double tolerance) @@ -399,20 +415,77 @@ std::string DrawUtil::formatVector(const QPointF& v) } //! compare 2 vectors for sorting - true if v1 < v2 +//! precision::Confusion() is too strict for vertex - vertex comparisons bool DrawUtil::vectorLess(const Base::Vector3d& v1, const Base::Vector3d& v2) { - if ((v1 - v2).Length() > Precision::Confusion()) { //ie v1 != v2 - if (!DrawUtil::fpCompare(v1.x, v2.x)) { - return v1.x < v2.x; - } else if (!DrawUtil::fpCompare(v1.y, v2.y)) { - return v1.y < v2.y; + if ((v1 - v2).Length() > EWTOLERANCE) { //ie v1 != v2 + if (!DrawUtil::fpCompare(v1.x, v2.x, 2.0 * EWTOLERANCE)) { + return (v1.x < v2.x); + } else if (!DrawUtil::fpCompare(v1.y, v2.y, 2.0 * EWTOLERANCE)) { + return (v1.y < v2.y); } else { - return v1.z < v2.z; + return (v1.z < v2.z); } } return false; } +//! test for equality of two vertexes using the vectorLess comparator as used +//! in sorts and containers +bool DrawUtil::vertexEqual(TopoDS_Vertex& v1, TopoDS_Vertex& v2) +{ + gp_Pnt gv1 = BRep_Tool::Pnt(v1); + gp_Pnt gv2 = BRep_Tool::Pnt(v2); + Base::Vector3d vv1(gv1.X(), gv1.Y(), gv1.Z()); + Base::Vector3d vv2(gv2.X(), gv2.Y(), gv2.Z()); + return vectorEqual(vv1, vv2); +} + +//! test for equality of two vectors using the vectorLess comparator as used +//! in sorts and containers +bool DrawUtil::vectorEqual(Base::Vector3d& v1, Base::Vector3d& v2) +{ + bool less1 = vectorLess(v1, v2); + bool less2 = vectorLess(v2, v1); + return !less1 && !less2; +} + +//TODO: the next 2 could be templated +//construct a compound shape from a list of edges +TopoDS_Shape DrawUtil::vectorToCompound(std::vector vecIn) +{ + BRep_Builder builder; + TopoDS_Compound compOut; + builder.MakeCompound(compOut); + for (auto& v : vecIn) { + builder.Add(compOut, v); + } + return TechDraw::mirrorShape(compOut); +} + +//construct a compound shape from a list of wires +TopoDS_Shape DrawUtil::vectorToCompound(std::vector vecIn) +{ + BRep_Builder builder; + TopoDS_Compound compOut; + builder.MakeCompound(compOut); + for (auto& v : vecIn) { + builder.Add(compOut, v); + } + return TechDraw::mirrorShape(compOut); +} + +//constructs a list of edges from a shape +std::vector DrawUtil::shapeToVector(TopoDS_Shape shapeIn) +{ + std::vector vectorOut; + TopExp_Explorer expl(shapeIn, TopAbs_EDGE); + for ( ; expl.More(); expl.Next()) { + vectorOut.push_back(TopoDS::Edge(expl.Current())); + } + return vectorOut; +} + //!convert fromPoint in coordinate system fromSystem to reference coordinate system Base::Vector3d DrawUtil::toR3(const gp_Ax2& fromSystem, const Base::Vector3d& fromPoint) { @@ -742,17 +815,6 @@ bool DrawUtil::isCrazy(TopoDS_Edge e) return false; } -//construct a compound shape from a list of edges -TopoDS_Shape DrawUtil::vectorToCompound(std::vector vecIn) -{ - BRep_Builder builder; - TopoDS_Compound compOut; - builder.MakeCompound(compOut); - for (auto& v : vecIn) { - builder.Add(compOut, v); - } - return TopoDS_Compound(std::move(compOut)); -} //get 3d position of a face's center Base::Vector3d DrawUtil::getFaceCenter(TopoDS_Face f) { @@ -1220,7 +1282,7 @@ void DrawUtil::dumpEdges(const char* text, const TopoDS_Shape& s) void DrawUtil::dump1Vertex(const char* text, const TopoDS_Vertex& v) { - Base::Console().Message("DUMP - dump1Vertex - %s\n", text); +// Base::Console().Message("DUMP - dump1Vertex - %s\n",text); gp_Pnt pnt = BRep_Tool::Pnt(v); Base::Console().Message("%s: (%.3f, %.3f, %.3f)\n", text, pnt.X(), pnt.Y(), pnt.Z()); } diff --git a/src/Mod/TechDraw/App/DrawUtil.h b/src/Mod/TechDraw/App/DrawUtil.h index cf2a4fd7e2..56fc899528 100644 --- a/src/Mod/TechDraw/App/DrawUtil.h +++ b/src/Mod/TechDraw/App/DrawUtil.h @@ -42,6 +42,7 @@ #include #include #include +#include #include #include @@ -54,6 +55,7 @@ #endif #define VERTEXTOLERANCE (2.0 * Precision::Confusion()) +#define VECTORTOLERANCE (Precision::Confusion()) #define SVG_NS_URI "http://www.w3.org/2000/svg" #define FREECAD_SVG_NS_URI "http://www.freecadweb.org/wiki/index.php?title=Svg_Namespace" @@ -73,6 +75,8 @@ class TechDrawExport DrawUtil { static double sensibleScale(double working_scale); static double angleWithX(TopoDS_Edge e, bool reverse); static double angleWithX(TopoDS_Edge e, TopoDS_Vertex v, double tolerance = VERTEXTOLERANCE); + static double incidenceAngleAtVertex(TopoDS_Edge e, TopoDS_Vertex v, double tolerance); + static bool isFirstVert(TopoDS_Edge e, TopoDS_Vertex v, double tolerance = VERTEXTOLERANCE); static bool isLastVert(TopoDS_Edge e, TopoDS_Vertex v, double tolerance = VERTEXTOLERANCE); static bool fpCompare(const double& d1, const double& d2, double tolerance = FLT_EPSILON); @@ -82,8 +86,6 @@ class TechDrawExport DrawUtil { double yRange) ; static Base::Vector3d vertex2Vector(const TopoDS_Vertex& v); - static TopoDS_Shape vectorToCompound(std::vector vecIn); - static std::string formatVector(const Base::Vector3d& v); static std::string formatVector(const gp_Dir& v); static std::string formatVector(const gp_Dir2d& v); @@ -93,6 +95,19 @@ class TechDrawExport DrawUtil { static std::string formatVector(const QPointF& v); static bool vectorLess(const Base::Vector3d& v1, const Base::Vector3d& v2); + //!std::map require comparator to be a type not a function + struct vectorLessType { + bool operator()(const Base::Vector3d& a, const Base::Vector3d& b) const { + return DrawUtil::vectorLess(a, b); + } + }; + static bool vertexEqual(TopoDS_Vertex& v1, TopoDS_Vertex& v2); + static bool vectorEqual(Base::Vector3d& v1, Base::Vector3d& v2); + + static TopoDS_Shape vectorToCompound(std::vector vecIn); + static TopoDS_Shape vectorToCompound(std::vector vecIn); + static std::vector shapeToVector(TopoDS_Shape shapeIn); + static Base::Vector3d toR3(const gp_Ax2& fromSystem, const Base::Vector3d& fromPoint); static bool checkParallel(const Base::Vector3d v1, const Base::Vector3d v2, double tolerance = FLT_EPSILON); //! rotate vector by angle radians around axis through org diff --git a/src/Mod/TechDraw/App/DrawViewPart.cpp b/src/Mod/TechDraw/App/DrawViewPart.cpp index 167a87a987..e3e6437643 100644 --- a/src/Mod/TechDraw/App/DrawViewPart.cpp +++ b/src/Mod/TechDraw/App/DrawViewPart.cpp @@ -41,6 +41,7 @@ #include #include #include +#include #include #include #include @@ -414,8 +415,7 @@ void DrawViewPart::onHlrFinished(void) //start face finding in a separate thread. We don't find faces when using the polygon //HLR method. - if (handleFaces() && !CoarseView.getValue() && - !waitingForFaces() && !waitingForHlr()) { + if (handleFaces() && !CoarseView.getValue() ) { try { //note that &m_faceWatcher in the third parameter is not strictly required, but using the //4 parameter signature instead of the 3 parameter signature prevents clazy warning: @@ -481,127 +481,180 @@ void DrawViewPart::extractFaces() showProgressMessage(getNameInDocument(), "is extracting faces"); - //make a copy of the input edges so the loose tolerances of face finding are - //not applied to the real edge geometry. See TopoDS_Shape::TShape(). const std::vector& goEdges = - geometryObject->getVisibleFaceEdges(SmoothVisible.getValue(), SeamVisible.getValue()); + geometryObject->getVisibleFaceEdges(SmoothVisible.getValue(),SeamVisible.getValue()); if (goEdges.empty()) { Base::Console().Message("DVP::extractFaces - %s - no face edges available!\n", getNameInDocument()); return; } - std::vector copyEdges; - for (auto& tdEdge: goEdges) { - BRepBuilderAPI_Copy copier(tdEdge->occEdge, true, true); //copy occEdge with its geometry (TShape) and mesh info - copyEdges.push_back(TopoDS::Edge(copier.Shape())); - } + if (newFaceFinder()) { + std::vector closedEdges; + std::vector cleanEdges = DrawProjectSplit::scrubEdges(goEdges, closedEdges); - std::vector nonZero; - for (auto& e:copyEdges) { //drop any zero edges (shouldn't be any by now!!!) - if (!DrawUtil::isZeroEdge(e)) { - nonZero.push_back(e); + //use EdgeWalker to make wires from edges + EdgeWalker eWalker; + std::vector sortedWires; + try { + if (!cleanEdges.empty()) { + sortedWires = eWalker.execute(cleanEdges, true); //include outer wire + } } - } - geometryObject->clearFaceGeom(); + catch (Base::Exception &e) { + throw Base::RuntimeError(e.what()); + } + geometryObject->clearFaceGeom(); - //HLR algo does not provide all edge intersections for edge endpoints. - //need to split long edges touched by Vertex of another edge - std::vector splits; - std::vector::iterator itOuter = nonZero.begin(); - int iOuter = 0; - for (; itOuter != nonZero.end(); ++itOuter, iOuter++) { - TopoDS_Vertex v1 = TopExp::FirstVertex((*itOuter)); - TopoDS_Vertex v2 = TopExp::LastVertex((*itOuter)); - Bnd_Box sOuter; - BRepBndLib::AddOptimal(*itOuter, sOuter); - sOuter.SetGap(0.1); - if (sOuter.IsVoid()) { - continue; + std::vector closedWires; + for (auto& e: closedEdges) { + BRepBuilderAPI_MakeWire mkWire(e); + TopoDS_Wire w = mkWire.Wire(); + closedWires.push_back(w); } - if (DrawUtil::isZeroEdge(*itOuter)) { - continue; //skip zero length edges. shouldn't happen ;) + if (!closedWires.empty()) { + sortedWires.insert(sortedWires.end(), closedWires.begin(), closedWires.end()); + //inserting the closedWires that did not go through EdgeWalker into + //sortedWires ruins EdgeWalker's sort by size, so we have to do it again. + sortedWires = eWalker.sortWiresBySize(sortedWires); } - int iInner = 0; - std::vector::iterator itInner = nonZero.begin(); //***sb itOuter + 1; - for (; itInner != nonZero.end(); ++itInner, iInner++) { - if (iInner == iOuter) { + + if (sortedWires.empty()) { + Base::Console().Warning("DVP::extractFaces - %s - Can't make faces from projected edges\n", getNameInDocument()); + } else { + BRepTools::Write(DrawUtil::vectorToCompound(sortedWires), "DVPSortedWires.brep"); //debug + double minWireArea = 0.000001; //arbitrary very small face size + std::vector::iterator itWire = sortedWires.begin(); + for (; itWire != sortedWires.end(); itWire++) { + if (!BRep_Tool::IsClosed(*itWire)) { + continue; //can not make a face from open wire + } else { + double area = ShapeAnalysis::ContourArea(*itWire); + if (area <= minWireArea) { + continue; //can not make a face from wire with no area + } + } + TechDraw::FacePtr f(std::make_shared()); + const TopoDS_Wire& wire = (*itWire); + TechDraw::Wire* w = new TechDraw::Wire(wire); + f->wires.push_back(w); + if (geometryObject) { + geometryObject->addFaceGeom(f); + } + } + } + } else { //use original method + //make a copy of the input edges so the loose tolerances of face finding are + //not applied to the real edge geometry. See TopoDS_Shape::TShape(). + std::vector copyEdges; + bool copyGeometry = true; + bool copyMesh = false; + for (const auto& e: goEdges) { + BRepBuilderAPI_Copy copier(e->occEdge, copyGeometry, copyMesh); + copyEdges.push_back(TopoDS::Edge(copier.Shape())); + } + std::vector nonZero; + for (auto& e: copyEdges) { //drop any zero edges (shouldn't be any by now!!!) + if (!DrawUtil::isZeroEdge(e)) { + nonZero.push_back(e); + } else { + Base::Console().Log("INFO - DVP::extractFaces for %s found ZeroEdge!\n",getNameInDocument()); + } + } + + //HLR algo does not provide all edge intersections for edge endpoints. + //need to split long edges touched by Vertex of another edge + std::vector splits; + std::vector::iterator itOuter = nonZero.begin(); + int iOuter = 0; + for (; itOuter != nonZero.end(); ++itOuter, iOuter++) { //*** itOuter != nonZero.end() - 1 + TopoDS_Vertex v1 = TopExp::FirstVertex((*itOuter)); + TopoDS_Vertex v2 = TopExp::LastVertex((*itOuter)); + Bnd_Box sOuter; + BRepBndLib::AddOptimal(*itOuter, sOuter); + sOuter.SetGap(0.1); + if (sOuter.IsVoid()) { + Base::Console().Log("DVP::Extract Faces - outer Bnd_Box is void for %s\n",getNameInDocument()); continue; } - if (DrawUtil::isZeroEdge((*itInner))) { + if (DrawUtil::isZeroEdge(*itOuter)) { + Base::Console().Log("DVP::extractFaces - outerEdge: %d is ZeroEdge\n",iOuter); //this is not finding ZeroEdges continue; //skip zero length edges. shouldn't happen ;) } + int iInner = 0; + std::vector::iterator itInner = nonZero.begin(); //***sb itOuter + 1; + for (; itInner != nonZero.end(); ++itInner,iInner++) { + if (iInner == iOuter) { + continue; + } + if (DrawUtil::isZeroEdge((*itInner))) { + continue; //skip zero length edges. shouldn't happen ;) + } - Bnd_Box sInner; - BRepBndLib::AddOptimal(*itInner, sInner); - sInner.SetGap(0.1); - if (sInner.IsVoid()) { - continue; + Bnd_Box sInner; + BRepBndLib::AddOptimal(*itInner, sInner); + sInner.SetGap(0.1); + if (sInner.IsVoid()) { + Base::Console().Log("INFO - DVP::Extract Faces - inner Bnd_Box is void for %s\n",getNameInDocument()); + continue; + } + if (sOuter.IsOut(sInner)) { //bboxes of edges don't intersect, don't bother + continue; + } + + double param = -1; + if (DrawProjectSplit::isOnEdge((*itInner),v1,param,false)) { + gp_Pnt pnt1 = BRep_Tool::Pnt(v1); + splitPoint s1; + s1.i = iInner; + s1.v = Base::Vector3d(pnt1.X(),pnt1.Y(),pnt1.Z()); + s1.param = param; + splits.push_back(s1); + } + if (DrawProjectSplit::isOnEdge((*itInner),v2,param,false)) { + gp_Pnt pnt2 = BRep_Tool::Pnt(v2); + splitPoint s2; + s2.i = iInner; + s2.v = Base::Vector3d(pnt2.X(),pnt2.Y(),pnt2.Z()); + s2.param = param; + splits.push_back(s2); + } + } //inner loop + } //outer loop + + std::vector sorted = DrawProjectSplit::sortSplits(splits,true); + auto last = std::unique(sorted.begin(), sorted.end(), DrawProjectSplit::splitEqual); //duplicates to back + sorted.erase(last, sorted.end()); //remove dupl splits + std::vector newEdges = DrawProjectSplit::splitEdges(nonZero,sorted); + + if (newEdges.empty()) { + Base::Console().Log("DVP::extractFaces - no newEdges\n"); + return; + } + + newEdges = DrawProjectSplit::removeDuplicateEdges(newEdges); + + geometryObject->clearFaceGeom(); + + //find all the wires in the pile of faceEdges + std::vector sortedWires; + EdgeWalker eWalker; + sortedWires = eWalker.execute(newEdges); + if (sortedWires.empty()) { + Base::Console().Warning("DVP::extractFaces - %s -Can't make faces from projected edges\n", getNameInDocument()); + return; + } else { + std::vector::iterator itWire = sortedWires.begin(); + for (; itWire != sortedWires.end(); itWire++) { + //version 1: 1 wire/face - no voids in face + TechDraw::FacePtr f(std::make_shared()); + const TopoDS_Wire& wire = (*itWire); + TechDraw::Wire* w = new TechDraw::Wire(wire); + f->wires.push_back(w); + if (geometryObject) { + geometryObject->addFaceGeom(f); + } } - if (sOuter.IsOut(sInner)) { //bboxes of edges don't intersect, don't bother - continue; - } - - double param = -1; //parametric point on edge where the vertex touches - if (DrawProjectSplit::isOnEdge((*itInner), v1, param, false)) { - gp_Pnt pnt1 = BRep_Tool::Pnt(v1); - splitPoint s1; - s1.i = iInner; - s1.v = Base::Vector3d(pnt1.X(), pnt1.Y(), pnt1.Z()); - s1.param = param; - splits.push_back(s1); - } - if (DrawProjectSplit::isOnEdge((*itInner), v2, param, false)) { - gp_Pnt pnt2 = BRep_Tool::Pnt(v2); - splitPoint s2; - s2.i = iInner; - s2.v = Base::Vector3d(pnt2.X(), pnt2.Y(), pnt2.Z()); - s2.param = param; - splits.push_back(s2); - } - } //inner loop - } //outer loop - - //if edge A was touched at the same point by multiple edges B, we only want to split A once - std::vector sorted = DrawProjectSplit::sortSplits(splits, true); - auto last = std::unique(sorted.begin(), sorted.end(), DrawProjectSplit::splitEqual); //duplicates to back - sorted.erase(last, sorted.end()); //remove duplicate splits - - std::vector newEdges = DrawProjectSplit::splitEdges(nonZero, sorted); - - if (newEdges.empty()) { - Base::Console().Log("DVP::extractFaces - no edges return by splitting process\n"); - waitingForFaces(false); - return; - } - - //try to remove any duplicated edges since they will confuse the edgeWalker - newEdges = DrawProjectSplit::removeDuplicateEdges(newEdges); - -//find all the wires in the pile of faceEdges - EdgeWalker ew; - ew.loadEdges(newEdges); - bool success = ew.perform(); - if (!success) { - Base::Console().Warning("DVP::extractFaces - %s - Can't make faces from projected edges\n", getNameInDocument()); - waitingForFaces(false); - return; - } - std::vector fw = ew.getResultNoDups(); - - std::vector sortedWires = ew.sortStrip(fw, true); - - std::vector::iterator itWire = sortedWires.begin(); - for (; itWire != sortedWires.end(); itWire++) { - //version 1: 1 wire/face - no voids in face - TechDraw::FacePtr f(std::make_shared()); - const TopoDS_Wire& wire = (*itWire); - TechDraw::Wire* w = new TechDraw::Wire(wire); - f->wires.push_back(w); - if (geometryObject) { - //it can happen that a new hlr cycle deletes geometryObject while we are - //extracting faces. if it does happen, a new cycle should fix it. - geometryObject->addFaceGeom(f); } } } @@ -609,7 +662,7 @@ void DrawViewPart::extractFaces() //continue processing after extractFaces thread completes void DrawViewPart::onFacesFinished(void) { -// Base::Console().Message("DVP::onFacesFinished()\n"); +// Base::Console().Message("DVP::onFacesFinished() - %s\n", getNameInDocument()); waitingForFaces(false); QObject::disconnect(connectFaceWatcher); showProgressMessage(getNameInDocument(), "has finished extracting faces"); @@ -980,6 +1033,14 @@ bool DrawViewPart::handleFaces() return hGrp->GetBool("HandleFaces", 1l); } +bool DrawViewPart::newFaceFinder(void) +{ + Base::Reference hGrp = App::GetApplication().GetUserParameter() + .GetGroup("BaseApp")->GetGroup("Preferences")->GetGroup("Mod/TechDraw/General"); + bool result = hGrp->GetBool("NewFaceFinder", 0l); + return result; +} + //! remove features that are useless without this DVP //! hatches, geomhatches, dimensions, ... void DrawViewPart::unsetupObject() diff --git a/src/Mod/TechDraw/App/DrawViewPart.h b/src/Mod/TechDraw/App/DrawViewPart.h index 796c4a7034..70e2db9865 100644 --- a/src/Mod/TechDraw/App/DrawViewPart.h +++ b/src/Mod/TechDraw/App/DrawViewPart.h @@ -159,6 +159,7 @@ public: bool handleFaces(); + bool newFaceFinder(); bool isUnsetting() { return nowUnsetting; } diff --git a/src/Mod/TechDraw/App/EWTOLERANCE.h b/src/Mod/TechDraw/App/EWTOLERANCE.h new file mode 100644 index 0000000000..24b37434aa --- /dev/null +++ b/src/Mod/TechDraw/App/EWTOLERANCE.h @@ -0,0 +1,30 @@ +/*************************************************************************** + * Copyright (c) 2022 Wanderer Fan * + * * + * 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 * + * * + ***************************************************************************/ + +//some shapes are being passed in where edges that should be connected are in fact +//separated by more than 2*Precision::Confusion (expected tolerance for 2 TopoDS_Vertex) +//this value is used in EdgeWalker, DrawProjectSplit and DrawUtil and needs to be in sync in +//all 3 files. +#define EWTOLERANCE 0.0001 //arbitrary number that seems to give good results for drawing + +//a multiplier for EWTOLERANCE used in fuzzy fuse and common operations. +#define FUZZYADJUST 4.0 diff --git a/src/Mod/TechDraw/App/EdgeWalker.cpp b/src/Mod/TechDraw/App/EdgeWalker.cpp index 5aa27ecbae..067c655487 100644 --- a/src/Mod/TechDraw/App/EdgeWalker.cpp +++ b/src/Mod/TechDraw/App/EdgeWalker.cpp @@ -27,8 +27,6 @@ #include "PreCompiled.h" #ifndef _PreComp_ -#include -#include #include #include #include @@ -38,19 +36,19 @@ #include #include #include -#include -#include -#include -#include - #endif + #include #include +#include +#include + #include #include #include "DrawUtil.h" +#include "EWTOLERANCE.h" #include "EdgeWalker.h" using namespace TechDraw; @@ -69,19 +67,18 @@ void edgeVisitor::next_edge(Edge e) we.v2 = t; we.ed = e; we.idx = get(edge_index, m_g, e); - //Base::Console().Message("TRACE - EV::next_Edge - visiting (%d, %d) idx: %d\n", s,t, we.idx); wireEdges.push_back(we); } void edgeVisitor::begin_face() { - //Base::Console().Message("TRACE - EV::begin_face()\n"); +// Base::Console().Message("EV::begin_face()\n"); wireEdges.clear(); } void edgeVisitor::end_face() { - //Base::Console().Message("TRACE - EV::end_face()\n"); +// Base::Console().Message("EV::end_face()\n"); graphWires.push_back(wireEdges); } @@ -99,11 +96,6 @@ void edgeVisitor::setGraph(TechDraw::graph& g) //* EdgeWalker methods //******************************************************* -//some shapes are being passed in where edges that should be connected are in fact -//separated by more than 2*Precision::Confusion (expected tolerance for 2 TopoDS_Vertex) -#define EWTOLERANCE 0.00001 //arbitrary number that seems to give good results for drawing - - EdgeWalker::EdgeWalker() { } @@ -115,7 +107,7 @@ EdgeWalker::~EdgeWalker() //loads a list of unique edges into the traversal mechanism bool EdgeWalker::loadEdges(std::vector& edges) { - //Base::Console().Message("TRACE -EW::loadEdges(we)\n"); +// Base::Console().Message("EW::loadEdges(we) - WEdgesIn: %d\n", edges.size()); int idx = 0; for (auto& e: edges) { std::pair p; @@ -131,7 +123,7 @@ bool EdgeWalker::loadEdges(std::vector& edges) bool EdgeWalker::loadEdges(std::vector edges) { - //Base::Console().Message("TRACE -EW::loadEdges(TopoDS)\n"); +// Base::Console().Message("EW::loadEdges(TopoDS) - edges: %d\n", edges.size()); if (edges.empty()) { throw Base::ValueError("EdgeWalker has no edges to load\n"); } @@ -146,19 +138,19 @@ bool EdgeWalker::loadEdges(std::vector edges) return true; } -bool EdgeWalker::setSize(int size) +bool EdgeWalker::setSize(std::size_t size) { m_g.clear(); - for (int i = 0; i < size; i++) { + for (std::size_t i = 0; i < size; i++) { boost::adjacency_list<>::vertex_descriptor vd = boost::add_vertex(m_g); (void)vd; } return true; } -bool EdgeWalker::perform() +bool EdgeWalker::prepare() { - //Base::Console().Message("TRACE - EW::perform()\n"); + //Base::Console().Message("TRACE - EW::prepare()\n"); // Initialize the interior edge index property_map::type e_index = get(edge_index, m_g); graph_traits::edges_size_type edge_count = 0; @@ -198,16 +190,16 @@ bool EdgeWalker::perform() std::back_inserter(kEdges)); if (!isPlanar) { //TODO: remove kura subgraph to make planar?? - Base::Console().Log("LOG - EW::perform - input is NOT planar\n"); + Base::Console().Message("EW::prepare - input is NOT planar\n"); ki_end = kEdges.end(); std::stringstream ss; - ss << "EW::perform - obstructing edges: "; + ss << "EW::prepare - obstructing edges: "; for(ki = kEdges.begin(); ki != ki_end; ++ki) { e1 = *ki; ss << boost::get(edge_index, m_g, e1) << ", "; } ss << std::endl; - Base::Console().Log("LOG - %s\n", ss.str().c_str()); + Base::Console().Message("%s\n", ss.str().c_str()); return false; } @@ -217,6 +209,18 @@ bool EdgeWalker::perform() return true; } +std::vector EdgeWalker::execute(std::vector edgeList, bool biggie) +{ + std::vector sortedWires; + loadEdges(edgeList); + bool success = prepare(); + if (success) { + std::vector rw = getResultNoDups(); + sortedWires = sortStrip(rw, biggie); + } + return sortedWires; +} + ewWireList EdgeWalker::getResult() { //Base::Console().Message("TRACE - EW::getResult()\n"); @@ -242,7 +246,7 @@ std::vector EdgeWalker::getResultWires() TopoDS_Edge e = m_saveInEdges.at((*iEdge).idx); topoEdges.push_back(e); } - TopoDS_Wire w = makeCleanWire(topoEdges); //make 1 clean wire from its edges + TopoDS_Wire w = makeCleanWire(topoEdges, EWTOLERANCE); //make 1 clean wire from its edges fw.push_back(w); } return fw; @@ -267,8 +271,8 @@ std::vector EdgeWalker::getResultNoDups() TopoDS_Edge e = m_saveInEdges.at((*iEdge).idx); topoEdges.push_back(e); } - TopoDS_Wire w = makeCleanWire(topoEdges); //make 1 clean wire from its edges - fw.push_back(w); + TopoDS_Wire w = makeCleanWire(topoEdges, EWTOLERANCE); //make 1 clean wire from its edges + fw.push_back(w); } return fw; } @@ -289,43 +293,50 @@ TopoDS_Wire EdgeWalker::makeCleanWire(std::vector edges, double tol Handle(ShapeFix_Wire) fixer = new ShapeFix_Wire; fixer->Load(wireData); - fixer->Perform(); - fixer->FixReorder(); + fixer->SetPrecision(2.0 * EWTOLERANCE); fixer->SetMaxTolerance(tol); fixer->ClosedWireMode() = Standard_True; - fixer->FixConnected(Precision::Confusion()); - fixer->FixClosed(Precision::Confusion()); + fixer->ModifyGeometryMode() = Standard_True; + fixer->ModifyTopologyMode() = Standard_False; + fixer->FixSelfIntersectingEdgeMode() = Standard_True; + fixer->FixIntersectingEdgesMode() = Standard_True; + fixer->FixIntersectingEdgesMode() = Standard_True; + fixer->FixConnectedMode() = Standard_True; + fixer->FixReorderMode() = Standard_True; + fixer->Perform(); - for (int i = 1; i <= wireData->NbEdges(); i ++) { - TopoDS_Edge edge = fixer->WireData()->Edge(i); - sTol.SetTolerance(edge, tol, TopAbs_VERTEX); - mkWire.Add(edge); - } + result = fixer->WireAPIMake(); - result = mkWire.Wire(); return result; } std::vector EdgeWalker:: makeUniqueVList(std::vector edges) { - //Base::Console().Message("TRACE - EW::makeUniqueVList()\n"); +// Base::Console().Message("TRACE - EW::makeUniqueVList() - edgesIn: %d\n", edges.size()); std::vector uniqueVert; for(auto& e:edges) { - TopoDS_Vertex v1 = TopExp::FirstVertex(e); - TopoDS_Vertex v2 = TopExp::LastVertex(e); + Base::Vector3d v1 = DrawUtil::vertex2Vector(TopExp::FirstVertex(e)); + Base::Vector3d v2 = DrawUtil::vertex2Vector(TopExp::LastVertex(e)); bool addv1 = true; bool addv2 = true; - for (const auto& v:uniqueVert) { - if (DrawUtil::isSamePoint(v, v1, EWTOLERANCE)) + //check if we've already added this vertex + for (const auto& v: uniqueVert) { + Base::Vector3d v3d = DrawUtil::vertex2Vector(v); + if (v3d.IsEqual(v1, EWTOLERANCE)) { addv1 = false; - if (DrawUtil::isSamePoint(v, v2, EWTOLERANCE)) + } + if (v3d.IsEqual(v2, EWTOLERANCE)) { addv2 = false; + } + } + if (addv1) { + uniqueVert.push_back(TopExp::FirstVertex(e)); + } + if (addv2) { + uniqueVert.push_back(TopExp::LastVertex(e)); } - if (addv1) - uniqueVert.push_back(v1); - if (addv2) - uniqueVert.push_back(v2); } +// Base::Console().Message("EW::makeUniqueVList - verts out: %d\n", uniqueVert.size()); return uniqueVert; } @@ -333,17 +344,24 @@ std::vector EdgeWalker:: makeUniqueVList(std::vector std::vector EdgeWalker::makeWalkerEdges(std::vector edges, std::vector verts) { -// Base::Console().Message("TRACE - EW::makeWalkerEdges()\n"); +// Base::Console().Message("TRACE - EW::makeWalkerEdges() - edges: %d verts: %d\n", edges.size(), verts.size()); m_saveInEdges = edges; std::vector walkerEdges; for (const auto& e:edges) { - TopoDS_Vertex ev1 = TopExp::FirstVertex(e); - TopoDS_Vertex ev2 = TopExp::LastVertex(e); - int v1dx = findUniqueVert(ev1, verts); - int v2dx = findUniqueVert(ev2, verts); + TopoDS_Vertex edgeVertex1 = TopExp::FirstVertex(e); + TopoDS_Vertex edgeVertex2 = TopExp::LastVertex(e); + std::size_t vertex1Index = findUniqueVert(edgeVertex1, verts); + if (vertex1Index == SIZE_MAX) { + continue; + } + std::size_t vertex2Index = findUniqueVert(edgeVertex2, verts); + if (vertex2Index == SIZE_MAX) { + continue; + } + WalkerEdge we; - we.v1 = v1dx; - we.v2 = v2dx; + we.v1 = vertex1Index; + we.v2 = vertex2Index; we.idx = 0; walkerEdges.push_back(we); } @@ -352,18 +370,20 @@ std::vector EdgeWalker::makeWalkerEdges(std::vector edg return walkerEdges; } -int EdgeWalker::findUniqueVert(TopoDS_Vertex vx, std::vector &uniqueVert) +size_t EdgeWalker::findUniqueVert(TopoDS_Vertex vx, std::vector &uniqueVert) { // Base::Console().Message("TRACE - EW::findUniqueVert()\n"); - int idx = 0; - int result = 0; - for(auto& v:uniqueVert) { //we're always going to find vx, right? - if (DrawUtil::isSamePoint(v, vx, EWTOLERANCE)) { + std::size_t idx = 0; + std::size_t result = SIZE_MAX; + Base::Vector3d vx3d = DrawUtil::vertex2Vector(vx); + for(auto& v : uniqueVert) { + Base::Vector3d v3d = DrawUtil::vertex2Vector(v); + if (vx3d.IsEqual(v3d, EWTOLERANCE)) { result = idx; break; } idx++; - } //if idx >= uniqueVert.size() TARFU + } return result; } @@ -377,8 +397,8 @@ std::vector EdgeWalker::sortStrip(std::vector fw, bool } std::vector sortedWires = sortWiresBySize(closedWires, false); //biggest 1st if (sortedWires.empty()) { - Base::Console().Log("INFO - EW::sortStrip - no sorted Wires!\n"); - return sortedWires; // might happen in the middle of changes? + Base::Console().Message("EW::sortStrip - no sorted Wires!\n"); + return sortedWires; } if (!includeBiggest) { @@ -415,30 +435,34 @@ std::vector EdgeWalker::makeEmbedding(const std::vector // edges.size(), uniqueVList.size()); std::vector result; - int iv = 0; + std::size_t iVert = 0; + //make an embedItem for each vertex in uniqueVList + //for each vertex v + // find all the edges that have v as first or last vertex for (auto& v: uniqueVList) { - int ie = 0; + TopoDS_Vertex cv = v; //v is const but we need non-const for vertexEqual + std::size_t iEdge = 0; std::vector iiList; for (auto& e: edges) { double angle = 0; - if (DrawUtil::isFirstVert(e, v,EWTOLERANCE)) { - angle = DrawUtil::angleWithX(e, v,EWTOLERANCE); - incidenceItem ii(ie, angle, m_saveWalkerEdges[ie].ed); + TopoDS_Vertex edgeVertex1 = TopExp::FirstVertex(e); + TopoDS_Vertex edgeVertex2 = TopExp::LastVertex(e); + if (DrawUtil::vertexEqual(cv, edgeVertex1)) { + angle = DrawUtil::incidenceAngleAtVertex(e,v,EWTOLERANCE); + incidenceItem ii(iEdge, angle, m_saveWalkerEdges[iEdge].ed); iiList.push_back(ii); - } else if (DrawUtil::isLastVert(e, v,EWTOLERANCE)) { - angle = DrawUtil::angleWithX(e, v,EWTOLERANCE); - incidenceItem ii(ie, angle, m_saveWalkerEdges[ie].ed); + } else if (DrawUtil::vertexEqual(cv, edgeVertex2)) { + angle = DrawUtil::incidenceAngleAtVertex(e,v,EWTOLERANCE); + incidenceItem ii(iEdge, angle, m_saveWalkerEdges[iEdge].ed); iiList.push_back(ii); - } else { - //Base::Console().Message("TRACE - EW::makeEmbedding - neither first nor last\n"); } - ie++; + iEdge++; } //sort incidenceList by angle iiList = embedItem::sortIncidenceList(iiList, false); - embedItem embed(iv, iiList); + embedItem embed(iVert, iiList); result.push_back(embed); - iv++; + iVert++; } return result; } @@ -523,7 +547,7 @@ void ewWire::push_back(WalkerEdge w) wedges.push_back(w); } -int ewWire::size() +std::size_t ewWire::size(void) { return wedges.size(); } @@ -560,7 +584,7 @@ void ewWireList::push_back(ewWire w) wires.push_back(w); } -int ewWireList::size() +std::size_t ewWireList::size(void) { return wires.size(); } diff --git a/src/Mod/TechDraw/App/EdgeWalker.h b/src/Mod/TechDraw/App/EdgeWalker.h index e62668eafb..ae29ec91fc 100644 --- a/src/Mod/TechDraw/App/EdgeWalker.h +++ b/src/Mod/TechDraw/App/EdgeWalker.h @@ -31,6 +31,8 @@ #include #include +#include + #include #include #include @@ -49,7 +51,7 @@ using graph = boost::adjacency_list < boost::vecS, boost::vecS, - boost::undirectedS, + boost::bidirectionalS, boost::property, boost::property >; @@ -78,7 +80,7 @@ public: std::size_t v1; std::size_t v2; edge_t ed; - int idx; + std::size_t idx; }; class TechDrawExport ewWire @@ -89,7 +91,7 @@ public: std::vector wedges; //[WE] representing 1 wire void push_back(WalkerEdge w); void clear() {wedges.clear();} - int size(); + std::size_t size(void); }; class TechDrawExport ewWireList @@ -99,7 +101,7 @@ public: std::vector wires; void push_back(ewWire e); - int size(); + std::size_t size(void); }; @@ -124,11 +126,11 @@ class TechDrawExport incidenceItem { public: incidenceItem() {iEdge = 0; angle = 0.0;} - incidenceItem(int idx, double a, edge_t ed) {iEdge = idx; angle = a; eDesc = ed;} + incidenceItem(std::size_t idx, double a, edge_t ed) {iEdge = idx; angle = a; eDesc = ed;} ~incidenceItem() = default; static bool iiCompare(const incidenceItem& i1, const incidenceItem& i2); static bool iiEqual(const incidenceItem& i1, const incidenceItem& i2); - int iEdge; + std::size_t iEdge; double angle; edge_t eDesc; }; @@ -137,11 +139,11 @@ class TechDrawExport embedItem { public: embedItem(); - embedItem(int i, + embedItem(std::size_t i, std::vector list) { iVertex = i; incidenceList = list;} ~embedItem() = default; - int iVertex; + std::size_t iVertex; std::vector incidenceList; std::string dump(); static std::vector sortIncidenceList (std::vector &list, bool ascend); @@ -156,8 +158,9 @@ public: bool loadEdges(std::vector& edges); bool loadEdges(std::vector edges); - bool setSize(int size); - bool perform(); + bool setSize(std::size_t size); + std::vector execute(std::vector edgeList, bool biggie = true); + ewWireList getResult(); std::vector getResultWires(); std::vector getResultNoDups(); @@ -166,7 +169,7 @@ public: std::vector makeWalkerEdges(std::vector edges, std::vector verts); - int findUniqueVert(TopoDS_Vertex vx, std::vector &uniqueVert); + size_t findUniqueVert(TopoDS_Vertex vx, std::vector &uniqueVert); std::vector sortStrip(std::vector fw, bool includeBiggest); std::vector sortWiresBySize(std::vector& w, bool reverse = false); static TopoDS_Wire makeCleanWire(std::vector edges, double tol = 0.10); @@ -177,6 +180,7 @@ public: const std::vector uniqueVList); protected: + bool prepare(); static bool wireCompare(const TopoDS_Wire& w1, const TopoDS_Wire& w2); std::vector m_saveWalkerEdges; std::vector m_saveInEdges;