From 73e8b5ec877b91e6659a90979e65f65f9eaec76f Mon Sep 17 00:00:00 2001 From: David Kaufman Date: Sun, 7 Jan 2024 16:53:45 -0500 Subject: [PATCH] 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]