From 36423f24deef7919f6fe483b3a5d09752c4e125c Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Thu, 19 Jan 2017 23:07:06 +0800 Subject: [PATCH] Path: added Path.Area and Path.FeatureArea --- src/Mod/Path/App/AppPath.cpp | 16 +- src/Mod/Path/App/Area.cpp | 592 +++++++++++ src/Mod/Path/App/Area.h | 238 +++++ src/Mod/Path/App/AreaParams.h | 129 +++ src/Mod/Path/App/AreaPy.xml | 62 ++ src/Mod/Path/App/AreaPyImp.cpp | 292 +++++ src/Mod/Path/App/CMakeLists.txt | 10 + src/Mod/Path/App/FeatureArea.cpp | 157 +++ src/Mod/Path/App/FeatureArea.h | 65 ++ src/Mod/Path/App/ParamsHelper.h | 998 ++++++++++++++++++ src/Mod/Path/Gui/AppPathGui.cpp | 3 + src/Mod/Path/Gui/CMakeLists.txt | 2 + src/Mod/Path/Gui/Command.cpp | 82 +- src/Mod/Path/Gui/Resources/Path.qrc | 1 + .../Path/Gui/Resources/icons/Path-Area.svg | 648 ++++++++++++ src/Mod/Path/Gui/ViewProviderArea.cpp | 125 +++ src/Mod/Path/Gui/ViewProviderArea.h | 60 ++ src/Mod/Path/InitGui.py | 2 +- 18 files changed, 3479 insertions(+), 3 deletions(-) create mode 100644 src/Mod/Path/App/Area.cpp create mode 100644 src/Mod/Path/App/Area.h create mode 100644 src/Mod/Path/App/AreaParams.h create mode 100644 src/Mod/Path/App/AreaPy.xml create mode 100644 src/Mod/Path/App/AreaPyImp.cpp create mode 100644 src/Mod/Path/App/FeatureArea.cpp create mode 100644 src/Mod/Path/App/FeatureArea.h create mode 100644 src/Mod/Path/App/ParamsHelper.h create mode 100644 src/Mod/Path/Gui/Resources/icons/Path-Area.svg create mode 100644 src/Mod/Path/Gui/ViewProviderArea.cpp create mode 100644 src/Mod/Path/Gui/ViewProviderArea.h diff --git a/src/Mod/Path/App/AppPath.cpp b/src/Mod/Path/App/AppPath.cpp index ab6ffe02e..4723ff7b1 100644 --- a/src/Mod/Path/App/AppPath.cpp +++ b/src/Mod/Path/App/AppPath.cpp @@ -41,6 +41,8 @@ #include "PropertyTooltable.h" #include "FeaturePathCompound.h" #include "FeaturePathShape.h" +#include "AreaPy.h" +#include "FeatureArea.h" namespace Path { extern PyObject* initModule(); @@ -49,15 +51,24 @@ extern PyObject* initModule(); /* Python entry */ PyMODINIT_FUNC initPath() { + // 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. @@ -74,4 +85,7 @@ PyMODINIT_FUNC initPath() Path::FeatureCompoundPython ::init(); Path::FeatureShape ::init(); Path::FeatureShapePython ::init(); + Path::Area ::init(); + Path::FeatureArea ::init(); + Path::FeatureAreaPython ::init(); } diff --git a/src/Mod/Path/App/Area.cpp b/src/Mod/Path/App/Area.cpp new file mode 100644 index 000000000..45564e960 --- /dev/null +++ b/src/Mod/Path/App/Area.cpp @@ -0,0 +1,592 @@ +/**************************************************************************** + * 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 "Area.h" +#include "../libarea/Area.h" + +using namespace Path; + +CAreaParams::CAreaParams() + :PARAM_INIT(NAME,AREA_PARAMS_CAREA) +{} + +AreaParams::AreaParams() + :PARAM_INIT(NAME,AREA_PARAMS_BASE) +{} + +CAreaConfig::CAreaConfig(const CAreaParams &p, bool noFitArcs) + :params(p) +{ + // Arc fitting is lossy. we shall reduce the number of unecessary fit + if(noFitArcs) + params.FitArcs=false; + +#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))(params.PARAM_FNAME(_param)); + + PARAM_FOREACH(AREA_CONF_SAVE_AND_APPLY,AREA_PARAMS_CAREA) +} + +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); + +Area::Area(const AreaParams *params) +:myArea(NULL) +,myAreaOpen(NULL) +,myHaveFace(false) +{ + if(params) + setParams(*params); +} + +Area::~Area() { + clean(); +} + +void Area::setPlane(const TopoDS_Shape &shape) { + myWorkPlane = shape; +} + +void Area::add(CArea &area, const TopoDS_Shape &shape, const gp_Trsf *trsf, + double deflection, CArea *areaOpen, bool to_edges, bool reorder) +{ + bool haveShape = false; + + for (TopExp_Explorer it(shape, TopAbs_FACE); it.More(); it.Next()) { + haveShape = true; + const TopoDS_Face &face = TopoDS::Face(it.Current()); + for (TopExp_Explorer it(face, TopAbs_WIRE); it.More(); it.Next()) + add(area,TopoDS::Wire(it.Current()),trsf,deflection); + } + + if(haveShape) return; + + 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(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); + }else + add(_areaOpen,wire,trsf,deflection); + } + + if(!haveShape) { + for (TopExp_Explorer it(shape, TopAbs_EDGE); it.More(); it.Next()) { + add(_areaOpen,BRepBuilderAPI_MakeWire( + TopoDS::Edge(it.Current())).Wire(),trsf,deflection); + } + } + + if(reorder) + _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); +} + +void Area::add(CArea &area, const TopoDS_Wire& wire, + const gp_Trsf *trsf, double deflection) +{ + 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()) { + 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: { + ccurve.append(CVertex(Point(p.X(),p.Y()))); + break; + } case GeomAbs_Circle:{ + double first = curve.FirstParameter(); + double last = curve.LastParameter(); + gp_Circ circle = curve.Circle(); + gp_Ax1 axis = circle.Axis(); + int dir = axis.Direction().Z()<0?-1:1; + if(reversed) dir = -dir; + gp_Pnt loc = axis.Location(); + if(fabs(first-last)>M_PI) { + // Split arc(circle) larger than half circle. This is + // translated from PathUtil code. Not sure why it is + // needed. + gp_Pnt mid = curve.Value((last-first)*0.5+first); + ccurve.append(CVertex(dir,Point(mid.X(),mid.Y()), + Point(loc.X(),loc.Y()))); + } + ccurve.append(CVertex(dir,Point(p.X(),p.Y()), + Point(loc.X(),loc.Y()))); + break; + } default: { + // Discretize all other type of curves + GCPnts_UniformDeflection discretizer(curve, deflection, + curve.FirstParameter(), curve.LastParameter()); + if (discretizer.IsDone () && discretizer.NbPoints () > 0) { + int nbPoints = discretizer.NbPoints (); + for (int i=1; i<=nbPoints; i++) { + gp_Pnt pt = discretizer.Value (i); + ccurve.append(CVertex(Point(pt.X(),pt.Y()))); + } + }else + Standard_Failure::Raise("Curve discretization failed"); + }} + } + if(BRep_Tool::IsClosed(wire) && !ccurve.IsClosed()) { + Base::Console().Warning("ccurve not closed\n"); + ccurve.append(ccurve.m_vertices.front()); + } + area.append(ccurve); +} + + +void Area::clean(bool deleteShapes) { + myShape.Nullify(); + delete myArea; + myArea = NULL; + delete myAreaOpen; + myAreaOpen = NULL; + if(deleteShapes) + myShapes.clear(); +} + +void Area::add(const TopoDS_Shape &shape,short op) { +#define AREA_SRC_OP(_v) op + PARAM_ENUM_CONVERT(AREA_SRC_OP,,PARAM_ENUM_EXCEPT,AREA_PARAMS_OPCODE); + TopExp_Explorer it(shape, TopAbs_SHELL); + if(it.More()) + throw Base::ValueError("not a 2D shape"); + clean(); + if(myShapes.empty()) + Operation = ClipperLib::ctUnion; + myShapes.push_back(Shape((short)Operation,shape)); +} + + +void Area::setParams(const AreaParams ¶ms) { +#define AREA_SRC2(_v) params._v + // 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(!myHaveFace) { + TopExp_Explorer it(shape, TopAbs_FACE); + myHaveFace = it.More(); + } + CArea areaOpen; + add(area,shape,&myTrsf,myParams.Deflection,&areaOpen, + myParams.OpenMode==OpenModeEdges,myParams.Reorder); + if(areaOpen.m_curves.size()) { + if(&area == myArea || myParams.OpenMode == OpenModeNone) + myAreaOpen->m_curves.splice(myAreaOpen->m_curves.end(),areaOpen.m_curves); + else + Base::Console().Warning("open wires discarded in clipping shapes\n"); + } +} + +void Area::build() { + if(myArea) return; + + if(myShapes.empty()) + throw Base::ValueError("Null shape"); + +#define AREA_SRC(_v) myParams._v + PARAM_ENUM_CONVERT(AREA_SRC,,PARAM_ENUM_EXCEPT,AREA_PARAMS_CLIPPER_FILL); + + TopoDS_Builder builder; + TopoDS_Compound comp; + builder.MakeCompound(comp); + if(!myWorkPlane.IsNull()) + builder.Add(comp,myWorkPlane); + else { + for(const Shape &s : myShapes) + builder.Add(comp, s.shape); + } + BRepLib_FindSurface planeFinder(comp,-1,Standard_True); + if (!planeFinder.Found()) + throw Base::ValueError("shapes are not coplanar"); + + myTrsf.SetTransformation(GeomAdaptor_Surface( + planeFinder.Surface()).Plane().Position()); + + myArea = new CArea(); + myAreaOpen = new CArea(); + + CAreaConfig conf(myParams); + CArea areaClip; + + short op = ClipperLib::ctUnion; + bool pending = false; + for(const Shape &s : myShapes) { + if(op!=s.op) { + if(myParams.OpenMode!=OpenModeNone) + myArea->m_curves.splice(myArea->m_curves.end(),myAreaOpen->m_curves); + pending = false; + myArea->Clip((ClipperLib::ClipType)op,&areaClip,SubjectFill,ClipFill); + areaClip.m_curves.clear(); + op=s.op; + } + addToBuild(op==ClipperLib::ctUnion?*myArea:areaClip,s.shape); + pending = true; + } + if(pending){ + if(myParams.OpenMode!=OpenModeNone) + myArea->m_curves.splice(myArea->m_curves.end(),myAreaOpen->m_curves); + myArea->Clip((ClipperLib::ClipType)op,&areaClip,SubjectFill,ClipFill); + } + myArea->m_curves.splice(myArea->m_curves.end(),myAreaOpen->m_curves); +} + +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) { + CArea copy(area); + copy.FitArcs(); + return toShape(copy,bFill,&trsf); + } + area.FitArcs(); + } + return toShape(area,bFill,&trsf); +} + +const TopoDS_Shape &Area::getShape() { + if(myShape.IsNull()) { + build(); + CAreaConfig conf(myParams); + myShape = toShape(*myArea,myParams.Fill); + } + return myShape; +} + +TopoDS_Shape Area::makeOffset(PARAM_ARGS(ARG,AREA_PARAMS_OFFSET)) { + std::list shapes; + makeOffset(shapes,PARAM_FIELDS(ARG,AREA_PARAMS_OFFSET)); + if(shapes.empty()) + return TopoDS_Shape(); + if(shapes.size()==1) + return shapes.front(); + BRep_Builder builder; + TopoDS_Compound compound; + builder.MakeCompound(compound); + for(const TopoDS_Shape &s : shapes) + builder.Add(compound,s); + return compound; +} + +void Area::makeOffset(std::list &shapes, + PARAM_ARGS(ARG,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_ENUM_EXCEPT,AREA_PARAMS_OFFSET_CONF); +#ifdef AREA_OFFSET_ALGO + PARAM_ENUM_CONVERT(AREA_SRC,,PARAM_ENUM_EXCEPT,AREA_PARAMS_CLIPPER_FILL); +#endif + + for(int i=0;count<0||im_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(area.m_curves.empty()) + return; + + if(count == 1) { + shapes.push_back(toShape(area,myParams.Fill)); + return; + } + shapes.push_back(toShape(area,Area::FillNone)); + } +} + +TopoDS_Shape Area::makePocket(PARAM_ARGS(ARG,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(); + + PocketMode pm; + switch(mode) { + case Area::PocketModeZigZag: + pm = ZigZagPocketMode; + break; + case Area::PocketModeSpiral: + pm = SpiralPocketMode; + break; + case Area::PocketModeOffset: { + PARAM_DECLARE_INIT(NAME,AREA_PARAMS_OFFSET); + Offset = -tool_radius-extra_offset; + ExtraPass = -1; + Stepover = -stepover; + return makeOffset(PARAM_FIELDS(NAME,AREA_PARAMS_OFFSET)); + }case Area::PocketModeZigZagOffset: + pm = ZigZagThenSingleOffsetPocketMode; + break; + default: + throw Base::ValueError("unknown poket mode"); + } + + build(); + 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); + 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_Shape Area::toShape(const CArea &area, bool fill, const gp_Trsf *trsf) { + BRep_Builder builder; + TopoDS_Compound compound; + builder.MakeCompound(compound); + + for(const CCurve &c : area.m_curves) { + 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(v.m_type == 0) { + mkWire.Add(BRepBuilderAPI_MakeEdge(pt,pnext).Edge()); + } else { + gp_Pnt center(v.m_c.x,v.m_c.y,0); + double r = center.Distance(pt); + double r2 = center.Distance(pnext); + if(fabs(r-r2) > Precision::Confusion()) { + double d = pt.Distance(pnext); + double q = sqrt(r*r - d*d*0.25); + double x = (pt.X()+pnext.X())*0.5; + double y = (pt.Y()+pnext.Y())*0.5; + double dx = q*(pt.Y()-pnext.Y())/d; + double dy = q*(pnext.X()-pt.X())/d; + gp_Pnt newCenter(x + dx, y + dy,0); + if(IsLeft(pt,pnext,center) != IsLeft(pt,pnext,newCenter)) { + newCenter.SetX(x - dx); + newCenter.SetY(y - dy); + } + Base::Console().Warning( + "Arc correction: %lf,%lf, center(%lf,%lf)->(%lf,%lf)\n", + r,r2,center.X(),center.Y(),newCenter.X(),newCenter.Y()); + center = newCenter; + } + gp_Ax2 axis(center, gp_Dir(0,0,v.m_type)); + mkWire.Add(BRepBuilderAPI_MakeEdge(gp_Circ(axis,r),pt,pnext).Edge()); + } + pt = pnext; + } + if(c.IsClosed() && !BRep_Tool::IsClosed(mkWire.Wire())){ + // This should never happen after changing libarea's + // Point::tolerance to be the same as Precision::Confusion(). + // Just leave it here in case. + BRepAdaptor_Curve curve(mkWire.Edge()); + gp_Pnt p1(curve.Value(curve.FirstParameter())); + gp_Pnt p2(curve.Value(curve.LastParameter())); + std::stringstream str; + str<< "warning: patch open wire" << + c.m_vertices.back().m_type << endl << + '(' << p1.X() << ',' << p1.Y() << ')' << endl << + '(' << p2.X() << ',' << p2.Y() << ')' << endl << + '(' << pt.X() << ',' << pt.Y() << ')' << endl << + '(' << pstart.X() << ',' <* + * * + * 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 "AreaParams.h" + +class CArea; + +namespace Path +{ + +/** Store libarea algorithm configuration */ +struct PathExport CAreaParams { + PARAM_DECLARE(NAME,AREA_PARAMS_CAREA) + CAreaParams(); +}; + +/** Store all Area configurations */ +struct PathExport AreaParams: CAreaParams { + + PARAM_DECLARE(NAME,AREA_PARAMS_BASE) + PARAM_DECLARE(NAME,AREA_PARAMS_OFFSET_CONF) + + 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_CONF) + return true; + } + bool operator!=(const AreaParams &other) const { + return !(*this == other); + } + + AreaParams(); +}; + +/** 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 { + + /** Stores current libarea settings */ + PARAM_DECLARE(NAME,AREA_PARAMS_CAREA) + + /** Stores user defined setting */ + CAreaParams params; + + /** 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(); + +protected: + + struct Shape { + short op; + TopoDS_Shape shape; + + Shape(short opCode, const TopoDS_Shape &s) + :op(opCode) + ,shape(s) + {} + }; + + std::list myShapes; + CArea *myArea; + CArea *myAreaOpen; + gp_Trsf myTrsf; + AreaParams myParams; + TopoDS_Shape myWorkPlane; + TopoDS_Shape myShape; + bool myHaveFace; + + /** 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); + +public: + /** Declare all parameters defined in #AREA_PARAMS_ALL as member variable */ + PARAM_ENUM_DECLARE(AREA_PARAMS_ALL) + +public: + Area(const AreaParams *params = NULL); + virtual ~Area(); + + /** Set a working plane + * + * If no working plane are set, Area will try to find a working plane from + * all the added children shapes. The purpose of this function is in case + * the child shapes are all colinear edges + * + * \arg \c shape: a shape defining a working plane + */ + void setPlane(const TopoDS_Shape &shape); + + /** 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(ARG,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(PARAM_ARGS_DEF(ARG,AREA_PARAMS_OFFSET)); + + /** Obtain a list of offset shapes of the combined shape, + * + * See #AREA_PARAMS_OFFSET for description of the arguments. + */ + void makeOffset(std::list &shapes, + PARAM_ARGS_DEF(ARG,AREA_PARAMS_OFFSET)); + + /** Make a pocket of the combined shape + * + * See #AREA_PARAMS_POCKET for description of the arguments. + */ + TopoDS_Shape makePocket(PARAM_ARGS_DEF(ARG,AREA_PARAMS_POCKET)); + + + /** Config this Area object */ + void setParams(const AreaParams ¶ms); + + + /** 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 */ + const TopoDS_Shape &getShape(); + + /** 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 defecting non circular curves + * */ + static void add(CArea &area, const TopoDS_Wire &wire, + const gp_Trsf *trsf=NULL,double deflection=0.01); + + /** 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 areaOpen: for collecting open curves. If not supplied, open + * curves are added to \c area + * \arg \c to_edges: separate open wires to individual edges + * \arg \c reorder: reorder closed wires for wire only shape + * */ + static void add(CArea &area, const TopoDS_Shape &shape, const gp_Trsf *trsf=NULL, + double deflection=0.01,CArea *areaOpen=NULL, bool to_edges=false, bool reorder=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); +}; + +} //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 000000000..01b153676 --- /dev/null +++ b/src/Mod/Path/App/AreaParams.h @@ -0,0 +1,129 @@ +/**************************************************************************** + * 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)) + +/** 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)))\ + ((bool,reorder,Reorder,false,"Re-orient closed wires in wire only shapes so that inner wires become holes."))\ + ((enum,open_mode,OpenMode,0,"Specify how to handle open wires.\n"\ + "'None' means combin without openeration.\n"\ + "'Edges' means separate to edges before Union.\n"\ + "ClipperLib seems to have an urge to close open wires.",(None)(Union)(Edges)))\ + AREA_PARAMS_CLIPPER_FILL \ + ((double,deflection,Deflection,0.01,"Deflection for non circular curve discretization")) + +/** libarea algorithm option parameters */ +#define AREA_PARAMS_CAREA \ + ((double,tolerance,Tolerance,Precision::Confusion(),"Point coincidence tolerance"))\ + ((bool,fit_arcs,FitArcs,true,"Enable arc fitting"))\ + ((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 \nconvert floating points.")) + +/** Pocket parameters + * + * These parameters cooresponds to CAreaPocketParams in libarea + * */ +#define AREA_PARAMS_POCKET \ + ((enum,mode,PocketMode,1,"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")) + +/** Operation code */ +#define AREA_PARAMS_OPCODE \ + ((enum2,op,Operation,0,"Boolean operation",\ + (Union)(Difference)(Intersection)(Xor),(ClipperLib::ClipType,ClipperLib::ct))) + +/** 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")) + +#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 \ + ((bool,thicken,Thicken,false,"Thicken the resulting wires with Offset"))\ + ((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")) + +/** Group of all Area configuration parameters */ +#define AREA_PARAMS_CONF \ + AREA_PARAMS_CAREA \ + AREA_PARAMS_BASE \ + AREA_PARAMS_OFFSET_CONF + +/** Group of all Area parameters */ +#define AREA_PARAMS_ALL \ + AREA_PARAMS_CONF \ + AREA_PARAMS_OPCODE \ + AREA_PARAMS_OFFSET \ + AREA_PARAMS_POCKET + +#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 000000000..e4952e13b --- /dev/null +++ b/src/Mod/Path/App/AreaPy.xml @@ -0,0 +1,62 @@ + + + + + + FreeCAD python wrapper of libarea + + + + + + + + + setPlane(shape): Set the working plane. The shape will not be used for +any operation + + + + + toShape(rebuild=False): Return the resulting shape + + + + + + + + + + + + + + + + + + + + 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. + + + + diff --git a/src/Mod/Path/App/AreaPyImp.cpp b/src/Mod/Path/App/AreaPyImp.cpp new file mode 100644 index 000000000..227740519 --- /dev/null +++ b/src/Mod/Path/App/AreaPyImp.cpp @@ -0,0 +1,292 @@ +/**************************************************************************** + * 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 "Mod/Path/App/Area.h" + +// inclusion of the generated files (generated out of AreaPy.xml) +#include "AreaPy.h" +#include "AreaPy.cpp" + + +struct AreaDoc { + const char *name; + const char *doc; +}; + +/** Generate doc string from parameter definitions + * It will generate doc string and replace the one generated from xml + * */ +static const AreaDoc myDocs[] = { + { + "setParams", + "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", + + "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 fused together regardless of the op code given.\n" + "Subsequent shape's wire will be combined using the op code. All shape wires\n" + "shall be coplanar, and are used to determine a working plane for face making and\n" + "offseting. You can call setPlane() to supply a reference shape to determin the\n" + "working plane in case the added shapes are all colinear lines.\n", + }, + + { + "makeOffset", + + "makeOffset(" PARAM_PY_ARGS_DOC(ARG,AREA_PARAMS_OFFSET) "):\n" + "Make an 2D offset of the shape.\n" + PARAM_PY_DOC(ARG,AREA_PARAMS_OFFSET), + }, + { + "makePocket", + + "makePocket(" PARAM_PY_ARGS_DOC(ARG,AREA_PARAMS_POCKET) "):\n" + "Generate pocket toolpath of the shape.\n" + PARAM_PY_DOC(ARG,AREA_PARAMS_POCKET), + }, +}; + +struct AreaPyDoc { + AreaPyDoc() { + for(PyMethodDef &method : Path::AreaPy::Methods) { + if(!method.ml_name) continue; + for(const AreaDoc &doc : myDocs) { + if(std::strcmp(method.ml_name,doc.name)==0) { + method.ml_doc = doc.doc; + break; + } + } + } + } +}; + +static AreaPyDoc doc; + +namespace Part { + extern 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 *, PyObject *) // Python wrapper +{ + return new AreaPy(new Area); +} + +// constructor method +int AreaPy::PyInit(PyObject* /*args*/, PyObject* /*kwd*/) +{ + return 0; +} + +PyObject* AreaPy::setPlane(PyObject *args) { + PyObject *pcObj; + if (!PyArg_ParseTuple(args, "O!", &(Part::TopoShapePy::Type), &pcObj)) + Py_Error(Base::BaseExceptionFreeCADError, "Wrong parameters"); + +#define GET_TOPOSHAPE(_p) static_cast(_p)->getTopoShapePtr()->getShape() + getAreaPtr()->setPlane(GET_TOPOSHAPE(pcObj)); + return Py_None; +} + +PyObject* AreaPy::toShape(PyObject *args, PyObject *keywds) +{ + PyObject *pcObj = Py_True; + static char *kwlist[] = {"rebuild", NULL}; + if (!PyArg_ParseTupleAndKeywords(args, keywds,"|O",kwlist,&pcObj)) + Py_Error(Base::BaseExceptionFreeCADError, "This method accepts no argument"); + + try { + if(PyObject_IsTrue(pcObj)) + getAreaPtr()->clean(true); + return Py::new_reference_to(Part::shape2pyshape(getAreaPtr()->getShape())); + } + PY_CATCH_OCC; +} + +PyObject* AreaPy::add(PyObject *args, PyObject *keywds) +{ + PARAM_PY_DECLARE_INIT(ARG,AREA_PARAMS_OPCODE) + PyObject *pcObj; + static char *kwlist[] = {PARAM_FIELD_STRINGS(ARG,AREA_PARAMS_OPCODE), NULL}; + if (!PyArg_ParseTupleAndKeywords(args, keywds, + "O|" PARAM_PY_KWDS(AREA_PARAMS_OPCODE), + kwlist,&pcObj,PARAM_REF(ARG,AREA_PARAMS_OPCODE))) + Py_Error(Base::BaseExceptionFreeCADError, "Wrong parameters"); + + if (PyObject_TypeCheck(pcObj, &(Part::TopoShapePy::Type))) { + getAreaPtr()->add(GET_TOPOSHAPE(pcObj),op); + return Py_None; + } + 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(ARG,AREA_PARAMS_OPCODE)); + } + return Py_None; +} + +PyObject* AreaPy::makeOffset(PyObject *args, PyObject *keywds) +{ + //Generate a keyword string defined in the ARG field of OFFSET parameter list + static char *kwlist[] = {PARAM_FIELD_STRINGS(ARG,AREA_PARAMS_OFFSET), NULL}; + + //Declare variables defined in the ARG field of the OFFSET parameter list with + //initialization to defaults + PARAM_PY_DECLARE_INIT(ARG,AREA_PARAMS_OFFSET) + + //Parse arguments to overwrite the defaults + if (!PyArg_ParseTupleAndKeywords(args, keywds, + "|" PARAM_PY_KWDS(AREA_PARAMS_OFFSET), kwlist, + PARAM_REF(ARG,AREA_PARAMS_OFFSET))) + Py_Error(Base::BaseExceptionFreeCADError, "Wrong parameters"); + + try { + //Expand the variable as function call arguments + TopoDS_Shape resultShape = getAreaPtr()->makeOffset( + PARAM_PY_FIELDS(ARG,AREA_PARAMS_OFFSET)); + return Py::new_reference_to(Part::shape2pyshape(resultShape)); + } + PY_CATCH_OCC; +} + +PyObject* AreaPy::makePocket(PyObject *args, PyObject *keywds) +{ + static char *kwlist[] = {PARAM_FIELD_STRINGS(ARG,AREA_PARAMS_POCKET), NULL}; + + PARAM_PY_DECLARE_INIT(ARG,AREA_PARAMS_POCKET) + + if (!PyArg_ParseTupleAndKeywords(args, keywds, + "|" PARAM_PY_KWDS(AREA_PARAMS_POCKET), kwlist, + PARAM_REF(ARG,AREA_PARAMS_POCKET))) + Py_Error(Base::BaseExceptionFreeCADError, "Wrong parameters"); + + try { + TopoDS_Shape resultShape = getAreaPtr()->makePocket( + PARAM_PY_FIELDS(ARG,AREA_PARAMS_POCKET)); + return Py::new_reference_to(Part::shape2pyshape(resultShape)); + } + PY_CATCH_OCC; +} + + +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(NAME,AREA_PARAMS_CONF); + + AreaParams params = getAreaPtr()->getParams(); + +#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_CONF) + + //Parse arguments to overwrite CONF variables + if (!PyArg_ParseTupleAndKeywords(args, keywds, + "|" PARAM_PY_KWDS(AREA_PARAMS_CONF), kwlist, + PARAM_REF(NAME,AREA_PARAMS_CONF))) + Py_Error(Base::BaseExceptionFreeCADError, + "Wrong parameters, call getParamsDesc() to get supported params"); + +#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_CONF) + + getAreaPtr()->setParams(params); + return Py_None; +} + +PyObject* AreaPy::getParams(PyObject *args) +{ + if (!PyArg_ParseTuple(args, "")) + Py_Error(Base::BaseExceptionFreeCADError, "This method accepts no argument"); + + const AreaParams ¶ms =getAreaPtr()->getParams(); + + PyObject *dict = PyDict_New(); +#define AREA_SRC(_v) params._v + PARAM_PY_DICT_SET_VALUE(dict,AREA_SRC,AREA_PARAMS_CONF) + return dict; +} + +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)) + Py_Error(Base::BaseExceptionFreeCADError, "This method accepts no argument"); + + if(PyObject_IsTrue(pcObj)) + return PyString_FromString(PARAM_PY_DOC(NAME,AREA_PARAMS_CONF)); + + PyObject *dict = PyDict_New(); + PARAM_PY_DICT_SET_DOC(dict,AREA_PARAMS_CONF) + return dict; +} + +// 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 266209f8b..14531ff4d 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,7 @@ generate_from_xml(PathPy) generate_from_xml(ToolPy) generate_from_xml(TooltablePy) generate_from_xml(FeaturePathCompoundPy) +generate_from_xml(AreaPy) SET(Python_SRCS CommandPy.xml @@ -41,6 +43,8 @@ SET(Python_SRCS TooltablePyImp.cpp FeaturePathCompoundPy.xml FeaturePathCompoundPyImp.cpp + AreaPy.xml + AreaPyImp.cpp ) SET(Mod_SRCS @@ -67,6 +71,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 000000000..cdc26f1d7 --- /dev/null +++ b/src/Mod/Path/App/FeatureArea.cpp @@ -0,0 +1,157 @@ +/**************************************************************************** + * 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 +#include +#include + +using namespace Path; + +PROPERTY_SOURCE(Path::FeatureArea, Part::Feature) + +PARAM_ENUM_STRING_DECLARE(static const char *Enums,AREA_PARAMS_ALL) + +FeatureArea::FeatureArea() +{ + 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("Pocket",AREA_PARAMS_POCKET); + PARAM_PROP_ADD("Offset Settings", AREA_PARAMS_OFFSET_CONF); + PARAM_PROP_ADD("libarea Settings",AREA_PARAMS_CAREA); + + PARAM_PROP_SET_ENUM(Enums,AREA_PARAMS_ALL); + PocketMode.setValue((long)0); +} + +FeatureArea::~FeatureArea() +{ +} + +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"); + } + + AreaParams params; + +#define AREA_PROP_GET(_param) \ + params.PARAM_FNAME(_param) = PARAM_FNAME(_param).getValue(); + + PARAM_FOREACH(AREA_PROP_GET,AREA_PARAMS_CONF) + + Area area(¶ms); + + TopoDS_Shape workPlane = WorkPlane.getShape().getShape(); + if(!workPlane.IsNull()) + area.setPlane(workPlane); + + area.clean(true); + for (std::vector::iterator it = links.begin(); it != links.end(); ++it) { + area.add(static_cast(*it)->Shape.getShape().getShape(), + PARAM_PROP_ARGS(AREA_PARAMS_OPCODE)); + } + + std::list shapes; + if(fabs(Offset.getValue())>Precision::Confusion()) + area.makeOffset(shapes,PARAM_PROP_ARGS(AREA_PARAMS_OFFSET)); + + if(PocketMode.getValue()) { + Area areaPocket(¶ms); + if(shapes.empty()) + areaPocket.add(area.getShape()); + else{ + bool front = true; + if(shapes.size()>1) { + double step = Stepover.getValue(); + if(fabs(step)0; + } + areaPocket.add(front?shapes.front():shapes.back()); + } + shapes.push_back(areaPocket.makePocket(PARAM_PROP_ARGS(AREA_PARAMS_POCKET))); + } + + if(shapes.empty()) + this->Shape.setValue(area.getShape()); + else if(shapes.size()==1) + this->Shape.setValue(shapes.front()); + else { + BRep_Builder builder; + TopoDS_Compound compound; + builder.MakeCompound(compound); + for(const TopoDS_Shape &s : shapes) + builder.Add(compound,s); + this->Shape.setValue(compound); + } + return Part::Feature::execute(); +} + +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(); +} + +// Python Area feature --------------------------------------------------------- + +namespace App { +/// @cond DOXERR +PROPERTY_SOURCE_TEMPLATE(Path::FeatureAreaPython, Path::FeatureArea) + +template<> const char* Path::FeatureAreaPython::getViewProviderName(void) const { + return "PathGui::ViewProviderArea"; +} +/// @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 000000000..d5e10c2de --- /dev/null +++ b/src/Mod/Path/App/FeatureArea.h @@ -0,0 +1,65 @@ +/**************************************************************************** + * 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(); + + /// 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; + + App::PropertyLinkList Sources; + Part::PropertyPartShape WorkPlane; + + PARAM_PROP_DECLARE(AREA_PARAMS_ALL) +}; + +typedef App::FeaturePythonT FeatureAreaPython; + +} //namespace Path + + +#endif // PATH_FeaturePath_H diff --git a/src/Mod/Path/App/ParamsHelper.h b/src/Mod/Path/App/ParamsHelper.h new file mode 100644 index 000000000..f0d65dd96 --- /dev/null +++ b/src/Mod/Path/App/ParamsHelper.h @@ -0,0 +1,998 @@ +/**************************************************************************** + * 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,_field,_param) \ + PARAM_TYPE(_param) PARAM_FIELD(_field,_param); + +/** + * Delcares parameters using the given field as name + * + * \arg \c _field: specifies the \ref ParamField "field" to use as name + * + * Expands to: + * \code{.unparsed} + * type1 _field1;type2 _field2; ... + * \endcode + * \ingroup ParamCommon + */ +#define PARAM_DECLARE(_field,_seq) \ + BOOST_PP_SEQ_FOR_EACH(PARAM_DECLARE_,_field,_seq) + + +/** Helper for #PARAM_DECLARE_INIT */ +#define PARAM_DECLARE_INIT_(_1,_field,_param) \ + PARAM_TYPE(_param) PARAM_FIELD(_field,_param) = PARAM_FDEF(_param); + +/** + * Delcares parameters with initialization to default using the given field as + * name + * + * \arg \c _field: \ref ParamField "field" to use as name + * + * Expands to: + * \code{.unparsed} + * type1 _field1=_def1;type2 _field2=_def2; ... + * \endcode + * \ingroup ParamCommon + */ +#define PARAM_DECLARE_INIT(_field,_seq) \ + BOOST_PP_SEQ_FOR_EACH(PARAM_DECLARE_INIT_,_field,_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(_name) = \ + 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_FNAME(_param));\ + switch(_src(PARAM_FNAME(_param))) {\ + BOOST_PP_SEQ_FOR_EACH_I(PARAM_ENUM_CONVERT__,\ + (_dst,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: Optional macro to generate source variable. The signature must + * be _src(_name)<\tt>, where \c _name will be the parameters \a name field. + * In case you just want \c _name as the source variable name, you can simply + * omit this argument, because newer C++ preprocessor allows empty argument. + * \arg \c _dst: Optional macro to generate destination variable. 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(_v) BOOST_PP_CAT(my,_v) + * \code{.unparsed} + * + * calling (note that the \c _src macro is omitted) + * \code{.unparsed} + * PARAM_ENUM_CONVERT(,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_FNAME(_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: Optional macro to generate source variable. The signature must + * be _src(_name)<\tt>, where \c _name will be the parameters \a name field. + * In case you just want \c _name as the source variable name, you can simply + * omit this argument, because newer C++ preprocessor allows empty argument. + * + * \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_(_,_field,_i,_param) \ + BOOST_PP_COMMA_IF(_i) PARAM_FIELD(_field,_param)(PARAM_FDEF(_param)) + +/** Constructor initialization + * + * \arg \c _field: specifies the \ref ParamField "field" to use as name + * + * Expand to, + * \code{.unparsed} + * field1(def1), field2(def2)... + * \endcode + * \ingroup ParamCommon + */ +#define PARAM_INIT(_field,_seq) \ + BOOST_PP_SEQ_FOR_EACH_I(PARAM_INIT_,_field,_seq) + + +/** Helper for #PARAM_ARGS_DEF */ +#define PARAM_ARGS_DEF_(_,_field,_i,_param) \ + BOOST_PP_COMMA_IF(_i) PARAM_TYPE(_param) PARAM_FIELD(_field,_param)=PARAM_FDEF(_param) + +/** Delcare the parameters as function argument list with defaults. + * + * \arg \c _field: specifies the \ref ParamField "field" to use as name + * + * Expand to: + * \code{.unparsed} + * type1 field1=def1, type2 field2=def2 ... + * \endcode + * \ingroup ParamCommon + */ +#define PARAM_ARGS_DEF(_field,_seq) \ + BOOST_PP_SEQ_FOR_EACH_I(PARAM_ARGS_DEF_,_field,_seq) + + +/** Helper for #PARAM_ARGS */ +#define PARAM_ARGS_(_,_field,_i,_param) \ + BOOST_PP_COMMA_IF(_i) PARAM_TYPE(_param) PARAM_FIELD(_field,_param) + +/** Delcare the parameters as function argument list without defaults. + * + * \arg \c _field: specifies the \ref ParamField "field" to use as name + * + * Expand to: + * \code{.unparsed} + * type1 field1, type2 field2 ... + * \endcode + * \ingroup ParamCommon + */ +#define PARAM_ARGS(_field,_seq) \ + BOOST_PP_SEQ_FOR_EACH_I(PARAM_ARGS_,_field,_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,_field,_i,_param) \ + BOOST_PP_COMMA_IF(_i) PARAM_FIELD(_field,_param) + +/** Expand to a list of the given field in the parameter sequence + * + * For example, PARAM_FIELDS(ARG, _seq) expands to: + * \code{.unparsed} + * arg1,arg2 ... + * \endcode + * \ingroup ParamCommon ParamPy + */ +#define PARAM_FIELDS(_field,_seq) \ + BOOST_PP_SEQ_FOR_EACH_I(PARAM_FIELDS_,_field,_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,_field,_i,_param) \ + BOOST_PP_COMMA_IF(_i) PARAM_TYPED(PARAM_CAST_PY_,_param)(PARAM_FIELD(_field,_param)) + +/** Expand to a list of the given field in the sequence + * + * The field will be casted from python C to C type + * \ingroup ParamCommon ParamPy + */ +#define PARAM_PY_FIELDS(_field,_seq) \ + BOOST_PP_SEQ_FOR_EACH_I(PARAM_PY_FIELDS_,_field,_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 ParamCommon 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,_field,_param) \ + PARAM_TYPED(PARAM_PY_TYPE_,_param) PARAM_FIELD(_field,_param); + +/** Declare field variables for Python C type without initialization + * \ingroup ParamPy + */ +#define PARAM_PY_DECLARE(_field,_seq) \ + BOOST_PP_SEQ_FOR_EACH(PARAM_PY_DECLARE_,_field,_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,_field,_param) \ + PARAM_TYPED(PARAM_PY_TYPE_,_param) PARAM_FIELD(_field,_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(_field,_seq) \ + BOOST_PP_SEQ_FOR_EACH(PARAM_PY_DECLARE_INIT_,_field,_seq) + + +/** Helper for #PARAM_REF */ +#define PARAM_REF_(_1,_field,_i,_param) \ + BOOST_PP_COMMA_IF(_i) &PARAM_FIELD(_field,_param) + +/** Generate a list of field references + * + * Expand to: + * \code{.unparsed} + * &_field1, &_field2 ... + * \endcode + * \ingroup ParamPy + */ +#define PARAM_REF(_field,_seq) \ + BOOST_PP_SEQ_FOR_EACH_I(PARAM_REF_,_field,_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 */ +#define PARAM_PY_STRINGIZE(_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_STRINGIZE(NAME,_param),\ + PARAM_TYPED(PARAM_CAST_PYOBJ_,_param)(\ + BOOST_PP_TUPLE_ELEM(1,_args)(PARAM_FIELD(NAME,_param)))); + +/** Populate a Python dict with a structure variable + * + * \arg \c _dict: the Python dictionary object + * \arg \c _src: Optional macro to generate source variable. The signature must + * be _src(_name)<\tt>, where \c _name will be the parameters \a name field. + * In case you just want \c _name as the source variable name, you can simply + * omit this argument, because newer C++ preprocessor allows empty argument. + * + * Roughly translated to: + * \code{.unparsed} + * PyDict_SetItem(_dict,#name1,_src(name1)); + * PyDict_SetItem(_dict,#name2,_src(name2)); + * ... + * \endcode + * \ingroup ParamPy + */ +#define PARAM_PY_DICT_SET_VALUE(_dict,_src,_seq) \ + BOOST_PP_SEQ_FOR_EACH(PARAM_PY_DICT_SET_VALUE_,(_dict,_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,_dict,_param) \ + PyDict_SetItem(_dict, PARAM_PY_STRINGIZE(NAME,_param),\ + PyString_FromString(PARAM_TYPED(PARAM_PY_DICT_DOC_,_param)(_param))); + +/** Populate a Python dict with the doc field of the parameter sequence + * + * Roughly translated to: + * \code{.unparsed} + * PyDict_SetItem(_dict,#name1,doc1); + * PyDict_SetItem(_dict,#name2,doc2); + * ... + * \endcode + * \ingroup ParamDoc + */ +#define PARAM_PY_DICT_SET_DOC(_dict,_seq) \ + BOOST_PP_SEQ_FOR_EACH(PARAM_PY_DICT_SET_DOC_,_dict,_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/Gui/AppPathGui.cpp b/src/Mod/Path/Gui/AppPathGui.cpp index 3d0545634..1e4d93faf 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,8 @@ PyMODINIT_FUNC initPathGui() PathGui::ViewProviderPathCompoundPython ::init(); PathGui::ViewProviderPathShape ::init(); PathGui::ViewProviderPathPython ::init(); + PathGui::ViewProviderArea ::init(); + PathGui::ViewProviderAreaPython ::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 8f90c3e41..2be21706d 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 9dc8085f9..ab0a8296d 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,8 +41,86 @@ #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::vector Sel = + getSelection().getSelectionEx(NULL, Part::Feature::getClassTypeId()); + std::list cmds; + std::ostringstream sources; + if (Sel.size() > 0) { + for(const Gui::SelectionObject &selObj : Sel) { + const Part::Feature *pcObj = static_cast(selObj.getObject()); + if(selObj.getSubNames().empty()) { + const TopoDS_Shape &shape = pcObj->Shape.getShape().getShape(); + TopExp_Explorer it(shape, TopAbs_SHELL); + if(it.More()) { + Base::Console().Error("Selected shape is not 2D\n"); + return; + } + sources << "FreeCAD.activeDocument()." << pcObj->getNameInDocument() << ","; + continue; + } + + for(const std::string &name : selObj.getSubNames()) { + if(!name.compare(0,4,"Face") && + !name.compare(0,4,"Edge")) + { + Base::Console().Error("Selected shape is not 2D\n"); + return; + } + + int index = atoi(name.substr(4).c_str()); + + std::ostringstream subname; + subname << pcObj->getNameInDocument() << '_' << name; + std::string sub_fname = getUniqueObjectName(subname.str().c_str()); + + std::ostringstream cmd; + cmd << "FreeCAD.activeDocument().addObject('Path::Feature','" << sub_fname << "').Shape = " << + pcObj->getNameInDocument() << '.' << name.substr(0,4) << '[' << index-1 << ']'; + cmds.push_back(cmd.str()); + sources << "FreeCAD.activeDocument()." << sub_fname << ","; + } + } + } + std::string FeatName = getUniqueObjectName("FeatureArea"); + openCommand("Create Path Area"); + 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(); +} + // Path compound ##################################################################################################### @@ -95,7 +175,6 @@ bool CmdPathCompound::isActive(void) return hasActiveDocument(); } - // Path Shape ##################################################################################################### @@ -149,4 +228,5 @@ void CreatePathCommands(void) Gui::CommandManager &rcCmdMgr = Gui::Application::Instance->commandManager(); rcCmdMgr.addCommand(new CmdPathCompound()); rcCmdMgr.addCommand(new CmdPathShape()); + rcCmdMgr.addCommand(new CmdPathArea()); } diff --git a/src/Mod/Path/Gui/Resources/Path.qrc b/src/Mod/Path/Gui/Resources/Path.qrc index b6800333c..23035a803 100644 --- a/src/Mod/Path/Gui/Resources/Path.qrc +++ b/src/Mod/Path/Gui/Resources/Path.qrc @@ -46,6 +46,7 @@ icons/Path-ToolChange.svg icons/Path-Toolpath.svg icons/Path-ToolTable.svg + icons/Path-Area.svg icons/preferences-path.svg panels/ContourEdit.ui panels/DlgJobChooser.ui 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 000000000..a620c23b2 --- /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 000000000..2a3057f20 --- /dev/null +++ b/src/Mod/Path/Gui/ViewProviderArea.cpp @@ -0,0 +1,125 @@ +/**************************************************************************** + * 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 ----------------------------------------------------------------------- + +namespace Gui { +/// @cond DOXERR +PROPERTY_SOURCE_TEMPLATE(PathGui::ViewProviderAreaPython, PathGui::ViewProviderArea) +/// @endcond + +// explicit template instantiation +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 000000000..7d3b43313 --- /dev/null +++ b/src/Mod/Path/Gui/ViewProviderArea.h @@ -0,0 +1,60 @@ +/**************************************************************************** + * 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; + +} //namespace PathGui + + +#endif // PATH_ViewProviderArea_H diff --git a/src/Mod/Path/InitGui.py b/src/Mod/Path/InitGui.py index dd0c1d6f3..61c10a7ae 100644 --- a/src/Mod/Path/InitGui.py +++ b/src/Mod/Path/InitGui.py @@ -86,7 +86,7 @@ class PathWorkbench (Workbench): 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_Area"] #modcmdmore = ["Path_Hop",] #remotecmdlist = ["Path_Remote"]