Part/Toponaming: Add original implementation of makEWires

Renamed to makeElementWires and modified to compile in the current codebase.
This commit is contained in:
Chris Hennes
2024-01-13 12:36:11 -06:00
parent 7bc3317455
commit 574ebeae19
2 changed files with 408 additions and 1 deletions

View File

@@ -48,7 +48,10 @@ class Color;
namespace Part
{
class ShapeHasher;
class TopoShape;
class TopoShapeCache;
typedef std::unordered_map<TopoShape, TopoShape, ShapeHasher, ShapeHasher> TopoShapeMap;
/* A special sub-class to indicate null shapes
*/
@@ -610,6 +613,7 @@ public:
bool canMapElement(const TopoShape &other) const;
void mapSubElement(const TopoShape &other,const char *op=nullptr, bool forceHasher=false);
void mapSubElement(const std::vector<TopoShape> &shapes, const char *op);
void mapSubElementsTo(std::vector<TopoShape> &shapes, const char *op=nullptr) const;
bool hasPendingElementMap() const;
@@ -627,7 +631,117 @@ public:
* a reference so that multiple operations can be carried out for
* the same shape in the same line of code.
*/
TopoShape &makeElementCompound(const std::vector<TopoShape> &shapes, const char *op=nullptr, bool force=true);
TopoShape& makeElementCompound(const std::vector<TopoShape>& shapes,
const char* op = nullptr,
bool force = true);
/** Make a compound of wires by connecting input edges
*
* @param shapes: input shapes. Can be any type of shape. Edges will be
* extracted for building wires.
* @param op: optional string to be encoded into topo naming for indicating
* the operation
* @param keepOrder: whether to respect the order of the input edges
* @param tol: tolerance for checking the distance of two vertex to decide
* if two edges are connected
* @param shared: if true, then only connect edges if they shared the same
* vertex, or else use \c tol to check for connection.
* @param output: optional output mapping from wire edges to input edge.
* Note that edges may be modified after adding to the wire,
* so the output edges may not be the same as the input
* ones.
*
* @return The function produces either a wire or a compound of wires. The
* original content of this TopoShape is discarded and replaced
* with the new shape. The function returns the TopoShape itself as
* a reference so that multiple operations can be carried out for
* the same shape in the same line of code.
*/
TopoShape& makeElementWires(const std::vector<TopoShape>& shapes,
const char* op = nullptr,
double tol = 0.0,
bool shared = false,
TopoShapeMap* output = nullptr);
/** Make a compound of wires by connecting input edges
*
* @param shape: input shape. Can be any type of shape. Edges will be
* extracted for building wires.
* @param op: optional string to be encoded into topo naming for indicating
* the operation
* @param keepOrder: whether to respect the order of the input edges
* @param tol: tolerance for checking the distance of two vertex to decide
* if two edges are connected
* @param shared: if true, then only connect edges if they shared the same
* vertex, or else use \c tol to check for connection.
* @param output: optional output mapping from wire edges to input edge.
* Note that edges may be modified after adding to the wire,
* so the output edges may not be the same as the input
* ones.
*
* @return The function produces either a wire or a compound of wires. The
* original content of this TopoShape is discarded and replaced
* with the new shape. The function returns the TopoShape itself as
* a reference so that multiple operations can be carried out for
* the same shape in the same line of code.
*/
TopoShape& makeElementWires(const TopoShape& shape,
const char* op = nullptr,
double tol = 0.0,
bool shared = false,
TopoShapeMap* output = nullptr);
/** Make a compound of wires by connecting input edges in the given order
*
* @param shapes: input shapes. Can be any type of shape. Edges will be
* extracted for building wires.
* @param op: optional string to be encoded into topo naming for indicating
* the operation
* @param tol: tolerance for checking the distance of two vertex to decide
* if two edges are connected
* @param output: optional output mapping from wire edges to input edge.
* Note that edges may be modified after adding to the wire,
* so the output edges may not be the same as the input
* ones.
*
* @return Same as makeElementWires() but respects the order of the input edges.
* The function produces either a wire or a compound of wires. The
* original content of this TopoShape is discarded and replaced
* with the new shape. The function returns the TopoShape itself as
* a reference so that multiple operations can be carried out for
* the same shape in the same line of code.
*/
TopoShape& makeElementOrderedWires(const std::vector<TopoShape>& shapes,
const char* op = nullptr,
double tol = 0.0,
TopoShapeMap* output = nullptr);
/** Make a wire or compound of wires with the edges inside the this shape
*
* @param op: optional string to be encoded into topo naming for indicating
* the operation
* @param keepOrder: whether to respect the order of the input edges
* @param tol: tolerance for checking the distance of two vertex to decide
* if two edges are connected
* @param shared: if true, then only connect edges if they shared the same
* vertex, or else use \c tol to check for connection.
* @param output: optional output mapping from wire edges to input edge.
* Note that edges may be modified after adding to the wire,
* so the output edges may not be the same as the input
* ones.
*
*
* @return The function returns a new shape of either a single wire or a
* compound of wires. The shape itself is not modified.
*/
TopoShape makeElementWires(const char* op = nullptr,
double tol = 0.0,
bool shared = false,
TopoShapeMap* output = nullptr) const
{
return TopoShape(0, Hasher).makeElementWires(*this, op, tol, shared, output);
}
friend class TopoShapeCache;
@@ -761,6 +875,19 @@ private:
bool& warned);
void mapCompoundSubElements(const std::vector<TopoShape>& shapes, const char* op);
/** Given a set of edges, return a sorted list of connected edges
*
* @param edges: (input/output) input list of shapes. Must be of type edge.
* On return, the returned connected edges will be removed
* from this list. You can repeated call this function to find
* all wires.
* @param keepOrder: whether to respect the order of the input edges
* @param tol: tolerance for checking the distance of two vertex to decide
* if two edges are connected
* @return Return a list of ordered connected edges.
*/
static std::deque<TopoShape>
sortEdges(std::list<TopoShape>& edges, bool keepOrder = false, double tol = 0.0);
};
} // namespace Part

