* Applied modifications to reduce the number of Lint warnings (round 3) * Added note about the WireJoiner class in Mod/CAM/App/Area.cpp
3167 lines
109 KiB
C++
3167 lines
109 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 <utility>
|
|
|
|
#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;
|
|
|
|
const size_t RParametersNumber = 16UL;
|
|
using RParameters = bgi::linear<RParametersNumber>;
|
|
|
|
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 &eForEndPoints, gp_Pnt &p1, gp_Pnt &p2) {
|
|
p1 = BRep_Tool::Pnt(TopExp::FirstVertex(eForEndPoints));
|
|
p2 = BRep_Tool::Pnt(TopExp::LastVertex(eForEndPoints));
|
|
}
|
|
|
|
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()));
|
|
}
|
|
|
|
// Originally here there was the definition of the precompiler macro assertCheck() and of the method
|
|
// _assertCheck(), that have been replaced with the already defined precompiler macro assert().
|
|
// See
|
|
// https://github.com/realthunder/FreeCAD/blob/6f15849be2505f98927e75d0e8352185e14e7b72/src/Mod/Part/App/WireJoiner.cpp#L107
|
|
// for reference and https://github.com/FreeCAD/FreeCAD/pull/12535/files#r1526647457 for the
|
|
// discussion about replacing it
|
|
|
|
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;
|
|
|
|
using Box = bg::model::box<gp_Pnt>;
|
|
|
|
bool checkBBox(const Bnd_Box &box) const
|
|
{
|
|
if (box.IsVoid()) {
|
|
return false;
|
|
}
|
|
Standard_Real xMin = Standard_Real();
|
|
Standard_Real yMin = Standard_Real();
|
|
Standard_Real zMin = Standard_Real();
|
|
Standard_Real xMax = Standard_Real();
|
|
Standard_Real yMax = Standard_Real();
|
|
Standard_Real zMax = Standard_Real();
|
|
box.Get(xMin, yMin, zMin, xMax, yMax, zMax);
|
|
return zMax - zMin <= myTol;
|
|
}
|
|
|
|
WireJoinerP()
|
|
: catchObject(App::GetApplication()
|
|
.GetParameterGroupByPath("User parameter:BaseApp/Preferences/WireJoiner")
|
|
->GetASCII("ObjectName"))
|
|
, catchIteration(static_cast<int>(
|
|
App::GetApplication()
|
|
.GetParameterGroupByPath("User parameter:BaseApp/Preferences/WireJoiner")
|
|
->GetInt("Iteration", 0)))
|
|
{}
|
|
|
|
bool getBBox(const TopoDS_Shape &eForBBox, Bnd_Box &bound) {
|
|
BRepBndLib::AddOptimal(eForBBox,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(eForBBox, "invalid");
|
|
}
|
|
if (bound.SquareExtent() < myTol2) {
|
|
return false;
|
|
}
|
|
bound.Enlarge(myTol);
|
|
return true;
|
|
}
|
|
|
|
bool getBBox(const TopoDS_Shape &eForBBox, Box &box) {
|
|
Bnd_Box bound;
|
|
if (!getBBox(eForBBox, bound)) {
|
|
return false;
|
|
}
|
|
Standard_Real xMin = Standard_Real();
|
|
Standard_Real yMin = Standard_Real();
|
|
Standard_Real zMin = Standard_Real();
|
|
Standard_Real xMax = Standard_Real();
|
|
Standard_Real yMax = Standard_Real();
|
|
Standard_Real zMax = Standard_Real();
|
|
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;
|
|
std::array<int, 2> iStart {}; // adjacent list index start for p1 and p2
|
|
std::array<int, 2> iEnd {}; // 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& eForInfo,
|
|
const gp_Pnt& pt1,
|
|
const gp_Pnt& pt2,
|
|
const Box& bound,
|
|
bool bbox,
|
|
bool isLinear)
|
|
: edge(eForInfo)
|
|
, p1(pt1)
|
|
, p2(pt2)
|
|
, box(bound)
|
|
, queryBBox(bbox)
|
|
, isLinear(isLinear)
|
|
{
|
|
curve = BRep_Tool::Curve(eForInfo, firstParam, lastParam);
|
|
type = GeomAdaptor_Curve(curve).GetType();
|
|
|
|
// Originally here there was a call to the precompiler macro assertCheck(), which has
|
|
// been replaced with the precompiler macro assert()
|
|
|
|
assert(!curve.IsNull());
|
|
const double halving {0.5};
|
|
GeomLProp_CLProps prop(curve,(firstParam+lastParam)*halving,0,Precision::Confusion());
|
|
mid = prop.Value();
|
|
|
|
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 sForWire = shape();
|
|
if (sForWire.ShapeType() == TopAbs_WIRE) {
|
|
return TopoDS::Wire(sForWire);
|
|
}
|
|
return BRepBuilderAPI_MakeWire(TopoDS::Edge(sForWire)).Wire();
|
|
}
|
|
};
|
|
|
|
template<class T>
|
|
struct VectorSet {
|
|
void sort()
|
|
{
|
|
if (!sorted) {
|
|
sorted = true;
|
|
std::sort(data.begin(), data.end());
|
|
}
|
|
}
|
|
bool contains(const T &vForContains)
|
|
{
|
|
if (!sorted) {
|
|
const size_t dataSizeMax = 30;
|
|
if (data.size() < dataSizeMax) {
|
|
return std::find(data.begin(), data.end(), vForContains) != data.end();
|
|
}
|
|
sort();
|
|
}
|
|
auto it = std::lower_bound(data.begin(), data.end(), vForContains);
|
|
return it!=data.end() && *it == vForContains;
|
|
}
|
|
bool intersects(const VectorSet<T> &other)
|
|
{
|
|
if (other.size() < size()) {
|
|
return other.intersects(*this);
|
|
}
|
|
if (!sorted) {
|
|
for (const auto &vector : data) {
|
|
if (other.contains(vector)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
other.sort();
|
|
auto it = other.data.begin();
|
|
for (const auto &vertex : data) {
|
|
it = std::lower_bound(it, other.data.end(), vertex);
|
|
if (it == other.data.end()) {
|
|
return false;
|
|
}
|
|
if (*it == vertex) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
void insert(const T &vToInsert)
|
|
{
|
|
if (sorted) {
|
|
data.insert(std::upper_bound(data.begin(), data.end(), vToInsert), vToInsert);
|
|
}
|
|
else {
|
|
data.push_back(vToInsert);
|
|
}
|
|
}
|
|
bool insertUnique(const T &vToInsertUnique)
|
|
{
|
|
if (sorted) {
|
|
auto it = std::lower_bound(data.begin(), data.end(), vToInsertUnique);
|
|
bool insert = !(it != data.end() && *it == vToInsertUnique);
|
|
if (insert) {
|
|
data.insert(it, vToInsertUnique);
|
|
}
|
|
return insert;
|
|
}
|
|
|
|
if (contains(vToInsertUnique)) {
|
|
return false;
|
|
}
|
|
data.push_back(vToInsertUnique);
|
|
return true;
|
|
}
|
|
void erase(const T &vToErase)
|
|
{
|
|
if (!sorted) {
|
|
data.erase(std::remove(data.begin(), data.end(), vToErase), data.end());
|
|
}
|
|
else {
|
|
auto it = std::lower_bound(data.begin(), data.end(), vToErase);
|
|
auto itEnd = it;
|
|
while (itEnd != data.end() && *itEnd == vToErase) {
|
|
++itEnd;
|
|
}
|
|
data.erase(it, itEnd);
|
|
}
|
|
const size_t dataSizeMax = 20;
|
|
if (data.size() < dataSizeMax) {
|
|
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;
|
|
|
|
using Edges = std::list<EdgeInfo>;
|
|
Edges edges {};
|
|
|
|
std::map<EdgeInfo*, Edges::iterator> edgesTable {};
|
|
|
|
struct VertexInfo {
|
|
Edges::iterator it {};
|
|
bool start {};
|
|
VertexInfo() = default;
|
|
VertexInfo(Edges::iterator it, bool start)
|
|
:it(it),start(start)
|
|
{}
|
|
VertexInfo reversed() const {
|
|
return {it, !start};
|
|
}
|
|
bool operator==(const VertexInfo &other) const {
|
|
return it==other.it && start==other.start;
|
|
}
|
|
bool operator<(const VertexInfo &other) const {
|
|
auto thisInfo = edgeInfo();
|
|
auto otherInfo = other.edgeInfo();
|
|
if (thisInfo < otherInfo) {
|
|
return true;
|
|
}
|
|
if (thisInfo > otherInfo) {
|
|
return false;
|
|
}
|
|
return static_cast<int>(start) < static_cast<int>(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;
|
|
explicit 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;
|
|
}
|
|
|
|
// Originally here there was a call to the precompiler macro assertCheck(), which has
|
|
// been replaced with the precompiler macro assert()
|
|
|
|
assert(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 vA, int vB) {
|
|
return vertices[vA] < vertices[vB];
|
|
});
|
|
}
|
|
int find(const VertexInfo &info) const
|
|
{
|
|
const size_t verticesSizeMax = 20;
|
|
if (vertices.size() < verticesSizeMax) {
|
|
auto it = std::find(vertices.begin(), vertices.end(), info);
|
|
if (it == vertices.end()) {
|
|
return 0;
|
|
}
|
|
return (static_cast<int>(it - vertices.begin()) + 1);
|
|
}
|
|
sort();
|
|
auto it = std::lower_bound(sorted.begin(), sorted.end(), info,
|
|
[&](int idx, const VertexInfo &vertex) {return vertices[idx]<vertex;});
|
|
int res = 0;
|
|
if (it != sorted.end() && vertices[*it] == info) {
|
|
res = *it + 1;
|
|
}
|
|
return res;
|
|
}
|
|
int find(const EdgeInfo *info) const
|
|
{
|
|
const size_t verticesSizeMax = 20;
|
|
if (vertices.size() < verticesSizeMax) {
|
|
for (auto it=vertices.begin(); it!=vertices.end(); ++it) {
|
|
if (it->edgeInfo() == info) {
|
|
return (static_cast<int>(it - vertices.begin()) + 1);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
sort();
|
|
auto it = std::lower_bound(sorted.begin(), sorted.end(), info,
|
|
[&](int idx, const EdgeInfo *vertex) {return vertices[idx].edgeInfo()<vertex;});
|
|
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 &vertex : other.vertices) {
|
|
if (vertex.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) const
|
|
{
|
|
if (wireInfo.box.IsVoid()) {
|
|
for (auto& vertex : wireInfo.vertices) {
|
|
BRepBndLib::Add(vertex.it->shape(), wireInfo.box);
|
|
}
|
|
wireInfo.box.Enlarge(myTol);
|
|
}
|
|
return wireInfo.box;
|
|
}
|
|
|
|
// This method was originally part of WireJoinerP::initWireInfo(), split to reduce cognitive
|
|
// complexity
|
|
bool initWireInfoWireClosed(const WireInfo& wireInfo)
|
|
{
|
|
if (!BRep_Tool::IsClosed(wireInfo.wire)) {
|
|
showShape(wireInfo.wire, "FailedToClose");
|
|
FC_ERR("Wire not closed");
|
|
for (auto& vertex : wireInfo.vertices) {
|
|
showShape(vertex.edgeInfo(), vertex.start ? "failed" : "failed_r", iteration);
|
|
}
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// This method was originally part of WireJoinerP::initWireInfo(), split to reduce cognitive
|
|
// complexity
|
|
bool initWireInfoFaceDone(WireInfo& wireInfo)
|
|
{
|
|
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 initWireInfo(WireInfo &wireInfo)
|
|
{
|
|
if (!wireInfo.face.IsNull()) {
|
|
return true;
|
|
}
|
|
getWireBound(wireInfo);
|
|
if (wireInfo.wire.IsNull()) {
|
|
wireData->Clear();
|
|
for (auto& vertex : wireInfo.vertices) {
|
|
wireData->Add(vertex.it->shape(vertex.start));
|
|
}
|
|
wireInfo.wire = makeCleanWire();
|
|
}
|
|
|
|
if (!initWireInfoWireClosed(wireInfo)) {
|
|
return false;
|
|
}
|
|
|
|
if (!initWireInfoFaceDone(wireInfo)) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool isInside(const WireInfo &wireInfo, gp_Pnt &pt) const
|
|
{
|
|
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) const
|
|
{
|
|
if (getWireBound(wireInfo).IsOut(pt)) {
|
|
return false;
|
|
}
|
|
BRepClass_FaceClassifier fc(wireInfo.face, pt, myTol);
|
|
return fc.State() == TopAbs_OUT;
|
|
}
|
|
|
|
struct PntGetter
|
|
{
|
|
using result_type = const gp_Pnt&;
|
|
result_type operator()(const VertexInfo &vInfo) const {
|
|
return vInfo.pt();
|
|
}
|
|
};
|
|
|
|
bgi::rtree<VertexInfo, RParameters, PntGetter> vmap {};
|
|
|
|
struct BoxGetter
|
|
{
|
|
using result_type = const Box&;
|
|
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 &eToAdd, bool queryBBox=false)
|
|
{
|
|
auto it = edges.begin();
|
|
return add(eToAdd, queryBBox, it);
|
|
}
|
|
|
|
int add(const TopoDS_Edge &eToAdd, bool queryBBox, Edges::iterator &it)
|
|
{
|
|
Box bbox;
|
|
if (!getBBox(eToAdd, bbox)) {
|
|
showShape(eToAdd, "small");
|
|
aHistory->Remove(eToAdd);
|
|
return 0;
|
|
}
|
|
return add(eToAdd, queryBBox, bbox, it) ? 1 : -1;
|
|
}
|
|
|
|
// This method was originally part of WireJoinerP::add(), split to reduce cognitive complexity
|
|
bool addNoDuplicates(const TopoDS_Edge& eToAdd,
|
|
TopoDS_Vertex& v2,
|
|
TopoDS_Edge& ev2,
|
|
const bool isLinear,
|
|
const VertexInfo& vinfo,
|
|
std::unique_ptr<Geometry>& geo)
|
|
{
|
|
if (v2.IsNull()) {
|
|
ev2 = vinfo.edge();
|
|
v2 = vinfo.otherVertex();
|
|
}
|
|
if (isLinear && vinfo.edgeInfo()->isLinear) {
|
|
showShape(eToAdd, "duplicate");
|
|
aHistory->Remove(eToAdd);
|
|
return false;
|
|
}
|
|
if (auto geoEdge = vinfo.edgeInfo()->geometry()) {
|
|
if (!geo) {
|
|
geo = Geometry::fromShape(eToAdd, /*silent*/ true);
|
|
}
|
|
if (geo && geo->isSame(*geoEdge, myTol, myAngularTol)) {
|
|
showShape(eToAdd, "duplicate");
|
|
aHistory->Remove(eToAdd);
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// This method was originally part of WireJoinerP::add(), split to reduce cognitive complexity
|
|
bool addValidEdges(const TopoDS_Edge& eToAdd,
|
|
const gp_Pnt p1,
|
|
const double tol,
|
|
TopoDS_Vertex& v1,
|
|
TopoDS_Edge& ev1,
|
|
const gp_Pnt p2,
|
|
TopoDS_Vertex& v2,
|
|
TopoDS_Edge& ev2,
|
|
const bool isLinear)
|
|
{
|
|
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 (!addNoDuplicates(eToAdd, v2, ev2, isLinear, vinfo, geo)){
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool add(const TopoDS_Edge &eToAdd, bool queryBBox, const Box &bbox, Edges::iterator &it)
|
|
{
|
|
gp_Pnt p1 = gp_Pnt();
|
|
gp_Pnt p2 = gp_Pnt();
|
|
getEndPoints(eToAdd,p1,p2);
|
|
TopoDS_Vertex v1 = TopoDS_Vertex();
|
|
TopoDS_Vertex v2 = TopoDS_Vertex();
|
|
TopoDS_Edge ev1 = TopoDS_Edge();
|
|
TopoDS_Edge ev2 = TopoDS_Edge();
|
|
double tol = myTol2;
|
|
// search for duplicate edges
|
|
showShape(eToAdd, "addcheck");
|
|
bool isLinear = TopoShape(eToAdd).isLinearEdge();
|
|
|
|
if (!addValidEdges(eToAdd, p1, tol, v1, ev1, p2, v2, ev2, isLinear)){
|
|
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& eCurrent,
|
|
const TopoDS_Vertex& vCurrent,
|
|
const TopoDS_Edge& eOther,
|
|
const TopoDS_Vertex& vOther) {
|
|
if (vOther.IsNull()) {
|
|
return;
|
|
}
|
|
if (vCurrent.IsSame(vOther)) {
|
|
return;
|
|
}
|
|
double tol = std::max(BRep_Tool::Pnt(vCurrent).Distance(BRep_Tool::Pnt(vOther)),
|
|
BRep_Tool::Tolerance(vOther));
|
|
if (tol >= BRep_Tool::Tolerance(vCurrent)) {
|
|
ShapeFix_ShapeTolerance fix;
|
|
const double halving {0.5};
|
|
fix.SetTolerance(vCurrent, std::max(tol*halving, myTol), TopAbs_VERTEX);
|
|
}
|
|
BRepBuilderAPI_MakeWire mkWire(eOther);
|
|
mkWire.Add(eCurrent);
|
|
auto newEdge = mkWire.Edge();
|
|
TopoDS_Vertex vFirst = TopExp::FirstVertex(newEdge);
|
|
TopoDS_Vertex vLast = TopExp::LastVertex(newEdge);
|
|
|
|
// Originally here there was a call to the precompiler macro assertCheck(), which has
|
|
// been replaced with the precompiler macro assert()
|
|
|
|
assert(vLast.IsSame(vOther) || vFirst.IsSame(vOther));
|
|
eCurrent = newEdge;
|
|
};
|
|
|
|
TopoDS_Edge edge = eToAdd;
|
|
TopoDS_Vertex vFirst = TopExp::FirstVertex(eToAdd);
|
|
TopoDS_Vertex vLast = TopExp::LastVertex(eToAdd);
|
|
connectEdge(edge, vFirst, ev1, v1);
|
|
connectEdge(edge, vLast, ev2, v2);
|
|
if (!edge.IsSame(eToAdd)) {
|
|
auto itSource = sourceEdges.find(eToAdd);
|
|
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 method was originally part of WireJoinerP::join(), split to reduce cognitive complexity
|
|
void joinMakeWire(const int idx,
|
|
BRepBuilderAPI_MakeWire& mkWire,
|
|
const Edges::iterator it,
|
|
bool& done)
|
|
{
|
|
double tol = myTol2;
|
|
gp_Pnt pstart(it->p1);
|
|
gp_Pnt pend(it->p2);
|
|
while (!edges.empty()) {
|
|
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));
|
|
|
|
// Originally here there was a call to the precompiler macro assertCheck(),
|
|
// which has been replaced with the precompiler macro assert()
|
|
|
|
assert(ret.size() == 1);
|
|
double dist = ret[0].pt().SquareDistance(pt);
|
|
if (dist > tol) {
|
|
break;
|
|
}
|
|
|
|
const auto& info = *ret[0].it;
|
|
bool start = ret[0].start;
|
|
if (dist > Precision::SquareConfusion()) {
|
|
// insert a filling edge to solve the tolerance problem
|
|
const gp_Pnt& pt = ret[idx].pt();
|
|
if (idx != 0) {
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
//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()
|
|
{
|
|
while (!edges.empty()) {
|
|
auto it = edges.begin();
|
|
BRepBuilderAPI_MakeWire mkWire;
|
|
mkWire.Add(it->edge);
|
|
remove(it);
|
|
|
|
bool done = false;
|
|
for (int idx=0;!done&&idx<2;++idx) {
|
|
joinMakeWire(idx, mkWire, it, done);
|
|
}
|
|
|
|
builder.Add(compound,mkWire.Wire());
|
|
}
|
|
}
|
|
|
|
struct IntersectInfo {
|
|
double param;
|
|
TopoDS_Shape intersectShape;
|
|
gp_Pnt point;
|
|
IntersectInfo(double pToIntersect, const gp_Pnt& pt, TopoDS_Shape sToIntersect)
|
|
: param(pToIntersect)
|
|
, intersectShape(std::move(sToIntersect))
|
|
, point(pt)
|
|
{}
|
|
bool operator<(const IntersectInfo &other) const {
|
|
return param < other.param;
|
|
}
|
|
};
|
|
|
|
void checkSelfIntersection(const EdgeInfo &info, std::set<IntersectInfo> ¶ms) const
|
|
{
|
|
// 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;
|
|
}
|
|
const TopoDS_Face& face = mkFace.Face();
|
|
ShapeAnalysis_Wire analysis(wire, face, myTol);
|
|
analysis.CheckSelfIntersectingEdge(1, points2d, points3d);
|
|
|
|
// Originally here there was a call to the precompiler macro assertCheck(), which has been
|
|
// replaced with the precompiler macro assert()
|
|
|
|
assert(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);
|
|
}
|
|
}
|
|
|
|
// This method was originally part of WireJoinerP::checkIntersection(), split to reduce
|
|
// cognitive complexity
|
|
bool checkIntersectionPlanar(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 false;
|
|
}
|
|
}
|
|
for (int i = 1; i <= extss.NbSolution(); ++i) {
|
|
Standard_Real par = Standard_Real();
|
|
auto s1 = extss.SupportOnShape1(i);
|
|
auto s2 = extss.SupportOnShape2(i);
|
|
if (s1.ShapeType() == TopAbs_EDGE) {
|
|
extss.ParOnEdgeS1(i, par);
|
|
pushIntersection(params1, par, extss.PointOnShape1(i), other.edge);
|
|
}
|
|
if (s2.ShapeType() == TopAbs_EDGE) {
|
|
extss.ParOnEdgeS2(i, par);
|
|
pushIntersection(params2, par, extss.PointOnShape2(i), info.edge);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// This method was originally part of WireJoinerP::checkIntersection(), split to reduce
|
|
// cognitive complexity
|
|
static bool checkIntersectionEdgeDone(const BRepBuilderAPI_MakeEdge& mkEdge)
|
|
{
|
|
if (!mkEdge.IsDone()) {
|
|
if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) {
|
|
FC_WARN("Failed to build edge for checking intersection");
|
|
}
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// This method was originally part of WireJoinerP::checkIntersection(), split to reduce
|
|
// cognitive complexity
|
|
static bool checkIntersectionWireDone(const BRepBuilderAPI_MakeWire& mkWire)
|
|
{
|
|
if (!mkWire.IsDone()) {
|
|
if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) {
|
|
FC_WARN("Failed to build wire for checking intersection");
|
|
}
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// This method was originally part of WireJoinerP::checkIntersection(), split to reduce
|
|
// cognitive complexity
|
|
static bool checkIntersectionMakeWire(const EdgeInfo& info,
|
|
const EdgeInfo& other,
|
|
int& idx,
|
|
TopoDS_Wire& wire)
|
|
{
|
|
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 (!checkIntersectionEdgeDone(mkEdge)) {
|
|
return false;
|
|
}
|
|
|
|
mkWire.Add(mkEdge.Edge());
|
|
mkWire.Add(other.edge);
|
|
}
|
|
|
|
if (!checkIntersectionWireDone(mkWire)) {
|
|
return false;
|
|
}
|
|
|
|
wire = mkWire.Wire();
|
|
if (!BRep_Tool::IsClosed(wire)) {
|
|
gp_Pnt p1 = gp_Pnt();
|
|
gp_Pnt p2 = gp_Pnt();
|
|
getEndPoints(wire, p1, p2);
|
|
BRepBuilderAPI_MakeEdge mkEdge(p1, p2);
|
|
|
|
if (!checkIntersectionEdgeDone(mkEdge)) {
|
|
return false;
|
|
}
|
|
|
|
mkWire.Add(mkEdge.Edge());
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// This method was originally part of WireJoinerP::checkIntersection(), split to reduce
|
|
// cognitive complexity
|
|
static bool checkIntersectionFaceDone(const BRepBuilderAPI_MakeFace& mkFace)
|
|
{
|
|
if (!mkFace.IsDone()) {
|
|
if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) {
|
|
FC_WARN("Failed to build face for checking intersection");
|
|
}
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void checkIntersection(const EdgeInfo &info,
|
|
const EdgeInfo &other,
|
|
std::set<IntersectInfo> ¶ms1,
|
|
std::set<IntersectInfo> ¶ms2)
|
|
{
|
|
if(!checkIntersectionPlanar(info, other, params1, params2)){
|
|
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 = 0;
|
|
|
|
if (!checkIntersectionMakeWire(info, other, idx, wire)){
|
|
return;
|
|
}
|
|
|
|
BRepBuilderAPI_MakeFace mkFace(wire);
|
|
if (!checkIntersectionFaceDone(mkFace)) {
|
|
return;
|
|
}
|
|
|
|
const TopoDS_Face& face = mkFace.Face();
|
|
ShapeAnalysis_Wire analysis(wire, face, myTol);
|
|
analysis.CheckIntersectingEdges(1, idx, points2d, points3d, errors);
|
|
|
|
// Originally here there was a call to the precompiler macro assertCheck(), which has been
|
|
// replaced with the precompiler macro assert()
|
|
|
|
assert(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);
|
|
}
|
|
|
|
struct SplitInfo {
|
|
TopoDS_Edge edge;
|
|
TopoDS_Shape intersectShape;
|
|
Box bbox;
|
|
};
|
|
|
|
// This method was originally part of WireJoinerP::splitEdges(), split to reduce cognitive
|
|
// complexity
|
|
void splitEdgesMakeEdge(const std::set<IntersectInfo>::iterator& itParam,
|
|
const EdgeInfo& info,
|
|
std::vector<SplitInfo>& splits,
|
|
std::set<IntersectInfo>::iterator& itPrevParam,
|
|
const TopoDS_Shape& intersectShape)
|
|
{
|
|
// 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()) {
|
|
splits.emplace_back();
|
|
auto& entry = splits.back();
|
|
entry.edge = mkEdge.Edge();
|
|
entry.intersectShape = intersectShape;
|
|
if (getBBox(entry.edge, entry.bbox)) {
|
|
itPrevParam = itParam;
|
|
}
|
|
else {
|
|
splits.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());
|
|
}
|
|
}
|
|
|
|
// This method was originally part of WireJoinerP::splitEdges(), split to reduce cognitive
|
|
// complexity
|
|
void splitEdgesMakeEdges(std::set<IntersectInfo>::iterator& itParam,
|
|
const std::set<IntersectInfo>& params,
|
|
const EdgeInfo& info,
|
|
std::vector<SplitInfo>& splits)
|
|
{
|
|
for (auto itPrevParam = itParam++; itParam != params.end(); ++itParam) {
|
|
const auto& intersectShape = itParam->intersectShape.IsNull()
|
|
? itPrevParam->intersectShape
|
|
: itParam->intersectShape;
|
|
if (intersectShape.IsNull()) {
|
|
break;
|
|
}
|
|
|
|
splitEdgesMakeEdge(itParam, info, splits, itPrevParam, intersectShape);
|
|
}
|
|
}
|
|
|
|
// Try splitting any edges that intersects other edge
|
|
void splitEdges()
|
|
{
|
|
std::unordered_map<const EdgeInfo*, std::set<IntersectInfo>> intersects;
|
|
|
|
int idx=0;
|
|
for (auto& info : edges) {
|
|
info.iteration = ++idx;
|
|
}
|
|
|
|
std::unique_ptr<Base::SequencerLauncher> seq(
|
|
new Base::SequencerLauncher("Splitting edges", edges.size()));
|
|
|
|
idx = 0;
|
|
for (auto& info : edges) {
|
|
seq->next(true);
|
|
++idx;
|
|
auto ¶ms = intersects[&info];
|
|
checkSelfIntersection(info, params);
|
|
|
|
for (auto vit=boxMap.qbegin(bgi::intersects(info.box)); vit!=boxMap.qend(); ++vit) {
|
|
const auto &other = *(*vit);
|
|
if (other.iteration <= idx) {
|
|
// means the edge is before us, and we've already checked intersection
|
|
continue;
|
|
}
|
|
checkIntersection(info, other, params, intersects[&other]);
|
|
}
|
|
}
|
|
|
|
idx=0;
|
|
std::vector<SplitInfo> splits;
|
|
for (auto it=edges.begin(); it!=edges.end(); ) {
|
|
++idx;
|
|
auto iter = intersects.find(&(*it));
|
|
if (iter == intersects.end()) {
|
|
++it;
|
|
continue;
|
|
}
|
|
auto &info = *it;
|
|
auto ¶ms = iter->second;
|
|
if (params.empty()) {
|
|
++it;
|
|
continue;
|
|
}
|
|
|
|
auto itParam = params.begin();
|
|
if (itParam->point.SquareDistance(info.p1) < myTol2) {
|
|
params.erase(itParam);
|
|
}
|
|
params.emplace(info.firstParam, info.p1, TopoDS_Shape());
|
|
itParam = params.end();
|
|
--itParam;
|
|
if (itParam->point.SquareDistance(info.p2) < myTol2) {
|
|
params.erase(itParam);
|
|
}
|
|
params.emplace(info.lastParam, info.p2, TopoDS_Shape());
|
|
|
|
if (params.size() <= 2) {
|
|
++it;
|
|
continue;
|
|
}
|
|
|
|
splits.clear();
|
|
itParam = params.begin();
|
|
|
|
splitEdgesMakeEdges(itParam, params, info, splits);
|
|
|
|
if (splits.size() <= 1) {
|
|
++it;
|
|
continue;
|
|
}
|
|
|
|
showShape(info.edge, "remove");
|
|
auto removedEdge = info.edge;
|
|
it = remove(it);
|
|
for (const auto& split : splits) {
|
|
if (!add(split.edge, false, split.bbox, it)) {
|
|
continue;
|
|
}
|
|
auto &newInfo = *it++;
|
|
aHistory->AddModified(split.intersectShape, newInfo.edge);
|
|
// if (v.intersectShape != removedEdge)
|
|
// aHistory->AddModified(removedEdge, newInfo.edge);
|
|
showShape(newInfo.edge, "split");
|
|
}
|
|
}
|
|
}
|
|
|
|
// This method was originally part of WireJoinerP::findSuperEdges(), split to reduce cognitive
|
|
// complexity
|
|
void findSuperEdgeFromAdjacent(std::deque<VertexInfo>& vertices, const int direction)
|
|
{
|
|
bool done = false;
|
|
auto begin = direction == 1 ? vertices.back().reversed() : vertices.front();
|
|
while (true) {
|
|
auto currentVertex = direction == 1 ? vertices.front() : vertices.back();
|
|
auto current = currentVertex.edgeInfo();
|
|
// showShape(current, "siter", k);
|
|
const int idx = (currentVertex.start ? 1 : 0) ^ direction;
|
|
EdgeInfo* found = nullptr;
|
|
for (int i = current->iStart[idx]; i < current->iEnd[idx]; ++i) {
|
|
const auto& vertex = adjacentList[i];
|
|
auto next = vertex.edgeInfo();
|
|
if (next->iteration < 0 // skipped
|
|
|| next == current) { // skip self (see how adjacent list is built)
|
|
continue;
|
|
}
|
|
if (vertex == begin) {
|
|
// closed
|
|
done = true;
|
|
break;
|
|
}
|
|
if (found // more than one branch
|
|
|| edgeSet.contains(next)) // or, self intersect
|
|
{
|
|
|
|
// Originally here there were some lines of code that have been removed
|
|
// as them are commented out.
|
|
// See
|
|
// https://github.com/realthunder/FreeCAD/blob/6f15849be2505f98927e75d0e8352185e14e7b72/src/Mod/Part/App/WireJoiner.cpp#L1141
|
|
// for reference.
|
|
|
|
found = nullptr;
|
|
break;
|
|
}
|
|
found = next;
|
|
currentVertex = vertex;
|
|
}
|
|
if (done || !found) {
|
|
break;
|
|
}
|
|
// showShape(found, "snext", k);
|
|
if (direction == 1) {
|
|
edgeSet.insert(current);
|
|
vertices.push_front(currentVertex.reversed());
|
|
}
|
|
else {
|
|
edgeSet.insert(found);
|
|
vertices.push_back(currentVertex);
|
|
}
|
|
}
|
|
}
|
|
|
|
// This method was originally part of WireJoinerP::findSuperEdges(), split to reduce cognitive
|
|
// complexity
|
|
void findSuperEdge(std::deque<VertexInfo>& vertices, const Edges::iterator it)
|
|
{
|
|
vertices.clear();
|
|
vertices.emplace_back(it, true);
|
|
edgeSet.clear();
|
|
|
|
for (int direction = 0; direction < 2; ++direction) { // search in both direction
|
|
findSuperEdgeFromAdjacent(vertices, direction);
|
|
}
|
|
}
|
|
|
|
// This method was originally part of WireJoinerP::findSuperEdges(), split to reduce cognitive
|
|
// complexity
|
|
void findSuperEdgesUpdateFirst(std::deque<VertexInfo> vertices)
|
|
{
|
|
Bnd_Box bbox;
|
|
for (const auto& vertex : vertices) {
|
|
auto current = vertex.edgeInfo();
|
|
bbox.Add(current->box.min_corner());
|
|
bbox.Add(current->box.max_corner());
|
|
wireData->Add(current->shape(vertex.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();
|
|
const 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& vertex = adjacentList[i];
|
|
if (vertex.it == vLast.it) {
|
|
vertex.it = vFirst.it;
|
|
vertex.start = !vFirst.start;
|
|
}
|
|
}
|
|
bbox.Enlarge(myTol);
|
|
first->box = Box(bbox.CornerMin(), bbox.CornerMax());
|
|
}
|
|
}
|
|
|
|
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");
|
|
|
|
findSuperEdge(vertices, it);
|
|
|
|
if (vertices.size() <= 1) {
|
|
continue;
|
|
}
|
|
|
|
wireData->Clear();
|
|
|
|
findSuperEdgesUpdateFirst(vertices);
|
|
}
|
|
}
|
|
|
|
void buildAdjacentListPopulate()
|
|
{
|
|
// populate adjacent list
|
|
for (auto& info : edges) {
|
|
if (info.iteration == -2) {
|
|
|
|
// Originally there was the following precompiler directive around assertCheck():
|
|
// #if OCC_VERSION_HEX >= 0x070000
|
|
// The precompiler directive has been removed as the minimum OCCT version supported
|
|
// is 7.3.0 and the precompiler macro has been replaced with assert()
|
|
|
|
assert(BRep_Tool::IsClosed(info.shape()));
|
|
|
|
showShape(&info, "closed");
|
|
if (!doTightBound) {
|
|
builder.Add(compound, info.wire());
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (info.iteration < 0) {
|
|
continue;
|
|
}
|
|
|
|
if (info.p1.SquareDistance(info.p2) <= myTol2) {
|
|
if (!doTightBound) {
|
|
builder.Add(compound, info.wire());
|
|
}
|
|
info.iteration = -2;
|
|
continue;
|
|
}
|
|
|
|
std::array<gp_Pnt, 2> pt {};
|
|
pt[0] = info.p1;
|
|
pt[1] = info.p2;
|
|
for (int i = 0; i < 2; ++i) {
|
|
const int ic = i;
|
|
if (info.iStart[ic] >= 0) {
|
|
continue;
|
|
}
|
|
info.iEnd[ic] = info.iStart[ic] = (int)adjacentList.size();
|
|
|
|
for (auto vit = vmap.qbegin(bgi::nearest(pt[ic], INT_MAX)); vit != vmap.qend();
|
|
++vit) {
|
|
auto& vinfo = *vit;
|
|
if (vinfo.pt().SquareDistance(pt[ic]) > 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[ic];
|
|
}
|
|
|
|
// copy the adjacent indices to all connected edges
|
|
for (int j = info.iStart[ic]; j < info.iEnd[ic]; ++j) {
|
|
auto& other = adjacentList[j];
|
|
auto& otherInfo = *other.it;
|
|
if (&otherInfo != &info) {
|
|
const int kc = other.start ? 0 : 1;
|
|
otherInfo.iStart[kc] = info.iStart[ic];
|
|
otherInfo.iEnd[kc] = info.iEnd[ic];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void buildAdjacentListSkipEdges()
|
|
{
|
|
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) {
|
|
const int kc = k;
|
|
int idx = 0;
|
|
for (idx = info.iStart[kc]; idx < info.iEnd[kc]; ++idx) {
|
|
const auto& vertex = adjacentList[idx];
|
|
auto other = vertex.edgeInfo();
|
|
if (other->iteration >= 0 && other != &info) {
|
|
break;
|
|
}
|
|
}
|
|
if (idx == info.iEnd[kc]) {
|
|
// If merge or tight bound, then repeat until no edges
|
|
// can be skipped.
|
|
done = !doMergeEdge && !doTightBound;
|
|
info.iteration = -3;
|
|
showShape(&info, "skip");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void buildAdjacentList()
|
|
{
|
|
builder.MakeCompound(compound);
|
|
|
|
for (auto& info : edges) {
|
|
info.reset();
|
|
}
|
|
|
|
adjacentList.clear();
|
|
|
|
buildAdjacentListPopulate();
|
|
|
|
buildAdjacentListSkipEdges();
|
|
}
|
|
|
|
// 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) {
|
|
|
|
// Originally here there was a call to the precompiler macro assertCheck(), which
|
|
// has been replaced with the precompiler macro assert()
|
|
|
|
assert(!beginInfo.wireInfo);
|
|
beginInfo.wireInfo.reset(new WireInfo());
|
|
beginInfo.wireInfo->vertices.emplace_back(it, true);
|
|
beginInfo.wireInfo->wire = wire;
|
|
}
|
|
for (auto &entry : stack) {
|
|
const auto &vertex = vertexStack[entry.iCurrent];
|
|
auto &info = *vertex.it;
|
|
if (tightBound) {
|
|
beginInfo.wireInfo->vertices.push_back(vertex);
|
|
}
|
|
if (!info.wireInfo) {
|
|
info.wireInfo = beginInfo.wireInfo;
|
|
// showShape(&info, "visited");
|
|
}
|
|
}
|
|
showShape(wire,"joined");
|
|
if (!tightBound) {
|
|
builder.Add(compound, wire);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Originally here there was the definition of the method checkStack(), which does nothing and
|
|
// therefor has been removed. See
|
|
// https://github.com/realthunder/FreeCAD/blob/6f15849be2505f98927e75d0e8352185e14e7b72/src/Mod/Part/App/WireJoiner.cpp#L1366
|
|
// for reference
|
|
|
|
void checkWireInfo(const WireInfo &wireInfo)
|
|
{
|
|
(void)wireInfo;
|
|
if (FC_LOG_INSTANCE.level() <= FC_LOGLEVEL_TRACE) {
|
|
return;
|
|
}
|
|
int idx = 0;
|
|
for (auto &info : edges) {
|
|
++idx;
|
|
if (auto wire = info.wireInfo.get()) {
|
|
|
|
// Originally here there was a call to the precompiler macro assertCheck(), which
|
|
// has been replaced with the precompiler macro assert()
|
|
|
|
assert(wire->vertices.front().edgeInfo()->wireInfo.get() == wire);
|
|
}
|
|
}
|
|
}
|
|
|
|
// This method was originally part of WireJoinerP::_findClosedWires(), split to reduce cognitive
|
|
// complexity
|
|
void _findClosedWiresBeginEdge(const std::shared_ptr<WireInfo>& wireInfo,
|
|
const EdgeInfo& beginInfo,
|
|
int& idx)
|
|
{
|
|
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;
|
|
|
|
// Originally here there was a call to the method checkStack(),
|
|
// which does nothing and therefor has been removed.
|
|
}
|
|
}
|
|
}
|
|
|
|
// This method was originally part of WireJoinerP::_findClosedWires(), split to reduce cognitive
|
|
// complexity
|
|
int _findClosedWiresWithExisting(int* idxVertex,
|
|
const std::shared_ptr<WireInfo>& wireInfo,
|
|
int* const stackPos,
|
|
bool& proceed,
|
|
const VertexInfo& vinfo,
|
|
const int ic,
|
|
StackInfo& stackBack,
|
|
const EdgeInfo& beginInfo,
|
|
EdgeInfo& info)
|
|
{
|
|
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.
|
|
|
|
int idx = wireInfo->find(vinfo);
|
|
|
|
if (idx != 0) {
|
|
vertexStack.push_back(adjacentList[ic]);
|
|
stackBack.iCurrent = stackBack.iEnd++;
|
|
--idx;
|
|
proceed = false;
|
|
if (idxVertex) {
|
|
*idxVertex = idx;
|
|
}
|
|
if (stackPos) {
|
|
*stackPos = (int)stack.size() - 2;
|
|
}
|
|
|
|
_findClosedWiresBeginEdge(wireInfo, beginInfo, idx);
|
|
|
|
return 1;
|
|
}
|
|
|
|
if (wireInfo->find(VertexInfo(vinfo.it, !vinfo.start)) != 0) {
|
|
showShape(&info, "rintersect", iteration);
|
|
// Only used when exhausting tight bound.
|
|
wireInfo->purge = true;
|
|
return 2;
|
|
}
|
|
|
|
if (isOutside(*wireInfo, info.mid)) {
|
|
showShape(&info, "outside", iteration);
|
|
return 2;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// This method was originally part of WireJoinerP::_findClosedWires(), split to reduce cognitive
|
|
// complexity
|
|
void _findClosedWiresUpdateStack(int* idxVertex,
|
|
const std::shared_ptr<WireInfo>& wireInfo,
|
|
int* stackPos,
|
|
const EdgeInfo* currentInfo,
|
|
const int currentIdx,
|
|
bool& proceed,
|
|
const EdgeInfo& beginInfo)
|
|
{
|
|
auto& stackBack = stack.back();
|
|
|
|
// 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.
|
|
stackBack.iEnd = stackBack.iStart;
|
|
vertexStack.resize(size);
|
|
break;
|
|
}
|
|
|
|
if (abort || currentInfo->wireInfo2) {
|
|
if (wireInfo) {
|
|
wireInfo->purge = true;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (info.iteration == iteration) {
|
|
continue;
|
|
}
|
|
info.iteration = iteration;
|
|
|
|
int exists = _findClosedWiresWithExisting(idxVertex,
|
|
wireInfo,
|
|
stackPos,
|
|
proceed,
|
|
vinfo,
|
|
i,
|
|
stackBack,
|
|
beginInfo,
|
|
info);
|
|
|
|
if (exists == 1) {
|
|
break;
|
|
}
|
|
|
|
if (exists == 2) {
|
|
continue;
|
|
}
|
|
|
|
vertexStack.push_back(adjacentList[i]);
|
|
++stackBack.iEnd;
|
|
}
|
|
}
|
|
|
|
// This method was originally part of WireJoinerP::_findClosedWires(), split to reduce cognitive
|
|
// complexity
|
|
bool _findClosedWiresUpdateEdges(VertexInfo& currentVertex,
|
|
gp_Pnt& pend,
|
|
EdgeInfo* currentInfo,
|
|
int& currentIdx,
|
|
const size_t stackEnd)
|
|
{
|
|
while (true) {
|
|
auto& stackBack = stack.back();
|
|
if (stackBack.iCurrent < stackBack.iEnd) {
|
|
// now pick one edge from stack.back(), connect it to
|
|
// pend, then extend pend
|
|
currentVertex = vertexStack[stackBack.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() + static_cast<long>(stackBack.iStart), vertexStack.end());
|
|
|
|
stack.pop_back();
|
|
if (stack.size() == stackEnd) {
|
|
// If stack reaches the end, it means this wire is open.
|
|
return true;
|
|
}
|
|
|
|
auto& lastInfo = *vertexStack[stack.back().iCurrent].it;
|
|
edgeSet.erase(&lastInfo);
|
|
wireSet.erase(lastInfo.wireInfo.get());
|
|
showShape(&lastInfo, "pop", iteration);
|
|
++stack.back().iCurrent;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// This method was originally part of WireJoinerP::_findClosedWires(), split to reduce cognitive
|
|
// complexity
|
|
bool _findClosedWiresIsClosed(const VertexInfo& beginVertex,
|
|
const TopoDS_Wire& wire,
|
|
const EdgeInfo& beginInfo)
|
|
{
|
|
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& entry : stack) {
|
|
const auto& vertex = vertexStack[entry.iCurrent];
|
|
auto& info = *vertex.it;
|
|
showShape(info.shape(vertex.start), vertex.start ? "failed" : "failed_r", iteration);
|
|
}
|
|
|
|
// Originally here there was a call to the precompiler macro assertCheck(), which
|
|
// has been replaced with the precompiler macro assert()
|
|
|
|
assert(false);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
TopoDS_Wire _findClosedWires(VertexInfo beginVertex,
|
|
VertexInfo currentVertex,
|
|
int *idxVertex = nullptr,
|
|
const std::shared_ptr<WireInfo>& wireInfo = std::shared_ptr<WireInfo>(),
|
|
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();
|
|
|
|
// Originally here there was a call to the method checkStack(), which does nothing and
|
|
// therefor has been removed.
|
|
|
|
// 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());
|
|
showShape(currentInfo, "check", iteration);
|
|
|
|
bool proceed = true;
|
|
|
|
_findClosedWiresUpdateStack(idxVertex,
|
|
wireInfo,
|
|
stackPos,
|
|
currentInfo,
|
|
currentIdx,
|
|
proceed,
|
|
beginInfo);
|
|
|
|
// Originally here there was a call to the method checkStack(), which does nothing and
|
|
// therefor has been removed.
|
|
|
|
if (proceed) {
|
|
if (_findClosedWiresUpdateEdges(currentVertex,
|
|
pend,
|
|
currentInfo,
|
|
currentIdx,
|
|
stackEnd)) {
|
|
return {};
|
|
}
|
|
|
|
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 &entry : stack) {
|
|
const auto &vertex = vertexStack[entry.iCurrent];
|
|
auto &info = *vertex.it;
|
|
wireData->Add(info.shape(vertex.start));
|
|
}
|
|
TopoDS_Wire wire = makeCleanWire();
|
|
if (!_findClosedWiresIsClosed(beginVertex, wire, beginInfo)) {
|
|
continue;
|
|
}
|
|
return wire;
|
|
}
|
|
}
|
|
|
|
// This method was originally part of WireJoinerP::findTightBound(), split to reduce cognitive
|
|
// complexity
|
|
void findTightBoundSplitWire(const std::shared_ptr<WireInfo>& wireInfo,
|
|
const EdgeInfo& beginInfo,
|
|
const std::vector<VertexInfo>& wireVertices,
|
|
std::shared_ptr<WireInfo>& splitWire,
|
|
const int idxV,
|
|
int& idxStart,
|
|
const int idxEnd)
|
|
{
|
|
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);
|
|
|
|
// Originally here there was a call to the precompiler macro
|
|
// assertCheck(), which has been replaced with the precompiler macro
|
|
// assert()
|
|
|
|
assert(info != &beginInfo);
|
|
}
|
|
if (info->wireInfo == wireInfo) {
|
|
if (!splitWire) {
|
|
idxStart = idx;
|
|
splitWire.reset(new WireInfo());
|
|
}
|
|
info->wireInfo = splitWire;
|
|
}
|
|
}
|
|
}
|
|
|
|
// This method was originally part of WireJoinerP::findTightBound(), split to reduce cognitive
|
|
// complexity
|
|
void findTightBoundWithSplit(const std::vector<VertexInfo>& wireVertices,
|
|
const int idxV,
|
|
const std::shared_ptr<WireInfo>& splitWire,
|
|
const int idxStart,
|
|
const int idxEnd,
|
|
const int stackPos,
|
|
const int stackStart)
|
|
{
|
|
auto& splitEdges = splitWire->vertices;
|
|
gp_Pnt pstart;
|
|
gp_Pnt pt;
|
|
bool first = true;
|
|
for (int idx = idxStart; idx != idxEnd; ++idx) {
|
|
auto& vertex = wireVertices[idx];
|
|
if (first) {
|
|
first = false;
|
|
pstart = vertex.pt();
|
|
}
|
|
else {
|
|
|
|
// Originally here there was a call to the precompiler macro
|
|
// assertCheck(), which has been replaced with the precompiler
|
|
// macro assert()
|
|
|
|
assert(pt.SquareDistance(vertex.pt()) < myTol2);
|
|
}
|
|
pt = vertex.ptOther();
|
|
splitEdges.push_back(vertex);
|
|
}
|
|
for (int i = stackPos; i >= stackStart; --i) {
|
|
const auto& vertex = vertexStack[stack[i].iCurrent];
|
|
|
|
// Originally here there was a call to the precompiler macro
|
|
// assertCheck(), which has been replaced with the precompiler macro
|
|
// assert()
|
|
|
|
assert(pt.SquareDistance(vertex.ptOther()) < myTol2);
|
|
pt = vertex.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(vertex.reversed());
|
|
}
|
|
for (int idx = idxV; idx != idxStart; ++idx) {
|
|
auto& vertex = wireVertices[idx];
|
|
|
|
// Originally here there was a call to the precompiler macro
|
|
// assertCheck(), which has been replaced with the precompiler macro
|
|
// assert()
|
|
|
|
assert(pt.SquareDistance(vertex.pt()) < myTol2);
|
|
pt = vertex.ptOther();
|
|
splitEdges.push_back(vertex);
|
|
}
|
|
|
|
// Originally here there was a call to the precompiler macro
|
|
// assertCheck(), which has been replaced with the precompiler macro
|
|
// assert()
|
|
|
|
assert(pt.SquareDistance(pstart) < myTol2);
|
|
showShape(*splitWire, "swire", iteration);
|
|
}
|
|
|
|
// This method was originally part of WireJoinerP::findTightBound(), split to reduce cognitive
|
|
// complexity
|
|
void findTightBoundByVertices(EdgeInfo& beginInfo,
|
|
const std::vector<VertexInfo>& wireVertices,
|
|
int& idxV,
|
|
const int iteration2,
|
|
const gp_Pnt& pstart,
|
|
const std::shared_ptr<WireInfo>& wireInfo,
|
|
const VertexInfo& beginVertex,
|
|
std::shared_ptr<WireInfo>& newWire)
|
|
{
|
|
const int idx = wireVertices[idxV].start ? 1 : 0;
|
|
|
|
auto current = wireVertices[idxV].edgeInfo();
|
|
showShape(current, "current", iteration);
|
|
|
|
for (int vertex = current->iStart[idx]; vertex < current->iEnd[idx]; ++vertex) {
|
|
const auto& currentVertex = adjacentList[vertex];
|
|
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);
|
|
|
|
// Originally here there was a call to the method checkStack(), which does
|
|
// nothing and therefor has been removed.
|
|
|
|
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,
|
|
&idxEnd,
|
|
beginInfo.wireInfo,
|
|
&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& entry : stack) {
|
|
const auto& vertex = vertexStack[entry.iCurrent];
|
|
newWireVertices.push_back(vertex);
|
|
}
|
|
if (!wire.IsNull()) {
|
|
newWire->wire = wire;
|
|
}
|
|
else if (!initWireInfo(*newWire)) {
|
|
newWire.reset();
|
|
vertexStack.pop_back();
|
|
stack.pop_back();
|
|
edgeSet.erase(next);
|
|
continue;
|
|
}
|
|
for (auto& vertex : newWire->vertices) {
|
|
if (vertex.edgeInfo()->wireInfo == wireInfo) {
|
|
vertex.edgeInfo()->wireInfo = newWire;
|
|
}
|
|
}
|
|
beginInfo.wireInfo = newWire;
|
|
showShape(*newWire, "nwire", iteration);
|
|
|
|
std::shared_ptr<WireInfo> splitWire;
|
|
if (idxEnd == 0) {
|
|
idxEnd = (int)wireVertices.size();
|
|
}
|
|
++idxV;
|
|
|
|
// Originally here there was a call to the precompiler macro assertCheck(),
|
|
// which has been replaced with the precompiler macro assert()
|
|
|
|
assert(idxV <= idxEnd);
|
|
int idxStart = idxV;
|
|
|
|
findTightBoundSplitWire(wireInfo,
|
|
beginInfo,
|
|
wireVertices,
|
|
splitWire,
|
|
idxV,
|
|
idxStart,
|
|
idxEnd);
|
|
|
|
if (splitWire) {
|
|
findTightBoundWithSplit(wireVertices,
|
|
idxV,
|
|
splitWire,
|
|
idxStart,
|
|
idxEnd,
|
|
stackPos,
|
|
stackStart);
|
|
}
|
|
|
|
checkWireInfo(*newWire);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// This method was originally part of WireJoinerP::findTightBound(), split to reduce cognitive
|
|
// complexity
|
|
void findTightBoundUpdateVertices(EdgeInfo& beginInfo)
|
|
{
|
|
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& vertex : beginInfo.wireInfo->vertices) {
|
|
auto info = vertex.edgeInfo();
|
|
if (!info->wireInfo) {
|
|
info->wireInfo = beginInfo.wireInfo;
|
|
continue;
|
|
}
|
|
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());
|
|
}
|
|
}
|
|
|
|
// Originally here there was a call to the precompiler macro assertCheck(),
|
|
// which has been replaced with the precompiler macro assert()
|
|
|
|
assert(info != &beginInfo);
|
|
info->wireInfo = beginInfo.wireInfo;
|
|
checkWireInfo(*otherWire);
|
|
}
|
|
checkWireInfo(*beginInfo.wireInfo);
|
|
}
|
|
|
|
void findTightBound()
|
|
{
|
|
// Assumption: all edges lies on a common manifold surface
|
|
//
|
|
// Definition of 'Tight Bound': a wire that cannot be split 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& vertex : wireVertices) {
|
|
vertex.it->iteration2 = iteration2;
|
|
}
|
|
|
|
stack.clear();
|
|
vertexStack.clear();
|
|
edgeSet.clear();
|
|
|
|
std::shared_ptr<WireInfo> newWire;
|
|
gp_Pnt pstart = beginVertex.pt();
|
|
|
|
int idxV = 0;
|
|
while (true) {
|
|
findTightBoundByVertices(beginInfo,
|
|
wireVertices,
|
|
idxV,
|
|
iteration2,
|
|
pstart,
|
|
wireInfo,
|
|
beginVertex,
|
|
newWire);
|
|
|
|
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());
|
|
|
|
// Originally here there was a call to the method checkStack(), which does
|
|
// nothing and therefor has been removed.
|
|
}
|
|
|
|
if (!newWire) {
|
|
findTightBoundUpdateVertices(beginInfo);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// This method was originally part of WireJoinerP::exhaustTightBound(), split to reduce cognitive
|
|
// complexity
|
|
void exhaustTightBoundUpdateVertex(const int iteration2,
|
|
const VertexInfo& beginVertex,
|
|
const int idxV,
|
|
const gp_Pnt& pstart,
|
|
const std::vector<VertexInfo>& wireVertices,
|
|
std::shared_ptr<WireInfo>& newWire,
|
|
const std::shared_ptr<WireInfo>& wireInfo)
|
|
{
|
|
const int idx = wireVertices[idxV].start ? 1 : 0;
|
|
auto current = wireVertices[idxV].edgeInfo();
|
|
|
|
for (int vertex = current->iStart[idx]; vertex < current->iEnd[idx]; ++vertex) {
|
|
const auto& currentVertex = adjacentList[vertex];
|
|
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);
|
|
|
|
// Originally here there a call to the method checkStack(), which
|
|
// does nothing and therefor has been removed.
|
|
|
|
TopoDS_Wire wire;
|
|
if (pstart.SquareDistance(currentVertex.ptOther()) > myTol2) {
|
|
wire = _findClosedWires(beginVertex, currentVertex, nullptr, 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& entry : stack) {
|
|
const auto& vertex = vertexStack[entry.iCurrent];
|
|
newWireVertices.push_back(vertex);
|
|
}
|
|
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& vertex : newWire->vertices) {
|
|
if (vertex.edgeInfo()->wireInfo == wireInfo) {
|
|
vertex.edgeInfo()->wireInfo = newWire;
|
|
}
|
|
}
|
|
showShape(*newWire, "nwire2", iteration);
|
|
checkWireInfo(*newWire);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// This method was originally part of WireJoinerP::exhaustTightBound(), split to reduce cognitive
|
|
// complexity
|
|
void exhaustTightBoundUpdateEdge(const int iteration2,
|
|
const VertexInfo& beginVertex,
|
|
const std::vector<VertexInfo>& wireVertices,
|
|
const gp_Pnt& pstart,
|
|
std::shared_ptr<WireInfo>& wireInfo)
|
|
{
|
|
std::shared_ptr<WireInfo> newWire;
|
|
|
|
int idxV = 1;
|
|
while (true) {
|
|
exhaustTightBoundUpdateVertex(iteration2,
|
|
beginVertex,
|
|
idxV,
|
|
pstart,
|
|
wireVertices,
|
|
newWire,
|
|
wireInfo);
|
|
|
|
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());
|
|
|
|
// Originally here there a call to the method checkStack(), which does
|
|
// nothing and therefor has been removed.
|
|
}
|
|
}
|
|
|
|
// This method was originally part of WireJoinerP::exhaustTightBound(), split to reduce cognitive
|
|
// complexity
|
|
void exhaustTightBoundWithAdjacent(const EdgeInfo& info,
|
|
int& iteration2,
|
|
const VertexInfo beginVertex,
|
|
const EdgeInfo* check)
|
|
{
|
|
const gp_Pnt& pstart = beginVertex.pt();
|
|
const 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& entry : stack) {
|
|
const auto& vertex = vertexStack[entry.iCurrent];
|
|
wireInfo->vertices.push_back(vertex);
|
|
}
|
|
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& vertex : wireVertices) {
|
|
vertex.it->iteration2 = iteration2;
|
|
}
|
|
|
|
exhaustTightBoundUpdateEdge(iteration2,
|
|
beginVertex,
|
|
wireVertices,
|
|
pstart,
|
|
wireInfo);
|
|
}
|
|
|
|
if (wireInfo && wireInfo->done) {
|
|
for (auto& vertex : wireInfo->vertices) {
|
|
auto edgeInfo = vertex.edgeInfo();
|
|
|
|
// Originally here there was a call to the precompiler macro
|
|
// assertCheck(), which has been replaced with the precompiler macro
|
|
// assert()
|
|
|
|
assert(edgeInfo->wireInfo != nullptr);
|
|
if (edgeInfo->wireInfo->isSame(*wireInfo)) {
|
|
wireInfo = edgeInfo->wireInfo;
|
|
break;
|
|
}
|
|
}
|
|
for (auto& vertex : wireInfo->vertices) {
|
|
auto edgeInfo = vertex.edgeInfo();
|
|
if (!edgeInfo->wireInfo2 && edgeInfo->wireInfo != wireInfo) {
|
|
edgeInfo->wireInfo2 = wireInfo;
|
|
}
|
|
}
|
|
|
|
// Originally here there were two calls to the precompiler macro
|
|
// assertCheck(), which have been replaced with the precompiler macro
|
|
// assert()
|
|
|
|
assert(info.wireInfo2 == wireInfo);
|
|
assert(info.wireInfo2 != info.wireInfo);
|
|
showShape(*wireInfo, "exhaust");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// This method was originally part of WireJoinerP::exhaustTightBound(), split to reduce cognitive
|
|
// complexity
|
|
void exhaustTightBoundUpdateWire(const EdgeInfo& info, int& iteration2)
|
|
{
|
|
|
|
showShape(*info.wireInfo, "iwire2", iteration);
|
|
showShape(&info, "begin2", iteration);
|
|
|
|
int idx = info.wireInfo->find(&info);
|
|
|
|
// Originally here there was a call to the precompiler macro assertCheck(), which has
|
|
// been replaced with the precompiler macro assert()
|
|
|
|
assert(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 idxV = 0; idxV < count && !info.wireInfo2; ++idxV) {
|
|
auto check = vertices[idxV == 0 ? nextIdx : prevIdx].edgeInfo();
|
|
auto beginVertex = vertices[idx];
|
|
if (idxV == 1) {
|
|
beginVertex.start = !beginVertex.start;
|
|
}
|
|
|
|
exhaustTightBoundWithAdjacent(info, iteration2, beginVertex, check);
|
|
}
|
|
}
|
|
|
|
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 &vertex : info.wireInfo->vertices) {
|
|
auto edgeInfo = vertex.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;
|
|
}
|
|
|
|
exhaustTightBoundUpdateWire(info, iteration2);
|
|
|
|
}
|
|
wireSet.clear();
|
|
}
|
|
|
|
// This method was originally part of WireJoinerP::makeCleanWire(), split to reduce cognitive
|
|
// complexity
|
|
void printHistoryInit(const Handle_BRepTools_History& newHistory,
|
|
const std::vector<TopoShape>& inputEdges)
|
|
{
|
|
FC_MSG("init:");
|
|
for (const auto& shape : sourceEdges) {
|
|
FC_MSG(shape.getShape().TShape().get() << ", " << shape.getShape().HashCode(INT_MAX));
|
|
}
|
|
printHistory(aHistory, sourceEdges);
|
|
printHistory(newHistory, inputEdges);
|
|
}
|
|
|
|
// This method was originally part of WireJoinerP::makeCleanWire(), split to reduce cognitive
|
|
// complexity
|
|
void printHistoryFinal()
|
|
{
|
|
printHistory(aHistory, sourceEdges);
|
|
FC_MSG("final:");
|
|
for (int i = 1; i <= wireData->NbEdges(); ++i) {
|
|
auto shape = wireData->Edge(i);
|
|
FC_MSG(shape.TShape().get() << ", " << shape.HashCode(INT_MAX));
|
|
}
|
|
}
|
|
|
|
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;
|
|
Handle(ShapeBuild_ReShape) reshape = new ShapeBuild_ReShape();
|
|
fixer.SetContext(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) {
|
|
printHistoryInit(newHistory, inputEdges);
|
|
}
|
|
|
|
aHistory->Merge(newHistory);
|
|
|
|
|
|
if (FC_LOG_INSTANCE.level() > FC_LOGLEVEL_TRACE + 1) {
|
|
printHistoryFinal();
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
// This method was originally part of WireJoinerP::printHistory(), split to reduce cognitive
|
|
// complexity
|
|
template<class T>
|
|
void printHistoryOfShape(const Handle(BRepTools_History)& hist, const T& shape)
|
|
{
|
|
for (TopTools_ListIteratorOfListOfShape it(hist->Modified(shape.getShape())); it.More();
|
|
it.Next()) {
|
|
FC_MSG(shape.getShape().TShape().get()
|
|
<< ", " << shape.getShape().HashCode(INT_MAX) << " -> "
|
|
<< it.Value().TShape().get() << ", " << it.Value().HashCode(INT_MAX));
|
|
}
|
|
}
|
|
|
|
template<class T>
|
|
void printHistory(Handle(BRepTools_History) hist, const T &input)
|
|
{
|
|
FC_MSG("\nHistory:\n");
|
|
for (const auto& shape : input) {
|
|
printHistoryOfShape(hist, shape);
|
|
}
|
|
}
|
|
|
|
bool canShowShape(int idx=-1, bool forced=false) const
|
|
{
|
|
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) const
|
|
{
|
|
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& sToShow,
|
|
const char* name,
|
|
int idx = -1,
|
|
bool forced = false) const
|
|
{
|
|
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(sToShow, name);
|
|
FC_MSG(obj->getNameInDocument() << " " << ShapeHasher()(sToShow));
|
|
if (catchObject == obj->getNameInDocument()) {
|
|
FC_MSG("found");
|
|
}
|
|
}
|
|
|
|
// This method was originally part of WireJoinerP::build(), split to reduce cognitive complexity
|
|
void buildClosedWire()
|
|
{
|
|
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& vertex : info.wireInfo2->vertices) {
|
|
if (++counter[vertex.edgeInfo()] == 2) {
|
|
vertex.edgeInfo()->iteration = -1;
|
|
done = false;
|
|
showShape(vertex.edgeInfo(), "removed2", iteration);
|
|
aHistory->Remove(info.edge);
|
|
}
|
|
}
|
|
}
|
|
if (!wires.insert(info.wireInfo.get()).second) {
|
|
continue;
|
|
}
|
|
for (auto& vertex : info.wireInfo->vertices) {
|
|
if (++counter[vertex.edgeInfo()] == 2) {
|
|
vertex.edgeInfo()->iteration = -1;
|
|
done = false;
|
|
showShape(vertex.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();
|
|
}
|
|
|
|
void build()
|
|
{
|
|
clear();
|
|
sourceEdges.clear();
|
|
sourceEdges.insert(sourceEdgeArray.begin(), sourceEdgeArray.end());
|
|
for (const auto& edge : sourceEdgeArray) {
|
|
add(TopoDS::Edge(edge.getShape()), true);
|
|
}
|
|
|
|
if (doTightBound || doSplitEdge) {
|
|
splitEdges();
|
|
}
|
|
|
|
buildAdjacentList();
|
|
|
|
if (!doTightBound && !doOutline) {
|
|
findClosedWires();
|
|
}
|
|
else {
|
|
buildClosedWire();
|
|
}
|
|
|
|
// 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 &edge : it->getSubShapes(TopAbs_EDGE)) {
|
|
if (source.findSubShapesWithSharedVertex(TopoShape(edge, -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() = default;
|
|
|
|
void WireJoiner::addShape(const TopoShape &shape)
|
|
{
|
|
NotDone();
|
|
for (auto& edge : shape.getSubTopoShapes(TopAbs_EDGE)) {
|
|
pimpl->sourceEdgeArray.push_back(edge);
|
|
}
|
|
}
|
|
|
|
void WireJoiner::addShape(const std::vector<TopoShape> &shapes)
|
|
{
|
|
NotDone();
|
|
for (const auto &shape : shapes) {
|
|
for (auto& edge : shape.getSubTopoShapes(TopAbs_EDGE)) {
|
|
pimpl->sourceEdgeArray.push_back(edge);
|
|
}
|
|
}
|
|
}
|
|
|
|
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& theRange)
|
|
{
|
|
(void)theRange;
|
|
#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& SThatGenerates)
|
|
{
|
|
Build();
|
|
return pimpl->aHistory->Generated(SThatGenerates);
|
|
}
|
|
|
|
const TopTools_ListOfShape& WireJoiner::Modified (const TopoDS_Shape& SThatModifies)
|
|
{
|
|
Build();
|
|
return pimpl->aHistory->Modified(SThatModifies);
|
|
}
|
|
|
|
Standard_Boolean WireJoiner::IsDeleted (const TopoDS_Shape& SDeleted)
|
|
{
|
|
Build();
|
|
return pimpl->aHistory->IsRemoved(SDeleted);
|
|
}
|