Path: added Path.fromShapes and Path.sortWires

* Path.fromShapes can now convert any number of shapes to Path with
optimzied travel distances. It internally uses Path.sortWires to
minimize travel distances, and also sort wires by its Z height in case
of sectioned wires.

* The above python function is impelmented in Path::Area class.

* Path::FeatureShape is rewrote to take advantage of these two
functions.

* Add Path::FeatureAreaView to partially display a Path::FeatureArea's
sections.
This commit is contained in:
Zheng, Lei 2017-01-27 17:13:16 +08:00
parent 79a261e868
commit a3f46a40e9
26 changed files with 2093 additions and 378 deletions

View File

@ -88,4 +88,6 @@ PyMODINIT_FUNC initPath()
Path::Area ::init();
Path::FeatureArea ::init();
Path::FeatureAreaPython ::init();
Path::FeatureAreaView ::init();
Path::FeatureAreaViewPython ::init();
}

View File

@ -37,6 +37,7 @@
#include <App/DocumentObjectPy.h>
#include <App/Application.h>
#include <Mod/Part/App/OCCError.h>
#include <Mod/Part/App/TopoShape.h>
#include <Mod/Part/App/TopoShapePy.h>
#include <TopoDS.hxx>
@ -57,7 +58,45 @@
#include "Path.h"
#include "FeaturePath.h"
#include "FeaturePathCompound.h"
#include "Area.h"
#define PATH_CATCH catch (Standard_Failure &e) \
{ \
std::string str; \
Standard_CString msg = e.GetMessageString(); \
str += typeid(e).name(); \
str += " "; \
if (msg) {str += msg;} \
else {str += "No OCCT Exception Message";} \
Base::Console().Error(str.c_str()); \
PyErr_SetString(Part::PartExceptionOCCError,str.c_str()); \
} \
catch(Base::Exception &e) \
{ \
std::string str; \
str += "FreeCAD exception thrown ("; \
str += e.what(); \
str += ")"; \
e.ReportException(); \
PyErr_SetString(Base::BaseExceptionFreeCADError,str.c_str());\
} \
catch(std::exception &e) \
{ \
std::string str; \
str += "STL exception thrown ("; \
str += e.what(); \
str += ")"; \
Base::Console().Error(str.c_str()); \
PyErr_SetString(Base::BaseExceptionFreeCADError,str.c_str());\
} \
catch(const char *e) \
{ \
PyErr_SetString(Base::BaseExceptionFreeCADError,e); \
} throw Py::Exception();
namespace Part {
extern PartExport Py::Object shape2pyshape(const TopoDS_Shape &shape);
}
namespace Path {
class Module : public Py::ExtensionModule<Module>
@ -79,6 +118,22 @@ public:
add_varargs_method("fromShape",&Module::fromShape,
"fromShape(Shape): Returns a Path object from a Part Shape"
);
add_keyword_method("fromShapes",&Module::fromShapes,
"fromShapes(shapes, sort=True, start=Vector(), " PARAM_PY_ARGS_DOC(ARG,AREA_PARAMS_PATH) ")\n"
"\nReturns a Path object from a list of shapes\n"
"\n* shapes: input list of shapes.\n"
"\n* start (Vector()): optional start position.\n"
PARAM_PY_DOC(ARG, AREA_PARAMS_PATH)
);
add_keyword_method("sortWires",&Module::sortWires,
"sortWires(shapes, start=Vector(), params=None, " PARAM_PY_ARGS_DOC(ARG,AREA_PARAMS_SORT_WIRES) ")\n"
"\nReturns (wires,end), where 'wires' is sorted accross Z value and with optimized travel distance,\n"
"and 'end' is the ending position of the whole wires\n"
"\n* shapes: input shape list\n"
"\n* start (Vector()): optional start position.\n"
"\n* params (None): optional dictionary for configuring Path.Area internally used to sort the wires.\n"
PARAM_PY_DOC(ARG, AREA_PARAMS_SORT_WIRES)
);
initialize("This module is the Path module."); // register with Python
}
@ -262,6 +317,118 @@ private:
}
}
Py::Object fromShapes(const Py::Tuple& args, const Py::Dict &kwds)
{
PARAM_PY_DECLARE_INIT(PARAM_FARG,AREA_PARAMS_PATH)
PyObject *pShapes=NULL;
PyObject *start=NULL;
static char* kwd_list[] = {"shapes", "start",
PARAM_FIELD_STRINGS(ARG,AREA_PARAMS_PATH), NULL};
if (!PyArg_ParseTupleAndKeywords(args.ptr(), kwds.ptr(),
"O|O!" PARAM_PY_KWDS(AREA_PARAMS_PATH), kwd_list,
&pShapes, &(Base::VectorPy::Type), &start,
PARAM_REF(PARAM_FARG,AREA_PARAMS_PATH)))
throw Py::Exception();
std::list<TopoDS_Shape> shapes;
if (PyObject_TypeCheck(pShapes, &(Part::TopoShapePy::Type)))
shapes.push_back(static_cast<Part::TopoShapePy*>(pShapes)->getTopoShapePtr()->getShape());
else if (PyObject_TypeCheck(pShapes, &(PyList_Type)) ||
PyObject_TypeCheck(pShapes, &(PyTuple_Type)))
{
Py::Sequence shapeSeq(pShapes);
for (Py::Sequence::iterator it = shapeSeq.begin(); it != shapeSeq.end(); ++it) {
PyObject* item = (*it).ptr();
if(!PyObject_TypeCheck(item, &(Part::TopoShapePy::Type))) {
PyErr_SetString(PyExc_TypeError, "non-shape object in sequence");
throw Py::Exception();
}
shapes.push_back(static_cast<Part::TopoShapePy*>(item)->getTopoShapePtr()->getShape());
}
}
gp_Pnt pstart;
if(start) {
Base::Vector3d vec = static_cast<Base::VectorPy*>(start)->value();
pstart.SetCoord(vec.x, vec.y, vec.z);
}
try {
std::unique_ptr<Toolpath> path(new Toolpath);
Area::toPath(*path,shapes,&pstart,PARAM_PY_FIELDS(PARAM_FARG,AREA_PARAMS_PATH));
return Py::asObject(new PathPy(path.release()));
} PATH_CATCH
}
Py::Object sortWires(const Py::Tuple& args, const Py::Dict &kwds)
{
AreaParams params;
PARAM_PY_DECLARE_INIT(PARAM_FARG,AREA_PARAMS_SORT_WIRES)
PyObject *pShapes=NULL;
PyObject *start=NULL;
PyObject *pParams=NULL;
static char* kwd_list[] = {"shapes", "start", "params",
PARAM_FIELD_STRINGS(ARG,AREA_PARAMS_SORT_WIRES), NULL};
if (!PyArg_ParseTupleAndKeywords(args.ptr(), kwds.ptr(),
"O|O!O!" PARAM_PY_KWDS(AREA_PARAMS_SORT_WIRES), kwd_list,
&pShapes, &(Base::VectorPy::Type), &start, &PyDict_Type, &pParams,
PARAM_REF(PARAM_FARG,AREA_PARAMS_SORT_WIRES)))
throw Py::Exception();
std::list<TopoDS_Shape> shapes;
if (PyObject_TypeCheck(pShapes, &(Part::TopoShapePy::Type)))
shapes.push_back(static_cast<Part::TopoShapePy*>(pShapes)->getTopoShapePtr()->getShape());
else if (PyObject_TypeCheck(pShapes, &(PyList_Type)) ||
PyObject_TypeCheck(pShapes, &(PyTuple_Type))) {
Py::Sequence shapeSeq(pShapes);
for (Py::Sequence::iterator it = shapeSeq.begin(); it != shapeSeq.end(); ++it) {
PyObject* item = (*it).ptr();
if(!PyObject_TypeCheck(item, &(Part::TopoShapePy::Type))) {
PyErr_SetString(PyExc_TypeError, "non-shape object in sequence");
throw Py::Exception();
}
shapes.push_back(static_cast<Part::TopoShapePy*>(item)->getTopoShapePtr()->getShape());
}
}
if(pParams) {
static char *kwlist[] = {PARAM_FIELD_STRINGS(NAME,AREA_PARAMS_CONF),NULL};
PARAM_PY_DECLARE(PARAM_FNAME,AREA_PARAMS_CONF);
#define AREA_SET(_param) \
PARAM_FNAME(_param) = \
PARAM_TYPED(PARAM_PY_CAST_,_param)(params.PARAM_FNAME(_param));
PARAM_FOREACH(AREA_SET,AREA_PARAMS_CONF)
if (!PyArg_ParseTupleAndKeywords(NULL, pParams,
"|" PARAM_PY_KWDS(AREA_PARAMS_CONF), kwlist,
PARAM_REF(PARAM_FNAME,AREA_PARAMS_CONF)))
throw Py::Exception();
#define AREA_GET(_param) \
params.PARAM_FNAME(_param) = \
PARAM_TYPED(PARAM_CAST_PY_,_param)(PARAM_FNAME(_param));
PARAM_FOREACH(AREA_GET,AREA_PARAMS_CONF)
}
gp_Pnt pstart,pend;
if(start) {
Base::Vector3d vec = static_cast<Base::VectorPy*>(start)->value();
pstart.SetCoord(vec.x, vec.y, vec.z);
}
try {
std::list<TopoDS_Shape> wires = Area::sortWires(shapes,&params,&pstart,
&pend, PARAM_PY_FIELDS(PARAM_FARG,AREA_PARAMS_SORT_WIRES));
PyObject *list = PyList_New(0);
for(auto &wire : wires)
PyList_Append(list,Py::new_reference_to(
Part::shape2pyshape(TopoDS::Wire(wire))));
PyObject *ret = PyTuple_New(2);
PyTuple_SetItem(ret,0,list);
PyTuple_SetItem(ret,1,new Base::VectorPy(
Base::Vector3d(pend.X(),pend.Y(),pend.Z())));
return Py::asObject(ret);
} PATH_CATCH
}
};
PyObject* initModule()

View File

