From a3f46a40e92c92d59590318472ef75ec2ebeaaf4 Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Fri, 27 Jan 2017 17:13:16 +0800 Subject: [PATCH] Path: added Path.fromShapes and Path.sortWires * Path.fromShapes can now convert any number of shapes to Path with optimzied travel distances. It internally uses Path.sortWires to minimize travel distances, and also sort wires by its Z height in case of sectioned wires. * The above python function is impelmented in Path::Area class. * Path::FeatureShape is rewrote to take advantage of these two functions. * Add Path::FeatureAreaView to partially display a Path::FeatureArea's sections. --- src/Mod/Path/App/AppPath.cpp | 2 + src/Mod/Path/App/AppPathPy.cpp | 169 ++++- src/Mod/Path/App/Area.cpp | 582 ++++++++++++---- src/Mod/Path/App/Area.h | 95 ++- src/Mod/Path/App/AreaParams.h | 71 +- src/Mod/Path/App/AreaPy.xml | 5 + src/Mod/Path/App/AreaPyImp.cpp | 75 +- src/Mod/Path/App/FeatureArea.cpp | 95 ++- src/Mod/Path/App/FeatureArea.h | 31 +- src/Mod/Path/App/FeatureAreaPyImp.cpp | 2 +- src/Mod/Path/App/FeaturePathShape.cpp | 105 +-- src/Mod/Path/App/FeaturePathShape.h | 12 +- src/Mod/Path/App/Path.h | 188 ++--- src/Mod/Path/Gui/AppPathGui.cpp | 2 + src/Mod/Path/Gui/Command.cpp | 101 ++- src/Mod/Path/Gui/Resources/Path.qrc | 1 + .../Gui/Resources/icons/Path-Area-View.svg | 657 ++++++++++++++++++ src/Mod/Path/Gui/ViewProviderArea.cpp | 72 ++ src/Mod/Path/Gui/ViewProviderArea.h | 23 + src/Mod/Path/Gui/ViewProviderPathShape.cpp | 76 +- src/Mod/Path/Gui/ViewProviderPathShape.h | 13 + src/Mod/Path/InitGui.py | 4 +- src/Mod/Path/PathCommands.py | 25 + src/Mod/Path/libarea/Area.cpp | 52 ++ src/Mod/Path/libarea/Area.h | 2 + src/Mod/Path/libarea/AreaClipper.cpp | 11 +- 26 files changed, 2093 insertions(+), 378 deletions(-) create mode 100644 src/Mod/Path/Gui/Resources/icons/Path-Area-View.svg diff --git a/src/Mod/Path/App/AppPath.cpp b/src/Mod/Path/App/AppPath.cpp index 4723ff7b1..3a9e1cd0e 100644 --- a/src/Mod/Path/App/AppPath.cpp +++ b/src/Mod/Path/App/AppPath.cpp @@ -88,4 +88,6 @@ PyMODINIT_FUNC initPath() Path::Area ::init(); Path::FeatureArea ::init(); Path::FeatureAreaPython ::init(); + Path::FeatureAreaView ::init(); + Path::FeatureAreaViewPython ::init(); } diff --git a/src/Mod/Path/App/AppPathPy.cpp b/src/Mod/Path/App/AppPathPy.cpp index 79365f083..36371b451 100644 --- a/src/Mod/Path/App/AppPathPy.cpp +++ b/src/Mod/Path/App/AppPathPy.cpp @@ -37,6 +37,7 @@ #include #include +#include #include #include #include @@ -57,7 +58,45 @@ #include "Path.h" #include "FeaturePath.h" #include "FeaturePathCompound.h" +#include "Area.h" +#define PATH_CATCH catch (Standard_Failure &e) \ + { \ + std::string str; \ + Standard_CString msg = e.GetMessageString(); \ + str += typeid(e).name(); \ + str += " "; \ + if (msg) {str += msg;} \ + else {str += "No OCCT Exception Message";} \ + Base::Console().Error(str.c_str()); \ + PyErr_SetString(Part::PartExceptionOCCError,str.c_str()); \ + } \ + catch(Base::Exception &e) \ + { \ + std::string str; \ + str += "FreeCAD exception thrown ("; \ + str += e.what(); \ + str += ")"; \ + e.ReportException(); \ + PyErr_SetString(Base::BaseExceptionFreeCADError,str.c_str());\ + } \ + catch(std::exception &e) \ + { \ + std::string str; \ + str += "STL exception thrown ("; \ + str += e.what(); \ + str += ")"; \ + Base::Console().Error(str.c_str()); \ + PyErr_SetString(Base::BaseExceptionFreeCADError,str.c_str());\ + } \ + catch(const char *e) \ + { \ + PyErr_SetString(Base::BaseExceptionFreeCADError,e); \ + } throw Py::Exception(); + +namespace Part { +extern PartExport Py::Object shape2pyshape(const TopoDS_Shape &shape); +} namespace Path { class Module : public Py::ExtensionModule @@ -79,6 +118,22 @@ public: add_varargs_method("fromShape",&Module::fromShape, "fromShape(Shape): Returns a Path object from a Part Shape" ); + add_keyword_method("fromShapes",&Module::fromShapes, + "fromShapes(shapes, sort=True, start=Vector(), " PARAM_PY_ARGS_DOC(ARG,AREA_PARAMS_PATH) ")\n" + "\nReturns a Path object from a list of shapes\n" + "\n* shapes: input list of shapes.\n" + "\n* start (Vector()): optional start position.\n" + PARAM_PY_DOC(ARG, AREA_PARAMS_PATH) + ); + add_keyword_method("sortWires",&Module::sortWires, + "sortWires(shapes, start=Vector(), params=None, " PARAM_PY_ARGS_DOC(ARG,AREA_PARAMS_SORT_WIRES) ")\n" + "\nReturns (wires,end), where 'wires' is sorted accross Z value and with optimized travel distance,\n" + "and 'end' is the ending position of the whole wires\n" + "\n* shapes: input shape list\n" + "\n* start (Vector()): optional start position.\n" + "\n* params (None): optional dictionary for configuring Path.Area internally used to sort the wires.\n" + PARAM_PY_DOC(ARG, AREA_PARAMS_SORT_WIRES) + ); initialize("This module is the Path module."); // register with Python } @@ -261,7 +316,119 @@ private: throw Py::RuntimeError(e.what()); } } - + + Py::Object fromShapes(const Py::Tuple& args, const Py::Dict &kwds) + { + PARAM_PY_DECLARE_INIT(PARAM_FARG,AREA_PARAMS_PATH) + PyObject *pShapes=NULL; + PyObject *start=NULL; + static char* kwd_list[] = {"shapes", "start", + PARAM_FIELD_STRINGS(ARG,AREA_PARAMS_PATH), NULL}; + if (!PyArg_ParseTupleAndKeywords(args.ptr(), kwds.ptr(), + "O|O!" PARAM_PY_KWDS(AREA_PARAMS_PATH), kwd_list, + &pShapes, &(Base::VectorPy::Type), &start, + PARAM_REF(PARAM_FARG,AREA_PARAMS_PATH))) + throw Py::Exception(); + + std::list shapes; + if (PyObject_TypeCheck(pShapes, &(Part::TopoShapePy::Type))) + shapes.push_back(static_cast(pShapes)->getTopoShapePtr()->getShape()); + else if (PyObject_TypeCheck(pShapes, &(PyList_Type)) || + PyObject_TypeCheck(pShapes, &(PyTuple_Type))) + { + Py::Sequence shapeSeq(pShapes); + for (Py::Sequence::iterator it = shapeSeq.begin(); it != shapeSeq.end(); ++it) { + PyObject* item = (*it).ptr(); + if(!PyObject_TypeCheck(item, &(Part::TopoShapePy::Type))) { + PyErr_SetString(PyExc_TypeError, "non-shape object in sequence"); + throw Py::Exception(); + } + shapes.push_back(static_cast(item)->getTopoShapePtr()->getShape()); + } + } + + gp_Pnt pstart; + if(start) { + Base::Vector3d vec = static_cast(start)->value(); + pstart.SetCoord(vec.x, vec.y, vec.z); + } + + try { + std::unique_ptr path(new Toolpath); + Area::toPath(*path,shapes,&pstart,PARAM_PY_FIELDS(PARAM_FARG,AREA_PARAMS_PATH)); + return Py::asObject(new PathPy(path.release())); + } PATH_CATCH + } + + Py::Object sortWires(const Py::Tuple& args, const Py::Dict &kwds) + { + AreaParams params; + PARAM_PY_DECLARE_INIT(PARAM_FARG,AREA_PARAMS_SORT_WIRES) + PyObject *pShapes=NULL; + PyObject *start=NULL; + PyObject *pParams=NULL; + static char* kwd_list[] = {"shapes", "start", "params", + PARAM_FIELD_STRINGS(ARG,AREA_PARAMS_SORT_WIRES), NULL}; + if (!PyArg_ParseTupleAndKeywords(args.ptr(), kwds.ptr(), + "O|O!O!" PARAM_PY_KWDS(AREA_PARAMS_SORT_WIRES), kwd_list, + &pShapes, &(Base::VectorPy::Type), &start, &PyDict_Type, &pParams, + PARAM_REF(PARAM_FARG,AREA_PARAMS_SORT_WIRES))) + throw Py::Exception(); + + std::list shapes; + if (PyObject_TypeCheck(pShapes, &(Part::TopoShapePy::Type))) + shapes.push_back(static_cast(pShapes)->getTopoShapePtr()->getShape()); + else if (PyObject_TypeCheck(pShapes, &(PyList_Type)) || + PyObject_TypeCheck(pShapes, &(PyTuple_Type))) { + Py::Sequence shapeSeq(pShapes); + for (Py::Sequence::iterator it = shapeSeq.begin(); it != shapeSeq.end(); ++it) { + PyObject* item = (*it).ptr(); + if(!PyObject_TypeCheck(item, &(Part::TopoShapePy::Type))) { + PyErr_SetString(PyExc_TypeError, "non-shape object in sequence"); + throw Py::Exception(); + } + shapes.push_back(static_cast(item)->getTopoShapePtr()->getShape()); + } + } + + if(pParams) { + static char *kwlist[] = {PARAM_FIELD_STRINGS(NAME,AREA_PARAMS_CONF),NULL}; + PARAM_PY_DECLARE(PARAM_FNAME,AREA_PARAMS_CONF); +#define AREA_SET(_param) \ + PARAM_FNAME(_param) = \ + PARAM_TYPED(PARAM_PY_CAST_,_param)(params.PARAM_FNAME(_param)); + PARAM_FOREACH(AREA_SET,AREA_PARAMS_CONF) + if (!PyArg_ParseTupleAndKeywords(NULL, pParams, + "|" PARAM_PY_KWDS(AREA_PARAMS_CONF), kwlist, + PARAM_REF(PARAM_FNAME,AREA_PARAMS_CONF))) + throw Py::Exception(); + +#define AREA_GET(_param) \ + params.PARAM_FNAME(_param) = \ + PARAM_TYPED(PARAM_CAST_PY_,_param)(PARAM_FNAME(_param)); + PARAM_FOREACH(AREA_GET,AREA_PARAMS_CONF) + } + + gp_Pnt pstart,pend; + if(start) { + Base::Vector3d vec = static_cast(start)->value(); + pstart.SetCoord(vec.x, vec.y, vec.z); + } + + try { + std::list wires = Area::sortWires(shapes,¶ms,&pstart, + &pend, PARAM_PY_FIELDS(PARAM_FARG,AREA_PARAMS_SORT_WIRES)); + PyObject *list = PyList_New(0); + for(auto &wire : wires) + PyList_Append(list,Py::new_reference_to( + Part::shape2pyshape(TopoDS::Wire(wire)))); + PyObject *ret = PyTuple_New(2); + PyTuple_SetItem(ret,0,list); + PyTuple_SetItem(ret,1,new Base::VectorPy( + Base::Vector3d(pend.X(),pend.Y(),pend.Z()))); + return Py::asObject(ret); + } PATH_CATCH + } }; PyObject* initModule() diff --git a/src/Mod/Path/App/Area.cpp b/src/Mod/Path/App/Area.cpp index 58516bab9..51d690890 100644 --- a/src/Mod/Path/App/Area.cpp +++ b/src/Mod/Path/App/Area.cpp @@ -24,6 +24,8 @@ #ifndef _PreComp_ #endif +#include + #include #include #include @@ -150,6 +152,7 @@ void Area::setPlane(const TopoDS_Shape &shape) { } bool Area::isCoplanar(const TopoDS_Shape &s1, const TopoDS_Shape &s2) { + if(s1.IsEqual(s2)) return true; TopoDS_Builder builder; TopoDS_Compound comp; builder.MakeCompound(comp); @@ -161,7 +164,7 @@ bool Area::isCoplanar(const TopoDS_Shape &s1, const TopoDS_Shape &s2) { int Area::add(CArea &area, const TopoDS_Shape &shape, const gp_Trsf *trsf, double deflection, const TopoDS_Shape *plane, bool force_coplanar, - CArea *areaOpen, bool to_edges, bool reorder) + CArea *areaOpen, bool to_edges, bool reorient) { bool haveShape = false; int skipped = 0; @@ -193,7 +196,7 @@ int Area::add(CArea &area, const TopoDS_Shape &shape, const gp_Trsf *trsf, else if(to_edges) { for (TopExp_Explorer it(wire, TopAbs_EDGE); it.More(); it.Next()) add(_areaOpen,BRepBuilderAPI_MakeWire( - TopoDS::Edge(it.Current())).Wire(),trsf,deflection); + TopoDS::Edge(it.Current())).Wire(),trsf,deflection,true); }else add(_areaOpen,wire,trsf,deflection); } @@ -210,7 +213,7 @@ int Area::add(CArea &area, const TopoDS_Shape &shape, const gp_Trsf *trsf, } } - if(reorder) + if(reorient) _area.Reorder(); area.m_curves.splice(area.m_curves.end(),_area.m_curves); if(areaOpen) @@ -221,7 +224,7 @@ int Area::add(CArea &area, const TopoDS_Shape &shape, const gp_Trsf *trsf, } void Area::add(CArea &area, const TopoDS_Wire& wire, - const gp_Trsf *trsf, double deflection) + const gp_Trsf *trsf, double deflection, bool to_edges) { CCurve ccurve; BRepTools_WireExplorer xp(trsf?TopoDS::Wire( @@ -239,26 +242,32 @@ void Area::add(CArea &area, const TopoDS_Wire& wire, switch (curve.GetType()) { case GeomAbs_Line: { ccurve.append(CVertex(Point(p.X(),p.Y()))); + if(to_edges) { + area.append(ccurve); + ccurve.m_vertices.pop_front(); + } break; } case GeomAbs_Circle:{ - double first = curve.FirstParameter(); - double last = curve.LastParameter(); - gp_Circ circle = curve.Circle(); - gp_Ax1 axis = circle.Axis(); - int dir = axis.Direction().Z()<0?-1:1; - if(reversed) dir = -dir; - gp_Pnt loc = axis.Location(); - if(fabs(first-last)>M_PI) { - // Split arc(circle) larger than half circle. This is - // translated from PathUtil code. Not sure why it is - // needed. - gp_Pnt mid = curve.Value((last-first)*0.5+first); - ccurve.append(CVertex(dir,Point(mid.X(),mid.Y()), + if(!to_edges) { + double first = curve.FirstParameter(); + double last = curve.LastParameter(); + gp_Circ circle = curve.Circle(); + gp_Ax1 axis = circle.Axis(); + int dir = axis.Direction().Z()<0?-1:1; + if(reversed) dir = -dir; + gp_Pnt loc = axis.Location(); + if(fabs(first-last)>M_PI) { + // Split arc(circle) larger than half circle. Because gcode + // can't handle full circle? + gp_Pnt mid = curve.Value((last-first)*0.5+first); + ccurve.append(CVertex(dir,Point(mid.X(),mid.Y()), + Point(loc.X(),loc.Y()))); + } + ccurve.append(CVertex(dir,Point(p.X(),p.Y()), Point(loc.X(),loc.Y()))); + break; } - ccurve.append(CVertex(dir,Point(p.X(),p.Y()), - Point(loc.X(),loc.Y()))); - break; + //fall through } default: { // Discretize all other type of curves GCPnts_UniformDeflection discretizer(curve, deflection, @@ -268,16 +277,22 @@ void Area::add(CArea &area, const TopoDS_Wire& wire, for (int i=1; i<=nbPoints; i++) { gp_Pnt pt = discretizer.Value (i); ccurve.append(CVertex(Point(pt.X(),pt.Y()))); + if(to_edges) { + area.append(ccurve); + ccurve.m_vertices.pop_front(); + } } }else Standard_Failure::Raise("Curve discretization failed"); }} } - if(BRep_Tool::IsClosed(wire) && !ccurve.IsClosed()) { - Base::Console().Warning("ccurve not closed\n"); - ccurve.append(ccurve.m_vertices.front()); + if(!to_edges) { + if(BRep_Tool::IsClosed(wire) && !ccurve.IsClosed()) { + Base::Console().Warning("ccurve not closed\n"); + ccurve.append(ccurve.m_vertices.front()); + } + area.append(ccurve); } - area.append(ccurve); } @@ -296,10 +311,32 @@ void Area::clean(bool deleteShapes) { } void Area::add(const TopoDS_Shape &shape,short op) { -#define AREA_SRC_OP(_param) op - PARAM_ENUM_CONVERT(AREA_SRC_OP,PARAM_FNAME,PARAM_ENUM_EXCEPT,AREA_PARAMS_OPCODE); - Q_UNUSED(Operation); +#define AREA_CONVERT_OP \ + ClipperLib::ClipType Operation;\ + switch(op){\ + case OperationUnion:\ + Operation = ClipperLib::ctUnion;\ + break;\ + case OperationDifference:\ + Operation = ClipperLib::ctDifference;\ + break;\ + case OperationIntersection:\ + Operation = ClipperLib::ctIntersection;\ + break;\ + case OperationXor:\ + Operation = ClipperLib::ctXor;\ + break;\ + default:\ + throw Base::ValueError("invalid Operation");\ + } + if(shape.IsNull()) + throw Base::ValueError("null shape"); + + if(op!=OperationCompound) { + AREA_CONVERT_OP; + Q_UNUSED(Operation); + } bool haveSolid = false; for(TopExp_Explorer it(shape, TopAbs_SOLID);it.More();) { haveSolid = true; @@ -313,7 +350,7 @@ void Area::add(const TopoDS_Shape &shape,short op) { myHaveSolid = haveSolid; clean(); - if(myShapes.empty()) + if(op!=OperationCompound && myShapes.empty()) op = OperationUnion; myShapes.push_back(Shape(op,shape)); } @@ -330,7 +367,7 @@ void Area::setParams(const AreaParams ¶ms) { } void Area::addToBuild(CArea &area, const TopoDS_Shape &shape) { - if(!myHaveFace) { + if(myParams.Fill==FillAuto && !myHaveFace) { TopExp_Explorer it(shape, TopAbs_FACE); myHaveFace = it.More(); } @@ -342,7 +379,7 @@ void Area::addToBuild(CArea &area, const TopoDS_Shape &shape) { CArea areaOpen; mySkippedShapes += add(area,shape,&myTrsf,myParams.Deflection,plane, myHaveSolid||myParams.Coplanar==CoplanarForce,&areaOpen, - myParams.OpenMode==OpenModeEdges,myParams.Reorder); + myParams.OpenMode==OpenModeEdges,myParams.Reorient); if(areaOpen.m_curves.size()) { if(&area == myArea.get() || myParams.OpenMode == OpenModeNone) myAreaOpen->m_curves.splice(myAreaOpen->m_curves.end(),areaOpen.m_curves); @@ -359,7 +396,7 @@ void Area::build() { if(myArea || mySections.size()) return; if(myShapes.empty()) - throw Base::ValueError("Null shape"); + throw Base::ValueError("no shape added"); #define AREA_SRC(_param) myParams.PARAM_FNAME(_param) PARAM_ENUM_CONVERT(AREA_SRC,PARAM_FNAME,PARAM_ENUM_EXCEPT,AREA_PARAMS_CLIPPER_FILL); @@ -368,24 +405,41 @@ void Area::build() { myShapePlane.Nullify(); for(const Shape &s : myShapes) { bool haveShape = false; + bool done = false; + TopoDS_Shape shapePlane; + gp_Trsf trsf; + gp_Ax3 pos; #define AREA_CHECK_PLANE(_type) \ + shapePlane.Nullify();\ for(TopExp_Explorer it(s.shape, TopAbs_##_type); it.More(); it.Next()) {\ haveShape = true;\ BRepLib_FindSurface planeFinder(it.Current(),-1,Standard_True);\ if (!planeFinder.Found())\ continue;\ - myShapePlane = it.Current();\ - myTrsf.SetTransformation(GeomAdaptor_Surface(\ - planeFinder.Surface()).Plane().Position());\ - break;\ + shapePlane = it.Current();\ + pos = GeomAdaptor_Surface(planeFinder.Surface()).Plane().Position();\ + trsf.SetTransformation(pos);\ + gp_Dir dir(pos.Direction());\ + if(fabs(dir.X()) zMax-zMin) + if(count<0 || count*myParams.Stepdown > zMax-zMin) { count = ceil((zMax-zMin)/myParams.Stepdown); + if((count-1)*myParams.Stepdown < zMax-zMin) + ++count; + } for(int i=0;im_curves.splice(myArea->m_curves.end(),myAreaOpen->m_curves); pending = false; - PARAM_ENUM_CONVERT(AREA_SRC_OP,PARAM_FNAME,PARAM_ENUM_EXCEPT,AREA_PARAMS_OPCODE); - myArea->Clip(Operation,&areaClip,SubjectFill,ClipFill); - areaClip.m_curves.clear(); + if(areaClip.m_curves.size()) { + if(op == OperationCompound) + myArea->m_curves.splice(myArea->m_curves.end(),areaClip.m_curves); + else{ + AREA_CONVERT_OP; + myArea->Clip(Operation,&areaClip,SubjectFill,ClipFill); + areaClip.m_curves.clear(); + } + } op=s.op; } addToBuild(op==OperationUnion?*myArea:areaClip,s.shape); @@ -521,17 +582,106 @@ void Area::build() { if(pending){ if(myParams.OpenMode!=OpenModeNone) myArea->m_curves.splice(myArea->m_curves.end(),myAreaOpen->m_curves); - PARAM_ENUM_CONVERT(AREA_SRC_OP,PARAM_FNAME,PARAM_ENUM_EXCEPT,AREA_PARAMS_OPCODE); - myArea->Clip(Operation,&areaClip,SubjectFill,ClipFill); + if(op == OperationCompound) + myArea->m_curves.splice(myArea->m_curves.end(),areaClip.m_curves); + else{ + AREA_CONVERT_OP; + myArea->Clip(Operation,&areaClip,SubjectFill,ClipFill); + } } myArea->m_curves.splice(myArea->m_curves.end(),myAreaOpen->m_curves); + //Reassemble wires after explode + if(myParams.Explode) { + std::list edges; + gp_Trsf trsf(myTrsf.Inverted()); + for(const auto &c : myArea->m_curves) { + TopoDS_Wire wire = toShape(c,&trsf); + if(wire.IsNull()) continue; + TopExp_Explorer it(wire, TopAbs_EDGE); + edges.push_back(TopoDS::Edge(it.Current())); + } + Area area(&myParams); + area.myParams.Explode = false; + area.myParams.Coplanar = CoplanarNone; + area.myWorkPlane = myWorkPlane.IsNull()?myShapePlane:myWorkPlane; + area.myTrsf = myTrsf; + while(edges.size()) { + BRepBuilderAPI_MakeWire mkWire; + for(const auto &e : Part::sort_Edges(myParams.Tolerance,edges)) + mkWire.Add(TopoDS::Edge(e)); + area.add(mkWire.Wire(),OperationCompound); + } + area.build(); + myArea = std::move(area.myArea); + } + }catch(...) { clean(); throw; } } +list Area::sortWires(int index, int count, const gp_Pnt *pstart, + gp_Pnt *_pend, PARAM_ARGS(PARAM_FARG,AREA_PARAMS_MIN_DIST)) +{ + std::list wires; + + build(); + + gp_Pnt pend,pt; + if(pstart) pt = *pstart; + + pt.Transform(TopLoc_Location(myTrsf)); + + if(mySections.size()) { + if(index>=(int)mySections.size()) + throw Base::ValueError("index out of bound"); + TopLoc_Location loc(myTrsf.Inverted()); + if(index<0) { + index = 0; + count = mySections.size(); + } + if(count<=0 || count>(int)mySections.size()) + count = mySections.size(); + for(int i=index;i ws = + mySections[i]->sortWires(-1,0,&pt,&pend, + PARAM_FIELDS(PARAM_FARG,AREA_PARAMS_MIN_DIST)); + for(auto &wire : ws) + wires.push_back(wire.Moved(loc)); + pt = pend; + } + if(_pend) + *_pend = pend.Transformed(loc); + return wires; + } + + if(!myArea || myArea->m_curves.empty()) return wires; + + CArea area(*myArea); + Point p(pt.X(),pt.Y()); + area.ChangeStartToNearest(&p, + PARAM_FIELDS(PARAM_FARG,AREA_PARAMS_MIN_DIST)); + gp_Trsf trsf(myTrsf.Inverted()); + for(const CCurve &c : area.m_curves) { + const TopoDS_Wire &wire = toShape(c,&trsf); + if(wire.IsNull()) continue; + wires.push_back(toShape(c,&trsf)); + } + if(_pend) { + gp_Pnt pend = pt; + if(area.m_curves.size() && + area.m_curves.back().m_vertices.size()) + { + const Point &pt = area.m_curves.back().m_vertices.back().m_p; + pend.SetCoord(pt.x,pt.y,0.0); + } + *_pend = pend.Transformed(TopLoc_Location(trsf)); + } + return wires; +} + TopoDS_Shape Area::toShape(CArea &area, short fill) { gp_Trsf trsf(myTrsf.Inverted()); bool bFill; @@ -559,14 +709,17 @@ TopoDS_Shape Area::toShape(CArea &area, short fill) { #define AREA_SECTION(_op,_index,...) do {\ if(mySections.size()) {\ if(_index>=(int)mySections.size())\ - throw Base::ValueError("index out of bound");\ + return TopoDS_Shape();\ TopLoc_Location loc(myTrsf.Inverted());\ if(_index<0) {\ BRep_Builder builder;\ TopoDS_Compound compound;\ builder.MakeCompound(compound);\ - for(shared_ptr area : mySections)\ - builder.Add(compound,area->_op(-1, ## __VA_ARGS__).Moved(loc));\ + for(shared_ptr area : mySections){\ + const TopoDS_Shape &s = area->_op(-1, ## __VA_ARGS__);\ + if(s.IsNull()) continue;\ + builder.Add(compound,s.Moved(loc));\ + }\ return compound;\ }\ return mySections[_index]->_op(-1, ## __VA_ARGS__).Moved(loc);\ @@ -637,6 +790,7 @@ TopoDS_Shape Area::getShape(int index) { for(shared_ptr area : areas) { if(myParams.Thicken) area->Thicken(myParams.ToolRadius); + const TopoDS_Shape &shape = toShape(*area,fill); builder.Add(compound,toShape(*area,fill)); } builder.Add(compound,areaPocket.makePocket( @@ -781,7 +935,6 @@ TopoDS_Shape Area::makePocket(int index, PARAM_ARGS(PARAM_FARG,AREA_PARAMS_POCKE throw Base::ValueError("unknown poket mode"); } - build(); CAreaConfig conf(myParams); CAreaPocketParams params( tool_radius,extra_offset,stepover,from_center,pm,zig_angle); @@ -802,75 +955,85 @@ static inline bool IsLeft(const gp_Pnt &a, const gp_Pnt &b, const gp_Pnt &c) { return ((b.X() - a.X())*(c.Y() - a.Y()) - (b.Y() - a.Y())*(c.X() - a.X())) > 0; } +TopoDS_Wire Area::toShape(const CCurve &c, const gp_Trsf *trsf) { + BRepBuilderAPI_MakeWire mkWire; + gp_Pnt pstart,pt; + bool first = true; + for(const CVertex &v : c.m_vertices){ + if(first){ + first = false; + pstart = pt = gp_Pnt(v.m_p.x,v.m_p.y,0); + continue; + } + gp_Pnt pnext(v.m_p.x,v.m_p.y,0); + if(pnext.Distance(pt) Precision::Confusion()) { + double d = pt.Distance(pnext); + double q = sqrt(r*r - d*d*0.25); + double x = (pt.X()+pnext.X())*0.5; + double y = (pt.Y()+pnext.Y())*0.5; + double dx = q*(pt.Y()-pnext.Y())/d; + double dy = q*(pnext.X()-pt.X())/d; + gp_Pnt newCenter(x + dx, y + dy,0); + if(IsLeft(pt,pnext,center) != IsLeft(pt,pnext,newCenter)) { + newCenter.SetX(x - dx); + newCenter.SetY(y - dy); + } + Base::Console().Warning( + "Arc correction: %lf,%lf, center(%lf,%lf)->(%lf,%lf)\n", + r,r2,center.X(),center.Y(),newCenter.X(),newCenter.Y()); + center = newCenter; + } + gp_Ax2 axis(center, gp_Dir(0,0,v.m_type)); + mkWire.Add(BRepBuilderAPI_MakeEdge(gp_Circ(axis,r),pt,pnext).Edge()); + } + pt = pnext; + } + if(!mkWire.IsDone()) + return TopoDS_Wire(); + + if(c.IsClosed() && !BRep_Tool::IsClosed(mkWire.Wire())){ + // This should never happen after changing libarea's + // Point::tolerance to be the same as Precision::Confusion(). + // Just leave it here in case. + BRepAdaptor_Curve curve(mkWire.Edge()); + gp_Pnt p1(curve.Value(curve.FirstParameter())); + gp_Pnt p2(curve.Value(curve.LastParameter())); + std::stringstream str; + str<< "warning: patch open wire type " << + c.m_vertices.back().m_type << endl << + '(' << p1.X() << ',' << p1.Y() << ')' << endl << + '(' << p2.X() << ',' << p2.Y() << ')' << endl << + '(' << pt.X() << ',' << pt.Y() << ')' << endl << + '(' << pstart.X() << ',' < Precision::Confusion()) { - double d = pt.Distance(pnext); - double q = sqrt(r*r - d*d*0.25); - double x = (pt.X()+pnext.X())*0.5; - double y = (pt.Y()+pnext.Y())*0.5; - double dx = q*(pt.Y()-pnext.Y())/d; - double dy = q*(pnext.X()-pt.X())/d; - gp_Pnt newCenter(x + dx, y + dy,0); - if(IsLeft(pt,pnext,center) != IsLeft(pt,pnext,newCenter)) { - newCenter.SetX(x - dx); - newCenter.SetY(y - dy); - } - Base::Console().Warning( - "Arc correction: %lf,%lf, center(%lf,%lf)->(%lf,%lf)\n", - r,r2,center.X(),center.Y(),newCenter.X(),newCenter.Y()); - center = newCenter; - } - gp_Ax2 axis(center, gp_Dir(0,0,v.m_type)); - mkWire.Add(BRepBuilderAPI_MakeEdge(gp_Circ(axis,r),pt,pnext).Edge()); - } - pt = pnext; - } - if(c.IsClosed() && !BRep_Tool::IsClosed(mkWire.Wire())){ - // This should never happen after changing libarea's - // Point::tolerance to be the same as Precision::Confusion(). - // Just leave it here in case. - BRepAdaptor_Curve curve(mkWire.Edge()); - gp_Pnt p1(curve.Value(curve.FirstParameter())); - gp_Pnt p2(curve.Value(curve.LastParameter())); - std::stringstream str; - str<< "warning: patch open wire" << - c.m_vertices.back().m_type << endl << - '(' << p1.X() << ',' << p1.Y() << ')' << endl << - '(' << p2.X() << ',' << p2.Y() << ')' << endl << - '(' << pt.X() << ',' << pt.Y() << ')' << endl << - '(' << pstart.X() << ',' < Area::sortWires(const std::list &shapes, + const AreaParams *params, const gp_Pnt *_pstart, gp_Pnt *_pend, + PARAM_ARGS(PARAM_FARG,AREA_PARAMS_SORT_WIRES)) +{ + std::list wires; + + //Heristic sorting by shape's vertex Z value. For performance's sake, we don't + //perform any planar checking here + std::multimap shape_map; + + for (auto &shape : shapes) { + std::list subshapes; + if(!explode) + subshapes.push_back(shape); + else{ + bool haveShape=false; + for(TopExp_Explorer it(shape,TopAbs_WIRE);it.More();it.Next()) { + haveShape=true; + subshapes.push_back(it.Current()); + } + if(!haveShape) { + for(TopExp_Explorer it(shape,TopAbs_EDGE);it.More();it.Next()) + subshapes.push_back(it.Current()); + } + } + //Order the shapes by its vertex Z value. + for(auto &s : subshapes) { + bool first=true; + double z=0.0; + for(TopExp_Explorer it(s,TopAbs_VERTEX);it.More();) { + gp_Pnt p = BRep_Tool::Pnt(TopoDS::Vertex(it.Current())); + if(first || z < p.Z()) { + first = false; + z = p.Z(); + } + if(!top_z) break; + } + shape_map.insert(std::pair(z,s)); + } + } + if(!shape_map.size()) + return wires; + + Area area(params); + //We'll do planar checking here, so disable Area planar check + area.myParams.Coplanar = Area::CoplanarNone; + + gp_Pnt pstart,pend; + if(_pstart) pstart = *_pstart; + TopoDS_Shape plane = shape_map.rbegin()->second; + area.setPlane(plane); + for(auto &item : boost::adaptors::reverse(shape_map)) { + //do planar checking, and sort wires grouped by plane + if(!Area::isCoplanar(plane,item.second)) { + wires.splice(wires.end(),area.sortWires( + -1,0,&pstart,&pend, PARAM_FIELDS(PARAM_FARG,AREA_PARAMS_MIN_DIST))); + pstart = pend; + area.clean(true); + plane = item.second; + area.setPlane(plane); + } + area.add(item.second,Area::OperationCompound); + } + wires.splice(wires.end(),area.sortWires( + -1,0,&pstart,&pend, PARAM_FIELDS(PARAM_FARG,AREA_PARAMS_MIN_DIST))); + if(_pend) *_pend = pend; + return wires; +} + +static void addCommand(Toolpath &path, const gp_Pnt &p, + bool g0=false, double g0height=0.0, double clearance=0.0) +{ + Command cmd; + cmd.Name = g0?"G0":"G1"; + if(g0 && fabs(g0height)>Precision::Confusion()) { + cmd.Parameters["Z"] = g0height; + path.addCommand(cmd); + cmd.Parameters["X"] = p.X(); + cmd.Parameters["Y"] = p.Y(); + path.addCommand(cmd); + if(fabs(clearance)>Precision::Confusion()) { + cmd.Parameters["Z"] = p.Z()+clearance; + path.addCommand(cmd); + cmd.Name = "G1"; + } + }else + cmd.Parameters["X"] = p.X(); + cmd.Parameters["Y"] = p.Y(); + cmd.Parameters["Z"] = p.Z(); + path.addCommand(cmd); +} + +static void addCommand(Toolpath &path, + const gp_Pnt &pstart, const gp_Pnt &pend, + const gp_Pnt ¢er, bool clockwise) +{ + Command cmd; + cmd.Name = clockwise?"G2":"G3"; + cmd.Parameters["I"] = center.X()-pstart.X(); + cmd.Parameters["J"] = center.Y()-pstart.Y(); + cmd.Parameters["K"] = center.Z()-pstart.Z(); + cmd.Parameters["X"] = pend.X(); + cmd.Parameters["Y"] = pend.Y(); + cmd.Parameters["Z"] = pend.Z(); + path.addCommand(cmd); +} + +void Area::toPath(Toolpath &path, const std::list &shapes, + const gp_Pnt *pstart, PARAM_ARGS(PARAM_FARG,AREA_PARAMS_PATH)) +{ + std::list wires; + if(sort) + wires = sortWires(shapes,NULL,pstart); + else{ + for(auto &shape : shapes) { + if (shape.IsNull()) + continue; + bool haveShape=false; + for(TopExp_Explorer it(shape,TopAbs_WIRE);it.More();it.Next()) { + haveShape=true; + wires.push_back(it.Current()); + } + if(haveShape) continue; + for(TopExp_Explorer it(shape,TopAbs_EDGE);it.More();it.Next()) + wires.push_back(BRepBuilderAPI_MakeWire(TopoDS::Edge(it.Current())).Wire()); + } + } + + if(threshold < Precision::Confusion()) + threshold = Precision::Confusion(); + gp_Pnt plast,p; + if(pstart) plast = *pstart; + bool first = true; + for(const TopoDS_Shape &wire : wires) { + BRepTools_WireExplorer xp(TopoDS::Wire(wire)); + p = BRep_Tool::Pnt(xp.CurrentVertex()); + if(first||(p.Z()>=plast.Z()&&p.Distance(plast)>threshold)) + addCommand(path,p,true,height,clearance); + else + addCommand(path,p); + plast = p; + first = false; + for(;xp.More();xp.Next(),plast=p) { + BRepAdaptor_Curve curve(xp.Current()); + bool reversed = (xp.Current().Orientation()==TopAbs_REVERSED); + p = curve.Value(reversed?curve.FirstParameter():curve.LastParameter()); + + switch (curve.GetType()) { + case GeomAbs_Line: { + addCommand(path,p); + break; + } case GeomAbs_Circle:{ + double first = curve.FirstParameter(); + double last = curve.LastParameter(); + gp_Circ circle = curve.Circle(); + gp_Ax1 axis = circle.Axis(); + bool clockwise = axis.Direction().Z()<0; + if(reversed) clockwise = !clockwise; + gp_Pnt center = axis.Location(); + if(fabs(first-last)>M_PI) { + // Split arc(circle) larger than half circle. + gp_Pnt mid = curve.Value((last-first)*0.5+first); + addCommand(path,plast,mid,center,clockwise); + plast = mid; + } + addCommand(path,plast,p,center,clockwise); + break; + } default: { + // Discretize all other type of curves + GCPnts_UniformDeflection discretizer(curve, deflection, + curve.FirstParameter(), curve.LastParameter()); + if (discretizer.IsDone () && discretizer.NbPoints () > 0) { + int nbPoints = discretizer.NbPoints (); + for (int i=1; i<=nbPoints; i++) { + gp_Pnt pt = discretizer.Value (i); + addCommand(path,pt); + } + }else + Standard_Failure::Raise("Curve discretization failed"); + }} + } + } +} diff --git a/src/Mod/Path/App/Area.h b/src/Mod/Path/App/Area.h index ca2866149..ef7175532 100644 --- a/src/Mod/Path/App/Area.h +++ b/src/Mod/Path/App/Area.h @@ -23,14 +23,19 @@ #ifndef PATH_AREA_H #define PATH_AREA_H +#include +#include +#include #include #include #include #include +#include "Path.h" #include "AreaParams.h" class CArea; +class CCurve; namespace Path { @@ -152,8 +157,12 @@ public: /** Set a working plane * * If no working plane are set, Area will try to find a working plane from - * all the added children shapes. The purpose of this function is in case - * the child shapes are all colinear edges + * individual children faces, wires or edges. By right, we should create a + * compound of all shapes and then findplane on it. However, because we + * supports solid, and also because OCC may hang for a long time if + * something goes a bit off, we opt to find plane on each individual shape. + * If you intend to pass individual edges, you must supply a workplane shape + * manually * * \arg \c shape: a shape defining a working plane */ @@ -177,13 +186,13 @@ public: * If more than one offset is requested, a compound shape is return * containing all offset shapes as wires regardless of \c Fill setting. */ - TopoDS_Shape makeOffset(int index, PARAM_ARGS_DEF(PARAM_FARG,AREA_PARAMS_OFFSET)); + TopoDS_Shape makeOffset(int index=-1, PARAM_ARGS_DEF(PARAM_FARG,AREA_PARAMS_OFFSET)); /** Make a pocket of the combined shape * * See #AREA_PARAMS_POCKET for description of the arguments. */ - TopoDS_Shape makePocket(int index, PARAM_ARGS_DEF(PARAM_FARG,AREA_PARAMS_POCKET)); + TopoDS_Shape makePocket(int index=-1, PARAM_ARGS_DEF(PARAM_FARG,AREA_PARAMS_POCKET)); /** Config this Area object */ @@ -204,8 +213,11 @@ public: */ void clean(bool deleteShapes=false); - /** Get the combined shape */ - TopoDS_Shape getShape(int index); + /** Get the combined shape + * \arg \c index: index of the section, -1 for all sections. No effect on + * non-sectioned area. + */ + TopoDS_Shape getShape(int index=-1); /** Return the number of sections */ std::size_t getSectionCount() { @@ -219,10 +231,30 @@ public: * \arg \c wire: input wire object * \arg \c trsf: optional transform matrix to transform the wire shape into * XY0 plane. - * \arg \c deflection: for defecting non circular curves + * \arg \c deflection: for discretizing non circular curves + * \arg \c to_edges: if true, discretize all curves, and insert as open + * line segments * */ - static void add(CArea &area, const TopoDS_Wire &wire, - const gp_Trsf *trsf=NULL, double deflection=0.01); + static void add(CArea &area, const TopoDS_Wire &wire, const gp_Trsf *trsf=NULL, + double deflection=0.01, bool to_edges=false); + + /** Output a list or sorted wire with minimize traval distance + * + * \arg \c index: index of the section, -1 for all sections. No effect on + * non-sectioned area. + * \arg \c count: number of the sections to return, <=0 for all sections + * after \c index. No effect on non-sectioned area. + * \arg \c pstart: optional start point + * \arg \c pend: optional output containing the ending point of the returned + * wires + * + * See #AREA_PARAMS_MIN_DIST for other arguments + * + * \return sorted wires + * */ + std::list sortWires(int index=-1, int count=0, + const gp_Pnt *pstart=NULL, gp_Pnt *pend=NULL, + PARAM_ARGS_DEF(PARAM_FARG,AREA_PARAMS_MIN_DIST)); /** Add a OCC generic shape to CArea * @@ -236,7 +268,7 @@ public: * \arg \c areaOpen: for collecting open curves. If not supplied, open * curves are added to \c area * \arg \c to_edges: separate open wires to individual edges - * \arg \c reorder: reorder closed wires for wire only shape + * \arg \c reorient: reorient closed wires for wire only shape * * \return Returns the number of non coplaner. Planar testing only happens * if \c plane is supplied @@ -244,7 +276,7 @@ public: static int add(CArea &area, const TopoDS_Shape &shape, const gp_Trsf *trsf=NULL, double deflection=0.01,const TopoDS_Shape *plane = NULL, bool force_coplanar=true, CArea *areaOpen=NULL, bool to_edges=false, - bool reorder=true); + bool reorient=true); /** Convert curves in CArea into an OCC shape * @@ -256,7 +288,48 @@ public: static TopoDS_Shape toShape(const CArea &area, bool fill, const gp_Trsf *trsf=NULL); + /** Convert a single curve into an OCC wire + * + * \arg \c curve: input curve object + * \arg \c trsf: optional transform matrix to transform the shape back into + * its original position. + * */ + static TopoDS_Wire toShape(const CCurve &curve, const gp_Trsf *trsf=NULL); + + /** Check if two OCC shape is coplanar */ static bool isCoplanar(const TopoDS_Shape &s1, const TopoDS_Shape &s2); + + /** Group shapes by their plane, and return a list of sorted wires + * + * The output wires is ordered by its occupied plane, and sorted to + * minimize traval distance + * + * \arg \c shapes: input list of shapes. + * \arg \c params: optional Area parameters for the Area object internally + * used for sorting + * \arg \c pstart: optional start point + * \arg \c pend: optional output containing the ending point of the returned + * maybe broken if the algorithm see fits. + * + * See #AREA_PARAMS_SORT_WIRES for other arguments + * + * \return sorted wires + */ + static std::list sortWires(const std::list &shapes, + const AreaParams *params = NULL, const gp_Pnt *pstart=NULL, + gp_Pnt *pend=NULL, PARAM_ARGS_DEF(PARAM_FARG,AREA_PARAMS_SORT_WIRES)); + + /** Convert a list of wires to gcode + * + * \arg \c path: output toolpath + * \arg \c shapes: input list of shapes + * \arg \c pstart: output start point, + * + * See #AREA_PARAMS_PATH for other arguments + */ + static void toPath(Toolpath &path, const std::list &shapes, + const gp_Pnt *pstart=NULL, PARAM_ARGS_DEF(PARAM_FARG,AREA_PARAMS_PATH)); + }; } //namespace Path diff --git a/src/Mod/Path/App/AreaParams.h b/src/Mod/Path/App/AreaParams.h index 0da87d16e..0f38db3a6 100644 --- a/src/Mod/Path/App/AreaParams.h +++ b/src/Mod/Path/App/AreaParams.h @@ -44,20 +44,32 @@ ((enum2,clip_fill,ClipFill,0,\ "ClipperLib clip fill type. \nSee https://goo.gl/5pYQQP",AREA_CLIPPER_FILL_TYPE)) +/** Deflection parameter */ +#define AREA_PARAMS_DEFLECTION \ + ((double,deflection,Deflection,0.01,\ + "Deflection for non circular curve discretization. It also also used for\n"\ + "discretizing circular wires when you 'Explode' the shape for wire operations")) + /** Base parameters */ #define AREA_PARAMS_BASE \ ((enum,fill,Fill,2,"Fill the output wires to make a face. \n"\ "Auto means make a face if any of the children has a face.",(None)(Face)(Auto)))\ - ((enum,coplanar,Coplanar,2,"Specifies the way to check coplanar.\n"\ - "'Force' will discard non coplaner shapes, but 'Check' only gives warning.",\ - (None)(Check)(Force)))\ - ((bool,reorder,Reorder,true,"Re-orient closed wires in wire only shapes so that inner wires become holes."))\ - ((enum,open_mode,OpenMode,0,"Specify how to handle open wires.\n"\ - "'None' means combin without openeration.\n"\ - "'Edges' means separate to edges before Union.\n"\ - "ClipperLib seems to have an urge to close open wires.",(None)(Union)(Edges)))\ - AREA_PARAMS_CLIPPER_FILL \ - ((double,deflection,Deflection,0.01,"Deflection for non circular curve discretization")) + ((enum,coplanar,Coplanar,2,\ + "Specifies the way to check coplanar. 'Force' will discard non coplaner shapes,\n"\ + "but 'Check' only gives warning.",(None)(Check)(Force)))\ + ((bool,reorient,Reorient,true,\ + "Re-orient closed wires in wire only shapes so that inner wires become holes."))\ + ((bool,explode,Explode,false,\ + "If true, Area will explode the first shape into disconnected open edges, \n"\ + "with all curves discretized, so that later operations like 'Difference' \n"\ + "behave like wire cutting. Without exploding, 'Difference' in ClipperLib\n"\ + "behave like face cutting."))\ + ((enum,open_mode,OpenMode,0,\ + "Specify how to handle open wires. 'None' means combin without openeration.\n"\ + "'Edges' means separate to edges before Union. ClipperLib seems to have an.\n"\ + "urge to close open wires.",(None)(Union)(Edges)))\ + AREA_PARAMS_DEFLECTION \ + AREA_PARAMS_CLIPPER_FILL /** libarea algorithm option parameters */ #define AREA_PARAMS_CAREA \ @@ -72,7 +84,8 @@ ((short,min_arc_points,MinArcPoints,4,"Minimum segments for arc discretization"))\ ((short,max_arc_points,MaxArcPoints,100,"Maximum segments for arc discretization"))\ ((double,clipper_scale,ClipperScale,10000.0,\ - "ClipperLib operate on intergers. This is the scale factor to \nconvert floating points.")) + "ClipperLib operate on intergers. This is the scale factor to convert\n"\ + "floating points.")) /** Pocket parameters * @@ -91,8 +104,10 @@ /** Operation code */ #define AREA_PARAMS_OPCODE \ - ((enum2,op,Operation,0,"Boolean operation",\ - (Union)(Difference)(Intersection)(Xor),(ClipperLib::ClipType,ClipperLib::ct))) + ((enum,op,Operation,0,\ + "Boolean operation. For the first four operation, see https://goo.gl/Gj8RUu.\n"\ + "'Compound' means no operation, normal used to do Area.sortWires().",\ + (Union)(Difference)(Intersection)(Xor)(Compound))) /** Offset parameters */ #define AREA_PARAMS_OFFSET \ @@ -124,6 +139,36 @@ ((double,round_precision,RoundPreceision,0.0,\ "Round joint precision. If =0, it defaults to Accuracy. \nSee https://goo.gl/4odfQh")) +/** Area path generation parameters */ +#define AREA_PARAMS_PATH \ + ((bool, sort, SortShape, true, \ + "Whether to sort the shapes to optimize travel distance. You can customize wire\n"\ + "sorting by calling sortWires() manually."))\ + ((double, threshold, RetractThreshold, 0.0,\ + "If two wire's end points are separated within this threshold, they are consider\n"\ + "as connected. You may want to set this to the tool diameter to keep the tool down."))\ + ((double, height, RetractHeight, 0.0,"Tool retraction absolute height"))\ + ((double, clearance, Clearance, 0.0,\ + "When return from last retraction, this gives the pause height relative to the Z\n"\ + "value of the next move"))\ + AREA_PARAMS_DEFLECTION + +#define AREA_PARAMS_MIN_DIST \ + ((double, min_dist, MinDistance, 1.0, \ + "minimum distance for the generate new wires. Wires maybe broken if the\n"\ + "algorithm see fits."))\ + +/** Area wire sorting parameters */ +#define AREA_PARAMS_SORT_WIRES \ + ((bool, explode, Explode, true,\ + "If ture, the input shape will be exploded into wires before doing planar checking.\n"\ + "Otherwise, and whole shape is considered to be in one plane without checking."))\ + ((bool, top_z, TopZ, false, \ + "If ture, the planes is ordered by the first the shapes first vertex Z value.\n"\ + "Otherwise, by the highest Z of all of its vertexes."))\ + AREA_PARAMS_MIN_DIST + + /** Group of all Area configuration parameters except CArea's*/ #define AREA_PARAMS_AREA \ AREA_PARAMS_BASE \ diff --git a/src/Mod/Path/App/AreaPy.xml b/src/Mod/Path/App/AreaPy.xml index 3f0066381..062b82935 100644 --- a/src/Mod/Path/App/AreaPy.xml +++ b/src/Mod/Path/App/AreaPy.xml @@ -58,6 +58,11 @@ any operation + + + + + Get current algorithm parameters as a dictionary. diff --git a/src/Mod/Path/App/AreaPyImp.cpp b/src/Mod/Path/App/AreaPyImp.cpp index 8cbabda4d..75e370d2d 100644 --- a/src/Mod/Path/App/AreaPyImp.cpp +++ b/src/Mod/Path/App/AreaPyImp.cpp @@ -22,10 +22,10 @@ #include "PreCompiled.h" -#include #include +#include -#include "Mod/Path/App/Area.h" +#include "Area.h" // inclusion of the generated files (generated out of AreaPy.xml) #include "AreaPy.h" @@ -77,6 +77,16 @@ static const AreaDoc myDocs[] = { "\n* index (-1): the index of the section. -1 means all sections. No effect on planar shape.\n" PARAM_PY_DOC(ARG,AREA_PARAMS_POCKET), }, + { + "sortWires", + + "sortWires(index=-1, count=0, start=Vector()" PARAM_PY_ARGS_DOC(ARG,AREA_PARAMS_MIN_DIST) "):\n" + "Returns a tuple (wires,end): sorted wires with minimized travel distance, and the endpoint of the wires.\n" + "\n* index (-1): the index of the section. -1 means all sections. No effect on planar shape.\n" + "\n* count (0): the number of sections to return. <=0 means all sections starting from index.\n" + "\n* start (Vector()): a vector specifies the start point.\n" + PARAM_PY_DOC(ARG,AREA_PARAMS_MIN_DIST), + }, }; struct AreaPyDoc { @@ -141,12 +151,42 @@ PyObject* AreaPy::getShape(PyObject *args, PyObject *keywds) if (!PyArg_ParseTupleAndKeywords(args, keywds,"|hO",kwlist,&pcObj)) return 0; - try { - if(PyObject_IsTrue(pcObj)) - getAreaPtr()->clean(); - return Py::new_reference_to(Part::shape2pyshape(getAreaPtr()->getShape(index))); + if(PyObject_IsTrue(pcObj)) + getAreaPtr()->clean(); + return Py::new_reference_to(Part::shape2pyshape(getAreaPtr()->getShape(index))); +} + +PyObject* AreaPy::sortWires(PyObject *args, PyObject *keywds){ + PARAM_PY_DECLARE_INIT(PARAM_FARG,AREA_PARAMS_MIN_DIST) + short index = -1; + short count = 0; + PyObject *start = NULL; + + static char *kwlist[] = {"index","count","start", + PARAM_FIELD_STRINGS(ARG,AREA_PARAMS_MIN_DIST), NULL}; + if (!PyArg_ParseTupleAndKeywords(args, keywds, + "|hhO!" PARAM_PY_KWDS(AREA_PARAMS_MIN_DIST), + kwlist,&index,&count,&(Base::VectorPy::Type),&start, + PARAM_REF(PARAM_FARG,AREA_PARAMS_MIN_DIST))) + return 0; + + gp_Pnt pstart,pend; + if(start) { + Base::Vector3d vec = static_cast(start)->value(); + pstart.SetCoord(vec.x, vec.y, vec.z); } - PY_CATCH_OCC; + std::list wires = getAreaPtr()->sortWires( + index,count,&pstart,&pend, + PARAM_PY_FIELDS(PARAM_FARG,AREA_PARAMS_MIN_DIST)); + PyObject *list = PyList_New(0); + for(auto &wire : wires) + PyList_Append(list,Py::new_reference_to( + Part::shape2pyshape(TopoDS::Wire(wire)))); + PyObject *ret = PyTuple_New(2); + PyTuple_SetItem(ret,0,list); + PyTuple_SetItem(ret,1,new Base::VectorPy( + Base::Vector3d(pend.X(),pend.Y(),pend.Z()))); + return ret; } PyObject* AreaPy::add(PyObject *args, PyObject *keywds) @@ -205,13 +245,10 @@ PyObject* AreaPy::makeOffset(PyObject *args, PyObject *keywds) &index,PARAM_REF(PARAM_FARG,AREA_PARAMS_OFFSET))) return 0; - try { - //Expand the variable as function call arguments - TopoDS_Shape resultShape = getAreaPtr()->makeOffset(index, - PARAM_PY_FIELDS(PARAM_FARG,AREA_PARAMS_OFFSET)); - return Py::new_reference_to(Part::shape2pyshape(resultShape)); - } - PY_CATCH_OCC; + //Expand the variable as function call arguments + TopoDS_Shape resultShape = getAreaPtr()->makeOffset(index, + PARAM_PY_FIELDS(PARAM_FARG,AREA_PARAMS_OFFSET)); + return Py::new_reference_to(Part::shape2pyshape(resultShape)); } PyObject* AreaPy::makePocket(PyObject *args, PyObject *keywds) @@ -228,15 +265,11 @@ PyObject* AreaPy::makePocket(PyObject *args, PyObject *keywds) &index,PARAM_REF(PARAM_FARG,AREA_PARAMS_POCKET))) return 0; - try { - TopoDS_Shape resultShape = getAreaPtr()->makePocket(index, - PARAM_PY_FIELDS(PARAM_FARG,AREA_PARAMS_POCKET)); - return Py::new_reference_to(Part::shape2pyshape(resultShape)); - } - PY_CATCH_OCC; + TopoDS_Shape resultShape = getAreaPtr()->makePocket(index, + PARAM_PY_FIELDS(PARAM_FARG,AREA_PARAMS_POCKET)); + return Py::new_reference_to(Part::shape2pyshape(resultShape)); } - PyObject* AreaPy::setParams(PyObject *args, PyObject *keywds) { static char *kwlist[] = {PARAM_FIELD_STRINGS(NAME,AREA_PARAMS_CONF),NULL}; diff --git a/src/Mod/Path/App/FeatureArea.cpp b/src/Mod/Path/App/FeatureArea.cpp index a8193f5ef..5c65c8f79 100644 --- a/src/Mod/Path/App/FeatureArea.cpp +++ b/src/Mod/Path/App/FeatureArea.cpp @@ -41,6 +41,7 @@ PROPERTY_SOURCE(Path::FeatureArea, Part::Feature) PARAM_ENUM_STRING_DECLARE(static const char *Enums,AREA_PARAMS_ALL) FeatureArea::FeatureArea() + :myBuild(false) { ADD_PROPERTY(Sources,(0)); ADD_PROPERTY(WorkPlane,(TopoDS_Shape())); @@ -62,6 +63,14 @@ FeatureArea::~FeatureArea() { } +Area &FeatureArea::getArea() { + if(!myBuild) { + myBuild = true; + execute(); + } + return myArea; +} + App::DocumentObjectExecReturn *FeatureArea::execute(void) { std::vector links = Sources.getValues(); @@ -97,6 +106,17 @@ App::DocumentObjectExecReturn *FeatureArea::execute(void) return Part::Feature::execute(); } +std::list FeatureArea::getShapes() { + std::list shapes; + Area &area = getArea(); + if(area.getSectionCount()) { + for(int i=0;i<(int)area.getSectionCount();++i) + shapes.push_back(area.getShape(i)); + }else + shapes.push_back(area.getShape()); + return shapes; +} + short FeatureArea::mustExecute(void) const { if (Sources.isTouched()) @@ -119,14 +139,85 @@ PyObject *FeatureArea::getPyObject() } -// Python Area feature --------------------------------------------------------- +// FeatureAreaView ------------------------------------------------------------- +// +PROPERTY_SOURCE(Path::FeatureAreaView, Part::Feature) + +FeatureAreaView::FeatureAreaView() +{ + ADD_PROPERTY(Source,(0)); + ADD_PROPERTY_TYPE(SectionIndex,(0),"Section",App::Prop_None,"The start index of the section to show"); + ADD_PROPERTY_TYPE(SectionCount,(1),"Section",App::Prop_None,"Number of sections to show"); +} + +std::list FeatureAreaView::getShapes() { + std::list shapes; + App::DocumentObject* pObj = Source.getValue(); + if (!pObj) return shapes; + if(!pObj->isDerivedFrom(FeatureArea::getClassTypeId())) + return shapes; + Area &area = static_cast(pObj)->getArea(); + + int index=SectionIndex.getValue(),count=SectionCount.getValue(); + if(index<0) { + index = 0; + count = area.getSectionCount(); + } + if(index >= (int)area.getSectionCount()) + return shapes; + + if(count<=0) + count = (int)area.getSectionCount(); + if(count==1) + shapes.push_back(area.getShape(index)); + else{ + count += index; + if(count>(int)area.getSectionCount()) + count = area.getSectionCount(); + for(int i=index;iisDerivedFrom(FeatureArea::getClassTypeId())) + return new App::DocumentObjectExecReturn("Linked object is not a FeatureArea"); + + std::list shapes = getShapes(); + if(shapes.empty()) + Shape.setValue(TopoDS_Shape()); + else if(shapes.size()==1) { + Shape.setValue(shapes.front()); + }else{ + BRep_Builder builder; + TopoDS_Compound compound; + builder.MakeCompound(compound); + for(auto &shape : shapes) + builder.Add(compound,shape); + Shape.setValue(compound); + } + + return Part::Feature::execute(); +} + +// Python feature --------------------------------------------------------- namespace App { /// @cond DOXERR PROPERTY_SOURCE_TEMPLATE(Path::FeatureAreaPython, Path::FeatureArea) +PROPERTY_SOURCE_TEMPLATE(Path::FeatureAreaViewPython, Path::FeatureAreaView) template<> const char* Path::FeatureAreaPython::getViewProviderName(void) const { - return "PathGui::ViewProviderArea"; + return "PathGui::ViewProviderAreaPython"; +} +template<> const char* Path::FeatureAreaViewPython::getViewProviderName(void) const { + return "PathGui::ViewProviderAreaViewPython"; } /// @endcond diff --git a/src/Mod/Path/App/FeatureArea.h b/src/Mod/Path/App/FeatureArea.h index 5a084f3b1..7af914653 100644 --- a/src/Mod/Path/App/FeatureArea.h +++ b/src/Mod/Path/App/FeatureArea.h @@ -40,12 +40,13 @@ class PathExport FeatureArea : public Part::Feature PROPERTY_HEADER(Path::FeatureArea); public: - Area myArea; - /// Constructor FeatureArea(void); virtual ~FeatureArea(); + Area &getArea(); + std::list getShapes(); + /// returns the type name of the ViewProvider virtual const char* getViewProviderName(void) const { return "PathGui::ViewProviderArea"; @@ -58,10 +59,36 @@ public: Part::PropertyPartShape WorkPlane; PARAM_PROP_DECLARE(AREA_PARAMS_ALL) + +private: + bool myBuild; + Area myArea; }; typedef App::FeaturePythonT FeatureAreaPython; +class PathExport FeatureAreaView : public Part::Feature +{ + PROPERTY_HEADER(Path::FeatureAreaView); + +public: + /// Constructor + FeatureAreaView(void); + + std::list getShapes(); + + virtual const char* getViewProviderName(void) const { + return "PathGui::ViewProviderAreaView"; + } + virtual App::DocumentObjectExecReturn *execute(void); + + App::PropertyLink Source; + App::PropertyInteger SectionIndex; + App::PropertyInteger SectionCount; +}; + +typedef App::FeaturePythonT FeatureAreaViewPython; + } //namespace Path diff --git a/src/Mod/Path/App/FeatureAreaPyImp.cpp b/src/Mod/Path/App/FeatureAreaPyImp.cpp index 1e76840e2..755db2689 100644 --- a/src/Mod/Path/App/FeatureAreaPyImp.cpp +++ b/src/Mod/Path/App/FeatureAreaPyImp.cpp @@ -46,7 +46,7 @@ PyObject* FeatureAreaPy::getArea(PyObject *args) if (!PyArg_ParseTuple(args, "")) return NULL; - return new AreaPy(new Area(getFeatureAreaPtr()->myArea)); + return new AreaPy(new Area(getFeatureAreaPtr()->getArea())); } PyObject* FeatureAreaPy::setParams(PyObject *args, PyObject *keywds) diff --git a/src/Mod/Path/App/FeaturePathShape.cpp b/src/Mod/Path/App/FeaturePathShape.cpp index dcdd7e4fe..5f148b487 100644 --- a/src/Mod/Path/App/FeaturePathShape.cpp +++ b/src/Mod/Path/App/FeaturePathShape.cpp @@ -19,7 +19,9 @@ * Suite 330, Boston, MA 02111-1307, USA * * * ***************************************************************************/ - +/* + * Copyright (c) 2017 Zheng, Lei + */ #include "PreCompiled.h" @@ -32,20 +34,17 @@ #include #include #include +#include #include #include #include -#include -#include #include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include "FeatureArea.h" using namespace Path; @@ -54,79 +53,39 @@ PROPERTY_SOURCE(Path::FeatureShape, Path::Feature) FeatureShape::FeatureShape() { - ADD_PROPERTY_TYPE(Shape,(TopoDS_Shape()),"Path",App::Prop_None,"The shape data of this feature"); + ADD_PROPERTY(Sources,(0)); + ADD_PROPERTY_TYPE(StartPoint,(Base::Vector3d()),"Path",App::Prop_None,"Path start position"); + PARAM_PROP_ADD("Path",AREA_PARAMS_PATH); } FeatureShape::~FeatureShape() { } -short FeatureShape::mustExecute(void) const -{ - return Path::Feature::mustExecute(); -} - App::DocumentObjectExecReturn *FeatureShape::execute(void) { - TopoDS_Shape shape = Shape.getValue(); - if (!shape.IsNull()) { - if (shape.ShapeType() == TopAbs_WIRE) { - Path::Toolpath result; - bool first = true; - Base::Placement last; - - TopExp_Explorer ExpEdges (shape,TopAbs_EDGE); - while (ExpEdges.More()) { - const TopoDS_Edge& edge = TopoDS::Edge(ExpEdges.Current()); - TopExp_Explorer ExpVerts(edge,TopAbs_VERTEX); - bool vfirst = true; - while (ExpVerts.More()) { - const TopoDS_Vertex& vert = TopoDS::Vertex(ExpVerts.Current()); - gp_Pnt pnt = BRep_Tool::Pnt(vert); - Base::Placement tpl; - tpl.setPosition(Base::Vector3d(pnt.X(),pnt.Y(),pnt.Z())); - if (first) { - // add first point as a G0 move - Path::Command cmd; - std::ostringstream ctxt; - ctxt << "G0 X" << tpl.getPosition().x << " Y" << tpl.getPosition().y << " Z" << tpl.getPosition().z; - cmd.setFromGCode(ctxt.str()); - result.addCommand(cmd); - first = false; - vfirst = false; - } else { - if (vfirst) - vfirst = false; - else { - Path::Command cmd; - cmd.setFromPlacement(tpl); - - // write arc data if needed - BRepAdaptor_Curve adapt(edge); - if (adapt.GetType() == GeomAbs_Circle) { - gp_Circ circ = adapt.Circle(); - gp_Pnt c = circ.Location(); - bool clockwise = false; - gp_Dir n = circ.Axis().Direction(); - if (n.Z() < 0) - clockwise = true; - Base::Vector3d center = Base::Vector3d(c.X(),c.Y(),c.Z()); - // center coords must be relative to last point - center -= last.getPosition(); - cmd.setCenter(center,clockwise); - } - result.addCommand(cmd); - } - } - ExpVerts.Next(); - last = tpl; - } - ExpEdges.Next(); - } - - Path.setValue(result); - } + Toolpath path; + std::vector links = Sources.getValues(); + if (links.empty()) { + Path.setValue(path); + return new App::DocumentObjectExecReturn("No shapes linked"); } + + const Base::Vector3d &v = StartPoint.getValue(); + gp_Pnt pstart(v.x,v.y,v.z); + + std::list shapes; + for (std::vector::iterator it = links.begin(); it != links.end(); ++it) { + if (!(*it && (*it)->isDerivedFrom(Part::Feature::getClassTypeId()))) + continue; + const TopoDS_Shape &shape = static_cast(*it)->Shape.getShape().getShape(); + if (shape.IsNull()) + continue; + shapes.push_back(shape); + } + Area::toPath(path,shapes,&pstart,PARAM_PROP_ARGS(AREA_PARAMS_PATH)); + + Path.setValue(path); return App::DocumentObject::StdReturn; } diff --git a/src/Mod/Path/App/FeaturePathShape.h b/src/Mod/Path/App/FeaturePathShape.h index 23648abcb..7478a2589 100644 --- a/src/Mod/Path/App/FeaturePathShape.h +++ b/src/Mod/Path/App/FeaturePathShape.h @@ -19,6 +19,9 @@ * Suite 330, Boston, MA 02111-1307, USA * * * ***************************************************************************/ +/* + * Copyright (c) 2017 Zheng, Lei + */ #ifndef PATH_FeaturePathShape_H @@ -26,14 +29,13 @@ #include #include -#include #include #include #include "Mod/Part/App/PropertyTopoShape.h" -#include "Path.h" #include "PropertyPath.h" #include "FeaturePath.h" +#include "Area.h" namespace Path { @@ -47,12 +49,14 @@ public: FeatureShape(void); virtual ~FeatureShape(); - Part::PropertyPartShape Shape; + // Part::PropertyPartShape Shape; + App::PropertyLinkList Sources; + App::PropertyVector StartPoint; + PARAM_PROP_DECLARE(AREA_PARAMS_PATH) //@{ /// recalculate the feature virtual App::DocumentObjectExecReturn *execute(void); - virtual short mustExecute(void) const; //@} /// returns the type name of the ViewProvider diff --git a/src/Mod/Path/App/Path.h b/src/Mod/Path/App/Path.h index db7643f16..75f460b51 100644 --- a/src/Mod/Path/App/Path.h +++ b/src/Mod/Path/App/Path.h @@ -1,95 +1,95 @@ -/*************************************************************************** - * Copyright (c) Yorik van Havre (yorik@uncreated.net) 2014 * - * * - * 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 * - * * - ***************************************************************************/ +/*************************************************************************** + * Copyright (c) Yorik van Havre (yorik@uncreated.net) 2014 * + * * + * 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 * + * * + ***************************************************************************/ + + +#ifndef PATH_Path_H +#define PATH_Path_H - -#ifndef PATH_Path_H -#define PATH_Path_H - -#include "Command.h" -//#include "Mod/Robot/App/kdl_cp/path_composite.hpp" -//#include "Mod/Robot/App/kdl_cp/frames_io.hpp" -#include -#include - -namespace Path -{ - - /** The representation of a CNC Toolpath */ - - class PathExport Toolpath : public Base::Persistence - { - TYPESYSTEM_HEADER(); - - public: - Toolpath(); - Toolpath(const Toolpath&); - ~Toolpath(); - - Toolpath &operator=(const Toolpath&); - - // from base class - virtual unsigned int getMemSize (void) const; - virtual void Save (Base::Writer &/*writer*/) const; - virtual void Restore(Base::XMLReader &/*reader*/); - void SaveDocFile (Base::Writer &writer) const; - void RestoreDocFile(Base::Reader &reader); - - // interface - void clear(void); // clears the internal data - void addCommand(const Command &Cmd); // adds a command at the end - void insertCommand(const Command &Cmd, int); // inserts a command - void deleteCommand(int); // deletes a command - double getLength(void); // return the Length (mm) of the Path - void recalculate(void); // recalculates the points - void setFromGCode(const std::string); // sets the path from the contents of the given GCode string - std::string toGCode(void) const; // gets a gcode string representation from the Path - - // shortcut functions - unsigned int getSize(void) const{return vpcCommands.size();} - const std::vector &getCommands(void)const{return vpcCommands;} - const Command &getCommand(unsigned int pos)const {return *vpcCommands[pos];} - - protected: - std::vector vpcCommands; - //KDL::Path_Composite *pcPath; - - /* - inline KDL::Frame toFrame(const Base::Placement &To){ - return KDL::Frame(KDL::Rotation::Quaternion(To.getRotation()[0], - To.getRotation()[1], - To.getRotation()[2], - To.getRotation()[3]), - KDL::Vector(To.getPosition()[0], - To.getPosition()[1], - To.getPosition()[2])); - } - inline Base::Placement toPlacement(const KDL::Frame &To){ - double x,y,z,w; - To.M.GetQuaternion(x,y,z,w); - return Base::Placement(Base::Vector3d(To.p[0],To.p[1],To.p[2]),Base::Rotation(x,y,z,w)); - } */ - }; - -} //namespace Path - - -#endif // PATH_Path_H +#include "Command.h" +//#include "Mod/Robot/App/kdl_cp/path_composite.hpp" +//#include "Mod/Robot/App/kdl_cp/frames_io.hpp" +#include +#include + +namespace Path +{ + + /** The representation of a CNC Toolpath */ + + class PathExport Toolpath : public Base::Persistence + { + TYPESYSTEM_HEADER(); + + public: + Toolpath(); + Toolpath(const Toolpath&); + ~Toolpath(); + + Toolpath &operator=(const Toolpath&); + + // from base class + virtual unsigned int getMemSize (void) const; + virtual void Save (Base::Writer &/*writer*/) const; + virtual void Restore(Base::XMLReader &/*reader*/); + void SaveDocFile (Base::Writer &writer) const; + void RestoreDocFile(Base::Reader &reader); + + // interface + void clear(void); // clears the internal data + void addCommand(const Command &Cmd); // adds a command at the end + void insertCommand(const Command &Cmd, int); // inserts a command + void deleteCommand(int); // deletes a command + double getLength(void); // return the Length (mm) of the Path + void recalculate(void); // recalculates the points + void setFromGCode(const std::string); // sets the path from the contents of the given GCode string + std::string toGCode(void) const; // gets a gcode string representation from the Path + + // shortcut functions + unsigned int getSize(void) const{return vpcCommands.size();} + const std::vector &getCommands(void)const{return vpcCommands;} + const Command &getCommand(unsigned int pos)const {return *vpcCommands[pos];} + + protected: + std::vector vpcCommands; + //KDL::Path_Composite *pcPath; + + /* + inline KDL::Frame toFrame(const Base::Placement &To){ + return KDL::Frame(KDL::Rotation::Quaternion(To.getRotation()[0], + To.getRotation()[1], + To.getRotation()[2], + To.getRotation()[3]), + KDL::Vector(To.getPosition()[0], + To.getPosition()[1], + To.getPosition()[2])); + } + inline Base::Placement toPlacement(const KDL::Frame &To){ + double x,y,z,w; + To.M.GetQuaternion(x,y,z,w); + return Base::Placement(Base::Vector3d(To.p[0],To.p[1],To.p[2]),Base::Rotation(x,y,z,w)); + } */ + }; + +} //namespace Path + + +#endif // PATH_Path_H diff --git a/src/Mod/Path/Gui/AppPathGui.cpp b/src/Mod/Path/Gui/AppPathGui.cpp index 1e4d93faf..70a9186e1 100644 --- a/src/Mod/Path/Gui/AppPathGui.cpp +++ b/src/Mod/Path/Gui/AppPathGui.cpp @@ -80,6 +80,8 @@ PyMODINIT_FUNC initPathGui() PathGui::ViewProviderPathPython ::init(); PathGui::ViewProviderArea ::init(); PathGui::ViewProviderAreaPython ::init(); + PathGui::ViewProviderAreaView ::init(); + PathGui::ViewProviderAreaViewPython ::init(); // add resources and reloads the translators loadPathResource(); diff --git a/src/Mod/Path/Gui/Command.cpp b/src/Mod/Path/Gui/Command.cpp index 07528e0c9..2f7a125b9 100644 --- a/src/Mod/Path/Gui/Command.cpp +++ b/src/Mod/Path/Gui/Command.cpp @@ -68,39 +68,55 @@ void CmdPathArea::activated(int iMsg) Q_UNUSED(iMsg); std::list cmds; std::ostringstream sources; + std::string areaName; + bool addView = true; for(const Gui::SelectionObject &selObj : getSelection().getSelectionEx(NULL, Part::Feature::getClassTypeId())) { const Part::Feature *pcObj = static_cast(selObj.getObject()); const std::vector &subnames = selObj.getSubNames(); + if(addView && areaName.size()) addView = false; + if(subnames.empty()) { + if(addView && pcObj->getTypeId().isDerivedFrom(Path::FeatureArea::getClassTypeId())) + areaName = pcObj->getNameInDocument(); sources << "FreeCAD.activeDocument()." << pcObj->getNameInDocument() << ","; continue; } for(const std::string &name : subnames) { - if(!name.compare(0,4,"Face") && - !name.compare(0,4,"Edge")) - { + if(name.compare(0,4,"Face") && name.compare(0,4,"Edge")) { Base::Console().Error("Selected shape is not 2D\n"); return; } - int index = atoi(name.substr(4).c_str()); - std::ostringstream subname; subname << pcObj->getNameInDocument() << '_' << name; std::string sub_fname = getUniqueObjectName(subname.str().c_str()); std::ostringstream cmd; cmd << "FreeCAD.activeDocument().addObject('Part::Feature','" << sub_fname << - "').Shape = FreeCAD.activeDocument()." << pcObj->getNameInDocument() << ".Shape." << - name.substr(0,4) << "s[" << index-1 << ']'; + "').Shape = PathCommands.findShape(FreeCAD.activeDocument()." << + pcObj->getNameInDocument() << ".Shape,'" << name << "'"; + if(!name.compare(0,4,"Edge")) + cmd << ",'Wires'"; + cmd << ')'; cmds.push_back(cmd.str()); sources << "FreeCAD.activeDocument()." << sub_fname << ","; } } + if(addView && areaName.size()) { + std::string FeatName = getUniqueObjectName("FeatureAreaView"); + openCommand("Create Path Area View"); + doCommand(Doc,"FreeCAD.activeDocument().addObject('Path::FeatureAreaView','%s')",FeatName.c_str()); + doCommand(Doc,"FreeCAD.activeDocument().%s.Source = FreeCAD.activeDocument().%s", + FeatName.c_str(),areaName.c_str()); + commitCommand(); + updateActive(); + return; + } std::string FeatName = getUniqueObjectName("FeatureArea"); openCommand("Create Path Area"); + doCommand(Doc,"import PathCommands"); for(const std::string &cmd : cmds) doCommand(Doc,cmd.c_str()); doCommand(Doc,"FreeCAD.activeDocument().addObject('Path::FeatureArea','%s')",FeatName.c_str()); @@ -171,17 +187,12 @@ void CmdPathAreaWorkplane::activated(int iMsg) } for(const std::string &name : subnames) { - if(!name.compare(0,4,"Face") && - !name.compare(0,4,"Edge")) - { + if(name.compare(0,4,"Face") && name.compare(0,4,"Edge")) { Base::Console().Error("Selected shape is not 2D\n"); return; } - - int index = atoi(name.substr(4).c_str()); - std::ostringstream subname; - subname << planeSubname << '.' << name.substr(0,4) << "s[" << index-1 << ']'; + subname << planeSubname << ",'" << name << "','Wires'"; planeSubname = subname.str(); } } @@ -195,10 +206,10 @@ void CmdPathAreaWorkplane::activated(int iMsg) } openCommand("Select Workplane for Path Area"); - doCommand(Doc,"FreeCAD.activeDocument().%s.WorkPlane = FreeCAD.activeDocument().%s", - areaName.c_str(),planeSubname.c_str()); + doCommand(Doc,"import PathCommands"); + doCommand(Doc,"FreeCAD.activeDocument().%s.WorkPlane = PathCommands.findShape(" + "FreeCAD.activeDocument().%s)", areaName.c_str(),planeSubname.c_str()); doCommand(Doc,"FreeCAD.activeDocument().%s.ViewObject.Visibility = True",areaName.c_str()); - // doCommand(Doc,"FreeCAD.activeDocument().%s.ViewObject.Visibility = False",planeName.c_str()); commitCommand(); updateActive(); } @@ -284,24 +295,48 @@ CmdPathShape::CmdPathShape() void CmdPathShape::activated(int iMsg) { Q_UNUSED(iMsg); - std::vector Sel = getSelection().getSelection(); - if (Sel.size() == 1) { - if (Sel[0].pObject->getTypeId().isDerivedFrom(Part::Feature::getClassTypeId())) { - Part::Feature *pcPartObject = static_cast(Sel[0].pObject); - std::string FeatName = getUniqueObjectName("PathShape"); - openCommand("Create Path Compound"); - doCommand(Doc,"FreeCAD.activeDocument().addObject('Path::FeatureShape','%s')",FeatName.c_str()); - doCommand(Doc,"FreeCAD.activeDocument().%s.Shape = FreeCAD.activeDocument().%s.Shape.copy()",FeatName.c_str(),pcPartObject->getNameInDocument()); - commitCommand(); - updateActive(); - } else { - Base::Console().Error("Exactly one shape object must be selected\n"); - return; + std::list cmds; + std::ostringstream sources; + for(const Gui::SelectionObject &selObj : + getSelection().getSelectionEx(NULL, Part::Feature::getClassTypeId())) + { + const Part::Feature *pcObj = static_cast(selObj.getObject()); + const std::vector &subnames = selObj.getSubNames(); + if(subnames.empty()) { + sources << "FreeCAD.activeDocument()." << pcObj->getNameInDocument() << ","; + continue; + } + for(const std::string &name : subnames) { + if(name.compare(0,4,"Face") && name.compare(0,4,"Edge")) { + Base::Console().Warning("Ignored shape %s %s\n", + pcObj->getNameInDocument(), name.c_str()); + continue; + } + + std::ostringstream subname; + subname << pcObj->getNameInDocument() << '_' << name; + std::string sub_fname = getUniqueObjectName(subname.str().c_str()); + + std::ostringstream cmd; + cmd << "FreeCAD.activeDocument().addObject('Part::Feature','" << sub_fname << + "').Shape = PathCommands.findShape(FreeCAD.activeDocument()." << + pcObj->getNameInDocument() << ".Shape,'" << name << "'"; + if(!name.compare(0,4,"Edge")) + cmd << ",'Wires'"; + cmd << ')'; + cmds.push_back(cmd.str()); + sources << "FreeCAD.activeDocument()." << sub_fname << ","; } - } else { - Base::Console().Error("Exactly one shape object must be selected\n"); - return; } + std::string FeatName = getUniqueObjectName("PathShape"); + openCommand("Create Path Shape"); + doCommand(Doc,"import PathCommands"); + for(const std::string &cmd : cmds) + doCommand(Doc,cmd.c_str()); + doCommand(Doc,"FreeCAD.activeDocument().addObject('Path::FeatureShape','%s')",FeatName.c_str()); + doCommand(Doc,"FreeCAD.activeDocument().%s.Sources = [ %s ]",FeatName.c_str(),sources.str().c_str()); + commitCommand(); + updateActive(); } bool CmdPathShape::isActive(void) diff --git a/src/Mod/Path/Gui/Resources/Path.qrc b/src/Mod/Path/Gui/Resources/Path.qrc index 4ad635018..8600ec94e 100644 --- a/src/Mod/Path/Gui/Resources/Path.qrc +++ b/src/Mod/Path/Gui/Resources/Path.qrc @@ -47,6 +47,7 @@ icons/Path-Toolpath.svg icons/Path-ToolTable.svg icons/Path-Area.svg + icons/Path-Area-View.svg icons/Path-Area-Workplane.svg icons/preferences-path.svg panels/ContourEdit.ui diff --git a/src/Mod/Path/Gui/Resources/icons/Path-Area-View.svg b/src/Mod/Path/Gui/Resources/icons/Path-Area-View.svg new file mode 100644 index 000000000..504106dcb --- /dev/null +++ b/src/Mod/Path/Gui/Resources/icons/Path-Area-View.svg @@ -0,0 +1,657 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + Path-FaceProfile + 2016-01-19 + http://www.freecadweb.org/wiki/index.php?title=Artwork + + + FreeCAD + + + FreeCAD/src/Mod/Path/Gui/Resources/icons/Path- + + + FreeCAD LGPL2+ + + + https://www.gnu.org/copyleft/lesser.html + + + [agryson] Alexander Gryson + + + + + + + + + + + + + + + diff --git a/src/Mod/Path/Gui/ViewProviderArea.cpp b/src/Mod/Path/Gui/ViewProviderArea.cpp index 2a3057f20..040bacbea 100644 --- a/src/Mod/Path/Gui/ViewProviderArea.cpp +++ b/src/Mod/Path/Gui/ViewProviderArea.cpp @@ -115,11 +115,83 @@ bool ViewProviderArea::onDelete(const std::vector &) // Python object ----------------------------------------------------------------------- +PROPERTY_SOURCE(PathGui::ViewProviderAreaView, PartGui::ViewProviderPlaneParametric) + +ViewProviderAreaView::ViewProviderAreaView() +{ + sPixmap = "Path-Area-View.svg"; +} + +ViewProviderAreaView::~ViewProviderAreaView() +{ +} + +std::vector ViewProviderAreaView::claimChildren(void) const +{ + std::vector ret; + Path::FeatureAreaView* feature = static_cast(getObject()); + if(feature->Source.getValue()) + ret.push_back(feature->Source.getValue()); + return ret; +} + +bool ViewProviderAreaView::canDragObjects() const +{ + return true; +} + +bool ViewProviderAreaView::canDragObject(App::DocumentObject* obj) const +{ + return obj && obj->getTypeId().isDerivedFrom(Path::FeatureArea::getClassTypeId()); +} + +void ViewProviderAreaView::dragObject(App::DocumentObject* ) +{ + Path::FeatureAreaView* feature = static_cast(getObject()); + feature->Source.setValue(NULL); +} + +bool ViewProviderAreaView::canDropObjects() const +{ + return true; +} + +bool ViewProviderAreaView::canDropObject(App::DocumentObject* obj) const +{ + return canDragObject(obj); +} + +void ViewProviderAreaView::dropObject(App::DocumentObject* obj) +{ + Path::FeatureAreaView* feature = static_cast(getObject()); + feature->Source.setValue(obj); +} + +void ViewProviderAreaView::updateData(const App::Property* prop) +{ + PartGui::ViewProviderPlaneParametric::updateData(prop); + if (prop->getTypeId() == App::PropertyLink::getClassTypeId()) + Gui::Application::Instance->hideViewProvider( + static_cast(prop)->getValue()); +} + +bool ViewProviderAreaView::onDelete(const std::vector &) +{ + Path::FeatureAreaView* feature = static_cast(getObject()); + Gui::Application::Instance->showViewProvider(feature->Source.getValue()); + return true; +} + +// Python object ----------------------------------------------------------------------- + namespace Gui { /// @cond DOXERR PROPERTY_SOURCE_TEMPLATE(PathGui::ViewProviderAreaPython, PathGui::ViewProviderArea) +PROPERTY_SOURCE_TEMPLATE(PathGui::ViewProviderAreaViewPython, PathGui::ViewProviderAreaView) /// @endcond // explicit template instantiation template class PathGuiExport ViewProviderPythonFeatureT; +template class PathGuiExport ViewProviderPythonFeatureT; } + diff --git a/src/Mod/Path/Gui/ViewProviderArea.h b/src/Mod/Path/Gui/ViewProviderArea.h index 7d3b43313..f149d856b 100644 --- a/src/Mod/Path/Gui/ViewProviderArea.h +++ b/src/Mod/Path/Gui/ViewProviderArea.h @@ -54,6 +54,29 @@ public: typedef Gui::ViewProviderPythonFeatureT ViewProviderAreaPython; + +class PathGuiExport ViewProviderAreaView : public PartGui::ViewProviderPlaneParametric +{ + PROPERTY_HEADER(PathGui::ViewProviderAreaView); + +public: + ViewProviderAreaView(); + virtual ~ViewProviderAreaView(); + virtual std::vector claimChildren(void) const; + virtual void updateData(const App::Property*); + virtual bool onDelete(const std::vector &); + + /// drag and drop + virtual bool canDragObjects() const; + virtual bool canDragObject(App::DocumentObject*) const; + virtual void dragObject(App::DocumentObject*); + virtual bool canDropObjects() const; + virtual bool canDropObject(App::DocumentObject*) const; + virtual void dropObject(App::DocumentObject*); +}; + +typedef Gui::ViewProviderPythonFeatureT ViewProviderAreaViewPython; + } //namespace PathGui diff --git a/src/Mod/Path/Gui/ViewProviderPathShape.cpp b/src/Mod/Path/Gui/ViewProviderPathShape.cpp index 52f4c71a0..81b21372e 100644 --- a/src/Mod/Path/Gui/ViewProviderPathShape.cpp +++ b/src/Mod/Path/Gui/ViewProviderPathShape.cpp @@ -26,8 +26,11 @@ #ifndef _PreComp_ #endif -#include "ViewProviderPathShape.h" #include +#include +#include +#include +#include "ViewProviderPathShape.h" using namespace Gui; using namespace PathGui; @@ -38,3 +41,74 @@ QIcon ViewProviderPathShape::getIcon() const { return Gui::BitmapFactory().pixmap("Path-Shape"); } + +std::vector ViewProviderPathShape::claimChildren(void) const +{ + return std::vector( + static_cast(getObject())->Sources.getValues()); +} + +bool ViewProviderPathShape::canDragObjects() const +{ + return true; +} + +bool ViewProviderPathShape::canDragObject(App::DocumentObject* obj) const +{ + return obj && obj->getTypeId().isDerivedFrom(Part::Feature::getClassTypeId()); +} + +void ViewProviderPathShape::dragObject(App::DocumentObject* obj) +{ + Path::FeatureShape *feature = static_cast(getObject()); + std::vector sources = feature->Sources.getValues(); + for (std::vector::iterator it = sources.begin(); it != sources.end(); ++it) { + if (*it == obj) { + sources.erase(it); + feature->Sources.setValues(sources); + break; + } + } +} + +bool ViewProviderPathShape::canDropObjects() const +{ + return true; +} + +bool ViewProviderPathShape::canDropObject(App::DocumentObject* obj) const +{ + return canDragObject(obj); +} + +void ViewProviderPathShape::dropObject(App::DocumentObject* obj) +{ + Path::FeatureShape *feature = static_cast(getObject()); + std::vector sources = feature->Sources.getValues(); + sources.push_back(obj); + feature->Sources.setValues(sources); +} + +void ViewProviderPathShape::updateData(const App::Property* prop) +{ + PathGui::ViewProviderPath::updateData(prop); + if (prop->getTypeId() == App::PropertyLinkList::getClassTypeId()) { + std::vector pShapes = static_cast(prop)->getValues(); + for (std::vector::iterator it = pShapes.begin(); it != pShapes.end(); ++it) { + if (*it) + Gui::Application::Instance->hideViewProvider(*it); + } + } +} + +bool ViewProviderPathShape::onDelete(const std::vector &) +{ + // get the input shapes + Path::FeatureShape *feature = static_cast(getObject()); + std::vector pShapes =feature->Sources.getValues(); + for (std::vector::iterator it = pShapes.begin(); it != pShapes.end(); ++it) { + if (*it) + Gui::Application::Instance->showViewProvider(*it); + } + return true; +} diff --git a/src/Mod/Path/Gui/ViewProviderPathShape.h b/src/Mod/Path/Gui/ViewProviderPathShape.h index 8e525dcf4..7aeb95083 100644 --- a/src/Mod/Path/Gui/ViewProviderPathShape.h +++ b/src/Mod/Path/Gui/ViewProviderPathShape.h @@ -34,6 +34,19 @@ class PathGuiExport ViewProviderPathShape: public ViewProviderPath PROPERTY_HEADER(PathGui::ViewProviderPathShape); public: + + /// grouping handling + virtual std::vector claimChildren(void) const; + virtual void updateData(const App::Property*); + virtual bool onDelete(const std::vector &); + + /// drag and drop + virtual bool canDragObjects() const; + virtual bool canDragObject(App::DocumentObject*) const; + virtual void dragObject(App::DocumentObject*); + virtual bool canDropObjects() const; + virtual bool canDropObject(App::DocumentObject*) const; + virtual void dropObject(App::DocumentObject*); QIcon getIcon(void) const; }; diff --git a/src/Mod/Path/InitGui.py b/src/Mod/Path/InitGui.py index be7d46a6a..11a1120d1 100644 --- a/src/Mod/Path/InitGui.py +++ b/src/Mod/Path/InitGui.py @@ -81,12 +81,12 @@ class PathWorkbench (Workbench): # build commands list projcmdlist = ["Path_Job", "Path_Post", "Path_Inspect", "Path_Sanity"] toolcmdlist = ["Path_ToolLibraryEdit", "Path_LoadTool"] - prepcmdlist = ["Path_Plane", "Path_Fixture", "Path_ToolLenOffset", "Path_Comment", "Path_Stop", "Path_FaceProfile", "Path_FacePocket", "Path_Custom", "Path_FromShape"] + prepcmdlist = ["Path_Plane", "Path_Fixture", "Path_ToolLenOffset", "Path_Comment", "Path_Stop", "Path_FaceProfile", "Path_FacePocket", "Path_Custom", "Path_Shape"] twodopcmdlist = ["Path_Contour", "Path_Profile", "Path_Profile_Edges", "Path_Pocket", "Path_Drilling", "Path_Engrave", "Path_MillFace", "Path_Helix"] threedopcmdlist = ["Path_Surfacing"] modcmdlist = ["Path_Copy", "Path_CompoundExtended", "Path_Array", "Path_SimpleCopy" ] dressupcmdlist = ["PathDressup_Dogbone", "PathDressup_DragKnife", "PathDressup_HoldingTags"] - extracmdlist = ["Path_SelectLoop", "Path_Area", "Path_Area_Workplane"] + extracmdlist = ["Path_SelectLoop", "Path_Shape", "Path_Area", "Path_Area_Workplane"] #modcmdmore = ["Path_Hop",] #remotecmdlist = ["Path_Remote"] diff --git a/src/Mod/Path/PathCommands.py b/src/Mod/Path/PathCommands.py index 128f9b19a..d837c98be 100644 --- a/src/Mod/Path/PathCommands.py +++ b/src/Mod/Path/PathCommands.py @@ -78,3 +78,28 @@ class _CommandSelectLoop: if FreeCAD.GuiUp: FreeCADGui.addCommand('Path_SelectLoop',_CommandSelectLoop()) +def findShape(shape,subname=None,subtype=None): + '''To find a higher oder shape containing the subshape with subname. + E.g. to find the wire containing 'Edge1' in shape, + findShape(shape,'Edge1','Wires') + ''' + if not subname: + return shape + ret = shape.getElement(subname) + if not subtype or not ret or ret.isNull(): + return ret; + if subname.startswith('Face'): + tp = 'Faces' + elif subname.startswith('Edge'): + tp = 'Edges' + elif subname.startswith('Vertex'): + tp = 'Vertex' + else: + return ret + for obj in getattr(shape,subtype): + for sobj in getattr(obj,tp): + if sobj.isEqual(ret): + return obj + return ret + + diff --git a/src/Mod/Path/libarea/Area.cpp b/src/Mod/Path/libarea/Area.cpp index 2ed88cfaa..7323ce3f5 100644 --- a/src/Mod/Path/libarea/Area.cpp +++ b/src/Mod/Path/libarea/Area.cpp @@ -73,6 +73,58 @@ Point CArea::NearestPoint(const Point& p)const return best_point; } +void CArea::ChangeStartToNearest(const Point *point, double min_dist) { + + for(std::list::const_iterator It=m_curves.begin(),ItNext=It; + It != m_curves.end(); It=ItNext) + { + ++ItNext; + if(It->m_vertices.size()<=1) + m_curves.erase(It); + } + + if(m_curves.empty()) return; + + std::list curves; + Point p; + if(point) p =*point; + if(min_dist < Point::tolerance) + min_dist = Point::tolerance; + + while(m_curves.size()) { + std::list::iterator It=m_curves.begin(); + std::list::iterator ItBest=It++; + Point best_point = ItBest->NearestPoint(p); + double best_dist = p.dist(best_point); + for(; It != m_curves.end(); ++It) + { + const CCurve& curve = *It; + Point near_point = curve.NearestPoint(p); + double dist = near_point.dist(p); + if(dist < best_dist) + { + best_dist = dist; + best_point = near_point; + ItBest = It; + } + } + if(ItBest->IsClosed()) { + ItBest->ChangeStart(best_point); + }else if(ItBest->m_vertices.back().m_p.dist(best_point)<=min_dist) { + ItBest->Reverse(); + }else if(ItBest->m_vertices.front().m_p.dist(best_point)>min_dist) { + ItBest->Break(best_point); + m_curves.push_back(*ItBest); + m_curves.back().ChangeEnd(best_point); + ItBest->ChangeStart(best_point); + } + curves.splice(curves.end(),m_curves,ItBest); + p = curves.back().m_vertices.back().m_p; + } + m_curves.splice(m_curves.end(),curves); +} + + void CArea::GetBox(CBox2D &box) { for(std::list::iterator It = m_curves.begin(); It != m_curves.end(); It++) diff --git a/src/Mod/Path/libarea/Area.h b/src/Mod/Path/libarea/Area.h index 7a6634ac5..ca9a64152 100644 --- a/src/Mod/Path/libarea/Area.h +++ b/src/Mod/Path/libarea/Area.h @@ -85,6 +85,8 @@ public: void CurveIntersections(const CCurve& curve, std::list &pts)const; void InsideCurves(const CCurve& curve, std::list &curves_inside)const; + void ChangeStartToNearest(const Point *point=NULL, double min_dist=1.0); + //Avoid outside direct accessing static member variable because of Windows DLL issue #define CAREA_PARAM_DECLARE(_type,_name) \ static _type get_##_name();\ diff --git a/src/Mod/Path/libarea/AreaClipper.cpp b/src/Mod/Path/libarea/AreaClipper.cpp index bcb7ccd42..a4f4f0e2e 100644 --- a/src/Mod/Path/libarea/AreaClipper.cpp +++ b/src/Mod/Path/libarea/AreaClipper.cpp @@ -468,16 +468,23 @@ void CArea::Offset(double inwards_value) void CArea::PopulateClipper(Clipper &c, PolyType type) const { + int skipped = 0; for (std::list::const_iterator It = m_curves.begin(); It != m_curves.end(); It++) { const CCurve &curve = *It; bool closed = curve.IsClosed(); - if(type == ptClip && !closed) - continue; + if(!closed) { + if(type == ptClip){ + ++skipped; + continue; + } + } TPolygon p; MakePoly(curve, p, false); c.AddPath(p, type, closed); } + if(skipped) + std::cout << "libarea: warning skipped " << skipped << " open wires" << std::endl; } void CArea::Clip(ClipType op, const CArea *a,