Files
create/src/Mod/Part/App/WireJoiner.cpp
CalligaroV 0834709375 Part/Toponaming: Transfer WireJoiner
* Added test for WireJoiner::getResultWires()
 * Replaced references in test for WireJoiner::getOpenWires() with more correct references
 * Added a comment in WireJoiner::WireJoinerP::getResultWires() to better explain how it works

Signed-off-by: CalligaroV <vincenzo.calligaro@gmail.com>
2024-03-26 09:57:47 +01:00

2441 lines
89 KiB
C++

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