// SPDX-License-Identifier: LGPL-2.1-or-later /**************************************************************************** * 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 #include #include #include // inclusion of the generated files (generated out of AreaPy.xml) #include "PathPy.h" #include "AreaPy.h" #include "AreaPy.cpp" static PyObject* areaAbort(PyObject*, PyObject* args, PyObject* kwd) { static const std::array kwlist {"aborting", nullptr}; PyObject* pObj = Py_True; if (!Base::Wrapped_ParseTupleAndKeywords(args, kwd, "|O!", kwlist, &PyBool_Type, &pObj)) { return nullptr; } Area::abort(Base::asBoolean(pObj)); Py_Return; } static PyObject* areaSetParams(PyObject*, PyObject* args, PyObject* kwd) { static const std::array kwlist { PARAM_FIELD_STRINGS(NAME, AREA_PARAMS_STATIC_CONF), nullptr }; if (args && PySequence_Size(args) > 0) { PyErr_SetString(PyExc_ValueError, "Non-keyword argument is not supported"); } // Declare variables defined in the NAME field of the CONF parameter list PARAM_PY_DECLARE(PARAM_FNAME, AREA_PARAMS_STATIC_CONF); AreaStaticParams params = Area::getDefaultParams(); #define AREA_SET(_param) \ PARAM_FNAME(_param) = PARAM_TYPED(PARAM_PY_CAST_, _param)(params.PARAM_FNAME(_param)); // populate the CONF variables with params PARAM_FOREACH(AREA_SET, AREA_PARAMS_STATIC_CONF) // Parse arguments to overwrite CONF variables if (!Base::Wrapped_ParseTupleAndKeywords( args, kwd, "|" PARAM_PY_KWDS(AREA_PARAMS_STATIC_CONF), kwlist, PARAM_REF(PARAM_FNAME, AREA_PARAMS_STATIC_CONF) )) { return nullptr; } #define AREA_GET(_param) \ params.PARAM_FNAME(_param) = PARAM_TYPED(PARAM_CAST_PY_, _param)(PARAM_FNAME(_param)); // populate 'params' with the CONF variables PARAM_FOREACH(AREA_GET, AREA_PARAMS_STATIC_CONF) Area::setDefaultParams(params); Py_INCREF(Py_None); return Py_None; } static PyObject* areaGetParams(PyObject*, PyObject* args) { if (!PyArg_ParseTuple(args, "")) { return nullptr; } const AreaStaticParams& params = Area::getDefaultParams(); PyObject* dict = PyDict_New(); #define AREA_SRC(_param) params.PARAM_FNAME(_param) PARAM_PY_DICT_SET_VALUE(dict, NAME, AREA_SRC, AREA_PARAMS_STATIC_CONF) return dict; } static PyObject* areaGetParamsDesc(PyObject*, PyObject* args, PyObject* kwd) { PyObject* pcObj = Py_False; static const std::array kwlist {"as_string", nullptr}; if (!Base::Wrapped_ParseTupleAndKeywords(args, kwd, "|O!", kwlist, &PyBool_Type, &pcObj)) { return nullptr; } if (Base::asBoolean(pcObj)) { return PyUnicode_FromString(PARAM_PY_DOC(NAME, AREA_PARAMS_STATIC_CONF)); } PyObject* dict = PyDict_New(); PARAM_PY_DICT_SET_DOC(dict, NAME, AREA_PARAMS_STATIC_CONF) return dict; } static const PyMethodDef areaOverrides[] = { {"setParams", nullptr, 0, "setParam(key=value...): Set algorithm parameters. You can call getParamsDesc() to \n" "get a list of supported parameters and their descriptions.\n" PARAM_PY_DOC(NAME, AREA_PARAMS_CONF)}, { "add", nullptr, 0, "add((shape...)," PARAM_PY_ARGS_DOC( ARG, AREA_PARAMS_OPCODE ) "):\n" "Add TopoShape(s) with given operation code\n" PARAM_PY_DOC( ARG, AREA_PARAMS_OPCODE ) "\nThe first shape's wires will be unioned " "together regardless of the op code given\n" "(except for 'Compound'). Subsequent " "shape's wire will be combined using the " "op code.\n" "All shape wires shall be coplanar, and " "are used to determine a working plane for " "face\n" "making and offsetting. You can call " "setPlane() to supply a reference shape to " "determine\n" "the workplane in case the added shapes " "are all colinear lines.\n", }, { "makeOffset", nullptr, 0, "makeOffset(index=-1, " PARAM_PY_ARGS_DOC( ARG, AREA_PARAMS_OFFSET ) "):\n" "Make an 2D offset of the shape.\n" "\n* index (-1): the index of the section. -1 means all sections. " "No effect on planar shape.\n" PARAM_PY_DOC(ARG, AREA_PARAMS_OFFSET), }, { "makePocket", nullptr, 0, "makePocket(index=-1, " PARAM_PY_ARGS_DOC( ARG, AREA_PARAMS_POCKET ) "):\n" "Generate pocket toolpath of the shape.\n" "\n* index (-1): the index of the section. -1 means all sections. " "No effect on planar shape.\n" PARAM_PY_DOC(ARG, AREA_PARAMS_POCKET), }, { "makeSections", nullptr, 0, "makeSections(" PARAM_PY_ARGS_DOC( ARG, AREA_PARAMS_SECTION_EXTRA ) ", heights=[], plane=None):\n" "Make a list of area holding the sectioned children shapes " "on given heights\n" PARAM_PY_DOC( ARG, AREA_PARAMS_SECTION_EXTRA ) "\n* heights ([]): a list of " "section heights, the " "meaning of the value is " "determined by 'mode'.\n" "If not specified, the " "current SectionCount, and " "SectionOffset of this Area " "is used.\n" "\n* plane (None): optional " "shape to specify a section " "plane. If not give, the " "current workplane\n" "of this Area is used if " "section mode is " "'Workplane'.", }, { "getClearedArea", nullptr, 0, "getClearedArea(path, diameter, zmax, bbox):\n" "Gets the area cleared when a tool of the specified diameter follows the gcode represented " "in the path, ignoring cleared space above zmax and path segments that don't affect space " "within the x/y space of bbox.\n", }, { "getRestArea", nullptr, 0, "getRestArea(clearedAreas, diameter):\n" "Rest machining: gets the area left to be machined, assuming some of this area has already " "been cleared previous tool paths.\n" "clearedAreas: the regions already cleared.\n" "diameter: the tool diameter that finishes clearing this area.\n", }, {"toTopoShape", nullptr, 0, "toTopoShape():\n"}, {"setDefaultParams", reinterpret_cast(reinterpret_cast(areaSetParams)), METH_VARARGS | METH_KEYWORDS | METH_STATIC, "setDefaultParams(key=value...):\n" "Static method to set the default parameters of all following Path.Area, plus the following\n" "additional parameters.\n"}, {"getDefaultParams", (PyCFunction)areaGetParams, METH_VARARGS | METH_STATIC, "getDefaultParams(): Static method to return the current default parameters."}, { "abort", reinterpret_cast(reinterpret_cast(areaAbort)), METH_VARARGS | METH_KEYWORDS | METH_STATIC, "abort(aborting=True): Static method to abort any ongoing operation\n" "\nTo ensure no stray abortion is left in the previous operation, it is advised to " "manually clear\n" "the aborting flag by calling abort(False) before starting a new operation.", }, {"getParamsDesc", reinterpret_cast(reinterpret_cast(areaGetParamsDesc)), METH_VARARGS | METH_KEYWORDS | METH_STATIC, "getParamsDesc(as_string=False): Returns a list of supported parameters and their " "descriptions.\n" "\n* as_string: if False, then return a dictionary of documents of all supported parameters."}, }; struct AreaPyModifier { AreaPyModifier() { for (auto& method : Path::AreaPy::Methods) { if (!method.ml_name) { continue; } for (auto& entry : areaOverrides) { if (std::strcmp(method.ml_name, entry.ml_name) == 0) { if (entry.ml_doc) { method.ml_doc = entry.ml_doc; } if (entry.ml_meth) { method.ml_meth = entry.ml_meth; } if (entry.ml_flags) { method.ml_flags = entry.ml_flags; } break; } } } } }; static AreaPyModifier mod; using namespace Path; // returns a string which represents the object e.g. when printed in python std::string AreaPy::representation() const { std::stringstream str; str << ""; return str.str(); } PyObject* AreaPy::PyMake(struct _typeobject*, PyObject* args, PyObject* kwd) // Python wrapper { AreaPy* ret = new AreaPy(new Area); if (!ret->setParams(args, kwd)) { Py_DecRef(ret); return nullptr; } // If setParams() was successful it increments the ref counter. // So, it must be decremented again. Py_DecRef(ret); return ret; } // constructor method int AreaPy::PyInit(PyObject*, PyObject*) { return 0; } PyObject* AreaPy::setPlane(PyObject* args) { PyObject* pcObj; if (!PyArg_ParseTuple(args, "O!", &(Part::TopoShapePy::Type), &pcObj)) { return nullptr; } #define GET_TOPOSHAPE(_p) static_cast(_p)->getTopoShapePtr()->getShape() getAreaPtr()->setPlane(GET_TOPOSHAPE(pcObj)); Py_INCREF(this); return this; } PyObject* AreaPy::getShape(PyObject* args, PyObject* keywds) { PyObject* pcObj = Py_False; short index = -1; static const std::array kwlist {"index", "rebuild", nullptr}; if (!Base::Wrapped_ParseTupleAndKeywords(args, keywds, "|hO!", kwlist, &index, &PyBool_Type, &pcObj)) { return nullptr; } PY_TRY { if (Base::asBoolean(pcObj)) { getAreaPtr()->clean(); } return Py::new_reference_to(Part::shape2pyshape(getAreaPtr()->getShape(index))); } PY_CATCH_OCC } PyObject* AreaPy::add(PyObject* args, PyObject* keywds) { PARAM_PY_DECLARE_INIT(PARAM_FARG, AREA_PARAMS_OPCODE) PyObject* pcObj; // Strangely, PyArg_ParseTupleAndKeywords requires all arguments to be keyword based, // even non-optional ones? That doesn't make sense in python. Seems only in python 3 // they added '$' to address that issue. static const std::array kwlist { "shape", PARAM_FIELD_STRINGS(ARG, AREA_PARAMS_OPCODE), nullptr }; if (!Base::Wrapped_ParseTupleAndKeywords( args, keywds, "O|" PARAM_PY_KWDS(AREA_PARAMS_OPCODE), kwlist, &pcObj, PARAM_REF(PARAM_FARG, AREA_PARAMS_OPCODE) )) { return nullptr; } PY_TRY { if (PyObject_TypeCheck(pcObj, &(Part::TopoShapePy::Type))) { getAreaPtr()->add(GET_TOPOSHAPE(pcObj), op); Py_INCREF(this); return this; } else if (PyObject_TypeCheck(pcObj, &(PyList_Type)) || PyObject_TypeCheck(pcObj, &(PyTuple_Type))) { Py::Sequence shapeSeq(pcObj); for (Py::Sequence::iterator it = shapeSeq.begin(); it != shapeSeq.end(); ++it) { PyObject* item = (*it).ptr(); if (!PyObject_TypeCheck(item, &(Part::TopoShapePy::Type))) { PyErr_SetString(PyExc_TypeError, "non-shape object in sequence"); return nullptr; } } for (Py::Sequence::iterator it = shapeSeq.begin(); it != shapeSeq.end(); ++it) { PyObject* item = (*it).ptr(); getAreaPtr()->add(GET_TOPOSHAPE(item), PARAM_PY_FIELDS(PARAM_FARG, AREA_PARAMS_OPCODE)); } Py_INCREF(this); return this; } } PY_CATCH_OCC PyErr_SetString(PyExc_TypeError, "shape must be 'TopoShape' or list of 'TopoShape'"); return nullptr; } PyObject* AreaPy::makeOffset(PyObject* args, PyObject* keywds) { // Generate a keyword string defined in the ARG field of OFFSET parameter list static const std::array kwlist { "index", PARAM_FIELD_STRINGS(ARG, AREA_PARAMS_OFFSET), nullptr }; short index = -1; // Declare variables defined in the ARG field of the OFFSET parameter list with // initialization to defaults PARAM_PY_DECLARE_INIT(PARAM_FARG, AREA_PARAMS_OFFSET) // Parse arguments to overwrite the defaults if (!Base::Wrapped_ParseTupleAndKeywords( args, keywds, "|h" PARAM_PY_KWDS(AREA_PARAMS_OFFSET), kwlist, &index, PARAM_REF(PARAM_FARG, AREA_PARAMS_OFFSET) )) { return nullptr; } PY_TRY { // Expand the variable as function call arguments TopoDS_Shape resultShape = getAreaPtr()->makeOffset(index, PARAM_PY_FIELDS(PARAM_FARG, AREA_PARAMS_OFFSET)); return Py::new_reference_to(Part::shape2pyshape(resultShape)); } PY_CATCH_OCC } PyObject* AreaPy::makePocket(PyObject* args, PyObject* keywds) { static const std::array kwlist { "index", PARAM_FIELD_STRINGS(ARG, AREA_PARAMS_POCKET), nullptr }; short index = -1; PARAM_PY_DECLARE_INIT(PARAM_FARG, AREA_PARAMS_POCKET) // Override pocket mode default mode = Area::PocketModeZigZagOffset; if (!Base::Wrapped_ParseTupleAndKeywords( args, keywds, "|h" PARAM_PY_KWDS(AREA_PARAMS_POCKET), kwlist, &index, PARAM_REF(PARAM_FARG, AREA_PARAMS_POCKET) )) { return nullptr; } PY_TRY { TopoDS_Shape resultShape = getAreaPtr()->makePocket(index, PARAM_PY_FIELDS(PARAM_FARG, AREA_PARAMS_POCKET)); return Py::new_reference_to(Part::shape2pyshape(resultShape)); } PY_CATCH_OCC } PyObject* AreaPy::makeSections(PyObject* args, PyObject* keywds) { static const std::array kwlist { PARAM_FIELD_STRINGS(ARG, AREA_PARAMS_SECTION_EXTRA), "heights", "plane", nullptr }; PyObject* heights = nullptr; PyObject* plane = nullptr; PARAM_PY_DECLARE_INIT(PARAM_FARG, AREA_PARAMS_SECTION_EXTRA) if (!Base::Wrapped_ParseTupleAndKeywords( args, keywds, "|" PARAM_PY_KWDS(AREA_PARAMS_SECTION_EXTRA) "OO!", kwlist, PARAM_REF(PARAM_FARG, AREA_PARAMS_SECTION_EXTRA), &heights, &(Part::TopoShapePy::Type), &plane )) { return nullptr; } PY_TRY { std::vector h; if (heights) { if (PyObject_TypeCheck(heights, &(PyFloat_Type))) { h.push_back(PyFloat_AsDouble(heights)); } else if (PyObject_TypeCheck(heights, &(PyList_Type)) || PyObject_TypeCheck(heights, &(PyTuple_Type))) { Py::Sequence shapeSeq(heights); h.reserve(shapeSeq.size()); for (Py::Sequence::iterator it = shapeSeq.begin(); it != shapeSeq.end(); ++it) { PyObject* item = (*it).ptr(); if (!PyObject_TypeCheck(item, &(PyFloat_Type))) { PyErr_SetString(PyExc_TypeError, "heights must only contain float type"); return nullptr; } h.push_back(PyFloat_AsDouble(item)); } } else { PyErr_SetString(PyExc_TypeError, "heights must be of type float or list/tuple of float"); return nullptr; } } std::vector> sections = getAreaPtr()->makeSections( PARAM_PY_FIELDS(PARAM_FARG, AREA_PARAMS_SECTION_EXTRA), h, plane ? GET_TOPOSHAPE(plane) : TopoDS_Shape() ); Py::List ret; for (auto& area : sections) { ret.append(Py::asObject(new AreaPy(new Area(*area, true)))); } return Py::new_reference_to(ret); } PY_CATCH_OCC } PyObject* AreaPy::getClearedArea(PyObject* args) {PY_TRY {PyObject * pyPath, *pyBbox; double diameter, zmax; if (!PyArg_ParseTuple(args, "OddO", &pyPath, &diameter, &zmax, &pyBbox)) { return nullptr; } if (!PyObject_TypeCheck(pyPath, &(PathPy::Type))) { PyErr_SetString(PyExc_TypeError, "path must be of type PathPy"); return nullptr; } if (!PyObject_TypeCheck(pyBbox, &(Base::BoundBoxPy::Type))) { PyErr_SetString(PyExc_TypeError, "bbox must be of type BoundBoxPy"); return nullptr; } const PathPy* path = static_cast(pyPath); const Py::BoundingBox bbox(pyBbox, false); std::shared_ptr clearedArea = getAreaPtr()->getClearedArea(path->getToolpathPtr(), diameter, zmax, bbox.getValue()); auto pyClearedArea = Py::asObject(new AreaPy(new Area(*clearedArea, true))); return Py::new_reference_to(pyClearedArea); } PY_CATCH_OCC } PyObject* AreaPy::getRestArea(PyObject* args) {PY_TRY {PyObject * pyClearedAreas; std::vector> clearedAreas; double diameter; if (!PyArg_ParseTuple(args, "Od", &pyClearedAreas, &diameter)) { return nullptr; } if (pyClearedAreas && PyObject_TypeCheck(pyClearedAreas, &PyList_Type)) { Py::Sequence clearedAreasSeq(pyClearedAreas); clearedAreas.reserve(clearedAreasSeq.size()); for (Py::Sequence::iterator it = clearedAreasSeq.begin(); it != clearedAreasSeq.end(); ++it) { PyObject* item = (*it).ptr(); if (!PyObject_TypeCheck(item, &(AreaPy::Type))) { PyErr_SetString(PyExc_TypeError, "cleared areas must only contain AreaPy type"); return nullptr; } clearedAreas.push_back(std::make_shared(*static_cast(item)->getAreaPtr(), true)); } } else { PyErr_SetString(PyExc_TypeError, "clearedAreas must be of type list of AreaPy"); return nullptr; } std::shared_ptr restArea = getAreaPtr()->getRestArea(clearedAreas, diameter); if (!restArea) { return Py_None; } auto pyRestArea = Py::asObject(new AreaPy(new Area(*restArea, true))); return Py::new_reference_to(pyRestArea); } PY_CATCH_OCC } PyObject* AreaPy::toTopoShape(PyObject* args) {PY_TRY {if (!PyArg_ParseTuple(args, "")) return nullptr; return Py::new_reference_to(Part::shape2pyshape(getAreaPtr()->toTopoShape())); } PY_CATCH_OCC } PyObject* AreaPy::setDefaultParams(PyObject*, PyObject*) { return nullptr; } PyObject* AreaPy::setParams(PyObject* args, PyObject* keywds) { static const std::array kwlist { PARAM_FIELD_STRINGS(NAME, AREA_PARAMS_CONF), nullptr }; // Declare variables defined in the NAME field of the CONF parameter list PARAM_PY_DECLARE(PARAM_FNAME, AREA_PARAMS_CONF); AreaParams params = getAreaPtr()->getParams(); // populate the CONF variables with params PARAM_FOREACH(AREA_SET, AREA_PARAMS_CONF) // Parse arguments to overwrite CONF variables if (!Base::Wrapped_ParseTupleAndKeywords( args, keywds, "|" PARAM_PY_KWDS(AREA_PARAMS_CONF), kwlist, PARAM_REF(PARAM_FNAME, AREA_PARAMS_CONF) )) { return nullptr; } PY_TRY { // populate 'params' with the CONF variables PARAM_FOREACH(AREA_GET, AREA_PARAMS_CONF) getAreaPtr()->setParams(params); Py_INCREF(this); return this; } PY_CATCH_OCC } PyObject* AreaPy::getParams(PyObject* args) { if (!PyArg_ParseTuple(args, "")) { return nullptr; } const AreaParams& params = getAreaPtr()->getParams(); PyObject* dict = PyDict_New(); PARAM_PY_DICT_SET_VALUE(dict, NAME, AREA_SRC, AREA_PARAMS_CONF) return dict; } PyObject* AreaPy::getDefaultParams(PyObject*) { return nullptr; } PyObject* AreaPy::abort(PyObject*, PyObject*) { return nullptr; } PyObject* AreaPy::getParamsDesc(PyObject*, PyObject*) { return nullptr; } Py::List AreaPy::getSections() const { Py::List ret; Area* area = getAreaPtr(); for (size_t i = 0, count = area->getSectionCount(); i < count; ++i) { ret.append(Part::shape2pyshape(getAreaPtr()->getShape(i))); } return ret; } Py::List AreaPy::getShapes() const { Py::List ret; Area* area = getAreaPtr(); const std::list& shapes = area->getChildren(); for (auto& s : shapes) { ret.append(Py::TupleN(Part::shape2pyshape(s.shape), Py::Long(s.op))); } return ret; } Py::Object AreaPy::getWorkplane() const { return Part::shape2pyshape(getAreaPtr()->getPlane()); } void AreaPy::setWorkplane(Py::Object obj) { PyObject* p = obj.ptr(); if (!PyObject_TypeCheck(p, &(Part::TopoShapePy::Type))) { std::string error = std::string("type must be 'TopoShape', not "); error += p->ob_type->tp_name; throw Py::TypeError(error); } getAreaPtr()->setPlane(GET_TOPOSHAPE(p)); } // custom attributes get/set PyObject* AreaPy::getCustomAttributes(const char* /*attr*/) const { return nullptr; } int AreaPy::setCustomAttributes(const char* /*attr*/, PyObject* /*obj*/) { return 0; }