Toposhape/Part: Transfer in makEOffset, makEOffsetFace, makEOffset2d

This commit is contained in:
Zheng, Lei
2024-02-20 12:32:36 -05:00
committed by bgbsww
parent 582d015eaf
commit 201d48659f
2 changed files with 564 additions and 0 deletions

View File

@@ -882,6 +882,137 @@ public:
return TopoShape(0,Hasher).makeElementThickSolid(*this,faces,offset,tol,intersection,selfInter,
offsetMode,join,op);
}
/** Make a 3D offset of a given shape
*
* @param source: source shape
* @param offset: distance to offset
* @param tol: tolerance criterion for coincidence in generated shapes
* @param intersection: whether to check intersection in all generated parallel
* (OCCT document states the option is not fully implemented)
* @param selfInter: whether to eliminate self intersection
* (OCCT document states the option is not implemented)
* @param offsetMode: defines the construction type of parallels applied to free edges
* (OCCT document states the option is not implemented)
* @param join: join type. Only support JoinType::Arc and JoinType::Intersection.
* @param fill: whether to build a solid by fill the offset
* @param op: optional string to be encoded into topo naming for indicating
* the operation
*
* @return The original content of this TopoShape is discarded and replaced
* with the new shape. The function returns the TopoShape itself as
* a self reference so that multiple operations can be carried out
* for the same shape in the same line of code.
*/
TopoShape &makEOffset(const TopoShape &source, double offset, double tol,
bool intersection = false, bool selfInter = false, short offsetMode = 0,
JoinType join = JoinType::Arc, bool fill = false, const char *op=nullptr);
/** Make a 3D offset of this shape
*
* @param offset: distance to offset
* @param tol: tolerance criterion for coincidence in generated shapes
* @param intersection: whether to check intersection in all generated parallel
* (OCCT document states the option is not fully implemented)
* @param selfInter: whether to eliminate self intersection
* (OCCT document states the option is not implemented)
* @param offsetMode: defines the construction type of parallels applied to free edges
* (OCCT document states the option is not implemented)
* @param fill: whether to build a solid by fill the offset
* @param join: join type. Only support JoinType::Arc and JoinType::Intersection.
* @param op: optional string to be encoded into topo naming for indicating
* the operation
*
* @return Return the new shape. The TopoShape itself is not modified.
*/
TopoShape makEOffset(double offset, double tol, bool intersection = false, bool selfInter = false,
short offsetMode=0, JoinType join=JoinType::Arc, bool fill=false, const char *op=nullptr) const {
return TopoShape(0,Hasher).makEOffset(*this,offset,tol,intersection,selfInter,
offsetMode,join,fill,op);
}
/** Make a 2D offset of a given shape
*
* @param source: source shape of edge, wire, face, or compound
* @param offset: distance to offset
* @param allowOpenResult: whether to allow open edge/wire
* @param join: join type. Only support JoinType::Arc and JoinType::Intersection.
* @param intersection: if true, then offset all non-compound shape
* together to deal with possible intersection after
* expanding the shape. If false, then offset each
* shape separately.
* @param op: optional string to be encoded into topo naming for indicating
* the operation
*
* @return The original content of this TopoShape is discarded and replaced
* with the new shape. The function returns the TopoShape itself as
* a self reference so that multiple operations can be carried out
* for the same shape in the same line of code.
*/
TopoShape &makEOffset2D(const TopoShape &source, double offset, JoinType join=JoinType::Arc, bool fill=false,
bool allowOpenResult=false, bool intersection=false, const char *op=nullptr);
/** Make a 2D offset of a given shape
*
* @param source: source shape of edge, wire, face, or compound
* @param offset: distance to offset
* @param allowOpenResult: whether to allow open edge/wire
* @param join: join type. Only support JoinType::Arc and JoinType::Intersection.
* @param intersection: if true, then offset all non-compound shape
* together to deal with possible intersection after
* expanding the shape. If false, then offset each
* shape separately.
* @param op: optional string to be encoded into topo naming for indicating
* the operation
*
* @return Return the new shape. The TopoShape itself is not modified.
*/
TopoShape makEOffset2D(double offset, JoinType join=JoinType::Arc, bool fill=false, bool allowOpenResult=false,
bool intersection=false, const char *op=nullptr) const {
return TopoShape(0,Hasher).makEOffset2D(*this,offset,join,fill,allowOpenResult,intersection,op);
}
/** Make a 2D offset of face with separate control for outer and inner (hole) wires
*
* @param source: source shape of any type, but only faces inside will be used
* @param offset: distance to offset for outer wires of the faces
* @param innerOffset: distance to offset for inner wires of the faces
* @param join: join type of outer wire. Only support JoinType::Arc and JoinType::Intersection.
* @param innerJoin: join type of inner wire. Only support JoinType::Arc and JoinType::Intersection.
* @param op: optional string to be encoded into topo naming for indicating
* the operation
*
* @return The original content of this TopoShape is discarded and replaced
* with the new shape. The function returns the TopoShape itself as
* a self reference so that multiple operations can be carried out
* for the same shape in the same line of code.
*/
TopoShape &makEOffsetFace(const TopoShape &source,
double offset,
double innerOffset,
JoinType join = JoinType::Arc,
JoinType innerJoin = JoinType::Arc,
const char *op = nullptr);
/** Make a 2D offset of face with separate control for outer and inner (hole) wires
*
* @param source: source shape of any type, but only faces inside will be used
* @param offset: distance to offset for outer wires of the faces
* @param innerOffset: distance to offset for inner wires of the faces
* @param join: join type of outer wire. Only support JoinType::Arc and JoinType::Intersection.
* @param innerJoin: join type of inner wire. Only support JoinType::Arc and JoinType::Intersection.
* @param op: optional string to be encoded into topo naming for indicating
* the operation
*
* @return Return the new shape. The TopoShape itself is not modified.
*/
TopoShape makEOffsetFace(double offset,
double innerOffset,
JoinType join = JoinType::Arc,
JoinType innerJoin = JoinType::Arc,
const char *op = nullptr) const
{
return TopoShape(0,Hasher).makEOffsetFace(*this,offset,innerOffset,join,innerJoin,op);
}
/** Make revolved shell around a basis shape

View File

@@ -2271,6 +2271,439 @@ TopoShape& TopoShape::makeElementPipeShell(const std::vector<TopoShape>& shapes,
return makeElementShape(mkPipeShell, shapes, op);
}
TopoShape &TopoShape::makEOffset(const TopoShape &shape,
double offset, double tol, bool intersection, bool selfInter,
short offsetMode, JoinType join, bool fill, const char *op)
{
if(!op) op = Part::OpCodes::Offset;
#if OCC_VERSION_HEX < 0x070200
BRepOffsetAPI_MakeOffsetShape mkOffset(shape.getShape(), offset, tol, BRepOffset_Mode(offsetMode),
intersection ? Standard_True : Standard_False,
selfInter ? Standard_True : Standard_False,
GeomAbs_JoinType(join));
#else
BRepOffsetAPI_MakeOffsetShape mkOffset;
mkOffset.PerformByJoin(shape.getShape(), offset, tol, BRepOffset_Mode(offsetMode),
intersection ? Standard_True : Standard_False,
selfInter ? Standard_True : Standard_False,
GeomAbs_JoinType(join));
#endif
if (!mkOffset.IsDone())
FC_THROWM(Base::CADKernelError,"BRepOffsetAPI_MakeOffsetShape not done");
TopoShape res(Tag,Hasher);
res.makEShape(mkOffset,shape,op);
if(shape.hasSubShape(TopAbs_SOLID) && !res.hasSubShape(TopAbs_SOLID)) {
try {
res = res.makESolid();
}catch (Standard_Failure &e) {
FC_WARN("failed to make solid: " << e.GetMessageString());
}
}
if (!fill) {
*this = res;
return *this;
}
//get perimeter wire of original shape.
//Wires returned seem to have edges in connection order.
ShapeAnalysis_FreeBoundsProperties freeCheck(shape.getShape());
freeCheck.Perform();
if (freeCheck.NbClosedFreeBounds() < 1)
{
FC_THROWM(Base::CADKernelError,"no closed bounds");
}
BRep_Builder builder;
std::vector<TopoShape> shapes;
for (int index = 1; index <= freeCheck.NbClosedFreeBounds(); ++index)
{
TopoShape originalWire(shape.Tag,shape.Hasher,freeCheck.ClosedFreeBound(index)->FreeBound());
originalWire.mapSubElement(shape);
const BRepAlgo_Image& img = mkOffset.MakeOffset().OffsetEdgesFromShapes();
//build offset wire.
TopoDS_Wire offsetWire;
builder.MakeWire(offsetWire);
for(const auto &s : originalWire.getSubShapes(TopAbs_EDGE)) {
if (!img.HasImage(s))
{
FC_THROWM(Base::CADKernelError,"no image for shape");
}
const TopTools_ListOfShape& currentImage = img.Image(s);
TopTools_ListIteratorOfListOfShape listIt;
int edgeCount(0);
TopoDS_Edge mappedEdge;
for (listIt.Initialize(currentImage); listIt.More(); listIt.Next())
{
if (listIt.Value().ShapeType() != TopAbs_EDGE)
continue;
edgeCount++;
mappedEdge = TopoDS::Edge(listIt.Value());
}
if (edgeCount != 1)
{
std::ostringstream stream;
stream << "wrong edge count: " << edgeCount << std::endl;
FC_THROWM(Base::CADKernelError,stream.str().c_str());
}
builder.Add(offsetWire, mappedEdge);
}
std::vector<TopoShape> wires;
wires.push_back(originalWire);
wires.push_back(TopoShape(Tag,Hasher,offsetWire));
wires.back().mapSubElement(res);
//It would be nice if we could get thruSections to build planar faces
//in all areas possible, so we could run through refine. I tried setting
//ruled to standard_true, but that didn't have the desired affect.
BRepOffsetAPI_ThruSections aGenerator;
aGenerator.AddWire(TopoDS::Wire(originalWire.getShape()));
aGenerator.AddWire(offsetWire);
aGenerator.Build();
if (!aGenerator.IsDone())
{
FC_THROWM(Base::CADKernelError,"ThruSections failed");
}
shapes.push_back(TopoShape(Tag,Hasher).makEShape(aGenerator,wires));
}
TopoShape perimeterCompound(Tag,Hasher);
perimeterCompound.makECompound(shapes,op);
//still had to sew. not using the passed in parameter for sew.
//Sew has it's own default tolerance. Opinions?
BRepBuilderAPI_Sewing sewTool;
sewTool.Add(shape.getShape());
sewTool.Add(perimeterCompound.getShape());
sewTool.Add(res.getShape());
sewTool.Perform(); //Perform Sewing
TopoDS_Shape outputShape = sewTool.SewedShape();
if ((outputShape.ShapeType() == TopAbs_SHELL) && (outputShape.Closed()))
{
BRepBuilderAPI_MakeSolid solidMaker(TopoDS::Shell(outputShape));
if (solidMaker.IsDone())
{
TopoDS_Solid temp = solidMaker.Solid();
//contrary to the occ docs the return value OrientCloseSolid doesn't
//indicate whether the shell was open or not. It returns true with an
//open shell and we end up with an invalid solid.
if (BRepLib::OrientClosedSolid(temp))
outputShape = temp;
}
}
shapes.clear();
shapes.push_back(shape);
shapes.push_back(res);
shapes.push_back(perimeterCompound);
*this = TopoShape(Tag,Hasher).makESHAPE(outputShape,MapperSewing(sewTool),shapes,op);
return *this;
}
TopoShape &TopoShape::makEOffsetFace(const TopoShape &shape,
double offset,
double innerOffset,
JoinType joinType,
JoinType innerJoinType,
const char *op)
{
if (std::abs(innerOffset) < Precision::Confusion()
&& std::abs(offset) < Precision::Confusion()) {
*this = shape;
return *this;
}
if (shape.isNull())
FC_THROWM(Base::ValueError, "makeOffsetFace: input shape is null!");
if (!shape.hasSubShape(TopAbs_FACE))
FC_THROWM(Base::ValueError, "makeOffsetFace: no face found");
std::vector<TopoShape> res;
for (auto & face : shape.getSubTopoShapes(TopAbs_FACE)) {
std::vector<TopoShape> wires;
TopoShape outerWire = face.splitWires(&wires, ReorientForward);
if (wires.empty()) {
res.push_back(makEOffset2D(face, offset, joinType, false, false, false, op));
continue;
}
if (outerWire.isNull())
FC_THROWM(Base::CADKernelError, "makeOffsetFace: missing outer wire!");
if (std::abs(offset) > Precision::Confusion())
outerWire = outerWire.makEOffset2D(offset, joinType, false, false, false, op);
if (std::abs(innerOffset) > Precision::Confusion()) {
TopoShape innerWires(0, Hasher);
innerWires.makECompound(wires, "", false);
innerWires = innerWires.makEOffset2D(innerOffset, innerJoinType, false, false, true, op);
wires = innerWires.getSubTopoShapes(TopAbs_WIRE);
}
wires.push_back(outerWire);
gp_Pln pln;
res.push_back(TopoShape(0, Hasher).makEFace(wires,
nullptr,
nullptr,
face.findPlane(pln) ? &pln : nullptr));
}
return makECompound(res, "", false);
}
TopoShape &TopoShape::makEOffset2D(const TopoShape &shape, double offset, JoinType joinType,
bool fill, bool allowOpenResult, bool intersection, const char *op)
{
if(!op) op = Part::OpCodes::Offset2D;
if(shape.isNull())
FC_THROWM(Base::ValueError, "makeOffset2D: input shape is null!");
if (allowOpenResult && OCC_VERSION_HEX < 0x060900)
FC_THROWM(Base::AttributeError, "openResult argument is not supported on OCC < 6.9.0.");
// OUTLINE OF MAKEOFFSET2D
// * Prepare shapes to process
// ** if _Shape is a compound, recursively call this routine for all subcompounds
// ** if intrsection, dump all non-compound children into shapes to process; otherwise call this routine recursively for all children
// ** if _shape isn't a compound, dump it straight to shapes to process
// * Test for shape types, and convert them all to wires
// * find plane
// * OCC call (BRepBuilderAPI_MakeOffset)
// * postprocessing (facemaking):
// ** convert offset result back to faces, if inputs were faces
// ** OR do offset filling:
// *** for closed wires, simply feed source wires + offset wires to smart facemaker
// *** for open wires, try to connect source anf offset result by creating new edges (incomplete implementation)
// ** actual call to FaceMakerBullseye, unified for all facemaking.
std::vector<TopoShape> shapesToProcess;
std::vector<TopoShape> shapesToReturn;
bool forceOutputCompound = false;
if (shape.getShape().ShapeType() == TopAbs_COMPOUND){
if (!intersection){
//simply recursively process the children, independently
expandCompound(shape,shapesToProcess);
forceOutputCompound = true;
} else {
//collect non-compounds from this compound for collective offset. Process other shapes independently.
for(auto &s : shape.getSubTopoShapes()) {
if(s.getShape().ShapeType() == TopAbs_COMPOUND){
//recursively process subcompounds
shapesToReturn.push_back(TopoShape(Tag,Hasher).makEOffset2D(
s, offset, joinType, fill, allowOpenResult, intersection, op));
forceOutputCompound = true;
} else {
shapesToProcess.push_back(s);
}
}
}
} else {
shapesToProcess.push_back(shape);
}
if(shapesToProcess.size() > 0){
TopoShape res(Tag,Hasher);
//although 2d offset supports offsetting a face directly, it seems there is
//no way to do a collective offset of multiple faces. So, we are doing it
//by getting all wires from the faces, and applying offsets to them, and
//reassembling the faces later.
std::vector<TopoShape> sourceWires;
bool haveWires = false;
bool haveFaces = false;
for(auto &s : shapesToProcess){
const auto &sh = s.getShape();
switch (sh.ShapeType()) {
case TopAbs_EDGE:
sourceWires.push_back(s.makEWires());
haveWires = true;
break;
case TopAbs_WIRE:
sourceWires.push_back(s);
haveWires = true;
break;
case TopAbs_FACE:{
auto outerWire = s.splitWires(&sourceWires);
sourceWires.push_back(outerWire);
haveFaces = true;
}break;
default:
FC_THROWM(Base::TypeError, "makeOffset2D: input shape is not an edge, wire or face or compound of those.");
break;
}
}
if (haveWires && haveFaces)
FC_THROWM(Base::TypeError, "makeOffset2D: collective offset of a mix of wires and faces is not supported");
if (haveFaces)
allowOpenResult = false;
//find plane.
gp_Pln workingPlane;
if (!TopoShape().makECompound(sourceWires,"",false).findPlane(workingPlane))
FC_THROWM(Base::CADKernelError,"makeOffset2D: wires are nonplanar or noncoplanar");
//do the offset..
TopoShape offsetShape;
if (fabs(offset) > Precision::Confusion()){
BRepOffsetAPI_MakeOffsetFix mkOffset(GeomAbs_JoinType(joinType), allowOpenResult);
for(auto &w : sourceWires) {
mkOffset.AddWire(TopoDS::Wire(w.getShape()));
}
try {
#if defined(__GNUC__) && defined (FC_OS_LINUX)
Base::SignalException se;
#endif
mkOffset.Perform(offset);
}
catch (Standard_Failure &){
throw;
}
catch (...) {
FC_THROWM(Base::CADKernelError,"BRepOffsetAPI_MakeOffset has crashed! (Unknown exception caught)");
}
if(mkOffset.Shape().IsNull())
FC_THROWM(NullShapeException, "makeOffset2D: result of offsetting is null!");
//Copying shape to fix strange orientation behavior, OCC7.0.0. See bug #2699
// http://www.freecadweb.org/tracker/view.php?id=2699
offsetShape = shape.makEShape(mkOffset,op).makECopy();
} else {
offsetShape = TopoShape(Tag,Hasher).makECompound(sourceWires,0,false);
}
std::vector<TopoShape> offsetWires;
//interestingly, if wires are removed, empty compounds are returned by MakeOffset (as of OCC 7.0.0)
//so, we just extract all nesting
expandCompound(offsetShape,offsetWires);
if (offsetWires.empty())
FC_THROWM(Base::CADKernelError, "makeOffset2D: offset result has no wires.");
std::vector<TopoShape> wiresForMakingFaces;
if (!fill){
if (haveFaces){
wiresForMakingFaces.insert(wiresForMakingFaces.end(), offsetWires.begin(),offsetWires.end());
} else {
shapesToReturn.insert(shapesToReturn.end(),offsetWires.begin(),offsetWires.end());
}
} else {
//fill offset
if (fabs(offset) < Precision::Confusion())
FC_THROWM(Base::ValueError, "makeOffset2D: offset distance is zero. Can't fill offset.");
//filling offset. There are three major cases to consider:
// 1. source wires and result wires are closed (simplest) -> make face
// from source wire + offset wire
//
// 2. source wire is open, but offset wire is closed (if not
// allowOpenResult). -> throw away source wire and make face right from
// offset result.
//
// 3. source and offset wire are both open (note that there may be
// closed islands in offset result) -> need connecting offset result to
// source wire with new edges
//first, lets split apart closed and open wires.
std::vector<TopoShape> closedWires;
std::vector<TopoShape> openWires;
for(auto &w : sourceWires)
if (BRep_Tool::IsClosed(TopoDS::Wire(w.getShape())))
closedWires.push_back(w);
else
openWires.push_back(w);
for(auto &w : offsetWires)
if (BRep_Tool::IsClosed(TopoDS::Wire(w.getShape())))
closedWires.push_back(w);
else
openWires.push_back(w);
wiresForMakingFaces.insert(wiresForMakingFaces.end(),closedWires.begin(),closedWires.end());
if (!allowOpenResult || openWires.size() == 0){
//just ignore all open wires
} else {
//We need to connect open wires to form closed wires.
//for now, only support offsetting one open wire -> there should be exactly two open wires for connecting
if (openWires.size() != 2)
FC_THROWM(Base::CADKernelError, "makeOffset2D: collective offset with filling of multiple wires is not supported yet.");
TopoShape openWire1 = openWires.front();
TopoShape openWire2 = openWires.back();
//find open vertices
BRepTools_WireExplorer xp;
xp.Init(TopoDS::Wire(openWire1.getShape()));
TopoDS_Vertex v1 = xp.CurrentVertex();
for(;xp.More();xp.Next()){};
TopoDS_Vertex v2 = xp.CurrentVertex();
//find open vertices
xp.Init(TopoDS::Wire(openWire2.getShape()));
TopoDS_Vertex v3 = xp.CurrentVertex();
for(;xp.More();xp.Next()){};
TopoDS_Vertex v4 = xp.CurrentVertex();
//check
if (v1.IsNull()) FC_THROWM(NullShapeException, "v1 is null");
if (v2.IsNull()) FC_THROWM(NullShapeException, "v2 is null");
if (v3.IsNull()) FC_THROWM(NullShapeException, "v3 is null");
if (v4.IsNull()) FC_THROWM(NullShapeException, "v4 is null");
//assemble new wire
//we want the connection order to be
//v1 -> openWire1 -> v2 -> (new edge) -> v4 -> openWire2(rev) -> v3 -> (new edge) -> v1
//let's check if it's the case. If not, we reverse one wire and swap its endpoints.
if (fabs(gp_Vec(BRep_Tool::Pnt(v2), BRep_Tool::Pnt(v3)).Magnitude() - fabs(offset)) <= BRep_Tool::Tolerance(v2) + BRep_Tool::Tolerance(v3)){
openWire2._Shape.Reverse();
std::swap(v3, v4);
v3.Reverse();
v4.Reverse();
} else if ((fabs(gp_Vec(BRep_Tool::Pnt(v2), BRep_Tool::Pnt(v4)).Magnitude() - fabs(offset)) <= BRep_Tool::Tolerance(v2) + BRep_Tool::Tolerance(v4))){
//orientation is as expected, nothing to do
} else {
FC_THROWM(Base::CADKernelError, "makeOffset2D: fill offset: failed to establish open vertex relationship.");
}
//now directions of open wires are aligned. Finally. make new wire!
BRepBuilderAPI_MakeWire mkWire;
//add openWire1
BRepTools_WireExplorer it;
for(it.Init(TopoDS::Wire(openWire1.getShape())); it.More(); it.Next()){
mkWire.Add(it.Current());
}
//add first joining edge
mkWire.Add(BRepBuilderAPI_MakeEdge(v2,v4).Edge());
//add openWire2, in reverse order
openWire2._Shape.Reverse();
for(it.Init(TopoDS::Wire(openWire2.getShape())); it.More(); it.Next()){
mkWire.Add(it.Current());
}
//add final joining edge
mkWire.Add(BRepBuilderAPI_MakeEdge(v3,v1).Edge());
mkWire.Build();
wiresForMakingFaces.push_back(TopoShape(Tag,Hasher).makEShape(mkWire,openWires,op));
}
}
//make faces
if (wiresForMakingFaces.size()>0) {
TopoShape face(0, Hasher);
face.makEFace(wiresForMakingFaces, nullptr, nullptr, &workingPlane);
expandCompound(face, shapesToReturn);
}
}
return makECompound(shapesToReturn,op,forceOutputCompound);
}
TopoShape& TopoShape::makeElementThickSolid(const TopoShape& shape,
const std::vector<TopoShape>& faces,
double offset,