From ea1c9e4c57e2d8196a0ac6a7a01f1ecaf8e4ec41 Mon Sep 17 00:00:00 2001 From: David Kaufman Date: Thu, 4 Jan 2024 15:05:07 -0500 Subject: [PATCH 01/11] proof of concept rest machining from prior path gcode --- src/Mod/Path/App/Area.cpp | 178 +++++++++++++++++++++++++++++++++ src/Mod/Path/App/Area.h | 1 + src/Mod/Path/App/AreaPy.xml | 5 + src/Mod/Path/App/AreaPyImp.cpp | 24 +++++ src/Mod/Path/Path/Op/Area.py | 18 +++- 5 files changed, 225 insertions(+), 1 deletion(-) diff --git a/src/Mod/Path/App/Area.cpp b/src/Mod/Path/App/Area.cpp index b57317bd4a..d26bd7fff0 100644 --- a/src/Mod/Path/App/Area.cpp +++ b/src/Mod/Path/App/Area.cpp @@ -75,6 +75,7 @@ #include #include #include +#include #include #include "Area.h" @@ -527,6 +528,181 @@ std::shared_ptr Area::getClearedArea(double tipDiameter, double diameter) return clearedArea; } +class ClearedAreaSegmentVisitor : public PathSegmentVisitor +{ +private: + CArea pathSegments; + double maxZ; + double diameter; + + void line(const Base::Vector3d &last, const Base::Vector3d &next) + { + if (last.z <= maxZ && next.z <= maxZ) { + CCurve curve; + curve.append(CVertex{{last.x, last.y}}); + curve.append(CVertex{{next.x, next.y}}); + pathSegments.append(curve); + count++; + } else { + // printf("SKIP!"); + } + } +public: + int count = 0; + + ClearedAreaSegmentVisitor(double maxZ, double diameter) : maxZ(maxZ), diameter(diameter) + { + } + + CArea getClearedArea() + { + CArea result{pathSegments}; + result.Thicken(diameter / 2); + return result; + } + + void g0(int id, const Base::Vector3d &last, const Base::Vector3d &next, const std::deque &pts) override + { + (void)id; + // printf("g0 [ "); + processPt(last); + // printf("] "); + processPts(pts); + // printf("_ "); + processPt(next); + + line(last, next); + // printf("\n"); + } + + void g1(int id, const Base::Vector3d &last, const Base::Vector3d &next, const std::deque &pts) override + { + (void)id; + // printf("g1 [ "); + processPt(last); + // printf("] "); + processPts(pts); + // printf("_ "); + processPt(next); + + line(last, next); + // printf("\n"); + } + + void g23(int id, const Base::Vector3d &last, const Base::Vector3d &next, const std::deque &pts, const Base::Vector3d ¢er) override + { + (void)id; + (void)center; + // printf("g23 [ "); + processPt(last); + // printf("] "); + processPts(pts); + // printf("_ "); + processPt(next); + + // TODO do I need to add last and next to the list??? + // TODO rounding resolution of the function that calls this is too big + Base::Vector3d prev = last; + for (Base::Vector3d p : pts) { + line(prev, p); + prev = p; + } + line(prev, next); + // printf("\n"); + } + + void g8x(int id, const Base::Vector3d &last, const Base::Vector3d &next, const std::deque &pts, + const std::deque &p, const std::deque &q) override + { + (void)id; + (void)q; // always within the bounds of p + printf("g8x UNHANDLED\n"); + // processPt(last); + // processPts(pts); + // processPts(p); + // processPt(next); + (void)last; + (void)pts; + (void)p; + (void)next; + } + void g38(int id, const Base::Vector3d &last, const Base::Vector3d &next) override + { + (void)id; + printf("g38 UNHANDLED\n"); + // processPt(last); + // processPt(next); + (void)last; + (void)next; + } + +private: + void processPts(const std::deque &pts) { + for (std::deque::const_iterator it=pts.begin(); pts.end() != it; ++it) { + processPt(*it); + } + } + + void processPt(const Base::Vector3d &pt) { + // printf("(%7.3f, %7.3f, %7.3f) ", pt.x, pt.y, pt.z); + } +}; + +std::shared_ptr Area::getClearedAreaFromPath(const Toolpath *path, double diameter, double zmax) { + build(); +#define AREA_MY(_param) myParams.PARAM_FNAME(_param) + PARAM_ENUM_CONVERT(AREA_MY, PARAM_FNAME, PARAM_ENUM_EXCEPT, AREA_PARAMS_OFFSET_CONF); + PARAM_ENUM_CONVERT(AREA_MY, PARAM_FNAME, PARAM_ENUM_EXCEPT, AREA_PARAMS_CLIPPER_FILL); + (void)SubjectFill; + (void)ClipFill; + + // Do not fit arcs after these offsets; it introduces unnecessary approximation error, and all off + // those arcs will be converted back to segments again for clipper differencing in getRestArea anyway + CAreaConfig conf(myParams, /*no_fit_arcs*/ true); + + const double roundPrecision = myParams.Accuracy; + const double buffer = 2 * roundPrecision; + (void)buffer; + (void)JoinType; + (void)EndType; + + Base::Vector3d pos = Base::Vector3d(0, 0, zmax + 1); + printf("getClearedAreaFromPath(path, diameter=%g, zmax=%g:\n", diameter, zmax); + // printf("Gcode:\n"); + for (auto c : path->getCommands()) { + // printf("\t%s ", c->Name.c_str()); + for (std::map::iterator i = c->Parameters.begin(); i != c->Parameters.end(); ++i) { + // printf("%s%g ", i->first.c_str(), i->second); + } + // printf("\n"); + } + + printf("\n"); + printf("GCode walker:\n"); + ClearedAreaSegmentVisitor visitor(zmax, diameter); + PathSegmentWalker walker(*path); + walker.walk(visitor, Base::Vector3d(0, 0, zmax + 1)); + printf("Count: %d\n", visitor.count); + printf("\n"); + printf("\n"); + + + std::shared_ptr clearedArea = make_shared(&myParams); + //clearedArea->myTrsf = myTrsf; + clearedArea->myTrsf = {}; + if (visitor.count > 0) { + //gp_Trsf trsf(myTrsf.Inverted()); + TopoDS_Shape clearedAreaShape = Area::toShape(visitor.getClearedArea(), false/*, &trsf*/); + clearedArea->add(clearedAreaShape, OperationCompound); + clearedArea->build(); + } else { + clearedArea->myArea = std::make_unique(); + clearedArea->myAreaOpen = std::make_unique(); + } + + return clearedArea; +} + std::shared_ptr Area::getRestArea(std::vector> clearedAreas, double diameter) { build(); PARAM_ENUM_CONVERT(AREA_MY, PARAM_FNAME, PARAM_ENUM_EXCEPT, AREA_PARAMS_OFFSET_CONF); @@ -538,6 +714,8 @@ std::shared_ptr Area::getRestArea(std::vector> clear // transform all clearedAreas into our workplane Area clearedAreasInPlane(&myParams); clearedAreasInPlane.myArea.reset(new CArea()); + printf("getRestArea\n"); + printf("\n"); for (std::shared_ptr clearedArea : clearedAreas) { gp_Trsf trsf = clearedArea->myTrsf; trsf.Invert(); diff --git a/src/Mod/Path/App/Area.h b/src/Mod/Path/App/Area.h index 5b192ae08a..3dc769dc9a 100644 --- a/src/Mod/Path/App/Area.h +++ b/src/Mod/Path/App/Area.h @@ -243,6 +243,7 @@ public: const TopoDS_Shape& plane = TopoDS_Shape()); std::shared_ptr getClearedArea(double tipDiameter, double diameter); + std::shared_ptr getClearedAreaFromPath(const Toolpath *path, double diameter, double zmax); std::shared_ptr getRestArea(std::vector> clearedAreas, double diameter); TopoDS_Shape toTopoShape(); diff --git a/src/Mod/Path/App/AreaPy.xml b/src/Mod/Path/App/AreaPy.xml index a38a84aec1..029b245c21 100644 --- a/src/Mod/Path/App/AreaPy.xml +++ b/src/Mod/Path/App/AreaPy.xml @@ -67,6 +67,11 @@ same algorithm + + + + + diff --git a/src/Mod/Path/App/AreaPyImp.cpp b/src/Mod/Path/App/AreaPyImp.cpp index 817aed0e37..ec6c8d66e8 100644 --- a/src/Mod/Path/App/AreaPyImp.cpp +++ b/src/Mod/Path/App/AreaPyImp.cpp @@ -27,6 +27,7 @@ #include // inclusion of the generated files (generated out of AreaPy.xml) +#include "PathPy.h" #include "AreaPy.h" #include "AreaPy.cpp" @@ -152,6 +153,11 @@ static const PyMethodDef areaOverrides[] = { "getClearedArea(tipDiameter, diameter):\n" "Gets the area cleared when a tool maximally clears this area. This method assumes a tool tip diameter 'tipDiameter' traces the full area, and that (perhaps at a different height on the tool) this clears a different region with tool diameter 'diameter'.\n", }, + { + "getClearedAreaFromPath",nullptr,0, + "getClearedAreaFromPath(path, diameter, zmax):\n" + "Gets the area cleared when a tool of the specified diameter follows the gcode represented in the path, ignoring cleared space above zmax.\n", + }, { "getRestArea",nullptr,0, "getRestArea(clearedAreas, diameter):\n" @@ -415,6 +421,24 @@ PyObject* AreaPy::getClearedArea(PyObject *args) } PY_CATCH_OCC } +PyObject* AreaPy::getClearedAreaFromPath(PyObject *args) +{ + PY_TRY { + PyObject *pyPath; + double diameter, zmax; + if (!PyArg_ParseTuple(args, "Odd", &pyPath, &diameter, &zmax)) + return nullptr; + if (!PyObject_TypeCheck(pyPath, &(PathPy::Type))) { + PyErr_SetString(PyExc_TypeError, "path must be of type PathPy"); + return nullptr; + } + const PathPy *path = static_cast(pyPath); + std::shared_ptr clearedArea = getAreaPtr()->getClearedAreaFromPath(path->getToolpathPtr(), diameter, zmax); + auto pyClearedArea = Py::asObject(new AreaPy(new Area(*clearedArea, true))); + return Py::new_reference_to(pyClearedArea); + } PY_CATCH_OCC +} + PyObject* AreaPy::getRestArea(PyObject *args) { PY_TRY { diff --git a/src/Mod/Path/Path/Op/Area.py b/src/Mod/Path/Path/Op/Area.py index 8e329ae4b5..77862274c2 100644 --- a/src/Mod/Path/Path/Op/Area.py +++ b/src/Mod/Path/Path/Op/Area.py @@ -272,7 +272,23 @@ class ObjectOp(PathOp.ObjectOp): restSections = [] for section in sections: z = section.getShape().BoundBox.ZMin - sectionClearedAreas = [a for a in clearedAreas if a.getShape().BoundBox.ZMax <= z] + # sectionClearedAreas = [a for a in clearedAreas if a.getShape().BoundBox.ZMax <= z] + sectionClearedAreas = [] + for op in self.job.Operations.Group: + print(op.Name) + if self in [x.Proxy for x in [op] + op.OutListRecursive if hasattr(x, "Proxy")]: + print("found self") + break + if hasattr(op, "Active") and op.Active and op.Path: + tool = op.Proxy.tool if hasattr(op.Proxy, "tool") else op.ToolController.Proxy.getTool(op.ToolController) + diameter = tool.Diameter.getValueAs("mm") + sectionClearedAreas.append(area.getClearedAreaFromPath(op.Path, diameter, z+0.001)) + debugZ = -1.5 + if debugZ -.1 < z and z < debugZ + .1: + debugObj = obj.Document.addObject("Part::Feature", "Debug_{}_{}".format(debugZ, op.Name)) + debugObj.Label = "Debug_{}_{}".format(debugZ, op.Label) + debugObj.Shape = sectionClearedAreas[-1].getShape() + pass restSection = section.getRestArea(sectionClearedAreas, self.tool.Diameter.getValueAs("mm")) restSections.append(restSection) sections = restSections From 2a9ce21c81357dfbfe6d73cbe07abc2718a2a989 Mon Sep 17 00:00:00 2001 From: David Kaufman Date: Fri, 5 Jan 2024 11:46:39 -0500 Subject: [PATCH 02/11] expand cleared area by a small buffer to cover numeric errors --- src/Mod/Path/App/Area.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Mod/Path/App/Area.cpp b/src/Mod/Path/App/Area.cpp index d26bd7fff0..de9b33e85d 100644 --- a/src/Mod/Path/App/Area.cpp +++ b/src/Mod/Path/App/Area.cpp @@ -662,7 +662,6 @@ std::shared_ptr Area::getClearedAreaFromPath(const Toolpath *path, double const double roundPrecision = myParams.Accuracy; const double buffer = 2 * roundPrecision; - (void)buffer; (void)JoinType; (void)EndType; @@ -679,7 +678,7 @@ std::shared_ptr Area::getClearedAreaFromPath(const Toolpath *path, double printf("\n"); printf("GCode walker:\n"); - ClearedAreaSegmentVisitor visitor(zmax, diameter); + ClearedAreaSegmentVisitor visitor(zmax, diameter + buffer); PathSegmentWalker walker(*path); walker.walk(visitor, Base::Vector3d(0, 0, zmax + 1)); printf("Count: %d\n", visitor.count); From 9fac948ba7107c562aab2f64436e44424952c89f Mon Sep 17 00:00:00 2001 From: David Kaufman Date: Sun, 7 Jan 2024 16:53:45 -0500 Subject: [PATCH 03/11] fix precision computation --- src/Mod/Path/App/Area.cpp | 108 +++++++++++++++++++++------------ src/Mod/Path/App/AreaPyImp.cpp | 3 + src/Mod/Path/Path/Op/Area.py | 14 ++--- 3 files changed, 79 insertions(+), 46 deletions(-) diff --git a/src/Mod/Path/App/Area.cpp b/src/Mod/Path/App/Area.cpp index de9b33e85d..73b8200327 100644 --- a/src/Mod/Path/App/Area.cpp +++ b/src/Mod/Path/App/Area.cpp @@ -533,7 +533,7 @@ class ClearedAreaSegmentVisitor : public PathSegmentVisitor private: CArea pathSegments; double maxZ; - double diameter; + double radius; void line(const Base::Vector3d &last, const Base::Vector3d &next) { @@ -550,26 +550,27 @@ private: public: int count = 0; - ClearedAreaSegmentVisitor(double maxZ, double diameter) : maxZ(maxZ), diameter(diameter) + ClearedAreaSegmentVisitor(double maxZ, double radius) : maxZ(maxZ), radius(radius) { } CArea getClearedArea() { CArea result{pathSegments}; - result.Thicken(diameter / 2); + result.Thicken(radius); return result; } void g0(int id, const Base::Vector3d &last, const Base::Vector3d &next, const std::deque &pts) override { (void)id; + (void)pts; // printf("g0 [ "); - processPt(last); + //processPt(last); // printf("] "); - processPts(pts); + //processPts(pts); // printf("_ "); - processPt(next); + //processPt(next); line(last, next); // printf("\n"); @@ -578,12 +579,13 @@ public: void g1(int id, const Base::Vector3d &last, const Base::Vector3d &next, const std::deque &pts) override { (void)id; + (void)pts; // printf("g1 [ "); - processPt(last); + //processPt(last); // printf("] "); - processPts(pts); + //processPts(pts); // printf("_ "); - processPt(next); + //processPt(next); line(last, next); // printf("\n"); @@ -594,20 +596,29 @@ public: (void)id; (void)center; // printf("g23 [ "); - processPt(last); + // processPt(last); // printf("] "); - processPts(pts); + // processPts(pts); // printf("_ "); - processPt(next); + // processPt(next); - // TODO do I need to add last and next to the list??? - // TODO rounding resolution of the function that calls this is too big - Base::Vector3d prev = last; - for (Base::Vector3d p : pts) { - line(prev, p); - prev = p; - } - line(prev, next); + // Compute cw vs ccw + const Base::Vector3d vdirect = next - last; + const Base::Vector3d vstep = pts[0] - last; + const bool ccw = vstep.x * vdirect.y - vstep.y * vdirect.x > 0; + + // Add an arc + CCurve curve; + curve.append(CVertex{{last.x, last.y}}); + curve.append(CVertex{ccw ? 1 : -1, {next.x, next.y}, {center.x, center.y}}); + pathSegments.append(curve); + count++; + // Base::Vector3d prev = last; + // for (Base::Vector3d p : pts) { + // line(prev, p); + // prev = p; + // } + // line(prev, next); // printf("\n"); } @@ -644,27 +655,25 @@ private: } void processPt(const Base::Vector3d &pt) { - // printf("(%7.3f, %7.3f, %7.3f) ", pt.x, pt.y, pt.z); + printf("(%7.3f, %7.3f, %7.3f) ", pt.x, pt.y, pt.z); } }; std::shared_ptr Area::getClearedAreaFromPath(const Toolpath *path, double diameter, double zmax) { build(); -#define AREA_MY(_param) myParams.PARAM_FNAME(_param) - PARAM_ENUM_CONVERT(AREA_MY, PARAM_FNAME, PARAM_ENUM_EXCEPT, AREA_PARAMS_OFFSET_CONF); - PARAM_ENUM_CONVERT(AREA_MY, PARAM_FNAME, PARAM_ENUM_EXCEPT, AREA_PARAMS_CLIPPER_FILL); - (void)SubjectFill; - (void)ClipFill; + + // Precision losses in arc/segment conversions (multiples of Accuracy): + // 2.3 in generation of gcode (see documentation in the implementation of CCurve::CheckForArc (libarea/Curve.cpp) + // 1 in gcode arc to segment + // 1 in Thicken() cleared area + // 2 in getRestArea target area offset in and back out + // Oversize cleared areas by buffer to smooth out imprecision in arc/segment conversion. getRestArea() will compensate for this + const double buffer = myParams.Accuracy * 6.3; // Do not fit arcs after these offsets; it introduces unnecessary approximation error, and all off // those arcs will be converted back to segments again for clipper differencing in getRestArea anyway CAreaConfig conf(myParams, /*no_fit_arcs*/ true); - const double roundPrecision = myParams.Accuracy; - const double buffer = 2 * roundPrecision; - (void)JoinType; - (void)EndType; - Base::Vector3d pos = Base::Vector3d(0, 0, zmax + 1); printf("getClearedAreaFromPath(path, diameter=%g, zmax=%g:\n", diameter, zmax); // printf("Gcode:\n"); @@ -678,7 +687,7 @@ std::shared_ptr Area::getClearedAreaFromPath(const Toolpath *path, double printf("\n"); printf("GCode walker:\n"); - ClearedAreaSegmentVisitor visitor(zmax, diameter + buffer); + ClearedAreaSegmentVisitor visitor(zmax, diameter/2 + buffer); PathSegmentWalker walker(*path); walker.walk(visitor, Base::Vector3d(0, 0, zmax + 1)); printf("Count: %d\n", visitor.count); @@ -707,8 +716,14 @@ std::shared_ptr Area::getRestArea(std::vector> clear PARAM_ENUM_CONVERT(AREA_MY, PARAM_FNAME, PARAM_ENUM_EXCEPT, AREA_PARAMS_OFFSET_CONF); PARAM_ENUM_CONVERT(AREA_MY, PARAM_FNAME, PARAM_ENUM_EXCEPT, AREA_PARAMS_CLIPPER_FILL); - const double roundPrecision = myParams.Accuracy; - const double buffer = 2 * roundPrecision; + // Precision losses in arc/segment conversions (multiples of Accuracy): + // 2.3 in generation of gcode (see documentation in the implementation of CCurve::CheckForArc (libarea/Curve.cpp) + // 1 in gcode arc to segment + // 1 in Thicken() cleared area + // 2 in getRestArea target area offset in and back out + // Cleared area representations are oversized by buffer to smooth out imprecision in arc/segment conversion. getRestArea() will compensate for this + const double buffer = myParams.Accuracy * 6.3; + const double roundPrecision = buffer; // transform all clearedAreas into our workplane Area clearedAreasInPlane(&myParams); @@ -724,20 +739,35 @@ std::shared_ptr Area::getRestArea(std::vector> clear &myWorkPlane); } - // remaining = A - prevCleared - CArea remaining(*myArea); + // clearable = offset(offset(A, -dTool/2), dTool/2) + printf("Compute clearable\n"); + CArea clearable(*myArea); + clearable.OffsetWithClipper(-diameter/2, JoinType, EndType, myParams.MiterLimit, roundPrecision); + clearable.OffsetWithClipper(diameter/2, JoinType, EndType, myParams.MiterLimit, roundPrecision); + + // remaining = clearable - prevCleared + printf("Compute remaining\n"); + CArea remaining(clearable); remaining.Clip(toClipperOp(Area::OperationDifference), &*(clearedAreasInPlane.myArea), SubjectFill, ClipFill); - // rest = intersect(A, offset(remaining, dTool)) + // rest = intersect(clearable, offset(remaining, dTool)) + // add buffer to dTool to compensate for oversizing in getClearedAreaFromPath + printf("Compute rest\n"); CArea restCArea(remaining); restCArea.OffsetWithClipper(diameter + buffer, JoinType, EndType, myParams.MiterLimit, roundPrecision); - restCArea.Clip(toClipperOp(Area::OperationIntersection), &*myArea, SubjectFill, ClipFill); + restCArea.Clip(toClipperOp(Area::OperationIntersection), &clearable, SubjectFill, ClipFill); + printf("Convert CArea to Area (num curves: %d)\n", (int)restCArea.m_curves.size()); + if(restCArea.m_curves.size() == 0) { + return {}; + } + + std::shared_ptr restArea = make_shared(&myParams); gp_Trsf trsf(myTrsf.Inverted()); TopoDS_Shape restShape = Area::toShape(restCArea, false, &trsf); - std::shared_ptr restArea = make_shared(&myParams); restArea->add(restShape, OperationCompound); + printf("return\n"); return restArea; } diff --git a/src/Mod/Path/App/AreaPyImp.cpp b/src/Mod/Path/App/AreaPyImp.cpp index ec6c8d66e8..4041d7c734 100644 --- a/src/Mod/Path/App/AreaPyImp.cpp +++ b/src/Mod/Path/App/AreaPyImp.cpp @@ -464,6 +464,9 @@ PyObject* AreaPy::getRestArea(PyObject *args) } std::shared_ptr restArea = getAreaPtr()->getRestArea(clearedAreas, diameter); + if (!restArea) { + return Py_None; + } auto pyRestArea = Py::asObject(new AreaPy(new Area(*restArea, true))); return Py::new_reference_to(pyRestArea); } PY_CATCH_OCC diff --git a/src/Mod/Path/Path/Op/Area.py b/src/Mod/Path/Path/Op/Area.py index 77862274c2..e98794b59b 100644 --- a/src/Mod/Path/Path/Op/Area.py +++ b/src/Mod/Path/Path/Op/Area.py @@ -283,14 +283,14 @@ class ObjectOp(PathOp.ObjectOp): tool = op.Proxy.tool if hasattr(op.Proxy, "tool") else op.ToolController.Proxy.getTool(op.ToolController) diameter = tool.Diameter.getValueAs("mm") sectionClearedAreas.append(area.getClearedAreaFromPath(op.Path, diameter, z+0.001)) - debugZ = -1.5 - if debugZ -.1 < z and z < debugZ + .1: - debugObj = obj.Document.addObject("Part::Feature", "Debug_{}_{}".format(debugZ, op.Name)) - debugObj.Label = "Debug_{}_{}".format(debugZ, op.Label) - debugObj.Shape = sectionClearedAreas[-1].getShape() - pass + # debugZ = -1.5 + # if debugZ -.1 < z and z < debugZ + .1: + # debugObj = obj.Document.addObject("Part::Feature", "Debug_{}_{}".format(debugZ, op.Name)) + # debugObj.Label = "Debug_{}_{}".format(debugZ, op.Label) + # debugObj.Shape = sectionClearedAreas[-1].getShape() restSection = section.getRestArea(sectionClearedAreas, self.tool.Diameter.getValueAs("mm")) - restSections.append(restSection) + if (restSection is not None): + restSections.append(restSection) sections = restSections shapelist = [sec.getShape() for sec in sections] From 9cbf0318952fbdf1bf752d8777612a233c9130ea Mon Sep 17 00:00:00 2001 From: David Kaufman Date: Sun, 7 Jan 2024 17:00:34 -0500 Subject: [PATCH 04/11] do computations at higher precision to mitigate error stackup --- src/Mod/Path/App/Area.cpp | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/Mod/Path/App/Area.cpp b/src/Mod/Path/App/Area.cpp index 73b8200327..cb96f3cf71 100644 --- a/src/Mod/Path/App/Area.cpp +++ b/src/Mod/Path/App/Area.cpp @@ -668,11 +668,13 @@ std::shared_ptr Area::getClearedAreaFromPath(const Toolpath *path, double // 1 in Thicken() cleared area // 2 in getRestArea target area offset in and back out // Oversize cleared areas by buffer to smooth out imprecision in arc/segment conversion. getRestArea() will compensate for this - const double buffer = myParams.Accuracy * 6.3; + AreaParams params = myParams; + params.Accuracy = myParams.Accuracy * .7/4; // 2.3 already encoded in gcode; 4 * .7/4 = 3 total + const double buffer = myParams.Accuracy * 3; // Do not fit arcs after these offsets; it introduces unnecessary approximation error, and all off // those arcs will be converted back to segments again for clipper differencing in getRestArea anyway - CAreaConfig conf(myParams, /*no_fit_arcs*/ true); + CAreaConfig conf(params, /*no_fit_arcs*/ true); Base::Vector3d pos = Base::Vector3d(0, 0, zmax + 1); printf("getClearedAreaFromPath(path, diameter=%g, zmax=%g:\n", diameter, zmax); @@ -695,7 +697,7 @@ std::shared_ptr Area::getClearedAreaFromPath(const Toolpath *path, double printf("\n"); - std::shared_ptr clearedArea = make_shared(&myParams); + std::shared_ptr clearedArea = make_shared(¶ms); //clearedArea->myTrsf = myTrsf; clearedArea->myTrsf = {}; if (visitor.count > 0) { @@ -722,11 +724,13 @@ std::shared_ptr Area::getRestArea(std::vector> clear // 1 in Thicken() cleared area // 2 in getRestArea target area offset in and back out // Cleared area representations are oversized by buffer to smooth out imprecision in arc/segment conversion. getRestArea() will compensate for this - const double buffer = myParams.Accuracy * 6.3; - const double roundPrecision = buffer; + AreaParams params = myParams; + params.Accuracy = myParams.Accuracy * .7/4; // 2.3 already encoded in gcode; 4 * .7/4 = 3 total + const double buffer = myParams.Accuracy * 3; + const double roundPrecision = params.Accuracy; // transform all clearedAreas into our workplane - Area clearedAreasInPlane(&myParams); + Area clearedAreasInPlane(¶ms); clearedAreasInPlane.myArea.reset(new CArea()); printf("getRestArea\n"); printf("\n"); @@ -742,8 +746,8 @@ std::shared_ptr Area::getRestArea(std::vector> clear // clearable = offset(offset(A, -dTool/2), dTool/2) printf("Compute clearable\n"); CArea clearable(*myArea); - clearable.OffsetWithClipper(-diameter/2, JoinType, EndType, myParams.MiterLimit, roundPrecision); - clearable.OffsetWithClipper(diameter/2, JoinType, EndType, myParams.MiterLimit, roundPrecision); + clearable.OffsetWithClipper(-diameter/2, JoinType, EndType, params.MiterLimit, roundPrecision); + clearable.OffsetWithClipper(diameter/2, JoinType, EndType, params.MiterLimit, roundPrecision); // remaining = clearable - prevCleared printf("Compute remaining\n"); @@ -754,7 +758,7 @@ std::shared_ptr Area::getRestArea(std::vector> clear // add buffer to dTool to compensate for oversizing in getClearedAreaFromPath printf("Compute rest\n"); CArea restCArea(remaining); - restCArea.OffsetWithClipper(diameter + buffer, JoinType, EndType, myParams.MiterLimit, roundPrecision); + restCArea.OffsetWithClipper(diameter + buffer, JoinType, EndType, params.MiterLimit, roundPrecision); restCArea.Clip(toClipperOp(Area::OperationIntersection), &clearable, SubjectFill, ClipFill); printf("Convert CArea to Area (num curves: %d)\n", (int)restCArea.m_curves.size()); @@ -762,7 +766,7 @@ std::shared_ptr Area::getRestArea(std::vector> clear return {}; } - std::shared_ptr restArea = make_shared(&myParams); + std::shared_ptr restArea = make_shared(¶ms); gp_Trsf trsf(myTrsf.Inverted()); TopoDS_Shape restShape = Area::toShape(restCArea, false, &trsf); restArea->add(restShape, OperationCompound); From 57313f7b5c58819808f55f613ef717d7851e310d Mon Sep 17 00:00:00 2001 From: David Kaufman Date: Sun, 7 Jan 2024 17:18:02 -0500 Subject: [PATCH 05/11] remove old/deprecated rest machining code --- src/Mod/Path/App/Area.cpp | 32 +------------------------- src/Mod/Path/App/Area.h | 1 - src/Mod/Path/App/AreaPy.xml | 5 ---- src/Mod/Path/App/AreaPyImp.cpp | 17 -------------- src/Mod/Path/Path/Op/Area.py | 34 --------------------------- src/Mod/Path/Path/Op/PocketBase.py | 37 ++++-------------------------- 6 files changed, 5 insertions(+), 121 deletions(-) diff --git a/src/Mod/Path/App/Area.cpp b/src/Mod/Path/App/Area.cpp index cb96f3cf71..b979a6d155 100644 --- a/src/Mod/Path/App/Area.cpp +++ b/src/Mod/Path/App/Area.cpp @@ -497,37 +497,6 @@ void Area::add(const TopoDS_Shape& shape, short op) { myShapes.emplace_back(op, shape); } -std::shared_ptr Area::getClearedArea(double tipDiameter, double diameter) { - build(); -#define AREA_MY(_param) myParams.PARAM_FNAME(_param) - PARAM_ENUM_CONVERT(AREA_MY, PARAM_FNAME, PARAM_ENUM_EXCEPT, AREA_PARAMS_OFFSET_CONF); - PARAM_ENUM_CONVERT(AREA_MY, PARAM_FNAME, PARAM_ENUM_EXCEPT, AREA_PARAMS_CLIPPER_FILL); - (void)SubjectFill; - (void)ClipFill; - - // Do not fit arcs after these offsets; it introduces unnecessary approximation error, and all off - // those arcs will be converted back to segments again for clipper differencing in getRestArea anyway - CAreaConfig conf(myParams, /*no_fit_arcs*/ true); - - const double roundPrecision = myParams.Accuracy; - const double buffer = 2 * roundPrecision; - - // A = myArea - // prevCenters = offset(A, -rTip) - const double rTip = tipDiameter / 2.; - CArea prevCenter(*myArea); - prevCenter.OffsetWithClipper(-rTip, JoinType, EndType, myParams.MiterLimit, roundPrecision); - - // prevCleared = offset(prevCenter, r). - CArea prevCleared(prevCenter); - prevCleared.OffsetWithClipper(diameter / 2. + buffer, JoinType, EndType, myParams.MiterLimit, roundPrecision); - - std::shared_ptr clearedArea = make_shared(*this); - clearedArea->myArea.reset(new CArea(prevCleared)); - - return clearedArea; -} - class ClearedAreaSegmentVisitor : public PathSegmentVisitor { private: @@ -715,6 +684,7 @@ std::shared_ptr Area::getClearedAreaFromPath(const Toolpath *path, double std::shared_ptr Area::getRestArea(std::vector> clearedAreas, double diameter) { build(); +#define AREA_MY(_param) myParams.PARAM_FNAME(_param) PARAM_ENUM_CONVERT(AREA_MY, PARAM_FNAME, PARAM_ENUM_EXCEPT, AREA_PARAMS_OFFSET_CONF); PARAM_ENUM_CONVERT(AREA_MY, PARAM_FNAME, PARAM_ENUM_EXCEPT, AREA_PARAMS_CLIPPER_FILL); diff --git a/src/Mod/Path/App/Area.h b/src/Mod/Path/App/Area.h index 3dc769dc9a..af21097705 100644 --- a/src/Mod/Path/App/Area.h +++ b/src/Mod/Path/App/Area.h @@ -242,7 +242,6 @@ public: const std::vector& heights = std::vector(), const TopoDS_Shape& plane = TopoDS_Shape()); - std::shared_ptr getClearedArea(double tipDiameter, double diameter); std::shared_ptr getClearedAreaFromPath(const Toolpath *path, double diameter, double zmax); std::shared_ptr getRestArea(std::vector> clearedAreas, double diameter); TopoDS_Shape toTopoShape(); diff --git a/src/Mod/Path/App/AreaPy.xml b/src/Mod/Path/App/AreaPy.xml index 029b245c21..209765c3e0 100644 --- a/src/Mod/Path/App/AreaPy.xml +++ b/src/Mod/Path/App/AreaPy.xml @@ -62,11 +62,6 @@ same algorithm - - - - - diff --git a/src/Mod/Path/App/AreaPyImp.cpp b/src/Mod/Path/App/AreaPyImp.cpp index 4041d7c734..5ee87a3a9c 100644 --- a/src/Mod/Path/App/AreaPyImp.cpp +++ b/src/Mod/Path/App/AreaPyImp.cpp @@ -148,11 +148,6 @@ static const PyMethodDef areaOverrides[] = { "\n* plane (None): optional shape to specify a section plane. If not give, the current workplane\n" "of this Area is used if section mode is 'Workplane'.", }, - { - "getClearedArea",nullptr,0, - "getClearedArea(tipDiameter, diameter):\n" - "Gets the area cleared when a tool maximally clears this area. This method assumes a tool tip diameter 'tipDiameter' traces the full area, and that (perhaps at a different height on the tool) this clears a different region with tool diameter 'diameter'.\n", - }, { "getClearedAreaFromPath",nullptr,0, "getClearedAreaFromPath(path, diameter, zmax):\n" @@ -409,18 +404,6 @@ PyObject* AreaPy::makeSections(PyObject *args, PyObject *keywds) } PY_CATCH_OCC } -PyObject* AreaPy::getClearedArea(PyObject *args) -{ - PY_TRY { - double tipDiameter, diameter; - if (!PyArg_ParseTuple(args, "dd", &tipDiameter, &diameter)) - return nullptr; - std::shared_ptr clearedArea = getAreaPtr()->getClearedArea(tipDiameter, diameter); - auto pyClearedArea = Py::asObject(new AreaPy(new Area(*clearedArea, true))); - return Py::new_reference_to(pyClearedArea); - } PY_CATCH_OCC -} - PyObject* AreaPy::getClearedAreaFromPath(PyObject *args) { PY_TRY { diff --git a/src/Mod/Path/Path/Op/Area.py b/src/Mod/Path/Path/Op/Area.py index e98794b59b..36d0c923eb 100644 --- a/src/Mod/Path/Path/Op/Area.py +++ b/src/Mod/Path/Path/Op/Area.py @@ -240,39 +240,9 @@ class ObjectOp(PathOp.ObjectOp): # Rest machining self.sectionShapes = self.sectionShapes + [section.toTopoShape() for section in sections] if hasattr(obj, "UseRestMachining") and obj.UseRestMachining: - # Loop through prior operations - clearedAreas = [] - foundSelf = False - for op in self.job.Operations.Group: - if foundSelf: - break - oplist = [op] + op.OutListRecursive - oplist = list(filter(lambda op: hasattr(op, "Active"), oplist)) - for op in oplist: - if op.Proxy == self: - # Ignore self, and all later operations - foundSelf = True - break - if hasattr(op, "RestMachiningRegions") and op.Active: - if hasattr(op, "RestMachiningRegionsNeedRecompute") and op.RestMachiningRegionsNeedRecompute: - Path.Log.warning( - translate("PathAreaOp", "Previous operation %s is required for rest machining, but it has no stored rest machining metadata. Recomputing to generate this metadata...") % op.Label - ) - op.recompute() - - tool = op.Proxy.tool if hasattr(op.Proxy, "tool") else op.ToolController.Proxy.getTool(op.ToolController) - diameter = tool.Diameter.getValueAs("mm") - def shapeToArea(shape): - area = Path.Area() - area.setPlane(PathUtils.makeWorkplane(shape)) - area.add(shape) - return area - opClearedAreas = [shapeToArea(pa).getClearedArea(diameter, diameter) for pa in op.RestMachiningRegions.SubShapes] - clearedAreas.extend(opClearedAreas) restSections = [] for section in sections: z = section.getShape().BoundBox.ZMin - # sectionClearedAreas = [a for a in clearedAreas if a.getShape().BoundBox.ZMax <= z] sectionClearedAreas = [] for op in self.job.Operations.Group: print(op.Name) @@ -497,10 +467,6 @@ class ObjectOp(PathOp.ObjectOp): ) ) - if hasattr(obj, "RestMachiningRegions"): - obj.RestMachiningRegions = Part.makeCompound(self.sectionShapes) - if hasattr(obj, "RestMachiningRegionsNeedRecompute"): - obj.RestMachiningRegionsNeedRecompute = False Path.Log.debug("obj.Name: " + str(obj.Name) + "\n\n") return sims diff --git a/src/Mod/Path/Path/Op/PocketBase.py b/src/Mod/Path/Path/Op/PocketBase.py index 829dd865c3..9dc30836de 100644 --- a/src/Mod/Path/Path/Op/PocketBase.py +++ b/src/Mod/Path/Path/Op/PocketBase.py @@ -194,16 +194,6 @@ class ObjectPocket(PathAreaOp.ObjectOp): "Skips machining regions that have already been cleared by previous operations.", ), ) - obj.addProperty( - "Part::PropertyPartShape", - "RestMachiningRegions", - "Pocket", - QT_TRANSLATE_NOOP( - "App::Property", - "The areas cleared by this operation, one area per height, stored as a compound part. Used internally for rest machining.", - ), - ) - obj.setEditorMode("RestMachiningRegions", 2) # hide for n in self.pocketPropertyEnumerations(): setattr(obj, n[0], n[1]) @@ -277,29 +267,10 @@ class ObjectPocket(PathAreaOp.ObjectOp): ), ) - if not hasattr(obj, "RestMachiningRegions"): - obj.addProperty( - "Part::PropertyPartShape", - "RestMachiningRegions", - "Pocket", - QT_TRANSLATE_NOOP( - "App::Property", - "The areas cleared by this operation, one area per height, stored as a compound part. Used internally for rest machining.", - ), - ) - obj.setEditorMode("RestMachiningRegions", 2) # hide - - obj.addProperty( - "App::PropertyBool", - "RestMachiningRegionsNeedRecompute", - "Pocket", - QT_TRANSLATE_NOOP( - "App::Property", - "Flag to indicate that the rest machining regions have never been computed, and must be recomputed before being used.", - ), - ) - obj.setEditorMode("RestMachiningRegionsNeedRecompute", 2) # hide - obj.RestMachiningRegionsNeedRecompute = True + if hasattr(obj, "RestMachiningRegions"): + obj.removeProperty("RestMachiningRegions") + if hasattr(obj, "RestMachiningRegionsNeedRecompute"): + obj.removeProperty("RestMachiningRegionsNeedRecompute") Path.Log.track() From 7b55376d76cc2810e17358cf910c7188e99d665e Mon Sep 17 00:00:00 2001 From: David Kaufman Date: Sun, 7 Jan 2024 17:23:03 -0500 Subject: [PATCH 06/11] rename getClearedAreaFromPath -> getClearedArea --- src/Mod/Path/App/Area.cpp | 6 +++--- src/Mod/Path/App/Area.h | 2 +- src/Mod/Path/App/AreaPy.xml | 2 +- src/Mod/Path/App/AreaPyImp.cpp | 8 ++++---- src/Mod/Path/Path/Op/Area.py | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Mod/Path/App/Area.cpp b/src/Mod/Path/App/Area.cpp index b979a6d155..30d680ff12 100644 --- a/src/Mod/Path/App/Area.cpp +++ b/src/Mod/Path/App/Area.cpp @@ -628,7 +628,7 @@ private: } }; -std::shared_ptr Area::getClearedAreaFromPath(const Toolpath *path, double diameter, double zmax) { +std::shared_ptr Area::getClearedArea(const Toolpath *path, double diameter, double zmax) { build(); // Precision losses in arc/segment conversions (multiples of Accuracy): @@ -646,7 +646,7 @@ std::shared_ptr Area::getClearedAreaFromPath(const Toolpath *path, double CAreaConfig conf(params, /*no_fit_arcs*/ true); Base::Vector3d pos = Base::Vector3d(0, 0, zmax + 1); - printf("getClearedAreaFromPath(path, diameter=%g, zmax=%g:\n", diameter, zmax); + printf("getClearedArea(path, diameter=%g, zmax=%g:\n", diameter, zmax); // printf("Gcode:\n"); for (auto c : path->getCommands()) { // printf("\t%s ", c->Name.c_str()); @@ -725,7 +725,7 @@ std::shared_ptr Area::getRestArea(std::vector> clear remaining.Clip(toClipperOp(Area::OperationDifference), &*(clearedAreasInPlane.myArea), SubjectFill, ClipFill); // rest = intersect(clearable, offset(remaining, dTool)) - // add buffer to dTool to compensate for oversizing in getClearedAreaFromPath + // add buffer to dTool to compensate for oversizing in getClearedArea printf("Compute rest\n"); CArea restCArea(remaining); restCArea.OffsetWithClipper(diameter + buffer, JoinType, EndType, params.MiterLimit, roundPrecision); diff --git a/src/Mod/Path/App/Area.h b/src/Mod/Path/App/Area.h index af21097705..8056f4a3da 100644 --- a/src/Mod/Path/App/Area.h +++ b/src/Mod/Path/App/Area.h @@ -242,7 +242,7 @@ public: const std::vector& heights = std::vector(), const TopoDS_Shape& plane = TopoDS_Shape()); - std::shared_ptr getClearedAreaFromPath(const Toolpath *path, double diameter, double zmax); + std::shared_ptr getClearedArea(const Toolpath *path, double diameter, double zmax); std::shared_ptr getRestArea(std::vector> clearedAreas, double diameter); TopoDS_Shape toTopoShape(); diff --git a/src/Mod/Path/App/AreaPy.xml b/src/Mod/Path/App/AreaPy.xml index 209765c3e0..a38a84aec1 100644 --- a/src/Mod/Path/App/AreaPy.xml +++ b/src/Mod/Path/App/AreaPy.xml @@ -62,7 +62,7 @@ same algorithm - + diff --git a/src/Mod/Path/App/AreaPyImp.cpp b/src/Mod/Path/App/AreaPyImp.cpp index 5ee87a3a9c..a0a85b930b 100644 --- a/src/Mod/Path/App/AreaPyImp.cpp +++ b/src/Mod/Path/App/AreaPyImp.cpp @@ -149,8 +149,8 @@ static const PyMethodDef areaOverrides[] = { "of this Area is used if section mode is 'Workplane'.", }, { - "getClearedAreaFromPath",nullptr,0, - "getClearedAreaFromPath(path, diameter, zmax):\n" + "getClearedArea",nullptr,0, + "getClearedArea(path, diameter, zmax):\n" "Gets the area cleared when a tool of the specified diameter follows the gcode represented in the path, ignoring cleared space above zmax.\n", }, { @@ -404,7 +404,7 @@ PyObject* AreaPy::makeSections(PyObject *args, PyObject *keywds) } PY_CATCH_OCC } -PyObject* AreaPy::getClearedAreaFromPath(PyObject *args) +PyObject* AreaPy::getClearedArea(PyObject *args) { PY_TRY { PyObject *pyPath; @@ -416,7 +416,7 @@ PyObject* AreaPy::getClearedAreaFromPath(PyObject *args) return nullptr; } const PathPy *path = static_cast(pyPath); - std::shared_ptr clearedArea = getAreaPtr()->getClearedAreaFromPath(path->getToolpathPtr(), diameter, zmax); + std::shared_ptr clearedArea = getAreaPtr()->getClearedArea(path->getToolpathPtr(), diameter, zmax); auto pyClearedArea = Py::asObject(new AreaPy(new Area(*clearedArea, true))); return Py::new_reference_to(pyClearedArea); } PY_CATCH_OCC diff --git a/src/Mod/Path/Path/Op/Area.py b/src/Mod/Path/Path/Op/Area.py index 36d0c923eb..5f9e3a759f 100644 --- a/src/Mod/Path/Path/Op/Area.py +++ b/src/Mod/Path/Path/Op/Area.py @@ -252,7 +252,7 @@ class ObjectOp(PathOp.ObjectOp): if hasattr(op, "Active") and op.Active and op.Path: tool = op.Proxy.tool if hasattr(op.Proxy, "tool") else op.ToolController.Proxy.getTool(op.ToolController) diameter = tool.Diameter.getValueAs("mm") - sectionClearedAreas.append(area.getClearedAreaFromPath(op.Path, diameter, z+0.001)) + sectionClearedAreas.append(area.getClearedArea(op.Path, diameter, z+0.001)) # debugZ = -1.5 # if debugZ -.1 < z and z < debugZ + .1: # debugObj = obj.Document.addObject("Part::Feature", "Debug_{}_{}".format(debugZ, op.Name)) From 20ce99cfccc8526a6e7a4393975dd9541a6a7ebe Mon Sep 17 00:00:00 2001 From: David Kaufman Date: Mon, 8 Jan 2024 00:52:19 -0500 Subject: [PATCH 07/11] Important performance optimization: union Thicken() polygons individually, not en masse --- src/Mod/Path/libarea/AreaClipper.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Mod/Path/libarea/AreaClipper.cpp b/src/Mod/Path/libarea/AreaClipper.cpp index d3e9ff8397..48f3dc93af 100644 --- a/src/Mod/Path/libarea/AreaClipper.cpp +++ b/src/Mod/Path/libarea/AreaClipper.cpp @@ -253,11 +253,15 @@ static void OffsetSpansWithObrounds(const CArea& area, TPolyPolygon &pp_new, dou { Clipper c; c.StrictlySimple(CArea::m_clipper_simple); - + pp_new.clear(); for(std::list::const_iterator It = area.m_curves.begin(); It != area.m_curves.end(); It++) { + c.Clear(); + c.AddPaths(pp_new, ptSubject, true); + pp_new.clear(); pts_for_AddVertex.clear(); + const CCurve& curve = *It; const CVertex* prev_vertex = NULL; for(std::list::const_iterator It2 = curve.m_vertices.begin(); It2 != curve.m_vertices.end(); It2++) @@ -278,10 +282,9 @@ static void OffsetSpansWithObrounds(const CArea& area, TPolyPolygon &pp_new, dou } prev_vertex = &vertex; } + c.Execute(ctUnion, pp_new, pftNonZero, pftNonZero); } - pp_new.clear(); - c.Execute(ctUnion, pp_new, pftNonZero, pftNonZero); // reverse all the resulting polygons TPolyPolygon copy = pp_new; From 4cb991cf476a2eb4e4b171e8f63b56b791aeb3c4 Mon Sep 17 00:00:00 2001 From: David Kaufman Date: Mon, 8 Jan 2024 14:33:19 -0500 Subject: [PATCH 08/11] add bbox check to getClearedArea to filter out irrelevant gcode --- src/Mod/Path/App/Area.cpp | 47 ++++++++++++++++------------------ src/Mod/Path/App/Area.h | 2 +- src/Mod/Path/App/AreaPyImp.cpp | 16 ++++++++---- src/Mod/Path/Path/Op/Area.py | 5 ++-- 4 files changed, 37 insertions(+), 33 deletions(-) diff --git a/src/Mod/Path/App/Area.cpp b/src/Mod/Path/App/Area.cpp index 30d680ff12..7c9ed4ce00 100644 --- a/src/Mod/Path/App/Area.cpp +++ b/src/Mod/Path/App/Area.cpp @@ -503,24 +503,26 @@ private: CArea pathSegments; double maxZ; double radius; + Base::BoundBox3d bbox; void line(const Base::Vector3d &last, const Base::Vector3d &next) { if (last.z <= maxZ && next.z <= maxZ) { - CCurve curve; - curve.append(CVertex{{last.x, last.y}}); - curve.append(CVertex{{next.x, next.y}}); - pathSegments.append(curve); - count++; - } else { - // printf("SKIP!"); + Base::BoundBox2d segBox = {}; + segBox.Add({last.x, last.y}); + segBox.Add({next.x, next.y}); + if (bbox.Intersect(segBox)) { + CCurve curve; + curve.append(CVertex{{last.x, last.y}}); + curve.append(CVertex{{next.x, next.y}}); + pathSegments.append(curve); + } } } public: - int count = 0; - - ClearedAreaSegmentVisitor(double maxZ, double radius) : maxZ(maxZ), radius(radius) + ClearedAreaSegmentVisitor(double maxZ, double radius, Base::BoundBox3d bbox) : maxZ(maxZ), radius(radius), bbox(bbox) { + bbox.Enlarge(radius); } CArea getClearedArea() @@ -581,7 +583,6 @@ public: curve.append(CVertex{{last.x, last.y}}); curve.append(CVertex{ccw ? 1 : -1, {next.x, next.y}, {center.x, center.y}}); pathSegments.append(curve); - count++; // Base::Vector3d prev = last; // for (Base::Vector3d p : pts) { // line(prev, p); @@ -597,10 +598,10 @@ public: (void)id; (void)q; // always within the bounds of p printf("g8x UNHANDLED\n"); - // processPt(last); - // processPts(pts); - // processPts(p); - // processPt(next); + processPt(last); + processPts(pts); + processPts(p); + processPt(next); (void)last; (void)pts; (void)p; @@ -608,10 +609,8 @@ public: } void g38(int id, const Base::Vector3d &last, const Base::Vector3d &next) override { + // probe operation; clears nothing (void)id; - printf("g38 UNHANDLED\n"); - // processPt(last); - // processPt(next); (void)last; (void)next; } @@ -628,7 +627,7 @@ private: } }; -std::shared_ptr Area::getClearedArea(const Toolpath *path, double diameter, double zmax) { +std::shared_ptr Area::getClearedArea(const Toolpath *path, double diameter, double zmax, Base::BoundBox3d bbox) { build(); // Precision losses in arc/segment conversions (multiples of Accuracy): @@ -658,20 +657,18 @@ std::shared_ptr Area::getClearedArea(const Toolpath *path, double diameter printf("\n"); printf("GCode walker:\n"); - ClearedAreaSegmentVisitor visitor(zmax, diameter/2 + buffer); + ClearedAreaSegmentVisitor visitor(zmax, diameter/2 + buffer, bbox); PathSegmentWalker walker(*path); walker.walk(visitor, Base::Vector3d(0, 0, zmax + 1)); - printf("Count: %d\n", visitor.count); printf("\n"); printf("\n"); std::shared_ptr clearedArea = make_shared(¶ms); - //clearedArea->myTrsf = myTrsf; clearedArea->myTrsf = {}; - if (visitor.count > 0) { - //gp_Trsf trsf(myTrsf.Inverted()); - TopoDS_Shape clearedAreaShape = Area::toShape(visitor.getClearedArea(), false/*, &trsf*/); + const CArea ca = visitor.getClearedArea(); + if (ca.m_curves.size() > 0) { + TopoDS_Shape clearedAreaShape = Area::toShape(ca, false); clearedArea->add(clearedAreaShape, OperationCompound); clearedArea->build(); } else { diff --git a/src/Mod/Path/App/Area.h b/src/Mod/Path/App/Area.h index 8056f4a3da..e79a62577d 100644 --- a/src/Mod/Path/App/Area.h +++ b/src/Mod/Path/App/Area.h @@ -242,7 +242,7 @@ public: const std::vector& heights = std::vector(), const TopoDS_Shape& plane = TopoDS_Shape()); - std::shared_ptr getClearedArea(const Toolpath *path, double diameter, double zmax); + std::shared_ptr getClearedArea(const Toolpath *path, double diameter, double zmax, Base::BoundBox3d bbox); std::shared_ptr getRestArea(std::vector> clearedAreas, double diameter); TopoDS_Shape toTopoShape(); diff --git a/src/Mod/Path/App/AreaPyImp.cpp b/src/Mod/Path/App/AreaPyImp.cpp index a0a85b930b..bbe4182c23 100644 --- a/src/Mod/Path/App/AreaPyImp.cpp +++ b/src/Mod/Path/App/AreaPyImp.cpp @@ -22,6 +22,7 @@ #include "PreCompiled.h" +#include #include #include #include @@ -150,8 +151,8 @@ static const PyMethodDef areaOverrides[] = { }, { "getClearedArea",nullptr,0, - "getClearedArea(path, diameter, zmax):\n" - "Gets the area cleared when a tool of the specified diameter follows the gcode represented in the path, ignoring cleared space above zmax.\n", + "getClearedArea(path, diameter, zmax, bbox):\n" + "Gets the area cleared when a tool of the specified diameter follows the gcode represented in the path, ignoring cleared space above zmax and path segments that don't affect space within bbox.\n", }, { "getRestArea",nullptr,0, @@ -407,16 +408,21 @@ PyObject* AreaPy::makeSections(PyObject *args, PyObject *keywds) PyObject* AreaPy::getClearedArea(PyObject *args) { PY_TRY { - PyObject *pyPath; + PyObject *pyPath, *pyBbox; double diameter, zmax; - if (!PyArg_ParseTuple(args, "Odd", &pyPath, &diameter, &zmax)) + if (!PyArg_ParseTuple(args, "OddO", &pyPath, &diameter, &zmax, &pyBbox)) return nullptr; if (!PyObject_TypeCheck(pyPath, &(PathPy::Type))) { PyErr_SetString(PyExc_TypeError, "path must be of type PathPy"); return nullptr; } + if (!PyObject_TypeCheck(pyBbox, &(Base::BoundBoxPy::Type))) { + PyErr_SetString(PyExc_TypeError, "bbox must be of type BoundBoxPy"); + return nullptr; + } const PathPy *path = static_cast(pyPath); - std::shared_ptr clearedArea = getAreaPtr()->getClearedArea(path->getToolpathPtr(), diameter, zmax); + const Py::BoundingBox bbox(pyBbox, false); + std::shared_ptr clearedArea = getAreaPtr()->getClearedArea(path->getToolpathPtr(), diameter, zmax, bbox.getValue()); auto pyClearedArea = Py::asObject(new AreaPy(new Area(*clearedArea, true))); return Py::new_reference_to(pyClearedArea); } PY_CATCH_OCC diff --git a/src/Mod/Path/Path/Op/Area.py b/src/Mod/Path/Path/Op/Area.py index 5f9e3a759f..e88440f55f 100644 --- a/src/Mod/Path/Path/Op/Area.py +++ b/src/Mod/Path/Path/Op/Area.py @@ -242,7 +242,8 @@ class ObjectOp(PathOp.ObjectOp): if hasattr(obj, "UseRestMachining") and obj.UseRestMachining: restSections = [] for section in sections: - z = section.getShape().BoundBox.ZMin + bbox = section.getShape().BoundBox + z = bbox.ZMin sectionClearedAreas = [] for op in self.job.Operations.Group: print(op.Name) @@ -252,7 +253,7 @@ class ObjectOp(PathOp.ObjectOp): if hasattr(op, "Active") and op.Active and op.Path: tool = op.Proxy.tool if hasattr(op.Proxy, "tool") else op.ToolController.Proxy.getTool(op.ToolController) diameter = tool.Diameter.getValueAs("mm") - sectionClearedAreas.append(area.getClearedArea(op.Path, diameter, z+0.001)) + sectionClearedAreas.append(section.getClearedArea(op.Path, diameter, z+0.001, bbox)) # debugZ = -1.5 # if debugZ -.1 < z and z < debugZ + .1: # debugObj = obj.Document.addObject("Part::Feature", "Debug_{}_{}".format(debugZ, op.Name)) From b20d702b0d6477ec14390626da055bdadbacc866 Mon Sep 17 00:00:00 2001 From: David Kaufman Date: Mon, 8 Jan 2024 19:31:00 -0500 Subject: [PATCH 09/11] add support for drilling gcode --- src/Mod/Path/App/Area.cpp | 44 +++++++++++++++++++++++++++--------- src/Mod/Path/Path/Op/Area.py | 3 ++- 2 files changed, 35 insertions(+), 12 deletions(-) diff --git a/src/Mod/Path/App/Area.cpp b/src/Mod/Path/App/Area.cpp index 7c9ed4ce00..5b13bd75cc 100644 --- a/src/Mod/Path/App/Area.cpp +++ b/src/Mod/Path/App/Area.cpp @@ -501,10 +501,27 @@ class ClearedAreaSegmentVisitor : public PathSegmentVisitor { private: CArea pathSegments; + CArea holes; double maxZ; double radius; Base::BoundBox3d bbox; + void point(const Base::Vector3d &p) + { + printf("Point? (%.0f, %.0f, %.0f) ", p.x, p.y, p.z); + if (p.z <= maxZ) { + if (bbox.MinX <= p.x && p.x <= bbox.MaxX && bbox.MinY <= p.y && p.y <= bbox.MaxY) { + CCurve curve; + curve.append(CVertex{{p.x + radius, p.y}}); + curve.append(CVertex{1, {p.x - radius, p.y}, {p.x, p.y}}); + curve.append(CVertex{1, {p.x + radius, p.y}, {p.x, p.y}}); + holes.append(curve); + printf("accepted"); + } + } + printf("\n"); + } + void line(const Base::Vector3d &last, const Base::Vector3d &next) { if (last.z <= maxZ && next.z <= maxZ) { @@ -529,6 +546,7 @@ public: { CArea result{pathSegments}; result.Thicken(radius); + result.Union(holes); return result; } @@ -593,19 +611,20 @@ public: } void g8x(int id, const Base::Vector3d &last, const Base::Vector3d &next, const std::deque &pts, - const std::deque &p, const std::deque &q) override + const std::deque &plist, const std::deque &qlist) override { + // (peck) drilling (void)id; - (void)q; // always within the bounds of p - printf("g8x UNHANDLED\n"); - processPt(last); - processPts(pts); - processPts(p); - processPt(next); - (void)last; - (void)pts; - (void)p; - (void)next; + (void)qlist; // pecks are always within the bounds of plist + + point(last); + for (const auto p : pts) { + point(p); + } + for (const auto p : plist) { + point(p); + } + point(next); } void g38(int id, const Base::Vector3d &last, const Base::Vector3d &next) override { @@ -638,6 +657,8 @@ std::shared_ptr Area::getClearedArea(const Toolpath *path, double diameter // Oversize cleared areas by buffer to smooth out imprecision in arc/segment conversion. getRestArea() will compensate for this AreaParams params = myParams; params.Accuracy = myParams.Accuracy * .7/4; // 2.3 already encoded in gcode; 4 * .7/4 = 3 total + params.SubjectFill = ClipperLib::pftNonZero; + params.ClipFill = ClipperLib::pftNonZero; const double buffer = myParams.Accuracy * 3; // Do not fit arcs after these offsets; it introduces unnecessary approximation error, and all off @@ -667,6 +688,7 @@ std::shared_ptr Area::getClearedArea(const Toolpath *path, double diameter std::shared_ptr clearedArea = make_shared(¶ms); clearedArea->myTrsf = {}; const CArea ca = visitor.getClearedArea(); + printf("Cleared area segments: %ld\n", ca.m_curves.size()); if (ca.m_curves.size() > 0) { TopoDS_Shape clearedAreaShape = Area::toShape(ca, false); clearedArea->add(clearedAreaShape, OperationCompound); diff --git a/src/Mod/Path/Path/Op/Area.py b/src/Mod/Path/Path/Op/Area.py index e88440f55f..5ec88b640f 100644 --- a/src/Mod/Path/Path/Op/Area.py +++ b/src/Mod/Path/Path/Op/Area.py @@ -253,7 +253,8 @@ class ObjectOp(PathOp.ObjectOp): if hasattr(op, "Active") and op.Active and op.Path: tool = op.Proxy.tool if hasattr(op.Proxy, "tool") else op.ToolController.Proxy.getTool(op.ToolController) diameter = tool.Diameter.getValueAs("mm") - sectionClearedAreas.append(section.getClearedArea(op.Path, diameter, z+0.001, bbox)) + dz = 0 if not hasattr(tool, "TipAngle") else -PathUtils.drillTipLength(tool) # for drills, dz moves to the full width part of the tool + sectionClearedAreas.append(section.getClearedArea(op.Path, diameter, z+dz+0.001, bbox)) # debugZ = -1.5 # if debugZ -.1 < z and z < debugZ + .1: # debugObj = obj.Document.addObject("Part::Feature", "Debug_{}_{}".format(debugZ, op.Name)) From 4ceb984f738107c7ca9604849fa2a91335c2ccea Mon Sep 17 00:00:00 2001 From: David Kaufman Date: Mon, 8 Jan 2024 19:44:22 -0500 Subject: [PATCH 10/11] cleanup --- src/Mod/Path/App/Area.cpp | 67 ---------------------------- src/Mod/Path/App/AreaPyImp.cpp | 2 +- src/Mod/Path/Path/Op/Area.py | 9 +--- src/Mod/Path/libarea/AreaClipper.cpp | 10 ++--- 4 files changed, 7 insertions(+), 81 deletions(-) diff --git a/src/Mod/Path/App/Area.cpp b/src/Mod/Path/App/Area.cpp index 5b13bd75cc..39fbc884af 100644 --- a/src/Mod/Path/App/Area.cpp +++ b/src/Mod/Path/App/Area.cpp @@ -508,7 +508,6 @@ private: void point(const Base::Vector3d &p) { - printf("Point? (%.0f, %.0f, %.0f) ", p.x, p.y, p.z); if (p.z <= maxZ) { if (bbox.MinX <= p.x && p.x <= bbox.MaxX && bbox.MinY <= p.y && p.y <= bbox.MaxY) { CCurve curve; @@ -516,10 +515,8 @@ private: curve.append(CVertex{1, {p.x - radius, p.y}, {p.x, p.y}}); curve.append(CVertex{1, {p.x + radius, p.y}, {p.x, p.y}}); holes.append(curve); - printf("accepted"); } } - printf("\n"); } void line(const Base::Vector3d &last, const Base::Vector3d &next) @@ -554,42 +551,20 @@ public: { (void)id; (void)pts; - // printf("g0 [ "); - //processPt(last); - // printf("] "); - //processPts(pts); - // printf("_ "); - //processPt(next); - line(last, next); - // printf("\n"); } void g1(int id, const Base::Vector3d &last, const Base::Vector3d &next, const std::deque &pts) override { (void)id; (void)pts; - // printf("g1 [ "); - //processPt(last); - // printf("] "); - //processPts(pts); - // printf("_ "); - //processPt(next); - line(last, next); - // printf("\n"); } void g23(int id, const Base::Vector3d &last, const Base::Vector3d &next, const std::deque &pts, const Base::Vector3d ¢er) override { (void)id; (void)center; - // printf("g23 [ "); - // processPt(last); - // printf("] "); - // processPts(pts); - // printf("_ "); - // processPt(next); // Compute cw vs ccw const Base::Vector3d vdirect = next - last; @@ -601,13 +576,6 @@ public: curve.append(CVertex{{last.x, last.y}}); curve.append(CVertex{ccw ? 1 : -1, {next.x, next.y}, {center.x, center.y}}); pathSegments.append(curve); - // Base::Vector3d prev = last; - // for (Base::Vector3d p : pts) { - // line(prev, p); - // prev = p; - // } - // line(prev, next); - // printf("\n"); } void g8x(int id, const Base::Vector3d &last, const Base::Vector3d &next, const std::deque &pts, @@ -633,17 +601,6 @@ public: (void)last; (void)next; } - -private: - void processPts(const std::deque &pts) { - for (std::deque::const_iterator it=pts.begin(); pts.end() != it; ++it) { - processPt(*it); - } - } - - void processPt(const Base::Vector3d &pt) { - printf("(%7.3f, %7.3f, %7.3f) ", pt.x, pt.y, pt.z); - } }; std::shared_ptr Area::getClearedArea(const Toolpath *path, double diameter, double zmax, Base::BoundBox3d bbox) { @@ -665,30 +622,13 @@ std::shared_ptr Area::getClearedArea(const Toolpath *path, double diameter // those arcs will be converted back to segments again for clipper differencing in getRestArea anyway CAreaConfig conf(params, /*no_fit_arcs*/ true); - Base::Vector3d pos = Base::Vector3d(0, 0, zmax + 1); - printf("getClearedArea(path, diameter=%g, zmax=%g:\n", diameter, zmax); - // printf("Gcode:\n"); - for (auto c : path->getCommands()) { - // printf("\t%s ", c->Name.c_str()); - for (std::map::iterator i = c->Parameters.begin(); i != c->Parameters.end(); ++i) { - // printf("%s%g ", i->first.c_str(), i->second); - } - // printf("\n"); - } - - printf("\n"); - printf("GCode walker:\n"); ClearedAreaSegmentVisitor visitor(zmax, diameter/2 + buffer, bbox); PathSegmentWalker walker(*path); walker.walk(visitor, Base::Vector3d(0, 0, zmax + 1)); - printf("\n"); - printf("\n"); - std::shared_ptr clearedArea = make_shared(¶ms); clearedArea->myTrsf = {}; const CArea ca = visitor.getClearedArea(); - printf("Cleared area segments: %ld\n", ca.m_curves.size()); if (ca.m_curves.size() > 0) { TopoDS_Shape clearedAreaShape = Area::toShape(ca, false); clearedArea->add(clearedAreaShape, OperationCompound); @@ -721,8 +661,6 @@ std::shared_ptr Area::getRestArea(std::vector> clear // transform all clearedAreas into our workplane Area clearedAreasInPlane(¶ms); clearedAreasInPlane.myArea.reset(new CArea()); - printf("getRestArea\n"); - printf("\n"); for (std::shared_ptr clearedArea : clearedAreas) { gp_Trsf trsf = clearedArea->myTrsf; trsf.Invert(); @@ -733,24 +671,20 @@ std::shared_ptr Area::getRestArea(std::vector> clear } // clearable = offset(offset(A, -dTool/2), dTool/2) - printf("Compute clearable\n"); CArea clearable(*myArea); clearable.OffsetWithClipper(-diameter/2, JoinType, EndType, params.MiterLimit, roundPrecision); clearable.OffsetWithClipper(diameter/2, JoinType, EndType, params.MiterLimit, roundPrecision); // remaining = clearable - prevCleared - printf("Compute remaining\n"); CArea remaining(clearable); remaining.Clip(toClipperOp(Area::OperationDifference), &*(clearedAreasInPlane.myArea), SubjectFill, ClipFill); // rest = intersect(clearable, offset(remaining, dTool)) // add buffer to dTool to compensate for oversizing in getClearedArea - printf("Compute rest\n"); CArea restCArea(remaining); restCArea.OffsetWithClipper(diameter + buffer, JoinType, EndType, params.MiterLimit, roundPrecision); restCArea.Clip(toClipperOp(Area::OperationIntersection), &clearable, SubjectFill, ClipFill); - printf("Convert CArea to Area (num curves: %d)\n", (int)restCArea.m_curves.size()); if(restCArea.m_curves.size() == 0) { return {}; } @@ -760,7 +694,6 @@ std::shared_ptr Area::getRestArea(std::vector> clear TopoDS_Shape restShape = Area::toShape(restCArea, false, &trsf); restArea->add(restShape, OperationCompound); - printf("return\n"); return restArea; } diff --git a/src/Mod/Path/App/AreaPyImp.cpp b/src/Mod/Path/App/AreaPyImp.cpp index bbe4182c23..bbfb1b5088 100644 --- a/src/Mod/Path/App/AreaPyImp.cpp +++ b/src/Mod/Path/App/AreaPyImp.cpp @@ -152,7 +152,7 @@ static const PyMethodDef areaOverrides[] = { { "getClearedArea",nullptr,0, "getClearedArea(path, diameter, zmax, bbox):\n" - "Gets the area cleared when a tool of the specified diameter follows the gcode represented in the path, ignoring cleared space above zmax and path segments that don't affect space within bbox.\n", + "Gets the area cleared when a tool of the specified diameter follows the gcode represented in the path, ignoring cleared space above zmax and path segments that don't affect space within the x/y space of bbox.\n", }, { "getRestArea",nullptr,0, diff --git a/src/Mod/Path/Path/Op/Area.py b/src/Mod/Path/Path/Op/Area.py index 5ec88b640f..fd831341b2 100644 --- a/src/Mod/Path/Path/Op/Area.py +++ b/src/Mod/Path/Path/Op/Area.py @@ -246,20 +246,13 @@ class ObjectOp(PathOp.ObjectOp): z = bbox.ZMin sectionClearedAreas = [] for op in self.job.Operations.Group: - print(op.Name) if self in [x.Proxy for x in [op] + op.OutListRecursive if hasattr(x, "Proxy")]: - print("found self") break if hasattr(op, "Active") and op.Active and op.Path: tool = op.Proxy.tool if hasattr(op.Proxy, "tool") else op.ToolController.Proxy.getTool(op.ToolController) diameter = tool.Diameter.getValueAs("mm") - dz = 0 if not hasattr(tool, "TipAngle") else -PathUtils.drillTipLength(tool) # for drills, dz moves to the full width part of the tool + dz = 0 if not hasattr(tool, "TipAngle") else -PathUtils.drillTipLength(tool) # for drills, dz translates to the full width part of the tool sectionClearedAreas.append(section.getClearedArea(op.Path, diameter, z+dz+0.001, bbox)) - # debugZ = -1.5 - # if debugZ -.1 < z and z < debugZ + .1: - # debugObj = obj.Document.addObject("Part::Feature", "Debug_{}_{}".format(debugZ, op.Name)) - # debugObj.Label = "Debug_{}_{}".format(debugZ, op.Label) - # debugObj.Shape = sectionClearedAreas[-1].getShape() restSection = section.getRestArea(sectionClearedAreas, self.tool.Diameter.getValueAs("mm")) if (restSection is not None): restSections.append(restSection) diff --git a/src/Mod/Path/libarea/AreaClipper.cpp b/src/Mod/Path/libarea/AreaClipper.cpp index 48f3dc93af..1ee608bbba 100644 --- a/src/Mod/Path/libarea/AreaClipper.cpp +++ b/src/Mod/Path/libarea/AreaClipper.cpp @@ -252,14 +252,14 @@ static void MakeObround(const Point &pt0, const CVertex &vt1, double radius) static void OffsetSpansWithObrounds(const CArea& area, TPolyPolygon &pp_new, double radius) { Clipper c; - c.StrictlySimple(CArea::m_clipper_simple); + c.StrictlySimple(CArea::m_clipper_simple); pp_new.clear(); for(std::list::const_iterator It = area.m_curves.begin(); It != area.m_curves.end(); It++) { - c.Clear(); - c.AddPaths(pp_new, ptSubject, true); - pp_new.clear(); + c.Clear(); + c.AddPaths(pp_new, ptSubject, true); + pp_new.clear(); pts_for_AddVertex.clear(); const CCurve& curve = *It; @@ -282,7 +282,7 @@ static void OffsetSpansWithObrounds(const CArea& area, TPolyPolygon &pp_new, dou } prev_vertex = &vertex; } - c.Execute(ctUnion, pp_new, pftNonZero, pftNonZero); + c.Execute(ctUnion, pp_new, pftNonZero, pftNonZero); } From dd5bb66cb96a044015a98b2d34e81e2a1650d841 Mon Sep 17 00:00:00 2001 From: David Kaufman Date: Thu, 11 Jan 2024 22:49:11 -0500 Subject: [PATCH 11/11] replace hard coded 1um tolerance with job.GeometryTolerance --- src/Mod/Path/Path/Op/Area.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Mod/Path/Path/Op/Area.py b/src/Mod/Path/Path/Op/Area.py index fd831341b2..5a3756cacd 100644 --- a/src/Mod/Path/Path/Op/Area.py +++ b/src/Mod/Path/Path/Op/Area.py @@ -252,7 +252,7 @@ class ObjectOp(PathOp.ObjectOp): tool = op.Proxy.tool if hasattr(op.Proxy, "tool") else op.ToolController.Proxy.getTool(op.ToolController) diameter = tool.Diameter.getValueAs("mm") dz = 0 if not hasattr(tool, "TipAngle") else -PathUtils.drillTipLength(tool) # for drills, dz translates to the full width part of the tool - sectionClearedAreas.append(section.getClearedArea(op.Path, diameter, z+dz+0.001, bbox)) + sectionClearedAreas.append(section.getClearedArea(op.Path, diameter, z+dz+self.job.GeometryTolerance.getValueAs("mm"), bbox)) restSection = section.getRestArea(sectionClearedAreas, self.tool.Diameter.getValueAs("mm")) if (restSection is not None): restSections.append(restSection)