@ -24,6 +24,8 @@
#ifndef _PreComp_
#endif
#include <boost/range/adaptor/reversed.hpp>
#include <BRepLib.hxx>
#include <BRep_Builder.hxx>
#include <BRep_Tool.hxx>
@ -150,6 +152,7 @@ void Area::setPlane(const TopoDS_Shape &shape) {
}
bool Area::isCoplanar(const TopoDS_Shape &s1, const TopoDS_Shape &s2) {
if(s1.IsEqual(s2)) return true;
TopoDS_Builder builder;
TopoDS_Compound comp;
builder.MakeCompound(comp);
@ -161,7 +164,7 @@ bool Area::isCoplanar(const TopoDS_Shape &s1, const TopoDS_Shape &s2) {
int Area::add(CArea &area, const TopoDS_Shape &shape, const gp_Trsf *trsf,
double deflection, const TopoDS_Shape *plane, bool force_coplanar,
CArea *areaOpen, bool to_edges, bool reorder)
CArea *areaOpen, bool to_edges, bool reorient)
{
bool haveShape = false;
int skipped = 0;
@ -193,7 +196,7 @@ int Area::add(CArea &area, const TopoDS_Shape &shape, const gp_Trsf *trsf,
else if(to_edges) {
for (TopExp_Explorer it(wire, TopAbs_EDGE); it.More(); it.Next())
add(_areaOpen,BRepBuilderAPI_MakeWire(
TopoDS::Edge(it.Current())).Wire(),trsf,deflection);
TopoDS::Edge(it.Current())).Wire(),trsf,deflection,true);
}else
add(_areaOpen,wire,trsf,deflection);
}
@ -210,7 +213,7 @@ int Area::add(CArea &area, const TopoDS_Shape &shape, const gp_Trsf *trsf,
}
}
if(reorder)
if(reorient)
_area.Reorder();
area.m_curves.splice(area.m_curves.end(),_area.m_curves);
if(areaOpen)
@ -221,7 +224,7 @@ int Area::add(CArea &area, const TopoDS_Shape &shape, const gp_Trsf *trsf,
}
void Area::add(CArea &area, const TopoDS_Wire& wire,
const gp_Trsf *trsf, double deflection)
const gp_Trsf *trsf, double deflection, bool to_edges)
{
CCurve ccurve;
BRepTools_WireExplorer xp(trsf?TopoDS::Wire(
@ -239,26 +242,32 @@ void Area::add(CArea &area, const TopoDS_Wire& wire,
switch (curve.GetType()) {
case GeomAbs_Line: {
ccurve.append(CVertex(Point(p.X(),p.Y())));
if(to_edges) {
area.append(ccurve);
ccurve.m_vertices.pop_front();
}
break;
} case GeomAbs_Circle:{
double first = curve.FirstParameter();
double last = curve.LastParameter();
gp_Circ circle = curve.Circle();
gp_Ax1 axis = circle.Axis();
int dir = axis.Direction().Z()<0?-1:1;
if(reversed) dir = -dir;
gp_Pnt loc = axis.Location();
if(fabs(first-last)>M_PI) {
// Split arc(circle) larger than half circle. This is
// translated from PathUtil code. Not sure why it is
// needed.
gp_Pnt mid = curve.Value((last-first)*0.5+first);
ccurve.append(CVertex(dir,Point(mid.X(),mid.Y()),
if(!to_edges) {
double first = curve.FirstParameter();
double last = curve.LastParameter();
gp_Circ circle = curve.Circle();
gp_Ax1 axis = circle.Axis();
int dir = axis.Direction().Z()<0?-1:1;
if(reversed) dir = -dir;
gp_Pnt loc = axis.Location();
if(fabs(first-last)>M_PI) {
// Split arc(circle) larger than half circle. Because gcode
// can't handle full circle?
gp_Pnt mid = curve.Value((last-first)*0.5+first);
ccurve.append(CVertex(dir,Point(mid.X(),mid.Y()),
Point(loc.X(),loc.Y())));
}
ccurve.append(CVertex(dir,Point(p.X(),p.Y()),
Point(loc.X(),loc.Y())));
break;
}
ccurve.append(CVertex(dir,Point(p.X(),p.Y()),
Point(loc.X(),loc.Y())));
break;
//fall through
} default: {
// Discretize all other type of curves
GCPnts_UniformDeflection discretizer(curve, deflection,
@ -268,16 +277,22 @@ void Area::add(CArea &area, const TopoDS_Wire& wire,
for (int i=1; i<=nbPoints; i++) {
gp_Pnt pt = discretizer.Value (i);
ccurve.append(CVertex(Point(pt.X(),pt.Y())));
if(to_edges) {
area.append(ccurve);
ccurve.m_vertices.pop_front();
}
}
}else
Standard_Failure::Raise("Curve discretization failed");
}}
}
if(BRep_Tool::IsClosed(wire) && !ccurve.IsClosed()) {
Base::Console().Warning("ccurve not closed\n");
ccurve.append(ccurve.m_vertices.front());
if(!to_edges) {
if(BRep_Tool::IsClosed(wire) && !ccurve.IsClosed()) {
Base::Console().Warning("ccurve not closed\n");
ccurve.append(ccurve.m_vertices.front());
}
area.append(ccurve);
}
area.append(ccurve);
}
@ -296,10 +311,32 @@ void Area::clean(bool deleteShapes) {
}
void Area::add(const TopoDS_Shape &shape,short op) {
#define AREA_SRC_OP(_param) op
PARAM_ENUM_CONVERT(AREA_SRC_OP,PARAM_FNAME,PARAM_ENUM_EXCEPT,AREA_PARAMS_OPCODE);
Q_UNUSED(Operation);
#define AREA_CONVERT_OP \
ClipperLib::ClipType Operation;\
switch(op){\
case OperationUnion:\
Operation = ClipperLib::ctUnion;\
break;\
case OperationDifference:\
Operation = ClipperLib::ctDifference;\
break;\
case OperationIntersection:\
Operation = ClipperLib::ctIntersection;\
break;\
case OperationXor:\
Operation = ClipperLib::ctXor;\
break;\
default:\
throw Base::ValueError("invalid Operation");\
}
if(shape.IsNull())
throw Base::ValueError("null shape");
if(op!=OperationCompound) {
AREA_CONVERT_OP;
Q_UNUSED(Operation);
}
bool haveSolid = false;
for(TopExp_Explorer it(shape, TopAbs_SOLID);it.More();) {
haveSolid = true;
@ -313,7 +350,7 @@ void Area::add(const TopoDS_Shape &shape,short op) {
myHaveSolid = haveSolid;
clean();
if(myShapes.empty())
if(op!=OperationCompound && myShapes.empty())
op = OperationUnion;
myShapes.push_back(Shape(op,shape));
}
@ -330,7 +367,7 @@ void Area::setParams(const AreaParams &params) {
}
void Area::addToBuild(CArea &area, const TopoDS_Shape &shape) {
if(!myHaveFace) {
if(myParams.Fill==FillAuto && !myHaveFace) {
TopExp_Explorer it(shape, TopAbs_FACE);
myHaveFace = it.More();
}
@ -342,7 +379,7 @@ void Area::addToBuild(CArea &area, const TopoDS_Shape &shape) {
CArea areaOpen;
mySkippedShapes += add(area,shape,&myTrsf,myParams.Deflection,plane,
myHaveSolid||myParams.Coplanar==CoplanarForce,&areaOpen,
myParams.OpenMode==OpenModeEdges,myParams.Reorder);
myParams.OpenMode==OpenModeEdges,myParams.Reorient);
if(areaOpen.m_curves.size()) {
if(&area == myArea.get() || myParams.OpenMode == OpenModeNone)
myAreaOpen->m_curves.splice(myAreaOpen->m_curves.end(),areaOpen.m_curves);
@ -359,7 +396,7 @@ void Area::build() {
if(myArea || mySections.size()) return;
if(myShapes.empty())
throw Base::ValueError("Null shape");
throw Base::ValueError("no shape added");
#define AREA_SRC(_param) myParams.PARAM_FNAME(_param)
PARAM_ENUM_CONVERT(AREA_SRC,PARAM_FNAME,PARAM_ENUM_EXCEPT,AREA_PARAMS_CLIPPER_FILL);
@ -368,24 +405,41 @@ void Area::build() {
myShapePlane.Nullify();
for(const Shape &s : myShapes) {
bool haveShape = false;
bool done = false;
TopoDS_Shape shapePlane;
gp_Trsf trsf;
gp_Ax3 pos;
#define AREA_CHECK_PLANE(_type) \
shapePlane.Nullify();\
for(TopExp_Explorer it(s.shape, TopAbs_##_type); it.More(); it.Next()) {\
haveShape = true;\
BRepLib_FindSurface planeFinder(it.Current(),-1,Standard_True);\
if (!planeFinder.Found())\
continue;\
myShapePlane = it.Current();\
myTrsf.SetTransformation(GeomAdaptor_Surface(\
planeFinder.Surface()).Plane().Position());\
break;\
shapePlane = it.Current();\
pos = GeomAdaptor_Surface(planeFinder.Surface()).Plane().Position();\
trsf.SetTransformation(pos);\
gp_Dir dir(pos.Direction());\
if(fabs(dir.X())<Precision::Confusion() &&\
fabs(dir.Y())<Precision::Confusion()) {\
myShapePlane = shapePlane;\
myTrsf = trsf;\
done = true;\
break;\
}\
if(myShapePlane.IsNull()) {\
myShapePlane = shapePlane;\
myTrsf = trsf;\
}\
}\
if(!myShapePlane.IsNull()) break;\
if(done) break;\
if(haveShape) continue;
//Try to find a plane by iterating through shapes, prefer plane paralell with XY0
AREA_CHECK_PLANE(FACE)
AREA_CHECK_PLANE(WIRE)
AREA_CHECK_PLANE(EDGE)
}
if(myShapePlane.IsNull())
throw Base::ValueError("shapes are not planar");
}
@ -412,19 +466,13 @@ void Area::build() {
int error = 0;
int count = myParams.SectionCount;
if(count<0 || count*myParams.Stepdown > zMax-zMin)
if(count<0 || count*myParams.Stepdown > zMax-zMin) {
count = ceil((zMax-zMin)/myParams.Stepdown);
if((count-1)*myParams.Stepdown < zMax-zMin)
++count;
}
for(int i=0;i<count;++i,zMax-=myParams.Stepdown) {
if(zMax < zMin) zMax = zMin;
// gp_Pnt p1(xMax,yMin,zMax);
// gp_Pnt p2(xMax,yMax,zMax);
// gp_Pnt p3(xMin,yMax,zMax);
// gp_Pnt p4(xMin,yMin,zMax);
// mkWire.Add(BRepBuilderAPI_MakeEdge(p1,p2).Edge());
// mkWire.Add(BRepBuilderAPI_MakeEdge(p2,p3).Edge());
// mkWire.Add(BRepBuilderAPI_MakeEdge(p3,p4).Edge());
// mkWire.Add(BRepBuilderAPI_MakeEdge(p4,p1).Edge());
gp_Pln pln(gp_Pnt(0,0,zMax),gp_Dir(0,0,1));
BRepLib_MakeFace mkFace(pln,xMin,xMax,yMin,yMax);
const TopoDS_Shape &face = mkFace.Face();
@ -501,14 +549,27 @@ void Area::build() {
mySkippedShapes = 0;
short op = OperationUnion;
bool pending = false;
bool explode = myParams.Explode;
for(const Shape &s : myShapes) {
if(op!=s.op) {
if(explode) {
explode = false;
for (TopExp_Explorer it(s.shape, TopAbs_EDGE); it.More(); it.Next())
add(*myArea,BRepBuilderAPI_MakeWire(
TopoDS::Edge(it.Current())).Wire(),&myTrsf,myParams.Deflection,true);
continue;
}else if(op!=s.op) {
if(myParams.OpenMode!=OpenModeNone)
myArea->m_curves.splice(myArea->m_curves.end(),myAreaOpen->m_curves);
pending = false;
PARAM_ENUM_CONVERT(AREA_SRC_OP,PARAM_FNAME,PARAM_ENUM_EXCEPT,AREA_PARAMS_OPCODE);
myArea->Clip(Operation,&areaClip,SubjectFill,ClipFill);
areaClip.m_curves.clear();
if(areaClip.m_curves.size()) {
if(op == OperationCompound)
myArea->m_curves.splice(myArea->m_curves.end(),areaClip.m_curves);
else{
AREA_CONVERT_OP;
myArea->Clip(Operation,&areaClip,SubjectFill,ClipFill);
areaClip.m_curves.clear();
}
}
op=s.op;
}
addToBuild(op==OperationUnion?*myArea:areaClip,s.shape);
@ -521,17 +582,106 @@ void Area::build() {
if(pending){
if(myParams.OpenMode!=OpenModeNone)
myArea->m_curves.splice(myArea->m_curves.end(),myAreaOpen->m_curves);
PARAM_ENUM_CONVERT(AREA_SRC_OP,PARAM_FNAME,PARAM_ENUM_EXCEPT,AREA_PARAMS_OPCODE);
myArea->Clip(Operation,&areaClip,SubjectFill,ClipFill);
if(op == OperationCompound)
myArea->m_curves.splice(myArea->m_curves.end(),areaClip.m_curves);
else{
AREA_CONVERT_OP;
myArea->Clip(Operation,&areaClip,SubjectFill,ClipFill);
}
}
myArea->m_curves.splice(myArea->m_curves.end(),myAreaOpen->m_curves);
//Reassemble wires after explode
if(myParams.Explode) {
std::list<TopoDS_Edge> edges;
gp_Trsf trsf(myTrsf.Inverted());
for(const auto &c : myArea->m_curves) {
TopoDS_Wire wire = toShape(c,&trsf);
if(wire.IsNull()) continue;
TopExp_Explorer it(wire, TopAbs_EDGE);
edges.push_back(TopoDS::Edge(it.Current()));
}
Area area(&myParams);
area.myParams.Explode = false;
area.myParams.Coplanar = CoplanarNone;
area.myWorkPlane = myWorkPlane.IsNull()?myShapePlane:myWorkPlane;
area.myTrsf = myTrsf;
while(edges.size()) {
BRepBuilderAPI_MakeWire mkWire;
for(const auto &e : Part::sort_Edges(myParams.Tolerance,edges))
mkWire.Add(TopoDS::Edge(e));
area.add(mkWire.Wire(),OperationCompound);
}
area.build();
myArea = std::move(area.myArea);
}
}catch(...) {
clean();
throw;
}
}
list<TopoDS_Shape> Area::sortWires(int index, int count, const gp_Pnt *pstart,
gp_Pnt *_pend, PARAM_ARGS(PARAM_FARG,AREA_PARAMS_MIN_DIST))
{
std::list<TopoDS_Shape> wires;
build();
gp_Pnt pend,pt;
if(pstart) pt = *pstart;
pt.Transform(TopLoc_Location(myTrsf));
if(mySections.size()) {
if(index>=(int)mySections.size())
throw Base::ValueError("index out of bound");
TopLoc_Location loc(myTrsf.Inverted());
if(index<0) {
index = 0;
count = mySections.size();
}
if(count<=0 || count>(int)mySections.size())
count = mySections.size();
for(int i=index;i<count;++i) {
const std::list<TopoDS_Shape> ws =
mySections[i]->sortWires(-1,0,&pt,&pend,
PARAM_FIELDS(PARAM_FARG,AREA_PARAMS_MIN_DIST));
for(auto &wire : ws)
wires.push_back(wire.Moved(loc));
pt = pend;
}
if(_pend)
*_pend = pend.Transformed(loc);
return wires;
}
if(!myArea || myArea->m_curves.empty()) return wires;
CArea area(*myArea);
Point p(pt.X(),pt.Y());
area.ChangeStartToNearest(&p,
PARAM_FIELDS(PARAM_FARG,AREA_PARAMS_MIN_DIST));
gp_Trsf trsf(myTrsf.Inverted());
for(const CCurve &c : area.m_curves) {
const TopoDS_Wire &wire = toShape(c,&trsf);
if(wire.IsNull()) continue;
wires.push_back(toShape(c,&trsf));
}
if(_pend) {
gp_Pnt pend = pt;
if(area.m_curves.size() &&
area.m_curves.back().m_vertices.size())
{
const Point &pt = area.m_curves.back().m_vertices.back().m_p;
pend.SetCoord(pt.x,pt.y,0.0);
}
*_pend = pend.Transformed(TopLoc_Location(trsf));
}
return wires;
}
TopoDS_Shape Area::toShape(CArea &area, short fill) {
gp_Trsf trsf(myTrsf.Inverted());
bool bFill;
@ -559,14 +709,17 @@ TopoDS_Shape Area::toShape(CArea &area, short fill) {
#define AREA_SECTION(_op,_index,...) do {\
if(mySections.size()) {\
if(_index>=(int)mySections.size())\
throw Base::ValueError("index out of bound");\
return TopoDS_Shape();\
TopLoc_Location loc(myTrsf.Inverted());\
if(_index<0) {\
BRep_Builder builder;\
TopoDS_Compound compound;\
builder.MakeCompound(compound);\
for(shared_ptr<Area> area : mySections)\
builder.Add(compound,area->_op(-1, ## __VA_ARGS__).Moved(loc));\
for(shared_ptr<Area> area : mySections){\
const TopoDS_Shape &s = area->_op(-1, ## __VA_ARGS__);\
if(s.IsNull()) continue;\
builder.Add(compound,s.Moved(loc));\
}\
return compound;\
}\
return mySections[_index]->_op(-1, ## __VA_ARGS__).Moved(loc);\
@ -637,6 +790,7 @@ TopoDS_Shape Area::getShape(int index) {
for(shared_ptr<CArea> area : areas) {
if(myParams.Thicken)
area->Thicken(myParams.ToolRadius);
const TopoDS_Shape &shape = toShape(*area,fill);
builder.Add(compound,toShape(*area,fill));
}
builder.Add(compound,areaPocket.makePocket(
@ -781,7 +935,6 @@ TopoDS_Shape Area::makePocket(int index, PARAM_ARGS(PARAM_FARG,AREA_PARAMS_POCKE
throw Base::ValueError("unknown poket mode");
}
build();
CAreaConfig conf(myParams);
CAreaPocketParams params(
tool_radius,extra_offset,stepover,from_center,pm,zig_angle);
@ -802,75 +955,85 @@ static inline bool IsLeft(const gp_Pnt &a, const gp_Pnt &b, const gp_Pnt &c) {
return ((b.X() - a.X())*(c.Y() - a.Y()) - (b.Y() - a.Y())*(c.X() - a.X())) > 0;
}
TopoDS_Wire Area::toShape(const CCurve &c, const gp_Trsf *trsf) {
BRepBuilderAPI_MakeWire mkWire;
gp_Pnt pstart,pt;
bool first = true;
for(const CVertex &v : c.m_vertices){
if(first){
first = false;
pstart = pt = gp_Pnt(v.m_p.x,v.m_p.y,0);
continue;
}
gp_Pnt pnext(v.m_p.x,v.m_p.y,0);
if(pnext.Distance(pt)<Precision::Confusion())
continue;
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(!mkWire.IsDone())
return TopoDS_Wire();
if(c.IsClosed() && !BRep_Tool::IsClosed(mkWire.Wire())){
// This should never happen after changing libarea's
// Point::tolerance to be the same as Precision::Confusion().
// Just leave it here in case.
BRepAdaptor_Curve curve(mkWire.Edge());
gp_Pnt p1(curve.Value(curve.FirstParameter()));
gp_Pnt p2(curve.Value(curve.LastParameter()));
std::stringstream str;
str<< "warning: patch open wire type " <<
c.m_vertices.back().m_type << endl <<
'(' << p1.X() << ',' << p1.Y() << ')' << endl <<
'(' << p2.X() << ',' << p2.Y() << ')' << endl <<
'(' << pt.X() << ',' << pt.Y() << ')' << endl <<
'(' << pstart.X() << ',' <<pstart.Y() <<')' <<endl;
Base::Console().Warning(str.str().c_str());
mkWire.Add(BRepBuilderAPI_MakeEdge(pt,pstart).Edge());
}
if(trsf)
return TopoDS::Wire(mkWire.Wire().Moved(TopLoc_Location(*trsf)));
else
return mkWire.Wire();
}
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() << ',' <<pstart.Y() <<')' <<endl;
Base::Console().Warning(str.str().c_str());
mkWire.Add(BRepBuilderAPI_MakeEdge(pt,pstart).Edge());
}
if(trsf)
builder.Add(compound,mkWire.Wire().Moved(TopLoc_Location(*trsf)));
else
builder.Add(compound,mkWire.Wire());
const TopoDS_Wire &wire = toShape(c,trsf);
if(!wire.IsNull())
builder.Add(compound,wire);
}
if(fill) {
if(!compound.IsNull() && fill) {
try{
Part::FaceMakerBullseye mkFace;
if(trsf)
@ -888,3 +1051,186 @@ TopoDS_Shape Area::toShape(const CArea &area, bool fill, const gp_Trsf *trsf) {
return compound;
}
std::list<TopoDS_Shape> Area::sortWires(const std::list<TopoDS_Shape> &shapes,
const AreaParams *params, const gp_Pnt *_pstart, gp_Pnt *_pend,
PARAM_ARGS(PARAM_FARG,AREA_PARAMS_SORT_WIRES))
{
std::list<TopoDS_Shape> wires;
//Heristic sorting by shape's vertex Z value. For performance's sake, we don't
//perform any planar checking here
std::multimap<double,TopoDS_Shape> shape_map;
for (auto &shape : shapes) {
std::list<TopoDS_Shape> subshapes;
if(!explode)
subshapes.push_back(shape);
else{
bool haveShape=false;
for(TopExp_Explorer it(shape,TopAbs_WIRE);it.More();it.Next()) {
haveShape=true;
subshapes.push_back(it.Current());
}
if(!haveShape) {
for(TopExp_Explorer it(shape,TopAbs_EDGE);it.More();it.Next())
subshapes.push_back(it.Current());
}
}
//Order the shapes by its vertex Z value.
for(auto &s : subshapes) {
bool first=true;
double z=0.0;
for(TopExp_Explorer it(s,TopAbs_VERTEX);it.More();) {
gp_Pnt p = BRep_Tool::Pnt(TopoDS::Vertex(it.Current()));
if(first || z < p.Z()) {
first = false;
z = p.Z();
}
if(!top_z) break;
}
shape_map.insert(std::pair<double,TopoDS_Shape>(z,s));
}
}
if(!shape_map.size())
return wires;
Area area(params);
//We'll do planar checking here, so disable Area planar check
area.myParams.Coplanar = Area::CoplanarNone;
gp_Pnt pstart,pend;
if(_pstart) pstart = *_pstart;
TopoDS_Shape plane = shape_map.rbegin()->second;
area.setPlane(plane);
for(auto &item : boost::adaptors::reverse(shape_map)) {
//do planar checking, and sort wires grouped by plane
if(!Area::isCoplanar(plane,item.second)) {
wires.splice(wires.end(),area.sortWires(
-1,0,&pstart,&pend, PARAM_FIELDS(PARAM_FARG,AREA_PARAMS_MIN_DIST)));
pstart = pend;
area.clean(true);
plane = item.second;
area.setPlane(plane);
}
area.add(item.second,Area::OperationCompound);
}
wires.splice(wires.end(),area.sortWires(
-1,0,&pstart,&pend, PARAM_FIELDS(PARAM_FARG,AREA_PARAMS_MIN_DIST)));
if(_pend) *_pend = pend;
return wires;
}
static void addCommand(Toolpath &path, const gp_Pnt &p,
bool g0=false, double g0height=0.0, double clearance=0.0)
{
Command cmd;
cmd.Name = g0?"G0":"G1";
if(g0 && fabs(g0height)>Precision::Confusion()) {
cmd.Parameters["Z"] = g0height;
path.addCommand(cmd);
cmd.Parameters["X"] = p.X();
cmd.Parameters["Y"] = p.Y();
path.addCommand(cmd);
if(fabs(clearance)>Precision::Confusion()) {
cmd.Parameters["Z"] = p.Z()+clearance;
path.addCommand(cmd);
cmd.Name = "G1";
}
}else
cmd.Parameters["X"] = p.X();
cmd.Parameters["Y"] = p.Y();
cmd.Parameters["Z"] = p.Z();
path.addCommand(cmd);
}
static void addCommand(Toolpath &path,
const gp_Pnt &pstart, const gp_Pnt &pend,
const gp_Pnt &center, bool clockwise)
{
Command cmd;
cmd.Name = clockwise?"G2":"G3";
cmd.Parameters["I"] = center.X()-pstart.X();
cmd.Parameters["J"] = center.Y()-pstart.Y();
cmd.Parameters["K"] = center.Z()-pstart.Z();
cmd.Parameters["X"] = pend.X();
cmd.Parameters["Y"] = pend.Y();
cmd.Parameters["Z"] = pend.Z();
path.addCommand(cmd);
}
void Area::toPath(Toolpath &path, const std::list<TopoDS_Shape> &shapes,
const gp_Pnt *pstart, PARAM_ARGS(PARAM_FARG,AREA_PARAMS_PATH))
{
std::list<TopoDS_Shape> wires;
if(sort)
wires = sortWires(shapes,NULL,pstart);
else{
for(auto &shape : shapes) {
if (shape.IsNull())
continue;
bool haveShape=false;
for(TopExp_Explorer it(shape,TopAbs_WIRE);it.More();it.Next()) {
haveShape=true;
wires.push_back(it.Current());
}
if(haveShape) continue;
for(TopExp_Explorer it(shape,TopAbs_EDGE);it.More();it.Next())
wires.push_back(BRepBuilderAPI_MakeWire(TopoDS::Edge(it.Current())).Wire());
}
}
if(threshold < Precision::Confusion())
threshold = Precision::Confusion();
gp_Pnt plast,p;
if(pstart) plast = *pstart;
bool first = true;
for(const TopoDS_Shape &wire : wires) {
BRepTools_WireExplorer xp(TopoDS::Wire(wire));
p = BRep_Tool::Pnt(xp.CurrentVertex());
if(first||(p.Z()>=plast.Z()&&p.Distance(plast)>threshold))
addCommand(path,p,true,height,clearance);
else
addCommand(path,p);
plast = p;
first = false;
for(;xp.More();xp.Next(),plast=p) {
BRepAdaptor_Curve curve(xp.Current());
bool reversed = (xp.Current().Orientation()==TopAbs_REVERSED);
p = curve.Value(reversed?curve.FirstParameter():curve.LastParameter());
switch (curve.GetType()) {
case GeomAbs_Line: {
addCommand(path,p);
break;
} case GeomAbs_Circle:{
double first = curve.FirstParameter();
double last = curve.LastParameter();
gp_Circ circle = curve.Circle();
gp_Ax1 axis = circle.Axis();
bool clockwise = axis.Direction().Z()<0;
if(reversed) clockwise = !clockwise;
gp_Pnt center = axis.Location();
if(fabs(first-last)>M_PI) {
// Split arc(circle) larger than half circle.
gp_Pnt mid = curve.Value((last-first)*0.5+first);
addCommand(path,plast,mid,center,clockwise);
plast = mid;
}
addCommand(path,plast,p,center,clockwise);
break;
} default: {
// Discretize all other type of curves
GCPnts_UniformDeflection discretizer(curve, deflection,
curve.FirstParameter(), curve.LastParameter());
if (discretizer.IsDone () && discretizer.NbPoints () > 0) {
int nbPoints = discretizer.NbPoints ();
for (int i=1; i<=nbPoints; i++) {
gp_Pnt pt = discretizer.Value (i);
addCommand(path,pt);
}
}else
Standard_Failure::Raise("Curve discretization failed");
}}
}
}
}

View File

@ -23,14 +23,19 @@
#ifndef PATH_AREA_H
#define PATH_AREA_H
#include <memory>
#include <vector>
#include <list>
#include <TopoDS.hxx>
#include <gp_Pln.hxx>
#include <gp_Circ.hxx>
#include <gp_GTrsf.hxx>
#include "Path.h"
#include "AreaParams.h"
class CArea;
class CCurve;
namespace Path
{
@ -152,8 +157,12 @@ public:
/** Set a working plane
*
* If no working plane are set, Area will try to find a working plane from
* all the added children shapes. The purpose of this function is in case
* the child shapes are all colinear edges
* individual children faces, wires or edges. By right, we should create a
* compound of all shapes and then findplane on it. However, because we
* supports solid, and also because OCC may hang for a long time if
* something goes a bit off, we opt to find plane on each individual shape.
* If you intend to pass individual edges, you must supply a workplane shape
* manually
*
* \arg \c shape: a shape defining a working plane
*/
@ -177,13 +186,13 @@ public:
* If more than one offset is requested, a compound shape is return
* containing all offset shapes as wires regardless of \c Fill setting.
*/
TopoDS_Shape makeOffset(int index, PARAM_ARGS_DEF(PARAM_FARG,AREA_PARAMS_OFFSET));
TopoDS_Shape makeOffset(int index=-1, PARAM_ARGS_DEF(PARAM_FARG,AREA_PARAMS_OFFSET));
/** Make a pocket of the combined shape
*
* See #AREA_PARAMS_POCKET for description of the arguments.
*/
TopoDS_Shape makePocket(int index, PARAM_ARGS_DEF(PARAM_FARG,AREA_PARAMS_POCKET));
TopoDS_Shape makePocket(int index=-1, PARAM_ARGS_DEF(PARAM_FARG,AREA_PARAMS_POCKET));
/** Config this Area object */
@ -204,8 +213,11 @@ public:
*/
void clean(bool deleteShapes=false);
/** Get the combined shape */
TopoDS_Shape getShape(int index);
/** Get the combined shape
* \arg \c index: index of the section, -1 for all sections. No effect on
* non-sectioned area.
*/
TopoDS_Shape getShape(int index=-1);
/** Return the number of sections */
std::size_t getSectionCount() {
@ -219,10 +231,30 @@ public:
* \arg \c wire: input wire object
* \arg \c trsf: optional transform matrix to transform the wire shape into
* XY0 plane.
* \arg \c deflection: for defecting non circular curves
* \arg \c deflection: for discretizing non circular curves
* \arg \c to_edges: if true, discretize all curves, and insert as open
* line segments
* */
static void add(CArea &area, const TopoDS_Wire &wire,
const gp_Trsf *trsf=NULL, double deflection=0.01);
static void add(CArea &area, const TopoDS_Wire &wire, const gp_Trsf *trsf=NULL,
double deflection=0.01, bool to_edges=false);
/** Output a list or sorted wire with minimize traval distance
*
* \arg \c index: index of the section, -1 for all sections. No effect on
* non-sectioned area.
* \arg \c count: number of the sections to return, <=0 for all sections
* after \c index. No effect on non-sectioned area.
* \arg \c pstart: optional start point
* \arg \c pend: optional output containing the ending point of the returned
* wires
*
* See #AREA_PARAMS_MIN_DIST for other arguments
*
* \return sorted wires
* */
std::list<TopoDS_Shape> sortWires(int index=-1, int count=0,
const gp_Pnt *pstart=NULL, gp_Pnt *pend=NULL,
PARAM_ARGS_DEF(PARAM_FARG,AREA_PARAMS_MIN_DIST));
/** Add a OCC generic shape to CArea
*
@ -236,7 +268,7 @@ public:
* \arg \c areaOpen: for collecting open curves. If not supplied, open
* curves are added to \c area
* \arg \c to_edges: separate open wires to individual edges
* \arg \c reorder: reorder closed wires for wire only shape
* \arg \c reorient: reorient closed wires for wire only shape
*
* \return Returns the number of non coplaner. Planar testing only happens
* if \c plane is supplied
@ -244,7 +276,7 @@ public:
static int add(CArea &area, const TopoDS_Shape &shape, const gp_Trsf *trsf=NULL,
double deflection=0.01,const TopoDS_Shape *plane = NULL,
bool force_coplanar=true, CArea *areaOpen=NULL, bool to_edges=false,
bool reorder=true);
bool reorient=true);
/** Convert curves in CArea into an OCC shape
*
@ -256,7 +288,48 @@ public:
static TopoDS_Shape toShape(const CArea &area, bool fill,
const gp_Trsf *trsf=NULL);
/** Convert a single curve into an OCC wire
*
* \arg \c curve: input curve object
* \arg \c trsf: optional transform matrix to transform the shape back into
* its original position.
* */
static TopoDS_Wire toShape(const CCurve &curve, const gp_Trsf *trsf=NULL);
/** Check if two OCC shape is coplanar */
static bool isCoplanar(const TopoDS_Shape &s1, const TopoDS_Shape &s2);
/** Group shapes by their plane, and return a list of sorted wires
*
* The output wires is ordered by its occupied plane, and sorted to
* minimize traval distance
*
* \arg \c shapes: input list of shapes.
* \arg \c params: optional Area parameters for the Area object internally
* used for sorting
* \arg \c pstart: optional start point
* \arg \c pend: optional output containing the ending point of the returned
* maybe broken if the algorithm see fits.
*
* See #AREA_PARAMS_SORT_WIRES for other arguments
*
* \return sorted wires
*/
static std::list<TopoDS_Shape> sortWires(const std::list<TopoDS_Shape> &shapes,
const AreaParams *params = NULL, const gp_Pnt *pstart=NULL,
gp_Pnt *pend=NULL, PARAM_ARGS_DEF(PARAM_FARG,AREA_PARAMS_SORT_WIRES));
/** Convert a list of wires to gcode
*
* \arg \c path: output toolpath
* \arg \c shapes: input list of shapes
* \arg \c pstart: output start point,
*
* See #AREA_PARAMS_PATH for other arguments
*/
static void toPath(Toolpath &path, const std::list<TopoDS_Shape> &shapes,
const gp_Pnt *pstart=NULL, PARAM_ARGS_DEF(PARAM_FARG,AREA_PARAMS_PATH));
};
} //namespace Path

View File

@ -44,20 +44,32 @@
((enum2,clip_fill,ClipFill,0,\
"ClipperLib clip fill type. \nSee https://goo.gl/5pYQQP",AREA_CLIPPER_FILL_TYPE))
/** Deflection parameter */
#define AREA_PARAMS_DEFLECTION \
((double,deflection,Deflection,0.01,\
"Deflection for non circular curve discretization. It also also used for\n"\
"discretizing circular wires when you 'Explode' the shape for wire operations"))
/** Base parameters */
#define AREA_PARAMS_BASE \
((enum,fill,Fill,2,"Fill the output wires to make a face. \n"\
"Auto means make a face if any of the children has a face.",(None)(Face)(Auto)))\
((enum,coplanar,Coplanar,2,"Specifies the way to check coplanar.\n"\
"'Force' will discard non coplaner shapes, but 'Check' only gives warning.",\
(None)(Check)(Force)))\
((bool,reorder,Reorder,true,"Re-orient closed wires in wire only shapes so that inner wires become holes."))\
((enum,open_mode,OpenMode,0,"Specify how to handle open wires.\n"\
"'None' means combin without openeration.\n"\
"'Edges' means separate to edges before Union.\n"\
"ClipperLib seems to have an urge to close open wires.",(None)(Union)(Edges)))\
AREA_PARAMS_CLIPPER_FILL \
((double,deflection,Deflection,0.01,"Deflection for non circular curve discretization"))
((enum,coplanar,Coplanar,2,\
"Specifies the way to check coplanar. 'Force' will discard non coplaner shapes,\n"\
"but 'Check' only gives warning.",(None)(Check)(Force)))\
((bool,reorient,Reorient,true,\
"Re-orient closed wires in wire only shapes so that inner wires become holes."))\
((bool,explode,Explode,false,\
"If true, Area will explode the first shape into disconnected open edges, \n"\
"with all curves discretized, so that later operations like 'Difference' \n"\
"behave like wire cutting. Without exploding, 'Difference' in ClipperLib\n"\
"behave like face cutting."))\
((enum,open_mode,OpenMode,0,\
"Specify how to handle open wires. 'None' means combin without openeration.\n"\
"'Edges' means separate to edges before Union. ClipperLib seems to have an.\n"\
"urge to close open wires.",(None)(Union)(Edges)))\
AREA_PARAMS_DEFLECTION \
AREA_PARAMS_CLIPPER_FILL
/** libarea algorithm option parameters */
#define AREA_PARAMS_CAREA \
@ -72,7 +84,8 @@
((short,min_arc_points,MinArcPoints,4,"Minimum segments for arc discretization"))\
((short,max_arc_points,MaxArcPoints,100,"Maximum segments for arc discretization"))\
((double,clipper_scale,ClipperScale,10000.0,\
"ClipperLib operate on intergers. This is the scale factor to \nconvert floating points."))
"ClipperLib operate on intergers. This is the scale factor to convert\n"\
"floating points."))
/** Pocket parameters
*
@ -91,8 +104,10 @@
/** Operation code */
#define AREA_PARAMS_OPCODE \
((enum2,op,Operation,0,"Boolean operation",\
(Union)(Difference)(Intersection)(Xor),(ClipperLib::ClipType,ClipperLib::ct)))
((enum,op,Operation,0,\
"Boolean operation. For the first four operation, see https://goo.gl/Gj8RUu.\n"\
"'Compound' means no operation, normal used to do Area.sortWires().",\
(Union)(Difference)(Intersection)(Xor)(Compound)))
/** Offset parameters */
#define AREA_PARAMS_OFFSET \
@ -124,6 +139,36 @@
((double,round_precision,RoundPreceision,0.0,\
"Round joint precision. If =0, it defaults to Accuracy. \nSee https://goo.gl/4odfQh"))
/** Area path generation parameters */
#define AREA_PARAMS_PATH \
((bool, sort, SortShape, true, \
"Whether to sort the shapes to optimize travel distance. You can customize wire\n"\
"sorting by calling sortWires() manually."))\
((double, threshold, RetractThreshold, 0.0,\
"If two wire's end points are separated within this threshold, they are consider\n"\
"as connected. You may want to set this to the tool diameter to keep the tool down."))\
((double, height, RetractHeight, 0.0,"Tool retraction absolute height"))\
((double, clearance, Clearance, 0.0,\
"When return from last retraction, this gives the pause height relative to the Z\n"\
"value of the next move"))\
AREA_PARAMS_DEFLECTION
#define AREA_PARAMS_MIN_DIST \
((double, min_dist, MinDistance, 1.0, \
"minimum distance for the generate new wires. Wires maybe broken if the\n"\
"algorithm see fits."))\
/** Area wire sorting parameters */
#define AREA_PARAMS_SORT_WIRES \
((bool, explode, Explode, true,\
"If ture, the input shape will be exploded into wires before doing planar checking.\n"\
"Otherwise, and whole shape is considered to be in one plane without checking."))\
((bool, top_z, TopZ, false, \
"If ture, the planes is ordered by the first the shapes first vertex Z value.\n"\
"Otherwise, by the highest Z of all of its vertexes."))\
AREA_PARAMS_MIN_DIST
/** Group of all Area configuration parameters except CArea's*/
#define AREA_PARAMS_AREA \
AREA_PARAMS_BASE \

View File

@ -58,6 +58,11 @@ any operation</UserDocu>
</UserDocu>
</Documentation>
</Methode>
<Methode Name="sortWires" Keyword="true">
<Documentation>
<UserDocu></UserDocu>
</Documentation>
</Methode>
<Methode Name="getParams">
<Documentation>
<UserDocu>Get current algorithm parameters as a dictionary.</UserDocu>

View File

@ -22,10 +22,10 @@
#include "PreCompiled.h"
#include <Mod/Part/App/OCCError.h>
#include <Mod/Part/App/TopoShapePy.h>
#include <Base/VectorPy.h>
#include "Mod/Path/App/Area.h"
#include "Area.h"
// inclusion of the generated files (generated out of AreaPy.xml)
#include "AreaPy.h"
@ -77,6 +77,16 @@ static const AreaDoc myDocs[] = {
"\n* index (-1): the index of the section. -1 means all sections. No effect on planar shape.\n"
PARAM_PY_DOC(ARG,AREA_PARAMS_POCKET),
},
{
"sortWires",
"sortWires(index=-1, count=0, start=Vector()" PARAM_PY_ARGS_DOC(ARG,AREA_PARAMS_MIN_DIST) "):\n"
"Returns a tuple (wires,end): sorted wires with minimized travel distance, and the endpoint of the wires.\n"
"\n* index (-1): the index of the section. -1 means all sections. No effect on planar shape.\n"
"\n* count (0): the number of sections to return. <=0 means all sections starting from index.\n"
"\n* start (Vector()): a vector specifies the start point.\n"
PARAM_PY_DOC(ARG,AREA_PARAMS_MIN_DIST),
},
};
struct AreaPyDoc {
@ -141,12 +151,42 @@ PyObject* AreaPy::getShape(PyObject *args, PyObject *keywds)
if (!PyArg_ParseTupleAndKeywords(args, keywds,"|hO",kwlist,&pcObj))
return 0;
try {
if(PyObject_IsTrue(pcObj))
getAreaPtr()->clean();
return Py::new_reference_to(Part::shape2pyshape(getAreaPtr()->getShape(index)));
if(PyObject_IsTrue(pcObj))
getAreaPtr()->clean();
return Py::new_reference_to(Part::shape2pyshape(getAreaPtr()->getShape(index)));
}
PyObject* AreaPy::sortWires(PyObject *args, PyObject *keywds){
PARAM_PY_DECLARE_INIT(PARAM_FARG,AREA_PARAMS_MIN_DIST)
short index = -1;
short count = 0;
PyObject *start = NULL;
static char *kwlist[] = {"index","count","start",
PARAM_FIELD_STRINGS(ARG,AREA_PARAMS_MIN_DIST), NULL};
if (!PyArg_ParseTupleAndKeywords(args, keywds,
"|hhO!" PARAM_PY_KWDS(AREA_PARAMS_MIN_DIST),
kwlist,&index,&count,&(Base::VectorPy::Type),&start,
PARAM_REF(PARAM_FARG,AREA_PARAMS_MIN_DIST)))
return 0;
gp_Pnt pstart,pend;
if(start) {
Base::Vector3d vec = static_cast<Base::VectorPy*>(start)->value();
pstart.SetCoord(vec.x, vec.y, vec.z);
}
PY_CATCH_OCC;
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)
@ -205,13 +245,10 @@ PyObject* AreaPy::makeOffset(PyObject *args, PyObject *keywds)
&index,PARAM_REF(PARAM_FARG,AREA_PARAMS_OFFSET)))
return 0;
try {
//Expand the variable as function call arguments
TopoDS_Shape resultShape = getAreaPtr()->makeOffset(index,
PARAM_PY_FIELDS(PARAM_FARG,AREA_PARAMS_OFFSET));
return Py::new_reference_to(Part::shape2pyshape(resultShape));
}
PY_CATCH_OCC;
//Expand the variable as function call arguments
TopoDS_Shape resultShape = getAreaPtr()->makeOffset(index,
PARAM_PY_FIELDS(PARAM_FARG,AREA_PARAMS_OFFSET));
return Py::new_reference_to(Part::shape2pyshape(resultShape));
}
PyObject* AreaPy::makePocket(PyObject *args, PyObject *keywds)
@ -228,15 +265,11 @@ PyObject* AreaPy::makePocket(PyObject *args, PyObject *keywds)
&index,PARAM_REF(PARAM_FARG,AREA_PARAMS_POCKET)))
return 0;
try {
TopoDS_Shape resultShape = getAreaPtr()->makePocket(index,
PARAM_PY_FIELDS(PARAM_FARG,AREA_PARAMS_POCKET));
return Py::new_reference_to(Part::shape2pyshape(resultShape));
}
PY_CATCH_OCC;
TopoDS_Shape resultShape = getAreaPtr()->makePocket(index,
PARAM_PY_FIELDS(PARAM_FARG,AREA_PARAMS_POCKET));
return Py::new_reference_to(Part::shape2pyshape(resultShape));
}
PyObject* AreaPy::setParams(PyObject *args, PyObject *keywds)
{
static char *kwlist[] = {PARAM_FIELD_STRINGS(NAME,AREA_PARAMS_CONF),NULL};

View File

@ -41,6 +41,7 @@ PROPERTY_SOURCE(Path::FeatureArea, Part::Feature)
PARAM_ENUM_STRING_DECLARE(static const char *Enums,AREA_PARAMS_ALL)
FeatureArea::FeatureArea()
:myBuild(false)
{
ADD_PROPERTY(Sources,(0));
ADD_PROPERTY(WorkPlane,(TopoDS_Shape()));
@ -62,6 +63,14 @@ FeatureArea::~FeatureArea()
{
}
Area &FeatureArea::getArea() {
if(!myBuild) {
myBuild = true;
execute();
}
return myArea;
}
App::DocumentObjectExecReturn *FeatureArea::execute(void)
{
std::vector<App::DocumentObject*> links = Sources.getValues();
@ -97,6 +106,17 @@ App::DocumentObjectExecReturn *FeatureArea::execute(void)
return Part::Feature::execute();
}
std::list<TopoDS_Shape> FeatureArea::getShapes() {
std::list<TopoDS_Shape> shapes;
Area &area = getArea();
if(area.getSectionCount()) {
for(int i=0;i<(int)area.getSectionCount();++i)
shapes.push_back(area.getShape(i));
}else
shapes.push_back(area.getShape());
return shapes;
}
short FeatureArea::mustExecute(void) const
{
if (Sources.isTouched())
@ -119,14 +139,85 @@ PyObject *FeatureArea::getPyObject()
}
// Python Area feature ---------------------------------------------------------
// FeatureAreaView -------------------------------------------------------------
//
PROPERTY_SOURCE(Path::FeatureAreaView, Part::Feature)
FeatureAreaView::FeatureAreaView()
{
ADD_PROPERTY(Source,(0));
ADD_PROPERTY_TYPE(SectionIndex,(0),"Section",App::Prop_None,"The start index of the section to show");
ADD_PROPERTY_TYPE(SectionCount,(1),"Section",App::Prop_None,"Number of sections to show");
}
std::list<TopoDS_Shape> FeatureAreaView::getShapes() {
std::list<TopoDS_Shape> shapes;
App::DocumentObject* pObj = Source.getValue();
if (!pObj) return shapes;
if(!pObj->isDerivedFrom(FeatureArea::getClassTypeId()))
return shapes;
Area &area = static_cast<FeatureArea*>(pObj)->getArea();
int index=SectionIndex.getValue(),count=SectionCount.getValue();
if(index<0) {
index = 0;
count = area.getSectionCount();
}
if(index >= (int)area.getSectionCount())
return shapes;
if(count<=0)
count = (int)area.getSectionCount();
if(count==1)
shapes.push_back(area.getShape(index));
else{
count += index;
if(count>(int)area.getSectionCount())
count = area.getSectionCount();
for(int i=index;i<count;++i)
shapes.push_back(area.getShape(i));
}
return shapes;
}
App::DocumentObjectExecReturn *FeatureAreaView::execute(void)
{
App::DocumentObject* pObj = Source.getValue();
if (!pObj)
return new App::DocumentObjectExecReturn("No shape linked");
if(!pObj->isDerivedFrom(FeatureArea::getClassTypeId()))
return new App::DocumentObjectExecReturn("Linked object is not a FeatureArea");
std::list<TopoDS_Shape> shapes = getShapes();
if(shapes.empty())
Shape.setValue(TopoDS_Shape());
else if(shapes.size()==1) {
Shape.setValue(shapes.front());
}else{
BRep_Builder builder;
TopoDS_Compound compound;
builder.MakeCompound(compound);
for(auto &shape : shapes)
builder.Add(compound,shape);
Shape.setValue(compound);
}
return Part::Feature::execute();
}
// Python feature ---------------------------------------------------------
namespace App {
/// @cond DOXERR
PROPERTY_SOURCE_TEMPLATE(Path::FeatureAreaPython, Path::FeatureArea)
PROPERTY_SOURCE_TEMPLATE(Path::FeatureAreaViewPython, Path::FeatureAreaView)
template<> const char* Path::FeatureAreaPython::getViewProviderName(void) const {
return "PathGui::ViewProviderArea";
return "PathGui::ViewProviderAreaPython";
}
template<> const char* Path::FeatureAreaViewPython::getViewProviderName(void) const {
return "PathGui::ViewProviderAreaViewPython";
}
/// @endcond

View File

@ -40,12 +40,13 @@ class PathExport FeatureArea : public Part::Feature
PROPERTY_HEADER(Path::FeatureArea);
public:
Area myArea;
/// Constructor
FeatureArea(void);
virtual ~FeatureArea();
Area &getArea();
std::list<TopoDS_Shape> getShapes();
/// returns the type name of the ViewProvider
virtual const char* getViewProviderName(void) const {
return "PathGui::ViewProviderArea";
@ -58,10 +59,36 @@ public:
Part::PropertyPartShape WorkPlane;
PARAM_PROP_DECLARE(AREA_PARAMS_ALL)
private:
bool myBuild;
Area myArea;
};
typedef App::FeaturePythonT<FeatureArea> FeatureAreaPython;
class PathExport FeatureAreaView : public Part::Feature
{
PROPERTY_HEADER(Path::FeatureAreaView);
public:
/// Constructor
FeatureAreaView(void);
std::list<TopoDS_Shape> getShapes();
virtual const char* getViewProviderName(void) const {
return "PathGui::ViewProviderAreaView";
}
virtual App::DocumentObjectExecReturn *execute(void);
App::PropertyLink Source;
App::PropertyInteger SectionIndex;
App::PropertyInteger SectionCount;
};
typedef App::FeaturePythonT<FeatureAreaView> FeatureAreaViewPython;
} //namespace Path

View File

@ -46,7 +46,7 @@ PyObject* FeatureAreaPy::getArea(PyObject *args)
if (!PyArg_ParseTuple(args, ""))
return NULL;
return new AreaPy(new Area(getFeatureAreaPtr()->myArea));
return new AreaPy(new Area(getFeatureAreaPtr()->getArea()));
}
PyObject* FeatureAreaPy::setParams(PyObject *args, PyObject *keywds)

View File

@ -19,7 +19,9 @@
* Suite 330, Boston, MA 02111-1307, USA *
* *
***************************************************************************/
/*
* Copyright (c) 2017 Zheng, Lei <realthunder.dev@gmail.com>
*/
#include "PreCompiled.h"
@ -32,20 +34,17 @@
#include <App/DocumentObjectPy.h>
#include <Base/Placement.h>
#include <Mod/Part/App/TopoShape.h>
#include <Mod/Part/App/PartFeature.h>
#include <TopoDS.hxx>
#include <TopoDS_Shape.hxx>
#include <TopoDS_Edge.hxx>
#include <TopoDS_Vertex.hxx>
#include <TopoDS_Iterator.hxx>
#include <TopExp_Explorer.hxx>
#include <gp_Lin.hxx>
#include <BRep_Tool.hxx>
#include <BRepAdaptor_CompCurve.hxx>
#include <BRepAdaptor_HCompCurve.hxx>
#include <Approx_Curve3d.hxx>
#include <BRepAdaptor_HCurve.hxx>
#include <Standard_Failure.hxx>
#include <Standard_Version.hxx>
#include <BRepBuilderAPI_MakeWire.hxx>
#include "FeatureArea.h"
using namespace Path;
@ -54,79 +53,39 @@ PROPERTY_SOURCE(Path::FeatureShape, Path::Feature)
FeatureShape::FeatureShape()
{
ADD_PROPERTY_TYPE(Shape,(TopoDS_Shape()),"Path",App::Prop_None,"The shape data of this feature");
ADD_PROPERTY(Sources,(0));
ADD_PROPERTY_TYPE(StartPoint,(Base::Vector3d()),"Path",App::Prop_None,"Path start position");
PARAM_PROP_ADD("Path",AREA_PARAMS_PATH);
}
FeatureShape::~FeatureShape()
{
}
short FeatureShape::mustExecute(void) const
{
return Path::Feature::mustExecute();
}
App::DocumentObjectExecReturn *FeatureShape::execute(void)
{
TopoDS_Shape shape = Shape.getValue();
if (!shape.IsNull()) {
if (shape.ShapeType() == TopAbs_WIRE) {
Path::Toolpath result;
bool first = true;
Base::Placement last;
TopExp_Explorer ExpEdges (shape,TopAbs_EDGE);
while (ExpEdges.More()) {
const TopoDS_Edge& edge = TopoDS::Edge(ExpEdges.Current());
TopExp_Explorer ExpVerts(edge,TopAbs_VERTEX);
bool vfirst = true;
while (ExpVerts.More()) {
const TopoDS_Vertex& vert = TopoDS::Vertex(ExpVerts.Current());
gp_Pnt pnt = BRep_Tool::Pnt(vert);
Base::Placement tpl;
tpl.setPosition(Base::Vector3d(pnt.X(),pnt.Y(),pnt.Z()));
if (first) {
// add first point as a G0 move
Path::Command cmd;
std::ostringstream ctxt;
ctxt << "G0 X" << tpl.getPosition().x << " Y" << tpl.getPosition().y << " Z" << tpl.getPosition().z;
cmd.setFromGCode(ctxt.str());
result.addCommand(cmd);
first = false;
vfirst = false;
} else {
if (vfirst)
vfirst = false;
else {
Path::Command cmd;
cmd.setFromPlacement(tpl);
// write arc data if needed
BRepAdaptor_Curve adapt(edge);
if (adapt.GetType() == GeomAbs_Circle) {
gp_Circ circ = adapt.Circle();
gp_Pnt c = circ.Location();
bool clockwise = false;
gp_Dir n = circ.Axis().Direction();
if (n.Z() < 0)
clockwise = true;
Base::Vector3d center = Base::Vector3d(c.X(),c.Y(),c.Z());
// center coords must be relative to last point
center -= last.getPosition();
cmd.setCenter(center,clockwise);
}
result.addCommand(cmd);
}
}
ExpVerts.Next();
last = tpl;
}
ExpEdges.Next();
}
Path.setValue(result);
}
Toolpath path;
std::vector<App::DocumentObject*> links = Sources.getValues();
if (links.empty()) {
Path.setValue(path);
return new App::DocumentObjectExecReturn("No shapes linked");
}
const Base::Vector3d &v = StartPoint.getValue();
gp_Pnt pstart(v.x,v.y,v.z);
std::list<TopoDS_Shape> shapes;
for (std::vector<App::DocumentObject*>::iterator it = links.begin(); it != links.end(); ++it) {
if (!(*it && (*it)->isDerivedFrom(Part::Feature::getClassTypeId())))
continue;
const TopoDS_Shape &shape = static_cast<Part::Feature*>(*it)->Shape.getShape().getShape();
if (shape.IsNull())
continue;
shapes.push_back(shape);
}
Area::toPath(path,shapes,&pstart,PARAM_PROP_ARGS(AREA_PARAMS_PATH));
Path.setValue(path);
return App::DocumentObject::StdReturn;
}

View File

@ -19,6 +19,9 @@
* Suite 330, Boston, MA 02111-1307, USA *
* *
***************************************************************************/
/*
* Copyright (c) 2017 Zheng, Lei <realthunder.dev@gmail.com>
*/
#ifndef PATH_FeaturePathShape_H
@ -26,14 +29,13 @@
#include <App/DocumentObject.h>
#include <App/GeoFeature.h>
#include <App/PropertyFile.h>
#include <App/PropertyGeo.h>
#include <App/FeaturePython.h>
#include "Mod/Part/App/PropertyTopoShape.h"
#include "Path.h"
#include "PropertyPath.h"
#include "FeaturePath.h"
#include "Area.h"
namespace Path
{
@ -47,12 +49,14 @@ public:
FeatureShape(void);
virtual ~FeatureShape();
Part::PropertyPartShape Shape;
// Part::PropertyPartShape Shape;
App::PropertyLinkList Sources;
App::PropertyVector StartPoint;
PARAM_PROP_DECLARE(AREA_PARAMS_PATH)
//@{
/// recalculate the feature
virtual App::DocumentObjectExecReturn *execute(void);
virtual short mustExecute(void) const;
//@}
/// returns the type name of the ViewProvider

View File

@ -80,6 +80,8 @@ PyMODINIT_FUNC initPathGui()
PathGui::ViewProviderPathPython ::init();
PathGui::ViewProviderArea ::init();
PathGui::ViewProviderAreaPython ::init();
PathGui::ViewProviderAreaView ::init();
PathGui::ViewProviderAreaViewPython ::init();
// add resources and reloads the translators
loadPathResource();

View File

@ -68,39 +68,55 @@ void CmdPathArea::activated(int iMsg)
Q_UNUSED(iMsg);
std::list<std::string> cmds;
std::ostringstream sources;
std::string areaName;
bool addView = true;
for(const Gui::SelectionObject &selObj :
getSelection().getSelectionEx(NULL, Part::Feature::getClassTypeId()))
{
const Part::Feature *pcObj = static_cast<const Part::Feature*>(selObj.getObject());
const std::vector<std::string> &subnames = selObj.getSubNames();
if(addView && areaName.size()) addView = false;
if(subnames.empty()) {
if(addView && pcObj->getTypeId().isDerivedFrom(Path::FeatureArea::getClassTypeId()))
areaName = pcObj->getNameInDocument();
sources << "FreeCAD.activeDocument()." << pcObj->getNameInDocument() << ",";
continue;
}
for(const std::string &name : subnames) {
if(!name.compare(0,4,"Face") &&
!name.compare(0,4,"Edge"))
{
if(name.compare(0,4,"Face") && name.compare(0,4,"Edge")) {
Base::Console().Error("Selected shape is not 2D\n");
return;
}
int index = atoi(name.substr(4).c_str());
std::ostringstream subname;
subname << pcObj->getNameInDocument() << '_' << name;
std::string sub_fname = getUniqueObjectName(subname.str().c_str());
std::ostringstream cmd;
cmd << "FreeCAD.activeDocument().addObject('Part::Feature','" << sub_fname <<
"').Shape = FreeCAD.activeDocument()." << pcObj->getNameInDocument() << ".Shape." <<
name.substr(0,4) << "s[" << index-1 << ']';
"').Shape = PathCommands.findShape(FreeCAD.activeDocument()." <<
pcObj->getNameInDocument() << ".Shape,'" << name << "'";
if(!name.compare(0,4,"Edge"))
cmd << ",'Wires'";
cmd << ')';
cmds.push_back(cmd.str());
sources << "FreeCAD.activeDocument()." << sub_fname << ",";
}
}
if(addView && areaName.size()) {
std::string FeatName = getUniqueObjectName("FeatureAreaView");
openCommand("Create Path Area View");
doCommand(Doc,"FreeCAD.activeDocument().addObject('Path::FeatureAreaView','%s')",FeatName.c_str());
doCommand(Doc,"FreeCAD.activeDocument().%s.Source = FreeCAD.activeDocument().%s",
FeatName.c_str(),areaName.c_str());
commitCommand();
updateActive();
return;
}
std::string FeatName = getUniqueObjectName("FeatureArea");
openCommand("Create Path Area");
doCommand(Doc,"import PathCommands");
for(const std::string &cmd : cmds)
doCommand(Doc,cmd.c_str());
doCommand(Doc,"FreeCAD.activeDocument().addObject('Path::FeatureArea','%s')",FeatName.c_str());
@ -171,17 +187,12 @@ void CmdPathAreaWorkplane::activated(int iMsg)
}
for(const std::string &name : subnames) {
if(!name.compare(0,4,"Face") &&
!name.compare(0,4,"Edge"))
{
if(name.compare(0,4,"Face") && name.compare(0,4,"Edge")) {
Base::Console().Error("Selected shape is not 2D\n");
return;
}
int index = atoi(name.substr(4).c_str());
std::ostringstream subname;
subname << planeSubname << '.' << name.substr(0,4) << "s[" << index-1 << ']';
subname << planeSubname << ",'" << name << "','Wires'";
planeSubname = subname.str();
}
}
@ -195,10 +206,10 @@ void CmdPathAreaWorkplane::activated(int iMsg)
}
openCommand("Select Workplane for Path Area");
doCommand(Doc,"FreeCAD.activeDocument().%s.WorkPlane = FreeCAD.activeDocument().%s",
areaName.c_str(),planeSubname.c_str());
doCommand(Doc,"import PathCommands");
doCommand(Doc,"FreeCAD.activeDocument().%s.WorkPlane = PathCommands.findShape("
"FreeCAD.activeDocument().%s)", areaName.c_str(),planeSubname.c_str());
doCommand(Doc,"FreeCAD.activeDocument().%s.ViewObject.Visibility = True",areaName.c_str());
// doCommand(Doc,"FreeCAD.activeDocument().%s.ViewObject.Visibility = False",planeName.c_str());
commitCommand();
updateActive();
}
@ -284,24 +295,48 @@ CmdPathShape::CmdPathShape()
void CmdPathShape::activated(int iMsg)
{
Q_UNUSED(iMsg);
std::vector<Gui::SelectionSingleton::SelObj> Sel = getSelection().getSelection();
if (Sel.size() == 1) {
if (Sel[0].pObject->getTypeId().isDerivedFrom(Part::Feature::getClassTypeId())) {
Part::Feature *pcPartObject = static_cast<Part::Feature*>(Sel[0].pObject);
std::string FeatName = getUniqueObjectName("PathShape");
openCommand("Create Path Compound");
doCommand(Doc,"FreeCAD.activeDocument().addObject('Path::FeatureShape','%s')",FeatName.c_str());
doCommand(Doc,"FreeCAD.activeDocument().%s.Shape = FreeCAD.activeDocument().%s.Shape.copy()",FeatName.c_str(),pcPartObject->getNameInDocument());
commitCommand();
updateActive();
} else {
Base::Console().Error("Exactly one shape object must be selected\n");
return;
std::list<std::string> cmds;
std::ostringstream sources;
for(const Gui::SelectionObject &selObj :
getSelection().getSelectionEx(NULL, Part::Feature::getClassTypeId()))
{
const Part::Feature *pcObj = static_cast<const Part::Feature*>(selObj.getObject());
const std::vector<std::string> &subnames = selObj.getSubNames();
if(subnames.empty()) {
sources << "FreeCAD.activeDocument()." << pcObj->getNameInDocument() << ",";
continue;
}
for(const std::string &name : subnames) {
if(name.compare(0,4,"Face") && name.compare(0,4,"Edge")) {
Base::Console().Warning("Ignored shape %s %s\n",
pcObj->getNameInDocument(), name.c_str());
continue;
}
std::ostringstream subname;
subname << pcObj->getNameInDocument() << '_' << name;
std::string sub_fname = getUniqueObjectName(subname.str().c_str());
std::ostringstream cmd;
cmd << "FreeCAD.activeDocument().addObject('Part::Feature','" << sub_fname <<
"').Shape = PathCommands.findShape(FreeCAD.activeDocument()." <<
pcObj->getNameInDocument() << ".Shape,'" << name << "'";
if(!name.compare(0,4,"Edge"))
cmd << ",'Wires'";
cmd << ')';
cmds.push_back(cmd.str());
sources << "FreeCAD.activeDocument()." << sub_fname << ",";
}
} else {
Base::Console().Error("Exactly one shape object must be selected\n");
return;
}
std::string FeatName = getUniqueObjectName("PathShape");
openCommand("Create Path Shape");
doCommand(Doc,"import PathCommands");
for(const std::string &cmd : cmds)
doCommand(Doc,cmd.c_str());
doCommand(Doc,"FreeCAD.activeDocument().addObject('Path::FeatureShape','%s')",FeatName.c_str());
doCommand(Doc,"FreeCAD.activeDocument().%s.Sources = [ %s ]",FeatName.c_str(),sources.str().c_str());
commitCommand();
updateActive();
}
bool CmdPathShape::isActive(void)

View File

@ -47,6 +47,7 @@
<file>icons/Path-Toolpath.svg</file>
<file>icons/Path-ToolTable.svg</file>
<file>icons/Path-Area.svg</file>
<file>icons/Path-Area-View.svg</file>
<file>icons/Path-Area-Workplane.svg</file>
<file>icons/preferences-path.svg</file>
<file>panels/ContourEdit.ui</file>

View File

@ -0,0 +1,657 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="64px"
height="64px"
id="svg2816"
version="1.1"
inkscape:version="0.91 r13725"
sodipodi:docname="Path-Area-View.svg">
<defs
id="defs2818">
<linearGradient
inkscape:collect="always"
id="linearGradient4066">
<stop
style="stop-color:#4e9a06;stop-opacity:1"
offset="0"
id="stop4068" />
<stop
style="stop-color:#8ae234;stop-opacity:1"
offset="1"
id="stop4070" />
</linearGradient>
<linearGradient
inkscape:collect="always"
id="linearGradient4022">
<stop
style="stop-color:#204a87;stop-opacity:1"
offset="0"
id="stop4024" />
<stop
style="stop-color:#729fcf;stop-opacity:1"
offset="1"
id="stop4026" />
</linearGradient>
<linearGradient
id="linearGradient4513">
<stop
style="stop-color:#ffffff;stop-opacity:1;"
offset="0"
id="stop4515" />
<stop
style="stop-color:#999999;stop-opacity:1;"
offset="1"
id="stop4517" />
</linearGradient>
<linearGradient
id="linearGradient3681">
<stop
id="stop3697"
offset="0"
style="stop-color:#fff110;stop-opacity:1;" />
<stop
style="stop-color:#cf7008;stop-opacity:1;"
offset="1"
id="stop3685" />
</linearGradient>
<inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 32 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="64 : 32 : 1"
inkscape:persp3d-origin="32 : 21.333333 : 1"
id="perspective2824" />
<inkscape:perspective
id="perspective3622"
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
inkscape:vp_z="1 : 0.5 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 0.5 : 1"
sodipodi:type="inkscape:persp3d" />
<inkscape:perspective
id="perspective3622-9"
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
inkscape:vp_z="1 : 0.5 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 0.5 : 1"
sodipodi:type="inkscape:persp3d" />
<inkscape:perspective
id="perspective3653"
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
inkscape:vp_z="1 : 0.5 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 0.5 : 1"
sodipodi:type="inkscape:persp3d" />
<inkscape:perspective
id="perspective3675"
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
inkscape:vp_z="1 : 0.5 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 0.5 : 1"
sodipodi:type="inkscape:persp3d" />
<inkscape:perspective
id="perspective3697"
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
inkscape:vp_z="1 : 0.5 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 0.5 : 1"
sodipodi:type="inkscape:persp3d" />
<inkscape:perspective
id="perspective3720"
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
inkscape:vp_z="1 : 0.5 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 0.5 : 1"
sodipodi:type="inkscape:persp3d" />
<inkscape:perspective
id="perspective3742"
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
inkscape:vp_z="1 : 0.5 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 0.5 : 1"
sodipodi:type="inkscape:persp3d" />
<inkscape:perspective
id="perspective3764"
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
inkscape:vp_z="1 : 0.5 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 0.5 : 1"
sodipodi:type="inkscape:persp3d" />
<inkscape:perspective
id="perspective3785"
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
inkscape:vp_z="1 : 0.5 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 0.5 : 1"
sodipodi:type="inkscape:persp3d" />
<inkscape:perspective
id="perspective3806"
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
inkscape:vp_z="1 : 0.5 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 0.5 : 1"
sodipodi:type="inkscape:persp3d" />
<inkscape:perspective
id="perspective3806-3"
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
inkscape:vp_z="1 : 0.5 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 0.5 : 1"
sodipodi:type="inkscape:persp3d" />
<inkscape:perspective
id="perspective3835"
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
inkscape:vp_z="1 : 0.5 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 0.5 : 1"
sodipodi:type="inkscape:persp3d" />
<inkscape:perspective
id="perspective3614"
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
inkscape:vp_z="1 : 0.5 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 0.5 : 1"
sodipodi:type="inkscape:persp3d" />
<inkscape:perspective
id="perspective3614-8"
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
inkscape:vp_z="1 : 0.5 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 0.5 : 1"
sodipodi:type="inkscape:persp3d" />
<inkscape:perspective
id="perspective3643"
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
inkscape:vp_z="1 : 0.5 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 0.5 : 1"
sodipodi:type="inkscape:persp3d" />
<inkscape:perspective
id="perspective3643-3"
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
inkscape:vp_z="1 : 0.5 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 0.5 : 1"
sodipodi:type="inkscape:persp3d" />
<inkscape:perspective
id="perspective3672"
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
inkscape:vp_z="1 : 0.5 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 0.5 : 1"
sodipodi:type="inkscape:persp3d" />
<inkscape:perspective
id="perspective3672-5"
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
inkscape:vp_z="1 : 0.5 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 0.5 : 1"
sodipodi:type="inkscape:persp3d" />
<inkscape:perspective
id="perspective3701"
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
inkscape:vp_z="1 : 0.5 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 0.5 : 1"
sodipodi:type="inkscape:persp3d" />
<inkscape:perspective
id="perspective3701-8"
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
inkscape:vp_z="1 : 0.5 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 0.5 : 1"
sodipodi:type="inkscape:persp3d" />
<inkscape:perspective
id="perspective3746"
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
inkscape:vp_z="1 : 0.5 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 0.5 : 1"
sodipodi:type="inkscape:persp3d" />
<pattern
patternTransform="matrix(0.67643728,-0.81829155,2.4578314,1.8844554,-26.450606,18.294947)"
id="pattern5231"
xlink:href="#Strips1_1-4"
inkscape:collect="always" />
<inkscape:perspective
id="perspective5224"
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
inkscape:vp_z="1 : 0.5 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 0.5 : 1"
sodipodi:type="inkscape:persp3d" />
<pattern
inkscape:stockid="Stripes 1:1"
id="Strips1_1-4"
patternTransform="matrix(0.66772843,-1.0037085,2.4261878,2.3114548,3.4760987,3.534923)"
height="1"
width="2"
patternUnits="userSpaceOnUse"
inkscape:collect="always">
<rect
id="rect4483-4"
height="2"
width="1"
y="-0.5"
x="0"
style="fill:black;stroke:none" />
</pattern>
<inkscape:perspective
id="perspective5224-9"
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
inkscape:vp_z="1 : 0.5 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 0.5 : 1"
sodipodi:type="inkscape:persp3d" />
<pattern
patternTransform="matrix(0.66772843,-1.0037085,2.4261878,2.3114548,39.618381,8.9692804)"
id="pattern5231-4"
xlink:href="#Strips1_1-6"
inkscape:collect="always" />
<inkscape:perspective
id="perspective5224-3"
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
inkscape:vp_z="1 : 0.5 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 0.5 : 1"
sodipodi:type="inkscape:persp3d" />
<pattern
inkscape:stockid="Stripes 1:1"
id="Strips1_1-6"
patternTransform="matrix(0.66772843,-1.0037085,2.4261878,2.3114548,3.4760987,3.534923)"
height="1"
width="2"
patternUnits="userSpaceOnUse"
inkscape:collect="always">
<rect
id="rect4483-0"
height="2"
width="1"
y="-0.5"
x="0"
style="fill:black;stroke:none" />
</pattern>
<pattern
patternTransform="matrix(0.66513382,-1.0631299,2.4167603,2.4482973,-49.762569,2.9546807)"
id="pattern5296"
xlink:href="#pattern5231-3"
inkscape:collect="always" />
<inkscape:perspective
id="perspective5288"
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
inkscape:vp_z="1 : 0.5 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 0.5 : 1"
sodipodi:type="inkscape:persp3d" />
<pattern
patternTransform="matrix(0.66772843,-1.0037085,2.4261878,2.3114548,-26.336284,10.887197)"
id="pattern5231-3"
xlink:href="#Strips1_1-4-3"
inkscape:collect="always" />
<pattern
inkscape:stockid="Stripes 1:1"
id="Strips1_1-4-3"
patternTransform="matrix(0.66772843,-1.0037085,2.4261878,2.3114548,3.4760987,3.534923)"
height="1"
width="2"
patternUnits="userSpaceOnUse"
inkscape:collect="always">
<rect
id="rect4483-4-6"
height="2"
width="1"
y="-0.5"
x="0"
style="fill:black;stroke:none" />
</pattern>
<pattern
patternTransform="matrix(0.42844886,-0.62155849,1.5567667,1.431396,27.948414,13.306456)"
id="pattern5330"
xlink:href="#Strips1_1-9"
inkscape:collect="always" />
<inkscape:perspective
id="perspective5323"
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
inkscape:vp_z="1 : 0.5 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 0.5 : 1"
sodipodi:type="inkscape:persp3d" />
<pattern
inkscape:stockid="Stripes 1:1"
id="Strips1_1-9"
patternTransform="matrix(0.66772843,-1.0037085,2.4261878,2.3114548,3.4760987,3.534923)"
height="1"
width="2"
patternUnits="userSpaceOnUse"
inkscape:collect="always">
<rect
id="rect4483-3"
height="2"
width="1"
y="-0.5"
x="0"
style="fill:black;stroke:none" />
</pattern>
<inkscape:perspective
id="perspective5361"
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
inkscape:vp_z="1 : 0.5 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 0.5 : 1"
sodipodi:type="inkscape:persp3d" />
<inkscape:perspective
id="perspective5383"
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
inkscape:vp_z="1 : 0.5 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 0.5 : 1"
sodipodi:type="inkscape:persp3d" />
<inkscape:perspective
id="perspective5411"
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
inkscape:vp_z="1 : 0.5 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 0.5 : 1"
sodipodi:type="inkscape:persp3d" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient3681"
id="linearGradient3687"
x1="37.89756"
y1="41.087898"
x2="4.0605712"
y2="40.168594"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(127.27273,-51.272729)" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient3681"
id="linearGradient3695"
x1="37.894287"
y1="40.484772"
x2="59.811455"
y2="43.558987"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(127.27273,-51.272729)" />
<linearGradient
id="linearGradient3681-3">
<stop
id="stop3697-3"
offset="0"
style="stop-color:#fff110;stop-opacity:1;" />
<stop
style="stop-color:#cf7008;stop-opacity:1;"
offset="1"
id="stop3685-4" />
</linearGradient>
<linearGradient
y2="43.558987"
x2="59.811455"
y1="40.484772"
x1="37.894287"
gradientTransform="translate(-37.00068,-20.487365)"
gradientUnits="userSpaceOnUse"
id="linearGradient3608"
xlink:href="#linearGradient3681-3"
inkscape:collect="always" />
<linearGradient
id="linearGradient4513-2">
<stop
style="stop-color:#ffffff;stop-opacity:1;"
offset="0"
id="stop4515-2" />
<stop
style="stop-color:#999999;stop-opacity:1;"
offset="1"
id="stop4517-4" />
</linearGradient>
<radialGradient
r="23.634638"
fy="7.9319997"
fx="32.151962"
cy="7.9319997"
cx="32.151962"
gradientTransform="matrix(1,0,0,1.1841158,-8.5173246,-3.4097568)"
gradientUnits="userSpaceOnUse"
id="radialGradient4538"
xlink:href="#linearGradient4513-2"
inkscape:collect="always" />
<linearGradient
id="linearGradient4513-1">
<stop
style="stop-color:#ffffff;stop-opacity:1;"
offset="0"
id="stop4515-8" />
<stop
style="stop-color:#999999;stop-opacity:1;"
offset="1"
id="stop4517-6" />
</linearGradient>
<radialGradient
r="23.634638"
fy="7.9319997"
fx="32.151962"
cy="7.9319997"
cx="32.151962"
gradientTransform="matrix(1,0,0,1.1841158,-8.5173246,-3.4097568)"
gradientUnits="userSpaceOnUse"
id="radialGradient4538-6"
xlink:href="#linearGradient4513-1"
inkscape:collect="always" />
<linearGradient
id="linearGradient4513-1-3">
<stop
style="stop-color:#ffffff;stop-opacity:1;"
offset="0"
id="stop4515-8-7" />
<stop
style="stop-color:#999999;stop-opacity:1;"
offset="1"
id="stop4517-6-5" />
</linearGradient>
<radialGradient
r="23.634638"
fy="35.869175"
fx="32.151962"
cy="35.869175"
cx="32.151962"
gradientTransform="matrix(0.39497909,0,0,1.1841158,-2.716491,-26.067007)"
gradientUnits="userSpaceOnUse"
id="radialGradient3069"
xlink:href="#linearGradient4513-1-3"
inkscape:collect="always" />
<linearGradient
id="linearGradient4513-1-2">
<stop
style="stop-color:#ffffff;stop-opacity:1;"
offset="0"
id="stop4515-8-6" />
<stop
style="stop-color:#999999;stop-opacity:1;"
offset="1"
id="stop4517-6-6" />
</linearGradient>
<radialGradient
r="23.634638"
fy="35.869175"
fx="32.151962"
cy="35.869175"
cx="32.151962"
gradientTransform="matrix(0.39497909,0,0,1.1841158,-2.716491,-26.067007)"
gradientUnits="userSpaceOnUse"
id="radialGradient3102"
xlink:href="#linearGradient4513-1-2"
inkscape:collect="always" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient4513-1"
id="radialGradient3132"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.39497909,0,0,1.1841158,64.952609,-7.0541574)"
cx="32.151962"
cy="27.950663"
fx="32.151962"
fy="27.950663"
r="23.634638" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient4022"
id="linearGradient4028"
x1="36.538239"
y1="45.928013"
x2="36.804447"
y2="23.332739"
gradientUnits="userSpaceOnUse" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient4066"
id="linearGradient4072"
x1="41.610756"
y1="59.853931"
x2="32.101425"
y2="10.99928"
gradientUnits="userSpaceOnUse" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient4031"
id="linearGradient4055"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(71.494719,-3.1982556)"
x1="30.000002"
y1="14"
x2="36"
y2="54.227272" />
<linearGradient
id="linearGradient4031">
<stop
id="stop4033"
offset="0"
style="stop-color:#d3d7cf;stop-opacity:1" />
<stop
id="stop4035"
offset="1"
style="stop-color:#888a85;stop-opacity:1" />
</linearGradient>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="3.8890873"
inkscape:cx="-7.4813451"
inkscape:cy="10.659623"
inkscape:current-layer="layer1"
showgrid="true"
inkscape:document-units="px"
inkscape:grid-bbox="true"
inkscape:snap-bbox="true"
inkscape:bbox-paths="true"
inkscape:bbox-nodes="true"
inkscape:snap-bbox-edge-midpoints="true"
inkscape:snap-bbox-midpoints="true"
inkscape:object-paths="true"
inkscape:object-nodes="true"
inkscape:window-width="1375"
inkscape:window-height="876"
inkscape:window-x="65"
inkscape:window-y="24"
inkscape:window-maximized="1"
inkscape:snap-global="true">
<inkscape:grid
type="xygrid"
id="grid3234"
empspacing="2"
visible="true"
enabled="true"
snapvisiblegridlinesonly="true" />
</sodipodi:namedview>
<metadata
id="metadata2821">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
<dc:title>Path-FaceProfile</dc:title>
<dc:date>2016-01-19</dc:date>
<dc:relation>http://www.freecadweb.org/wiki/index.php?title=Artwork</dc:relation>
<dc:publisher>
<cc:Agent>
<dc:title>FreeCAD</dc:title>
</cc:Agent>
</dc:publisher>
<dc:identifier>FreeCAD/src/Mod/Path/Gui/Resources/icons/Path-</dc:identifier>
<dc:rights>
<cc:Agent>
<dc:title>FreeCAD LGPL2+</dc:title>
</cc:Agent>
</dc:rights>
<cc:license>https://www.gnu.org/copyleft/lesser.html</cc:license>
<dc:contributor>
<cc:Agent>
<dc:title>[agryson] Alexander Gryson</dc:title>
</cc:Agent>
</dc:contributor>
</cc:Work>
</rdf:RDF>
</metadata>
<g
id="layer1"
inkscape:label="Layer 1"
inkscape:groupmode="layer">
<path
style="color:#000000;fill:url(#linearGradient4028);fill-opacity:1;fill-rule:nonzero;stroke:#0b1521;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
d="M 33,21 17,45 41,49 53,33 49,23 z"
id="rect3083"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccccc" />
<path
style="color:#000000;fill:none;stroke:#172a04;stroke-width:8;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
d="M 50,13 39,11 c -4,-1 -5.171573,-1.4142136 -8,0 -2,1 -3,2 -5,5 L 7,46 c -3,4 -1,7 3,8 l 28,4 c 5,1 9,-2 12,-6 l 4,-5"
id="rect3083-0"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccscccccc" />
<path
style="color:#000000;fill:none;stroke:#729fcf;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
d="M 33.965909,23.113636 20.349931,43.571429 40.140486,46.839294 50.710729,32.691101 47.57301,24.842851 z"
id="rect3083-3"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccccc" />
<path
style="color:#000000;fill:none;stroke:url(#linearGradient4072);stroke-width:4;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
d="M 50,13 39,11 c -4,-1 -5.171573,-1.414214 -8,0 -2,1 -3,2 -5,5 L 7,46 c -3,4 -1,7 3,8 l 28,4 c 5,1 9,-2 12,-6 l 4,-5"
id="rect3083-0-6"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccscccccc" />
<path
style="color:#000000;fill:none;stroke:#8ae234;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
d="M 50.064283,12 38.517882,9.919647 c -4,-1.0000001 -5.171573,-1.4142141 -8,0 -2,1 -3,2 -5,5 l -19.0000003,30 c -2.9999999,4 -1,7 2.9999998,8 l 28.0000005,4 c 5,1 9,-2 12,-6 L 53,46"
id="rect3083-0-6-7"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccscccccc" />
<path
id="path3599"
d="m 26.202906,1.6813301 c -9.673905,0 -20.9601314,6.2397496 -25.79708379,18.7192689 C 5.2427746,29.76025 14.916679,36 26.202906,36 37.489143,36 47.163048,29.76025 52,20.400599 47.163048,7.9210797 35.876821,1.6813301 26.202906,1.6813301 Z m 0,31.1987859 c -9.673905,0 -17.7354897,-6.23975 -19.3478099,-12.479517 C 8.4674163,14.160848 16.529001,7.9210797 26.202906,7.9210797 c 9.673915,0 17.7355,6.2397683 19.34782,12.4795193 -1.61232,6.239767 -9.673905,12.479517 -19.34782,12.479517 z m 0,-21.839152 c -1.007692,0 -1.914621,0.194993 -2.821549,0.438734 1.662698,0.731222 2.821549,2.339912 2.821549,4.241091 0,2.583655 -2.166544,4.67981 -4.836953,4.67981 -1.965006,0 -3.627715,-1.121208 -4.383483,-2.729881 -0.251923,0.877467 -0.453469,1.754917 -0.453469,2.729881 0,5.167306 4.333107,9.359651 9.673905,9.359651 5.340808,0 9.673915,-4.192345 9.673915,-9.359651 0,-5.167292 -4.333107,-9.359635 -9.673915,-9.359635 z"
inkscape:connector-curvature="0" />
<path
style="opacity:0.84799972;fill:#ffffff;stroke:#ed1521;stroke-width:0.18025407;stroke-opacity:1"
d="M 24.513492,32.743549 C 20.158618,32.279756 16.320657,30.874437 13.101873,28.565038 10.565128,26.744986 8.0555748,23.645238 7.240207,21.324834 6.9310922,20.445146 6.9319596,20.410993 7.289147,19.39655 c 1.39318,-3.956736 5.894877,-8.025327 10.97742,-9.9212893 2.388118,-0.8908503 4.189049,-1.243432 6.954324,-1.3615029 3.700832,-0.1580165 6.748099,0.3948804 10.068295,1.8267941 4.509958,1.9450261 8.541022,5.8276231 9.860413,9.4972421 l 0.343034,0.954079 -0.406823,1.087884 c -1.903008,5.08883 -8.045412,9.656313 -14.648854,10.892867 -1.533211,0.287106 -4.793511,0.491266 -5.923465,0.370925 z m 3.944714,-3.168696 c 3.268532,-0.752696 6.040924,-3.250309 7.009108,-6.314414 0.74188,-2.347903 0.609807,-4.448295 -0.422862,-6.724951 -1.727573,-3.808655 -5.799065,-6.000195 -10.098702,-5.435771 -0.713226,0.09362 -1.36028,0.238742 -1.437897,0.322477 -0.07761,0.08374 0.205724,0.373163 0.629646,0.643172 2.40184,1.529797 2.621108,4.930323 0.448249,6.951712 -2.087088,1.941597 -5.735326,1.528636 -7.161292,-0.810623 -0.190194,-0.312008 -0.397714,-0.56753 -0.461157,-0.567826 -0.184558,-8.66e-4 -0.490195,1.808374 -0.485585,2.874447 0.0097,2.22833 0.762415,4.123494 2.376571,5.983168 2.345137,2.701835 6.063739,3.893862 9.603921,3.078609 z"
id="path3630"
inkscape:connector-curvature="0" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 24 KiB

View File

@ -115,11 +115,83 @@ bool ViewProviderArea::onDelete(const std::vector<std::string> &)
// Python object -----------------------------------------------------------------------
PROPERTY_SOURCE(PathGui::ViewProviderAreaView, PartGui::ViewProviderPlaneParametric)
ViewProviderAreaView::ViewProviderAreaView()
{
sPixmap = "Path-Area-View.svg";
}
ViewProviderAreaView::~ViewProviderAreaView()
{
}
std::vector<App::DocumentObject*> ViewProviderAreaView::claimChildren(void) const
{
std::vector<App::DocumentObject*> ret;
Path::FeatureAreaView* feature = static_cast<Path::FeatureAreaView*>(getObject());
if(feature->Source.getValue())
ret.push_back(feature->Source.getValue());
return ret;
}
bool ViewProviderAreaView::canDragObjects() const
{
return true;
}
bool ViewProviderAreaView::canDragObject(App::DocumentObject* obj) const
{
return obj && obj->getTypeId().isDerivedFrom(Path::FeatureArea::getClassTypeId());
}
void ViewProviderAreaView::dragObject(App::DocumentObject* )
{
Path::FeatureAreaView* feature = static_cast<Path::FeatureAreaView*>(getObject());
feature->Source.setValue(NULL);
}
bool ViewProviderAreaView::canDropObjects() const
{
return true;
}
bool ViewProviderAreaView::canDropObject(App::DocumentObject* obj) const
{
return canDragObject(obj);
}
void ViewProviderAreaView::dropObject(App::DocumentObject* obj)
{
Path::FeatureAreaView* feature = static_cast<Path::FeatureAreaView*>(getObject());
feature->Source.setValue(obj);
}
void ViewProviderAreaView::updateData(const App::Property* prop)
{
PartGui::ViewProviderPlaneParametric::updateData(prop);
if (prop->getTypeId() == App::PropertyLink::getClassTypeId())
Gui::Application::Instance->hideViewProvider(
static_cast<const App::PropertyLink*>(prop)->getValue());
}
bool ViewProviderAreaView::onDelete(const std::vector<std::string> &)
{
Path::FeatureAreaView* feature = static_cast<Path::FeatureAreaView*>(getObject());
Gui::Application::Instance->showViewProvider(feature->Source.getValue());
return true;
}
// Python object -----------------------------------------------------------------------
namespace Gui {
/// @cond DOXERR
PROPERTY_SOURCE_TEMPLATE(PathGui::ViewProviderAreaPython, PathGui::ViewProviderArea)
PROPERTY_SOURCE_TEMPLATE(PathGui::ViewProviderAreaViewPython, PathGui::ViewProviderAreaView)
/// @endcond
// explicit template instantiation
template class PathGuiExport ViewProviderPythonFeatureT<PathGui::ViewProviderArea>;
template class PathGuiExport ViewProviderPythonFeatureT<PathGui::ViewProviderAreaView>;
}

View File

@ -54,6 +54,29 @@ public:
typedef Gui::ViewProviderPythonFeatureT<ViewProviderArea> ViewProviderAreaPython;
class PathGuiExport ViewProviderAreaView : public PartGui::ViewProviderPlaneParametric
{
PROPERTY_HEADER(PathGui::ViewProviderAreaView);
public:
ViewProviderAreaView();
virtual ~ViewProviderAreaView();
virtual std::vector<App::DocumentObject*> claimChildren(void) const;
virtual void updateData(const App::Property*);
virtual bool onDelete(const std::vector<std::string> &);
/// 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<ViewProviderAreaView> ViewProviderAreaViewPython;
} //namespace PathGui

View File

@ -26,8 +26,11 @@
#ifndef _PreComp_
#endif
#include "ViewProviderPathShape.h"
#include <Gui/BitmapFactory.h>
#include <Gui/Application.h>
#include <Mod/Part/App/PartFeature.h>
#include <Mod/Path/App/FeaturePathShape.h>
#include "ViewProviderPathShape.h"
using namespace Gui;
using namespace PathGui;
@ -38,3 +41,74 @@ QIcon ViewProviderPathShape::getIcon() const
{
return Gui::BitmapFactory().pixmap("Path-Shape");
}
std::vector<App::DocumentObject*> ViewProviderPathShape::claimChildren(void) const
{
return std::vector<App::DocumentObject*>(
static_cast<Path::FeatureShape*>(getObject())->Sources.getValues());
}
bool ViewProviderPathShape::canDragObjects() const
{
return true;
}
bool ViewProviderPathShape::canDragObject(App::DocumentObject* obj) const
{
return obj && obj->getTypeId().isDerivedFrom(Part::Feature::getClassTypeId());
}
void ViewProviderPathShape::dragObject(App::DocumentObject* obj)
{
Path::FeatureShape *feature = static_cast<Path::FeatureShape*>(getObject());
std::vector<App::DocumentObject*> sources = feature->Sources.getValues();
for (std::vector<App::DocumentObject*>::iterator it = sources.begin(); it != sources.end(); ++it) {
if (*it == obj) {
sources.erase(it);
feature->Sources.setValues(sources);
break;
}
}
}
bool ViewProviderPathShape::canDropObjects() const
{
return true;
}
bool ViewProviderPathShape::canDropObject(App::DocumentObject* obj) const
{
return canDragObject(obj);
}
void ViewProviderPathShape::dropObject(App::DocumentObject* obj)
{
Path::FeatureShape *feature = static_cast<Path::FeatureShape*>(getObject());
std::vector<App::DocumentObject*> sources = feature->Sources.getValues();
sources.push_back(obj);
feature->Sources.setValues(sources);
}
void ViewProviderPathShape::updateData(const App::Property* prop)
{
PathGui::ViewProviderPath::updateData(prop);
if (prop->getTypeId() == App::PropertyLinkList::getClassTypeId()) {
std::vector<App::DocumentObject*> pShapes = static_cast<const App::PropertyLinkList*>(prop)->getValues();
for (std::vector<App::DocumentObject*>::iterator it = pShapes.begin(); it != pShapes.end(); ++it) {
if (*it)
Gui::Application::Instance->hideViewProvider(*it);
}
}
}
bool ViewProviderPathShape::onDelete(const std::vector<std::string> &)
{
// get the input shapes
Path::FeatureShape *feature = static_cast<Path::FeatureShape*>(getObject());
std::vector<App::DocumentObject*> pShapes =feature->Sources.getValues();
for (std::vector<App::DocumentObject*>::iterator it = pShapes.begin(); it != pShapes.end(); ++it) {
if (*it)
Gui::Application::Instance->showViewProvider(*it);
}
return true;
}

View File

@ -35,6 +35,19 @@ class PathGuiExport ViewProviderPathShape: public ViewProviderPath
public:
/// grouping handling
virtual std::vector<App::DocumentObject*> claimChildren(void) const;
virtual void updateData(const App::Property*);
virtual bool onDelete(const std::vector<std::string> &);
/// drag and drop
virtual bool canDragObjects() const;
virtual bool canDragObject(App::DocumentObject*) const;
virtual void dragObject(App::DocumentObject*);
virtual bool canDropObjects() const;
virtual bool canDropObject(App::DocumentObject*) const;
virtual void dropObject(App::DocumentObject*);
QIcon getIcon(void) const;
};

View File

@ -81,12 +81,12 @@ class PathWorkbench (Workbench):
# build commands list
projcmdlist = ["Path_Job", "Path_Post", "Path_Inspect", "Path_Sanity"]
toolcmdlist = ["Path_ToolLibraryEdit", "Path_LoadTool"]
prepcmdlist = ["Path_Plane", "Path_Fixture", "Path_ToolLenOffset", "Path_Comment", "Path_Stop", "Path_FaceProfile", "Path_FacePocket", "Path_Custom", "Path_FromShape"]
prepcmdlist = ["Path_Plane", "Path_Fixture", "Path_ToolLenOffset", "Path_Comment", "Path_Stop", "Path_FaceProfile", "Path_FacePocket", "Path_Custom", "Path_Shape"]
twodopcmdlist = ["Path_Contour", "Path_Profile", "Path_Profile_Edges", "Path_Pocket", "Path_Drilling", "Path_Engrave", "Path_MillFace", "Path_Helix"]
threedopcmdlist = ["Path_Surfacing"]
modcmdlist = ["Path_Copy", "Path_CompoundExtended", "Path_Array", "Path_SimpleCopy" ]
dressupcmdlist = ["PathDressup_Dogbone", "PathDressup_DragKnife", "PathDressup_HoldingTags"]
extracmdlist = ["Path_SelectLoop", "Path_Area", "Path_Area_Workplane"]
extracmdlist = ["Path_SelectLoop", "Path_Shape", "Path_Area", "Path_Area_Workplane"]
#modcmdmore = ["Path_Hop",]
#remotecmdlist = ["Path_Remote"]

View File

@ -78,3 +78,28 @@ class _CommandSelectLoop:
if FreeCAD.GuiUp:
FreeCADGui.addCommand('Path_SelectLoop',_CommandSelectLoop())
def findShape(shape,subname=None,subtype=None):
'''To find a higher oder shape containing the subshape with subname.
E.g. to find the wire containing 'Edge1' in shape,
findShape(shape,'Edge1','Wires')
'''
if not subname:
return shape
ret = shape.getElement(subname)
if not subtype or not ret or ret.isNull():
return ret;
if subname.startswith('Face'):
tp = 'Faces'
elif subname.startswith('Edge'):
tp = 'Edges'
elif subname.startswith('Vertex'):
tp = 'Vertex'
else:
return ret
for obj in getattr(shape,subtype):
for sobj in getattr(obj,tp):
if sobj.isEqual(ret):
return obj
return ret

View File

@ -73,6 +73,58 @@ Point CArea::NearestPoint(const Point& p)const
return best_point;
}
void CArea::ChangeStartToNearest(const Point *point, double min_dist) {
for(std::list<CCurve>::const_iterator It=m_curves.begin(),ItNext=It;
It != m_curves.end(); It=ItNext)
{
++ItNext;
if(It->m_vertices.size()<=1)
m_curves.erase(It);
}
if(m_curves.empty()) return;
std::list<CCurve> curves;
Point p;
if(point) p =*point;
if(min_dist < Point::tolerance)
min_dist = Point::tolerance;
while(m_curves.size()) {
std::list<CCurve>::iterator It=m_curves.begin();
std::list<CCurve>::iterator ItBest=It++;
Point best_point = ItBest->NearestPoint(p);
double best_dist = p.dist(best_point);
for(; It != m_curves.end(); ++It)
{
const CCurve& curve = *It;
Point near_point = curve.NearestPoint(p);
double dist = near_point.dist(p);
if(dist < best_dist)
{
best_dist = dist;
best_point = near_point;
ItBest = It;
}
}
if(ItBest->IsClosed()) {
ItBest->ChangeStart(best_point);
}else if(ItBest->m_vertices.back().m_p.dist(best_point)<=min_dist) {
ItBest->Reverse();
}else if(ItBest->m_vertices.front().m_p.dist(best_point)>min_dist) {
ItBest->Break(best_point);
m_curves.push_back(*ItBest);
m_curves.back().ChangeEnd(best_point);
ItBest->ChangeStart(best_point);
}
curves.splice(curves.end(),m_curves,ItBest);
p = curves.back().m_vertices.back().m_p;
}
m_curves.splice(m_curves.end(),curves);
}
void CArea::GetBox(CBox2D &box)
{
for(std::list<CCurve>::iterator It = m_curves.begin(); It != m_curves.end(); It++)

View File

@ -85,6 +85,8 @@ public:
void CurveIntersections(const CCurve& curve, std::list<Point> &pts)const;
void InsideCurves(const CCurve& curve, std::list<CCurve> &curves_inside)const;
void ChangeStartToNearest(const Point *point=NULL, double min_dist=1.0);
//Avoid outside direct accessing static member variable because of Windows DLL issue
#define CAREA_PARAM_DECLARE(_type,_name) \
static _type get_##_name();\

View File

@ -468,16 +468,23 @@ void CArea::Offset(double inwards_value)
void CArea::PopulateClipper(Clipper &c, PolyType type) const
{
int skipped = 0;
for (std::list<CCurve>::const_iterator It = m_curves.begin(); It != m_curves.end(); It++)
{
const CCurve &curve = *It;
bool closed = curve.IsClosed();
if(type == ptClip && !closed)
continue;
if(!closed) {
if(type == ptClip){
++skipped;
continue;
}
}
TPolygon p;
MakePoly(curve, p, false);
c.AddPath(p, type, closed);
}
if(skipped)
std::cout << "libarea: warning skipped " << skipped << " open wires" << std::endl;
}
void CArea::Clip(ClipType op, const CArea *a,