View File

@@ -27,10 +27,16 @@
#ifndef _PreComp_
#include <BRep_Builder.hxx>
#include <BRep_Tool.hxx>
#include <BRepBuilderAPI_MakeEdge.hxx>
#include <BRepBuilderAPI_MakeWire.hxx>
#include <TopTools_HSequenceOfShape.hxx>
#include <Precision.hxx>
#include <ShapeAnalysis_FreeBounds.hxx>
#endif
#include "TopoShape.h"
#include "TopoShapeCache.h"
#include "TopoShapeOpCode.h"
FC_LOG_LEVEL_INIT("TopoShape", true, true) // NOLINT
@@ -429,6 +435,11 @@ void TopoShape::mapSubElement(const TopoShape& other, const char* op, bool force
mapSubElementForShape(other, op);
}
void TopoShape::mapSubElementsTo(std::vector<TopoShape> &shapes, const char *op) const {
for(auto &shape : shapes)
shape.mapSubElement(*this,op);
}
std::vector<Data::ElementMap::MappedChildElements>
TopoShape::createChildMap(size_t count, const std::vector<TopoShape>& shapes, const char* op)
{
@@ -540,4 +551,273 @@ TopoShape::makeElementCompound(const std::vector<TopoShape>& shapes, const char*
return *this;
}
TopoShape &TopoShape::makeElementWires(const std::vector<TopoShape> &shapes,
const char *op,
double tol,
bool shared,
TopoShapeMap *output)
{
if(shapes.empty())
FC_THROWM(NullShapeException, "Null shape");;
if(shapes.size() == 1)
return makeElementWires(shapes[0],op,tol,shared,output);
return makeElementWires(TopoShape(Tag).makeElementCompound(shapes),op,tol,shared,output);
}
TopoShape &TopoShape::makeElementWires(const TopoShape &shape,
const char *op,
double tol,
bool shared,
TopoShapeMap *output)
{
if(!op) op = Part::OpCodes::Wire;
if(tol<Precision::Confusion()) tol = Precision::Confusion();
if (shared) {
// Can't use ShapeAnalysis_FreeBounds if not shared. It seems the output
// edges are modified some how, and it is not obvious how to map the
// resulting edges.
Handle(TopTools_HSequenceOfShape) hEdges = new TopTools_HSequenceOfShape();
Handle(TopTools_HSequenceOfShape) hWires = new TopTools_HSequenceOfShape();
for(TopExp_Explorer xp(shape.getShape(),TopAbs_EDGE);xp.More();xp.Next())
hEdges->Append(xp.Current());
if(!hEdges->Length())
FC_THROWM(NullShapeException, "Null shape");;
ShapeAnalysis_FreeBounds::ConnectEdgesToWires(hEdges, tol, Standard_True, hWires);
if(!hWires->Length())
FC_THROWM(NullShapeException, "Null shape");;
std::vector<TopoShape> wires;
for (int i=1; i<=hWires->Length(); i++) {
auto wire = hWires->Value(i);
wires.push_back(TopoShape(Tag,Hasher,wire));
}
shape.mapSubElementsTo(wires,op);
return makeElementCompound(wires,"",false);
}
std::vector<TopoShape> wires;
std::list<TopoShape> edge_list;
for(auto &e : shape.getSubTopoShapes(TopAbs_EDGE))
edge_list.emplace_back(e);
std::vector<TopoShape> edges;
edges.reserve(edge_list.size());
wires.reserve(edge_list.size());
// sort them together to wires
while (edge_list.size() > 0) {
BRepBuilderAPI_MakeWire mkWire;
// add and erase first edge
edges.clear();
edges.push_back(edge_list.front());
mkWire.Add(TopoDS::Edge(edges.back().getShape()));
edges.back().setShape(mkWire.Edge(),false);
if (output)
(*output)[edges.back()] = edge_list.front();
edge_list.pop_front();
TopoDS_Wire new_wire = mkWire.Wire(); // current new wire
// try to connect each edge to the wire, the wire is complete if no more edges are connectible
bool found = false;
do {
found = false;
for (auto it=edge_list.begin();it!=edge_list.end();++it) {
mkWire.Add(TopoDS::Edge(it->getShape()));
if (mkWire.Error() != BRepBuilderAPI_DisconnectedWire) {
// edge added ==> remove it from list
found = true;
edges.push_back(*it);
// MakeWire will replace vertex of connected edge, which
// effectively creat a new edge. So we need to update the
// shape in order to preserve element mapping.
edges.back().setShape(mkWire.Edge(),false);
if (output)
(*output)[edges.back()] = *it;
edge_list.erase(it);
new_wire = mkWire.Wire();
break;
}
}
} while (found);
wires.emplace_back(new_wire);
wires.back().mapSubElement(edges, op);
wires.back().fix();
}
return makeElementCompound(wires,0,false);
}
struct EdgePoints {
gp_Pnt v1, v2;
std::list<TopoShape>::iterator it;
const TopoShape &edge;
bool closed;
EdgePoints(std::list<TopoShape>::iterator it, double tol)
:it(it), edge(*it), closed(false)
{
TopExp_Explorer xp(it->getShape(),TopAbs_VERTEX);
v1 = BRep_Tool::Pnt(TopoDS::Vertex(xp.Current()));
xp.Next();
if (xp.More()) {
v2 = BRep_Tool::Pnt(TopoDS::Vertex(xp.Current()));
closed = (v2.SquareDistance(v1) <= tol);
} else {
v2 = v1;
closed = true;
}
}
};
std::deque<TopoShape>
TopoShape::sortEdges(std::list<TopoShape>& edges, bool keepOrder, double tol)
{
if (tol<Precision::Confusion()) tol = Precision::Confusion();
double tol3d = tol * tol;
std::list<EdgePoints> edge_points;
for (auto it = edges.begin(); it != edges.end(); ++it)
edge_points.emplace_back(it, tol3d);
std::deque<TopoShape> sorted;
if (edge_points.empty())
return sorted;
gp_Pnt first, last;
first = edge_points.front().v1;
last = edge_points.front().v2;
sorted.push_back(edge_points.front().edge);
edges.erase(edge_points.front().it);
if (edge_points.front().closed)
return sorted;
edge_points.erase(edge_points.begin());
auto reverseEdge = [](const TopoShape &edge) {
Standard_Real first, last;
const Handle(Geom_Curve) & curve = BRep_Tool::Curve(TopoDS::Edge(edge.getShape()), first, last);
first = curve->ReversedParameter(first);
last = curve->ReversedParameter(last);
TopoShape res(BRepBuilderAPI_MakeEdge(curve->Reversed(), last, first));
auto edgeName = Data::IndexedName::fromConst("Edge", 1);
if (auto mapped = edge.getMappedName(edgeName))
res.elementMap()->setElementName(edgeName, mapped, Tag);
auto v1Name = Data::IndexedName::fromConst("Vertex", 1);
auto v2Name = Data::IndexedName::fromConst("Vertex", 2);
auto v1 = edge.getMappedName(v1Name);
auto v2 = edge.getMappedName(v2Name);
if (v1 && v2) {
res.elementMap()->setElementName(v1Name, v2, Tag);
res.elementMap()->setElementName(v2Name, v1, Tag);
}
else if (v1 && edge.countSubShapes(TopAbs_EDGE) == 1) {
// It's possible an edge has only one vertex, so no need to reverse
// the name
res.elementMap()->setElementName(v1Name, v1, Tag);
}
else if (v1)
res.elementMap()->setElementName(v2Name, v1, Tag);
else if (v2)
res.elementMap()->setElementName(v1Name, v2, Tag);
return res;
};
while (!edge_points.empty()) {
// search for adjacent edge
std::list<EdgePoints>::iterator pEI;
for (pEI = edge_points.begin(); pEI != edge_points.end(); ++pEI) {
if (pEI->closed)
continue;
if (keepOrder && sorted.size() == 1) {
if (pEI->v2.SquareDistance(first) <= tol3d
|| pEI->v1.SquareDistance(first) <= tol3d) {
sorted[0] = reverseEdge(sorted[0]);
std::swap(first, last);
}
}
if (pEI->v1.SquareDistance(last) <= tol3d) {
last = pEI->v2;
sorted.push_back(pEI->edge);
edges.erase(pEI->it);
edge_points.erase(pEI);
pEI = edge_points.begin();
break;
}
else if (pEI->v2.SquareDistance(first) <= tol3d) {
sorted.push_front(pEI->edge);
first = pEI->v1;
edges.erase(pEI->it);
edge_points.erase(pEI);
pEI = edge_points.begin();
break;
}
else if (pEI->v2.SquareDistance(last) <= tol3d) {
last = pEI->v1;
sorted.push_back(reverseEdge(pEI->edge));
edges.erase(pEI->it);
edge_points.erase(pEI);
pEI = edge_points.begin();
break;
}
else if (pEI->v1.SquareDistance(first) <= tol3d) {
first = pEI->v2;
sorted.push_front(reverseEdge(pEI->edge));
edges.erase(pEI->it);
edge_points.erase(pEI);
pEI = edge_points.begin();
break;
}
}
if ((pEI == edge_points.end()) || (last.SquareDistance(first) <= tol3d)) {
// no adjacent edge found or polyline is closed
return sorted;
}
}
return sorted;
}
TopoShape &TopoShape::makeElementOrderedWires(const std::vector<TopoShape> &shapes,
const char *op,
double tol,
TopoShapeMap *output)
{
if(!op) op = Part::OpCodes::Wire;
if(tol<Precision::Confusion()) tol = Precision::Confusion();
std::vector<TopoShape> wires;
std::list<TopoShape> edge_list;
auto shape = TopoShape().makeElementCompound(shapes, "", false);
for(auto &e : shape.getSubTopoShapes(TopAbs_EDGE))
edge_list.push_back(e);
while(edge_list.size()) {
BRepBuilderAPI_MakeWire mkWire;
std::vector<TopoShape> edges;
for (auto &edge : sortEdges(edge_list, true, tol)) {
edges.push_back(edge);
mkWire.Add(TopoDS::Edge(edge.getShape()));
// MakeWire will replace vertex of connected edge, which
// effectively creat a new edge. So we need to update the shape
// in order to preserve element mapping.
edges.back().setShape(mkWire.Edge(), false);
if (output)
(*output)[edges.back()] = edge;
}
wires.push_back(mkWire.Wire());
wires.back().mapSubElement(edges,op);
}
return makeElementCompound(wires,0,false);
}
} // namespace Part