diff --git a/src/Mod/Part/App/TopoShape.cpp b/src/Mod/Part/App/TopoShape.cpp index fe2743e8b..e9062645f 100644 --- a/src/Mod/Part/App/TopoShape.cpp +++ b/src/Mod/Part/App/TopoShape.cpp @@ -45,6 +45,7 @@ # include # include # include +# include # include # include # include @@ -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 shapesToProcess; + std::vector 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 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 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 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 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 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::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 closedWires; + std::list 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, diff --git a/src/Mod/Part/Gui/Command.cpp b/src/Mod/Part/Gui/Command.cpp index 5c588b2fa..ff3083313 100644 --- a/src/Mod/Part/Gui/Command.cpp +++ b/src/Mod/Part/Gui/Command.cpp @@ -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());