Path.Area: added segmentation and 3D wire support

This commit is contained in:
Zheng, Lei 2017-01-31 19:09:33 +08:00
parent c3293f00be
commit f834206ba7
9 changed files with 712 additions and 246 deletions

View File

@ -126,13 +126,13 @@ public:
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"
"sortWires(shapes, start=Vector(), params=None, " PARAM_PY_ARGS_DOC(ARG,AREA_PARAMS_SORT) ")\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)
PARAM_PY_DOC(ARG, AREA_PARAMS_SORT)
);
initialize("This module is the Path module."); // register with Python
}
@ -320,14 +320,17 @@ private:
Py::Object fromShapes(const Py::Tuple& args, const Py::Dict &kwds)
{
PARAM_PY_DECLARE_INIT(PARAM_FARG,AREA_PARAMS_PATH)
PARAM_PY_DECLARE_INIT(PARAM_FNAME,AREA_PARAMS_CONF)
PyObject *pShapes=NULL;
PyObject *start=NULL;
static char* kwd_list[] = {"shapes", "start",
PARAM_FIELD_STRINGS(ARG,AREA_PARAMS_PATH), NULL};
PARAM_FIELD_STRINGS(ARG,AREA_PARAMS_PATH),
PARAM_FIELD_STRINGS(NAME,AREA_PARAMS_CONF), NULL};
if (!PyArg_ParseTupleAndKeywords(args.ptr(), kwds.ptr(),
"O|O!" PARAM_PY_KWDS(AREA_PARAMS_PATH), kwd_list,
&pShapes, &(Base::VectorPy::Type), &start,
PARAM_REF(PARAM_FARG,AREA_PARAMS_PATH)))
PARAM_REF(PARAM_FARG,AREA_PARAMS_PATH),
PARAM_REF(PARAM_FNAME,AREA_PARAMS_CONF)))
throw Py::Exception();
std::list<TopoDS_Shape> shapes;
@ -347,6 +350,12 @@ private:
}
}
#define AREA_GET(_param) \
params.PARAM_FNAME(_param) = \
PARAM_TYPED(PARAM_CAST_PY_,_param)(PARAM_FNAME(_param));
AreaParams params;
PARAM_FOREACH(AREA_GET,AREA_PARAMS_CONF)
gp_Pnt pstart;
if(start) {
Base::Vector3d vec = static_cast<Base::VectorPy*>(start)->value();
@ -355,24 +364,26 @@ private:
try {
std::unique_ptr<Toolpath> path(new Toolpath);
Area::toPath(*path,shapes,&pstart,PARAM_PY_FIELDS(PARAM_FARG,AREA_PARAMS_PATH));
Area::toPath(*path,shapes,&params, &pstart, NULL,
PARAM_PY_FIELDS(PARAM_FARG,AREA_PARAMS_PATH));
return Py::asObject(new PathPy(path.release()));
} PATH_CATCH
}
Py::Object sortWires(const Py::Tuple& args, const Py::Dict &kwds)
{
AreaParams params;
PARAM_PY_DECLARE_INIT(PARAM_FARG,AREA_PARAMS_SORT_WIRES)
PARAM_PY_DECLARE_INIT(PARAM_FARG,AREA_PARAMS_SORT)
PARAM_PY_DECLARE_INIT(PARAM_FNAME,AREA_PARAMS_CONF)
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};
static char* kwd_list[] = {"shapes", "start",
PARAM_FIELD_STRINGS(ARG,AREA_PARAMS_SORT),
PARAM_FIELD_STRINGS(NAME,AREA_PARAMS_CONF), NULL};
if (!PyArg_ParseTupleAndKeywords(args.ptr(), kwds.ptr(),
"O|O!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)))
"O|O!" PARAM_PY_KWDS(AREA_PARAMS_SORT), kwd_list,
&pShapes, &(Base::VectorPy::Type), &start,
PARAM_REF(PARAM_FARG,AREA_PARAMS_SORT),
PARAM_REF(PARAM_FNAME,AREA_PARAMS_CONF)))
throw Py::Exception();
std::list<TopoDS_Shape> shapes;
@ -391,23 +402,8 @@ private:
}
}
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)
}
AreaParams params;
PARAM_FOREACH(AREA_GET,AREA_PARAMS_CONF)
gp_Pnt pstart,pend;
if(start) {
@ -417,7 +413,7 @@ private:
try {
std::list<TopoDS_Shape> wires = Area::sortWires(shapes,&params,&pstart,
&pend, PARAM_PY_FIELDS(PARAM_FARG,AREA_PARAMS_SORT_WIRES));
&pend, PARAM_PY_FIELDS(PARAM_FARG,AREA_PARAMS_SORT));
PyObject *list = PyList_New(0);
for(auto &wire : wires)
PyList_Append(list,Py::new_reference_to(

View File

@ -23,8 +23,6 @@
#ifndef _PreComp_
#endif
#include "boost/date_time/posix_time/posix_time.hpp"
#include <boost/range/adaptor/reversed.hpp>
#include <BRepLib.hxx>
@ -54,14 +52,16 @@
#include <gp_GTrsf.hxx>
#include <Standard_Version.hxx>
#include <GCPnts_QuasiUniformDeflection.hxx>
#include <GCPnts_UniformAbscissa.hxx>
#include <BRepBndLib.hxx>
#include <BRepLib_MakeFace.hxx>
#include <Bnd_Box.hxx>
#include <BRepBuilderAPI_Copy.hxx>
#include <BRepBuilderAPI_MakeVertex.hxx>
#include <BRepExtrema_DistShapeShape.hxx>
#include <Base/Exception.h>
#include <Base/Tools.h>
#include <Base/Console.h>
#include <App/Application.h>
#include <App/Document.h>
@ -73,7 +73,6 @@
#include "../libarea/Area.h"
using namespace Path;
using namespace boost::posix_time;
CAreaParams::CAreaParams()
:PARAM_INIT(PARAM_FNAME,AREA_PARAMS_CAREA)
@ -310,7 +309,7 @@ void Area::add(CArea &area, const TopoDS_Wire& wire,
}
if(!to_edges) {
if(BRep_Tool::IsClosed(wire) && !ccurve.IsClosed()) {
Base::Console().Warning("ccurve not closed\n");
AREA_WARN("ccurve not closed");
ccurve.append(ccurve.m_vertices.front());
}
area.append(ccurve);
@ -403,7 +402,7 @@ void Area::addToBuild(CArea &area, const TopoDS_Shape &shape) {
if(&area == myArea.get() || myParams.OpenMode == OpenModeNone)
myAreaOpen->m_curves.splice(myAreaOpen->m_curves.end(),areaOpen.m_curves);
else
Base::Console().Warning("open wires discarded in clipping shapes\n");
AREA_WARN("open wires discarded in clipping shapes");
}
}
@ -452,70 +451,92 @@ static void show(const TopoDS_Shape &shape, const char *name) {
}
#endif
TopoDS_Shape Area::findPlane(const TopoDS_Shape &shape, gp_Trsf &trsf)
{
TopoDS_Shape plane;
double top_z;
if(!findPlane(shape,TopAbs_FACE,plane,trsf,top_z) &&
!findPlane(shape,TopAbs_WIRE,plane,trsf,top_z))
findPlane(shape,TopAbs_EDGE,plane,trsf,top_z);
return plane;
template<class Func>
static int foreachSubshape(const TopoDS_Shape &shape, Func func, int type=TopAbs_FACE) {
bool haveShape = false;
switch(type) {
case TopAbs_FACE:
for(TopExp_Explorer it(shape,TopAbs_FACE); it.More(); it.Next()) {
haveShape = true;
func(it.Current(),TopAbs_FACE);
}
if(haveShape) return TopAbs_FACE;
//fall through
case TopAbs_WIRE:
for(TopExp_Explorer it(shape,TopAbs_WIRE); it.More(); it.Next()) {
haveShape = true;
func(it.Current(),TopAbs_WIRE);
}
if(haveShape) return TopAbs_WIRE;
//fall through
default:
for(TopExp_Explorer it(shape,TopAbs_EDGE); it.More(); it.Next()) {
haveShape = true;
func(it.Current(),TopAbs_EDGE);
}
}
return haveShape?TopAbs_EDGE:-1;
}
bool Area::findPlane(const TopoDS_Shape &shape, int type,
TopoDS_Shape &dst, gp_Trsf &dst_trsf, double &top_z)
{
bool haveShape = false;
bool top_found = !dst.IsNull();
gp_Trsf trsf;
for(TopExp_Explorer it(shape,(TopAbs_ShapeEnum)type); it.More(); it.Next()) {
haveShape = true;
const TopoDS_Shape &plane = it.Current();
BRepLib_FindSurface planeFinder(plane,-1,Standard_True);
if (!planeFinder.Found())
continue;
gp_Ax3 pos = GeomAdaptor_Surface(planeFinder.Surface()).Plane().Position();
struct FindPlane {
TopoDS_Shape &myShape;
gp_Trsf &myTrsf;
double &myZ;
FindPlane(TopoDS_Shape &s, gp_Trsf &t, double &z)
:myShape(s),myTrsf(t),myZ(z)
{}
void operator()(const TopoDS_Shape &shape, int) {
gp_Trsf trsf;
BRepLib_FindSurface finder(shape,-1,Standard_True);
if (!finder.Found())
return;
gp_Ax3 pos = GeomAdaptor_Surface(finder.Surface()).Plane().Position();
//force plane to be right handed
if(!pos.Direct())
pos = gp_Ax3(pos.Ax2());
gp_Dir dir(pos.Direction());
trsf.SetTransformation(pos);
gp_Pnt origin = pos.Location();
if(fabs(dir.X())<Precision::Confusion() &&
fabs(dir.Y())<Precision::Confusion())
fabs(dir.Y())<Precision::Confusion())
{
gp_Pnt origin = pos.Location();
// Probably another OCC bug, sometimes pos.Location().Z() for XY
// plane is stuck at zero, even though the plane is at above. So we
// double check the first vertex Z value
TopExp_Explorer it(plane,TopAbs_VERTEX);
TopExp_Explorer it(shape,TopAbs_VERTEX);
double z = BRep_Tool::Pnt(TopoDS::Vertex(it.Current())).Z();
if(fabs(origin.Z()-z)>Precision::Confusion()) {
Base::Console().Warning("XY plane has wrong Z height %lf, %lf\n",origin.Z(),z);
AREA_WARN("XY plane has wrong Z height "<<origin.Z()<<", "<<z);
gp_Trsf trsf2;
trsf2.SetTranslationPart(gp_XYZ(0,0,origin.Z()-z));
trsf.Multiply(trsf2);
}
gp_Pnt pt = origin.Transformed(TopLoc_Location(trsf));
if(fabs(pt.X()) > Precision::Confusion() ||
fabs(pt.Y()) > Precision::Confusion() ||
fabs(pt.Z()) > Precision::Confusion()) {
Base::Console().Warning("wrong transformation %lf, %lf, %lf\n",pt.X(),pt.Y(),pt.Z());
fabs(pt.Y()) > Precision::Confusion() ||
fabs(pt.Z()) > Precision::Confusion()) {
AREA_WARN("wrong transformation "<<AREA_PT(pt));
}
if(top_found && top_z > z)
continue;
top_found = true;
top_z = z;
}else if(!dst.IsNull())
continue;
dst = plane;
dst_trsf = trsf;
if(!myShape.IsNull() && myZ > z)
return;
myZ = z;
}else if(!myShape.IsNull())
return;
myShape = shape;
myTrsf = trsf;
}
return haveShape;
};
TopoDS_Shape Area::findPlane(const TopoDS_Shape &shape, gp_Trsf &trsf)
{
TopoDS_Shape plane;
double top_z;
foreachSubshape(shape,FindPlane(plane,trsf,top_z));
return plane;
}
std::vector<shared_ptr<Area> > Area::makeSections(
@ -534,6 +555,8 @@ std::vector<shared_ptr<Area> > Area::makeSections(
if(plane.IsNull())
throw Base::ValueError("failed to obtain section plane");
TIME_INIT2(t,t1);
TopLoc_Location loc(trsf);
Bnd_Box bounds;
@ -630,7 +653,7 @@ std::vector<shared_ptr<Area> > Area::makeSections(
Part::CrossSection section(a,b,c,it.Current());
std::list<TopoDS_Wire> wires = section.slice(-d);
if(wires.empty()) {
Base::Console().Log("Section returns no wires\n");
AREA_LOG("Section returns no wires");
continue;
}
@ -641,13 +664,13 @@ std::vector<shared_ptr<Area> > Area::makeSections(
try {
mkFace.Build();
if (mkFace.Shape().IsNull())
Base::Console().Warning("FaceMakerBullseye return null shape on section\n");
AREA_WARN("FaceMakerBullseye return null shape on section");
else {
builder.Add(comp,mkFace.Shape());
continue;
}
}catch (Base::Exception &e){
Base::Console().Warning("FaceMakerBullseye failed on section: %s\n", e.what());
AREA_WARN("FaceMakerBullseye failed on section: " << e.what());
}
for(const TopoDS_Wire &wire : wires)
builder.Add(comp,wire);
@ -662,8 +685,10 @@ std::vector<shared_ptr<Area> > Area::makeSections(
if(area->myShapes.size())
sections.push_back(area);
else
Base::Console().Warning("Discard empty section\n");
AREA_WARN("Discard empty section");
TIME_PRINT(t1,"makeSection " << z);
}
TIME_PRINT(t,"makeSection count: " << sections.size()<<", total");
return std::move(sections);
}
@ -676,11 +701,8 @@ TopoDS_Shape Area::getPlane(gp_Trsf *trsf) {
if(myShapes.empty())
throw Base::ValueError("no shape added");
double top_z;
for(auto &s : myShapes) {
if(!findPlane(s.shape,TopAbs_FACE,myShapePlane,myTrsf,top_z) &&
!findPlane(s.shape,TopAbs_WIRE,myShapePlane,myTrsf,top_z))
findPlane(s.shape,TopAbs_EDGE,myShapePlane,myTrsf,top_z);
}
for(auto &s : myShapes)
foreachSubshape(s.shape,FindPlane(myShapePlane,myTrsf,top_z));
if(myShapePlane.IsNull())
throw Base::ValueError("shapes are not planar");
}
@ -707,6 +729,7 @@ void Area::build() {
return;
}
TIME_INIT(t);
getPlane();
try {
@ -744,8 +767,8 @@ void Area::build() {
pending = true;
}
if(mySkippedShapes && !myHaveSolid)
Base::Console().Warning("%s %d non coplanar shapes\n",
myParams.Coplanar==CoplanarForce?"Skipped":"Found",mySkippedShapes);
AREA_WARN((myParams.Coplanar==CoplanarForce?"Skipped ":"Found ")<<
mySkippedShapes<<" non coplanar shapes");
if(pending){
if(myParams.OpenMode!=OpenModeNone)
@ -783,6 +806,8 @@ void Area::build() {
myArea = std::move(area.myArea);
}
TIME_PRINT(t,"prepare");
}catch(...) {
clean();
throw;
@ -790,7 +815,7 @@ void Area::build() {
}
list<TopoDS_Shape> Area::sortWires(int index, int count, const gp_Pnt *pstart,
gp_Pnt *_pend, PARAM_ARGS(PARAM_FARG,AREA_PARAMS_MIN_DIST))
gp_Pnt *_pend, PARAM_ARGS(PARAM_FARG,AREA_PARAMS_SORT))
{
std::list<TopoDS_Shape> wires;
@ -813,23 +838,22 @@ list<TopoDS_Shape> Area::sortWires(int index, int count, const gp_Pnt *pstart,
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));
mySections[i]->sortWires(0,0,&pt,&pend,
PARAM_FIELDS(PARAM_FARG,AREA_PARAMS_SORT));
for(auto &wire : ws)
wires.push_back(wire.Moved(loc));
pt = pend;
}
if(_pend)
*_pend = pend.Transformed(loc);
return wires;
return std::move(wires);
}
if(!myArea || myArea->m_curves.empty()) return wires;
CArea area(*myArea);
Point p(pt.X(),pt.Y());
area.ChangeStartToNearest(&p,
PARAM_FIELDS(PARAM_FARG,AREA_PARAMS_MIN_DIST));
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);
@ -846,7 +870,7 @@ list<TopoDS_Shape> Area::sortWires(int index, int count, const gp_Pnt *pstart,
}
*_pend = pend.Transformed(TopLoc_Location(trsf));
}
return wires;
return std::move(wires);
}
TopoDS_Shape Area::toShape(CArea &area, short fill) {
@ -930,6 +954,8 @@ TopoDS_Shape Area::getShape(int index) {
return myShape;
}
TIME_INIT(t);
// do offset first, then pocket the inner most offseted shape
std::list<shared_ptr<CArea> > areas;
makeOffset(areas,PARAM_FIELDS(AREA_MY,AREA_PARAMS_OFFSET));
@ -962,13 +988,20 @@ TopoDS_Shape Area::getShape(int index) {
builder.MakeCompound(compound);
short fill = myParams.Thicken?FillFace:FillNone;
TIME_INIT(t2);
DURATION_INIT(d);
for(shared_ptr<CArea> area : areas) {
if(myParams.Thicken)
if(myParams.Thicken){
area->Thicken(myParams.ToolRadius);
DURATION_PLUS(d,t2);
}
const TopoDS_Shape &shape = toShape(*area,fill);
if(shape.IsNull()) continue;
builder.Add(compound,shape);
}
if(myParams.Thicken)
DURATION_PRINT(d,"Thicken");
// make sure the compound has at least one edge
for(TopExp_Explorer it(compound,TopAbs_EDGE);it.More();) {
builder.Add(compound,areaPocket.makePocket(
@ -977,6 +1010,7 @@ TopoDS_Shape Area::getShape(int index) {
break;
}
myShapeDone = true;
TIME_PRINT(t,"total");
return myShape;
}
@ -989,7 +1023,9 @@ TopoDS_Shape Area::makeOffset(int index,PARAM_ARGS(PARAM_FARG,AREA_PARAMS_OFFSET
if(areas.empty()) {
if(myParams.Thicken && myParams.ToolRadius>Precision::Confusion()) {
CArea area(*myArea);
TIME_INIT(t);
area.Thicken(myParams.ToolRadius);
TIME_PRINT(t,"Thicken");
return toShape(area,FillFace);
}
return TopoDS_Shape();
@ -997,10 +1033,13 @@ TopoDS_Shape Area::makeOffset(int index,PARAM_ARGS(PARAM_FARG,AREA_PARAMS_OFFSET
BRep_Builder builder;
TopoDS_Compound compound;
builder.MakeCompound(compound);
TIME_INIT(t);
DURATION_INIT(d);
for(shared_ptr<CArea> area : areas) {
short fill;
if(myParams.Thicken && myParams.ToolRadius>Precision::Confusion()) {
area->Thicken(myParams.ToolRadius);
DURATION_PLUS(d,t);
fill = FillFace;
}else if(areas.size()==1)
fill = myParams.Fill;
@ -1010,6 +1049,8 @@ TopoDS_Shape Area::makeOffset(int index,PARAM_ARGS(PARAM_FARG,AREA_PARAMS_OFFSET
if(shape.IsNull()) continue;
builder.Add(compound,shape);
}
if(myParams.Thicken && myParams.ToolRadius>Precision::Confusion())
DURATION_PRINT(d,"Thicken");
for(TopExp_Explorer it(compound,TopAbs_EDGE);it.More();)
return compound;
return TopoDS_Shape();
@ -1021,6 +1062,8 @@ void Area::makeOffset(list<shared_ptr<CArea> > &areas,
if(fabs(offset)<Precision::Confusion())
return;
TIME_INIT2(t,t1);
long count = 1;
if(extra_pass) {
if(fabs(stepover)<Precision::Confusion())
@ -1076,10 +1119,12 @@ void Area::makeOffset(list<shared_ptr<CArea> > &areas,
break;
}
#endif
if(count>1)
TIME_PRINT(t1,"makeOffset " << i << '/' << count);
if(area.m_curves.empty())
return;
}
TIME_PRINT(t,"makeOffset count: " << count);
}
TopoDS_Shape Area::makePocket(int index, PARAM_ARGS(PARAM_FARG,AREA_PARAMS_POCKET)) {
@ -1098,6 +1143,8 @@ TopoDS_Shape Area::makePocket(int index, PARAM_ARGS(PARAM_FARG,AREA_PARAMS_POCKE
build();
AREA_SECTION(makePocket,index,PARAM_FIELDS(PARAM_FARG,AREA_PARAMS_POCKET));
TIME_INIT(t);
PocketMode pm;
switch(mode) {
case Area::PocketModeZigZag:
@ -1128,6 +1175,8 @@ TopoDS_Shape Area::makePocket(int index, PARAM_ARGS(PARAM_FARG,AREA_PARAMS_POCKE
in.Reorder();
in.MakePocketToolpath(out.m_curves,params);
TIME_PRINT(t,"makePocket");
if(myParams.Thicken){
out.Thicken(tool_radius);
return toShape(out,FillFace);
@ -1170,9 +1219,8 @@ TopoDS_Wire Area::toShape(const CCurve &c, const gp_Trsf *trsf) {
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());
AREA_WARN("Arc correction: "<<r<<", "<<r2<<", center"<<
AREA_PT(center)<<"->"<<AREA_PT(newCenter));
center = newCenter;
}
gp_Ax2 axis(center, gp_Dir(0,0,v.m_type));
@ -1190,14 +1238,9 @@ TopoDS_Wire Area::toShape(const CCurve &c, const gp_Trsf *trsf) {
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());
AREA_WARN("warning: patch open wire type " <<
c.m_vertices.back().m_type<<endl<<AREA_PT(p1)<<endl<<
AREA_PT(p2)<<endl<<AREA_PT(pt)<<endl<<AREA_PT(pstart));
mkWire.Add(BRepBuilderAPI_MakeEdge(pt,pstart).Edge());
}
if(trsf)
@ -1219,6 +1262,7 @@ TopoDS_Shape Area::toShape(const CArea &area, bool fill, const gp_Trsf *trsf) {
for(TopExp_Explorer it(compound,TopAbs_EDGE);it.More();) {
if(fill) {
try{
TIME_INIT(t);
Part::FaceMakerBullseye mkFace;
if(trsf)
mkFace.setPlane(gp_Pln().Transformed(*trsf));
@ -1226,10 +1270,11 @@ TopoDS_Shape Area::toShape(const CArea &area, bool fill, const gp_Trsf *trsf) {
mkFace.addWire(TopoDS::Wire(it.Current()));
mkFace.Build();
if (mkFace.Shape().IsNull())
Base::Console().Warning("FaceMakerBullseye returns null shape\n");
AREA_WARN("FaceMakerBullseye returns null shape");
TIME_PRINT(t,"makeFace");
return mkFace.Shape();
}catch (Base::Exception &e){
Base::Console().Warning("FaceMakerBullseye failed: %s\n", e.what());
AREA_WARN("FaceMakerBullseye failed: "<<e.what());
}
}
return compound;
@ -1237,73 +1282,355 @@ TopoDS_Shape Area::toShape(const CArea &area, bool fill, const gp_Trsf *trsf) {
return TopoDS_Shape();
}
struct WireInfo {
TopoDS_Shape wire;
gp_Pnt pend;
gp_Pnt pstart;
};
struct GetWires {
std::list<WireInfo> &wires;
GetWires(std::list<WireInfo> &ws)
:wires(ws)
{}
void operator()(const TopoDS_Shape &shape, int type) {
WireInfo info;
if(type == TopAbs_WIRE)
info.wire = shape;
else
info.wire = BRepBuilderAPI_MakeWire(TopoDS::Edge(shape)).Wire();
BRepTools_WireExplorer xp(TopoDS::Wire(shape));
info.pstart = BRep_Tool::Pnt(xp.CurrentVertex());
for(;xp.More();xp.Next());
info.pend = BRep_Tool::Pnt(xp.CurrentVertex());
info.wire = shape;
wires.push_back(info);
}
};
struct ShapeInfo{
gp_Pln myPln;
std::list<WireInfo> myWires;
TopoDS_Shape myShape;
gp_Pnt myBestPt;
std::list<WireInfo>::iterator myBestWire;
TopoDS_Shape mySupport;
bool mySupportEdge;
bool myPlanar;
bool myRebase;
bool myStart;
ShapeInfo(BRepLib_FindSurface &finder, const TopoDS_Shape &_shape)
:myPln(GeomAdaptor_Surface(finder.Surface()).Plane())
,myShape(_shape)
,myPlanar(true)
{}
ShapeInfo(const TopoDS_Shape &_shape)
:myShape(_shape)
,myPlanar(false)
{}
double nearest(const gp_Pnt &pt) {
if(myWires.empty())
foreachSubshape(myShape,GetWires(myWires),TopAbs_WIRE);
TopoDS_Shape v = BRepBuilderAPI_MakeVertex(pt);
bool first = true;
double best_d=1e20;
myBestWire = myWires.begin();
for(auto it=myWires.begin();it!=myWires.end();++it) {
const TopoDS_Shape &wire = it->wire;
TopoDS_Shape support;
bool support_edge;
double d;
gp_Pnt p;
bool done = false;
bool is_start = false;
if(BRep_Tool::IsClosed(wire)) {
BRepExtrema_DistShapeShape extss(v,wire);
if(extss.IsDone() && extss.NbSolution()) {
d = extss.Value();
p = extss.PointOnShape2(0);
support = extss.SupportOnShape2(0);
support_edge = extss.SupportTypeShape2(0)==BRepExtrema_IsOnEdge;
done = true;
}else
AREA_WARN("BRepExtrema_DistShapeShape failed");
}
if(!done){
double d1 = p.Distance(it->pstart);
double d2 = p.Distance(it->pend);
AREA_TRACE("start "<<AREA_PT(it->pstart)<<", " << d1 <<
AREA_PT(it->pend)<<", " <<d2);
if(d1<d2) {
d = d1;
p = it->pstart;
is_start = true;
}else{
d = d2;
p = it->pend;
is_start = false;
}
}
if(!first && d>=best_d) continue;
first = false;
myBestPt = p;
myBestWire = it;
best_d = d;
myRebase = done;
myStart = is_start;
if(done) {
mySupport = support;
mySupportEdge = support_edge;
}
}
return best_d;
}
//Assumes nearest() has been called. Rebased the best wire
//to begin with the best point. Currently only works with closed wire
TopoDS_Shape rebaseWire(gp_Pnt &pend) {
BRepBuilderAPI_MakeWire mkWire;
TopoDS_Shape estart;
TopoDS_Edge eend;
for(int state=0;state<3;++state) {
BRepTools_WireExplorer xp(TopoDS::Wire(myBestWire->wire));
pend = BRep_Tool::Pnt(xp.CurrentVertex());
//checking the case of bestpoint == wire start
if(state==0 && !mySupportEdge && pend.Distance(myBestPt)<Precision::Confusion()) {
pend = myBestWire->pend;
return myBestWire->wire;
}
gp_Pnt pt;
for(;xp.More();xp.Next(),pend=pt) {
//state==2 means we are in second pass. estart marks the new start of the wire
if(state==2 && estart.IsSame(xp.Current()))
break;
BRepAdaptor_Curve curve(xp.Current());
bool reversed = (xp.Current().Orientation()==TopAbs_REVERSED);
pt = curve.Value(reversed?curve.FirstParameter():curve.LastParameter());
//state!=0 means we've found the new start of wire, now just keep adding new edges
if(state) {
mkWire.Add(xp.Current());
pend = pt;
continue;
}
//state==0 means we are looking for the new start
if(mySupportEdge) {
//if best point is on some edge, break the edge in half
if(xp.Current().IsEqual(mySupport)) {
estart = mySupport;
state = 1;
eend = BRepBuilderAPI_MakeEdge(curve.Curve().Curve(), pend, myBestPt);
mkWire.Add(BRepBuilderAPI_MakeEdge(curve.Curve().Curve(), myBestPt, pt));
}
}else if(myBestPt.Distance(pend)<Precision::Confusion()){
//if best point is on some vertex
estart = xp.Current();
state = 1;
mkWire.Add(xp.Current());
}
}
}
if(!eend.IsNull())
mkWire.Add(eend);
if(mkWire.IsDone())
return mkWire.Wire();
AREA_WARN("wire rebase failed");
pend = myBestWire->pend;
return myBestWire->wire;
}
std::list<TopoDS_Shape> sortWires(gp_Pnt &pend) {
std::list<TopoDS_Shape> wires;
while(true) {
AREA_TRACE("3D sort pt " << AREA_PT(myBestPt));
if(myRebase) {
AREA_TRACE("3D sort rebase");
wires.push_back(rebaseWire(pend));
}else if(!myStart){
AREA_TRACE("3D sort reverse");
wires.push_back(myBestWire->wire.Reversed());
pend = myBestWire->pstart;
}else{
wires.push_back(myBestWire->wire);
pend = myBestWire->pend;
}
AREA_TRACE("3D sort end " << AREA_PT(pend));
myWires.erase(myBestWire);
if(myWires.empty()) break;
nearest(pend);
}
return std::move(wires);
}
};
struct ShapeInfoBuilder {
std::list<ShapeInfo> &myList;
ShapeInfoBuilder(std::list<ShapeInfo> &list)
:myList(list)
{}
void operator()(const TopoDS_Shape &shape, int) {
BRepLib_FindSurface finder(shape,-1,Standard_True);
if(finder.Found())
myList.push_back(ShapeInfo(finder,shape));
else
myList.push_back(ShapeInfo(shape));
}
};
std::list<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))
PARAM_ARGS(PARAM_FARG,AREA_PARAMS_SORT))
{
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;
if(shapes.empty()) return wires;
for (auto &shape : shapes) {
std::list<TopoDS_Shape> subshapes;
if(!explode)
subshapes.push_back(shape);
else{
if(sort_mode == SortModeNone) {
for(auto &shape : shapes) {
if (shape.IsNull())
continue;
bool haveShape=false;
for(TopExp_Explorer it(shape,TopAbs_WIRE);it.More();it.Next()) {
haveShape=true;
subshapes.push_back(it.Current());
}
if(!haveShape) {
for(TopExp_Explorer it(shape,TopAbs_EDGE);it.More();it.Next())
subshapes.push_back(it.Current());
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());
}
//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));
}
return std::move(wires);
}
std::list<ShapeInfo> shape_list;
TIME_INIT2(t,t1);
#define SORT_WIRE_TIME(_msg) \
TIME_PRINT(t1,"sortWires "<< _msg)
if(sort_mode == SortMode3D) {
for(auto &shape : shapes)
shape_list.push_back(ShapeInfo(shape));
}else{
//first pass, find plane of each shape
for(auto &shape : shapes) {
//explode the shape
foreachSubshape(shape,ShapeInfoBuilder(shape_list));
}
if(shape_list.empty())
return wires;
SORT_WIRE_TIME("plan finding");
}
Bnd_Box bounds;
gp_Pnt pstart,pend;
if(_pstart)
pstart = *_pstart;
bool use_bound = fabs(pstart.X())<Precision::Confusion() &&
fabs(pstart.Y())<Precision::Confusion() &&
fabs(pstart.Z())<Precision::Confusion();
if(use_bound || sort_mode == SortMode2D5) {
//Second stage, group shape by its plane, and find overall boundary
for(auto itNext=shape_list.begin(),it=itNext;it!=shape_list.end();it=itNext) {
++itNext;
if(use_bound)
BRepBndLib::Add(it->myShape, bounds, Standard_False);
if(!it->myPlanar) continue;
TopoDS_Builder builder;
TopoDS_Compound comp;
builder.MakeCompound(comp);
bool empty = true;
for(auto itNext3=itNext,itNext2=itNext;itNext2!=shape_list.end();itNext2=itNext3) {
++itNext3;
if(!itNext2->myPlanar ||
!it->myPln.Position().IsCoplanar(itNext2->myPln.Position(),
Precision::Confusion(),Precision::Confusion()))
continue;
if(itNext == itNext2) ++itNext;
builder.Add(comp,itNext2->myShape);
shape_list.erase(itNext2);
empty = false;
}
if(!empty) {
builder.Add(comp,it->myShape);
it->myShape = comp;
}
}
SORT_WIRE_TIME("plane merging");
}
if(!shape_map.size())
return wires;
Area area(params);
//We'll do planar checking here, so disable Area planar check
//We have done 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);
DURATION_INIT2(td1,td2);
if(use_bound) {
bounds.SetGap(0.0);
Standard_Real xMin, yMin, zMin, xMax, yMax, zMax;
bounds.Get(xMin, yMin, zMin, xMax, yMax, zMax);
pstart.SetCoord(xMax,yMax,zMax);
}
bool has_2d5=false,has_3d=false;
while(shape_list.size()) {
AREA_TRACE("start " << shape_list.size() << ' ' << AREA_PT(pstart));
double best_d;
auto best_it = shape_list.begin();
bool first = true;
for(auto it=best_it;it!=shape_list.end();++it) {
double d;
gp_Pnt pt;
if(it->myPlanar){
d = it->myPln.Distance(pstart);
#define AREA_TIME_2D5 \
DURATION_PLUS(td1,t1);\
has_2d5=true
AREA_TIME_2D5;
}else{
d = it->nearest(pstart);
#define AREA_TIME_3D \
DURATION_PLUS(td2,t1);\
has_3d=true
AREA_TIME_3D;
}
if(first || d<best_d) {
first = false;
best_it = it;
best_d = d;
}
}
if(best_it->myPlanar) {
area.clean(true);
area.myWorkPlane = best_it->myShape;
area.myTrsf.SetTransformation(best_it->myPln.Position());
area.add(best_it->myShape,Area::OperationCompound);
wires.splice(wires.end(),area.sortWires(
0,-1,&pstart,&pend, PARAM_FIELDS(PARAM_FARG,AREA_PARAMS_SORT)));
AREA_TIME_2D5;
}else{
wires.splice(wires.end(),best_it->sortWires(pend));
AREA_TIME_3D;
}
pstart = pend;
shape_list.erase(best_it);
}
wires.splice(wires.end(),area.sortWires(
-1,0,&pstart,&pend, PARAM_FIELDS(PARAM_FARG,AREA_PARAMS_MIN_DIST)));
if(_pend) *_pend = pend;
return wires;
if(has_2d5) DURATION_PRINT(td1,"sortWires 2D5");
if(has_3d) DURATION_PRINT(td2,"sortWires 3D");
TIME_PRINT(t,"sortWires total");
return std::move(wires);
}
static void addCommand(Toolpath &path, const gp_Pnt &p,
@ -1345,25 +1672,15 @@ static void addCommand(Toolpath &path,
}
void Area::toPath(Toolpath &path, const std::list<TopoDS_Shape> &shapes,
const gp_Pnt *pstart, PARAM_ARGS(PARAM_FARG,AREA_PARAMS_PATH))
const AreaParams *_params, const gp_Pnt *pstart, gp_Pnt *pend,
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());
}
}
AreaParams params;
if(_params) params =*_params;
wires = sortWires(shapes,&params,pstart,pend,
PARAM_FIELDS(PARAM_FARG,AREA_PARAMS_SORT));
if(threshold < Precision::Confusion())
threshold = Precision::Confusion();
@ -1373,7 +1690,7 @@ void Area::toPath(Toolpath &path, const std::list<TopoDS_Shape> &shapes,
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))
if(first||p.Distance(plast)>threshold)
addCommand(path,p,true,height,clearance);
else
addCommand(path,p);
@ -1386,6 +1703,25 @@ void Area::toPath(Toolpath &path, const std::list<TopoDS_Shape> &shapes,
switch (curve.GetType()) {
case GeomAbs_Line: {
if(segmentation > Precision::Confusion()) {
GCPnts_UniformAbscissa discretizer(curve, segmentation,
curve.FirstParameter(), curve.LastParameter());
if (discretizer.IsDone () && discretizer.NbPoints () > 2) {
int nbPoints = discretizer.NbPoints ();
if(reversed) {
for (int i=nbPoints-1; i>=1; --i) {
gp_Pnt pt = curve.Value(discretizer.Parameter(i));
addCommand(path,pt);
}
}else{
for (int i=2; i<=nbPoints; i++) {
gp_Pnt pt = curve.Value(discretizer.Parameter(i));
addCommand(path,pt);
}
}
break;
}
}
addCommand(path,p);
break;
} case GeomAbs_Circle:{
@ -1396,6 +1732,27 @@ void Area::toPath(Toolpath &path, const std::list<TopoDS_Shape> &shapes,
bool clockwise = axis.Direction().Z()<0;
if(reversed) clockwise = !clockwise;
gp_Pnt center = axis.Location();
if(segmentation > Precision::Confusion()) {
GCPnts_UniformAbscissa discretizer(curve, segmentation,
curve.FirstParameter(), curve.LastParameter());
if (discretizer.IsDone () && discretizer.NbPoints () > 2) {
int nbPoints = discretizer.NbPoints ();
if(reversed) {
for (int i=nbPoints-1; i>=1; --i) {
gp_Pnt pt = curve.Value(discretizer.Parameter(i));
addCommand(path,plast,pt,center,clockwise);
plast = pt;
}
}else{
for (int i=2; i<=nbPoints; i++) {
gp_Pnt pt = curve.Value(discretizer.Parameter(i));
addCommand(path,plast,pt,center,clockwise);
plast = pt;
}
}
break;
}
}
if(fabs(first-last)>M_PI) {
// Split arc(circle) larger than half circle.
gp_Pnt mid = curve.Value((last-first)*0.5+first);
@ -1406,7 +1763,7 @@ void Area::toPath(Toolpath &path, const std::list<TopoDS_Shape> &shapes,
break;
} default: {
// Discretize all other type of curves
GCPnts_QuasiUniformDeflection discretizer(curve, deflection,
GCPnts_QuasiUniformDeflection discretizer(curve, params.Deflection,
curve.FirstParameter(), curve.LastParameter());
if (discretizer.IsDone () && discretizer.NbPoints () > 1) {
int nbPoints = discretizer.NbPoints ();

View File

@ -23,6 +23,8 @@
#ifndef PATH_AREA_H
#define PATH_AREA_H
#include <QApplication>
#include <chrono>
#include <memory>
#include <vector>
#include <list>
@ -31,9 +33,78 @@
#include <gp_Circ.hxx>
#include <gp_GTrsf.hxx>
#include <Base/Console.h>
#include "Path.h"
#include "AreaParams.h"
// #define AREA_TRACE_ENABLE
#define _AREA_LOG(_l,_msg) do {\
std::stringstream str;\
str << "Path.Area: " << _msg;\
Base::Console()._l("%s\n",str.str().c_str());\
qApp->sendPostedEvents();\
}while(0)
#define AREA_LOG(_msg) _AREA_LOG(Log,_msg)
#define AREA_WARN(_msg) _AREA_LOG(Warning,_msg)
#define AREA_PT(_pt) '('<<(_pt).X()<<", " << (_pt).Y()<<", " << (_pt).Z()<<')'
#define AREA_PT2(_pt) '('<<(_pt).x<<", " << (_pt).y<<')'
#ifdef AREA_TRACE_ENABLE
# define AREA_TRACE AREA_LOG
#else
# define AREA_TRACE(...) do{}while(0)
#endif
#define AREA_TIME_ENABLE
#ifdef AREA_TIME_ENABLE
#define TIME_UNIT duration<double>
#define TIME_CLOCK high_resolution_clock
#define TIME_POINT std::chrono::TIME_CLOCK::time_point
#define TIME_INIT(_t) \
auto _t=std::chrono::TIME_CLOCK::now()
#define TIME_INIT2(_t1,_t2) TIME_INIT(_t1),_t2=_t1
#define TIME_INIT3(_t1,_t2,_t3) TIME_INIT(_t1),_t2=_t1,_t3=_t1
#define DURATION_PRINT(_d,_msg) \
AREA_LOG(_msg<< " time: " << _d.count()<<'s');
#define TIME_PRINT(_t,_msg) \
DURATION_PRINT(Path::getDuration(_t),_msg);
#define DURATION_INIT(_d) \
std::chrono::TIME_UNIT _d(0)
#define DURATION_INIT2(_d1,_d2) DURATION_INIT(_d1),_d2(0)
namespace Path {
inline std::chrono::TIME_UNIT getDuration(TIME_POINT &t)
{
auto tnow = std::chrono::TIME_CLOCK::now();
auto d = std::chrono::duration_cast<std::chrono::TIME_UNIT>(tnow-t);
t = tnow;
return d;
}
}
#define DURATION_PLUS(_d,_t) _d += Path::getDuration(_t)
#else
#define TIME_INIT(...) do{}while(0)
#define TIME_INIT2(...) do{}while(0)
#define TIME_INIT3(...) do{}while(0)
#define TIME_PRINT(...) do{}while(0)
#define DURATION_PRINT(...) do{}while(0)
#define DURATION_INIT(...) do{}while(0)
#define DURATION_INIT2(...) do{}while(0)
#define DURATION_PLUS(...) do{}while(0)
#endif
class CArea;
class CCurve;
@ -148,8 +219,6 @@ protected:
bool isBuilt() const;
static bool findPlane(const TopoDS_Shape &shape, int type,
TopoDS_Shape &plane, gp_Trsf &trsf, double &top_z);
TopoDS_Shape findPlane(const TopoDS_Shape &shape, gp_Trsf &trsf);
public:
@ -271,14 +340,15 @@ public:
* \arg \c pstart: optional start point
* \arg \c pend: optional output containing the ending point of the returned
* wires
* \arg \c allow_Break: whether allow to break open wires
*
* See #AREA_PARAMS_MIN_DIST for other arguments
* See #AREA_PARAMS_SORT 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));
const gp_Pnt *pstart=NULL, gp_Pnt *pend=NULL,
PARAM_ARGS_DEF(PARAM_FARG,AREA_PARAMS_SORT));
/** Add a OCC generic shape to CArea
*
@ -333,27 +403,31 @@ public:
* 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
* See #AREA_PARAMS_SORT 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));
gp_Pnt *pend=NULL, PARAM_ARGS_DEF(PARAM_FARG,AREA_PARAMS_SORT));
/** Convert a list of wires to gcode
*
* \arg \c path: output toolpath
* \arg \c shapes: input list of shapes
* \arg \c params: optional Area parameters for the Area object internally
* used for sorting
* \arg \c pstart: output start point,
* \arg \c pend: optional output containing the ending point of the returned
*
* See #AREA_PARAMS_PATH for other arguments
*/
static void toPath(Toolpath &path, const std::list<TopoDS_Shape> &shapes,
const gp_Pnt *pstart=NULL, PARAM_ARGS_DEF(PARAM_FARG,AREA_PARAMS_PATH));
const AreaParams *params=NULL, const gp_Pnt *pstart=NULL, gp_Pnt *pend=NULL,
PARAM_ARGS_DEF(PARAM_FARG,AREA_PARAMS_PATH));
PARAM_ENUM_DECLARE(AREA_PARAMS_PATH)
};
} //namespace Path

View File

@ -71,10 +71,13 @@
AREA_PARAMS_DEFLECTION \
AREA_PARAMS_CLIPPER_FILL
#define AREA_PARAMS_FIT_ARCS \
((bool,fit_arcs,FitArcs,true,"Enable arc fitting"))
/** libarea algorithm option parameters */
#define AREA_PARAMS_CAREA \
((double,tolerance,Tolerance,Precision::Confusion(),"Point coincidence tolerance"))\
((bool,fit_arcs,FitArcs,true,"Enable arc fitting"))\
AREA_PARAMS_FIT_ARCS \
((bool,clipper_simple,Simplify,false,\
"Simplify polygons after operation. See https://goo.gl/Mh9XK1"))\
((double,clipper_clean_distance,CleanDistance,0.0,\
@ -150,11 +153,24 @@
((double,round_precision,RoundPreceision,0.0,\
"Round joint precision. If =0, it defaults to Accuracy. \nSee https://goo.gl/4odfQh"))
#define AREA_PARAMS_MIN_DIST \
((double, min_dist, MinDistance, 0.0, \
"minimum distance for the generated new wires. Wires maybe broken if the algorithm see fits.\n"\
"Set to zero to disable wire breaking."))
/** Area wire sorting parameters */
#define AREA_PARAMS_SORT \
((enum, sort_mode, SortMode, 1, "Wire sorting mode to optimize travel distance.\n"\
"'2D5' explode shapes into wires, and groups the shapes by its plane. The 'start' position\n"\
"chooses the first plane to start. The algorithm will then sort within the plane and then\n"\
"move on to the next nearest plane.\n"\
"'3D' makes no assumption of planarity. The sorting is done across 3D space\n",\
(None)(2D5)(3D)))\
AREA_PARAMS_MIN_DIST
/** Area path generation parameters */
#define AREA_PARAMS_PATH \
((bool, sort, SortShape, true, \
"Whether to sort the shapes to optimize travel distance. You can customize wire\n"\
"sorting by calling sortWires() manually."))\
AREA_PARAMS_SORT \
((double, threshold, RetractThreshold, 0.0,\
"If two wire's end points are separated within this threshold, they are consider\n"\
"as connected. You may want to set this to the tool diameter to keep the tool down."))\
@ -162,23 +178,17 @@
((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
((double,segmentation,Segmentation,0.0,\
"Break long curves into segments of this length. One use case is for PCB autolevel,\n"\
"so that more correction points can be inserted"))
#define AREA_PARAMS_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
#define AREA_PARAMS_PATH_EXTRA \
AREA_PARAMS_DEFLECTION \
AREA_PARAMS_FIT_ARCS
#define AREA_PARAMS_PATH_CONF \
AREA_PARAMS_PATH \
AREA_PARAMS_PATH_EXTRA
/** Group of all Area configuration parameters except CArea's*/
#define AREA_PARAMS_AREA \

View File

@ -91,12 +91,12 @@ static const AreaDoc myDocs[] = {
{
"sortWires",
"sortWires(index=-1, count=0, start=Vector()" PARAM_PY_ARGS_DOC(ARG,AREA_PARAMS_MIN_DIST) "):\n"
"sortWires(index=-1, count=0, start=Vector(), allow_break=False, " PARAM_PY_ARGS_DOC(ARG,AREA_PARAMS_SORT) "):\n"
"Returns a tuple (wires,end): sorted wires with minimized travel distance, and the endpoint 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),
PARAM_PY_DOC(ARG,AREA_PARAMS_SORT),
},
};
@ -151,7 +151,8 @@ PyObject* AreaPy::setPlane(PyObject *args) {
#define GET_TOPOSHAPE(_p) static_cast<Part::TopoShapePy*>(_p)->getTopoShapePtr()->getShape()
getAreaPtr()->setPlane(GET_TOPOSHAPE(pcObj));
return Py_None;
Py_INCREF(this);
return this;
}
PyObject* AreaPy::getShape(PyObject *args, PyObject *keywds)
@ -168,17 +169,17 @@ PyObject* AreaPy::getShape(PyObject *args, PyObject *keywds)
}
PyObject* AreaPy::sortWires(PyObject *args, PyObject *keywds){
PARAM_PY_DECLARE_INIT(PARAM_FARG,AREA_PARAMS_MIN_DIST)
PARAM_PY_DECLARE_INIT(PARAM_FARG,AREA_PARAMS_SORT)
short index = -1;
short count = 0;
PyObject *start = NULL;
static char *kwlist[] = {"index","count","start",
PARAM_FIELD_STRINGS(ARG,AREA_PARAMS_MIN_DIST), NULL};
PARAM_FIELD_STRINGS(ARG,AREA_PARAMS_SORT), NULL};
if (!PyArg_ParseTupleAndKeywords(args, keywds,
"|hhO!" PARAM_PY_KWDS(AREA_PARAMS_MIN_DIST),
"|hhO!" PARAM_PY_KWDS(AREA_PARAMS_SORT),
kwlist,&index,&count,&(Base::VectorPy::Type),&start,
PARAM_REF(PARAM_FARG,AREA_PARAMS_MIN_DIST)))
PARAM_REF(PARAM_FARG,AREA_PARAMS_SORT)))
return 0;
gp_Pnt pstart,pend;
@ -188,7 +189,7 @@ PyObject* AreaPy::sortWires(PyObject *args, PyObject *keywds){
}
std::list<TopoDS_Shape> wires = getAreaPtr()->sortWires(
index,count,&pstart,&pend,
PARAM_PY_FIELDS(PARAM_FARG,AREA_PARAMS_MIN_DIST));
PARAM_PY_FIELDS(PARAM_FARG,AREA_PARAMS_SORT));
PyObject *list = PyList_New(0);
for(auto &wire : wires)
PyList_Append(list,Py::new_reference_to(
@ -217,7 +218,8 @@ PyObject* AreaPy::add(PyObject *args, PyObject *keywds)
if (PyObject_TypeCheck(pcObj, &(Part::TopoShapePy::Type))) {
getAreaPtr()->add(GET_TOPOSHAPE(pcObj),op);
return Py_None;
Py_INCREF(this);
return this;
} else if (PyObject_TypeCheck(pcObj, &(PyList_Type)) ||
PyObject_TypeCheck(pcObj, &(PyTuple_Type))) {
Py::Sequence shapeSeq(pcObj);
@ -233,7 +235,8 @@ PyObject* AreaPy::add(PyObject *args, PyObject *keywds)
getAreaPtr()->add(GET_TOPOSHAPE(item),
PARAM_PY_FIELDS(PARAM_FARG,AREA_PARAMS_OPCODE));
}
return Py_None;
Py_INCREF(this);
return this;
}
PyErr_SetString(PyExc_TypeError, "shape must be 'TopoShape' or list of 'TopoShape'");
@ -356,7 +359,8 @@ PyObject* AreaPy::setParams(PyObject *args, PyObject *keywds)
PARAM_FOREACH(AREA_GET,AREA_PARAMS_CONF)
getAreaPtr()->setParams(params);
return Py_None;
Py_INCREF(this);
return this;
}
PyObject* AreaPy::getParams(PyObject *args)

View File

@ -64,10 +64,8 @@ FeatureArea::~FeatureArea()
}
Area &FeatureArea::getArea() {
if(!myBuild) {
myBuild = true;
if(!myBuild)
execute();
}
return myArea;
}
@ -85,6 +83,9 @@ App::DocumentObjectExecReturn *FeatureArea::execute(void)
return new App::DocumentObjectExecReturn("Linked shape object is empty");
}
TIME_INIT(t);
myBuild = true;
AreaParams params;
#define AREA_PROP_GET(_param) \
@ -102,19 +103,35 @@ App::DocumentObjectExecReturn *FeatureArea::execute(void)
PARAM_PROP_ARGS(AREA_PARAMS_OPCODE));
}
this->Shape.setValue(myArea.getShape(-1));
myShapes.clear();
if(myArea.getSectionCount()==0)
myShapes.push_back(myArea.getShape(-1));
else {
myShapes.reserve(myArea.getSectionCount());
for(int i=0;i<(int)myArea.getSectionCount();++i)
myShapes.push_back(myArea.getShape(i));
}
if(myShapes.empty())
Shape.setValue(TopoDS_Shape());
else if(myShapes.size()==1)
Shape.setValue(myShapes.front());
else{
BRep_Builder builder;
TopoDS_Compound compound;
builder.MakeCompound(compound);
for(auto &shape : myShapes)
builder.Add(compound,shape);
Shape.setValue(compound);
}
TIME_PRINT(t,"feature execute");
return Part::Feature::execute();
}
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;
const std::vector<TopoDS_Shape> &FeatureArea::getShapes() {
getArea();
return myShapes;
}
short FeatureArea::mustExecute(void) const
@ -156,32 +173,31 @@ std::list<TopoDS_Shape> FeatureAreaView::getShapes() {
if (!pObj) return shapes;
if(!pObj->isDerivedFrom(FeatureArea::getClassTypeId()))
return shapes;
Area &area = static_cast<FeatureArea*>(pObj)->getArea();
if(!area.getSectionCount()) {
shapes.push_back(area.getShape());
auto all_shapes = static_cast<FeatureArea*>(pObj)->getShapes();
if(all_shapes.empty())
return shapes;
}
int index=SectionIndex.getValue(),count=SectionCount.getValue();
if(index<0) {
index += ((int)area.getSectionCount());
index += ((int)all_shapes.size());
if(index<0) return shapes;
if(count<=0 || index+1-count<0) {
count = index+1;
index = 0;
}else
index -= count-1;
}else if(index >= (int)area.getSectionCount())
}else if(index >= (int)all_shapes.size())
return shapes;
if(count<=0) count = area.getSectionCount();
if(count<=0) count = all_shapes.size();
count += index;
if(count>(int)area.getSectionCount())
count = area.getSectionCount();
if(count>(int)all_shapes.size())
count = all_shapes.size();
for(int i=index;i<count;++i)
shapes.push_back(area.getShape(i));
return shapes;
shapes.push_back(all_shapes[i]);
return std::move(shapes);
}
App::DocumentObjectExecReturn *FeatureAreaView::execute(void)

View File

@ -45,7 +45,7 @@ public:
virtual ~FeatureArea();
Area &getArea();
std::list<TopoDS_Shape> getShapes();
const std::vector<TopoDS_Shape> &getShapes();
/// returns the type name of the ViewProvider
virtual const char* getViewProviderName(void) const {
@ -63,6 +63,7 @@ public:
private:
bool myBuild;
Area myArea;
std::vector<TopoDS_Shape> myShapes;
};
typedef App::FeaturePythonT<FeatureArea> FeatureAreaPython;

View File

@ -50,12 +50,14 @@ using namespace Path;
PROPERTY_SOURCE(Path::FeatureShape, Path::Feature)
PARAM_ENUM_STRING_DECLARE(static const char *Enums,AREA_PARAMS_PATH)
FeatureShape::FeatureShape()
{
ADD_PROPERTY(Sources,(0));
ADD_PROPERTY_TYPE(StartPoint,(Base::Vector3d()),"Path",App::Prop_None,"Path start position");
PARAM_PROP_ADD("Path",AREA_PARAMS_PATH);
PARAM_PROP_ADD("Path",AREA_PARAMS_PATH_CONF);
PARAM_PROP_SET_ENUM(Enums,AREA_PARAMS_PATH_CONF);
}
FeatureShape::~FeatureShape()
@ -83,7 +85,13 @@ App::DocumentObjectExecReturn *FeatureShape::execute(void)
continue;
shapes.push_back(shape);
}
Area::toPath(path,shapes,&pstart,PARAM_PROP_ARGS(AREA_PARAMS_PATH));
AreaParams params;
#define AREA_PROP_GET(_param) \
params.PARAM_FNAME(_param) = PARAM_FNAME(_param).getValue();
PARAM_FOREACH(AREA_PROP_GET,AREA_PARAMS_PATH_EXTRA)
Area::toPath(path,shapes,&params,&pstart,NULL,PARAM_PROP_ARGS(AREA_PARAMS_PATH));
Path.setValue(path);
return App::DocumentObject::StdReturn;

View File

@ -52,7 +52,7 @@ public:
// Part::PropertyPartShape Shape;
App::PropertyLinkList Sources;
App::PropertyVector StartPoint;
PARAM_PROP_DECLARE(AREA_PARAMS_PATH)
PARAM_PROP_DECLARE(AREA_PARAMS_PATH_CONF)
//@{
/// recalculate the feature