Part: Offset2D: rewrite to use FaceMaker

new features supported:
* fill offset in collective mode when result wires are closed
* offsetting faces in collective mode

Remaining TODOs:
- collective offset of open wires in "skin" mode
This commit is contained in:
DeepSOIC 2016-09-25 18:39:38 +03:00
parent 1757f70cdf
commit c7f975279c
2 changed files with 246 additions and 259 deletions

View File

@ -45,6 +45,7 @@
# include <BRepAlgoAPI_Section.hxx>
# include <BRepBndLib.hxx>
# include <BRepBuilderAPI_FindPlane.hxx>
# include <BRepLib_FindSurface.hxx>
# include <BRepBuilderAPI_GTransform.hxx>
# include <BRepBuilderAPI_MakeEdge.hxx>
# include <BRepBuilderAPI_MakeFace.hxx>
@ -180,6 +181,7 @@
#include "modelRefine.h"
#include "Tools.h"
#include "encodeFilename.h"
#include "FaceMakerBullseye.h"
using namespace Part;
@ -2073,113 +2075,129 @@ TopoDS_Shape TopoShape::makeOffset2D(double offset, short joinType, bool fill, b
{
if (_Shape.IsNull())
throw Base::ValueError("makeOffset2D: input shape is null!");
if (fill && intersection)
throw Base::ValueError("Filling offset when 'intersection' is true is not supported yet.");
if (allowOpenResult && OCC_VERSION_HEX < 0x060900)
throw Base::AttributeError("openResult argument is not supported on OCC < 6.9.0.");
switch (_Shape.ShapeType()) {
case TopAbs_COMPOUND:{
BRep_Builder builder;
TopoDS_Compound comp;//to be returned
builder.MakeCompound(comp);
// 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<TopoDS_Shape> shapesToProcess;
std::vector<TopoDS_Shape> shapesToReturn;
bool forceOutputCompound = false;
if (this->_Shape.ShapeType() == TopAbs_COMPOUND){
if (!intersection){
//simply recursively process the children, independently
TopoDS_Iterator it(_Shape);
for( ; it.More() ; it.Next()){
builder.Add(comp, TopoShape(it.Value()).makeOffset2D(offset, joinType, fill, allowOpenResult, intersection));
shapesToReturn.push_back( TopoShape(it.Value()).makeOffset2D(offset, joinType, fill, allowOpenResult, intersection) );
forceOutputCompound = true;
}
} else {
//collect all wires from this compound for collective offset. Process other shapes independently.
std::list<TopoDS_Wire> wiresToOffset;
//collect non-compounds from this compound for collective offset. Process other shapes independently.
TopoDS_Iterator it(_Shape);
for( ; it.More() ; it.Next()){
if(it.Value().ShapeType() == TopAbs_WIRE){
wiresToOffset.push_back(TopoDS::Wire(it.Value()));
} else if (it.Value().ShapeType() == TopAbs_EDGE){
wiresToOffset.push_back(BRepBuilderAPI_MakeWire(TopoDS::Edge(it.Value())).Wire());
if(it.Value().ShapeType() == TopAbs_COMPOUND){
//recursively process subcompounds
shapesToReturn.push_back( TopoShape(it.Value()).makeOffset2D(offset, joinType, fill, allowOpenResult, intersection) );
forceOutputCompound = true;
} else {
builder.Add(comp, TopoShape(it.Value()).makeOffset2D(offset, joinType, fill, allowOpenResult, intersection));
shapesToProcess.push_back(it.Value());
}
}
//check if we have more than two wires for a collective offset. Otherwise, fall back to recursive calling.
if (wiresToOffset.size() == 0){
//nothing to do
} else if (wiresToOffset.size() == 1) {
builder.Add(comp, TopoShape(wiresToOffset.front()).makeOffset2D(offset, joinType, fill, allowOpenResult, intersection));
} else {
//collective offset
BRepOffsetAPI_MakeOffset mkOffset(wiresToOffset.front(), GeomAbs_JoinType(joinType)
#if OCC_VERSION_HEX >= 0x060900
, allowOpenResult
#endif
);
for(TopoDS_Wire &w : wiresToOffset){
if (&w == &wiresToOffset.front())
continue;
mkOffset.AddWire(w);
}
if (fabs(offset) > Precision::Confusion()){
try {
#if defined(__GNUC__) && defined (FC_OS_LINUX)
Base::SignalException se;
#endif
mkOffset.Perform(offset);
}
catch (Standard_Failure &){
throw;
}
catch (...) {
throw Base::Exception("BRepOffsetAPI_MakeOffset has crashed! (Unknown exception caught)");
}
//Copying shape to fix strange orientation behavior, OCC7.0.0. See bug #2699
// http://www.freecadweb.org/tracker/view.php?id=2699
TopoDS_Shape offsetWire = BRepBuilderAPI_Copy(mkOffset.Shape()).Shape();
if (offsetWire.IsNull())
throw Base::Exception("makeOffset2D: result of offset is null!");
ShapeExtend_Explorer xp; //using this explorer allows to avoid checking output type
Handle_TopTools_HSequenceOfShape seq = xp.SeqFromCompound(offsetWire, /*recursive*/ true);
for(int i = 0 ; i < seq->Length() ; ++i){
builder.Add(comp, seq->Value(i+1));
}
} else {
//zero offset, dump all wires straight through...
for(TopoDS_Wire &w : wiresToOffset){
builder.Add(comp, w);
}
}
}
}
return comp;
} else {
shapesToProcess.push_back(this->_Shape);
}
}break;
case TopAbs_EDGE:
case TopAbs_WIRE:{
//convert edge to a wire if necessary...
TopoDS_Wire sourceWire;
if (_Shape.ShapeType() == TopAbs_WIRE){
sourceWire = TopoDS::Wire(_Shape);
} else { //edge
sourceWire = BRepBuilderAPI_MakeWire(TopoDS::Edge(_Shape)).Wire();
if(shapesToProcess.size() > 0){
//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<TopoDS_Wire> sourceWires;
bool haveWires = false;
bool haveFaces = false;
for(TopoDS_Shape &sh : shapesToProcess){
switch (sh.ShapeType()) {
case TopAbs_EDGE:
case TopAbs_WIRE:{
//convert edge to a wire if necessary...
TopoDS_Wire sourceWire;
if (sh.ShapeType() == TopAbs_WIRE){
sourceWire = TopoDS::Wire(sh);
} else { //edge
sourceWire = BRepBuilderAPI_MakeWire(TopoDS::Edge(sh)).Wire();
}
sourceWires.push_back(sourceWire);
haveWires = true;
}break;
case TopAbs_FACE:{
//get all wires of the face
TopoDS_Iterator it(sh);
for(; it.More(); it.Next()){
sourceWires.push_back(TopoDS::Wire(it.Value()));
}
haveFaces = true;
}break;
default:
throw Base::TypeError("makeOffset2D: input shape is not an edge, wire or face or compound of those.");
break;
}
}
if (haveWires && haveFaces)
throw Base::TypeError("makeOffset2D: collective offset of a mix of wires and faces is not supported");
if (haveFaces)
allowOpenResult = false;
//find plane.
gp_Pln workingPlane;
TopoDS_Compound compoundSourceWires;
{
BRep_Builder builder;
builder.MakeCompound(compoundSourceWires);
for(TopoDS_Wire &w : sourceWires)
builder.Add(compoundSourceWires, w);
BRepLib_FindSurface planefinder(compoundSourceWires, -1, Standard_True);
if (!planefinder.Found())
throw Base::Exception("makeOffset2D: wires are nonplanar or noncoplanar");
if (haveFaces){
//extract plane from first face (useful for preserving the plane of face precisely if dealing with only one face)
workingPlane = BRepAdaptor_Surface(TopoDS::Face(shapesToProcess[0])).Plane();
} else {
workingPlane = GeomAdaptor_Surface(planefinder.Surface()).Plane();
}
}
//do the offset..
TopoDS_Shape offsetWire;
BRepOffsetAPI_MakeOffset mkOffset(sourceWire, GeomAbs_JoinType(joinType)
TopoDS_Shape offsetShape;
BRepOffsetAPI_MakeOffset mkOffset(sourceWires[0], GeomAbs_JoinType(joinType)
#if OCC_VERSION_HEX >= 0x060900
, allowOpenResult
#endif
);
for(TopoDS_Wire &w : sourceWires)
if (&w != &(sourceWires[0])) //filter out first wire - it's already added
mkOffset.AddWire(w);
if (fabs(offset) > Precision::Confusion()){
try {
#if defined(__GNUC__) && defined (FC_OS_LINUX)
#if defined(__GNUC__) && defined (FC_OS_LINUX)
Base::SignalException se;
#endif
#endif
mkOffset.Perform(offset);
}
catch (Standard_Failure &){
@ -2188,207 +2206,175 @@ TopoDS_Shape TopoShape::makeOffset2D(double offset, short joinType, bool fill, b
catch (...) {
throw Base::Exception("BRepOffsetAPI_MakeOffset has crashed! (Unknown exception caught)");
}
offsetShape = mkOffset.Shape();
if(offsetShape.IsNull())
throw Base::Exception("makeOffset2D: result of offseting is null!");
//Copying shape to fix strange orientation behavior, OCC7.0.0. See bug #2699
// http://www.freecadweb.org/tracker/view.php?id=2699
offsetWire = BRepBuilderAPI_Copy(mkOffset.Shape()).Shape();
offsetShape = BRepBuilderAPI_Copy(offsetShape).Shape();
} else {
offsetWire = sourceWire;
offsetShape = sourceWires.size()>1 ? TopoDS_Shape(compoundSourceWires) : sourceWires[0];
}
if(offsetWire.IsNull())
throw Base::Exception("makeOffset2D: result of offseting is null!");
if (!fill)
return offsetWire;
if (fabs(offset) < Precision::Confusion())
throw Base::ValueError("makeOffset2D: offset distance is zero. Can't fill offset.");
//Fill offset...
BRepBuilderAPI_FindPlane planefinder(sourceWire);
if (!planefinder.Found()){
// non-planar wire.
throw Base::Exception("Strange, but offset worked on a non-planar wire. Filling is not supported.");
std::list<TopoDS_Wire> offsetWires;
//interestingly, if wires are removed, empty compounds are returned by MakeOffset (as of OCC 7.0.0)
//so, we just extract all nesting
Handle_TopTools_HSequenceOfShape seq = ShapeExtend_Explorer().SeqFromCompound(offsetShape, Standard_True);
TopoDS_Iterator it(offsetShape);
for(int i = 0 ; i < seq->Length() ; ++i){
offsetWires.push_back(TopoDS::Wire(seq->Value(i+1)));
}
//Planar wire. Make planar face...
//first up, the offset wire can be a compound. Let's break it up
std::list<TopoDS_Wire> wires;
if (offsetWire.ShapeType() == TopAbs_COMPOUND){
TopoDS_Iterator it(offsetWire);
for(; it.More(); it.Next()){
wires.push_back(TopoDS::Wire(it.Value()));
}
} else if (offsetWire.ShapeType() == TopAbs_WIRE) {
wires.push_back(TopoDS::Wire(offsetWire));
}
if(wires.size() == 0)
if(offsetWires.empty())
throw Base::Exception("makeOffset2D: offset result has no wires.");
//For the face, we also need the original wire. And we need to tell apart the outer wire for the face.
TopoDS_Wire* largestWire = nullptr;
bool sourceWireIsClosed = BRep_Tool::IsClosed(sourceWire);
if (sourceWireIsClosed && offset < 0){
//in this case, the original wire is the outer wire of the face
wires.push_front(sourceWire);
largestWire = &wires.front();
} else if (sourceWireIsClosed || !allowOpenResult) {
//Source wire may be closed or not, but the offset wire(s) is closed.
//find largest wire. It will be the outer wire of the face
double largestSizeSeenSoFar = -1.0;
for (TopoDS_Wire &w : wires){
Bnd_Box bb;
BRepBndLib::Add(w, bb);
if (bb.SquareExtent() > largestSizeSeenSoFar){
largestWire = &w;
largestSizeSeenSoFar = bb.SquareExtent();
}
}
//add source wire to the list.
if (BRep_Tool::IsClosed(sourceWire)){
wires.push_back(TopoDS::Wire(sourceWire));
std::list<TopoDS_Wire> wiresForMakingFaces;
if (!fill){
if (haveFaces){
wiresForMakingFaces = offsetWires;
} else {
for(TopoDS_Wire &w : offsetWires)
shapesToReturn.push_back(w);
}
} else {
//source wire, and one of the offset wires are open.
//fill offset
if (fabs(offset) < Precision::Confusion())
throw Base::ValueError("makeOffset2D: offset distance is zero. Can't fill offset.");
//find the open offset wire
TopoDS_Wire openOffsetWire;
//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
for( std::list<TopoDS_Wire>::iterator it = wires.begin() ; it != wires.end() ; ++it){
if (!BRep_Tool::IsClosed(*it)){
openOffsetWire = *it;
wires.erase(it);
break;
}
}
if(openOffsetWire.IsNull())
throw Base::Exception("makeOffset2D: filling offset: expected to find an open wire in offset result, but there isn't one.");
//first, lets split apart closed and open wires.
std::list<TopoDS_Wire> closedWires;
std::list<TopoDS_Wire> openWires;
for(TopoDS_Wire &w : sourceWires)
if (BRep_Tool::IsClosed(w))
closedWires.push_back(w);
else
openWires.push_back(w);
for(TopoDS_Wire &w : offsetWires)
if (BRep_Tool::IsClosed(w))
closedWires.push_back(w);
else
openWires.push_back(w);
//join up the (open) source wire and open offset wire. This will be
//the outer wire for the face. The remaining wires are holes.
//find open vertices of source wire
BRepTools_WireExplorer xp;
xp.Init(openOffsetWire);
TopoDS_Vertex v1 = xp.CurrentVertex();
for(;xp.More();xp.Next()){};
TopoDS_Vertex v2 = xp.CurrentVertex();
//find open vertices of offset wire
xp.Init(sourceWire);
TopoDS_Vertex v3 = xp.CurrentVertex();
for(;xp.More();xp.Next()){};
TopoDS_Vertex v4 = xp.CurrentVertex();
//check
if (v1.IsNull()) throw Base::Exception("v1 is null");
if (v2.IsNull()) throw Base::Exception("v2 is null");
if (v3.IsNull()) throw Base::Exception("v3 is null");
if (v4.IsNull()) throw Base::Exception("v4 is null");
//assemble new wire
// hack. It seems that direction of offset wire and closed offset
// wires is always consistent for good facemaking, but does not
// care of the direction of original wire. So, we will reverse
// original wire if necessary.
//we want the connection order to be
//v1 -> openOffsetWire -> v2 -> (new edge) -> v4 -> sourceWire(rev) -> v3 -> (new edge) -> v1
//let's check if it's the case. If not, we reverse source wire and swap its endpoints.
// I tried to use mkOffset.Generated(v3) for the purpose, but
//returned was an empty list. So I find vertex correspondence by
//testing if the distance between them is equal to offset. --DeepSOIC
if (fabs(gp_Vec(BRep_Tool::Pnt(v2), BRep_Tool::Pnt(v3)).Magnitude() - fabs(offset)) <= BRep_Tool::Tolerance(v2) + BRep_Tool::Tolerance(v3)){
sourceWire.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
wiresForMakingFaces = closedWires;
if (!allowOpenResult || openWires.size() == 0){
//just ignore all open wires
} else {
throw Base::Exception("makeOffset2D: fill offset: failed to establish open vertex relationship.");
//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)
throw Base::Exception("makeOffset2D: collective offset with filling of multiple wires is not supported yet.");
TopoDS_Wire openWire1 = openWires.front();
TopoDS_Wire openWire2 = openWires.back();
//find open vertices
BRepTools_WireExplorer xp;
xp.Init(openWire1);
TopoDS_Vertex v1 = xp.CurrentVertex();
for(;xp.More();xp.Next()){};
TopoDS_Vertex v2 = xp.CurrentVertex();
//find open vertices
xp.Init(openWire2);
TopoDS_Vertex v3 = xp.CurrentVertex();
for(;xp.More();xp.Next()){};
TopoDS_Vertex v4 = xp.CurrentVertex();
//check
if (v1.IsNull()) throw Base::Exception("v1 is null");
if (v2.IsNull()) throw Base::Exception("v2 is null");
if (v3.IsNull()) throw Base::Exception("v3 is null");
if (v4.IsNull()) throw Base::Exception("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.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 {
throw Base::Exception("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(openWire1); 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.Reverse();
for(it.Init(TopoDS::Wire(openWire2)); it.More(); it.Next()){
mkWire.Add(it.Current());
}
//add final joining edge
mkWire.Add(BRepBuilderAPI_MakeEdge(v3,v1).Edge());
mkWire.Build();
wiresForMakingFaces.push_front(mkWire.Wire());
}
}
//now directions of source wire and offset wire are aligned. Finally. make new wire!
BRepBuilderAPI_MakeWire mkWire;
//add openOffsetWire
BRepTools_WireExplorer it;
for(it.Init(openOffsetWire); it.More(); it.Next()){
mkWire.Add(it.Current());
//make faces
if (wiresForMakingFaces.size()>0){
FaceMakerBullseye mkFace;
mkFace.setPlane(workingPlane);
for(TopoDS_Wire &w : wiresForMakingFaces){
mkFace.addWire(w);
}
//add first joining edge
mkWire.Add(BRepBuilderAPI_MakeEdge(v2,v4).Edge());
//add original wire, in reverse order
sourceWire.Reverse();
for(it.Init(TopoDS::Wire(sourceWire)); it.More(); it.Next()){
mkWire.Add(it.Current());
}
//add final joining edge
mkWire.Add(BRepBuilderAPI_MakeEdge(v3,v1).Edge());
mkFace.Build();
if (mkFace.Shape().IsNull())
throw Base::Exception("makeOffset2D: making face failed (null shape returned).");
TopoDS_Shape result = mkFace.Shape();
if (haveFaces && shapesToProcess.size() == 1)
result.Orientation(shapesToProcess[0].Orientation());
mkWire.Build();
wires.push_front(mkWire.Wire());
largestWire = &wires.front();
ShapeExtend_Explorer xp;
Handle_TopTools_HSequenceOfShape result_leaves = xp.SeqFromCompound(result, Standard_True);
for(int i = 0 ; i < result_leaves->Length() ; ++i)
shapesToReturn.push_back(result_leaves->Value(i+1));
}
//make the face
//TODO: replace all this reverseness alchemy with a common direction-tolerant face-with-holes-making code
BRepBuilderAPI_MakeFace mkFace(TopoDS::Wire(offset < 0 ? (*largestWire) : (*largestWire).Reversed()));
for(TopoDS_Wire &w : wires){
if (&w != largestWire)
mkFace.Add(TopoDS::Wire(w.Reversed()));
}
mkFace.Build();
if (mkFace.Shape().IsNull())
throw Base::Exception("makeOffset2D: making face failed (null shape returned).");
return mkFace.Shape();
}break;
case TopAbs_FACE:{
throw Base::TypeError("2d offsetting is not yet suported on faces, yet.");
//the following code works, but returns a wire. I'd rather want a face,
//but that is complicated, and best addressed by writing a powerful
//face-with-holes-maker mentioned a few lines above. Exposing it like
//this will cause breaking changes later, so I decided to disable it
//altogether, until a proper implementation is done.
// --DeepSOIC
TopoDS_Face sourceFace = TopoDS::Face(_Shape);
BRepOffsetAPI_MakeOffset mkOffset(sourceFace, GeomAbs_JoinType(joinType)
#if OCC_VERSION_HEX >= 0x060900
, allowOpenResult
#endif
);
try {
#if defined(__GNUC__) && defined (FC_OS_LINUX)
Base::SignalException se;
#endif
mkOffset.Perform(offset);
}
catch (Standard_Failure &){
throw;
}
catch (...) {
throw Base::Exception("BRepOffsetAPI_MakeOffset has crashed! (Unknown exception caught)");
}
if (mkOffset.Shape().IsNull())
throw Base::Exception("makeOffset2D: result shape is null!");
if (fill)
throw Base::ValueError("Filling the offset is not supported for 2d-offsetting of faces, yet.");
return mkOffset.Shape();
}break;
default:
throw Base::TypeError("makeOffset2D: input shape is not an edge, wire or face or compound of those.");
break;
}
//assemble output compound
if (shapesToReturn.empty())
return TopoDS_Shape(); //failure
if (shapesToReturn.size() > 1 || forceOutputCompound){
TopoDS_Compound result;
BRep_Builder builder;
builder.MakeCompound(result);
for(TopoDS_Shape &sh : shapesToReturn)
builder.Add(result, sh);
return result;
} else {
return shapesToReturn[0];
}
}
TopoDS_Shape TopoShape::makeThickSolid(const TopTools_ListOfShape& remFace,

View File

@ -1515,6 +1515,7 @@ void CmdPartOffset::activated(int iMsg)
doCommand(Doc,"App.ActiveDocument.%s.Source = App.ActiveDocument.%s" ,offset.c_str(), shape->getNameInDocument());
doCommand(Doc,"App.ActiveDocument.%s.Value = 1.0",offset.c_str());
updateActive();
doCommand(Gui,"Gui.ActiveDocument.%s.DisplayMode = 'Wireframe'", shape->getNameInDocument());
//if (isActiveObjectValid())
// doCommand(Gui,"Gui.ActiveDocument.hide(\"%s\")",shape->getNameInDocument());
doCommand(Gui,"Gui.ActiveDocument.setEdit('%s')",offset.c_str());