diff --git a/CMakeLists.txt b/CMakeLists.txt index 926e5d5c15..2b86cc8232 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -98,6 +98,13 @@ if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_COMPILER_IS_CLANGXX) endif() endif(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_COMPILER_IS_CLANGXX) +if(CMAKE_COMPILER_IS_CLANGXX) + # older boost.preprocessor turn off variadics for clang + add_definitions(-DBOOST_PP_VARIADICS=1) + message(STATUS "Force BOOST_PP_VARIADICS=1 for clang") +endif() + + # ================================================================================ # Output directories for install target diff --git a/src/Mod/Part/App/AppPartPy.cpp b/src/Mod/Part/App/AppPartPy.cpp index 1a5b24493d..6367229b2c 100644 --- a/src/Mod/Part/App/AppPartPy.cpp +++ b/src/Mod/Part/App/AppPartPy.cpp @@ -147,7 +147,7 @@ struct EdgePoints { TopoDS_Edge edge; }; -static std::list sort_Edges(double tol3d, std::list& edges) +PartExport std::list sort_Edges(double tol3d, std::list& edges) { tol3d = tol3d * tol3d; std::list edge_points; diff --git a/src/Mod/Part/App/Geom2d/Curve2dPyImp.cpp b/src/Mod/Part/App/Geom2d/Curve2dPyImp.cpp index 2a79113737..67a57a4b98 100644 --- a/src/Mod/Part/App/Geom2d/Curve2dPyImp.cpp +++ b/src/Mod/Part/App/Geom2d/Curve2dPyImp.cpp @@ -105,7 +105,9 @@ PyObject* Curve2dPy::reverse(PyObject * args) return 0; } +namespace Part { extern Py::Object shape2pyshape(const TopoDS_Shape &shape); +} PyObject* Curve2dPy::toShape(PyObject *args) { diff --git a/src/Mod/Part/App/TopoShapePyImp.cpp b/src/Mod/Part/App/TopoShapePyImp.cpp index dcc1cd385b..8463311e0c 100644 --- a/src/Mod/Part/App/TopoShapePyImp.cpp +++ b/src/Mod/Part/App/TopoShapePyImp.cpp @@ -157,8 +157,9 @@ int TopoShapePy::PyInit(PyObject* args, PyObject*) return 0; } +namespace Part { //common code.. maybe put somewhere else? -Py::Object shape2pyshape(const TopoDS_Shape &shape) +PartExport Py::Object shape2pyshape(const TopoDS_Shape &shape) { PyObject* ret = 0; if (!shape.IsNull()) { @@ -204,6 +205,7 @@ Py::Object shape2pyshape(const TopoDS_Shape &shape) return Py::asObject(ret); } +} //namespace Part PyObject* TopoShapePy::copy(PyObject *args) { diff --git a/src/Mod/Path/App/AppPath.cpp b/src/Mod/Path/App/AppPath.cpp index 3acf8b8284..eb7f6dbf9b 100644 --- a/src/Mod/Path/App/AppPath.cpp +++ b/src/Mod/Path/App/AppPath.cpp @@ -42,6 +42,8 @@ #include "PropertyTooltable.h" #include "FeaturePathCompound.h" #include "FeaturePathShape.h" +#include "AreaPy.h" +#include "FeatureArea.h" namespace Path { extern PyObject* initModule(); @@ -50,15 +52,24 @@ extern PyObject* initModule(); /* Python entry */ PyMOD_INIT_FUNC(Path) { + // load dependent module + try { + Base::Interpreter().runString("import Part"); + } + catch(const Base::Exception& e) { + PyErr_SetString(PyExc_ImportError, e.what()); + return; + } + PyObject* pathModule = Path::initModule(); Base::Console().Log("Loading Path module... done\n"); - // Add Types to module Base::Interpreter().addType(&Path::CommandPy ::Type, pathModule, "Command"); Base::Interpreter().addType(&Path::PathPy ::Type, pathModule, "Path"); Base::Interpreter().addType(&Path::ToolPy ::Type, pathModule, "Tool"); Base::Interpreter().addType(&Path::TooltablePy ::Type, pathModule, "Tooltable"); + Base::Interpreter().addType(&Path::AreaPy ::Type, pathModule, "Area"); // NOTE: To finish the initialization of our own type objects we must // call PyType_Ready, otherwise we run into a segmentation fault, later on. @@ -75,6 +86,11 @@ PyMOD_INIT_FUNC(Path) Path::FeatureCompoundPython ::init(); Path::FeatureShape ::init(); Path::FeatureShapePython ::init(); + Path::Area ::init(); + Path::FeatureArea ::init(); + Path::FeatureAreaPython ::init(); + Path::FeatureAreaView ::init(); + Path::FeatureAreaViewPython ::init(); PyMOD_Return(pathModule); } diff --git a/src/Mod/Path/App/AppPathPy.cpp b/src/Mod/Path/App/AppPathPy.cpp index 79365f0834..d2c10f97ff 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,23 @@ 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, start=Vector(), " PARAM_PY_ARGS_DOC(ARG,AREA_PARAMS_PATH) ", key=value...)\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) + "\n* : any key supported by Path.Area, see Path.Area.getParamDesc() for description" + ); + add_keyword_method("sortWires",&Module::sortWires, + "sortWires(shapes, start=Vector(), " PARAM_PY_ARGS_DOC(ARG,AREA_PARAMS_SORT) ", key=value...)\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" + PARAM_PY_DOC(ARG, AREA_PARAMS_SORT) + "\n* : any key supported by Path.Area, see Path.Area.getParamDesc() for description" + ); initialize("This module is the Path module."); // register with Python } @@ -261,7 +317,115 @@ 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) + PARAM_PY_DECLARE_INIT(PARAM_FNAME,AREA_PARAMS_CONF) + PyObject *pShapes=NULL; + PyObject *start=NULL; + static char* kwd_list[] = {"shapes", "start", + PARAM_FIELD_STRINGS(ARG,AREA_PARAMS_PATH), + PARAM_FIELD_STRINGS(NAME,AREA_PARAMS_CONF), NULL}; + if (!PyArg_ParseTupleAndKeywords(args.ptr(), kwds.ptr(), + "O|O!" PARAM_PY_KWDS(AREA_PARAMS_PATH) PARAM_PY_KWDS(AREA_PARAMS_CONF), + kwd_list, &pShapes, &(Base::VectorPy::Type), &start, + PARAM_REF(PARAM_FARG,AREA_PARAMS_PATH), + PARAM_REF(PARAM_FNAME,AREA_PARAMS_CONF))) + 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()); + } + } + +#define AREA_GET(_param) \ + params.PARAM_FNAME(_param) = \ + PARAM_TYPED(PARAM_CAST_PY_,_param)(PARAM_FNAME(_param)); + AreaParams params; + PARAM_FOREACH(AREA_GET,AREA_PARAMS_CONF) + + 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,¶ms, &pstart, NULL, + 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) + { + PARAM_PY_DECLARE_INIT(PARAM_FARG,AREA_PARAMS_SORT) + PARAM_PY_DECLARE_INIT(PARAM_FNAME,AREA_PARAMS_CONF) + PyObject *pShapes=NULL; + PyObject *start=NULL; + static char* kwd_list[] = {"shapes", "start", + PARAM_FIELD_STRINGS(ARG,AREA_PARAMS_SORT), + PARAM_FIELD_STRINGS(NAME,AREA_PARAMS_CONF), NULL}; + if (!PyArg_ParseTupleAndKeywords(args.ptr(), kwds.ptr(), + "O|O!" PARAM_PY_KWDS(AREA_PARAMS_SORT) PARAM_PY_KWDS(AREA_PARAMS_CONF), + kwd_list, &pShapes, &(Base::VectorPy::Type), &start, + PARAM_REF(PARAM_FARG,AREA_PARAMS_SORT), + PARAM_REF(PARAM_FNAME,AREA_PARAMS_CONF))) + 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()); + } + } + + AreaParams params; + 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)); + 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 new file mode 100644 index 0000000000..6e7a6364e6 --- /dev/null +++ b/src/Mod/Path/App/Area.cpp @@ -0,0 +1,1860 @@ +/**************************************************************************** + * Copyright (c) 2017 Zheng, Lei (realthunder) * + * * + * 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 * + * * + ****************************************************************************/ +#include "PreCompiled.h" + +#ifndef _PreComp_ +#endif +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include "Area.h" +#include "../libarea/Area.h" + +using namespace Path; + +CAreaParams::CAreaParams() + :PARAM_INIT(PARAM_FNAME,AREA_PARAMS_CAREA) +{} + +AreaParams::AreaParams() + :PARAM_INIT(PARAM_FNAME,AREA_PARAMS_AREA) +{} + +CAreaConfig::CAreaConfig(const CAreaParams &p, bool noFitArcs) +{ +#define AREA_CONF_SAVE_AND_APPLY(_param) \ + PARAM_FNAME(_param) = BOOST_PP_CAT(CArea::get_,PARAM_FARG(_param))();\ + BOOST_PP_CAT(CArea::set_,PARAM_FARG(_param))(p.PARAM_FNAME(_param)); + + PARAM_FOREACH(AREA_CONF_SAVE_AND_APPLY,AREA_PARAMS_CAREA) + + // Arc fitting is lossy. we shall reduce the number of unecessary fit + if(noFitArcs) + CArea::set_fit_arcs(false); + +} + +CAreaConfig::~CAreaConfig() { + +#define AREA_CONF_RESTORE(_param) \ + BOOST_PP_CAT(CArea::set_,PARAM_FARG(_param))(PARAM_FNAME(_param)); + + PARAM_FOREACH(AREA_CONF_RESTORE,AREA_PARAMS_CAREA) +} + +////////////////////////////////////////////////////////////////////////////// + +TYPESYSTEM_SOURCE(Path::Area, Base::BaseClass); + +bool Area::s_aborting; + +Area::Area(const AreaParams *params) +:myParams(s_params) +,myHaveFace(false) +,myHaveSolid(false) +,myShapeDone(false) +{ + if(params) + setParams(*params); +} + +Area::Area(const Area &other, bool deep_copy) +:Base::BaseClass(other) +,myShapes(other.myShapes) +,myTrsf(other.myTrsf) +,myParams(other.myParams) +,myWorkPlane(other.myWorkPlane) +,myHaveFace(other.myHaveFace) +,myHaveSolid(other.myHaveSolid) +,myShapeDone(false) +{ + if(!deep_copy || !other.isBuilt()) + return; + if(other.myArea) + myArea.reset(new CArea(*other.myArea)); + myShapePlane = other.myShapePlane; + myShape = other.myShape; + myShapeDone = other.myShapeDone; + mySections.reserve(other.mySections.size()); + for(shared_ptr area:mySections) + mySections.push_back(make_shared(*area,true)); +} + +Area::~Area() { + clean(); +} + +void Area::setPlane(const TopoDS_Shape &shape) { + if(shape.IsNull()) { + myWorkPlane.Nullify(); + return; + } + gp_Trsf trsf; + TopoDS_Shape plane = findPlane(shape,trsf); + if (plane.IsNull()) + throw Base::ValueError("shape is not planar"); + myWorkPlane = plane; + myTrsf = trsf; + clean(); +} + +bool Area::isCoplanar(const TopoDS_Shape &s1, const TopoDS_Shape &s2) { + if(s1.IsNull() || s2.IsNull()) return false; + if(s1.IsEqual(s2)) return true; + TopoDS_Builder builder; + TopoDS_Compound comp; + builder.MakeCompound(comp); + builder.Add(comp,s1); + builder.Add(comp,s2); + BRepLib_FindSurface planeFinder(comp,-1,Standard_True); + return planeFinder.Found(); +} + +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 reorient) +{ + bool haveShape = false; + int skipped = 0; + for (TopExp_Explorer it(shape, TopAbs_FACE); it.More(); it.Next()) { + haveShape = true; + const TopoDS_Face &face = TopoDS::Face(it.Current()); + if(plane && !isCoplanar(face,*plane)) { + ++skipped; + if(force_coplanar) continue; + } + for (TopExp_Explorer it(face, TopAbs_WIRE); it.More(); it.Next()) + add(area,TopoDS::Wire(it.Current()),trsf,deflection); + } + + if(haveShape) return skipped; + + CArea _area; + CArea _areaOpen; + + for (TopExp_Explorer it(shape, TopAbs_WIRE); it.More(); it.Next()) { + haveShape = true; + const TopoDS_Wire &wire = TopoDS::Wire(it.Current()); + if(plane && !isCoplanar(wire,*plane)) { + ++skipped; + if(force_coplanar) continue; + } + if(BRep_Tool::IsClosed(wire)) + add(_area,wire,trsf,deflection); + 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,true); + }else + add(_areaOpen,wire,trsf,deflection); + } + + if(!haveShape) { + for (TopExp_Explorer it(shape, TopAbs_EDGE); it.More(); it.Next()) { + if(plane && !isCoplanar(it.Current(),*plane)) { + ++skipped; + if(force_coplanar) continue; + } + TopoDS_Wire wire = BRepBuilderAPI_MakeWire( + TopoDS::Edge(it.Current())).Wire(); + add(BRep_Tool::IsClosed(wire)?_area:_areaOpen,wire,trsf,deflection); + } + } + + if(reorient) + _area.Reorder(); + area.m_curves.splice(area.m_curves.end(),_area.m_curves); + if(areaOpen) + areaOpen->m_curves.splice(areaOpen->m_curves.end(),_areaOpen.m_curves); + else + area.m_curves.splice(area.m_curves.end(),_areaOpen.m_curves); + return skipped; +} + +void Area::add(CArea &area, const TopoDS_Wire& wire, + const gp_Trsf *trsf, double deflection, bool to_edges) +{ + CCurve ccurve; + BRepTools_WireExplorer xp(trsf?TopoDS::Wire( + wire.Moved(TopLoc_Location(*trsf))):wire); + + gp_Pnt p = BRep_Tool::Pnt(xp.CurrentVertex()); + ccurve.append(CVertex(Point(p.X(),p.Y()))); + + for (;xp.More();xp.Next()) { + const TopoDS_Edge &edge = TopoDS::Edge(xp.Current()); + BRepAdaptor_Curve curve(edge); + bool reversed = (xp.Current().Orientation()==TopAbs_REVERSED); + + p = curve.Value(reversed?curve.FirstParameter():curve.LastParameter()); + + 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:{ + 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; + } + //fall through + } default: { + // Discretize all other type of curves + GCPnts_QuasiUniformDeflection discretizer(curve, deflection, + curve.FirstParameter(), curve.LastParameter()); + if (discretizer.IsDone () && discretizer.NbPoints () > 1) { + int nbPoints = discretizer.NbPoints (); + //strangly OCC discretizer points are one-based, not zero-based, why? + if(reversed) { + for (int i=nbPoints-1; i>=1; --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{ + for (int i=2; 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(!to_edges) { + if(BRep_Tool::IsClosed(wire) && !ccurve.IsClosed()) { + AREA_WARN("ccurve not closed"); + ccurve.append(ccurve.m_vertices.front()); + } + area.append(ccurve); + } +} + + +void Area::clean(bool deleteShapes) { + myShapeDone = false; + mySections.clear(); + myShape.Nullify(); + myArea.reset(); + myAreaOpen.reset(); + myShapePlane.Nullify(); + if(deleteShapes){ + myShapes.clear(); + myHaveFace = false; + myHaveSolid = false; + } +} + +void Area::add(const TopoDS_Shape &shape,short op) { +#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; + break; + } + //TODO: shall we support Shells? + if((!haveSolid && myHaveSolid) || + (haveSolid && !myHaveSolid && !myShapes.empty())) + throw Base::ValueError("mixing solid and planar shapes is not allowed"); + + myHaveSolid = haveSolid; + + clean(); + if(op!=OperationCompound && myShapes.empty()) + op = OperationUnion; + myShapes.push_back(Shape(op,shape)); +} + + +void Area::setParams(const AreaParams ¶ms) { +#define AREA_SRC2(_param) params.PARAM_FNAME(_param) + // Validate all enum type of parameters + PARAM_ENUM_CHECK(AREA_SRC2,PARAM_ENUM_EXCEPT,AREA_PARAMS_CONF); + if(params!=myParams) { + clean(); + myParams = params; + } +} + +void Area::addToBuild(CArea &area, const TopoDS_Shape &shape) { + if(myParams.Fill==FillAuto && !myHaveFace) { + TopExp_Explorer it(shape, TopAbs_FACE); + myHaveFace = it.More(); + } + TopoDS_Shape plane = getPlane(); + CArea areaOpen; + mySkippedShapes += add(area,shape,&myTrsf,myParams.Deflection, + myParams.Coplanar==CoplanarNone?NULL:&plane, + myHaveSolid||myParams.Coplanar==CoplanarForce,&areaOpen, + 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); + else + AREA_WARN("open wires discarded in clipping shapes"); + } +} + +namespace Part { +extern PartExport std::list sort_Edges(double tol3d, std::list& edges); +} + +void Area::explode(const TopoDS_Shape &shape) { + const TopoDS_Shape &plane = getPlane(); + bool haveShape = false; + for(TopExp_Explorer it(shape, TopAbs_FACE); it.More(); it.Next()) { + haveShape = true; + if(myParams.Coplanar!=CoplanarNone && !isCoplanar(it.Current(),plane)){ + ++mySkippedShapes; + if(myParams.Coplanar == CoplanarForce) + continue; + } + for(TopExp_Explorer itw(it.Current(), TopAbs_WIRE); itw.More(); itw.Next()) { + for(BRepTools_WireExplorer xp(TopoDS::Wire(itw.Current()));xp.More();xp.Next()) + add(*myArea,BRepBuilderAPI_MakeWire( + TopoDS::Edge(xp.Current())).Wire(),&myTrsf,myParams.Deflection,true); + } + } + if(haveShape) return; + for(TopExp_Explorer it(shape, TopAbs_EDGE); it.More(); it.Next()) { + if(myParams.Coplanar!=CoplanarNone && !isCoplanar(it.Current(),plane)){ + ++mySkippedShapes; + if(myParams.Coplanar == CoplanarForce) + continue; + } + add(*myArea,BRepBuilderAPI_MakeWire( + TopoDS::Edge(it.Current())).Wire(),&myTrsf,myParams.Deflection,true); + } +} + +#if 0 +static void show(const TopoDS_Shape &shape, const char *name) { + App::Document *pcDoc = App::GetApplication().getActiveDocument(); + if (!pcDoc) + pcDoc = App::GetApplication().newDocument(); + Part::Feature *pcFeature = (Part::Feature *)pcDoc->addObject("Part::Feature", name); + // copy the data + //TopoShape* shape = new MeshObject(*pShape->getTopoShapeObjectPtr()); + pcFeature->Shape.setValue(shape); + //pcDoc->recompute(); +} +#endif + +template +static int foreachSubshape(const TopoDS_Shape &shape, Func func, int type=TopAbs_FACE) { + bool haveShape = false; + switch(type) { + case TopAbs_FACE: + for(TopExp_Explorer it(shape,TopAbs_FACE); it.More(); it.Next()) { + haveShape = true; + func(it.Current(),TopAbs_FACE); + } + if(haveShape) return TopAbs_FACE; + //fall through + case TopAbs_WIRE: + for(TopExp_Explorer it(shape,TopAbs_WIRE); it.More(); it.Next()) { + haveShape = true; + func(it.Current(),TopAbs_WIRE); + } + if(haveShape) return TopAbs_WIRE; + //fall through + default: + for(TopExp_Explorer it(shape,TopAbs_EDGE); it.More(); it.Next()) { + haveShape = true; + func(it.Current(),TopAbs_EDGE); + } + } + return haveShape?TopAbs_EDGE:-1; +} + +struct FindPlane { + TopoDS_Shape &myShape; + gp_Trsf &myTrsf; + double &myZ; + FindPlane(TopoDS_Shape &s, gp_Trsf &t, double &z) + :myShape(s),myTrsf(t),myZ(z) + {} + void operator()(const TopoDS_Shape &shape, int) { + gp_Trsf trsf; + BRepLib_FindSurface finder(shape,-1,Standard_True); + if (!finder.Found()) + return; + + gp_Ax3 pos = GeomAdaptor_Surface(finder.Surface()).Plane().Position(); + + //force plane to be right handed + if(!pos.Direct()) + pos = gp_Ax3(pos.Ax2()); + gp_Dir dir(pos.Direction()); + + trsf.SetTransformation(pos); + if(fabs(dir.X())Precision::Confusion()) { + AREA_WARN("XY plane has wrong Z height "< Precision::Confusion() || + fabs(pt.Y()) > Precision::Confusion() || + fabs(pt.Z()) > Precision::Confusion()) { + AREA_WARN("wrong transformation "< z) + return; + myZ = z; + }else if(!myShape.IsNull()) + return; + myShape = shape; + myTrsf = trsf; + } +}; + +TopoDS_Shape Area::findPlane(const TopoDS_Shape &shape, gp_Trsf &trsf) +{ + TopoDS_Shape plane; + double top_z; + foreachSubshape(shape,FindPlane(plane,trsf,top_z)); + return plane; +} + +std::vector > Area::makeSections( + PARAM_ARGS(PARAM_FARG,AREA_PARAMS_SECTION_EXTRA), + const std::vector &_heights, + const TopoDS_Shape §ion_plane) +{ + TopoDS_Shape plane; + gp_Trsf trsf; + + if(!section_plane.IsNull()) + plane = findPlane(section_plane,trsf); + else + plane = getPlane(&trsf); + + if(plane.IsNull()) + throw Base::ValueError("failed to obtain section plane"); + + TIME_INIT2(t,t1); + + TopLoc_Location loc(trsf); + + Bnd_Box bounds; + for(const Shape &s : myShapes) { + const TopoDS_Shape &shape = s.shape.Moved(loc); + BRepBndLib::Add(shape, bounds, Standard_False); + } + bounds.SetGap(0.0); + Standard_Real xMin, yMin, zMin, xMax, yMax, zMax; + bounds.Get(xMin, yMin, zMin, xMax, yMax, zMax); + + bool hit_bottom = false; + std::vector heights; + if(_heights.empty()) { + if(mode != SectionModeAbsolute && myParams.SectionOffset<0) + throw Base::ValueError("only positive section offset is allowed in non-absolute mode"); + if(myParams.SectionCount>1 && myParams.Stepdown zMax-zMin) { + count = ceil((zMax-zMin)/myParams.Stepdown); + if((count-1)*myParams.Stepdown < zMax-zMin) + ++count; + } + heights.reserve(count); + for(int i=0;iPrecision::Confusion()) { + hit_bottom = true; + continue; + }else if ((z-zMax)>Precision::Confusion()) + continue; + heights.push_back(z); + } + } + + if(hit_bottom) + heights.push_back(zMin); + else if(heights.empty()) + heights.push_back(zMax); + + std::vector > sections; + sections.reserve(heights.size()); + for(double z : heights) { + gp_Pln pln(gp_Pnt(0,0,z),gp_Dir(0,0,1)); + Standard_Real a,b,c,d; + pln.Coefficients(a,b,c,d); + BRepLib_MakeFace mkFace(pln,xMin,xMax,yMin,yMax); + const TopoDS_Shape &face = mkFace.Face(); + + shared_ptr area(new Area(&myParams)); + area->setPlane(face); + for(const Shape &s : myShapes) { + BRep_Builder builder; + TopoDS_Compound comp; + builder.MakeCompound(comp); + for(TopExp_Explorer it(s.shape.Moved(loc), TopAbs_SOLID); it.More(); it.Next()) { + Part::CrossSection section(a,b,c,it.Current()); + std::list wires = section.slice(-d); + if(wires.empty()) { + AREA_LOG("Section returns no wires"); + continue; + } + + Part::FaceMakerBullseye mkFace; + mkFace.setPlane(pln); + for(const TopoDS_Wire &wire : wires) + mkFace.addWire(wire); + try { + mkFace.Build(); + if (mkFace.Shape().IsNull()) + AREA_WARN("FaceMakerBullseye return null shape on section"); + else { + builder.Add(comp,mkFace.Shape()); + continue; + } + }catch (Base::Exception &e){ + AREA_WARN("FaceMakerBullseye failed on section: " << e.what()); + } + for(const TopoDS_Wire &wire : wires) + builder.Add(comp,wire); + } + + // Make sure the compound has at least one edge + for(TopExp_Explorer it(comp,TopAbs_EDGE);it.More();) { + area->add(comp,s.op); + break; + } + } + if(area->myShapes.size()) + sections.push_back(area); + else + AREA_WARN("Discard empty section"); + TIME_PRINT(t1,"makeSection " << z); + } + TIME_PRINT(t,"makeSection count: " << sections.size()<<", total"); + return std::move(sections); +} + +TopoDS_Shape Area::getPlane(gp_Trsf *trsf) { + if(!myWorkPlane.IsNull()) { + if(trsf) *trsf = myTrsf; + return myWorkPlane; + } + if(myShapePlane.IsNull()) { + if(myShapes.empty()) + throw Base::ValueError("no shape added"); + double top_z; + for(auto &s : myShapes) + foreachSubshape(s.shape,FindPlane(myShapePlane,myTrsf,top_z)); + if(myShapePlane.IsNull()) + throw Base::ValueError("shapes are not planar"); + } + if(trsf) *trsf = myTrsf; + return myShapePlane; +} + +bool Area::isBuilt() const { + return (myArea || mySections.size()); +} + + +void Area::build() { + if(isBuilt()) return; + + if(myShapes.empty()) + 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); + + if(myHaveSolid && myParams.SectionCount) { + mySections = makeSections(myParams.SectionMode); + return; + } + + TIME_INIT(t); + getPlane(); + + try { + myArea.reset(new CArea()); + myAreaOpen.reset(new CArea()); + + CAreaConfig conf(myParams); + CArea areaClip; + + mySkippedShapes = 0; + short op = OperationUnion; + bool pending = false; + bool exploding = myParams.Explode; + for(const Shape &s : myShapes) { + if(exploding) { + exploding = false; + explode(s.shape); + continue; + }else if(op!=s.op) { + if(myParams.OpenMode!=OpenModeNone) + myArea->m_curves.splice(myArea->m_curves.end(),myAreaOpen->m_curves); + pending = false; + 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); + pending = true; + } + if(mySkippedShapes && !myHaveSolid) + AREA_WARN((myParams.Coplanar==CoplanarForce?"Skipped ":"Found ")<< + mySkippedShapes<<" non coplanar shapes"); + + if(pending){ + if(myParams.OpenMode!=OpenModeNone) + myArea->m_curves.splice(myArea->m_curves.end(),myAreaOpen->m_curves); + 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 = getPlane(&area.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); + } + + TIME_TRACE(t,"prepare"); + + }catch(...) { + clean(); + throw; + } +} + +list Area::sortWires(int index, int count, const gp_Pnt *pstart, + gp_Pnt *_pend, PARAM_ARGS(PARAM_FARG,AREA_PARAMS_SORT)) +{ + 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(0,0,&pt,&pend, + PARAM_FIELDS(PARAM_FARG,AREA_PARAMS_SORT)); + for(auto &wire : ws) + wires.push_back(wire.Moved(loc)); + pt = pend; + } + if(_pend) + *_pend = pend.Transformed(loc); + return std::move(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 std::move(wires); +} + +TopoDS_Shape Area::toShape(CArea &area, short fill) { + gp_Trsf trsf(myTrsf.Inverted()); + bool bFill; + switch(fill){ + case Area::FillAuto: + bFill = myHaveFace; + break; + case Area::FillFace: + bFill = true; + break; + default: + bFill = false; + } + if(myParams.FitArcs) { + if(&area == myArea.get()) { + CArea copy(area); + copy.FitArcs(); + return toShape(copy,bFill,&trsf); + } + area.FitArcs(); + } + return toShape(area,bFill,&trsf); +} + + +#define AREA_SECTION(_op,_index,...) do {\ + if(mySections.size()) {\ + if(_index>=(int)mySections.size())\ + 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){\ + const TopoDS_Shape &s = area->_op(-1, ## __VA_ARGS__);\ + if(s.IsNull()) continue;\ + builder.Add(compound,s.Moved(loc));\ + }\ + for(TopExp_Explorer it(compound,TopAbs_EDGE);it.More();)\ + return compound;\ + return TopoDS_Shape();\ + }\ + const TopoDS_Shape &shape = mySections[_index]->_op(-1, ## __VA_ARGS__);\ + if(!shape.IsNull())\ + return shape.Moved(loc);\ + return shape;\ + }\ +}while(0) + +TopoDS_Shape Area::getShape(int index) { + build(); + AREA_SECTION(getShape,index); + + if(myShapeDone) return myShape; + + if(!myArea) return TopoDS_Shape(); + + CAreaConfig conf(myParams); + +#define AREA_MY(_param) myParams.PARAM_FNAME(_param) + + // if no offset or thicken, try pocket + if(fabs(myParams.Offset) < Precision::Confusion() && !myParams.Thicken) { + if(myParams.PocketMode == PocketModeNone) { + myShape = toShape(*myArea,myParams.Fill); + myShapeDone = true; + return myShape; + } + myShape = makePocket(-1,PARAM_FIELDS(AREA_MY,AREA_PARAMS_POCKET)); + myShapeDone = true; + return myShape; + } + + // if no pocket, do offset or thicken + if(myParams.PocketMode == PocketModeNone){ + myShape = makeOffset(-1,PARAM_FIELDS(AREA_MY,AREA_PARAMS_OFFSET)); + myShapeDone = true; + return myShape; + } + + TIME_INIT(t); + + // do offset first, then pocket the inner most offseted shape + std::list > areas; + makeOffset(areas,PARAM_FIELDS(AREA_MY,AREA_PARAMS_OFFSET)); + + if(areas.empty()) + areas.push_back(make_shared(*myArea)); + + Area areaPocket(&myParams); + bool front = true; + if(areas.size()>1) { + double step = myParams.Stepover; + if(fabs(step)0; + } + + // for pocketing, we discard the outer most offset wire in order to achieve + // the effect of offseting shape first than pocket, where the actual offset + // path is not wanted. For extra outline profiling, add extra_offset + if(front) { + areaPocket.add(toShape(*areas.front(),myParams.Fill)); + areas.pop_back(); + }else{ + areaPocket.add(toShape(*areas.back(),myParams.Fill)); + areas.pop_front(); + } + + BRep_Builder builder; + TopoDS_Compound compound; + builder.MakeCompound(compound); + + short fill = myParams.Thicken?FillFace:FillNone; + TIME_INIT(t2); + DURATION_INIT(d); + for(shared_ptr area : areas) { + if(myParams.Thicken){ + area->Thicken(myParams.ToolRadius); + DURATION_PLUS(d,t2); + } + const TopoDS_Shape &shape = toShape(*area,fill); + if(shape.IsNull()) continue; + builder.Add(compound,shape); + } + if(myParams.Thicken) + DURATION_PRINT(d,"Thicken"); + + // make sure the compound has at least one edge + for(TopExp_Explorer it(compound,TopAbs_EDGE);it.More();) { + builder.Add(compound,areaPocket.makePocket( + -1,PARAM_FIELDS(AREA_MY,AREA_PARAMS_POCKET))); + myShape = compound; + break; + } + myShapeDone = true; + TIME_PRINT(t,"total"); + return myShape; +} + +TopoDS_Shape Area::makeOffset(int index,PARAM_ARGS(PARAM_FARG,AREA_PARAMS_OFFSET)) { + build(); + AREA_SECTION(makeOffset,index,PARAM_FIELDS(PARAM_FARG,AREA_PARAMS_OFFSET)); + + std::list > areas; + makeOffset(areas,PARAM_FIELDS(PARAM_FARG,AREA_PARAMS_OFFSET)); + if(areas.empty()) { + if(myParams.Thicken && myParams.ToolRadius>Precision::Confusion()) { + CArea area(*myArea); + TIME_INIT(t); + area.Thicken(myParams.ToolRadius); + TIME_PRINT(t,"Thicken"); + return toShape(area,FillFace); + } + return TopoDS_Shape(); + } + BRep_Builder builder; + TopoDS_Compound compound; + builder.MakeCompound(compound); + TIME_INIT(t); + DURATION_INIT(d); + for(shared_ptr area : areas) { + short fill; + if(myParams.Thicken && myParams.ToolRadius>Precision::Confusion()) { + area->Thicken(myParams.ToolRadius); + DURATION_PLUS(d,t); + fill = FillFace; + }else if(areas.size()==1) + fill = myParams.Fill; + else + fill = FillNone; + const TopoDS_Shape &shape = toShape(*area,fill); + if(shape.IsNull()) continue; + builder.Add(compound,shape); + } + if(myParams.Thicken && myParams.ToolRadius>Precision::Confusion()) + DURATION_PRINT(d,"Thicken"); + for(TopExp_Explorer it(compound,TopAbs_EDGE);it.More();) + return compound; + return TopoDS_Shape(); +} + +void Area::makeOffset(list > &areas, + PARAM_ARGS(PARAM_FARG,AREA_PARAMS_OFFSET)) +{ + if(fabs(offset) 0) { + count += extra_pass; + }else{ + if(stepover>0 || offset>0) + throw Base::ValueError("invalid extra count"); + // In this case, we loop until no outputs from clipper + count=-1; + } + } + + PARAM_ENUM_CONVERT(AREA_SRC,PARAM_FNAME,PARAM_ENUM_EXCEPT,AREA_PARAMS_OFFSET_CONF); +#ifdef AREA_OFFSET_ALGO + PARAM_ENUM_CONVERT(AREA_SRC,PARAM_FNAME,PARAM_ENUM_EXCEPT,AREA_PARAMS_CLIPPER_FILL); +#endif + + for(int i=0;count<0||i()); + CArea &area = *areas.back(); + CArea areaOpen; +#ifdef AREA_OFFSET_ALGO + if(myParams.Algo == Area::Algolibarea) { + for(const CCurve &c : myArea->m_curves) { + if(c.IsClosed()) + area.append(c); + else + areaOpen.append(c); + } + }else +#endif + area = *myArea; + +#ifdef AREA_OFFSET_ALGO + switch(myParams.Algo){ + case Area::Algolibarea: + // libarea somehow fails offset without Reorder, but ClipperOffset + // works okay. Don't know why + area.Reorder(); + area.Offset(-offset); + if(areaOpen.m_curves.size()) { + areaOpen.Thicken(offset); + area.Clip(ClipperLib::ctUnion,&areaOpen,SubjectFill,ClipFill); + } + break; + case Area::AlgoClipperOffset: +#endif + area.OffsetWithClipper(offset,JoinType,EndType, + myParams.MiterLimit,myParams.RoundPreceision); +#ifdef AREA_OFFSET_ALGO + break; + } +#endif + if(count>1) + TIME_PRINT(t1,"makeOffset " << i << '/' << count); + if(area.m_curves.empty()) + return; + } + TIME_PRINT(t,"makeOffset count: " << count); +} + +TopoDS_Shape Area::makePocket(int index, PARAM_ARGS(PARAM_FARG,AREA_PARAMS_POCKET)) { + if(tool_radius < Precision::Confusion()) + throw Base::ValueError("tool radius too small"); + + if(stepover == 0.0) + stepover = tool_radius; + + if(stepover < Precision::Confusion()) + throw Base::ValueError("stepover too small"); + + if(mode == Area::PocketModeNone) + return TopoDS_Shape(); + + build(); + AREA_SECTION(makePocket,index,PARAM_FIELDS(PARAM_FARG,AREA_PARAMS_POCKET)); + + TIME_INIT(t); + + PocketMode pm; + switch(mode) { + case Area::PocketModeZigZag: + pm = ZigZagPocketMode; + break; + case Area::PocketModeSpiral: + pm = SpiralPocketMode; + break; + case Area::PocketModeOffset: { + PARAM_DECLARE_INIT(PARAM_FNAME,AREA_PARAMS_OFFSET); + Offset = -tool_radius-extra_offset; + ExtraPass = -1; + Stepover = -stepover; + return makeOffset(index,PARAM_FIELDS(PARAM_FNAME,AREA_PARAMS_OFFSET)); + }case Area::PocketModeZigZagOffset: + pm = ZigZagThenSingleOffsetPocketMode; + break; + default: + throw Base::ValueError("unknown poket mode"); + } + + CAreaConfig conf(myParams); + CAreaPocketParams params( + tool_radius,extra_offset,stepover,from_center,pm,zig_angle); + CArea in(*myArea),out; + // MakePcoketToolPath internally uses libarea Offset which somehow demands + // reorder before input, otherwise nothing is shown. + in.Reorder(); + in.MakePocketToolpath(out.m_curves,params); + + TIME_PRINT(t,"makePocket"); + + if(myParams.Thicken){ + out.Thicken(tool_radius); + return toShape(out,FillFace); + }else + return toShape(out,FillNone); +} + +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); + } + AREA_WARN("Arc correction: "<"< &wires; + GetWires(std::list &ws) + :wires(ws) + {} + void operator()(const TopoDS_Shape &shape, int type) { + WireInfo info; + if(type == TopAbs_WIRE) + info.wire = TopoDS::Wire(shape); + else + info.wire = BRepBuilderAPI_MakeWire(TopoDS::Edge(shape)).Wire(); + + BRepTools_WireExplorer xp(info.wire); + info.pstart = BRep_Tool::Pnt(xp.CurrentVertex()); + for(;xp.More();xp.Next()); + info.pend = BRep_Tool::Pnt(xp.CurrentVertex()); + wires.push_back(info); + } +}; + +struct ShapeInfo{ + gp_Pln myPln; + std::list myWires; + TopoDS_Shape myShape; + gp_Pnt myBestPt; + std::list::iterator myBestWire; + TopoDS_Shape mySupport; + bool mySupportEdge; + bool myPlanar; + bool myRebase; + bool myStart; + + ShapeInfo(BRepLib_FindSurface &finder, const TopoDS_Shape &_shape) + :myPln(GeomAdaptor_Surface(finder.Surface()).Plane()) + ,myShape(_shape) + ,myPlanar(true) + {} + ShapeInfo(const TopoDS_Shape &_shape) + :myShape(_shape) + ,myPlanar(false) + {} + double nearest(const gp_Pnt &pt) { + if(myWires.empty()) + foreachSubshape(myShape,GetWires(myWires),TopAbs_WIRE); + TopoDS_Shape v = BRepBuilderAPI_MakeVertex(pt); + bool first = true; + double best_d=1e20; + myBestWire = myWires.begin(); + for(auto it=myWires.begin();it!=myWires.end();++it) { + const TopoDS_Shape &wire = it->wire; + TopoDS_Shape support; + bool support_edge; + double d; + gp_Pnt p; + bool done = false; + bool is_start = false; + if(BRep_Tool::IsClosed(wire)) { + BRepExtrema_DistShapeShape extss(v,wire); + if(extss.IsDone() && extss.NbSolution()) { + d = extss.Value(); + p = extss.PointOnShape2(1); + support = extss.SupportOnShape2(1); + support_edge = extss.SupportTypeShape2(1)==BRepExtrema_IsOnEdge; + done = true; + }else + AREA_WARN("BRepExtrema_DistShapeShape failed"); + } + if(!done){ + double d1 = pt.Distance(it->pstart); + double d2 = pt.Distance(it->pend); + if(d1pstart; + is_start = true; + }else{ + d = d2; + p = it->pend; + is_start = false; + } + } + if(!first && d>=best_d) continue; + first = false; + myBestPt = p; + myBestWire = it; + best_d = d; + myRebase = done; + myStart = is_start; + if(done) { + mySupport = support; + mySupportEdge = support_edge; + } + } + return best_d; + } + + //Assumes nearest() has been called. Rebased the best wire + //to begin with the best point. Currently only works with closed wire + TopoDS_Shape rebaseWire(gp_Pnt &pend, double min_dist) { + AREA_TRACE("rebase wire"); + BRepBuilderAPI_MakeWire mkWire; + TopoDS_Shape estart; + TopoDS_Edge eend; + + if(min_dist < Precision::Confusion()) + min_dist = Precision::Confusion(); + + for(int state=0;state<3;++state) { + BRepTools_WireExplorer xp(TopoDS::Wire(myBestWire->wire)); + pend = BRep_Tool::Pnt(xp.CurrentVertex()); + + //checking the case of bestpoint == wire start + if(state==0 && !mySupportEdge && pend.Distance(myBestPt)pend; + return myBestWire->wire; + } + + gp_Pnt pt; + for(;xp.More();xp.Next(),pend=pt) { + //state==2 means we are in second pass. estart marks the new start of the wire. + //so seeing estart means we're done + if(state==2 && estart.IsEqual(xp.Current())) + break; + + BRepAdaptor_Curve curve(xp.Current()); + bool reversed = (xp.Current().Orientation()==TopAbs_REVERSED); + pt = curve.Value(reversed?curve.FirstParameter():curve.LastParameter()); + //state!=0 means we've found the new start of wire, now just keep adding new edges + if(state) { + mkWire.Add(xp.Current()); + pend = pt; + continue; + } + //state==0 means we are looking for the new start + if(mySupportEdge) { + //if best point is on some edge, break the edge in half + if(xp.Current().IsEqual(mySupport)) { + double d1 = pend.Distance(myBestPt); + double d2 = pt.Distance(myBestPt); + + if(d1>min_dist && d2>min_dist) { + BRepBuilderAPI_MakeEdge mkEdge1,mkEdge2; + if(reversed) { + mkEdge1.Init(curve.Curve().Curve(), myBestPt, myBestPt); + mkEdge2.Init(curve.Curve().Curve(), pt, myBestPt); + }else{ + mkEdge1.Init(curve.Curve().Curve(), pend, myBestPt); + mkEdge2.Init(curve.Curve().Curve(), myBestPt, pt); + } + if(mkEdge1.IsDone() && mkEdge2.IsDone()) { + if(reversed) { + eend = TopoDS::Edge(mkEdge1.Edge().Reversed()); + mkWire.Add(TopoDS::Edge(mkEdge2.Edge().Reversed())); + }else{ + eend = mkEdge1.Edge(); + mkWire.Add(mkEdge2.Edge()); + } + estart = mySupport; + state = 1; + AREA_TRACE("edge broken "<start"); + estart = xp.Current(); + state = 1; + mkWire.Add(xp.Current()); + }else{ + AREA_TRACE("break edge->end"); + mySupportEdge = false; + myBestPt = pt; + continue; + } + } + }else if(myBestPt.Distance(pend)pend; + return myBestWire->wire; + } + + std::list sortWires3D(gp_Pnt &pend,double min_dist) { + std::list wires; + while(true) { + AREA_TRACE("3D sort pt " << AREA_PT(myBestPt)); + if(myRebase) { + AREA_TRACE("3D sort rebase"); + pend = myBestPt; + wires.push_back(rebaseWire(pend,min_dist)); + }else if(!myStart){ + AREA_TRACE("3D sort reverse"); + wires.push_back(myBestWire->wire.Reversed()); + pend = myBestWire->pstart; + }else{ + wires.push_back(myBestWire->wire); + pend = myBestWire->pend; + } + AREA_TRACE("3D sort end " << AREA_PT(pend)); + myWires.erase(myBestWire); + if(myWires.empty()) break; + nearest(pend); + } + return std::move(wires); + } +}; + +struct ShapeInfoBuilder { + std::list &myList; + + ShapeInfoBuilder(std::list &list) + :myList(list) + {} + + void operator()(const TopoDS_Shape &shape, int) { + BRepLib_FindSurface finder(shape,-1,Standard_True); + if(finder.Found()) + myList.push_back(ShapeInfo(finder,shape)); + else + myList.push_back(ShapeInfo(shape)); + } +}; + + +std::list Area::sortWires(const std::list &shapes, + const AreaParams *params, const gp_Pnt *_pstart, gp_Pnt *_pend, + PARAM_ARGS(PARAM_FARG,AREA_PARAMS_SORT)) +{ + std::list wires; + + if(shapes.empty()) return wires; + + if(sort_mode == SortModeNone) { + 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()); + } + return std::move(wires); + } + + std::list shape_list; + + TIME_INIT2(t,t1); +#define SORT_WIRE_TIME(_msg) \ + TIME_PRINT(t1,"sortWires "<< _msg) + + if(sort_mode == SortMode3D) { + for(auto &shape : shapes) + shape_list.push_back(ShapeInfo(shape)); + }else{ + //first pass, find plane of each shape + for(auto &shape : shapes) { + //explode the shape + foreachSubshape(shape,ShapeInfoBuilder(shape_list)); + } + + if(shape_list.empty()) + return wires; + + SORT_WIRE_TIME("plan finding"); + } + + Bnd_Box bounds; + gp_Pnt pstart,pend; + if(_pstart) + pstart = *_pstart; + bool use_bound = fabs(pstart.X())myShape, bounds, Standard_False); + if(!it->myPlanar) continue; + TopoDS_Builder builder; + TopoDS_Compound comp; + builder.MakeCompound(comp); + bool empty = true; + for(auto itNext3=itNext,itNext2=itNext;itNext2!=shape_list.end();itNext2=itNext3) { + ++itNext3; + if(!itNext2->myPlanar || + !it->myPln.Position().IsCoplanar(itNext2->myPln.Position(), + Precision::Confusion(),Precision::Confusion())) + continue; + if(itNext == itNext2) ++itNext; + builder.Add(comp,itNext2->myShape); + shape_list.erase(itNext2); + empty = false; + } + if(!empty) { + builder.Add(comp,it->myShape); + it->myShape = comp; + } + } + SORT_WIRE_TIME("plane merging"); + } + + Area area(params); + //We have done planar checking here, so disable Area planar check + area.myParams.Coplanar = Area::CoplanarNone; + + DURATION_INIT2(td1,td2); + + if(use_bound) { + bounds.SetGap(0.0); + Standard_Real xMin, yMin, zMin, xMax, yMax, zMax; + bounds.Get(xMin, yMin, zMin, xMax, yMax, zMax); + pstart.SetCoord(xMax,yMax,zMax); + } + bool has_2d5=false,has_3d=false; + while(shape_list.size()) { + AREA_TRACE("start " << shape_list.size() << ' ' << AREA_PT(pstart)); + double best_d; + auto best_it = shape_list.begin(); + bool first = true; + for(auto it=best_it;it!=shape_list.end();++it) { + double d; + gp_Pnt pt; + if(it->myPlanar){ + d = it->myPln.Distance(pstart); +#define AREA_TIME_2D5 \ + DURATION_PLUS(td1,t1);\ + has_2d5=true + + AREA_TIME_2D5; + }else{ + d = it->nearest(pstart); +#define AREA_TIME_3D \ + DURATION_PLUS(td2,t1);\ + has_3d=true + + AREA_TIME_3D; + } + if(first || dmyPlanar) { + area.clean(true); + area.myWorkPlane = best_it->myShape; + area.myTrsf.SetTransformation(best_it->myPln.Position()); + area.add(best_it->myShape,Area::OperationCompound); + wires.splice(wires.end(),area.sortWires( + 0,-1,&pstart,&pend, PARAM_FIELDS(PARAM_FARG,AREA_PARAMS_SORT))); + AREA_TIME_2D5; + }else{ + wires.splice(wires.end(),best_it->sortWires3D(pend,min_dist)); + AREA_TIME_3D; + } + pstart = pend; + shape_list.erase(best_it); + } + if(_pend) *_pend = pend; + + if(has_2d5) DURATION_PRINT(td1,"sortWires 2D5"); + if(has_3d) DURATION_PRINT(td2,"sortWires 3D"); + TIME_PRINT(t,"sortWires total"); + return std::move(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 AreaParams *_params, const gp_Pnt *pstart, gp_Pnt *pend, + PARAM_ARGS(PARAM_FARG,AREA_PARAMS_PATH)) +{ + std::list wires; + + AreaParams params; + if(_params) params =*_params; + wires = sortWires(shapes,¶ms,pstart,pend, + PARAM_FIELDS(PARAM_FARG,AREA_PARAMS_SORT)); + + 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.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: { + if(segmentation > Precision::Confusion()) { + GCPnts_UniformAbscissa discretizer(curve, segmentation, + curve.FirstParameter(), curve.LastParameter()); + if (discretizer.IsDone () && discretizer.NbPoints () > 2) { + int nbPoints = discretizer.NbPoints (); + if(reversed) { + for (int i=nbPoints-1; i>=1; --i) { + gp_Pnt pt = curve.Value(discretizer.Parameter(i)); + addCommand(path,pt); + } + }else{ + for (int i=2; i<=nbPoints; i++) { + gp_Pnt pt = curve.Value(discretizer.Parameter(i)); + addCommand(path,pt); + } + } + break; + } + } + 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(segmentation > Precision::Confusion()) { + GCPnts_UniformAbscissa discretizer(curve, segmentation, + curve.FirstParameter(), curve.LastParameter()); + if (discretizer.IsDone () && discretizer.NbPoints () > 2) { + int nbPoints = discretizer.NbPoints (); + if(reversed) { + for (int i=nbPoints-1; i>=1; --i) { + gp_Pnt pt = curve.Value(discretizer.Parameter(i)); + addCommand(path,plast,pt,center,clockwise); + plast = pt; + } + }else{ + for (int i=2; i<=nbPoints; i++) { + gp_Pnt pt = curve.Value(discretizer.Parameter(i)); + addCommand(path,plast,pt,center,clockwise); + plast = pt; + } + } + break; + } + } + 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_QuasiUniformDeflection discretizer(curve, params.Deflection, + curve.FirstParameter(), curve.LastParameter()); + if (discretizer.IsDone () && discretizer.NbPoints () > 1) { + int nbPoints = discretizer.NbPoints (); + if(reversed) { + for (int i=nbPoints-1; i>=1; --i) { + gp_Pnt pt = discretizer.Value (i); + addCommand(path,pt); + } + }else{ + for (int i=2; i<=nbPoints; i++) { + gp_Pnt pt = discretizer.Value (i); + addCommand(path,pt); + } + } + }else + Standard_Failure::Raise("Curve discretization failed"); + }} + } + } +} + +void Area::abort(bool aborting) { + s_aborting = aborting; +} + +bool Area::aborting() { + return s_aborting; +} + +AreaStaticParams::AreaStaticParams() + :PARAM_INIT(PARAM_FNAME,AREA_PARAMS_EXTRA_CONF) +{} + +AreaStaticParams Area::s_params; + +void Area::setDefaultParams(const AreaStaticParams ¶ms){ + s_params = params; +} + +const AreaStaticParams &Area::getDefaultParams() { + return s_params; +} + +#define AREA_LOG_CHECK_DEFINE(_1,_2,_elem) \ +bool Area::BOOST_PP_CAT(_elem,Enabled)() {\ + return s_params.LogLevel >= BOOST_PP_CAT(LogLevel,_elem);\ +} +BOOST_PP_SEQ_FOR_EACH(AREA_LOG_CHECK_DEFINE,_,AREA_PARAM_LOG_LEVEL) diff --git a/src/Mod/Path/App/Area.h b/src/Mod/Path/App/Area.h new file mode 100644 index 0000000000..336562c591 --- /dev/null +++ b/src/Mod/Path/App/Area.h @@ -0,0 +1,465 @@ +/**************************************************************************** + * Copyright (c) 2017 Zheng, Lei (realthunder) * + * * + * 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_AREA_H +#define PATH_AREA_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "Path.h" +#include "AreaParams.h" + +#define _AREA_LOG(_l,_msg) do {\ + if(Area::_l##Enabled()){\ + std::stringstream str;\ + str << "Path.Area: " << _msg;\ + Base::Console()._l("%s\n",str.str().c_str());\ + }\ + qApp->sendPostedEvents();\ + if(Area::aborting()) {\ + Area::abort(false);\ + throw Base::AbortException("operation aborted");\ + }\ +}while(0) + +#define AREA_LOG(_msg) _AREA_LOG(Log,_msg) +#define AREA_WARN(_msg) _AREA_LOG(Warning,_msg) +#define AREA_ERR(_msg) _AREA_LOG(Error,_msg) +#define AREA_PT(_pt) '('<<(_pt).X()<<", " << (_pt).Y()<<", " << (_pt).Z()<<')' +#define AREA_PT2(_pt) '('<<(_pt).x<<", " << (_pt).y<<')' + +#define AREA_TRACE(_msg) do{\ + if(Area::TraceEnabled()) AREA_LOG('('<<__LINE__<<"): " <<_msg);\ +}while(0) + +#define AREA_TIME_ENABLE + +#ifdef AREA_TIME_ENABLE +#define TIME_UNIT duration +#define TIME_CLOCK high_resolution_clock +#define TIME_POINT std::chrono::TIME_CLOCK::time_point + +#define TIME_INIT(_t) \ + auto _t=std::chrono::TIME_CLOCK::now() + +#define TIME_INIT2(_t1,_t2) TIME_INIT(_t1),_t2=_t1 +#define TIME_INIT3(_t1,_t2,_t3) TIME_INIT(_t1),_t2=_t1,_t3=_t1 + +#define _DURATION_PRINT(_l,_d,_msg) \ + AREA_##_l(_msg<< " time: " << _d.count()<<'s'); + +#define DURATION_PRINT(_d,_msg) _DURATION_PRINT(LOG,_d,_msg) + +#define TIME_PRINT(_t,_msg) \ + DURATION_PRINT(Path::getDuration(_t),_msg); + +#define TIME_TRACE(_t,_msg) \ + _DURATION_PRINT(TRACE,Path::getDuration(_t),_msg); + +#define DURATION_INIT(_d) \ + std::chrono::TIME_UNIT _d(0) + +#define DURATION_INIT2(_d1,_d2) DURATION_INIT(_d1),_d2(0) + +namespace Path { +inline std::chrono::TIME_UNIT getDuration(TIME_POINT &t) +{ + auto tnow = std::chrono::TIME_CLOCK::now(); + auto d = std::chrono::duration_cast(tnow-t); + t = tnow; + return d; +} +} + +#define DURATION_PLUS(_d,_t) _d += Path::getDuration(_t) + +#else + +#define TIME_INIT(...) do{}while(0) +#define TIME_INIT2(...) do{}while(0) +#define TIME_INIT3(...) do{}while(0) +#define TIME_PRINT(...) do{}while(0) +#define DURATION_PRINT(...) do{}while(0) +#define DURATION_INIT(...) do{}while(0) +#define DURATION_INIT2(...) do{}while(0) +#define DURATION_PLUS(...) do{}while(0) + +#endif + +class CArea; +class CCurve; + +namespace Path +{ + +/** Store libarea algorithm configuration */ +struct PathExport CAreaParams { + PARAM_DECLARE(PARAM_FNAME,AREA_PARAMS_CAREA) + CAreaParams(); +}; + +/** Store all Area configurations */ +struct PathExport AreaParams: CAreaParams { + + PARAM_DECLARE(PARAM_FNAME,AREA_PARAMS_AREA) + + bool operator==(const AreaParams &other) const { +#define AREA_COMPARE(_param) \ + if(PARAM_FIELD(NAME,_param)!=other.PARAM_FIELD(NAME,_param)) return false; + PARAM_FOREACH(AREA_COMPARE,AREA_PARAMS_CAREA) + PARAM_FOREACH(AREA_COMPARE,AREA_PARAMS_AREA) + return true; + } + bool operator!=(const AreaParams &other) const { + return !(*this == other); + } + + AreaParams(); +}; + +struct PathExport AreaStaticParams: AreaParams { + PARAM_DECLARE(PARAM_FNAME,AREA_PARAMS_EXTRA_CONF); + + AreaStaticParams(); +}; + +/** libarea configurator + * + * It is kind of troublesome with the fact that libarea uses static variables to + * config its algorithm. CAreaConfig makes it easy to safely customize libarea. + */ +struct PathExport CAreaConfig { + + /** For saving current libarea settings */ + PARAM_DECLARE(PARAM_FNAME,AREA_PARAMS_CAREA) + + /** The constructor automatically saves current setting and apply user defined ones + * + * \arg \c p user defined configurations + * \arg \c noFitArgs if true, will override and disable arc fitting. Because + * arc unfiting and fitting is lossy. And repeatedly perform these operation + * may cause shape deformation. So it is best to delay arc fitting until the + * final step*/ + CAreaConfig(const CAreaParams &p, bool noFitArcs=true); + + /** The destructor restores the setting, and thus exception safe. */ + ~CAreaConfig(); +}; + + +/** Base class for FreeCAD wrapping of libarea */ +class PathExport Area: public Base::BaseClass { + + TYPESYSTEM_HEADER(); + +public: + struct Shape { + short op; + TopoDS_Shape shape; + + Shape(short opCode, const TopoDS_Shape &s) + :op(opCode) + ,shape(s) + {} + }; + +protected: + std::list myShapes; + std::unique_ptr myArea; + std::unique_ptr myAreaOpen; + gp_Trsf myTrsf; + AreaParams myParams; + TopoDS_Shape myShapePlane; + TopoDS_Shape myWorkPlane; + TopoDS_Shape myShape; + std::vector > mySections; + bool myHaveFace; + bool myHaveSolid; + bool myShapeDone; + int mySkippedShapes; + + static bool s_aborting; + static AreaStaticParams s_params; + + /** Called internally to combine children shapes for further processing */ + void build(); + + /** Called by build() to add children shape + * + * Mainly for checking if there is any faces for auto fill*/ + void addToBuild(CArea &area, const TopoDS_Shape &shape); + + /** Called internally to obtain the combained children shapes */ + TopoDS_Shape toShape(CArea &area, short fill); + + /** Obtain a list of offseted areas + * + * See #AREA_PARAMS_OFFSET for description of the arguments. + */ + void makeOffset(std::list > &areas, + PARAM_ARGS_DEF(PARAM_FARG,AREA_PARAMS_OFFSET)); + + /** Make a pocket of the combined shape + * + * User #AREA_PARAMS_POCKET setting in myParams. + */ + TopoDS_Shape makePocket(); + + void explode(const TopoDS_Shape &shape); + + bool isBuilt() const; + + TopoDS_Shape findPlane(const TopoDS_Shape &shape, gp_Trsf &trsf); + +public: + /** Declare all parameters defined in #AREA_PARAMS_ALL as member variable */ + PARAM_ENUM_DECLARE(AREA_PARAMS_ALL) + + Area(const AreaParams *params = NULL); + Area(const Area &other, bool deep_copy=true); + virtual ~Area(); + + /** Set a working plane + * + * \arg \c shape: a shape defining a working plane. + * + * The supplied shape does not need to be planar. Area will try to find planar + * sub-shape (face, wire or edge). If more than one planar sub-shape is found, + * it will prefer the top plane parallel to XY0 plane. + * + * If no working plane are set, Area will try to find a working plane from + * the added children shape using the same algorithm + */ + void setPlane(const TopoDS_Shape &shape); + + /** Return the current active workplane + * + * \arg \c trsf: optional return of a transformation matrix that will bring the + * found plane to XY0 plane. + * + * If no workplane is set using setPlane(), the active workplane is derived from + * the added children shapes using the same algorithm empolyed by setPlane(). + */ + TopoDS_Shape getPlane(gp_Trsf *trsf = NULL); + + /** Add a child shape with given operation code + * + * No validation is done at this point. Exception will be thrown when asking + * for output shape, if any of the children shapes is not valid or not + * coplanar + * + * \arg \c shape: the child shape + * \arg \c op: operation code, see #AREA_PARAMS_OPCODE + */ + void add(const TopoDS_Shape &shape,PARAM_ARGS_DEF(PARAM_FARG,AREA_PARAMS_OPCODE)); + + + /** Generate an offset of the combined shape + * + * See #AREA_PARAMS_OFFSET for description of the arguments. + * 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=-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=-1, PARAM_ARGS_DEF(PARAM_FARG,AREA_PARAMS_POCKET)); + + + std::vector > makeSections( + PARAM_ARGS_DEF(PARAM_FARG,AREA_PARAMS_SECTION_EXTRA), + const std::vector &_heights = std::vector(), + const TopoDS_Shape &plane = TopoDS_Shape()); + + /** Config this Area object */ + void setParams(const AreaParams ¶ms); + + + const std::list getChildren() const { + return myShapes; + } + + /** Get the current configuration */ + const AreaParams &getParams() const { + return myParams; + } + + /** Clean internal caches + * + * The combained shapes is cached internally to make other operation more + * efficient, such as makeOffset() and makePocket() + * + * \arg \c deleteShapes: if true, delete all children shapes. + */ + void clean(bool deleteShapes=false); + + /** 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() { + build(); + return mySections.size(); + } + + /** Add a OCC wire shape to CArea + * + * \arg \c area: output converted curved object to here + * \arg \c wire: input wire object + * \arg \c trsf: optional transform matrix to transform the wire shape into + * XY0 plane. + * \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, 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 + * \arg \c allow_Break: whether allow to break open wires + * + * See #AREA_PARAMS_SORT 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_SORT)); + + /** Add a OCC generic shape to CArea + * + * \arg \c area: output converted curved object to here + * \arg \c shape: input shape 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 plane: a shape for testing coplanar + * \arg \c force_coplaner: if true, discard non-coplanar shapes. + * \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 reorient: reorient closed wires for wire only shape + * + * \return Returns the number of non coplaner. Planar testing only happens + * if \c plane is supplied + * */ + 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 reorient=true); + + /** Convert curves in CArea into an OCC shape + * + * \arg \c area: input area object + * \arg \c fill: if true, create a face object from the wires + * \arg \c trsf: optional transform matrix to transform the shape back into + * its original position. + * */ + 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 + * + * See #AREA_PARAMS_SORT 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)); + + /** Convert a list of wires to gcode + * + * \arg \c path: output toolpath + * \arg \c shapes: input list of shapes + * \arg \c params: optional Area parameters for the Area object internally + * used for sorting + * \arg \c pstart: output start point, + * \arg \c pend: optional output containing the ending point of the returned + * + * See #AREA_PARAMS_PATH for other arguments + */ + static void toPath(Toolpath &path, const std::list &shapes, + const AreaParams *params=NULL, const gp_Pnt *pstart=NULL, gp_Pnt *pend=NULL, + PARAM_ARGS_DEF(PARAM_FARG,AREA_PARAMS_PATH)); + + PARAM_ENUM_DECLARE(AREA_PARAMS_PATH) + + static void abort(bool aborting); + static bool aborting(); + + static void setDefaultParams(const AreaStaticParams ¶ms); + static const AreaStaticParams &getDefaultParams(); + +#define AREA_LOG_CHECK_DECLARE(_1,_2,_elem) \ + static bool BOOST_PP_CAT(_elem,Enabled)(); + BOOST_PP_SEQ_FOR_EACH(AREA_LOG_CHECK_DECLARE,_,AREA_PARAM_LOG_LEVEL) + + PARAM_ENUM_DECLARE(AREA_PARAMS_LOG_LEVEL) +}; + +} //namespace Path + +#endif //PATH_AREA_H diff --git a/src/Mod/Path/App/AreaParams.h b/src/Mod/Path/App/AreaParams.h new file mode 100644 index 0000000000..8dc4be2d2b --- /dev/null +++ b/src/Mod/Path/App/AreaParams.h @@ -0,0 +1,223 @@ +/**************************************************************************** + * Copyright (c) 2017 Zheng, Lei (realthunder) * + * * + * 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_AreaParams_H +#define PATH_AreaParams_H + +// deifne this to enable offset algo selection +// #define AREA_OFFSET_ALGO + +/** \file + * Parameters definition for Path::Area and its companion + * See \ref ParamPage "here" for details of parameter definition. + */ + +#include "ParamsHelper.h" + +/** clipper fill type */ +#define AREA_CLIPPER_FILL_TYPE \ + (NonZero)(EvenOdd)(Positive)(Negative),(ClipperLib::PolyFillType,ClipperLib::pft) + +/** Paramerters of clipper fill types */ +#define AREA_PARAMS_CLIPPER_FILL \ + ((enum2,subject_fill,SubjectFill,0,\ + "ClipperLib subject fill type. \nSee https://goo.gl/5pYQQP",AREA_CLIPPER_FILL_TYPE))\ + ((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. '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 + +#define AREA_PARAMS_FIT_ARCS \ + ((bool,fit_arcs,FitArcs,true,"Enable arc fitting")) + +/** libarea algorithm option parameters */ +#define AREA_PARAMS_CAREA \ + ((double,tolerance,Tolerance,Precision::Confusion(),"Point coincidence tolerance"))\ + AREA_PARAMS_FIT_ARCS \ + ((bool,clipper_simple,Simplify,false,\ + "Simplify polygons after operation. See https://goo.gl/Mh9XK1"))\ + ((double,clipper_clean_distance,CleanDistance,0.0,\ + "Clean polygon smaller than this distance. See https://goo.gl/jox3JY"))\ + ((double,accuracy,Accuracy,0.01,"Arc fitting accuracy"))\ + ((double,units,Unit,1.0,"Scaling factor for convertion to inch"))\ + ((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 convert\n"\ + "floating points.")) + +/** Pocket parameters + * + * These parameters cooresponds to CAreaPocketParams in libarea + * */ +#define AREA_PARAMS_POCKET \ + ((enum,mode,PocketMode,0,"Selects the pocket toolpath pattern",(None)(ZigZag)(Offset)(Spiral)(ZigZagOffset)))\ + ((double,tool_radius,ToolRadius,1.0,"Tool radius for pocketing"))\ + ((double,extra_offset,PocketExtraOffset,0.0,"Extra offset for pocketing"))\ + ((double,stepover,PocketStepover,0.0,"Cutter diameter to step over on each pass. If =0, use ToolRadius."))\ + ((bool,from_center,FromCenter,true,"Start pocketing from center"))\ + ((double,zig_angle,ZigAngle,45,"Zig angle in degree")) + +#define AREA_PARAMS_POCKET_CONF \ + ((bool,thicken,Thicken,false,"Thicken the resulting wires with ToolRadius")) + +/** Operation code */ +#define AREA_PARAMS_OPCODE \ + ((enum,op,Operation,0,"Boolean operation.\n"\ + "For the first four operations, see https://goo.gl/Gj8RUu.\n"\ + "'Compound' means no operation, normally used to do Area.sortWires().",\ + (Union)(Difference)(Intersection)(Xor)(Compound))) + +/** Offset parameters */ +#define AREA_PARAMS_OFFSET \ + ((double,offset,Offset,0.0,"Offset value, positive for expansion, negative for shrinking"))\ + ((long,extra_pass,ExtraPass,0,"Number of extra offset pass to generate."))\ + ((double,stepover,Stepover,0.0,"Cutter diameter to step over on each pass. If =0, use Offset")) + +#define AREA_PARAMS_SECTION_EXTRA \ + ((enum,mode,SectionMode,2,"Section offset coordinate mode.\n"\ + "'Absolute' means the absolute Z height to start section.\n"\ + "'BoundBox' means relative Z height to the bounding box of all the children shape. Only\n"\ + "positive value is allowed, which specifies the offset below the top Z of the bounding box.\n"\ + "Note that OCC has trouble getting the minimumi bounding box of some solids, particually\n"\ + "those with non-planar surface.\n"\ + "'Workplane' means relative to workplane.",\ + (Absolute)(BoundBox)(Workplane))) + +/** Section parameters */ +#define AREA_PARAMS_SECTION \ + ((long,count,SectionCount,0,"Number of sections to generate. -1 means full sections."))\ + ((double,stepdown,Stepdown,1.0,"Step down distance for each section"))\ + ((double,offset,SectionOffset,0.0,"Offset for the first section"))\ + AREA_PARAMS_SECTION_EXTRA + +#ifdef AREA_OFFSET_ALGO +# define AREA_PARAMS_OFFSET_ALGO \ + ((enum,algo,Algo,0,"Offset algorithm type",(Clipper)(libarea))) +#else +# define AREA_PARAMS_OFFSET_ALGO +#endif + +/** Offset configuration parameters */ +#define AREA_PARAMS_OFFSET_CONF \ + AREA_PARAMS_OFFSET_ALGO \ + ((enum2,join_type,JoinType,0,"ClipperOffset join type. \nSee https://goo.gl/4odfQh",\ + (Round)(Square)(Miter),(ClipperLib::JoinType,ClipperLib::jt)))\ + ((enum2,end_type,EndType,0,"\nClipperOffset end type. See https://goo.gl/tj7gkX",\ + (OpenRound)(ClosedPolygon)(ClosedLine)(OpenSquare)(OpenButt),(ClipperLib::EndType,ClipperLib::et)))\ + ((double,miter_limit,MiterLimit,2.0,"Miter limit for joint type Miter. See https://goo.gl/K8xX9h"))\ + ((double,round_precision,RoundPreceision,0.0,\ + "Round joint precision. If =0, it defaults to Accuracy. \nSee https://goo.gl/4odfQh")) + +#define AREA_PARAMS_MIN_DIST \ + ((double, min_dist, MinDistance, 0.0, \ + "minimum distance for the generated new wires. Wires maybe broken if the\n"\ + "algorithm see fits. Set to zero to disable wire breaking.")) + +/** Area wire sorting parameters */ +#define AREA_PARAMS_SORT \ + ((enum, sort_mode, SortMode, 1, "Wire sorting mode to optimize travel distance.\n"\ + "'2D5' explode shapes into wires, and groups the shapes by its plane. The 'start' position\n"\ + "chooses the first plane to start. The algorithm will then sort within the plane and then\n"\ + "move on to the next nearest plane.\n"\ + "'3D' makes no assumption of planarity. The sorting is done across 3D space\n",\ + (None)(2D5)(3D)))\ + AREA_PARAMS_MIN_DIST + +/** Area path generation parameters */ +#define AREA_PARAMS_PATH \ + AREA_PARAMS_SORT \ + ((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"))\ + ((double,segmentation,Segmentation,0.0,\ + "Break long curves into segments of this length. One use case is for PCB autolevel,\n"\ + "so that more correction points can be inserted")) + +#define AREA_PARAMS_PATH_EXTRA \ + AREA_PARAMS_DEFLECTION \ + AREA_PARAMS_FIT_ARCS + +#define AREA_PARAMS_PATH_CONF \ + AREA_PARAMS_PATH \ + AREA_PARAMS_PATH_EXTRA + +/** Group of all Area configuration parameters except CArea's*/ +#define AREA_PARAMS_AREA \ + AREA_PARAMS_BASE \ + AREA_PARAMS_OFFSET \ + AREA_PARAMS_OFFSET_CONF \ + AREA_PARAMS_POCKET \ + AREA_PARAMS_POCKET_CONF \ + AREA_PARAMS_SECTION + +/** Group of all Area configuration parameters */ +#define AREA_PARAMS_CONF \ + AREA_PARAMS_CAREA \ + AREA_PARAMS_AREA + +/** Group of all Area parameters */ +#define AREA_PARAMS_ALL \ + AREA_PARAMS_CONF \ + AREA_PARAMS_OPCODE + +#define AREA_PARAM_LOG_LEVEL (Error)(Warning)(Log)(Trace) +#define AREA_PARAMS_LOG_LEVEL \ + ((enum, log_level, LogLevel, 1, "Area log level", AREA_PARAM_LOG_LEVEL)) + +#define AREA_PARAMS_EXTRA_CONF \ + AREA_PARAMS_LOG_LEVEL + +#define AREA_PARAMS_STATIC_CONF \ + AREA_PARAMS_CONF \ + AREA_PARAMS_EXTRA_CONF + +#endif //PATH_AreaParam_H diff --git a/src/Mod/Path/App/AreaPy.xml b/src/Mod/Path/App/AreaPy.xml new file mode 100644 index 0000000000..ad4a0a306f --- /dev/null +++ b/src/Mod/Path/App/AreaPy.xml @@ -0,0 +1,114 @@ + + + + + + FreeCAD python wrapper of libarea\n +Path.Area(key=value ...)\n +The constuctor accepts the same parameters as setParams(...) to configure the object +All arguments are optional. + + + + + + + + + setPlane(shape): Set the working plane.\n +The supplied shape does not need to be planar. Area will try to find planar +sub-shape (face, wire or edge). If more than one planar sub-shape is found, it +will prefer the top plane parallel to XY0 plane. If no working plane are set, +Area will try to find a working plane from the added children shape using the +same algorithm + + + + + getShape(index=-1,rebuild=False): Return the resulting shape\n +\n* index (-1): the index of the section. -1 means all sections. No effect on planar shape.\n +\n* rebuild: clean the internal cache and rebuild + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + getParamsDesc(as_string=True): Returns a list of supported parameters and their descriptions.\n +* as_string: if False, then return a dictionary of documents of all supported parameters. + + + + + + + + + + + Get current algorithm parameters as a dictionary. + + + + + + + + + + List of sections in this area. + + + + + + The current workplane. If no plane is set, it is derived from the added shapes. + + + + + + A list of tuple: [(shape,op), ...] containing the added shapes together with their operation code + + + + + diff --git a/src/Mod/Path/App/AreaPyImp.cpp b/src/Mod/Path/App/AreaPyImp.cpp new file mode 100644 index 0000000000..3fb7f10893 --- /dev/null +++ b/src/Mod/Path/App/AreaPyImp.cpp @@ -0,0 +1,495 @@ +/**************************************************************************** + * Copyright (c) 2017 Zheng, Lei (realthunder) * + * * + * 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 * + * * + ****************************************************************************/ + +#include "PreCompiled.h" + +#include +#include + +#include "Area.h" + +// inclusion of the generated files (generated out of AreaPy.xml) +#include "AreaPy.h" +#include "AreaPy.cpp" + + +static PyObject * areaAbort(PyObject *, PyObject *args, PyObject *kwd) { + static char *kwlist[] = {"aborting", NULL}; + PyObject *pObj = Py_True; + if (!PyArg_ParseTupleAndKeywords(args,kwd,"|O",kwlist,&pObj)) + return 0; + Area::abort(PyObject_IsTrue(pObj)); + return Py_None; +} + +static PyObject * areaSetParams(PyObject *, PyObject *args, PyObject *kwd) { + + static char *kwlist[] = {PARAM_FIELD_STRINGS(NAME,AREA_PARAMS_STATIC_CONF),NULL}; + + //Declare variables defined in the NAME field of the CONF parameter list + PARAM_PY_DECLARE(PARAM_FNAME,AREA_PARAMS_STATIC_CONF); + + AreaStaticParams params = Area::getDefaultParams(); + +#define AREA_SET(_param) \ + PARAM_FNAME(_param) = \ + PARAM_TYPED(PARAM_PY_CAST_,_param)(params.PARAM_FNAME(_param)); + //populate the CONF variables with params + PARAM_FOREACH(AREA_SET,AREA_PARAMS_STATIC_CONF) + + //Parse arguments to overwrite CONF variables + if (!PyArg_ParseTupleAndKeywords(args, kwd, + "|" PARAM_PY_KWDS(AREA_PARAMS_STATIC_CONF), kwlist, + PARAM_REF(PARAM_FNAME,AREA_PARAMS_STATIC_CONF))) + return 0; + +#define AREA_GET(_param) \ + params.PARAM_FNAME(_param) = \ + PARAM_TYPED(PARAM_CAST_PY_,_param)(PARAM_FNAME(_param)); + //populate 'params' with the CONF variables + PARAM_FOREACH(AREA_GET,AREA_PARAMS_STATIC_CONF) + + Area::setDefaultParams(params); + return Py_None; +} + +static PyObject* areaGetParams(PyObject *, PyObject *args) { + if (!PyArg_ParseTuple(args, "")) + return 0; + + const AreaStaticParams ¶ms = Area::getDefaultParams(); + + PyObject *dict = PyDict_New(); +#define AREA_SRC(_param) params.PARAM_FNAME(_param) + PARAM_PY_DICT_SET_VALUE(dict,NAME,AREA_SRC,AREA_PARAMS_STATIC_CONF) + return dict; +} + + +static const PyMethodDef areaOverrides[] = { + { + "setParams",NULL,0, + "setParam(key=value...): Set algorithm parameters. You can call getParamsDesc() to \n" + "get a list of supported parameters and their descriptions.\n" + PARAM_PY_DOC(NAME,AREA_PARAMS_CONF) + }, + { + "add",NULL,0, + "add((shape...)," PARAM_PY_ARGS_DOC(ARG,AREA_PARAMS_OPCODE) "):\n" + "Add TopoShape(s) with given operation code\n" + PARAM_PY_DOC(ARG,AREA_PARAMS_OPCODE) + "\nThe first shape's wires will be unioned together regardless of the op code given\n" + "(except for 'Compound'). Subsequent shape's wire will be combined using the op code.\n" + "All shape wires shall be coplanar, and are used to determine a working plane for face\n" + "making and offseting. You can call setPlane() to supply a reference shape to determin\n" + "the workplane in case the added shapes are all colinear lines.\n", + }, + + { + "makeOffset",NULL,0, + "makeOffset(index=-1, " PARAM_PY_ARGS_DOC(ARG,AREA_PARAMS_OFFSET) "):\n" + "Make an 2D offset of the shape.\n" + "\n* index (-1): the index of the section. -1 means all sections. No effect on planar shape.\n" + PARAM_PY_DOC(ARG,AREA_PARAMS_OFFSET), + }, + { + "makePocket",NULL,0, + "makePocket(index=-1, " PARAM_PY_ARGS_DOC(ARG,AREA_PARAMS_POCKET) "):\n" + "Generate pocket toolpath of the shape.\n" + "\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), + }, + { + "makeSections",NULL,0, + "makeSections(" PARAM_PY_ARGS_DOC(ARG,AREA_PARAMS_SECTION_EXTRA) ", heights=[], plane=None):\n" + "Make a list of area holding the sectioned children shapes on given heights\n" + PARAM_PY_DOC(ARG,AREA_PARAMS_SECTION_EXTRA) + "\n* heights ([]): a list of section heights, the meaning of the value is determined by 'mode'.\n" + "If not specified, the current SectionCount, and SectionOffset of this Area is used.\n" + "\n* plane (None): optional shape to specify a section plane. If not give, the current workplane\n" + "of this Area is used.", + }, + { + "sortWires",NULL,0, + "sortWires(index=-1, count=0, start=Vector(), allow_break=False, " PARAM_PY_ARGS_DOC(ARG,AREA_PARAMS_SORT) "):\n" + "Returns a tuple (wires,end): sorted wires with minimized travel distance, and the endpoint\n" + "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_SORT), + }, + { + "setDefaultParams",(PyCFunction)areaSetParams, METH_VARARGS|METH_KEYWORDS|METH_STATIC, + "setDefaultParams(" PARAM_PY_ARGS_DOC(NAME,AREA_PARAMS_EXTRA_CONF) ", key=value...):\n" + "Static method to set the default parameters of all following Path.Area, plus the following\n" + "additional parameters.\n" + PARAM_PY_DOC(NAME,AREA_PARAMS_EXTRA_CONF) + }, + { + "getDefaultParams",(PyCFunction)areaGetParams, METH_VARARGS|METH_STATIC, + "getDefaultParams(): Static method to return the current default parameters." + }, + { + "abort",(PyCFunction)areaAbort, METH_VARARGS|METH_KEYWORDS|METH_STATIC, + "abort(aborting=True): Static method to abort any ongoing operation\n" + "\nTo ensure no stray abortion is left in the previous operaion, it is advised to manually clear\n" + "the aborting flag by calling abort(False) before starting a new operation.", + }, +}; + +struct AreaPyModifier { + AreaPyModifier() { + for(auto &method : Path::AreaPy::Methods) { + if(!method.ml_name) continue; + for(auto &entry : areaOverrides) { + if(std::strcmp(method.ml_name,entry.ml_name)==0) { + if(entry.ml_doc) + method.ml_doc = entry.ml_doc; + if(entry.ml_meth) + method.ml_meth = entry.ml_meth; + if(entry.ml_flags) + method.ml_flags = entry.ml_flags; + break; + } + } + } + } +}; + +static AreaPyModifier mod; + +namespace Part { +extern PartExport Py::Object shape2pyshape(const TopoDS_Shape &shape); +} + +using namespace Path; + +// returns a string which represents the object e.g. when printed in python +std::string AreaPy::representation(void) const +{ + std::stringstream str; + str << ""; + return str.str(); +} + +PyObject *AreaPy::PyMake(struct _typeobject *, PyObject *args, PyObject *kwd) // Python wrapper +{ + std::unique_ptr ret(new AreaPy(new Area)); + if(!ret->setParams(args,kwd)) + return 0; + return ret.release(); +} + +// constructor method +int AreaPy::PyInit(PyObject* , PyObject* ) +{ + return 0; +} + +PyObject* AreaPy::setPlane(PyObject *args) { + PyObject *pcObj; + if (!PyArg_ParseTuple(args, "O!", &(Part::TopoShapePy::Type), &pcObj)) + return 0; + +#define GET_TOPOSHAPE(_p) static_cast(_p)->getTopoShapePtr()->getShape() + getAreaPtr()->setPlane(GET_TOPOSHAPE(pcObj)); + Py_INCREF(this); + return this; +} + +PyObject* AreaPy::getShape(PyObject *args, PyObject *keywds) +{ + PyObject *pcObj = Py_False; + short index=-1; + static char *kwlist[] = {"index","rebuild", NULL}; + if (!PyArg_ParseTupleAndKeywords(args, keywds,"|hO",kwlist,&pcObj)) + return 0; + + 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_SORT) + short index = -1; + short count = 0; + PyObject *start = NULL; + + static char *kwlist[] = {"index","count","start", + PARAM_FIELD_STRINGS(ARG,AREA_PARAMS_SORT), NULL}; + if (!PyArg_ParseTupleAndKeywords(args, keywds, + "|hhO!" PARAM_PY_KWDS(AREA_PARAMS_SORT), + kwlist,&index,&count,&(Base::VectorPy::Type),&start, + PARAM_REF(PARAM_FARG,AREA_PARAMS_SORT))) + return 0; + + gp_Pnt pstart,pend; + if(start) { + Base::Vector3d vec = static_cast(start)->value(); + pstart.SetCoord(vec.x, vec.y, vec.z); + } + std::list wires = getAreaPtr()->sortWires( + index,count,&pstart,&pend, + PARAM_PY_FIELDS(PARAM_FARG,AREA_PARAMS_SORT)); + 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) +{ + PARAM_PY_DECLARE_INIT(PARAM_FARG,AREA_PARAMS_OPCODE) + PyObject *pcObj; + + //Strangely, PyArg_ParseTupleAndKeywords requires all arguments to be keyword based, + //even non-optional ones? That doesn't make sense in python. Seems only in python 3 + //they added '$' to address that issue. + static char *kwlist[] = {"shape",PARAM_FIELD_STRINGS(ARG,AREA_PARAMS_OPCODE), NULL}; + + if (!PyArg_ParseTupleAndKeywords(args, keywds, + "O|" PARAM_PY_KWDS(AREA_PARAMS_OPCODE), + kwlist,&pcObj,PARAM_REF(PARAM_FARG,AREA_PARAMS_OPCODE))) + return 0; + + if (PyObject_TypeCheck(pcObj, &(Part::TopoShapePy::Type))) { + getAreaPtr()->add(GET_TOPOSHAPE(pcObj),op); + Py_INCREF(this); + return this; + } else if (PyObject_TypeCheck(pcObj, &(PyList_Type)) || + PyObject_TypeCheck(pcObj, &(PyTuple_Type))) { + Py::Sequence shapeSeq(pcObj); + 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"); + return 0; + } + } + for (Py::Sequence::iterator it = shapeSeq.begin(); it != shapeSeq.end(); ++it){ + PyObject* item = (*it).ptr(); + getAreaPtr()->add(GET_TOPOSHAPE(item), + PARAM_PY_FIELDS(PARAM_FARG,AREA_PARAMS_OPCODE)); + } + Py_INCREF(this); + return this; + } + + PyErr_SetString(PyExc_TypeError, "shape must be 'TopoShape' or list of 'TopoShape'"); + return 0; +} + +PyObject* AreaPy::makeOffset(PyObject *args, PyObject *keywds) +{ + //Generate a keyword string defined in the ARG field of OFFSET parameter list + static char *kwlist[] = {"index",PARAM_FIELD_STRINGS(ARG,AREA_PARAMS_OFFSET), NULL}; + short index = -1; + + //Declare variables defined in the ARG field of the OFFSET parameter list with + //initialization to defaults + PARAM_PY_DECLARE_INIT(PARAM_FARG,AREA_PARAMS_OFFSET) + + //Parse arguments to overwrite the defaults + if (!PyArg_ParseTupleAndKeywords(args, keywds, + "|h" PARAM_PY_KWDS(AREA_PARAMS_OFFSET), kwlist, + &index,PARAM_REF(PARAM_FARG,AREA_PARAMS_OFFSET))) + return 0; + + //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) +{ + static char *kwlist[] = {"index",PARAM_FIELD_STRINGS(ARG,AREA_PARAMS_POCKET), NULL}; + short index = -1; + + PARAM_PY_DECLARE_INIT(PARAM_FARG,AREA_PARAMS_POCKET) + //Override pocket mode default + mode = Area::PocketModeZigZagOffset; + + if (!PyArg_ParseTupleAndKeywords(args, keywds, + "|h" PARAM_PY_KWDS(AREA_PARAMS_POCKET), kwlist, + &index,PARAM_REF(PARAM_FARG,AREA_PARAMS_POCKET))) + return 0; + + TopoDS_Shape resultShape = getAreaPtr()->makePocket(index, + PARAM_PY_FIELDS(PARAM_FARG,AREA_PARAMS_POCKET)); + return Py::new_reference_to(Part::shape2pyshape(resultShape)); +} + +PyObject* AreaPy::makeSections(PyObject *args, PyObject *keywds) +{ + static char *kwlist[] = {PARAM_FIELD_STRINGS(ARG,AREA_PARAMS_SECTION_EXTRA), + "heights", "plane", NULL}; + PyObject *heights = NULL; + PyObject *plane = NULL; + + PARAM_PY_DECLARE_INIT(PARAM_FARG,AREA_PARAMS_SECTION_EXTRA) + + if (!PyArg_ParseTupleAndKeywords(args, keywds, + "|" PARAM_PY_KWDS(AREA_PARAMS_SECTION_EXTRA) "OO!", kwlist, + PARAM_REF(PARAM_FARG,AREA_PARAMS_SECTION_EXTRA), + &heights, &(Part::TopoShapePy::Type), &plane)) + return 0; + + std::vector h; + if(heights) { + if (PyObject_TypeCheck(heights, &(PyFloat_Type))) + h.push_back(PyFloat_AsDouble(heights)); + else if (PyObject_TypeCheck(heights, &(PyList_Type)) || + PyObject_TypeCheck(heights, &(PyTuple_Type))) { + Py::Sequence shapeSeq(heights); + h.reserve(shapeSeq.size()); + for (Py::Sequence::iterator it = shapeSeq.begin(); it != shapeSeq.end(); ++it) { + PyObject* item = (*it).ptr(); + if(!PyObject_TypeCheck(item, &(PyFloat_Type))) { + PyErr_SetString(PyExc_TypeError, "heights must only contain float type"); + return 0; + } + h.push_back(PyFloat_AsDouble(item)); + } + }else{ + PyErr_SetString(PyExc_TypeError, "heights must be of type float or list/tuple of float"); + return 0; + } + } + + std::vector > sections = getAreaPtr()->makeSections( + PARAM_PY_FIELDS(PARAM_FARG,AREA_PARAMS_SECTION_EXTRA), + h,plane?GET_TOPOSHAPE(plane):TopoDS_Shape()); + + Py::List ret; + for(auto &area : sections) + ret.append(Py::asObject(new AreaPy(new Area(*area,false)))); + return Py::new_reference_to(ret); +} + +PyObject* AreaPy::setDefaultParams(PyObject *, PyObject *) +{ + return 0; +} + +PyObject* AreaPy::setParams(PyObject *args, PyObject *keywds) +{ + static char *kwlist[] = {PARAM_FIELD_STRINGS(NAME,AREA_PARAMS_CONF),NULL}; + + //Declare variables defined in the NAME field of the CONF parameter list + PARAM_PY_DECLARE(PARAM_FNAME,AREA_PARAMS_CONF); + + AreaParams params = getAreaPtr()->getParams(); + + //populate the CONF variables with params + PARAM_FOREACH(AREA_SET,AREA_PARAMS_CONF) + + //Parse arguments to overwrite CONF variables + if (!PyArg_ParseTupleAndKeywords(args, keywds, + "|" PARAM_PY_KWDS(AREA_PARAMS_CONF), kwlist, + PARAM_REF(PARAM_FNAME,AREA_PARAMS_CONF))) + return 0; + + //populate 'params' with the CONF variables + PARAM_FOREACH(AREA_GET,AREA_PARAMS_CONF) + + getAreaPtr()->setParams(params); + Py_INCREF(this); + return this; +} + +PyObject* AreaPy::getParams(PyObject *args) +{ + if (!PyArg_ParseTuple(args, "")) + return 0; + + const AreaParams ¶ms =getAreaPtr()->getParams(); + + PyObject *dict = PyDict_New(); + PARAM_PY_DICT_SET_VALUE(dict,NAME,AREA_SRC,AREA_PARAMS_CONF) + return dict; +} + +PyObject* AreaPy::getDefaultParams(PyObject *) +{ + return 0; +} + +PyObject* AreaPy::abort(PyObject *, PyObject *) { + return 0; +} + +PyObject* AreaPy::getParamsDesc(PyObject *args, PyObject *keywds) +{ + PyObject *pcObj = Py_True; + static char *kwlist[] = {"as_string", NULL}; + if (!PyArg_ParseTupleAndKeywords(args, keywds,"|O",kwlist,&pcObj)) + return 0; + + if(PyObject_IsTrue(pcObj)) + return PyString_FromString(PARAM_PY_DOC(NAME,AREA_PARAMS_CONF)); + + PyObject *dict = PyDict_New(); + PARAM_PY_DICT_SET_DOC(dict,NAME,AREA_PARAMS_CONF) + return dict; +} + +Py::List AreaPy::getSections(void) const { + Py::List ret; + Area *area = getAreaPtr(); + for(size_t i=0,count=area->getSectionCount(); igetShape(i))); + return ret; +} + +Py::List AreaPy::getShapes(void) const { + Py::List ret; + Area *area = getAreaPtr(); + const std::list &shapes = area->getChildren(); + for(auto &s : shapes) + ret.append(Py::TupleN(Part::shape2pyshape(s.shape),Py::Int(s.op))); + return ret; +} + +Py::Object AreaPy::getWorkplane(void) const { + return Part::shape2pyshape(getAreaPtr()->getPlane()); +} + + +// custom attributes get/set + +PyObject *AreaPy::getCustomAttributes(const char* /*attr*/) const +{ + return 0; +} + +int AreaPy::setCustomAttributes(const char* /*attr*/, PyObject* /*obj*/) +{ + return 0; +} diff --git a/src/Mod/Path/App/CMakeLists.txt b/src/Mod/Path/App/CMakeLists.txt index 266209f8b9..0816c7d077 100644 --- a/src/Mod/Path/App/CMakeLists.txt +++ b/src/Mod/Path/App/CMakeLists.txt @@ -22,6 +22,7 @@ link_directories(${OCC_LIBRARY_DIR}) set(Path_LIBS # Robot Part + area-native FreeCADApp ) @@ -30,6 +31,8 @@ generate_from_xml(PathPy) generate_from_xml(ToolPy) generate_from_xml(TooltablePy) generate_from_xml(FeaturePathCompoundPy) +generate_from_xml(AreaPy) +generate_from_xml(FeatureAreaPy) SET(Python_SRCS CommandPy.xml @@ -41,6 +44,10 @@ SET(Python_SRCS TooltablePyImp.cpp FeaturePathCompoundPy.xml FeaturePathCompoundPyImp.cpp + AreaPy.xml + AreaPyImp.cpp + FeatureAreaPy.xml + FeatureAreaPyImp.cpp ) SET(Mod_SRCS @@ -67,6 +74,12 @@ SET(Path_SRCS FeaturePathCompound.h FeaturePathShape.cpp FeaturePathShape.h + Area.cpp + Area.h + AreaParams.h + ParamsHelper.h + FeatureArea.cpp + FeatureArea.h ${Mod_SRCS} ${Python_SRCS} ) diff --git a/src/Mod/Path/App/FeatureArea.cpp b/src/Mod/Path/App/FeatureArea.cpp new file mode 100644 index 0000000000..5d399a599c --- /dev/null +++ b/src/Mod/Path/App/FeatureArea.cpp @@ -0,0 +1,247 @@ +/**************************************************************************** + * Copyright (c) 2017 Zheng, Lei (realthunder) * + * * + * 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 * + * * + ****************************************************************************/ + +#include "PreCompiled.h" + +#ifndef _PreComp_ +#endif + +#include +#include + +#include "FeatureArea.h" +#include "FeatureAreaPy.h" +#include +#include +#include + +using namespace Path; + +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())); + + PARAM_PROP_ADD("Area",AREA_PARAMS_OPCODE); + PARAM_PROP_ADD("Area",AREA_PARAMS_BASE); + PARAM_PROP_ADD("Offset",AREA_PARAMS_OFFSET); + PARAM_PROP_ADD("Offset", AREA_PARAMS_OFFSET_CONF); + PARAM_PROP_ADD("Pocket",AREA_PARAMS_POCKET); + PARAM_PROP_ADD("Pocket",AREA_PARAMS_POCKET_CONF); + PARAM_PROP_ADD("Section",AREA_PARAMS_SECTION); + PARAM_PROP_ADD("libarea",AREA_PARAMS_CAREA); + + PARAM_PROP_SET_ENUM(Enums,AREA_PARAMS_ALL); + PocketMode.setValue((long)0); +} + +FeatureArea::~FeatureArea() +{ +} + +Area &FeatureArea::getArea() { + if(!myBuild) + execute(); + return myArea; +} + +App::DocumentObjectExecReturn *FeatureArea::execute(void) +{ + std::vector links = Sources.getValues(); + if (links.empty()) + return new App::DocumentObjectExecReturn("No shapes linked"); + + for (std::vector::iterator it = links.begin(); it != links.end(); ++it) { + if (!(*it && (*it)->isDerivedFrom(Part::Feature::getClassTypeId()))) + return new App::DocumentObjectExecReturn("Linked object is not a Part object (has no Shape)."); + TopoDS_Shape shape = static_cast(*it)->Shape.getShape().getShape(); + if (shape.IsNull()) + return new App::DocumentObjectExecReturn("Linked shape object is empty"); + } + + TIME_INIT(t); + + myBuild = true; + AreaParams params; + +#define AREA_PROP_GET(_param) \ + params.PARAM_FNAME(_param) = PARAM_FNAME(_param).getValue(); + PARAM_FOREACH(AREA_PROP_GET,AREA_PARAMS_CONF) + + myArea.clean(true); + myArea.setParams(params); + + TopoDS_Shape workPlane = WorkPlane.getShape().getShape(); + myArea.setPlane(workPlane); + + for (std::vector::iterator it = links.begin(); it != links.end(); ++it) { + myArea.add(static_cast(*it)->Shape.getShape().getShape(), + PARAM_PROP_ARGS(AREA_PARAMS_OPCODE)); + } + + myShapes.clear(); + if(myArea.getSectionCount()==0) + myShapes.push_back(myArea.getShape(-1)); + else { + myShapes.reserve(myArea.getSectionCount()); + for(int i=0;i<(int)myArea.getSectionCount();++i) + myShapes.push_back(myArea.getShape(i)); + } + + if(myShapes.empty()) + Shape.setValue(TopoDS_Shape()); + else if(myShapes.size()==1) + Shape.setValue(myShapes.front()); + else{ + BRep_Builder builder; + TopoDS_Compound compound; + builder.MakeCompound(compound); + for(auto &shape : myShapes) + builder.Add(compound,shape); + Shape.setValue(compound); + } + + TIME_PRINT(t,"feature execute"); + return Part::Feature::execute(); +} + +const std::vector &FeatureArea::getShapes() { + getArea(); + return myShapes; +} + +short FeatureArea::mustExecute(void) const +{ + if (Sources.isTouched()) + return 1; + if (WorkPlane.isTouched()) + return 1; + + PARAM_PROP_TOUCHED(AREA_PARAMS_ALL) + + return Part::Feature::mustExecute(); +} + +PyObject *FeatureArea::getPyObject() +{ + if (PythonObject.is(Py::_None())){ + // ref counter is set to 1 + PythonObject = Py::Object(new FeatureAreaPy(this),true); + } + return Py::new_reference_to(PythonObject); +} + + +// 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, negative value for reverse index from bottom"); + ADD_PROPERTY_TYPE(SectionCount,(1),"Section",App::Prop_None,"Number of sections to show, 0 to show all section starting from SectionIndex"); +} + +std::list FeatureAreaView::getShapes() { + std::list shapes; + App::DocumentObject* pObj = Source.getValue(); + if (!pObj) return shapes; + if(!pObj->isDerivedFrom(FeatureArea::getClassTypeId())) + return shapes; + + auto all_shapes = static_cast(pObj)->getShapes(); + + if(all_shapes.empty()) + return shapes; + + int index=SectionIndex.getValue(),count=SectionCount.getValue(); + if(index<0) { + index += ((int)all_shapes.size()); + if(index<0) return shapes; + if(count<=0 || index+1-count<0) { + count = index+1; + index = 0; + }else + index -= count-1; + }else if(index >= (int)all_shapes.size()) + return shapes; + + if(count<=0) count = all_shapes.size(); + count += index; + if(count>(int)all_shapes.size()) + count = all_shapes.size(); + 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::ViewProviderAreaPython"; +} +template<> const char* Path::FeatureAreaViewPython::getViewProviderName(void) const { + return "PathGui::ViewProviderAreaViewPython"; +} +/// @endcond + +// explicit template instantiation +template class PathExport FeaturePythonT; +} + diff --git a/src/Mod/Path/App/FeatureArea.h b/src/Mod/Path/App/FeatureArea.h new file mode 100644 index 0000000000..b766491b81 --- /dev/null +++ b/src/Mod/Path/App/FeatureArea.h @@ -0,0 +1,96 @@ +/**************************************************************************** + * Copyright (c) 2017 Zheng, Lei (realthunder) * + * * + * 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_FeatureArea_H +#define PATH_FeatureArea_H + +#include +#include +#include +#include +#include +#include "Mod/Part/App/PartFeature.h" + +#include "Area.h" + +namespace Path +{ + +class PathExport FeatureArea : public Part::Feature +{ + PROPERTY_HEADER(Path::FeatureArea); + +public: + /// Constructor + FeatureArea(void); + virtual ~FeatureArea(); + + Area &getArea(); + const std::vector &getShapes(); + + /// returns the type name of the ViewProvider + virtual const char* getViewProviderName(void) const { + return "PathGui::ViewProviderArea"; + } + virtual App::DocumentObjectExecReturn *execute(void); + virtual short mustExecute(void) const; + virtual PyObject *getPyObject(void); + + App::PropertyLinkList Sources; + Part::PropertyPartShape WorkPlane; + + PARAM_PROP_DECLARE(AREA_PARAMS_ALL) + +private: + bool myBuild; + Area myArea; + std::vector myShapes; +}; + +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 + + +#endif // PATH_FeaturePath_H diff --git a/src/Mod/Path/App/FeatureAreaPy.xml b/src/Mod/Path/App/FeatureAreaPy.xml new file mode 100644 index 0000000000..e62d94ed16 --- /dev/null +++ b/src/Mod/Path/App/FeatureAreaPy.xml @@ -0,0 +1,30 @@ + + + + + + This class handles Path Area features + + + + Return a copy of the encapsulated Python Area object. + + + + + setParams(key=value...): Convenient function to configure this feature.\n +Same usage as Path.Area.setParams(). This function stores the parameters in the properties. + + + + + + diff --git a/src/Mod/Path/App/FeatureAreaPyImp.cpp b/src/Mod/Path/App/FeatureAreaPyImp.cpp new file mode 100644 index 0000000000..755db26899 --- /dev/null +++ b/src/Mod/Path/App/FeatureAreaPyImp.cpp @@ -0,0 +1,93 @@ +/**************************************************************************** + * Copyright (c) 2017 Zheng, Lei (realthunder) * + * * + * 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 * + * * + ****************************************************************************/ + +#include "PreCompiled.h" + +#include +#include "FeatureArea.h" + +// inclusion of the generated files (generated out of FeatureAreaPy.xml) +#include "FeatureAreaPy.h" +#include "FeatureAreaPy.cpp" + +#include "AreaPy.h" + +using namespace Path; + + +// returns a string which represent the object e.g. when printed in python +std::string FeatureAreaPy::representation(void) const +{ + return std::string(""); +} + + +PyObject* FeatureAreaPy::getArea(PyObject *args) +{ + if (!PyArg_ParseTuple(args, "")) + return NULL; + + return new AreaPy(new Area(getFeatureAreaPtr()->getArea())); +} + +PyObject* FeatureAreaPy::setParams(PyObject *args, PyObject *keywds) +{ + static char *kwlist[] = {PARAM_FIELD_STRINGS(NAME,AREA_PARAMS_CONF),NULL}; + + //Declare variables defined in the NAME field of the CONF parameter list + PARAM_PY_DECLARE(PARAM_FNAME,AREA_PARAMS_CONF); + + FeatureArea *feature = getFeatureAreaPtr(); + +#define AREA_SET(_param) \ + PARAM_FNAME(_param) = \ + PARAM_TYPED(PARAM_PY_CAST_,_param)(feature->PARAM_FNAME(_param).getValue()); + //populate the CONF variables with values in properties + PARAM_FOREACH(AREA_SET,AREA_PARAMS_CONF) + + //Parse arguments to overwrite CONF variables + if (!PyArg_ParseTupleAndKeywords(args, keywds, + "|" PARAM_PY_KWDS(AREA_PARAMS_CONF), kwlist, + PARAM_REF(PARAM_FNAME,AREA_PARAMS_CONF))) + Py_Error(Base::BaseExceptionFreeCADError, + "Wrong parameters, call getParamsDesc() to get supported params"); + +#define AREA_GET(_param) \ + feature->PARAM_FNAME(_param).setValue(\ + PARAM_TYPED(PARAM_CAST_PY_,_param)(PARAM_FNAME(_param))); + //populate properties with the CONF variables + PARAM_FOREACH(AREA_GET,AREA_PARAMS_CONF) + + return Py_None; +} + +PyObject *FeatureAreaPy::getCustomAttributes(const char* /*attr*/) const +{ + return 0; +} + + +int FeatureAreaPy::setCustomAttributes(const char* /*attr*/, PyObject* /*obj*/) +{ + return 0; +} + diff --git a/src/Mod/Path/App/FeaturePathShape.cpp b/src/Mod/Path/App/FeaturePathShape.cpp index dcdd7e4fe3..300b8739d1 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,101 +34,66 @@ #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; PROPERTY_SOURCE(Path::FeatureShape, Path::Feature) +PARAM_ENUM_STRING_DECLARE(static const char *Enums,AREA_PARAMS_PATH) 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_CONF); + PARAM_PROP_SET_ENUM(Enums,AREA_PARAMS_PATH_CONF); } 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); + } + + AreaParams params; +#define AREA_PROP_GET(_param) \ + params.PARAM_FNAME(_param) = PARAM_FNAME(_param).getValue(); + PARAM_FOREACH(AREA_PROP_GET,AREA_PARAMS_PATH_EXTRA) + + Area::toPath(path,shapes,¶ms,&pstart,NULL,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 23648abcb3..4a539761ca 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_CONF) //@{ /// 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/ParamsHelper.h b/src/Mod/Path/App/ParamsHelper.h new file mode 100644 index 0000000000..0d59116ba9 --- /dev/null +++ b/src/Mod/Path/App/ParamsHelper.h @@ -0,0 +1,1052 @@ +/**************************************************************************** + * Copyright (c) 2017 Zheng, Lei (realthunder) * + * * + * 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 PARAMS_HELPER_H +#define PARAMS_HELPER_H + +/** \page ParamPage Parameter helper macros + * Collections of macros for managing groups of parameters. + * + * \section Motivation + * + * For an application like FreeCAD, there are often cases where the same set of + * parameters are refered in dozons of different places. The macros here is + * designed to help managing those parameters, so that you can define groups of + * parameters once, and refer them everywhere in groups with simple macro calls for + * all kinds of purposes. Any changing, adding and removing of parameters in the + * group become much easier. And by everywhere, I mean \ref ParamCommon + * "class definition, impelentation", \ref ParamProperty "document object properties", + * \ref ParamPy "python c++ classes", and even \ref ParamDoc "doc string", + * pretty much everything except the python code, which although not implemented + * yet, is in fact also possible to be done using C preprocessor (No one says C + * preprocessor must produce C code :). It is also possible (not implemented + * yet) to use macros to generate python wrapper class instead of using + * FreeCAD's current xml python export. + * + * \section Debugging + * + * Extensive use of macros has one noticable disadvantage, though. If some thing + * goes wrong, the compiler error message is kind of cryptic. If so, first + * double check your macro definition of the parameter is correctly, not missing + * or having extra parathesis or comma. Then, you can use the CMake + * intermeidate file target to get the preprocessor output for checking. For + * example, for a file located at \c src/Mod/Path/App/Area.cpp, + * \code{.sh} + * cd /src/Mod/Path/App + * make Area.cpp.i + * \endcode + * + * The preprocessed intermediate output will be at, + * \code{.sh} + * /src/Mod/Path/App.CMakeFiles/Path.dir/Area.cpp.i + * \endcode + * + * \section Intrudction of Boost.Preprocessor + * + * The macros here make heavy use of the awsome + * [Boost.Preprocessor](http://www.boost.org/libs/preprocessor/) (short for + * Boost.PP). Here are some brief introduction on Boost.PP conecept in order to + * explain why this marco library is designed the way it is. + * + * In Boost.PP, a sequence is defined as, + * \code{.sh} + * (a)(b)(c)... + * \endcode + * + * A sequence cannot be empty. Thus, \c () is not a sequence. And also those + * a, b, c here cannot directly contain ,. These restriction + * is due to the fact that ( ) , are among those very few special + * characters recognized by the preprocssor. \c a can itself be a sequence or + * other Boost.PP types, so by right, our parameter can be defined as something + * like + * \code{.sh} + * ((type)(name)(default)...) + * \endcode + * + * A bit awkward to write. So another Boost.PP type is chosen, tuple, to define + * each individual parameter. A tuple is defined as + * \code{.sh} + * (a,b,c ...) + * \endcode + * + * This is why the parameter definition requires a double parathesis, as shown + * in the following section. + * + * \section Library Overview + * + * In this macro library, a parameter is defined using a tuple inside a sequence, + * \code{.sh} + * ((, , , , , , )) + * \endcode + * + * - \c type is the type of the parameter. Currently only five types of + * parameters are defined, short, long, double, bool, enum, enum2. + * \enum2 type is the same as \enum with additional information to be able to + * map to a user defined C enum type. To add more types, search this file for + * keyword \a _short, and supply all relavant macros. It's quite trivial + * actually. + * + * - \c arg is the agument name. It is intended to be used as function argument. + * By convention, the name shall be all small cases, but that's not required. + * This \c arg can be repurposed, if the parameter is not going to be used as + * function agument. The #AREA_PARAMS_CAREA parameters repurposed this field + * to CArea internal setting variables to implement save, apply and restore + * function using CAreaConfig class. + * + * - \c name is normally a %CamelCase name which are used as member variable and + * property name. Because of this, make sure the names are unique to avoid + * conflicts. + * + * - \c default is the default value of this parameter. Right now, you must + * supply a default value. Boost.PP has trouble dealing with empty values. + * Remember that a sequence cannot be empty. Neight can tuple. Only array, + * something like (0,()) for an empty array. It is awkward to write, + * and didn't add much functionality I want, hence the restriction of + * non-empty defaults here. + * + * - \c doc is a string to describe the variable. + * + * - \c seq \anchor ParamSeq. Right now this field is used by \c enum and + * \c enum2 type parameter to define its enumerations. As the name suggests, + * it must be a sequence. It is not a tuple because looping through tuple is + * not as easy as sequence. Other type of parameter do not need to have this + * field + * + * - \c info is used to provide the supplimentery information for \c enum2 type + * of parameter, which can be converted to a user defined enum type by + * #PARAM_ENUM_CONVERT. \c info must be a tuple, with the user defined enum + * type as the first element, and a prefix as the second element. For \c enum2 + * type of parameter, this field is mandatory. + * + * The common usage is that you define a macro of a group of parameters. And use + * the macro helper here to do operation on each parameter in the group. See + * AreaParams.h file for an example of parameter definitions. + * + * Area.h, Area.cpp, FeatureArea.h, FeatureArea.cpp for usage of variouse macros. + * + * See struct AreaDoc for an example of doc string generation. + * + * Each field of the parameter can be refered to with various + * \ref ParamAccessor "various accessor macros", and can be easily + * \ref ParamStringizer "stringified". + * + * \anchor ParamField You can also use #PARAM_FIELD(_field,_param) to refer to + * each field, where \a _field is one of TYPE, ARG, NAME, DEF, DOC, or SEQ. + * And #PARAM_FIELD_STR to stringify. + * + * Here \a _param is the parameter definition described above in the form of a + * Boost.PP tuple, and is usally supplied by various \ref ParamLooper "looper macros" + * + * You can of course directly use various Boost.PP sequence looper to pass + * aditional arguments to the operation macro. See #PARAM_PY_DICT_SET_VALUE for + * an example of using tuple, and the more complex example #PARAM_ENUM_CONVERT + * + * Note that when generating comma separated list, the first and last comma are + * conveniently ommited, so that the macros can be mixed with others intuitively + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** \defgroup ParamHelper Parameters helper macros + * Collections of macros for managing groups of parameters */ + +/** + * \defgroup ParamAccessor Field accessors + * To abstract parameter field details + * \ingroup ParamHelper + * @{ + */ +#define PARAM_ITYPE 0 +#define PARAM_IARG 1 +#define PARAM_INAME 2 +#define PARAM_IDEF 3 +#define PARAM_IDOC 4 +#define PARAM_ISEQ 5 +#define PARAM_IINFO 6 + +#define PARAM_FIELD(_idx,_param) BOOST_PP_TUPLE_ELEM(PARAM_I##_idx,_param) + +#define PARAM_FTYPE(_param) PARAM_FIELD(TYPE,_param) +#define PARAM_FARG(_param) PARAM_FIELD(ARG,_param) +#define PARAM_FNAME(_param) PARAM_FIELD(NAME,_param) +#define PARAM_FDEF(_param) PARAM_FIELD(DEF,_param) +#define PARAM_FDOC(_param) PARAM_FIELD(DOC,_param) +#define PARAM_FSEQ(_param) PARAM_FIELD(SEQ,_param) +#define PARAM_FINFO(_param) PARAM_FIELD(INFO,_param) +#define PARAM_FENUM_TYPE(_param) BOOST_PP_TUPLE_ELEM(0,PARAM_FINFO(_param)) +#define PARAM_FENUM_PREFIX(_param) BOOST_PP_TUPLE_ELEM(1,PARAM_FINFO(_param)) +/** @} */ + + +/** + * \defgroup ParamStringizer Field stringizers + * \ingroup ParamHelper + * @{ */ +#define PARAM_FIELD_STR(_idx,_param) \ + BOOST_PP_STRINGIZE(BOOST_PP_TUPLE_ELEM(PARAM_I##_idx,_param)) + +#define PARAM_FTYPE_STR(_param) PARAM_FIELD_STR(TYPE,_param) +#define PARAM_FARG_STR(_param) PARAM_FIELD_STR(ARG,_param) +#define PARAM_FNAME_STR(_param) PARAM_FIELD_STR(NAME,_param) +#define PARAM_FDEF_STR(_param) PARAM_FIELD_STR(DEF,_param) +/** @} */ + +/** Helper for #PARAM_FSEQ_STR */ +#define PARAM_FSEQ_STR_(_i,_elem) \ + BOOST_PP_COMMA_IF(_i) BOOST_PP_STRINGIZE(_elem) + +/** \c SEQ stringizer will stringify each element separately + * + * Expands to: + * #seq[0], #seq[1] ... + * \ingroup ParamHelper + */ +#define PARAM_FSEQ_STR(_param) \ + PARAM_FOREACH_I(PARAM_FSEQ_STR_,PARAM_FSEQ(_param)) + + +/** \defgroup ParamLooper Looper macros + * Macros for looping through sequence to parameters + * \ingroup ParamHelper + */ + +/** Helper for #PARAM_FOREACH */ +#define PARAM_FOREACH_(_,_op,_param) _op(_param) + +/** Apply macro \a _op to each parameter in sequence \a _seq + * + * Operation macro \a _op shoud be defined as, + * \code + * _op(_param) + * \endcode + * \ingroup ParamLooper + */ +#define PARAM_FOREACH(_op,_seq) \ + BOOST_PP_SEQ_FOR_EACH(PARAM_FOREACH_,_op,_seq) + +/** Helper for #PARAM_FOREACH_I */ +#define PARAM_FOREACH_I_(_,_op,_i,_param) _op(_i,_param) + +/** Apply macro \a _op to each parameter in sequence \a _seq with additional index + * + * Operation macro \a _op shoud be defined as, + * \code + * _op(_i,_param) + * \endcode + * \ingroup ParamLooper + * */ +#define PARAM_FOREACH_I(_op,_seq) \ + BOOST_PP_SEQ_FOR_EACH_I(PARAM_FOREACH_I_,_op,_seq) + + +/** Helper for #PARAM_TYPED_FOREACH */ +#define PARAM_TYPED_FOREACH_(_1,_op,_param) \ + PARAM_TYPED(_op,_param)(_param) + +/** Type depended macro construction + * + * Convert macro \a _op to \a _op##\. Note that it only converts the + * macro name, not contsucts a macro call. To expand to a macro call, simply + * \code + * PARAM_TYPED(_op,_param)(_param) + * \endcode + * \ingroup ParamLooper + */ +#define PARAM_TYPED(_op,_param) \ + BOOST_PP_CAT(_op,PARAM_FTYPE(_param)) + +/** Apply type dependent macro call to a sequence of parameters + * + * \a _op will be converted to \a _op##\ for each parameter + * \ingroup ParamLooper + */ +#define PARAM_TYPED_FOREACH(_op,_seq) \ + BOOST_PP_SEQ_FOR_EACH(PARAM_TYPED_FOREACH_,_op,_seq) + + +/** \defgroup ParamCommon Common helpers + * \ingroup ParamHelper + */ + +#define PARAM_TYPE_short short +#define PARAM_TYPE_long long +#define PARAM_TYPE_double double +#define PARAM_TYPE_bool bool +#define PARAM_TYPE_enum short +#define PARAM_TYPE_enum2 short + +/** Obtain parameter type + * + * The main purpose is to alias enum type to short + * \ingroup ParamCommon + */ +#define PARAM_TYPE(_param) \ + PARAM_TYPED(PARAM_TYPE_,_param) + + +/** Helper for #PARAM_DECLARE */ +#define PARAM_DECLARE_(_1,_src,_param) \ + PARAM_TYPE(_param) _src(_param); + +/** + * Delcares parameters using the given field as name + * + * \arg \c _src: \anchor ParamSrc Macro to generate source variable. The + * signature must be _src(_param)<\tt>, where \c _param is the tuple + * defining the parameter. You pass any of the \ref ParamAccessor "parameter + * accessors" to directly access the field. Or, supply your own macro to append + * any prefix as you like. For example: + * \code{.unparsed} + * #define MY_SRC(_param) BOOST_PP_CAT(my,PARAM_FNAME(_param)) + * -> + * my## + * \endcode + * + * Expands to: + * \code{.unparsed} + * type1 _src(_param1);type2 _src(_param2); ... + * \endcode + * \ingroup ParamCommon + */ +#define PARAM_DECLARE(_src,_seq) \ + BOOST_PP_SEQ_FOR_EACH(PARAM_DECLARE_,_src,_seq) + + +/** Helper for #PARAM_DECLARE_INIT */ +#define PARAM_DECLARE_INIT_(_1,_src,_param) \ + PARAM_TYPE(_param) _src(_param) = PARAM_FDEF(_param); + +/** + * Delcares parameters with initialization to default using the given field as + * name + * + * \arg \c _src: macro to generate source field. See \ref ParamSrc "here" for + * more details + * + * Expands to: + * \code{.unparsed} + * type1 _src(_param1)=_def1;type2 _src(_param2)=_def2; ... + * \endcode + * \ingroup ParamCommon + */ +#define PARAM_DECLARE_INIT(_src,_seq) \ + BOOST_PP_SEQ_FOR_EACH(PARAM_DECLARE_INIT_,_src,_seq) + + +#define PARAM_ENUM_DECLARE_enum_(_1,_name,_i,_elem) \ + BOOST_PP_COMMA_IF(_i) BOOST_PP_CAT(_name,_elem) + +#define PARAM_ENUM_DECLARE_enum(_param) \ + enum {BOOST_PP_SEQ_FOR_EACH_I(PARAM_ENUM_DECLARE_enum_,PARAM_FNAME(_param),PARAM_FSEQ(_param))}; + +#define PARAM_ENUM_DECLARE_short(_param) +#define PARAM_ENUM_DECLARE_long(_param) +#define PARAM_ENUM_DECLARE_double(_param) +#define PARAM_ENUM_DECLARE_bool(_param) +#define PARAM_ENUM_DECLARE_enum2 PARAM_ENUM_DECLARE_enum + +/** \defgroup ParamEnumHelper Enum convert helpers + * \ingroup ParamCommon + */ + +/** Make anonymous \c enum type + * + * Make anonymous \c enum type for \c enum type parameters in \a _seq. All other + * types are ignored. The enum member is prefixed with \a _name. Expand to: + * \code{.unparsed} + * enum {_name1##_seq1[0], _name1##_seq1[1] ...}; + * enum {_name2##_seq2[0], _name2##_seq2[1] ...}; + * ... + * \endcode + * \ingroup ParamEnumHelper*/ +#define PARAM_ENUM_DECLARE(_seq) \ + PARAM_TYPED_FOREACH(PARAM_ENUM_DECLARE_,_seq) + + +/** \addgroup ParamEnumHelper Enum convert helpers + * @{ */ +#define PARAM_ENUM_CONVERT_short(...) +#define PARAM_ENUM_CONVERT_long(...) +#define PARAM_ENUM_CONVERT_double(...) +#define PARAM_ENUM_CONVERT_bool(...) +#define PARAM_ENUM_CONVERT_enum(...) +#define PARAM_ENUM_CONVERT_enum2 PARAM_ENUM_CONVERT_SINGLE + +#define PARAM_ENUM_CONVERT_enum_(_dst,_name,_prefix,_elem) \ + case BOOST_PP_CAT(_name,_elem):\ + _dst = BOOST_PP_CAT(_prefix,_elem);\ + break; + +#define PARAM_ENUM_CONVERT__(_1,_args,_i,_elem) \ + PARAM_ENUM_CONVERT_enum_(BOOST_PP_TUPLE_ELEM(0,_args),\ + BOOST_PP_TUPLE_ELEM(1,_args),\ + BOOST_PP_TUPLE_ELEM(2,_args),\ + _elem); + +#define PARAM_ENUM_CONVERT_(_1,_args,_param) \ + PARAM_TYPED(PARAM_ENUM_CONVERT_,_param)(BOOST_PP_TUPLE_ELEM(0,_args),\ + BOOST_PP_TUPLE_ELEM(1,_args),\ + BOOST_PP_TUPLE_ELEM(2,_args),\ + _param) + +/** Convert single enum parameter value into user defined enum type + * + * This macro is used by #PARAM_ENUM_CONVERT to convert each parameter, but + * you can use it directly for a single parameter. Check #PARAM_NUM_CONVERT + * for more detail. Make sure the outer parathesis of \c _param is stripped, + * i.e. not double but single parathesis + */ +#define PARAM_ENUM_CONVERT_SINGLE(_src,_dst,_default,_param) \ + PARAM_FENUM_TYPE(_param) _dst(_param);\ + switch(_src(_param)) {\ + BOOST_PP_SEQ_FOR_EACH_I(PARAM_ENUM_CONVERT__,\ + (_dst(_param),PARAM_FNAME(_param),PARAM_FENUM_PREFIX(_param)),PARAM_FSEQ(_param))\ + default: \ + _default(_param);\ + } + +/** Default handling in #PARAM_ENUM_CONVERT and #PARAM_ENUM_CHECK*/ +#define PARAM_ENUM_EXCEPT(_param) \ + throw Base::ValueError("invalid value for enum " PARAM_FNAME_STR(_param)) + +/** @} */ + +/* Convert ParamHelper defined enum type to user defined ones + * + * This assumes the user defined enum type is given in \ref ParamSeq "seq_type" + * of the parameter definition, and it has the same postfix as the ones + * speficied in \ref ParamSeq "seq" member of the parameter definition. See + * \ref ParamEnumHelper "here" for implementations + * + * \ingroup ParamEnumHelper + * + * \arg \c _src: Macro to generate source variable. The signature must be + * _src(_param)<\tt>, where \c _param is the tuple defining the parameter. + * You pass any of the \ref ParamAccessor "parameter accessors" to directly + * access the field. Or, supply your own macro to append any prefix as you + * like. + * \arg \c _dst: Same as above. + * \arg \c _default: A macro to call for invalid value. Signature should be + * _default(_param)<\tt>, where \c _param is the parameter definition. You + * can use #PARAM_ENUM_EXCEPT to throw Base::ValueError exception in FreeCAD + * \arg \c _seq: Parameter sequence + * + * For example, with the following parameter definition + * \code{.unparsed} + * #define MY_PARAM_TEST \ + * ((enum,test1,Test1,0,"it's a test",(Foo)(Bar),(MyEnum1,myEnum1)) \ + * ((enum,test2,Test2,0,"it's a test",(Foo)(Bar),(MyEnum2,myEnum2))) + * + * #define MY_DST(_param) BOOST_PP_CAT(my,PARAM_FNAME(_param)) + * \code{.unparsed} + * + * calling + * \code{.unparsed} + * PARAM_ENUM_CONVERT(PARAM_FNAME,MY_DST,My,PARAM_ENUM_EXCEP,MY_PARAM_TEST) + * \code{.unparsed} + * + * expands to + * \code{.unparsed} + * MyEnum1 myTest1; + * switch(Test1) { + * case Test1Foo: + * myTest1 = myEnum1Foo; + * break; + * case Test1Bar: + * myTest1 = myEnum1Bar; + * break; + * default: + * throw Base::ValueError("invalid value for enum Test1"); + * } + * MyEnum2 myTest2; + * switch(Test2) { + * case Test1Foo: + * myTest2 = myEnum2Foo; + * break; + * case Test2Bar: + * myTest2 = myEnum2Bar; + * break; + * default: + * throw Base::ValueError("invalid value for enum Test2"); + * } + * \endcode + * + * The above code assumes you've already defined \a Test1 and \a Test2 some + * where as the source variable. + */ +#define PARAM_ENUM_CONVERT(_src,_dst,_default,_seq) \ + BOOST_PP_SEQ_FOR_EACH(PARAM_ENUM_CONVERT_,(_src,_dst,_default),_seq) + + +#define PARAM_ENUM_CHECK_short(...) +#define PARAM_ENUM_CHECK_long(...) +#define PARAM_ENUM_CHECK_double(...) +#define PARAM_ENUM_CHECK_bool(...) +#define PARAM_ENUM_CHECK_enum PARAM_ENUM_CHECK_SINGLE +#define PARAM_ENUM_CHECK_enum2 PARAM_ENUM_CHECK_SINGLE + +#define PARAM_ENUM_CHECK_enum_(_1,_name,_i,_elem) \ + case BOOST_PP_CAT(_name,_elem): break; + +#define PARAM_ENUM_CHECK_(_1,_args,_param) \ + PARAM_TYPED(PARAM_ENUM_CHECK_,_param)(BOOST_PP_TUPLE_ELEM(0,_args),\ + BOOST_PP_TUPLE_ELEM(1,_args),\ + _param) + +#define PARAM_ENUM_CHECK_SINGLE(_src,_default,_param) \ + switch(_src(_param)) {\ + BOOST_PP_SEQ_FOR_EACH_I(PARAM_ENUM_CHECK_enum_,\ + PARAM_FNAME(_param),PARAM_FSEQ(_param))\ + default: \ + _default(_param);\ + } + +/* Validate enum type parameters + * + * This macro validates the value a variable of enum type parameters. See + * similar macro #PARAM_ENUM_CONVERT for detail usage. + * + * \ingroup ParamEnumHelper + * + * \arg \c _src: Macro to generate source variable. The signature must be + * _src(_param)<\tt>, where \c _param is the tuple defining the parameter. + * You pass any of the \ref ParamAccessor "parameter accessors" to directly + * access the field. Or, supply your own macro to append any prefix as you + * like. + * + * \arg \c _default: A macro to call for invalid value. Signature should be + * _default(_param)<\tt>, where \c _param is the parameter definition. You + * can use #PARAM_ENUM_EXCEPT to throw Base::ValueError exception in FreeCAD + * + * \arg \c _seq: Parameter sequence + */ +#define PARAM_ENUM_CHECK(_src,_default,_seq) \ + BOOST_PP_SEQ_FOR_EACH(PARAM_ENUM_CHECK_,(_src,_default),_seq) + + +#define PARAM_ENUM_STRING_DECLARE_short(...) +#define PARAM_ENUM_STRING_DECLARE_long(...) +#define PARAM_ENUM_STRING_DECLARE_double(...) +#define PARAM_ENUM_STRING_DECLARE_bool(...) +#define PARAM_ENUM_STRING_DECLARE_enum2 PARAM_ENUM_STRING_DECLARE_enum + +/** Helper for #PARAM_ENUM_STRING_DECLARE */ +#define PARAM_ENUM_STRING_DECLARE_enum(_prefix,_param) \ + BOOST_PP_CAT(_prefix,PARAM_FNAME(_param))[] = {PARAM_FSEQ_STR(_param),NULL}; + +/** Helper for #PARAM_ENUM_STRING_DECLARE */ +#define PARAM_ENUM_STRING_DECLARE_(_1,_prefix,_param) \ + PARAM_TYPED(PARAM_ENUM_STRING_DECLARE_,_param)(_prefix,_param) + +/** Make \c enum string list + * + * Roughly translated: + * \code{.unparsed} + * _prefix##_name1[] = {#seq1[0], #seq1[1], ...,NULL}; + * _prefix##_name2[] = {#seq2[0], #seq2[1], ...,NULL}; + * ... + * \endcode + * Example usage: + * PARAM_ENUM_STRING_DECLARE(static const char *Enum, MyParamsSeq) + * \ingroup ParamEnumHelper + */ +#define PARAM_ENUM_STRING_DECLARE(_prefix,_seq) \ + BOOST_PP_SEQ_FOR_EACH(PARAM_ENUM_STRING_DECLARE_,_prefix,_seq) + + +/** Helper for #PARAM_INIT */ +#define PARAM_INIT_(_,_src,_i,_param) \ + BOOST_PP_COMMA_IF(_i) _src(_param)(PARAM_FDEF(_param)) + +/** Constructor initialization + * + * \arg \c _src: macro to generate source field. See \ref ParamSrc "here" for + * more details + * + * Expand to, + * \code{.unparsed} + * _src(_param1)(def1), _src(_param1)(def2)... + * \endcode + * \ingroup ParamCommon + */ +#define PARAM_INIT(_src,_seq) \ + BOOST_PP_SEQ_FOR_EACH_I(PARAM_INIT_,_src,_seq) + + +/** Helper for #PARAM_OP */ +#define PARAM_OP_(_,_args,_param) \ + BOOST_PP_TUPLE_ELEM(0,_args)(_param) BOOST_PP_TUPLE_ELEM(1,_args) \ + BOOST_PP_TUPLE_ELEM(2,_args)(_param); + +/** Perform operation on two instance of each parameter in a sequence + * + * \arg \c _src: Macro to generate source variable. The signature must be + * _src(_param)<\tt>, where \c _param is the tuple defining the parameter. + * You pass any of the \ref ParamAccessor "parameter accessors" to directly + * access the field. Or, supply your own macro to append any prefix as you + * like. + * \arg \c _op: a boolean operator + * \arg \c _dst: Same as \c _src above. + * + * Expands to: + * \code{.unparsed} + * _src(_param1) _op _src(_param2); + * \endcode + * + * \ingroup ParamCommon + */ +#define PARAM_OP(_src,_op,_dst,_seq) \ + BOOST_PP_SEQ_FOR_EACH(PARAM_COPY_,(_src,_op,_dst),_seq) + + +/** Helper for #PARAM_ARGS_DEF */ +#define PARAM_ARGS_DEF_(_,_src,_i,_param) \ + BOOST_PP_COMMA_IF(_i) PARAM_TYPE(_param) _src(_param)=PARAM_FDEF(_param) + +/** Delcare the parameters as function argument list with defaults. + * + * \arg \c _src: macro to generate source field. See \ref ParamSrc "here" for + * more details + * + * Expand to: + * \code{.unparsed} + * type1 _src(_param1)=def1, type2 _src(_param1)=def2 ... + * \endcode + * \ingroup ParamCommon + */ +#define PARAM_ARGS_DEF(_src,_seq) \ + BOOST_PP_SEQ_FOR_EACH_I(PARAM_ARGS_DEF_,_src,_seq) + + +/** Helper for #PARAM_ARGS */ +#define PARAM_ARGS_(_,_src,_i,_param) \ + BOOST_PP_COMMA_IF(_i) PARAM_TYPE(_param) _src(_param) + +/** Delcare the parameters as function argument list without defaults. + * + * \arg \c _src: macro to generate source field. See \ref ParamSrc "here" for + * more details + * + * Expand to: + * \code{.unparsed} + * type1 _src(_param1), type2 _src(_param2) ... + * \endcode + * \ingroup ParamCommon + */ +#define PARAM_ARGS(_src,_seq) \ + BOOST_PP_SEQ_FOR_EACH_I(PARAM_ARGS_,_src,_seq) + + +/** \defgroup ParamPy Python helper + * Helper macros for Python bindings + * \ingroup ParamHelper + */ + +/** \defgroup ParamDoc Python doc helper + * Generate argument doc string for Python + * \ingroup ParamPy + */ + +/** Helper for #PARAM_PY_DOC_enum */ +#define PARAM_PY_DOC_enum_(_i,_elem) \ + BOOST_PP_IF(_i,","," ") #_i "=" #_elem + +/** Generate doc for an enum parameter */ +#define PARAM_PY_DOC_enum(_field,_param) \ + "\n* " PARAM_FIELD_STR(_field,_param) "(" PARAM_FDEF_STR(_param)"):" \ + PARAM_FOREACH_I(PARAM_PY_DOC_enum_, PARAM_FSEQ(_param)) ". " \ + PARAM_FDOC(_param) "\n" + +/* Generate doc for other type of parameter */ +#define PARAM_PY_DOC_short(_field,_param) \ + "\n* " PARAM_FIELD_STR(_field,_param) "(" PARAM_FDEF_STR(_param)"): " \ + PARAM_FDOC(_param) "\n" +#define PARAM_PY_DOC_long PARAM_PY_DOC_short +#define PARAM_PY_DOC_double PARAM_PY_DOC_short +#define PARAM_PY_DOC_bool PARAM_PY_DOC_short +#define PARAM_PY_DOC_enum2 PARAM_PY_DOC_enum + +#define PARAM_PY_DOC_(_,_field,_param) \ + PARAM_TYPED(PARAM_PY_DOC_,_param)(_field,_param) + +/* Generate document of a sequence of parameters + * \ingroup ParamDoc + */ +#define PARAM_PY_DOC(_field,_seq) \ + BOOST_PP_SEQ_FOR_EACH(PARAM_PY_DOC_,_field,_seq) + + +/** Helper for #PARAM_PY_ARGS_DOC */ +#define PARAM_PY_ARGS_DOC_(_,_field,_i,_param) \ + BOOST_PP_IF(_i,", "," ") PARAM_FIELD_STR(_field,_param) "=" PARAM_FDEF_STR(_param) + +/** Generate argument list string + * \arg \c _field: specifies the \ref ParamField "field" to use as name + * + * Expand to a single string: + * \code{.unparsed} + * "_field1=_def1,_field2=_def2 ..." + * \endcode + * + * \ingroup ParamDoc + */ +#define PARAM_PY_ARGS_DOC(_field,_seq) \ + BOOST_PP_SEQ_FOR_EACH_I(PARAM_PY_ARGS_DOC_,_field,_seq) + + +/** Helper for #PARAM_FIELDS */ +#define PARAM_FIELDS_(_1,_src,_i,_param) \ + BOOST_PP_COMMA_IF(_i) _src(_param) + +/** Expand to a list of the given field in the parameter sequence + * + * \arg \c _src: macro to generate source field. See \ref ParamSrc "here" for + * more details + * + * For example, PARAM_FIELDS(PARAM_FARG, _seq) expands to: + * \code{.unparsed} + * arg1,arg2 ... + * \endcode + * \ingroup ParamCommon ParamPy + */ +#define PARAM_FIELDS(_src,_seq) \ + BOOST_PP_SEQ_FOR_EACH_I(PARAM_FIELDS_,_src,_seq) + + +#define PARAM_PY_CAST_short(_v) (_v) +#define PARAM_PY_CAST_long(_v) (_v) +#define PARAM_PY_CAST_double(_v) (_v) +#define PARAM_PY_CAST_bool(_v) ((_v)?Py_True:Py_False) +#define PARAM_PY_CAST_enum(_v) (_v) +#define PARAM_PY_CAST_enum2(_v) (_v) + +#define PARAM_CAST_PY_short(_v) (_v) +#define PARAM_CAST_PY_long(_v) (_v) +#define PARAM_CAST_PY_double(_v) (_v) +#define PARAM_CAST_PY_bool(_v) (PyObject_IsTrue(_v)?true:false) +#define PARAM_CAST_PY_enum(_v) (_v) +#define PARAM_CAST_PY_enum2(_v) (_v) + + +/** Helper for #PARAM_PY_FIELDS */ +#define PARAM_PY_FIELDS_(_1,_src,_i,_param) \ + BOOST_PP_COMMA_IF(_i) PARAM_TYPED(PARAM_CAST_PY_,_param)(_src(_param)) + +/** Expand to a comma separated list of the given field in the sequence + * + * \arg \c _src: macro to generate source field. See \ref ParamSrc "here" for + * more details + * + * The field will be casted from python C to C type + * \ingroup ParamCommon ParamPy + */ +#define PARAM_PY_FIELDS(_src,_seq) \ + BOOST_PP_SEQ_FOR_EACH_I(PARAM_PY_FIELDS_,_src,_seq) + + +/** Helper for #PARAM_FIELD_STRINGS */ +#define PARAM_FIELD_STRINGS_(_1,_field,_i,_param) \ + BOOST_PP_COMMA_IF(_i) PARAM_FIELD_STR(_field,_param) + +/** Expand to a list of stringified fields + * \ingroup ParamStringizer ParamPy + */ +#define PARAM_FIELD_STRINGS(_field,_seq) \ + BOOST_PP_SEQ_FOR_EACH_I(PARAM_FIELD_STRINGS_,_field,_seq) + + +#define PARAM_PYARG_short "h" +#define PARAM_PYARG_long "l" +#define PARAM_PYARG_double "d" +#define PARAM_PYARG_bool "O" +#define PARAM_PYARG_enum "h" +#define PARAM_PYARG_enum2 "h" + +/** Helper for #PARAM_PY_KWDS */ +#define PARAM_PY_KWDS_(_param) \ + PARAM_TYPED(PARAM_PYARG_,_param) + +/** Generate a format string for kewords based argument + * \ingroup ParamPy + */ +#define PARAM_PY_KWDS(_seq) \ + PARAM_FOREACH(PARAM_PY_KWDS_,_seq) + +#define PARAM_PY_TYPE_short short +#define PARAM_PY_TYPE_long long +#define PARAM_PY_TYPE_double double +#define PARAM_PY_TYPE_bool PyObject* +#define PARAM_PY_TYPE_enum short +#define PARAM_PY_TYPE_enum2 short + +/** Helper for #PARAM_PY_DECLARE */ +#define PARAM_PY_DECLARE_(_1,_src,_param) \ + PARAM_TYPED(PARAM_PY_TYPE_,_param) _src(_param); + +/** Declare field variables for Python C type without initialization + * \ingroup ParamPy + */ +#define PARAM_PY_DECLARE(_src,_seq) \ + BOOST_PP_SEQ_FOR_EACH(PARAM_PY_DECLARE_,_src,_seq) + +#define PARAM_PY_INIT_short(_v) _v +#define PARAM_PY_INIT_long(_v) _v +#define PARAM_PY_INIT_double(_v) _v +#define PARAM_PY_INIT_bool(_v) ((_v)?Py_True:Py_False) +#define PARAM_PY_INIT_enum(_v) _v +#define PARAM_PY_INIT_enum2(_v) _v + +/** Helper for #PARAM_PY_DECLARE_INIT */ +#define PARAM_PY_DECLARE_INIT_(_1,_src,_param) \ + PARAM_TYPED(PARAM_PY_TYPE_,_param) _src(_param) = \ + PARAM_TYPED(PARAM_PY_INIT_,_param)(PARAM_FDEF(_param)); + +/** Declare field variables of Python c type with initialization to default + * \ingroup ParamPy + */ +#define PARAM_PY_DECLARE_INIT(_src,_seq) \ + BOOST_PP_SEQ_FOR_EACH(PARAM_PY_DECLARE_INIT_,_src,_seq) + + +/** Helper for #PARAM_REF */ +#define PARAM_REF_(_1,_src,_i,_param) \ + BOOST_PP_COMMA_IF(_i) &_src(_param) + +/** Generate a list of field references + * + * \arg \c _src: macro to generate source field. See \ref ParamSrc "here" for + * + * more details + * Expand to: + * \code{.unparsed} + * &_src(_param1), &_src(_param1) ... + * \endcode + * \ingroup ParamPy + */ +#define PARAM_REF(_src,_seq) \ + BOOST_PP_SEQ_FOR_EACH_I(PARAM_REF_,_src,_seq) + + +#define PARAM_CAST_PYOBJ_short(_v) PyInt_FromLong(_v) +#define PARAM_CAST_PYOBJ_long(_v) PyInt_FromLong(_v) +#define PARAM_CAST_PYOBJ_double(_v) PyFloat_FromDouble(_v) +#define PARAM_CAST_PYOBJ_bool(_v) ((_v)?Py_True:Py_False) +#define PARAM_CAST_PYOBJ_enum PARAM_CAST_PYOBJ_short +#define PARAM_CAST_PYOBJ_enum2 PARAM_CAST_PYOBJ_short + + +/** Stringize field to a Python string + * \ingroup ParamPy ParamStringizer + */ +#define PARAM_PY_STR(_field,_param) \ + PyString_FromString(PARAM_FIELD_STR(_field,_param)) + +/** Helper for #PARAM_PY_DICT_SET_VALUE */ +#define PARAM_PY_DICT_SET_VALUE_(_1,_args,_param) \ + PyDict_SetItem(BOOST_PP_TUPLE_ELEM(0,_args), \ + PARAM_PY_STR(BOOST_PP_TUPLE_ELEM(1,_args),_param),\ + PARAM_TYPED(PARAM_CAST_PYOBJ_,_param)(\ + BOOST_PP_TUPLE_ELEM(2,_args)(_param))); + +/** Populate a Python dict with a structure variable + * + * \arg \c _dict: the Python dictionary object + * \arg \c _field: specifies the \ref ParamField "field" to use as key + * \arg \c _src: macro to generate source field. See \ref ParamSrc "here" for + * more details + * + * Roughly translated to: + * \code{.unparsed} + * PyDict_SetItem(_dict,#_field1,_src(_param)); + * PyDict_SetItem(_dict,#_field2,_src(_param)); + * ... + * \endcode + * \ingroup ParamPy + */ +#define PARAM_PY_DICT_SET_VALUE(_dict,_field,_src,_seq) \ + BOOST_PP_SEQ_FOR_EACH(PARAM_PY_DICT_SET_VALUE_,(_dict,_field,_src),_seq) + + +#define PARAM_PY_DICT_DOC_enum_(_i,_elem) \ + BOOST_PP_IF(_i,","," ") #_i "=" #_elem + +/** Generate doc for an enum parameter */ +#define PARAM_PY_DICT_DOC_enum(_param) \ + "(" PARAM_FDEF_STR(_param) ") - " \ + PARAM_FOREACH_I(PARAM_PY_DOC_enum_, PARAM_FSEQ(_param)) ".\n" \ + PARAM_FDOC(_param) "\n" + +/* Generate doc for other type of parameter */ +#define PARAM_PY_DICT_DOC_(_param) \ + "(" PARAM_FDEF_STR(_param) ") - " PARAM_FDOC(_param) "\n" + + +#define PARAM_PY_DICT_DOC_short PARAM_PY_DICT_DOC_ +#define PARAM_PY_DICT_DOC_long PARAM_PY_DICT_DOC_ +#define PARAM_PY_DICT_DOC_double PARAM_PY_DICT_DOC_ +#define PARAM_PY_DICT_DOC_bool PARAM_PY_DICT_DOC_ +#define PARAM_PY_DICT_DOC_enum2 PARAM_PY_DICT_DOC_enum + +/** Helper for #PARAM_PY_DICT_SET_DOC */ +#define PARAM_PY_DICT_SET_DOC_(_1,_args,_param) \ + PyDict_SetItem(BOOST_PP_TUPLE_ELEM(0,_args), \ + PARAM_PY_STR(BOOST_PP_TUPLE_ELEM(1,_args),_param),\ + PyString_FromString(PARAM_TYPED(PARAM_PY_DICT_DOC_,_param)(_param))); + +/** Populate a Python dict with the doc field of the parameter sequence + * + * \arg \c _dict: the Python dictionary object + * \arg \c _field: specifies the \ref ParamField "field" to use as key + * + * Roughly translated to: + * \code{.unparsed} + * PyDict_SetItem(_dict,#_field1,doc1); + * PyDict_SetItem(_dict,#_field1,doc2); + * ... + * \endcode + * \ingroup ParamDoc + */ +#define PARAM_PY_DICT_SET_DOC(_dict,_field,_seq) \ + BOOST_PP_SEQ_FOR_EACH(PARAM_PY_DICT_SET_DOC_,(_dict,_field),_seq) + + +/** \defgroup ParamProperty Property Macros + * Helper macros for FreeCAD properties + * \ingroup ParamHelper + * @{*/ +#define PARAM_PROP_bool(_v) App::PropertyBool _v +#define PARAM_PROP_double(_v) App::PropertyFloat _v +#define PARAM_PROP_short(_v) App::PropertyInteger _v +#define PARAM_PROP_long(_v) App::PropertyInteger _v +#define PARAM_PROP_enum(_v) App::PropertyEnumeration _v +#define PARAM_PROP_enum2(_v) App::PropertyEnumeration _v +/** @} */ + +/** Helper for #PARAM_PROP_DECLARE */ +#define PARAM_PROP_DECLARE_(_param) \ + PARAM_TYPED(PARAM_PROP_,_param)(PARAM_FNAME(_param)); + +/** Declare FreeCAD properties + * \ingroup ParamProperty + */ +#define PARAM_PROP_DECLARE(_seq) \ + PARAM_FOREACH(PARAM_PROP_DECLARE_,_seq) + +/** Replace FreeCAD #ADD_PROPERTY_TYPE to fix singifying macro */ +#define PARAM_ADD_PROPERTY_TYPE(_prop_, _defaultval_, _group_,_type_,_Docu_) \ + do { \ + this->_prop_.setValue _defaultval_;\ + this->_prop_.setContainer(this); \ + propertyData.addProperty(static_cast(this), BOOST_PP_STRINGIZE(_prop_), &this->_prop_, (_group_),(_type_),(_Docu_)); \ + } while (0) + +/** Generic property adding */ +#define PARAM_PROP_ADD_(_group,_param) \ + PARAM_ADD_PROPERTY_TYPE(PARAM_FNAME(_param), (PARAM_FDEF(_param)),\ + _group,App::Prop_None,PARAM_FDOC(_param)); + +#define PARAM_PROP_ADD_short PARAM_PROP_ADD_ +#define PARAM_PROP_ADD_long PARAM_PROP_ADD_ +#define PARAM_PROP_ADD_double PARAM_PROP_ADD_ +#define PARAM_PROP_ADD_bool PARAM_PROP_ADD_ +#define PARAM_PROP_ADD_enum2 PARAM_PROP_ADD_enum + +/** Add \c enum type parameter as property */ +#define PARAM_PROP_ADD_enum(_group,_param) \ + PARAM_ADD_PROPERTY_TYPE(PARAM_FNAME(_param), ((long)PARAM_FDEF(_param)),\ + _group,App::Prop_None,PARAM_FDOC(_param)); + +/** Helper for #PARAM_PROP_ADD */ +#define PARAM_PROP_ADD_TYPED(_1,_group,_i,_param) \ + PARAM_TYPED(PARAM_PROP_ADD_,_param)(_group,_param) + +/** Add FreeCAD properties + * \ingroup ParamProperty + */ +#define PARAM_PROP_ADD(_group,_seq) \ + BOOST_PP_SEQ_FOR_EACH_I(PARAM_PROP_ADD_TYPED,_group,_seq) + +#define PARAM_PROP_SET_ENUM_short(...) +#define PARAM_PROP_SET_ENUM_long(...) +#define PARAM_PROP_SET_ENUM_bool(...) +#define PARAM_PROP_SET_ENUM_double(...) +#define PARAM_PROP_SET_ENUM_enum2 PARAM_PROP_SET_ENUM_enum + +/** Setup \c enum type parameter */ +#define PARAM_PROP_SET_ENUM_enum(_prefix,_param) \ + PARAM_FNAME(_param).setEnums(BOOST_PP_CAT(_prefix,PARAM_FNAME(_param))); + +/** Helper for #PARAM_PROP_SET_ENUM */ +#define PARAM_PROP_SET_ENUM_TYPED(_1,_prefix,_param) \ + PARAM_TYPED(PARAM_PROP_SET_ENUM_,_param)(_prefix,_param) + +/* Setup the \c enum string list for \c enum type properties + * \ingroup ParamProperty + */ +#define PARAM_PROP_SET_ENUM(_prefix,_seq) \ + BOOST_PP_SEQ_FOR_EACH(PARAM_PROP_SET_ENUM_TYPED,_prefix,_seq) + + +/** Helper for #PARAM_PROP_ARGS */ +#define PARAM_PROP_ARGS_(_i,_param) \ + BOOST_PP_COMMA_IF(_i) PARAM_FNAME(_param).getValue() + +/** Expand the property list as function arguments + * + * Expand to: + * \code{.unparsed} + * name1.getValue(), name2.getValue() ... + * \endcode + * \ingroup ParamProperty + */ +#define PARAM_PROP_ARGS(_seq) \ + PARAM_FOREACH_I(PARAM_PROP_ARGS_,_seq) + + +/** Helper for #PARAM_PROP_TOUCHED */ +#define PARAM_PROP_TOUCHED_(_param) \ + if(PARAM_FNAME(_param).isTouched()) return 1; + +/** Returns 1 if any properties is touched + * + * Expand to: + * \code{.unparsed} + * if(name1.isTouched()) return 1; + * if(name2.isTouched()) return 1; + * ... + * \ingroup ParamProperty + */ +#define PARAM_PROP_TOUCHED(_seq) \ + PARAM_FOREACH(PARAM_PROP_TOUCHED_,_seq) + +#endif // PARAMS_HELPER_H diff --git a/src/Mod/Path/App/Path.h b/src/Mod/Path/App/Path.h index db7643f16e..7ce9e602a1 100644 --- a/src/Mod/Path/App/Path.h +++ b/src/Mod/Path/App/Path.h @@ -23,10 +23,10 @@ #ifndef PATH_Path_H #define PATH_Path_H - -#include "Command.h" + +#include "Command.h" //#include "Mod/Robot/App/kdl_cp/path_composite.hpp" -//#include "Mod/Robot/App/kdl_cp/frames_io.hpp" +//#include "Mod/Robot/App/kdl_cp/frames_io.hpp" #include #include diff --git a/src/Mod/Path/Gui/AppPathGui.cpp b/src/Mod/Path/Gui/AppPathGui.cpp index 714e1595e1..c56481e056 100644 --- a/src/Mod/Path/Gui/AppPathGui.cpp +++ b/src/Mod/Path/Gui/AppPathGui.cpp @@ -35,6 +35,7 @@ #include "DlgSettingsPathColor.h" #include "ViewProviderPathCompound.h" #include "ViewProviderPathShape.h" +#include "ViewProviderArea.h" // use a different name to CreateCommand() void CreatePathCommands(void); @@ -77,6 +78,10 @@ PyMOD_INIT_FUNC(PathGui) PathGui::ViewProviderPathCompoundPython ::init(); PathGui::ViewProviderPathShape ::init(); 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/CMakeLists.txt b/src/Mod/Path/Gui/CMakeLists.txt index 8f90c3e413..2be21706d1 100644 --- a/src/Mod/Path/Gui/CMakeLists.txt +++ b/src/Mod/Path/Gui/CMakeLists.txt @@ -81,6 +81,8 @@ SET(PathGui_SRCS_ViewProvider ViewProviderPathCompound.h ViewProviderPathShape.cpp ViewProviderPathShape.h + ViewProviderArea.cpp + ViewProviderArea.h ) SOURCE_GROUP("ViewProvider" FILES ${PathGui_SRCS_ViewProvider}) diff --git a/src/Mod/Path/Gui/Command.cpp b/src/Mod/Path/Gui/Command.cpp index 9dc8085f9b..2f7a125b9b 100644 --- a/src/Mod/Path/Gui/Command.cpp +++ b/src/Mod/Path/Gui/Command.cpp @@ -25,6 +25,8 @@ #ifndef _PreComp_ #endif +#include + #include #include #include @@ -39,6 +41,183 @@ #include #include #include +#include + + +// Path Area ##################################################################################################### + + +DEF_STD_CMD_A(CmdPathArea) + +CmdPathArea::CmdPathArea() + :Command("Path_Area") +{ + sAppModule = "Path"; + sGroup = QT_TR_NOOP("Path"); + sMenuText = QT_TR_NOOP("Area"); + sToolTipText = QT_TR_NOOP("Creates a feature area from selected objects"); + sWhatsThis = "Path_Area"; + sStatusTip = sToolTipText; + sPixmap = "Path-Area"; + sAccel = "P,A"; + +} + +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")) { + Base::Console().Error("Selected shape is not 2D\n"); + return; + } + + 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 << ","; + } + } + 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()); + doCommand(Doc,"FreeCAD.activeDocument().%s.Sources = [ %s ]",FeatName.c_str(),sources.str().c_str()); + commitCommand(); + updateActive(); +} + +bool CmdPathArea::isActive(void) +{ + return hasActiveDocument(); +} + + +DEF_STD_CMD_A(CmdPathAreaWorkplane) + +CmdPathAreaWorkplane::CmdPathAreaWorkplane() + :Command("Path_Area_Workplane") +{ + sAppModule = "Path"; + sGroup = QT_TR_NOOP("Path"); + sMenuText = QT_TR_NOOP("Area workplane"); + sToolTipText = QT_TR_NOOP("Select a workplane for a FeatureArea"); + sWhatsThis = "Path_Area_Workplane"; + sStatusTip = sToolTipText; + sPixmap = "Path-Area-Workplane"; + sAccel = "P,W"; + +} + +void CmdPathAreaWorkplane::activated(int iMsg) +{ + Q_UNUSED(iMsg); + + std::string areaName; + std::string planeSubname; + std::string planeName; + + for(Gui::SelectionObject &selObj : + getSelection().getSelectionEx(NULL, Part::Feature::getClassTypeId())) + { + const std::vector &subnames = selObj.getSubNames(); + if(subnames.size()>1) { + Base::Console().Error("Please select one sub shape object for plane only\n"); + return; + } + const Part::Feature *pcObj = static_cast(selObj.getObject()); + if(subnames.empty()) { + if(pcObj->getTypeId().isDerivedFrom(Path::FeatureArea::getClassTypeId())) { + if(areaName.size()){ + Base::Console().Error("Please select one FeatureArea only\n"); + return; + } + areaName = pcObj->getNameInDocument(); + continue; + } + for (TopExp_Explorer it(pcObj->Shape.getShape().getShape(), TopAbs_SHELL); it.More(); it.Next()) { + Base::Console().Error("Selected shape is not 2D\n"); + return; + } + } + if(planeName.size()){ + Base::Console().Error("Please select one shape object for plane only\n"); + return; + }else{ + planeSubname = planeName = pcObj->getNameInDocument(); + planeSubname += ".Shape"; + } + + for(const std::string &name : subnames) { + if(name.compare(0,4,"Face") && name.compare(0,4,"Edge")) { + Base::Console().Error("Selected shape is not 2D\n"); + return; + } + std::ostringstream subname; + subname << planeSubname << ",'" << name << "','Wires'"; + planeSubname = subname.str(); + } + } + if(areaName.empty()) { + Base::Console().Error("Please select one FeatureArea\n"); + return; + } + if(planeName.empty()) { + Base::Console().Error("Please select one shape object\n"); + return; + } + + openCommand("Select Workplane for Path Area"); + 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()); + commitCommand(); + updateActive(); +} + +bool CmdPathAreaWorkplane::isActive(void) +{ + return !getSelection().getSelectionEx(NULL, Path::FeatureArea::getClassTypeId()).empty(); +} // Path compound ##################################################################################################### @@ -95,7 +274,6 @@ bool CmdPathCompound::isActive(void) return hasActiveDocument(); } - // Path Shape ##################################################################################################### @@ -117,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) @@ -149,4 +351,6 @@ void CreatePathCommands(void) Gui::CommandManager &rcCmdMgr = Gui::Application::Instance->commandManager(); rcCmdMgr.addCommand(new CmdPathCompound()); rcCmdMgr.addCommand(new CmdPathShape()); + rcCmdMgr.addCommand(new CmdPathArea()); + rcCmdMgr.addCommand(new CmdPathAreaWorkplane()); } diff --git a/src/Mod/Path/Gui/Resources/Path.qrc b/src/Mod/Path/Gui/Resources/Path.qrc index b6800333c6..8600ec94e9 100644 --- a/src/Mod/Path/Gui/Resources/Path.qrc +++ b/src/Mod/Path/Gui/Resources/Path.qrc @@ -46,6 +46,9 @@ icons/Path-ToolChange.svg 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 panels/DlgJobChooser.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 0000000000..504106dcb5 --- /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/Resources/icons/Path-Area-Workplane.svg b/src/Mod/Path/Gui/Resources/icons/Path-Area-Workplane.svg new file mode 100644 index 0000000000..1498ef33fe --- /dev/null +++ b/src/Mod/Path/Gui/Resources/icons/Path-Area-Workplane.svg @@ -0,0 +1,676 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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/Resources/icons/Path-Area.svg b/src/Mod/Path/Gui/Resources/icons/Path-Area.svg new file mode 100644 index 0000000000..a620c23b26 --- /dev/null +++ b/src/Mod/Path/Gui/Resources/icons/Path-Area.svg @@ -0,0 +1,648 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 new file mode 100644 index 0000000000..040bacbea9 --- /dev/null +++ b/src/Mod/Path/Gui/ViewProviderArea.cpp @@ -0,0 +1,197 @@ +/**************************************************************************** + * Copyright (c) 2017 Zheng, Lei (realthunder) * + * * + * 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 * + * * + ****************************************************************************/ + +#include "PreCompiled.h" + +#ifndef _PreComp_ +#endif + +#include +#include +#include "ViewProviderArea.h" + +using namespace PathGui; + +PROPERTY_SOURCE(PathGui::ViewProviderArea, PartGui::ViewProviderPlaneParametric) + +ViewProviderArea::ViewProviderArea() +{ + sPixmap = "Path-Area.svg"; +} + +ViewProviderArea::~ViewProviderArea() +{ +} + +std::vector ViewProviderArea::claimChildren(void) const +{ + return std::vector( + static_cast(getObject())->Sources.getValues()); +} + +bool ViewProviderArea::canDragObjects() const +{ + return true; +} + +bool ViewProviderArea::canDragObject(App::DocumentObject* obj) const +{ + return obj && obj->getTypeId().isDerivedFrom(Part::Feature::getClassTypeId()); +} + +void ViewProviderArea::dragObject(App::DocumentObject* obj) +{ + Path::FeatureArea* area = static_cast(getObject()); + std::vector sources = area->Sources.getValues(); + for (std::vector::iterator it = sources.begin(); it != sources.end(); ++it) { + if (*it == obj) { + sources.erase(it); + area->Sources.setValues(sources); + break; + } + } +} + +bool ViewProviderArea::canDropObjects() const +{ + return true; +} + +bool ViewProviderArea::canDropObject(App::DocumentObject* obj) const +{ + return canDragObject(obj); +} + +void ViewProviderArea::dropObject(App::DocumentObject* obj) +{ + Path::FeatureArea* area = static_cast(getObject()); + std::vector sources = area->Sources.getValues(); + sources.push_back(obj); + area->Sources.setValues(sources); +} + +void ViewProviderArea::updateData(const App::Property* prop) +{ + PartGui::ViewProviderPart::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 ViewProviderArea::onDelete(const std::vector &) +{ + // get the input shapes + Path::FeatureArea* area = static_cast(getObject()); + std::vector pShapes =area->Sources.getValues(); + for (std::vector::iterator it = pShapes.begin(); it != pShapes.end(); ++it) { + if (*it) + Gui::Application::Instance->showViewProvider(*it); + } + return true; +} + +// 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 new file mode 100644 index 0000000000..f149d856b0 --- /dev/null +++ b/src/Mod/Path/Gui/ViewProviderArea.h @@ -0,0 +1,83 @@ +/**************************************************************************** + * Copyright (c) 2017 Zheng, Lei (realthunder) * + * * + * 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_ViewProviderArea_H +#define PATH_ViewProviderArea_H + +#include +#include + +namespace PathGui +{ + +class PathGuiExport ViewProviderArea : public PartGui::ViewProviderPlaneParametric +{ + PROPERTY_HEADER(PathGui::ViewProviderArea); + +public: + ViewProviderArea(); + virtual ~ViewProviderArea(); + + /// 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*); +}; + +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 + + +#endif // PATH_ViewProviderArea_H diff --git a/src/Mod/Path/Gui/ViewProviderPathShape.cpp b/src/Mod/Path/Gui/ViewProviderPathShape.cpp index 52f4c71a08..81b21372eb 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 8e525dcf46..7aeb950835 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 dd0c1d6f3c..11a1120d19 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"] + 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 128f9b19a9..d837c98be2 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 f33e338c04..a4e5bf29d9 100644 --- a/src/Mod/Path/libarea/Area.cpp +++ b/src/Mod/Path/libarea/Area.cpp @@ -10,7 +10,11 @@ double CArea::m_accuracy = 0.01; double CArea::m_units = 1.0; +bool CArea::m_clipper_simple = false; +double CArea::m_clipper_clean_distance = 0.0; bool CArea::m_fit_arcs = true; +int CArea::m_min_arc_points = 4; +int CArea::m_max_arc_points = 100; double CArea::m_single_area_processing_length = 0.0; double CArea::m_processing_done = 0.0; bool CArea::m_please_abort = false; @@ -20,6 +24,24 @@ bool CArea::m_set_processing_length_in_split = false; double CArea::m_after_MakeOffsets_length = 0.0; //static const double PI = 3.1415926535897932; +#define _CAREA_PARAM_DEFINE(_class,_type,_name) \ + _type CArea::get_##_name() {return _class::_name;}\ + void CArea::set_##_name(_type _name) {_class::_name = _name;} + +#define CAREA_PARAM_DEFINE(_type,_name) \ + _type CArea::get_##_name() {return m_##_name;}\ + void CArea::set_##_name(_type _name) {m_##_name = _name;} + +_CAREA_PARAM_DEFINE(Point,double,tolerance); +CAREA_PARAM_DEFINE(bool,fit_arcs) +CAREA_PARAM_DEFINE(bool,clipper_simple); +CAREA_PARAM_DEFINE(double,clipper_clean_distance); +CAREA_PARAM_DEFINE(double,accuracy); +CAREA_PARAM_DEFINE(double,units); +CAREA_PARAM_DEFINE(short,min_arc_points); +CAREA_PARAM_DEFINE(short,max_arc_points); +CAREA_PARAM_DEFINE(double,clipper_scale); + void CArea::append(const CCurve& curve) { m_curves.push_back(curve); @@ -51,6 +73,75 @@ Point CArea::NearestPoint(const Point& p)const return best_point; } +void CArea::ChangeStartToNearest(const Point *point, double min_dist) +{ + for(std::list::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; + double dist; + if(min_dist>Point::tolerance && !curve.IsClosed()) { + double d1 = curve.m_vertices.front().m_p.dist(p); + double d2 = curve.m_vertices.back().m_p.dist(p); + if(d1IsClosed()) { + ItBest->ChangeStart(best_point); + }else{ + double dfront = ItBest->m_vertices.front().m_p.dist(best_point); + double dback = ItBest->m_vertices.back().m_p.dist(best_point); + if(min_dist>Point::tolerance && dfront>min_dist && dback>min_dist) { + ItBest->Break(best_point); + m_curves.push_back(*ItBest); + m_curves.back().ChangeEnd(best_point); + ItBest->ChangeStart(best_point); + }else if(dfront>dback) + ItBest->Reverse(); + } + 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++) @@ -70,17 +161,22 @@ void CArea::Reorder() // returns 1, if the curves are overlapping CAreaOrderer ao; - for(std::list::iterator It = m_curves.begin(); It != m_curves.end(); It++) + for(std::list::iterator It = m_curves.begin(), ItNext=It; It != m_curves.end(); It=ItNext) { + ++ItNext; CCurve& curve = *It; - ao.Insert(&curve); + if(!It->IsClosed()) + continue; + ao.Insert(make_shared(curve)); if(m_set_processing_length_in_split) { CArea::m_processing_done += (m_split_processing_length / m_curves.size()); } + m_curves.erase(It); } - *this = ao.ResultArea(); + if(ao.m_top_level) + ao.m_top_level->GetArea(*this); } class ZigZag diff --git a/src/Mod/Path/libarea/Area.h b/src/Mod/Path/libarea/Area.h index 23937196c1..88e8fca5cf 100644 --- a/src/Mod/Path/libarea/Area.h +++ b/src/Mod/Path/libarea/Area.h @@ -7,6 +7,7 @@ #define AREA_HEADER #include "Curve.h" +#include "clipper.hpp" enum PocketMode { @@ -42,7 +43,11 @@ public: std::list m_curves; static double m_accuracy; static double m_units; // 1.0 for mm, 25.4 for inches. All points are multiplied by this before going to the engine + static bool m_clipper_simple; + static double m_clipper_clean_distance; static bool m_fit_arcs; + static int m_min_arc_points; + static int m_max_arc_points; static double m_processing_done; // 0.0 to 100.0, set inside MakeOnePocketCurve static double m_single_area_processing_length; static double m_after_MakeOffsets_length; @@ -50,6 +55,7 @@ public: static double m_split_processing_length; static bool m_set_processing_length_in_split; static bool m_please_abort; // the user sets this from another thread, to tell MakeOnePocketCurve to finish with no result. + static double m_clipper_scale; void append(const CCurve& curve); void Subtract(const CArea& a2); @@ -58,6 +64,11 @@ public: static CArea UniteCurves(std::list &curves); void Xor(const CArea& a2); void Offset(double inwards_value); + void OffsetWithClipper(double offset, + ClipperLib::JoinType joinType=ClipperLib::jtRound, + ClipperLib::EndType endType=ClipperLib::etOpenRound, + double miterLimit = 5.0, + double roundPrecision = 0.0); void Thicken(double value); void FitArcs(); unsigned int num_curves(){return static_cast(m_curves.size());} @@ -73,6 +84,30 @@ public: void SpanIntersections(const Span& span, std::list &pts)const; void CurveIntersections(const CCurve& curve, std::list &pts)const; void InsideCurves(const CCurve& curve, std::list &curves_inside)const; + + void ChangeStartToNearest(const Point *pstart=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();\ + static void set_##_name(_type _name); + + CAREA_PARAM_DECLARE(double,tolerance); + CAREA_PARAM_DECLARE(bool,fit_arcs) + CAREA_PARAM_DECLARE(bool,clipper_simple); + CAREA_PARAM_DECLARE(double,clipper_clean_distance); + CAREA_PARAM_DECLARE(double,accuracy); + CAREA_PARAM_DECLARE(double,units); + CAREA_PARAM_DECLARE(short,min_arc_points); + CAREA_PARAM_DECLARE(short,max_arc_points); + CAREA_PARAM_DECLARE(double,clipper_scale); + + // Following functions is add to operate on possible open curves + void PopulateClipper(ClipperLib::Clipper &c, ClipperLib::PolyType type) const; + void Clip(ClipperLib::ClipType op, + const CArea *a, + ClipperLib::PolyFillType subjFillType = ClipperLib::pftEvenOdd, + ClipperLib::PolyFillType clipFillType = ClipperLib::pftEvenOdd); }; enum eOverlapType diff --git a/src/Mod/Path/libarea/AreaClipper.cpp b/src/Mod/Path/libarea/AreaClipper.cpp index ec5e63730d..a4f4f0e2e9 100644 --- a/src/Mod/Path/libarea/AreaClipper.cpp +++ b/src/Mod/Path/libarea/AreaClipper.cpp @@ -12,7 +12,7 @@ using namespace ClipperLib; bool CArea::HolesLinked(){ return false; } //static const double PI = 3.1415926535897932; -static double Clipper4Factor = 10000.0; +double CArea::m_clipper_scale = 10000.0; class DoubleAreaPoint { @@ -20,8 +20,8 @@ public: double X, Y; DoubleAreaPoint(double x, double y){X = x; Y = y;} - DoubleAreaPoint(const IntPoint& p){X = (double)(p.X) / Clipper4Factor; Y = (double)(p.Y) / Clipper4Factor;} - IntPoint int_point(){return IntPoint((long64)(X * Clipper4Factor), (long64)(Y * Clipper4Factor));} + DoubleAreaPoint(const IntPoint& p){X = (double)(p.X) / CArea::m_clipper_scale; Y = (double)(p.Y) / CArea::m_clipper_scale;} + IntPoint int_point(){return IntPoint((long64)(X * CArea::m_clipper_scale), (long64)(Y * CArea::m_clipper_scale));} }; static std::list pts_for_AddVertex; @@ -81,10 +81,10 @@ static void AddVertex(const CVertex& vertex, const CVertex* prev_vertex) else Segments=(int)ceil(-phit/dphi); - if (Segments < 1) - Segments=1; - if (Segments > 100) - Segments=100; + if (Segments < CArea::m_min_arc_points) + Segments = CArea::m_min_arc_points; + if (Segments > CArea::m_max_arc_points) + Segments=CArea::m_max_arc_points; dphi=phit/(Segments); @@ -139,6 +139,7 @@ static void MakeLoop(const DoubleAreaPoint &pt0, const DoubleAreaPoint &pt1, con static void OffsetWithLoops(const TPolyPolygon &pp, TPolyPolygon &pp_new, double inwards_value) { Clipper c; + c.StrictlySimple(CArea::m_clipper_simple); bool inwards = (inwards_value > 0); bool reverse = false; @@ -251,6 +252,7 @@ static void MakeObround(const Point &pt0, const CVertex &vt1, double radius) static void OffsetSpansWithObrounds(const CArea& area, TPolyPolygon &pp_new, double radius) { Clipper c; + c.StrictlySimple(CArea::m_clipper_simple); for(std::list::const_iterator It = area.m_curves.begin(); It != area.m_curves.end(); It++) @@ -296,48 +298,14 @@ static void OffsetSpansWithObrounds(const CArea& area, TPolyPolygon &pp_new, dou } } -static void MakePolyPoly( const CArea& area, TPolyPolygon &pp, bool reverse = true ){ - pp.clear(); - - for(std::list::const_iterator It = area.m_curves.begin(); It != area.m_curves.end(); It++) - { - pts_for_AddVertex.clear(); - const CCurve& curve = *It; - const CVertex* prev_vertex = NULL; - for(std::list::const_iterator It2 = curve.m_vertices.begin(); It2 != curve.m_vertices.end(); It2++) - { - const CVertex& vertex = *It2; - if(prev_vertex)AddVertex(vertex, prev_vertex); - prev_vertex = &vertex; - } - - TPolygon p; - p.resize(pts_for_AddVertex.size()); - if(reverse) - { - std::size_t i = pts_for_AddVertex.size() - 1;// clipper wants them the opposite way to CArea - for(std::list::iterator It = pts_for_AddVertex.begin(); It != pts_for_AddVertex.end(); It++, i--) - { - p[i] = It->int_point(); - } - } - else - { - unsigned int i = 0; - for(std::list::iterator It = pts_for_AddVertex.begin(); It != pts_for_AddVertex.end(); It++, i++) - { - p[i] = It->int_point(); - } - } - - pp.push_back(p); - } -} - -static void MakePoly(const CCurve& curve, TPolygon &p) +static void MakePoly(const CCurve& curve, TPolygon &p, bool reverse = false) { pts_for_AddVertex.clear(); const CVertex* prev_vertex = NULL; + + if(!curve.m_vertices.size()) return; + if(!curve.IsClosed()) AddVertex(curve.m_vertices.front(),NULL); + for (std::list::const_iterator It2 = curve.m_vertices.begin(); It2 != curve.m_vertices.end(); It2++) { const CVertex& vertex = *It2; @@ -346,6 +314,15 @@ static void MakePoly(const CCurve& curve, TPolygon &p) } p.resize(pts_for_AddVertex.size()); + if(reverse) + { + std::size_t i = pts_for_AddVertex.size() - 1;// clipper wants them the opposite way to CArea + for(std::list::iterator It = pts_for_AddVertex.begin(); It != pts_for_AddVertex.end(); It++, i--) + { + p[i] = It->int_point(); + } + } + else { unsigned int i = 0; for (std::list::iterator It = pts_for_AddVertex.begin(); It != pts_for_AddVertex.end(); It++, i++) @@ -355,8 +332,21 @@ static void MakePoly(const CCurve& curve, TPolygon &p) } } -static void SetFromResult( CCurve& curve, const TPolygon& p, bool reverse = true ) +static void MakePolyPoly( const CArea& area, TPolyPolygon &pp, bool reverse = true ){ + pp.clear(); + + for(std::list::const_iterator It = area.m_curves.begin(); It != area.m_curves.end(); It++) + { + pp.push_back(TPolygon()); + MakePoly(*It,pp.back(),reverse); + } +} + +static void SetFromResult( CCurve& curve, TPolygon& p, bool reverse = true ) { + if(CArea::m_clipper_clean_distance >= Point::tolerance) + CleanPolygon(p,CArea::m_clipper_clean_distance); + for(unsigned int j = 0; j < p.size(); j++) { const IntPoint &pt = p[j]; @@ -372,14 +362,14 @@ static void SetFromResult( CCurve& curve, const TPolygon& p, bool reverse = true if(CArea::m_fit_arcs)curve.FitArcs(); } -static void SetFromResult( CArea& area, const TPolyPolygon& pp, bool reverse = true ) +static void SetFromResult( CArea& area, TPolyPolygon& pp, bool reverse = true ) { // delete existing geometry area.m_curves.clear(); for(unsigned int i = 0; i < pp.size(); i++) { - const TPolygon& p = pp[i]; + TPolygon& p = pp[i]; area.m_curves.push_back(CCurve()); CCurve &curve = area.m_curves.back(); @@ -390,6 +380,7 @@ static void SetFromResult( CArea& area, const TPolyPolygon& pp, bool reverse = t void CArea::Subtract(const CArea& a2) { Clipper c; + c.StrictlySimple(CArea::m_clipper_simple); TPolyPolygon pp1, pp2; MakePolyPoly(*this, pp1); MakePolyPoly(a2, pp2); @@ -403,6 +394,7 @@ void CArea::Subtract(const CArea& a2) void CArea::Intersect(const CArea& a2) { Clipper c; + c.StrictlySimple(CArea::m_clipper_simple); TPolyPolygon pp1, pp2; MakePolyPoly(*this, pp1); MakePolyPoly(a2, pp2); @@ -416,6 +408,7 @@ void CArea::Intersect(const CArea& a2) void CArea::Union(const CArea& a2) { Clipper c; + c.StrictlySimple(CArea::m_clipper_simple); TPolyPolygon pp1, pp2; MakePolyPoly(*this, pp1); MakePolyPoly(a2, pp2); @@ -430,6 +423,7 @@ void CArea::Union(const CArea& a2) CArea CArea::UniteCurves(std::list &curves) { Clipper c; + c.StrictlySimple(CArea::m_clipper_simple); TPolyPolygon pp; @@ -452,6 +446,7 @@ CArea CArea::UniteCurves(std::list &curves) void CArea::Xor(const CArea& a2) { Clipper c; + c.StrictlySimple(CArea::m_clipper_simple); TPolyPolygon pp1, pp2; MakePolyPoly(*this, pp1); MakePolyPoly(a2, pp2); @@ -471,6 +466,73 @@ void CArea::Offset(double inwards_value) this->Reorder(); } +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(!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, + PolyFillType subjFillType, + PolyFillType clipFillType) +{ + Clipper c; + c.StrictlySimple(CArea::m_clipper_simple); + PopulateClipper(c,ptSubject); + if(a) a->PopulateClipper(c,ptClip); + PolyTree tree; + c.Execute(op, tree, subjFillType,clipFillType); + TPolyPolygon solution; + PolyTreeToPaths(tree,solution); + SetFromResult(*this, solution); +} + +void CArea::OffsetWithClipper(double offset, + JoinType joinType/* =jtRound */, + EndType endType/* =etOpenRound */, + double miterLimit/* = 5.0 */, + double roundPrecision/* = 0.0 */) +{ + offset *= m_units*m_clipper_scale; + if(roundPrecision == 0.0) { + // Clipper roundPrecision definition: https://goo.gl/4odfQh + double dphi=acos(1.0-m_accuracy*m_clipper_scale/fabs(offset)); + int Segments=(int)ceil(PI/dphi); + if (Segments < 2*CArea::m_min_arc_points) + Segments = 2*CArea::m_min_arc_points; + if (Segments > CArea::m_max_arc_points) + Segments=CArea::m_max_arc_points; + dphi = PI/Segments; + roundPrecision = (1.0-cos(dphi))*fabs(offset); + }else + roundPrecision *= m_clipper_scale; + + ClipperOffset clipper(miterLimit,roundPrecision); + TPolyPolygon pp, pp2; + MakePolyPoly(*this, pp, false); + int i=0; + for(const CCurve &c : m_curves) + clipper.AddPath(pp[i++],joinType,c.IsClosed()?etClosedPolygon:endType); + clipper.Execute(pp2,(long64)(offset)); + SetFromResult(*this, pp2, false); + this->Reorder(); +} + void CArea::Thicken(double value) { TPolyPolygon pp; diff --git a/src/Mod/Path/libarea/AreaOrderer.cpp b/src/Mod/Path/libarea/AreaOrderer.cpp index c53c38d97c..d0732d0843 100644 --- a/src/Mod/Path/libarea/AreaOrderer.cpp +++ b/src/Mod/Path/libarea/AreaOrderer.cpp @@ -30,29 +30,27 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "AreaOrderer.h" #include "Area.h" +using namespace std; + CAreaOrderer* CInnerCurves::area_orderer = NULL; -CInnerCurves::CInnerCurves(CInnerCurves* pOuter, const CCurve* curve) +CInnerCurves::CInnerCurves(shared_ptr pOuter, shared_ptr curve) +:m_pOuter(pOuter) +,m_curve(curve) { - m_pOuter = pOuter; - m_curve = curve; - m_unite_area = NULL; } CInnerCurves::~CInnerCurves() { - delete m_unite_area; } -void CInnerCurves::Insert(const CCurve* pcurve) +void CInnerCurves::Insert(shared_ptr pcurve) { - std::list outside_of_these; - std::list crossing_these; + std::list > outside_of_these; + std::list > crossing_these; // check all inner curves - for(std::set::iterator It = m_inner_curves.begin(); It != m_inner_curves.end(); It++) - { - CInnerCurves* c = *It; + for(shared_ptr c : m_inner_curves) { switch(GetOverlapType(*pcurve, *(c->m_curve))) { @@ -75,28 +73,24 @@ void CInnerCurves::Insert(const CCurve* pcurve) } // add as a new inner - CInnerCurves* new_item = new CInnerCurves(this, pcurve); + shared_ptr new_item(new CInnerCurves(shared_from_this(), pcurve)); this->m_inner_curves.insert(new_item); - for(std::list::iterator It = outside_of_these.begin(); It != outside_of_these.end(); It++) - { + for(shared_ptr c : outside_of_these) { // move items - CInnerCurves* c = *It; c->m_pOuter = new_item; new_item->m_inner_curves.insert(c); this->m_inner_curves.erase(c); } - for(std::list::iterator It = crossing_these.begin(); It != crossing_these.end(); It++) - { + for(shared_ptr c : crossing_these) { // unite these - CInnerCurves* c = *It; new_item->Unite(c); this->m_inner_curves.erase(c); } } -void CInnerCurves::GetArea(CArea &area, bool outside, bool use_curve)const +void CInnerCurves::GetArea(CArea &area, bool outside, bool use_curve) { if(use_curve && m_curve) { @@ -104,11 +98,9 @@ void CInnerCurves::GetArea(CArea &area, bool outside, bool use_curve)const outside = !outside; } - std::list do_after; + std::list > do_after; - for(std::set::const_iterator It = m_inner_curves.begin(); It != m_inner_curves.end(); It++) - { - const CInnerCurves* c = *It; + for(shared_ptr c: m_inner_curves) { area.m_curves.push_back(*c->m_curve); if(!outside)area.m_curves.back().Reverse(); @@ -116,20 +108,16 @@ void CInnerCurves::GetArea(CArea &area, bool outside, bool use_curve)const else do_after.push_back(c); } - for(std::list::iterator It = do_after.begin(); It != do_after.end(); It++) - { - const CInnerCurves* c = *It; + for(shared_ptr c : do_after) c->GetArea(area, !outside, false); - } } -void CInnerCurves::Unite(const CInnerCurves* c) +void CInnerCurves::Unite(shared_ptr c) { // unite all the curves in c, with this one - CArea* new_area = new CArea(); + shared_ptr new_area(new CArea()); new_area->m_curves.push_back(*m_curve); - delete m_unite_area; - m_unite_area = new_area; + m_unite_area = new_area; CArea a2; c->GetArea(a2); @@ -140,21 +128,21 @@ void CInnerCurves::Unite(const CInnerCurves* c) { CCurve &curve = *It; if(It == m_unite_area->m_curves.begin()) - m_curve = &curve; + m_curve = make_shared(curve); else { if(curve.IsClockwise())curve.Reverse(); - Insert(&curve); + Insert(shared_ptr(new CCurve(curve))); } } } CAreaOrderer::CAreaOrderer() + :m_top_level(make_shared()) { - m_top_level = new CInnerCurves(NULL, NULL); } -void CAreaOrderer::Insert(CCurve* pcurve) +void CAreaOrderer::Insert(shared_ptr pcurve) { CInnerCurves::area_orderer = this; diff --git a/src/Mod/Path/libarea/AreaOrderer.h b/src/Mod/Path/libarea/AreaOrderer.h index 13d78db7d2..3a4d21c854 100644 --- a/src/Mod/Path/libarea/AreaOrderer.h +++ b/src/Mod/Path/libarea/AreaOrderer.h @@ -28,6 +28,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #pragma once +#include #include #include @@ -36,30 +37,31 @@ class CCurve; class CAreaOrderer; -class CInnerCurves +class CInnerCurves: public std::enable_shared_from_this { - CInnerCurves* m_pOuter; - const CCurve* m_curve; // always empty if top level - std::set m_inner_curves; - CArea *m_unite_area; // new curves made by uniting are stored here + std::shared_ptr m_pOuter; + std::shared_ptr m_curve; // always empty if top level + std::set > m_inner_curves; + std::shared_ptr m_unite_area; // new curves made by uniting are stored here public: static CAreaOrderer* area_orderer; - CInnerCurves(CInnerCurves* pOuter, const CCurve* curve); + CInnerCurves(std::shared_ptr pOuter, std::shared_ptr curve); + CInnerCurves(){} ~CInnerCurves(); - void Insert(const CCurve* pcurve); - void GetArea(CArea &area, bool outside = true, bool use_curve = true)const; - void Unite(const CInnerCurves* c); + void Insert(std::shared_ptr pcurve); + void GetArea(CArea &area, bool outside = true, bool use_curve = true); + void Unite(std::shared_ptr c); }; class CAreaOrderer { public: - CInnerCurves* m_top_level; + std::shared_ptr m_top_level; CAreaOrderer(); - void Insert(CCurve* pcurve); + void Insert(std::shared_ptr pcurve); CArea ResultArea()const; -}; \ No newline at end of file +}; diff --git a/src/Mod/Path/libarea/CMakeLists.txt b/src/Mod/Path/libarea/CMakeLists.txt index 8359267ab2..6e489b6382 100644 --- a/src/Mod/Path/libarea/CMakeLists.txt +++ b/src/Mod/Path/libarea/CMakeLists.txt @@ -65,37 +65,52 @@ file(GLOB headers "${CMAKE_CURRENT_SOURCE_DIR}/*.h") # this makes the Python module add_library( - area - MODULE + area-native + SHARED ${AREA_SRC_COMMON} ${AREA_SRC_CLIPPER} - ${PYAREA_SRC} +) + +add_library( + area + MODULE + ${PYAREA_SRC} ) if(MSVC) - set(area_LIBS - ${Boost_LIBRARIES} - ${PYTHON_LIBRARIES} + set(area_native_LIBS debug MSVCRTD.LIB debug MSVCPRTD.LIB optimized MSVCRT.LIB optimized MSVCPRT.LIB ) -elseif(MINGW) set(area_LIBS ${Boost_LIBRARIES} ${PYTHON_LIBRARIES} + ${area_native_LIBS} + ) +elseif(MINGW) + set(area_native_LIBS Rpcrt4.lib ) + set(area_LIBS + ${Boost_LIBRARIES} + ${PYTHON_LIBRARIES} + ${area_native_LIBS} + ) else(MSVC) + set(area_native_LIBS + ) set(area_LIBS ${Boost_LIBRARIES} ${PYTHON_LIBRARIES} ) endif(MSVC) -target_link_libraries(area ${area_LIBS}) +target_link_libraries(area-native ${area_native_LIBS}) +SET_BIN_DIR(area-native area-native) +target_link_libraries(area area-native ${area_LIBS} ${area_native_LIBS}) SET_BIN_DIR(area area) SET_PYTHON_PREFIX_SUFFIX(area) @@ -108,8 +123,20 @@ execute_process( message(STATUS "area module (for Path Workbench) will be installed to: " ${CMAKE_INSTALL_LIBDIR}) +if(WIN32) + set_target_properties(area-native PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS TRUE) + INSTALL(TARGETS area-native + RUNTIME DESTINATION bin + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ) +else(WIN32) + INSTALL(TARGETS area-native + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ) +endif(WIN32) + # this installs the python library install( TARGETS area - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + DESTINATION ${CMAKE_INSTALL_LIBDIR} ) diff --git a/src/Mod/Path/libarea/Curve.cpp b/src/Mod/Path/libarea/Curve.cpp index 456c546e31..d627631961 100644 --- a/src/Mod/Path/libarea/Curve.cpp +++ b/src/Mod/Path/libarea/Curve.cpp @@ -13,6 +13,12 @@ double Point::tolerance = 0.001; //static const double PI = 3.1415926535897932; duplicated in kurve/geometry.h +//This function is moved from header here to solve windows DLL not export +//static variable problem +bool Point::operator==(const Point& p)const{ + return fabs(x-p.x)::const_iterator It = c.m_vertices.begin(); It != c.m_vertices.end(); It++, i++) { const CVertex& vertex = *It; - printf("vertex %d type = %d, x = %g, y = %g", i+1, vertex.m_type, vertex.m_p.x / CArea::m_units, vertex.m_p.y / CArea::m_units); - if(vertex.m_type)printf(", xc = %g, yc = %g", vertex.m_c.x / CArea::m_units, vertex.m_c.y / CArea::m_units); + printf("vertex %d type = %d, x = %g, y = %g", i+1, vertex.m_type, vertex.m_p.x / CArea::get_units(), vertex.m_p.y / CArea::get_units()); + if(vertex.m_type)printf(", xc = %g, yc = %g", vertex.m_c.x / CArea::get_units(), vertex.m_c.y / CArea::get_units()); printf("\n"); } } @@ -112,12 +112,12 @@ static CVertex LastVertex(const CCurve& curve) static void set_units(double units) { - CArea::m_units = units; + CArea::set_units(units); } static double get_units() { - return CArea::m_units; + return CArea::get_units(); } static bool holes_linked() diff --git a/src/Mod/Path/libarea/clipper.hpp b/src/Mod/Path/libarea/clipper.hpp index 15af31e464..b88bffd790 100644 --- a/src/Mod/Path/libarea/clipper.hpp +++ b/src/Mod/Path/libarea/clipper.hpp @@ -44,7 +44,7 @@ //#define use_xyz //use_lines: Enables line clipping. Adds a very minor cost to performance. -//#define use_lines +#define use_lines //use_deprecated: Enables temporary support for the obsolete functions //#define use_deprecated