
* Path.fromShapes can now convert any number of shapes to Path with optimzied travel distances. It internally uses Path.sortWires to minimize travel distances, and also sort wires by its Z height in case of sectioned wires. * The above python function is impelmented in Path::Area class. * Path::FeatureShape is rewrote to take advantage of these two functions. * Add Path::FeatureAreaView to partially display a Path::FeatureArea's sections.
353 lines
13 KiB
C++
353 lines
13 KiB
C++
/****************************************************************************
|
|
* Copyright (c) 2017 Zheng, Lei (realthunder) <realthunder.dev@gmail.com>*
|
|
* *
|
|
* 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 <Mod/Part/App/TopoShapePy.h>
|
|
#include <Base/VectorPy.h>
|
|
|
|
#include "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(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",
|
|
|
|
"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),
|
|
},
|
|
{
|
|
"sortWires",
|
|
|
|
"sortWires(index=-1, count=0, start=Vector()" PARAM_PY_ARGS_DOC(ARG,AREA_PARAMS_MIN_DIST) "):\n"
|
|
"Returns a tuple (wires,end): sorted wires with minimized travel distance, and the endpoint of the wires.\n"
|
|
"\n* index (-1): the index of the section. -1 means all sections. No effect on planar shape.\n"
|
|
"\n* count (0): the number of sections to return. <=0 means all sections starting from index.\n"
|
|
"\n* start (Vector()): a vector specifies the start point.\n"
|
|
PARAM_PY_DOC(ARG,AREA_PARAMS_MIN_DIST),
|
|
},
|
|
};
|
|
|
|
struct AreaPyDoc {
|
|
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 PartExport Py::Object shape2pyshape(const TopoDS_Shape &shape);
|
|
}
|
|
|
|
using namespace Path;
|
|
|
|
// returns a string which represents the object e.g. when printed in python
|
|
std::string AreaPy::representation(void) const
|
|
{
|
|
std::stringstream str;
|
|
str << "<Area object at " << getAreaPtr() << ">";
|
|
return str.str();
|
|
}
|
|
|
|
PyObject *AreaPy::PyMake(struct _typeobject *, PyObject *args, PyObject *kwd) // Python wrapper
|
|
{
|
|
std::unique_ptr<AreaPy> ret(new AreaPy(new Area));
|
|
if(!ret->setParams(args,kwd))
|
|
return 0;
|
|
return ret.release();
|
|
}
|
|
|
|
// constructor method
|
|
int AreaPy::PyInit(PyObject* , PyObject* )
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
PyObject* AreaPy::setPlane(PyObject *args) {
|
|
PyObject *pcObj;
|
|
if (!PyArg_ParseTuple(args, "O!", &(Part::TopoShapePy::Type), &pcObj))
|
|
return 0;
|
|
|
|
#define GET_TOPOSHAPE(_p) static_cast<Part::TopoShapePy*>(_p)->getTopoShapePtr()->getShape()
|
|
getAreaPtr()->setPlane(GET_TOPOSHAPE(pcObj));
|
|
return Py_None;
|
|
}
|
|
|
|
PyObject* AreaPy::getShape(PyObject *args, PyObject *keywds)
|
|
{
|
|
PyObject *pcObj = Py_False;
|
|
short index=-1;
|
|
static char *kwlist[] = {"index","rebuild", NULL};
|
|
if (!PyArg_ParseTupleAndKeywords(args, keywds,"|hO",kwlist,&pcObj))
|
|
return 0;
|
|
|
|
if(PyObject_IsTrue(pcObj))
|
|
getAreaPtr()->clean();
|
|
return Py::new_reference_to(Part::shape2pyshape(getAreaPtr()->getShape(index)));
|
|
}
|
|
|
|
PyObject* AreaPy::sortWires(PyObject *args, PyObject *keywds){
|
|
PARAM_PY_DECLARE_INIT(PARAM_FARG,AREA_PARAMS_MIN_DIST)
|
|
short index = -1;
|
|
short count = 0;
|
|
PyObject *start = NULL;
|
|
|
|
static char *kwlist[] = {"index","count","start",
|
|
PARAM_FIELD_STRINGS(ARG,AREA_PARAMS_MIN_DIST), NULL};
|
|
if (!PyArg_ParseTupleAndKeywords(args, keywds,
|
|
"|hhO!" PARAM_PY_KWDS(AREA_PARAMS_MIN_DIST),
|
|
kwlist,&index,&count,&(Base::VectorPy::Type),&start,
|
|
PARAM_REF(PARAM_FARG,AREA_PARAMS_MIN_DIST)))
|
|
return 0;
|
|
|
|
gp_Pnt pstart,pend;
|
|
if(start) {
|
|
Base::Vector3d vec = static_cast<Base::VectorPy*>(start)->value();
|
|
pstart.SetCoord(vec.x, vec.y, vec.z);
|
|
}
|
|
std::list<TopoDS_Shape> wires = getAreaPtr()->sortWires(
|
|
index,count,&pstart,&pend,
|
|
PARAM_PY_FIELDS(PARAM_FARG,AREA_PARAMS_MIN_DIST));
|
|
PyObject *list = PyList_New(0);
|
|
for(auto &wire : wires)
|
|
PyList_Append(list,Py::new_reference_to(
|
|
Part::shape2pyshape(TopoDS::Wire(wire))));
|
|
PyObject *ret = PyTuple_New(2);
|
|
PyTuple_SetItem(ret,0,list);
|
|
PyTuple_SetItem(ret,1,new Base::VectorPy(
|
|
Base::Vector3d(pend.X(),pend.Y(),pend.Z())));
|
|
return ret;
|
|
}
|
|
|
|
PyObject* AreaPy::add(PyObject *args, PyObject *keywds)
|
|
{
|
|
PARAM_PY_DECLARE_INIT(PARAM_FARG,AREA_PARAMS_OPCODE)
|
|
PyObject *pcObj;
|
|
|
|
//Strangely, PyArg_ParseTupleAndKeywords requires all arguments to be keyword based,
|
|
//even non-optional ones? That doesn't make sense in python. Seems only in python 3
|
|
//they added '$' to address that issue.
|
|
static char *kwlist[] = {"shape",PARAM_FIELD_STRINGS(ARG,AREA_PARAMS_OPCODE), NULL};
|
|
|
|
if (!PyArg_ParseTupleAndKeywords(args, keywds,
|
|
"O|" PARAM_PY_KWDS(AREA_PARAMS_OPCODE),
|
|
kwlist,&pcObj,PARAM_REF(PARAM_FARG,AREA_PARAMS_OPCODE)))
|
|
return 0;
|
|
|
|
if (PyObject_TypeCheck(pcObj, &(Part::TopoShapePy::Type))) {
|
|
getAreaPtr()->add(GET_TOPOSHAPE(pcObj),op);
|
|
return Py_None;
|
|
} else if (PyObject_TypeCheck(pcObj, &(PyList_Type)) ||
|
|
PyObject_TypeCheck(pcObj, &(PyTuple_Type))) {
|
|
Py::Sequence shapeSeq(pcObj);
|
|
for (Py::Sequence::iterator it = shapeSeq.begin(); it != shapeSeq.end(); ++it) {
|
|
PyObject* item = (*it).ptr();
|
|
if(!PyObject_TypeCheck(item, &(Part::TopoShapePy::Type))) {
|
|
PyErr_SetString(PyExc_TypeError, "non-shape object in sequence");
|
|
return 0;
|
|
}
|
|
}
|
|
for (Py::Sequence::iterator it = shapeSeq.begin(); it != shapeSeq.end(); ++it){
|
|
PyObject* item = (*it).ptr();
|
|
getAreaPtr()->add(GET_TOPOSHAPE(item),
|
|
PARAM_PY_FIELDS(PARAM_FARG,AREA_PARAMS_OPCODE));
|
|
}
|
|
return Py_None;
|
|
}
|
|
|
|
PyErr_SetString(PyExc_TypeError, "shape must be 'TopoShape' or list of 'TopoShape'");
|
|
return 0;
|
|
}
|
|
|
|
PyObject* AreaPy::makeOffset(PyObject *args, PyObject *keywds)
|
|
{
|
|
//Generate a keyword string defined in the ARG field of OFFSET parameter list
|
|
static char *kwlist[] = {"index",PARAM_FIELD_STRINGS(ARG,AREA_PARAMS_OFFSET), NULL};
|
|
short index = -1;
|
|
|
|
//Declare variables defined in the ARG field of the OFFSET parameter list with
|
|
//initialization to defaults
|
|
PARAM_PY_DECLARE_INIT(PARAM_FARG,AREA_PARAMS_OFFSET)
|
|
|
|
//Parse arguments to overwrite the defaults
|
|
if (!PyArg_ParseTupleAndKeywords(args, keywds,
|
|
"|h" PARAM_PY_KWDS(AREA_PARAMS_OFFSET), kwlist,
|
|
&index,PARAM_REF(PARAM_FARG,AREA_PARAMS_OFFSET)))
|
|
return 0;
|
|
|
|
//Expand the variable as function call arguments
|
|
TopoDS_Shape resultShape = getAreaPtr()->makeOffset(index,
|
|
PARAM_PY_FIELDS(PARAM_FARG,AREA_PARAMS_OFFSET));
|
|
return Py::new_reference_to(Part::shape2pyshape(resultShape));
|
|
}
|
|
|
|
PyObject* AreaPy::makePocket(PyObject *args, PyObject *keywds)
|
|
{
|
|
static char *kwlist[] = {"index",PARAM_FIELD_STRINGS(ARG,AREA_PARAMS_POCKET), NULL};
|
|
short index = -1;
|
|
|
|
PARAM_PY_DECLARE_INIT(PARAM_FARG,AREA_PARAMS_POCKET)
|
|
//Override pocket mode default
|
|
mode = Area::PocketModeZigZagOffset;
|
|
|
|
if (!PyArg_ParseTupleAndKeywords(args, keywds,
|
|
"|h" PARAM_PY_KWDS(AREA_PARAMS_POCKET), kwlist,
|
|
&index,PARAM_REF(PARAM_FARG,AREA_PARAMS_POCKET)))
|
|
return 0;
|
|
|
|
TopoDS_Shape resultShape = getAreaPtr()->makePocket(index,
|
|
PARAM_PY_FIELDS(PARAM_FARG,AREA_PARAMS_POCKET));
|
|
return Py::new_reference_to(Part::shape2pyshape(resultShape));
|
|
}
|
|
|
|
PyObject* AreaPy::setParams(PyObject *args, PyObject *keywds)
|
|
{
|
|
static char *kwlist[] = {PARAM_FIELD_STRINGS(NAME,AREA_PARAMS_CONF),NULL};
|
|
|
|
//Declare variables defined in the NAME field of the CONF parameter list
|
|
PARAM_PY_DECLARE(PARAM_FNAME,AREA_PARAMS_CONF);
|
|
|
|
AreaParams params = getAreaPtr()->getParams();
|
|
|
|
#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(PARAM_FNAME,AREA_PARAMS_CONF)))
|
|
return 0;
|
|
|
|
#define AREA_GET(_param) \
|
|
params.PARAM_FNAME(_param) = \
|
|
PARAM_TYPED(PARAM_CAST_PY_,_param)(PARAM_FNAME(_param));
|
|
//populate 'params' with the CONF variables
|
|
PARAM_FOREACH(AREA_GET,AREA_PARAMS_CONF)
|
|
|
|
getAreaPtr()->setParams(params);
|
|
return Py_None;
|
|
}
|
|
|
|
PyObject* AreaPy::getParams(PyObject *args)
|
|
{
|
|
if (!PyArg_ParseTuple(args, ""))
|
|
return 0;
|
|
|
|
const AreaParams ¶ms =getAreaPtr()->getParams();
|
|
|
|
PyObject *dict = PyDict_New();
|
|
#define AREA_SRC(_param) params.PARAM_FNAME(_param)
|
|
PARAM_PY_DICT_SET_VALUE(dict,NAME,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))
|
|
return 0;
|
|
|
|
if(PyObject_IsTrue(pcObj))
|
|
return PyString_FromString(PARAM_PY_DOC(NAME,AREA_PARAMS_CONF));
|
|
|
|
PyObject *dict = PyDict_New();
|
|
PARAM_PY_DICT_SET_DOC(dict,NAME,AREA_PARAMS_CONF)
|
|
return dict;
|
|
}
|
|
|
|
Py::List AreaPy::getSections(void) const {
|
|
Py::List ret;
|
|
Area *area = getAreaPtr();
|
|
for(size_t i=0,count=area->getSectionCount(); i<count;++i)
|
|
ret.append(Part::shape2pyshape(getAreaPtr()->getShape(i)));
|
|
return ret;
|
|
}
|
|
|
|
// custom attributes get/set
|
|
|
|
PyObject *AreaPy::getCustomAttributes(const char* /*attr*/) const
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
int AreaPy::setCustomAttributes(const char* /*attr*/, PyObject* /*obj*/)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
|