1
/****************************************************************************
2
* Copyright (c) 2022 Zheng Lei (realthunder) <realthunder.dev@gmail.com> *
4
* This file is part of the FreeCAD CAx development system. *
6
* This library is free software; you can redistribute it and/or *
7
* modify it under the terms of the GNU Library General Public *
8
* License as published by the Free Software Foundation; either *
9
* version 2 of the License, or (at your option) any later version. *
11
* This library is distributed in the hope that it will be useful, *
12
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
13
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
14
* GNU Library General Public License for more details. *
16
* You should have received a copy of the GNU Library General Public *
17
* License along with this library; see the file COPYING.LIB. If not, *
18
* write to the Free Software Foundation, Inc., 59 Temple Place, *
19
* Suite 330, Boston, MA 02111-1307, USA *
21
****************************************************************************/
23
#include "PreCompiled.h"
24
#include <boost/core/ignore_unused.hpp>
25
#include <boost/geometry/geometries/register/point.hpp>
26
#include <boost/graph/graph_concepts.hpp>
29
# include <BRepLib.hxx>
30
# include <BRep_Builder.hxx>
31
# include <BRep_Tool.hxx>
32
# include <BRepBndLib.hxx>
33
# include <BRepBuilderAPI_Copy.hxx>
34
# include <BRepBuilderAPI_MakeEdge.hxx>
35
# include <BRepBuilderAPI_MakeWire.hxx>
36
# include <BRepBuilderAPI_MakeFace.hxx>
37
# include <BRepClass_FaceClassifier.hxx>
38
# include <BRepExtrema_DistShapeShape.hxx>
39
# include <BRepGProp.hxx>
40
# include <BRepTools.hxx>
41
# include <BRepTools_WireExplorer.hxx>
43
# include <GeomAdaptor_Curve.hxx>
44
# include <GeomLProp_CLProps.hxx>
45
# include <GProp_GProps.hxx>
46
# include <ShapeAnalysis_Wire.hxx>
47
# include <ShapeFix_ShapeTolerance.hxx>
48
# include <ShapeExtend_WireData.hxx>
49
# include <ShapeFix_Wire.hxx>
50
# include <ShapeFix_Shape.hxx>
52
# include <TopExp_Explorer.hxx>
53
# include <TopTools_HSequenceOfShape.hxx>
56
#include <BRepTools_History.hxx>
57
#include <ShapeBuild_ReShape.hxx>
59
#include <unordered_map>
60
#include <unordered_set>
62
#include <boost_geometry.hpp>
65
#include <Base/Console.h>
66
#include <Base/Exception.h>
67
#include <Base/Tools.h>
68
#include <Base/Sequencer.h>
69
#include <Base/Parameter.h>
70
#include <App/Application.h>
72
#include "WireJoiner.h"
75
#include "PartFeature.h"
76
#include "TopoShapeOpCode.h"
77
#include "TopoShapeMapper.h"
79
namespace bg = boost::geometry;
80
namespace bgi = boost::geometry::index;
82
const size_t RParametersNumber = 16UL;
83
using RParameters = bgi::linear<RParametersNumber>;
85
BOOST_GEOMETRY_REGISTER_POINT_3D_GET_SET(
86
gp_Pnt,double,bg::cs::cartesian,X,Y,Z,SetX,SetY,SetZ)
88
FC_LOG_LEVEL_INIT("WireJoiner",true, true)
92
static inline void getEndPoints(const TopoDS_Edge &eForEndPoints, gp_Pnt &p1, gp_Pnt &p2) {
93
p1 = BRep_Tool::Pnt(TopExp::FirstVertex(eForEndPoints));
94
p2 = BRep_Tool::Pnt(TopExp::LastVertex(eForEndPoints));
97
static inline void getEndPoints(const TopoDS_Wire &wire, gp_Pnt &p1, gp_Pnt &p2) {
98
BRepTools_WireExplorer xp(wire);
99
p1 = BRep_Tool::Pnt(TopoDS::Vertex(xp.CurrentVertex()));
100
for(;xp.More();xp.Next()) {};
101
p2 = BRep_Tool::Pnt(TopoDS::Vertex(xp.CurrentVertex()));
104
// Originally here there was the definition of the precompiler macro assertCheck() and of the method
105
// _assertCheck(), that have been replaced with the already defined precompiler macro assert().
107
// https://github.com/realthunder/FreeCAD/blob/6f15849be2505f98927e75d0e8352185e14e7b72/src/Mod/Part/App/WireJoiner.cpp#L107
108
// for reference and https://github.com/FreeCAD/FreeCAD/pull/12535/files#r1526647457 for the
109
// discussion about replacing it
111
class WireJoiner::WireJoinerP {
113
double myTol = Precision::Confusion();
114
double myTol2 = myTol * myTol;
115
double myAngularTol = Precision::Angular();
116
bool doSplitEdge = true;
117
bool doMergeEdge = true;
118
bool doOutline = false;
119
bool doTightBound = true;
121
std::string catchObject;
122
int catchIteration {};
125
using Box = bg::model::box<gp_Pnt>;
127
bool checkBBox(const Bnd_Box &box) const
132
Standard_Real xMin = Standard_Real();
133
Standard_Real yMin = Standard_Real();
134
Standard_Real zMin = Standard_Real();
135
Standard_Real xMax = Standard_Real();
136
Standard_Real yMax = Standard_Real();
137
Standard_Real zMax = Standard_Real();
138
box.Get(xMin, yMin, zMin, xMax, yMax, zMax);
139
return zMax - zMin <= myTol;
143
: catchObject(App::GetApplication()
144
.GetParameterGroupByPath("User parameter:BaseApp/Preferences/WireJoiner")
145
->GetASCII("ObjectName"))
146
, catchIteration(static_cast<int>(
147
App::GetApplication()
148
.GetParameterGroupByPath("User parameter:BaseApp/Preferences/WireJoiner")
149
->GetInt("Iteration", 0)))
152
bool getBBox(const TopoDS_Shape &eForBBox, Bnd_Box &bound) {
153
BRepBndLib::AddOptimal(eForBBox,bound,Standard_False);
154
if (bound.IsVoid()) {
155
if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) {
156
FC_WARN("failed to get bound of edge");
160
if (!checkBBox(bound)) {
161
showShape(eForBBox, "invalid");
163
if (bound.SquareExtent() < myTol2) {
166
bound.Enlarge(myTol);
170
bool getBBox(const TopoDS_Shape &eForBBox, Box &box) {
172
if (!getBBox(eForBBox, bound)) {
175
Standard_Real xMin = Standard_Real();
176
Standard_Real yMin = Standard_Real();
177
Standard_Real zMin = Standard_Real();
178
Standard_Real xMax = Standard_Real();
179
Standard_Real yMax = Standard_Real();
180
Standard_Real zMax = Standard_Real();
181
bound.Get(xMin, yMin, zMin, xMax, yMax, zMax);
182
box = Box(gp_Pnt(xMin,yMin,zMin), gp_Pnt(xMax,yMax,zMax));
191
TopoDS_Wire superEdge;
192
mutable TopoDS_Shape edgeReversed;
193
mutable TopoDS_Shape superEdgeReversed;
198
std::array<int, 2> iStart {}; // adjacent list index start for p1 and p2
199
std::array<int, 2> iEnd {}; // adjacent list index end
203
std::shared_ptr<WireInfo> wireInfo {};
204
std::shared_ptr<WireInfo> wireInfo2 {}; // an edge can be shared by at most two tight bound wires.
205
std::unique_ptr<Geometry> geo {};
206
Standard_Real firstParam {};
207
Standard_Real lastParam {};
208
Handle_Geom_Curve curve;
209
GeomAbs_CurveType type {};
212
EdgeInfo(const TopoDS_Edge& eForInfo,
225
curve = BRep_Tool::Curve(eForInfo, firstParam, lastParam);
226
type = GeomAdaptor_Curve(curve).GetType();
228
// Originally here there was a call to the precompiler macro assertCheck(), which has
229
// been replaced with the precompiler macro assert()
231
assert(!curve.IsNull());
232
const double halving {0.5};
233
GeomLProp_CLProps prop(curve,(firstParam+lastParam)*halving,0,Precision::Confusion());
238
Geometry *geometry() {
240
geo = Geometry::fromShape(edge, /*silent*/ true);
247
if (iteration >= 0) {
251
iStart[0] = iStart[1] = iEnd[0] = iEnd[1] = -1;
253
const TopoDS_Shape &shape(bool forward=true) const
255
if (superEdge.IsNull()) {
259
if (edgeReversed.IsNull()) {
260
edgeReversed = edge.Reversed();
267
if (superEdgeReversed.IsNull()) {
268
superEdgeReversed = superEdge.Reversed();
270
return superEdgeReversed;
272
TopoDS_Wire wire() const
274
auto sForWire = shape();
275
if (sForWire.ShapeType() == TopAbs_WIRE) {
276
return TopoDS::Wire(sForWire);
278
return BRepBuilderAPI_MakeWire(TopoDS::Edge(sForWire)).Wire();
288
std::sort(data.begin(), data.end());
291
bool contains(const T &vForContains)
294
const size_t dataSizeMax = 30;
295
if (data.size() < dataSizeMax) {
296
return std::find(data.begin(), data.end(), vForContains) != data.end();
300
auto it = std::lower_bound(data.begin(), data.end(), vForContains);
301
return it!=data.end() && *it == vForContains;
303
bool intersects(const VectorSet<T> &other)
305
if (other.size() < size()) {
306
return other.intersects(*this);
309
for (const auto &vector : data) {
310
if (other.contains(vector)) {
317
auto it = other.data.begin();
318
for (const auto &vertex : data) {
319
it = std::lower_bound(it, other.data.end(), vertex);
320
if (it == other.data.end()) {
330
void insert(const T &vToInsert)
333
data.insert(std::upper_bound(data.begin(), data.end(), vToInsert), vToInsert);
336
data.push_back(vToInsert);
339
bool insertUnique(const T &vToInsertUnique)
342
auto it = std::lower_bound(data.begin(), data.end(), vToInsertUnique);
343
bool insert = !(it != data.end() && *it == vToInsertUnique);
345
data.insert(it, vToInsertUnique);
350
if (contains(vToInsertUnique)) {
353
data.push_back(vToInsertUnique);
356
void erase(const T &vToErase)
359
data.erase(std::remove(data.begin(), data.end(), vToErase), data.end());
362
auto it = std::lower_bound(data.begin(), data.end(), vToErase);
364
while (itEnd != data.end() && *itEnd == vToErase) {
367
data.erase(it, itEnd);
369
const size_t dataSizeMax = 20;
370
if (data.size() < dataSizeMax) {
379
std::size_t size() const
389
std::vector<T> data {};
392
Handle(BRepTools_History) aHistory = new BRepTools_History;
394
using Edges = std::list<EdgeInfo>;
397
std::map<EdgeInfo*, Edges::iterator> edgesTable {};
400
Edges::iterator it {};
402
VertexInfo() = default;
403
VertexInfo(Edges::iterator it, bool start)
406
VertexInfo reversed() const {
409
bool operator==(const VertexInfo &other) const {
410
return it==other.it && start==other.start;
412
bool operator<(const VertexInfo &other) const {
413
auto thisInfo = edgeInfo();
414
auto otherInfo = other.edgeInfo();
415
if (thisInfo < otherInfo) {
418
if (thisInfo > otherInfo) {
421
return static_cast<int>(start) < static_cast<int>(other.start);
423
const gp_Pnt &pt() const {
424
return start?it->p1:it->p2;
427
return start?it->p1:it->p2;
429
const gp_Pnt &ptOther() const {
430
return start?it->p2:it->p1;
433
return start?it->p2:it->p1;
435
TopoDS_Vertex vertex() const {
436
return start ? TopExp::FirstVertex(edge()) : TopExp::LastVertex(edge());
438
TopoDS_Vertex otherVertex() const {
439
return start ? TopExp::LastVertex(edge()) : TopExp::FirstVertex(edge());
441
EdgeInfo *edgeInfo() const {
444
const TopoDS_Edge &edge() const {
453
explicit StackInfo(size_t idx = 0)
460
std::vector<StackInfo> stack {};
461
std::vector<VertexInfo> vertexStack {};
462
std::vector<VertexInfo> tmpVertices {};
463
std::vector<VertexInfo> adjacentList {};
466
std::vector<VertexInfo> vertices {};
467
mutable std::vector<int> sorted {};
476
if (sorted.size() == vertices.size()) {
480
// Originally here there was a call to the precompiler macro assertCheck(), which has
481
// been replaced with the precompiler macro assert()
483
assert(sorted.size() < vertices.size());
484
sorted.reserve(vertices.size());
485
for (int i = (int)sorted.size(); i < (int)vertices.size(); ++i) {
488
std::sort(sorted.begin(), sorted.end(), [&](int vA, int vB) {
489
return vertices[vA] < vertices[vB];
492
int find(const VertexInfo &info) const
494
const size_t verticesSizeMax = 20;
495
if (vertices.size() < verticesSizeMax) {
496
auto it = std::find(vertices.begin(), vertices.end(), info);
497
if (it == vertices.end()) {
500
return (static_cast<int>(it - vertices.begin()) + 1);
503
auto it = std::lower_bound(sorted.begin(), sorted.end(), info,
504
[&](int idx, const VertexInfo &vertex) {return vertices[idx]<vertex;});
506
if (it != sorted.end() && vertices[*it] == info) {
511
int find(const EdgeInfo *info) const
513
const size_t verticesSizeMax = 20;
514
if (vertices.size() < verticesSizeMax) {
515
for (auto it=vertices.begin(); it!=vertices.end(); ++it) {
516
if (it->edgeInfo() == info) {
517
return (static_cast<int>(it - vertices.begin()) + 1);
523
auto it = std::lower_bound(sorted.begin(), sorted.end(), info,
524
[&](int idx, const EdgeInfo *vertex) {return vertices[idx].edgeInfo()<vertex;});
526
if (it != sorted.end() && vertices[*it].edgeInfo() == info) {
531
bool isSame(const WireInfo &other) const
533
if (this == &other) {
536
if (vertices.size() != other.vertices.size()) {
539
if (vertices.empty()) {
542
int idx=find(other.vertices.front().edgeInfo()) - 1;
546
for (auto &vertex : other.vertices) {
547
if (vertex.edgeInfo() != vertices[idx].edgeInfo()) {
550
if (++idx == (int)vertices.size()) {
558
struct EdgeSet: VectorSet<EdgeInfo*> {
562
struct WireSet: VectorSet<WireInfo*> {
566
const Bnd_Box &getWireBound(const WireInfo &wireInfo) const
568
if (wireInfo.box.IsVoid()) {
569
for (auto& vertex : wireInfo.vertices) {
570
BRepBndLib::Add(vertex.it->shape(), wireInfo.box);
572
wireInfo.box.Enlarge(myTol);
577
// This method was originally part of WireJoinerP::initWireInfo(), split to reduce cognitive
579
bool initWireInfoWireClosed(const WireInfo& wireInfo)
581
if (!BRep_Tool::IsClosed(wireInfo.wire)) {
582
showShape(wireInfo.wire, "FailedToClose");
583
FC_ERR("Wire not closed");
584
for (auto& vertex : wireInfo.vertices) {
585
showShape(vertex.edgeInfo(), vertex.start ? "failed" : "failed_r", iteration);
592
// This method was originally part of WireJoinerP::initWireInfo(), split to reduce cognitive
594
bool initWireInfoFaceDone(WireInfo& wireInfo)
596
BRepBuilderAPI_MakeFace mkFace(wireInfo.wire);
597
if (!mkFace.IsDone()) {
598
FC_ERR("Failed to create face for wire");
599
showShape(wireInfo.wire, "FailedFace");
602
wireInfo.face = mkFace.Face();
606
bool initWireInfo(WireInfo &wireInfo)
608
if (!wireInfo.face.IsNull()) {
611
getWireBound(wireInfo);
612
if (wireInfo.wire.IsNull()) {
614
for (auto& vertex : wireInfo.vertices) {
615
wireData->Add(vertex.it->shape(vertex.start));
617
wireInfo.wire = makeCleanWire();
620
if (!initWireInfoWireClosed(wireInfo)) {
624
if (!initWireInfoFaceDone(wireInfo)) {
631
bool isInside(const WireInfo &wireInfo, gp_Pnt &pt) const
633
if (getWireBound(wireInfo).IsOut(pt)) {
636
BRepClass_FaceClassifier fc(wireInfo.face, pt, myTol);
637
return fc.State() == TopAbs_IN;
640
bool isOutside(const WireInfo &wireInfo, gp_Pnt &pt) const
642
if (getWireBound(wireInfo).IsOut(pt)) {
645
BRepClass_FaceClassifier fc(wireInfo.face, pt, myTol);
646
return fc.State() == TopAbs_OUT;
651
using result_type = const gp_Pnt&;
652
result_type operator()(const VertexInfo &vInfo) const {
657
bgi::rtree<VertexInfo, RParameters, PntGetter> vmap {};
661
using result_type = const Box&;
662
result_type operator()(Edges::iterator it) const {
666
bgi::rtree<Edges::iterator, RParameters, BoxGetter> boxMap {};
668
BRep_Builder builder;
669
TopoDS_Compound compound;
671
std::unordered_set<TopoShape, ShapeHasher, ShapeHasher> sourceEdges {};
672
std::vector<TopoShape> sourceEdgeArray {};
673
TopoDS_Compound openWireCompound;
675
Handle(ShapeExtend_WireData) wireData = new ShapeExtend_WireData();
686
adjacentList.clear();
690
builder.MakeCompound(compound);
691
openWireCompound.Nullify();
694
Edges::iterator remove(Edges::iterator it)
699
vmap.remove(VertexInfo(it,true));
700
vmap.remove(VertexInfo(it,false));
701
return edges.erase(it);
704
void remove(EdgeInfo *info)
706
if (edgesTable.empty()) {
707
for (auto it = edges.begin(); it != edges.end(); ++it) {
708
edgesTable[&(*it)] = it;
711
auto it = edgesTable.find(info);
712
if (it != edgesTable.end()) {
714
edgesTable.erase(it);
718
void add(Edges::iterator it)
720
vmap.insert(VertexInfo(it,true));
721
vmap.insert(VertexInfo(it,false));
725
showShape(it->edge, "add");
728
int add(const TopoDS_Edge &eToAdd, bool queryBBox=false)
730
auto it = edges.begin();
731
return add(eToAdd, queryBBox, it);
734
int add(const TopoDS_Edge &eToAdd, bool queryBBox, Edges::iterator &it)
737
if (!getBBox(eToAdd, bbox)) {
738
showShape(eToAdd, "small");
739
aHistory->Remove(eToAdd);
742
return add(eToAdd, queryBBox, bbox, it) ? 1 : -1;
745
// This method was originally part of WireJoinerP::add(), split to reduce cognitive complexity
746
bool addNoDuplicates(const TopoDS_Edge& eToAdd,
750
const VertexInfo& vinfo,
751
std::unique_ptr<Geometry>& geo)
755
v2 = vinfo.otherVertex();
757
if (isLinear && vinfo.edgeInfo()->isLinear) {
758
showShape(eToAdd, "duplicate");
759
aHistory->Remove(eToAdd);
762
if (auto geoEdge = vinfo.edgeInfo()->geometry()) {
764
geo = Geometry::fromShape(eToAdd, /*silent*/ true);
766
if (geo && geo->isSame(*geoEdge, myTol, myAngularTol)) {
767
showShape(eToAdd, "duplicate");
768
aHistory->Remove(eToAdd);
775
// This method was originally part of WireJoinerP::add(), split to reduce cognitive complexity
776
bool addValidEdges(const TopoDS_Edge& eToAdd,
786
std::unique_ptr<Geometry> geo;
787
for (auto vit = vmap.qbegin(bgi::nearest(p1, INT_MAX)); vit != vmap.qend(); ++vit) {
789
if (canShowShape()) {
790
#if OCC_VERSION_HEX < 0x070800
791
FC_MSG("addcheck " << vinfo.edge().HashCode(INT_MAX));
793
FC_MSG("addcheck " << std::hash<TopoDS_Edge> {}(vinfo.edge()));
796
double d1 = vinfo.pt().SquareDistance(p1);
804
double d2 = vinfo.ptOther().SquareDistance(p2);
806
if (!addNoDuplicates(eToAdd, v2, ev2, isLinear, vinfo, geo)){
814
bool add(const TopoDS_Edge &eToAdd, bool queryBBox, const Box &bbox, Edges::iterator &it)
816
gp_Pnt p1 = gp_Pnt();
817
gp_Pnt p2 = gp_Pnt();
818
getEndPoints(eToAdd,p1,p2);
819
TopoDS_Vertex v1 = TopoDS_Vertex();
820
TopoDS_Vertex v2 = TopoDS_Vertex();
821
TopoDS_Edge ev1 = TopoDS_Edge();
822
TopoDS_Edge ev2 = TopoDS_Edge();
824
// search for duplicate edges
825
showShape(eToAdd, "addcheck");
826
bool isLinear = TopoShape(eToAdd).isLinearEdge();
828
if (!addValidEdges(eToAdd, p1, tol, v1, ev1, p2, v2, ev2, isLinear)){
833
for (auto vit=vmap.qbegin(bgi::nearest(p2,1));vit!=vmap.qend();++vit) {
835
double d1 = vinfo.pt().SquareDistance(p2);
843
// Make sure coincident vertices are actually the same TopoDS_Vertex,
844
// which is crucial for the OCC internal shape hierarchy structure. We
845
// achieve this by making a temp wire and let OCC do the hard work of
846
// replacing the vertex.
847
auto connectEdge = [&](TopoDS_Edge& eCurrent,
848
const TopoDS_Vertex& vCurrent,
849
const TopoDS_Edge& eOther,
850
const TopoDS_Vertex& vOther) {
851
if (vOther.IsNull()) {
854
if (vCurrent.IsSame(vOther)) {
857
double tol = std::max(BRep_Tool::Pnt(vCurrent).Distance(BRep_Tool::Pnt(vOther)),
858
BRep_Tool::Tolerance(vOther));
859
if (tol >= BRep_Tool::Tolerance(vCurrent)) {
860
ShapeFix_ShapeTolerance fix;
861
const double halving {0.5};
862
fix.SetTolerance(vCurrent, std::max(tol*halving, myTol), TopAbs_VERTEX);
864
BRepBuilderAPI_MakeWire mkWire(eOther);
865
mkWire.Add(eCurrent);
866
auto newEdge = mkWire.Edge();
867
TopoDS_Vertex vFirst = TopExp::FirstVertex(newEdge);
868
TopoDS_Vertex vLast = TopExp::LastVertex(newEdge);
870
// Originally here there was a call to the precompiler macro assertCheck(), which has
871
// been replaced with the precompiler macro assert()
873
assert(vLast.IsSame(vOther) || vFirst.IsSame(vOther));
877
TopoDS_Edge edge = eToAdd;
878
TopoDS_Vertex vFirst = TopExp::FirstVertex(eToAdd);
879
TopoDS_Vertex vLast = TopExp::LastVertex(eToAdd);
880
connectEdge(edge, vFirst, ev1, v1);
881
connectEdge(edge, vLast, ev2, v2);
882
if (!edge.IsSame(eToAdd)) {
883
auto itSource = sourceEdges.find(eToAdd);
884
if (itSource != sourceEdges.end()) {
885
TopoShape newEdge = *itSource;
886
newEdge.setShape(edge, false);
887
sourceEdges.erase(itSource);
888
sourceEdges.insert(newEdge);
890
getEndPoints(edge,p1,p2);
891
// Shall we also update bbox?
893
it = edges.emplace(it,edge,p1,p2,bbox,queryBBox,isLinear);
898
void add(const TopoDS_Shape &shape, bool queryBBox=false)
900
for (TopExp_Explorer xp(shape, TopAbs_EDGE); xp.More(); xp.Next()) {
901
add(TopoDS::Edge(xp.Current()), queryBBox);
905
// This method was originally part of WireJoinerP::join(), split to reduce cognitive complexity
906
void joinMakeWire(const int idx,
907
BRepBuilderAPI_MakeWire& mkWire,
908
const Edges::iterator it,
912
gp_Pnt pstart(it->p1);
914
while (!edges.empty()) {
915
std::vector<VertexInfo> ret;
917
const gp_Pnt& pt = idx == 0 ? pstart : pend;
918
vmap.query(bgi::nearest(pt, 1), std::back_inserter(ret));
920
// Originally here there was a call to the precompiler macro assertCheck(),
921
// which has been replaced with the precompiler macro assert()
923
assert(ret.size() == 1);
924
double dist = ret[0].pt().SquareDistance(pt);
929
const auto& info = *ret[0].it;
930
bool start = ret[0].start;
931
if (dist > Precision::SquareConfusion()) {
932
// insert a filling edge to solve the tolerance problem
933
const gp_Pnt& pt = ret[idx].pt();
935
mkWire.Add(BRepBuilderAPI_MakeEdge(pend, pt).Edge());
938
mkWire.Add(BRepBuilderAPI_MakeEdge(pt, pstart).Edge());
942
if (idx == 1 && start) {
944
mkWire.Add(info.edge);
946
else if (idx == 0 && !start) {
948
mkWire.Add(info.edge);
950
else if (idx == 0 && start) {
952
mkWire.Add(TopoDS::Edge(info.edge.Reversed()));
956
mkWire.Add(TopoDS::Edge(info.edge.Reversed()));
959
if (pstart.SquareDistance(pend) <= Precision::SquareConfusion()) {
966
//This algorithm tries to join connected edges into wires
968
//tol*tol>Precision::SquareConfusion() can be used to join points that are
969
//close but do not coincide with a line segment. The close points may be
970
//the results of rounding issue.
974
while (!edges.empty()) {
975
auto it = edges.begin();
976
BRepBuilderAPI_MakeWire mkWire;
977
mkWire.Add(it->edge);
981
for (int idx=0;!done&&idx<2;++idx) {
982
joinMakeWire(idx, mkWire, it, done);
985
builder.Add(compound,mkWire.Wire());
989
struct IntersectInfo {
991
TopoDS_Shape intersectShape;
993
IntersectInfo(double pToIntersect, const gp_Pnt& pt, TopoDS_Shape sToIntersect)
994
: param(pToIntersect)
995
, intersectShape(std::move(sToIntersect))
998
bool operator<(const IntersectInfo &other) const {
999
return param < other.param;
1003
void checkSelfIntersection(const EdgeInfo &info, std::set<IntersectInfo> ¶ms) const
1005
// Early return if checking for self intersection (only for non linear spline curves)
1006
if (info.type <= GeomAbs_Parabola || info.isLinear) {
1009
IntRes2d_SequenceOfIntersectionPoint points2d;
1010
TColgp_SequenceOfPnt points3d;
1011
TColStd_SequenceOfReal errors;
1013
BRepBuilderAPI_MakeWire mkWire(info.edge);
1014
if (!mkWire.IsDone()) {
1017
if (!BRep_Tool::IsClosed(mkWire.Wire())) {
1018
BRepBuilderAPI_MakeEdge mkEdge(info.p1, info.p2);
1019
if (!mkEdge.IsDone()) {
1022
mkWire.Add(mkEdge.Edge());
1024
wire = mkWire.Wire();
1025
BRepBuilderAPI_MakeFace mkFace(wire);
1026
if (!mkFace.IsDone()) {
1029
const TopoDS_Face& face = mkFace.Face();
1030
ShapeAnalysis_Wire analysis(wire, face, myTol);
1031
analysis.CheckSelfIntersectingEdge(1, points2d, points3d);
1033
// Originally here there was a call to the precompiler macro assertCheck(), which has been
1034
// replaced with the precompiler macro assert()
1036
assert(points2d.Length() == points3d.Length());
1037
for (int i=1; i<=points2d.Length(); ++i) {
1038
params.emplace(points2d(i).ParamOnFirst(), points3d(i), info.edge);
1039
params.emplace(points2d(i).ParamOnSecond(), points3d(i), info.edge);
1043
// This method was originally part of WireJoinerP::checkIntersection(), split to reduce
1044
// cognitive complexity
1045
bool checkIntersectionPlanar(const EdgeInfo& info,
1046
const EdgeInfo& other,
1047
std::set<IntersectInfo>& params1,
1048
std::set<IntersectInfo>& params2)
1051
bool planar = TopoShape(info.edge).findPlane(pln);
1053
TopoDS_Compound comp;
1054
builder.MakeCompound(comp);
1055
builder.Add(comp, info.edge);
1056
builder.Add(comp, other.edge);
1057
planar = TopoShape(comp).findPlane(pln);
1059
BRepExtrema_DistShapeShape extss(info.edge, other.edge);
1061
if (extss.IsDone() && extss.NbSolution() > 0) {
1062
if (!extss.IsDone() || extss.NbSolution() <= 0 || extss.Value() >= myTol) {
1066
for (int i = 1; i <= extss.NbSolution(); ++i) {
1067
Standard_Real par = Standard_Real();
1068
auto s1 = extss.SupportOnShape1(i);
1069
auto s2 = extss.SupportOnShape2(i);
1070
if (s1.ShapeType() == TopAbs_EDGE) {
1071
extss.ParOnEdgeS1(i, par);
1072
pushIntersection(params1, par, extss.PointOnShape1(i), other.edge);
1074
if (s2.ShapeType() == TopAbs_EDGE) {
1075
extss.ParOnEdgeS2(i, par);
1076
pushIntersection(params2, par, extss.PointOnShape2(i), info.edge);
1086
// This method was originally part of WireJoinerP::checkIntersection(), split to reduce
1087
// cognitive complexity
1088
static bool checkIntersectionEdgeDone(const BRepBuilderAPI_MakeEdge& mkEdge)
1090
if (!mkEdge.IsDone()) {
1091
if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) {
1092
FC_WARN("Failed to build edge for checking intersection");
1099
// This method was originally part of WireJoinerP::checkIntersection(), split to reduce
1100
// cognitive complexity
1101
static bool checkIntersectionWireDone(const BRepBuilderAPI_MakeWire& mkWire)
1103
if (!mkWire.IsDone()) {
1104
if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) {
1105
FC_WARN("Failed to build wire for checking intersection");
1112
// This method was originally part of WireJoinerP::checkIntersection(), split to reduce
1113
// cognitive complexity
1114
static bool checkIntersectionMakeWire(const EdgeInfo& info,
1115
const EdgeInfo& other,
1119
BRepBuilderAPI_MakeWire mkWire(info.edge);
1120
mkWire.Add(other.edge);
1121
if (mkWire.IsDone()) {
1124
else if (mkWire.Error() == BRepBuilderAPI_DisconnectedWire) {
1126
BRepBuilderAPI_MakeEdge mkEdge(info.p1, other.p1);
1128
if (!checkIntersectionEdgeDone(mkEdge)) {
1132
mkWire.Add(mkEdge.Edge());
1133
mkWire.Add(other.edge);
1136
if (!checkIntersectionWireDone(mkWire)) {
1140
wire = mkWire.Wire();
1141
if (!BRep_Tool::IsClosed(wire)) {
1142
gp_Pnt p1 = gp_Pnt();
1143
gp_Pnt p2 = gp_Pnt();
1144
getEndPoints(wire, p1, p2);
1145
BRepBuilderAPI_MakeEdge mkEdge(p1, p2);
1147
if (!checkIntersectionEdgeDone(mkEdge)) {
1151
mkWire.Add(mkEdge.Edge());
1156
// This method was originally part of WireJoinerP::checkIntersection(), split to reduce
1157
// cognitive complexity
1158
static bool checkIntersectionFaceDone(const BRepBuilderAPI_MakeFace& mkFace)
1160
if (!mkFace.IsDone()) {
1161
if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) {
1162
FC_WARN("Failed to build face for checking intersection");
1169
void checkIntersection(const EdgeInfo &info,
1170
const EdgeInfo &other,
1171
std::set<IntersectInfo> ¶ms1,
1172
std::set<IntersectInfo> ¶ms2)
1174
if(!checkIntersectionPlanar(info, other, params1, params2)){
1178
// BRepExtrema_DistShapeShape has trouble finding all solutions for a
1179
// spline curve. ShapeAnalysis_Wire is better. Besides, it can check
1180
// for self intersection. It's slightly more troublesome to use, as it
1181
// requires to build a face for the wire, so we only use it for planar
1184
IntRes2d_SequenceOfIntersectionPoint points2d;
1185
TColgp_SequenceOfPnt points3d;
1186
TColStd_SequenceOfReal errors;
1190
if (!checkIntersectionMakeWire(info, other, idx, wire)){
1194
BRepBuilderAPI_MakeFace mkFace(wire);
1195
if (!checkIntersectionFaceDone(mkFace)) {
1199
const TopoDS_Face& face = mkFace.Face();
1200
ShapeAnalysis_Wire analysis(wire, face, myTol);
1201
analysis.CheckIntersectingEdges(1, idx, points2d, points3d, errors);
1203
// Originally here there was a call to the precompiler macro assertCheck(), which has been
1204
// replaced with the precompiler macro assert()
1206
assert(points2d.Length() == points3d.Length());
1207
for (int i=1; i<=points2d.Length(); ++i) {
1208
pushIntersection(params1, points2d(i).ParamOnFirst(), points3d(i), other.edge);
1209
pushIntersection(params2, points2d(i).ParamOnSecond(), points3d(i), info.edge);
1213
void pushIntersection(std::set<IntersectInfo>& params,
1216
const TopoDS_Shape& shape)
1218
IntersectInfo info(param, pt, shape);
1219
auto it = params.upper_bound(info);
1220
if (it != params.end()) {
1221
if (it->point.SquareDistance(pt) < myTol2) {
1225
if (it != params.begin()) {
1228
if (itPrev->point.SquareDistance(pt) < myTol2) {
1232
params.insert(it, info);
1237
TopoDS_Shape intersectShape;
1241
// This method was originally part of WireJoinerP::splitEdges(), split to reduce cognitive
1243
void splitEdgesMakeEdge(const std::set<IntersectInfo>::iterator& itParam,
1244
const EdgeInfo& info,
1245
std::vector<SplitInfo>& splits,
1246
std::set<IntersectInfo>::iterator& itPrevParam,
1247
const TopoDS_Shape& intersectShape)
1249
// Using points cause MakeEdge failure for some reason. Using
1250
// parameters is better.
1252
const gp_Pnt& p1 = itPrevParam->point;
1253
const gp_Pnt& p2 = itParam->point;
1254
const Standard_Real& param1 = itPrevParam->param;
1255
const Standard_Real& param2 = itParam->param;
1257
BRepBuilderAPI_MakeEdge mkEdge(info.curve, param1, param2);
1258
if (mkEdge.IsDone()) {
1259
splits.emplace_back();
1260
auto& entry = splits.back();
1261
entry.edge = mkEdge.Edge();
1262
entry.intersectShape = intersectShape;
1263
if (getBBox(entry.edge, entry.bbox)) {
1264
itPrevParam = itParam;
1270
else if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) {
1271
FC_WARN("edge split failed " << std::setprecision(16) << FC_XYZ(p1) << FC_XYZ(p2)
1272
<< ": " << mkEdge.Error());
1276
// This method was originally part of WireJoinerP::splitEdges(), split to reduce cognitive
1278
void splitEdgesMakeEdges(std::set<IntersectInfo>::iterator& itParam,
1279
const std::set<IntersectInfo>& params,
1280
const EdgeInfo& info,
1281
std::vector<SplitInfo>& splits)
1283
for (auto itPrevParam = itParam++; itParam != params.end(); ++itParam) {
1284
const auto& intersectShape = itParam->intersectShape.IsNull()
1285
? itPrevParam->intersectShape
1286
: itParam->intersectShape;
1287
if (intersectShape.IsNull()) {
1291
splitEdgesMakeEdge(itParam, info, splits, itPrevParam, intersectShape);
1295
// Try splitting any edges that intersects other edge
1298
std::unordered_map<const EdgeInfo*, std::set<IntersectInfo>> intersects;
1301
for (auto& info : edges) {
1302
info.iteration = ++idx;
1305
std::unique_ptr<Base::SequencerLauncher> seq(
1306
new Base::SequencerLauncher("Splitting edges", edges.size()));
1309
for (auto& info : edges) {
1312
auto ¶ms = intersects[&info];
1313
checkSelfIntersection(info, params);
1315
for (auto vit=boxMap.qbegin(bgi::intersects(info.box)); vit!=boxMap.qend(); ++vit) {
1316
const auto &other = *(*vit);
1317
if (other.iteration <= idx) {
1318
// means the edge is before us, and we've already checked intersection
1321
checkIntersection(info, other, params, intersects[&other]);
1326
std::vector<SplitInfo> splits;
1327
for (auto it=edges.begin(); it!=edges.end(); ) {
1329
auto iter = intersects.find(&(*it));
1330
if (iter == intersects.end()) {
1335
auto ¶ms = iter->second;
1336
if (params.empty()) {
1341
auto itParam = params.begin();
1342
if (itParam->point.SquareDistance(info.p1) < myTol2) {
1343
params.erase(itParam);
1345
params.emplace(info.firstParam, info.p1, TopoDS_Shape());
1346
itParam = params.end();
1348
if (itParam->point.SquareDistance(info.p2) < myTol2) {
1349
params.erase(itParam);
1351
params.emplace(info.lastParam, info.p2, TopoDS_Shape());
1353
if (params.size() <= 2) {
1359
itParam = params.begin();
1361
splitEdgesMakeEdges(itParam, params, info, splits);
1363
if (splits.size() <= 1) {
1368
showShape(info.edge, "remove");
1369
auto removedEdge = info.edge;
1371
for (const auto& split : splits) {
1372
if (!add(split.edge, false, split.bbox, it)) {
1375
auto &newInfo = *it++;
1376
aHistory->AddModified(split.intersectShape, newInfo.edge);
1377
// if (v.intersectShape != removedEdge)
1378
// aHistory->AddModified(removedEdge, newInfo.edge);
1379
showShape(newInfo.edge, "split");
1384
// This method was originally part of WireJoinerP::findSuperEdges(), split to reduce cognitive
1386
void findSuperEdgeFromAdjacent(std::deque<VertexInfo>& vertices, const int direction)
1389
auto begin = direction == 1 ? vertices.back().reversed() : vertices.front();
1391
auto currentVertex = direction == 1 ? vertices.front() : vertices.back();
1392
auto current = currentVertex.edgeInfo();
1393
// showShape(current, "siter", k);
1394
const int idx = (currentVertex.start ? 1 : 0) ^ direction;
1395
EdgeInfo* found = nullptr;
1396
for (int i = current->iStart[idx]; i < current->iEnd[idx]; ++i) {
1397
const auto& vertex = adjacentList[i];
1398
auto next = vertex.edgeInfo();
1399
if (next->iteration < 0 // skipped
1400
|| next == current) { // skip self (see how adjacent list is built)
1403
if (vertex == begin) {
1408
if (found // more than one branch
1409
|| edgeSet.contains(next)) // or, self intersect
1412
// Originally here there were some lines of code that have been removed
1413
// as them are commented out.
1415
// https://github.com/realthunder/FreeCAD/blob/6f15849be2505f98927e75d0e8352185e14e7b72/src/Mod/Part/App/WireJoiner.cpp#L1141
1422
currentVertex = vertex;
1424
if (done || !found) {
1427
// showShape(found, "snext", k);
1428
if (direction == 1) {
1429
edgeSet.insert(current);
1430
vertices.push_front(currentVertex.reversed());
1433
edgeSet.insert(found);
1434
vertices.push_back(currentVertex);
1439
// This method was originally part of WireJoinerP::findSuperEdges(), split to reduce cognitive
1441
void findSuperEdge(std::deque<VertexInfo>& vertices, const Edges::iterator it)
1444
vertices.emplace_back(it, true);
1447
for (int direction = 0; direction < 2; ++direction) { // search in both direction
1448
findSuperEdgeFromAdjacent(vertices, direction);
1452
// This method was originally part of WireJoinerP::findSuperEdges(), split to reduce cognitive
1454
void findSuperEdgesUpdateFirst(std::deque<VertexInfo> vertices)
1457
for (const auto& vertex : vertices) {
1458
auto current = vertex.edgeInfo();
1459
bbox.Add(current->box.min_corner());
1460
bbox.Add(current->box.max_corner());
1461
wireData->Add(current->shape(vertex.start));
1462
showShape(current, "edge");
1463
current->iteration = -1;
1466
auto first = vertices.front().edgeInfo();
1467
first->superEdge = makeCleanWire(false);
1468
first->superEdgeReversed.Nullify();
1469
if (BRep_Tool::IsClosed(first->superEdge)) {
1470
first->iteration = -2;
1471
showShape(first, "super_done");
1474
first->iteration = iteration;
1475
showShape(first, "super");
1476
auto& vFirst = vertices.front();
1477
auto& vLast = vertices.back();
1478
auto last = vLast.edgeInfo();
1479
vFirst.ptOther() = vLast.ptOther();
1480
const int idx = vFirst.start ? 1 : 0;
1481
first->iStart[idx] = last->iStart[vLast.start ? 1 : 0];
1482
first->iEnd[idx] = last->iEnd[vLast.start ? 1 : 0];
1484
for (int i = first->iStart[idx]; i < first->iEnd[idx]; ++i) {
1485
auto& vertex = adjacentList[i];
1486
if (vertex.it == vLast.it) {
1487
vertex.it = vFirst.it;
1488
vertex.start = !vFirst.start;
1491
bbox.Enlarge(myTol);
1492
first->box = Box(bbox.CornerMin(), bbox.CornerMax());
1496
void findSuperEdges()
1498
std::unique_ptr<Base::SequencerLauncher> seq(
1499
new Base::SequencerLauncher("Combining edges", edges.size()));
1501
std::deque<VertexInfo> vertices;
1505
// Join edges (let's call it super edge) that are connected to only one
1506
// other edges (count == 2 counts this and the other edge) on one of
1507
// its vertices to save traverse time.
1508
for (auto it = edges.begin(); it != edges.end(); ++it) {
1511
if (info.iteration == iteration || info.iteration < 0) {
1514
info.iteration = iteration;
1515
// showShape(&info, "scheck");
1517
findSuperEdge(vertices, it);
1519
if (vertices.size() <= 1) {
1525
findSuperEdgesUpdateFirst(vertices);
1529
void buildAdjacentListPopulate()
1531
// populate adjacent list
1532
for (auto& info : edges) {
1533
if (info.iteration == -2) {
1535
// Originally there was the following precompiler directive around assertCheck():
1536
// #if OCC_VERSION_HEX >= 0x070000
1537
// The precompiler directive has been removed as the minimum OCCT version supported
1538
// is 7.3.0 and the precompiler macro has been replaced with assert()
1540
assert(BRep_Tool::IsClosed(info.shape()));
1542
showShape(&info, "closed");
1543
if (!doTightBound) {
1544
builder.Add(compound, info.wire());
1549
if (info.iteration < 0) {
1553
if (info.p1.SquareDistance(info.p2) <= myTol2) {
1554
if (!doTightBound) {
1555
builder.Add(compound, info.wire());
1557
info.iteration = -2;
1561
std::array<gp_Pnt, 2> pt {};
1564
for (int i = 0; i < 2; ++i) {
1566
if (info.iStart[ic] >= 0) {
1569
info.iEnd[ic] = info.iStart[ic] = (int)adjacentList.size();
1571
for (auto vit = vmap.qbegin(bgi::nearest(pt[ic], INT_MAX)); vit != vmap.qend();
1574
if (vinfo.pt().SquareDistance(pt[ic]) > myTol2) {
1578
// We must push ourself too, because the adjacency
1579
// information is shared among all connected edges.
1581
// if (&(*vinfo.it) == &info)
1584
if (vinfo.it->iteration < 0) {
1588
adjacentList.push_back(vinfo);
1592
// copy the adjacent indices to all connected edges
1593
for (int j = info.iStart[ic]; j < info.iEnd[ic]; ++j) {
1594
auto& other = adjacentList[j];
1595
auto& otherInfo = *other.it;
1596
if (&otherInfo != &info) {
1597
const int kc = other.start ? 0 : 1;
1598
otherInfo.iStart[kc] = info.iStart[ic];
1599
otherInfo.iEnd[kc] = info.iEnd[ic];
1606
void buildAdjacentListSkipEdges()
1612
if (doMergeEdge || doTightBound) {
1616
// Skip edges that are connected to only one end
1617
for (auto& info : edges) {
1618
if (info.iteration < 0) {
1621
for (int k = 0; k < 2; ++k) {
1624
for (idx = info.iStart[kc]; idx < info.iEnd[kc]; ++idx) {
1625
const auto& vertex = adjacentList[idx];
1626
auto other = vertex.edgeInfo();
1627
if (other->iteration >= 0 && other != &info) {
1631
if (idx == info.iEnd[kc]) {
1632
// If merge or tight bound, then repeat until no edges
1634
done = !doMergeEdge && !doTightBound;
1635
info.iteration = -3;
1636
showShape(&info, "skip");
1644
void buildAdjacentList()
1646
builder.MakeCompound(compound);
1648
for (auto& info : edges) {
1652
adjacentList.clear();
1654
buildAdjacentListPopulate();
1656
buildAdjacentListSkipEdges();
1659
// This algorithm tries to find a set of closed wires that includes as many
1660
// edges (added by calling add() ) as possible. One edge may be included
1661
// in more than one closed wires if it connects to more than one edges.
1662
void findClosedWires(bool tightBound=false)
1664
std::unique_ptr<Base::SequencerLauncher> seq(
1665
new Base::SequencerLauncher("Finding wires", edges.size()));
1667
for (auto &info : edges) {
1668
info.wireInfo.reset();
1669
info.wireInfo2.reset();
1672
for (auto it=edges.begin(); it!=edges.end(); ++it) {
1673
VertexInfo beginVertex(it, true);
1674
auto &beginInfo = *it;
1677
if (beginInfo.iteration < 0 || beginInfo.wireInfo) {
1681
VertexInfo currentVertex(it, true);
1682
EdgeInfo *currentInfo = &beginInfo;
1683
showShape(currentInfo, "begin");
1685
vertexStack.clear();
1688
TopoDS_Wire wire = _findClosedWires(beginVertex, currentVertex);
1689
if (wire.IsNull()) {
1695
// Originally here there was a call to the precompiler macro assertCheck(), which
1696
// has been replaced with the precompiler macro assert()
1698
assert(!beginInfo.wireInfo);
1699
beginInfo.wireInfo.reset(new WireInfo());
1700
beginInfo.wireInfo->vertices.emplace_back(it, true);
1701
beginInfo.wireInfo->wire = wire;
1703
for (auto &entry : stack) {
1704
const auto &vertex = vertexStack[entry.iCurrent];
1705
auto &info = *vertex.it;
1707
beginInfo.wireInfo->vertices.push_back(vertex);
1709
if (!info.wireInfo) {
1710
info.wireInfo = beginInfo.wireInfo;
1711
// showShape(&info, "visited");
1714
showShape(wire,"joined");
1716
builder.Add(compound, wire);
1721
// Originally here there was the definition of the method checkStack(), which does nothing and
1722
// therefor has been removed. See
1723
// https://github.com/realthunder/FreeCAD/blob/6f15849be2505f98927e75d0e8352185e14e7b72/src/Mod/Part/App/WireJoiner.cpp#L1366
1726
void checkWireInfo(const WireInfo &wireInfo)
1729
if (FC_LOG_INSTANCE.level() <= FC_LOGLEVEL_TRACE) {
1732
for (auto &info : edges) {
1733
if (auto wire = info.wireInfo.get()) {
1734
boost::ignore_unused(wire);
1736
// Originally here there was a call to the precompiler macro assertCheck(), which
1737
// has been replaced with the precompiler macro assert()
1739
assert(wire->vertices.front().edgeInfo()->wireInfo.get() == wire);
1744
// This method was originally part of WireJoinerP::_findClosedWires(), split to reduce cognitive
1746
void _findClosedWiresBeginEdge(const std::shared_ptr<WireInfo>& wireInfo,
1747
const EdgeInfo& beginInfo,
1750
auto info = wireInfo->vertices[idx].edgeInfo();
1751
showShape(info, "merge", iteration);
1753
if (info != &beginInfo) {
1755
if (++idx == (int)wireInfo->vertices.size()) {
1759
info = wireInfo->vertices[idx].edgeInfo();
1760
if (info == &beginInfo) {
1763
stack.emplace_back(vertexStack.size());
1764
vertexStack.push_back(wireInfo->vertices[idx]);
1765
++stack.back().iEnd;
1767
// Originally here there was a call to the method checkStack(),
1768
// which does nothing and therefor has been removed.
1773
// This method was originally part of WireJoinerP::_findClosedWires(), split to reduce cognitive
1775
int _findClosedWiresWithExisting(int* idxVertex,
1776
const std::shared_ptr<WireInfo>& wireInfo,
1777
int* const stackPos,
1779
const VertexInfo& vinfo,
1781
StackInfo& stackBack,
1782
const EdgeInfo& beginInfo,
1786
// We may be called by findTightBound() with an existing wire
1787
// to try to find a new wire by splitting the current one. So
1788
// check if we've iterated to some edge in the existing wire.
1790
int idx = wireInfo->find(vinfo);
1793
vertexStack.push_back(adjacentList[ic]);
1794
stackBack.iCurrent = stackBack.iEnd++;
1801
*stackPos = (int)stack.size() - 2;
1804
_findClosedWiresBeginEdge(wireInfo, beginInfo, idx);
1809
if (wireInfo->find(VertexInfo(vinfo.it, !vinfo.start)) != 0) {
1810
showShape(&info, "rintersect", iteration);
1811
// Only used when exhausting tight bound.
1812
wireInfo->purge = true;
1816
if (isOutside(*wireInfo, info.mid)) {
1817
showShape(&info, "outside", iteration);
1824
// This method was originally part of WireJoinerP::_findClosedWires(), split to reduce cognitive
1826
void _findClosedWiresUpdateStack(int* idxVertex,
1827
const std::shared_ptr<WireInfo>& wireInfo,
1829
const EdgeInfo* currentInfo,
1830
const int currentIdx,
1832
const EdgeInfo& beginInfo)
1834
auto& stackBack = stack.back();
1836
// The loop below is to find all edges connected to pend, and save them into stack.back()
1837
auto size = vertexStack.size();
1838
for (int i = currentInfo->iStart[currentIdx]; i < currentInfo->iEnd[currentIdx]; ++i) {
1839
auto& vinfo = adjacentList[i];
1840
auto& info = *vinfo.it;
1841
if (info.iteration < 0 || currentInfo == &info) {
1846
if (!wireSet.empty() && wireSet.contains(info.wireInfo.get())) {
1847
showShape(&info, "wired", iteration);
1849
wireInfo->purge = true;
1854
if (edgeSet.contains(&info)) {
1855
showShape(&info, "intersect", iteration);
1856
// This means the current edge connects to an
1857
// existing edge in the middle of the stack.
1858
// skip the current edge.
1859
stackBack.iEnd = stackBack.iStart;
1860
vertexStack.resize(size);
1864
if (abort || currentInfo->wireInfo2) {
1866
wireInfo->purge = true;
1871
if (info.iteration == iteration) {
1874
info.iteration = iteration;
1876
int exists = _findClosedWiresWithExisting(idxVertex,
1894
vertexStack.push_back(adjacentList[i]);
1899
// This method was originally part of WireJoinerP::_findClosedWires(), split to reduce cognitive
1901
bool _findClosedWiresUpdateEdges(VertexInfo& currentVertex,
1903
EdgeInfo* currentInfo,
1905
const size_t stackEnd)
1908
auto& stackBack = stack.back();
1909
if (stackBack.iCurrent < stackBack.iEnd) {
1910
// now pick one edge from stack.back(), connect it to
1911
// pend, then extend pend
1912
currentVertex = vertexStack[stackBack.iCurrent];
1913
pend = currentVertex.ptOther();
1914
// update current edge info
1915
currentInfo = currentVertex.edgeInfo();
1916
showShape(currentInfo, "iterate", iteration);
1917
currentIdx = currentVertex.start ? 1 : 0;
1918
edgeSet.insert(currentInfo);
1919
if (!wireSet.empty()) {
1920
wireSet.insert(currentInfo->wireInfo.get());
1924
vertexStack.erase(vertexStack.begin() + static_cast<long>(stackBack.iStart), vertexStack.end());
1927
if (stack.size() == stackEnd) {
1928
// If stack reaches the end, it means this wire is open.
1932
auto& lastInfo = *vertexStack[stack.back().iCurrent].it;
1933
edgeSet.erase(&lastInfo);
1934
wireSet.erase(lastInfo.wireInfo.get());
1935
showShape(&lastInfo, "pop", iteration);
1936
++stack.back().iCurrent;
1941
// This method was originally part of WireJoinerP::_findClosedWires(), split to reduce cognitive
1943
bool _findClosedWiresIsClosed(const VertexInfo& beginVertex,
1944
const TopoDS_Wire& wire,
1945
const EdgeInfo& beginInfo)
1947
if (!BRep_Tool::IsClosed(wire)) {
1948
FC_WARN("failed to close some wire in iteration " << iteration);
1949
showShape(wire, "_FailedToClose", iteration);
1950
showShape(beginInfo.shape(beginVertex.start), "failed", iteration);
1951
for (auto& entry : stack) {
1952
const auto& vertex = vertexStack[entry.iCurrent];
1953
auto& info = *vertex.it;
1954
showShape(info.shape(vertex.start), vertex.start ? "failed" : "failed_r", iteration);
1957
// Originally here there was a call to the precompiler macro assertCheck(), which
1958
// has been replaced with the precompiler macro assert()
1966
TopoDS_Wire _findClosedWires(VertexInfo beginVertex,
1967
VertexInfo currentVertex,
1968
int *idxVertex = nullptr,
1969
const std::shared_ptr<WireInfo>& wireInfo = std::shared_ptr<WireInfo>(),
1970
int* stackPos = nullptr)
1972
Base::SequencerBase::Instance().checkAbort();
1973
EdgeInfo &beginInfo = *beginVertex.it;
1975
EdgeInfo *currentInfo = currentVertex.edgeInfo();
1976
int currentIdx = currentVertex.start ? 1 : 0;
1977
currentInfo->iteration = iteration;
1979
gp_Pnt pstart = beginVertex.pt();
1980
gp_Pnt pend = currentVertex.ptOther();
1982
auto stackEnd = stack.size();
1984
// Originally here there was a call to the method checkStack(), which does nothing and
1985
// therefor has been removed.
1987
// pstart and pend is the start and end vertex of the current wire
1989
// push a new stack entry
1990
stack.emplace_back(vertexStack.size());
1991
showShape(currentInfo, "check", iteration);
1993
bool proceed = true;
1995
_findClosedWiresUpdateStack(idxVertex,
2003
// Originally here there was a call to the method checkStack(), which does nothing and
2004
// therefor has been removed.
2007
if (_findClosedWiresUpdateEdges(currentVertex,
2015
if (pstart.SquareDistance(pend) > myTol2) {
2016
// if the wire is not closed yet, continue search for the
2017
// next connected edge
2022
*idxVertex = (int)wireInfo->vertices.size();
2025
*stackPos = (int)stack.size() - 1;
2031
wireData->Add(beginInfo.shape(beginVertex.start));
2032
for (auto &entry : stack) {
2033
const auto &vertex = vertexStack[entry.iCurrent];
2034
auto &info = *vertex.it;
2035
wireData->Add(info.shape(vertex.start));
2037
TopoDS_Wire wire = makeCleanWire();
2038
if (!_findClosedWiresIsClosed(beginVertex, wire, beginInfo)) {
2045
// This method was originally part of WireJoinerP::findTightBound(), split to reduce cognitive
2047
void findTightBoundSplitWire(const std::shared_ptr<WireInfo>& wireInfo,
2048
const EdgeInfo& beginInfo,
2049
const std::vector<VertexInfo>& wireVertices,
2050
std::shared_ptr<WireInfo>& splitWire,
2055
for (int idx = idxV; idx != idxEnd; ++idx) {
2056
auto info = wireVertices[idx].edgeInfo();
2057
if (info == &beginInfo) {
2058
showShape(*wireInfo, "exception", iteration, true);
2059
showShape(info, "exception", iteration, true);
2061
// Originally here there was a call to the precompiler macro
2062
// assertCheck(), which has been replaced with the precompiler macro
2065
assert(info != &beginInfo);
2067
if (info->wireInfo == wireInfo) {
2070
splitWire.reset(new WireInfo());
2072
info->wireInfo = splitWire;
2077
// This method was originally part of WireJoinerP::findTightBound(), split to reduce cognitive
2079
void findTightBoundWithSplit(const std::vector<VertexInfo>& wireVertices,
2081
const std::shared_ptr<WireInfo>& splitWire,
2085
const int stackStart)
2087
auto& splitEdges = splitWire->vertices;
2091
for (int idx = idxStart; idx != idxEnd; ++idx) {
2092
auto& vertex = wireVertices[idx];
2095
pstart = vertex.pt();
2099
// Originally here there was a call to the precompiler macro
2100
// assertCheck(), which has been replaced with the precompiler
2103
assert(pt.SquareDistance(vertex.pt()) < myTol2);
2105
pt = vertex.ptOther();
2106
splitEdges.push_back(vertex);
2108
for (int i = stackPos; i >= stackStart; --i) {
2109
const auto& vertex = vertexStack[stack[i].iCurrent];
2111
// Originally here there was a call to the precompiler macro
2112
// assertCheck(), which has been replaced with the precompiler macro
2115
assert(pt.SquareDistance(vertex.ptOther()) < myTol2);
2117
// The edges in the stack are the ones to slice
2118
// the wire in half. We construct a new wire
2119
// that includes the original beginning edge in
2120
// the loop above. And this loop contains the
2121
// other half. Note that the slicing edges
2122
// should run in the oppsite direction, hence reversed
2123
splitEdges.push_back(vertex.reversed());
2125
for (int idx = idxV; idx != idxStart; ++idx) {
2126
auto& vertex = wireVertices[idx];
2128
// Originally here there was a call to the precompiler macro
2129
// assertCheck(), which has been replaced with the precompiler macro
2132
assert(pt.SquareDistance(vertex.pt()) < myTol2);
2133
pt = vertex.ptOther();
2134
splitEdges.push_back(vertex);
2137
// Originally here there was a call to the precompiler macro
2138
// assertCheck(), which has been replaced with the precompiler macro
2141
assert(pt.SquareDistance(pstart) < myTol2);
2142
showShape(*splitWire, "swire", iteration);
2145
// This method was originally part of WireJoinerP::findTightBound(), split to reduce cognitive
2147
void findTightBoundByVertices(EdgeInfo& beginInfo,
2148
const std::vector<VertexInfo>& wireVertices,
2150
const int iteration2,
2151
const gp_Pnt& pstart,
2152
const std::shared_ptr<WireInfo>& wireInfo,
2153
const VertexInfo& beginVertex,
2154
std::shared_ptr<WireInfo>& newWire)
2156
const int idx = wireVertices[idxV].start ? 1 : 0;
2158
auto current = wireVertices[idxV].edgeInfo();
2159
showShape(current, "current", iteration);
2161
for (int vertex = current->iStart[idx]; vertex < current->iEnd[idx]; ++vertex) {
2162
const auto& currentVertex = adjacentList[vertex];
2163
auto next = currentVertex.edgeInfo();
2164
if (next == current || next->iteration2 == iteration2 || next->iteration < 0) {
2168
showShape(next, "tcheck", iteration);
2170
if (!isInside(*wireInfo, next->mid)) {
2171
showShape(next, "ninside", iteration);
2172
next->iteration2 = iteration2;
2176
edgeSet.insert(next);
2177
stack.emplace_back(vertexStack.size());
2178
++stack.back().iEnd;
2179
vertexStack.push_back(currentVertex);
2181
// Originally here there was a call to the method checkStack(), which does
2182
// nothing and therefor has been removed.
2184
int idxEnd = (int)wireVertices.size();
2185
int stackStart = (int)stack.size() - 1;
2186
int stackPos = (int)stack.size() - 1;
2189
if (pstart.SquareDistance(currentVertex.ptOther()) > myTol2) {
2190
wire = _findClosedWires(beginVertex,
2195
if (wire.IsNull()) {
2196
vertexStack.pop_back();
2198
edgeSet.erase(next);
2203
newWire.reset(new WireInfo());
2204
auto& newWireVertices = newWire->vertices;
2205
newWireVertices.push_back(beginVertex);
2206
for (auto& entry : stack) {
2207
const auto& vertex = vertexStack[entry.iCurrent];
2208
newWireVertices.push_back(vertex);
2210
if (!wire.IsNull()) {
2211
newWire->wire = wire;
2213
else if (!initWireInfo(*newWire)) {
2215
vertexStack.pop_back();
2217
edgeSet.erase(next);
2220
for (auto& vertex : newWire->vertices) {
2221
if (vertex.edgeInfo()->wireInfo == wireInfo) {
2222
vertex.edgeInfo()->wireInfo = newWire;
2225
beginInfo.wireInfo = newWire;
2226
showShape(*newWire, "nwire", iteration);
2228
std::shared_ptr<WireInfo> splitWire;
2230
idxEnd = (int)wireVertices.size();
2234
// Originally here there was a call to the precompiler macro assertCheck(),
2235
// which has been replaced with the precompiler macro assert()
2237
assert(idxV <= idxEnd);
2238
int idxStart = idxV;
2240
findTightBoundSplitWire(wireInfo,
2249
findTightBoundWithSplit(wireVertices,
2258
checkWireInfo(*newWire);
2263
// This method was originally part of WireJoinerP::findTightBound(), split to reduce cognitive
2265
void findTightBoundUpdateVertices(EdgeInfo& beginInfo)
2267
showShape(*beginInfo.wireInfo, "done", iteration);
2268
beginInfo.wireInfo->done = true;
2269
// If a wire is done, make sure all edges of this wire is
2270
// marked as done. This can also prevent duplicated wires.
2271
for (auto& vertex : beginInfo.wireInfo->vertices) {
2272
auto info = vertex.edgeInfo();
2273
if (!info->wireInfo) {
2274
info->wireInfo = beginInfo.wireInfo;
2277
if (info->wireInfo->done) {
2280
auto otherWire = info->wireInfo;
2281
auto& otherWireVertices = info->wireInfo->vertices;
2282
if (info == otherWireVertices.front().edgeInfo()) {
2283
// About to change the first edge of the other wireInfo.
2284
// Try to find a new first edge for it.
2285
tmpVertices.clear();
2286
auto it = otherWireVertices.begin();
2287
tmpVertices.push_back(*it);
2288
for (++it; it != otherWireVertices.end(); ++it) {
2289
if (it->edgeInfo()->wireInfo == otherWire) {
2292
tmpVertices.push_back(*it);
2294
if (tmpVertices.size() != otherWireVertices.size()) {
2295
otherWireVertices.erase(otherWireVertices.begin(), it);
2296
otherWireVertices.insert(otherWireVertices.end(),
2297
tmpVertices.begin(),
2302
// Originally here there was a call to the precompiler macro assertCheck(),
2303
// which has been replaced with the precompiler macro assert()
2305
assert(info != &beginInfo);
2306
info->wireInfo = beginInfo.wireInfo;
2307
checkWireInfo(*otherWire);
2309
checkWireInfo(*beginInfo.wireInfo);
2312
void findTightBound()
2314
// Assumption: all edges lies on a common manifold surface
2316
// Definition of 'Tight Bound': a wire that cannot be split into
2317
// smaller wires by any intersecting edges internal to the wire.
2319
// The idea of the searching algorithm is simple. The initial condition
2320
// here is that we've found a closed wire for each edge. To find the
2321
// tight bound, for each wire, check wire edge branches (using the
2322
// adjacent list built earlier), and split the wire whenever possible.
2324
std::unique_ptr<Base::SequencerLauncher> seq(
2325
new Base::SequencerLauncher("Finding tight bound", edges.size()));
2327
int iteration2 = iteration;
2328
for (auto &info : edges) {
2331
if (info.iteration < 0 || !info.wireInfo) {
2336
while(!info.wireInfo->done) {
2337
auto wireInfo = info.wireInfo;
2338
checkWireInfo(*wireInfo);
2339
const auto &wireVertices = wireInfo->vertices;
2340
auto beginVertex = wireVertices.front();
2341
auto &beginInfo = *beginVertex.it;
2342
initWireInfo(*wireInfo);
2343
showShape(wireInfo->wire, "iwire", iteration);
2344
for (auto& vertex : wireVertices) {
2345
vertex.it->iteration2 = iteration2;
2349
vertexStack.clear();
2352
std::shared_ptr<WireInfo> newWire;
2353
gp_Pnt pstart = beginVertex.pt();
2357
findTightBoundByVertices(beginInfo,
2371
if (++idxV == (int)wireVertices.size()) {
2375
stack.emplace_back(vertexStack.size());
2376
++stack.back().iEnd;
2377
vertexStack.push_back(wireVertices[idxV]);
2378
edgeSet.insert(wireVertices[idxV].edgeInfo());
2380
// Originally here there was a call to the method checkStack(), which does
2381
// nothing and therefor has been removed.
2385
findTightBoundUpdateVertices(beginInfo);
2391
// This method was originally part of WireJoinerP::exhaustTightBound(), split to reduce cognitive
2393
void exhaustTightBoundUpdateVertex(const int iteration2,
2394
const VertexInfo& beginVertex,
2396
const gp_Pnt& pstart,
2397
const std::vector<VertexInfo>& wireVertices,
2398
std::shared_ptr<WireInfo>& newWire,
2399
const std::shared_ptr<WireInfo>& wireInfo)
2401
const int idx = wireVertices[idxV].start ? 1 : 0;
2402
auto current = wireVertices[idxV].edgeInfo();
2404
for (int vertex = current->iStart[idx]; vertex < current->iEnd[idx]; ++vertex) {
2405
const auto& currentVertex = adjacentList[vertex];
2406
auto next = currentVertex.edgeInfo();
2407
if (next == current || next->iteration2 == iteration2 || next->iteration < 0) {
2411
showShape(next, "check2", iteration);
2413
if (!isInside(*wireInfo, next->mid)) {
2414
showShape(next, "ninside2", iteration);
2415
next->iteration2 = iteration2;
2419
edgeSet.insert(next);
2420
stack.emplace_back(vertexStack.size());
2421
++stack.back().iEnd;
2422
vertexStack.push_back(currentVertex);
2424
// Originally here there a call to the method checkStack(), which
2425
// does nothing and therefor has been removed.
2428
if (pstart.SquareDistance(currentVertex.ptOther()) > myTol2) {
2429
wire = _findClosedWires(beginVertex, currentVertex, nullptr, wireInfo);
2430
if (wire.IsNull()) {
2431
vertexStack.pop_back();
2433
edgeSet.erase(next);
2434
wireSet.erase(next->wireInfo.get());
2439
newWire.reset(new WireInfo());
2440
auto& newWireVertices = newWire->vertices;
2441
newWireVertices.push_back(beginVertex);
2442
for (auto& entry : stack) {
2443
const auto& vertex = vertexStack[entry.iCurrent];
2444
newWireVertices.push_back(vertex);
2446
if (!wire.IsNull()) {
2447
newWire->wire = wire;
2449
else if (!initWireInfo(*newWire)) {
2451
vertexStack.pop_back();
2453
edgeSet.erase(next);
2454
wireSet.erase(next->wireInfo.get());
2457
for (auto& vertex : newWire->vertices) {
2458
if (vertex.edgeInfo()->wireInfo == wireInfo) {
2459
vertex.edgeInfo()->wireInfo = newWire;
2462
showShape(*newWire, "nwire2", iteration);
2463
checkWireInfo(*newWire);
2468
// This method was originally part of WireJoinerP::exhaustTightBound(), split to reduce cognitive
2470
void exhaustTightBoundUpdateEdge(const int iteration2,
2471
const VertexInfo& beginVertex,
2472
const std::vector<VertexInfo>& wireVertices,
2473
const gp_Pnt& pstart,
2474
std::shared_ptr<WireInfo>& wireInfo)
2476
std::shared_ptr<WireInfo> newWire;
2480
exhaustTightBoundUpdateVertex(iteration2,
2494
if (++idxV == (int)wireVertices.size()) {
2495
if (wireInfo->purge) {
2496
showShape(*wireInfo, "discard2", iteration);
2500
wireInfo->done = true;
2501
showShape(*wireInfo, "done2", iteration);
2505
stack.emplace_back(vertexStack.size());
2506
++stack.back().iEnd;
2507
vertexStack.push_back(wireVertices[idxV]);
2508
edgeSet.insert(wireVertices[idxV].edgeInfo());
2510
// Originally here there a call to the method checkStack(), which does
2511
// nothing and therefor has been removed.
2515
// This method was originally part of WireJoinerP::exhaustTightBound(), split to reduce cognitive
2517
void exhaustTightBoundWithAdjacent(const EdgeInfo& info,
2519
const VertexInfo beginVertex,
2520
const EdgeInfo* check)
2522
const gp_Pnt& pstart = beginVertex.pt();
2523
const int vidx = beginVertex.start ? 1 : 0;
2526
vertexStack.clear();
2528
stack.emplace_back();
2529
for (int i = info.iStart[vidx]; i < info.iEnd[vidx]; ++i) {
2530
const auto& currentVertex = adjacentList[i];
2531
auto next = currentVertex.edgeInfo();
2532
if (next == &info || next == check || next->iteration < 0 || !next->wireInfo
2533
|| !next->wireInfo->done || next->wireInfo2) {
2537
showShape(next, "n2", iteration);
2540
stack.emplace_back();
2541
++stack.back().iEnd;
2542
vertexStack.clear();
2543
vertexStack.push_back(currentVertex);
2546
edgeSet.insert(next);
2548
wireSet.insert(next->wireInfo.get());
2551
if (pstart.SquareDistance(currentVertex.ptOther()) > myTol2) {
2552
wire = _findClosedWires(beginVertex, currentVertex);
2553
if (wire.IsNull()) {
2558
std::shared_ptr<WireInfo> wireInfo(new WireInfo());
2559
wireInfo->vertices.push_back(beginVertex);
2560
for (auto& entry : stack) {
2561
const auto& vertex = vertexStack[entry.iCurrent];
2562
wireInfo->vertices.push_back(vertex);
2564
if (!wire.IsNull()) {
2565
wireInfo->wire = wire;
2567
else if (!initWireInfo(*wireInfo)) {
2571
showShape(*wireInfo, "nw2", iteration);
2576
while (wireInfo && !wireInfo->done) {
2577
showShape(next, "next2", iteration);
2579
vertexStack.resize(1);
2582
edgeSet.insert(next);
2584
wireSet.insert(next->wireInfo.get());
2586
const auto& wireVertices = wireInfo->vertices;
2587
initWireInfo(*wireInfo);
2588
for (auto& vertex : wireVertices) {
2589
vertex.it->iteration2 = iteration2;
2592
exhaustTightBoundUpdateEdge(iteration2,
2599
if (wireInfo && wireInfo->done) {
2600
for (auto& vertex : wireInfo->vertices) {
2601
auto edgeInfo = vertex.edgeInfo();
2603
// Originally here there was a call to the precompiler macro
2604
// assertCheck(), which has been replaced with the precompiler macro
2607
assert(edgeInfo->wireInfo != nullptr);
2608
if (edgeInfo->wireInfo->isSame(*wireInfo)) {
2609
wireInfo = edgeInfo->wireInfo;
2613
for (auto& vertex : wireInfo->vertices) {
2614
auto edgeInfo = vertex.edgeInfo();
2615
if (!edgeInfo->wireInfo2 && edgeInfo->wireInfo != wireInfo) {
2616
edgeInfo->wireInfo2 = wireInfo;
2620
// Originally here there were two calls to the precompiler macro
2621
// assertCheck(), which have been replaced with the precompiler macro
2624
assert(info.wireInfo2 == wireInfo);
2625
assert(info.wireInfo2 != info.wireInfo);
2626
showShape(*wireInfo, "exhaust");
2632
// This method was originally part of WireJoinerP::exhaustTightBound(), split to reduce cognitive
2634
void exhaustTightBoundUpdateWire(const EdgeInfo& info, int& iteration2)
2637
showShape(*info.wireInfo, "iwire2", iteration);
2638
showShape(&info, "begin2", iteration);
2640
int idx = info.wireInfo->find(&info);
2642
// Originally here there was a call to the precompiler macro assertCheck(), which has
2643
// been replaced with the precompiler macro assert()
2646
const auto& vertices = info.wireInfo->vertices;
2648
int nextIdx = idx == (int)vertices.size() - 1 ? 0 : idx + 1;
2649
int prevIdx = idx == 0 ? (int)vertices.size() - 1 : idx - 1;
2650
int count = prevIdx == nextIdx ? 1 : 2;
2651
for (int idxV = 0; idxV < count && !info.wireInfo2; ++idxV) {
2652
auto check = vertices[idxV == 0 ? nextIdx : prevIdx].edgeInfo();
2653
auto beginVertex = vertices[idx];
2655
beginVertex.start = !beginVertex.start;
2658
exhaustTightBoundWithAdjacent(info, iteration2, beginVertex, check);
2662
void exhaustTightBound()
2664
// findTightBound() function will find a tight bound wire for each
2665
// edge. Now we try to find all possible tight bound wires, relying on
2666
// the important fact that an edge can be shared by at most two tight
2669
std::unique_ptr<Base::SequencerLauncher> seq(
2670
new Base::SequencerLauncher("Exhaust tight bound", edges.size()));
2672
for (auto &info : edges) {
2673
if (info.iteration < 0 || !info.wireInfo || !info.wireInfo->done) {
2676
for (auto &vertex : info.wireInfo->vertices) {
2677
auto edgeInfo = vertex.edgeInfo();
2678
if (edgeInfo->wireInfo != info.wireInfo) {
2679
edgeInfo->wireInfo2 = info.wireInfo;
2684
int iteration2 = iteration;
2685
for (auto &info : edges) {
2688
if (info.iteration < 0
2690
|| !info.wireInfo->done)
2692
if (info.wireInfo) {
2693
showShape(*info.wireInfo, "iskip");
2696
showShape(&info, "iskip");
2701
if (info.wireInfo2 && info.wireInfo2->done) {
2702
showShape(*info.wireInfo, "idone");
2706
exhaustTightBoundUpdateWire(info, iteration2);
2712
// This method was originally part of WireJoinerP::makeCleanWire(), split to reduce cognitive
2714
void printHistoryInit(const Handle_BRepTools_History& newHistory,
2715
const std::vector<TopoShape>& inputEdges)
2718
for (const auto& shape : sourceEdges) {
2719
#if OCC_VERSION_HEX < 0x070800
2720
FC_MSG(shape.getShape().TShape().get() << ", " << shape.getShape().HashCode(INT_MAX));
2722
FC_MSG(shape.getShape().TShape().get()
2723
<< ", " << std::hash<TopoDS_Shape> {}(shape.getShape()));
2726
printHistory(aHistory, sourceEdges);
2727
printHistory(newHistory, inputEdges);
2730
// This method was originally part of WireJoinerP::makeCleanWire(), split to reduce cognitive
2732
void printHistoryFinal()
2734
printHistory(aHistory, sourceEdges);
2736
for (int i = 1; i <= wireData->NbEdges(); ++i) {
2737
auto shape = wireData->Edge(i);
2738
#if OCC_VERSION_HEX < 0x070800
2739
FC_MSG(shape.TShape().get() << ", " << shape.HashCode(INT_MAX));
2741
FC_MSG(shape.TShape().get() << ", " << std::hash<TopoDS_Edge> {}(shape));
2746
TopoDS_Wire makeCleanWire(bool fixGap=true)
2748
// Make a clean wire with sorted, oriented, connected, etc edges
2750
std::vector<TopoShape> inputEdges;
2752
if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_TRACE)) {
2753
for (int i = 1; i <= wireData->NbEdges(); ++i) {
2754
inputEdges.emplace_back(wireData->Edge(i));
2758
ShapeFix_Wire fixer;
2759
Handle(ShapeBuild_ReShape) reshape = new ShapeBuild_ReShape();
2760
fixer.SetContext(reshape);
2761
fixer.Load(wireData);
2762
fixer.SetMaxTolerance(myTol);
2763
fixer.ClosedWireMode() = Standard_True;
2765
// fixer.FixReorder();
2766
// fixer.FixConnected();
2769
// Gap fixing may change vertex, but we need all concident vertexes
2770
// to be the same one.
2772
// fixer.FixGap3d(1, Standard_True);
2777
result = fixer.Wire();
2778
auto newHistory = fixer.Context()->History();
2781
if (FC_LOG_INSTANCE.level() > FC_LOGLEVEL_TRACE + 1) {
2782
printHistoryInit(newHistory, inputEdges);
2785
aHistory->Merge(newHistory);
2788
if (FC_LOG_INSTANCE.level() > FC_LOGLEVEL_TRACE + 1) {
2789
printHistoryFinal();
2795
// This method was originally part of WireJoinerP::printHistory(), split to reduce cognitive
2798
void printHistoryOfShape(const Handle(BRepTools_History)& hist, const T& shape)
2800
for (TopTools_ListIteratorOfListOfShape it(hist->Modified(shape.getShape())); it.More();
2802
#if OCC_VERSION_HEX < 0x070800
2803
FC_MSG(shape.getShape().TShape().get()
2804
<< ", " << shape.getShape().HashCode(INT_MAX) << " -> "
2805
<< it.Value().TShape().get() << ", " << it.Value().HashCode(INT_MAX));
2807
FC_MSG(shape.getShape().TShape().get()
2808
<< ", " << std::hash<TopoDS_Shape> {}(shape.getShape()) << " -> "
2809
<< it.Value().TShape().get() << ", " << std::hash<TopoDS_Shape> {}(it.Value()));
2815
void printHistory(Handle(BRepTools_History) hist, const T &input)
2817
FC_MSG("\nHistory:\n");
2818
for (const auto& shape : input) {
2819
printHistoryOfShape(hist, shape);
2823
bool canShowShape(int idx=-1, bool forced=false) const
2825
if (idx < 0 || catchIteration == 0 || catchIteration > idx) {
2826
if (!forced && FC_LOG_INSTANCE.level() <= FC_LOGLEVEL_TRACE) {
2833
void showShape(const EdgeInfo* info, const char* name, int idx = -1, bool forced = false) const
2835
if (!canShowShape(idx, forced)) {
2838
showShape(info->shape(), name, idx, forced);
2841
void showShape(WireInfo &wireInfo, const char *name, int idx=-1, bool forced=false)
2843
if (!canShowShape(idx, forced)) {
2846
if (wireInfo.wire.IsNull()) {
2847
initWireInfo(wireInfo);
2849
showShape(wireInfo.wire, name, idx, forced);
2852
void showShape(const TopoDS_Shape& sToShow,
2855
bool forced = false) const
2857
if (!canShowShape(idx, forced)) {
2864
_name += std::to_string(idx);
2866
name = _name.c_str();
2868
auto obj = Feature::create(sToShow, name);
2869
FC_MSG(obj->getNameInDocument() << " " << ShapeHasher()(sToShow));
2870
if (catchObject == obj->getNameInDocument()) {
2875
// This method was originally part of WireJoinerP::build(), split to reduce cognitive complexity
2876
void buildClosedWire()
2878
findClosedWires(true);
2880
exhaustTightBound();
2881
bool done = !doOutline;
2885
std::unordered_map<EdgeInfo*, int> counter;
2886
std::unordered_set<WireInfo*> wires;
2887
for (auto& info : edges) {
2888
if (info.iteration == -2) {
2891
if (info.iteration < 0 || !info.wireInfo || !info.wireInfo->done) {
2892
if (info.iteration >= 0) {
2893
info.iteration = -1;
2895
showShape(&info, "removed", iteration);
2896
aHistory->Remove(info.edge);
2900
if (info.wireInfo2 && wires.insert(info.wireInfo2.get()).second) {
2901
for (auto& vertex : info.wireInfo2->vertices) {
2902
if (++counter[vertex.edgeInfo()] == 2) {
2903
vertex.edgeInfo()->iteration = -1;
2905
showShape(vertex.edgeInfo(), "removed2", iteration);
2906
aHistory->Remove(info.edge);
2910
if (!wires.insert(info.wireInfo.get()).second) {
2913
for (auto& vertex : info.wireInfo->vertices) {
2914
if (++counter[vertex.edgeInfo()] == 2) {
2915
vertex.edgeInfo()->iteration = -1;
2917
showShape(vertex.edgeInfo(), "removed1", iteration);
2918
aHistory->Remove(info.edge);
2922
findClosedWires(true);
2926
builder.MakeCompound(compound);
2928
for (auto& info : edges) {
2929
if (info.iteration == -2) {
2930
if (!info.wireInfo) {
2931
builder.Add(compound, info.wire());
2934
addWire(info.wireInfo);
2935
addWire(info.wireInfo2);
2937
else if (info.iteration >= 0) {
2938
addWire(info.wireInfo2);
2939
addWire(info.wireInfo);
2948
sourceEdges.clear();
2949
sourceEdges.insert(sourceEdgeArray.begin(), sourceEdgeArray.end());
2950
for (const auto& edge : sourceEdgeArray) {
2951
add(TopoDS::Edge(edge.getShape()), true);
2954
if (doTightBound || doSplitEdge) {
2958
buildAdjacentList();
2960
if (!doTightBound && !doOutline) {
2967
// TODO: We choose to put open wires in a separated shape from the final
2968
// result shape, so the history may contains some entries that are not
2969
// presented in the final result, which will cause warning message when
2970
// generating topo naming in TopoShape::makESHAPE(). We've lowered log
2971
// message level to suppress the warning for the moment. The right way
2972
// to solve the problem is to reconstruct the history and filter out
2975
bool hasOpenEdge = false;
2976
for (const auto &info : edges) {
2977
if (info.iteration == -3 || (!info.wireInfo && info.iteration>=0)) {
2980
builder.MakeCompound(openWireCompound);
2982
builder.Add(openWireCompound, info.wire());
2987
void addWire(std::shared_ptr<WireInfo> &wireInfo)
2989
if (!wireInfo || !wireInfo->done || !wireSet.insertUnique(wireInfo.get())) {
2992
initWireInfo(*wireInfo);
2993
builder.Add(compound, wireInfo->wire);
2996
bool getOpenWires(TopoShape &shape, const char *op, bool noOriginal) {
2997
if (openWireCompound.IsNull()) {
2998
shape.setShape(TopoShape());
3001
auto comp = openWireCompound;
3003
TopoShape source(-1);
3004
source.makeElementCompound(sourceEdgeArray);
3005
auto wires = TopoShape(openWireCompound, -1).getSubTopoShapes(TopAbs_WIRE);
3006
bool touched = false;
3007
for (auto it=wires.begin(); it!=wires.end();) {
3009
for (const auto &edge : it->getSubShapes(TopAbs_EDGE)) {
3010
if (source.findSubShapesWithSharedVertex(TopoShape(edge, -1)).empty()) {
3016
it = wires.erase(it);
3024
if (wires.empty()) {
3025
shape.setShape(TopoShape());
3028
comp = TopoDS::Compound(TopoShape(-1).makeElementCompound(wires).getShape());
3031
shape.makeShapeWithElementMap(comp,
3032
MapperHistory(aHistory),
3033
{sourceEdges.begin(), sourceEdges.end()},
3038
bool getResultWires(TopoShape &shape, const char *op) {
3039
// As compound is created by various calls to builder.MakeCompound() it looks that the
3040
// following condition is always false.
3041
// Probably it may be needed to add something like compound.Nullify() as done for
3042
// openWireCompound in WireJoiner::WireJoinerP::clear()
3043
if (compound.IsNull()) {
3044
shape.setShape(TopoShape());
3047
shape.makeShapeWithElementMap(compound,
3048
MapperHistory(aHistory),
3049
{sourceEdges.begin(), sourceEdges.end()},
3056
WireJoiner::WireJoiner()
3057
:pimpl(new WireJoinerP())
3061
WireJoiner::~WireJoiner() = default;
3063
void WireJoiner::addShape(const TopoShape &shape)
3066
for (auto& edge : shape.getSubTopoShapes(TopAbs_EDGE)) {
3067
pimpl->sourceEdgeArray.push_back(edge);
3071
void WireJoiner::addShape(const std::vector<TopoShape> &shapes)
3074
for (const auto &shape : shapes) {
3075
for (auto& edge : shape.getSubTopoShapes(TopAbs_EDGE)) {
3076
pimpl->sourceEdgeArray.push_back(edge);
3081
void WireJoiner::addShape(const std::vector<TopoDS_Shape> &shapes)
3084
for (const auto &shape : shapes) {
3085
for (TopExp_Explorer xp(shape, TopAbs_EDGE); xp.More(); xp.Next()) {
3086
pimpl->sourceEdgeArray.emplace_back(TopoDS::Edge(xp.Current()), -1);
3091
void WireJoiner::setOutline(bool enable)
3093
if (enable != pimpl->doOutline) {
3095
pimpl->doOutline = enable;
3099
void WireJoiner::setTightBound(bool enable)
3101
if (enable != pimpl->doTightBound) {
3103
pimpl->doTightBound = enable;
3107
void WireJoiner::setSplitEdges(bool enable)
3109
if (enable != pimpl->doSplitEdge) {
3111
pimpl->doSplitEdge = enable;
3115
void WireJoiner::setMergeEdges(bool enable)
3117
if (enable != pimpl->doSplitEdge) {
3119
pimpl->doMergeEdge = enable;
3123
void WireJoiner::setTolerance(double tol, double atol)
3125
if (tol >= 0 && tol != pimpl->myTol) {
3128
pimpl->myTol2 = tol * tol;
3130
if (atol >= 0 && atol != pimpl->myAngularTol) {
3132
pimpl->myAngularTol = atol;
3136
#if OCC_VERSION_HEX < 0x070600
3137
void WireJoiner::Build()
3140
void WireJoiner::Build(const Message_ProgressRange& theRange)
3148
if (TopoShape(pimpl->compound).countSubShapes(TopAbs_SHAPE) > 0) {
3149
myShape = pimpl->compound;
3157
bool WireJoiner::getOpenWires(TopoShape &shape, const char *op, bool noOriginal)
3160
return pimpl->getOpenWires(shape, op, noOriginal);
3163
bool WireJoiner::getResultWires(TopoShape &shape, const char *op)
3166
return pimpl->getResultWires(shape, op);
3169
const TopTools_ListOfShape& WireJoiner::Generated (const TopoDS_Shape& SThatGenerates)
3172
return pimpl->aHistory->Generated(SThatGenerates);
3175
const TopTools_ListOfShape& WireJoiner::Modified (const TopoDS_Shape& SThatModifies)
3178
return pimpl->aHistory->Modified(SThatModifies);
3181
Standard_Boolean WireJoiner::IsDeleted (const TopoDS_Shape& SDeleted)
3184
return pimpl->aHistory->IsRemoved(SDeleted);