From 3812b3d576c314f378eaafc4e8f3737f0d5c994e Mon Sep 17 00:00:00 2001 From: David Kaufman Date: Fri, 12 Dec 2025 16:20:21 -0500 Subject: [PATCH 1/9] Revert "cleanup" This reverts commit 62caf967c7d5c91322d5df4053d5b308b278a85d. --- src/Mod/CAM/Path/Op/Adaptive.py | 3 ++ src/Mod/CAM/libarea/Adaptive.cpp | 70 +++++++++++++++++++++++++++++++- 2 files changed, 72 insertions(+), 1 deletion(-) diff --git a/src/Mod/CAM/Path/Op/Adaptive.py b/src/Mod/CAM/Path/Op/Adaptive.py index 4d8fb39dce..0b1a104cc6 100644 --- a/src/Mod/CAM/Path/Op/Adaptive.py +++ b/src/Mod/CAM/Path/Op/Adaptive.py @@ -784,7 +784,10 @@ def ExecuteModelAware(op, obj): "orderCutsByRegion": obj.OrderCutsByRegion, } + import random + insideInputStateObject = { + "TODO TESTING": random.random(), "tool": op.tool.Diameter.Value, "tolerance": obj.Tolerance, "geometry": [ diff --git a/src/Mod/CAM/libarea/Adaptive.cpp b/src/Mod/CAM/libarea/Adaptive.cpp index 5a3015a190..822de43207 100644 --- a/src/Mod/CAM/libarea/Adaptive.cpp +++ b/src/Mod/CAM/libarea/Adaptive.cpp @@ -24,6 +24,7 @@ #include "Adaptive.hpp" #include +#include #include #include #include @@ -926,6 +927,7 @@ public: } inline void Start() { +#define DEV_MODE #ifdef DEV_MODE start_ticks = clock(); if (running) { @@ -1187,13 +1189,17 @@ public: return max(min(angle, MAX_ANGLE), MIN_ANGLE); } + double getRandomAngle() + { + return MIN_ANGLE + (MAX_ANGLE - MIN_ANGLE) * double(rand()) / double(RAND_MAX); + } size_t getPointCount() { return (m_min ? 1 : 0) + (m_max ? 1 : 0); } public: - // {{angle, clipper point}, error} + //{{angle, clipper point}, error} std::optional, double>> m_min; std::optional, double>> m_max; }; @@ -2768,8 +2774,30 @@ void Adaptive2d::AddPathToProgress(TPaths& progressPaths, const Path pth, Motion } } +struct nostream +{ + nostream(string a) + {} + nostream operator<<(string a) + { + return *this; + } + nostream operator<<(double a) + { + return *this; + } + nostream operator<<(IntPoint a) + { + return *this; + } +}; + void Adaptive2d::ProcessPolyNode(Paths boundPaths, Paths toolBoundPaths) { + ofstream fout("adaptive_debug.txt"); + // nostream fout("adaptive_debug.txt"); + fout << "\n" << "\n" << "----------------------" << "\n"; + fout << "Start ProcessPolyNode" << "\n"; Perf_ProcessPolyNode.Start(); current_region++; cout << "** Processing region: " << current_region << endl; @@ -2822,12 +2850,14 @@ void Adaptive2d::ProcessPolyNode(Paths boundPaths, Paths toolBoundPaths) engageBounds.push_back(p); } outsideEntry = true; + fout << "Outside entry " << entryPoint << "\n"; } else { if (!FindEntryPoint(progressPaths, toolBoundPaths, boundPaths, cleared, entryPoint, toolPos, toolDir)) { Perf_ProcessPolyNode.Stop(); return; } + fout << "Helix entry " << entryPoint << "\n"; } EngagePoint engage(engageBounds); // engage point stepping instance @@ -2884,11 +2914,13 @@ void Adaptive2d::ProcessPolyNode(Paths boundPaths, Paths toolBoundPaths) #endif ClearedArea clearedBeforePass(toolRadiusScaled); clearedBeforePass.SetClearedPaths(cleared.GetCleared()); + fout << "Tool radius scaled: " << toolRadiusScaled << "\n"; //******************************* // LOOP - PASSES //******************************* for (long pass = 0; pass < PASSES_LIMIT; pass++) { + fout << "New pass! " << pass << "\n"; if (stopProcessing) { break; } @@ -2927,6 +2959,7 @@ void Adaptive2d::ProcessPolyNode(Paths boundPaths, Paths toolBoundPaths) // LOOP - POINTS //******************************* for (long point_index = 0; point_index < POINTS_PER_PASS_LIMIT; point_index++) { + fout << "\n" << "Point " << point_index << "\n"; if (stopProcessing) { break; } @@ -2965,12 +2998,14 @@ void Adaptive2d::ProcessPolyNode(Paths boundPaths, Paths toolBoundPaths) if (stepScaled < MIN_STEP_CLIPPER) { stepScaled = long(MIN_STEP_CLIPPER); } + fout << "\tstepScaled " << stepScaled << "\n"; //***************************** // ANGLE vs AREA ITERATIONS //***************************** double predictedAngle = averageDV(angleHistory); double maxError = AREA_ERROR_FACTOR * optimalCutAreaPD; + fout << "optimal area " << optimalCutAreaPD << " maxError " << maxError << "\n"; double area = 0; double areaPD = 0; interp.clear(); @@ -2981,35 +3016,50 @@ void Adaptive2d::ProcessPolyNode(Paths boundPaths, Paths toolBoundPaths) bool pointNotInterp; for (iteration = 0; iteration < MAX_ITERATIONS; iteration++) { total_iterations++; + fout << "It " << iteration << " "; if (iteration == 0) { angle = predictedAngle; pointNotInterp = true; + fout << "case predicted "; } else if (iteration == 1) { angle = interp.MIN_ANGLE; // max engage pointNotInterp = true; + fout << "case minimum "; } else if (iteration == 2) { if (interp.bothSides()) { angle = interp.interpolateAngle(); + fout << "(" << interp.m_min->first.first << ", " << interp.m_min->second + << ") ~ (" << interp.m_max->first.first << ", " << interp.m_max->second + << ") "; pointNotInterp = false; + fout << "case interp "; } else { angle = interp.MAX_ANGLE; // min engage + fout << "case maximum "; pointNotInterp = true; } } else { angle = interp.interpolateAngle(); + fout << "(" << interp.m_min->first.first << ", " << interp.m_min->second + << ") ~ (" << interp.m_max->first.first << ", " << interp.m_max->second + << ") "; pointNotInterp = false; + fout << "case interp "; } + fout << "raw " << angle << " "; angle = interp.clampAngle(angle); + fout << "clamped " << angle << " "; newToolDir = rotate(toolDir, angle); newToolPos = IntPoint( long(toolPos.X + newToolDir.X * stepScaled), long(toolPos.Y + newToolDir.Y * stepScaled) ); + fout << "int pos " << newToolPos << " "; // Skip iteration if this IntPoint has already been processed bool intRepeat = false; @@ -3032,6 +3082,7 @@ void Adaptive2d::ProcessPolyNode(Paths boundPaths, Paths toolBoundPaths) // integers continue; } + fout << "hit integer floor" << "\n"; // exit early, selecting the better of the two adjacent integers double error; if (abs(interp.m_min->second) < abs(interp.m_max->second)) { @@ -3048,14 +3099,17 @@ void Adaptive2d::ProcessPolyNode(Paths boundPaths, Paths toolBoundPaths) area = areaPD * double(stepScaled); break; } + fout << "skip area calc " << "\n"; continue; } area = CalcCutArea(clip, toolPos, newToolPos, cleared); areaPD = area / double(stepScaled); // area per distance + fout << "addPoint " << areaPD << " " << angle << " "; double error = areaPD - targetAreaPD; interp.addPoint(error, {angle, newToolPos}, pointNotInterp); + fout << "areaPD " << areaPD << " error " << error << " "; // cout << " iter:" << iteration << " angle:" << angle << " area:" << areaPD // << " target:" << targetAreaPD << " error:" << error << " max:" << maxError // << endl; @@ -3064,14 +3118,22 @@ void Adaptive2d::ProcessPolyNode(Paths boundPaths, Paths toolBoundPaths) if (angleHistory.size() > ANGLE_HISTORY_POINTS) { angleHistory.erase(angleHistory.begin()); } + fout << "small enough" << "\n"; + break; + } + if (iteration > 5 && fabs(error - prev_error) < 0.001) { + fout << "no change" << "\n"; break; } if (iteration == MAX_ITERATIONS - 1) { + fout << "too many iterations!" << "\n"; total_exceeded++; } + fout << "\n"; prev_error = error; } Perf_PointIterations.Stop(); + fout << "Iterations: " << iteration << "\n"; recalcArea = false; // approach end boundary tangentially @@ -3090,6 +3152,7 @@ void Adaptive2d::ProcessPolyNode(Paths boundPaths, Paths toolBoundPaths) long(toolPos.Y + newToolDir.Y * stepScaled) ); recalcArea = true; + fout << "\tRewrote tooldir/toolpos for boundary approach" << "\n"; } //********************************************** @@ -3105,6 +3168,7 @@ void Adaptive2d::ProcessPolyNode(Paths boundPaths, Paths toolBoundPaths) long(toolPos.X + newToolDir.X * stepScaled), long(toolPos.Y + newToolDir.Y * stepScaled) ); + fout << "\tMoving tool back within boundary..." << "\n"; } if (rotateStep >= 180) { #ifdef DEV_MODE @@ -3120,6 +3184,7 @@ void Adaptive2d::ProcessPolyNode(Paths boundPaths, Paths toolBoundPaths) // safety condition if (area > stepScaled * optimalCutAreaPD && areaPD > 2 * optimalCutAreaPD) { over_cut_count++; + fout << "\tCut area too big!!!" << "\n"; break; } @@ -3136,6 +3201,7 @@ void Adaptive2d::ProcessPolyNode(Paths boundPaths, Paths toolBoundPaths) prevDistFromStart = distFromStart; if (area > 0.5 * MIN_CUT_AREA_FACTOR * optimalCutAreaPD) { // cut is ok - record it + fout << "\tFinal cut acceptance" << "\n"; noCutDistance = 0; if (toClearPath.empty()) { toClearPath.push_back(toolPos); @@ -3188,9 +3254,11 @@ void Adaptive2d::ProcessPolyNode(Paths boundPaths, Paths toolBoundPaths) #endif // cout<<"Break: no cut @" << point_index << endl; if (noCutDistance > stepOverScaled) { + fout << "Points: " << point_index << "\n"; break; } noCutDistance += stepScaled; + fout << "\tFailed to accept point??" << "\n"; } } /* end of points loop*/ From dbf4f2c3eabe07f81a47d5def84e968bec3eefc5 Mon Sep 17 00:00:00 2001 From: David Kaufman Date: Fri, 19 Dec 2025 16:40:09 -0500 Subject: [PATCH 2/9] WIP - rewrote area calculation using floats; it works (compared against Clipper as ground truth) - rewrote boundary approach code - rewrote min cut/min engage code --- src/Mod/CAM/libarea/Adaptive.cpp | 779 +++++++++++++++++-------------- src/Mod/CAM/libarea/Adaptive.hpp | 9 +- 2 files changed, 434 insertions(+), 354 deletions(-) diff --git a/src/Mod/CAM/libarea/Adaptive.cpp b/src/Mod/CAM/libarea/Adaptive.cpp index 822de43207..5894b0c07d 100644 --- a/src/Mod/CAM/libarea/Adaptive.cpp +++ b/src/Mod/CAM/libarea/Adaptive.cpp @@ -424,7 +424,7 @@ void CleanPath(const Path& inp, Path& outpt, double tolerance) long size = long(tmp.size()); // CleanPolygon will have empty result if all points are collinear, - // need to add first and last point to the output + // need to add first and last point to the output if (size <= 2) { outpt.push_back(inp.front()); outpt.push_back(inp.back()); @@ -495,23 +495,13 @@ bool Circle2CircleIntersect( bool Line2CircleIntersect( const IntPoint& c, double radius, - const IntPoint& p1, - const IntPoint& p2, + const DoublePoint& p1, + const DoublePoint& p2, vector& result, bool clamp = true ) { // if more intersections returned, first is closer to p1 - - // box check for performance - if (clamp) { - BoundBox cBB(c, (ClipperLib::cInt)radius + 1); // circle bound box - BoundBox sBB(p1, p2); - if (!sBB.CollidesWith(cBB)) { - return false; - } - } - double dx = double(p2.X - p1.X); double dy = double(p2.Y - p1.Y); double lcx = double(p1.X - c.X); @@ -527,16 +517,10 @@ bool Line2CircleIntersect( double t1 = (-b - sq) / (2 * a); double t2 = (-b + sq) / (2 * a); result.clear(); - if (clamp) { - if (t1 >= 0.0 && t1 <= 1.0) { - result.emplace_back(p1.X + t1 * dx, p1.Y + t1 * dy); - } - if (t2 >= 0.0 && t2 <= 1.0) { - result.emplace_back(p1.X + t2 * dx, p1.Y + t2 * dy); - } + if ((t1 >= 0.0 && t1 <= 1.0) || !clamp) { + result.emplace_back(p1.X + t1 * dx, p1.Y + t1 * dy); } - else { - result.emplace_back(p1.X + t2 * dx, p1.Y + t2 * dy); + if ((t2 >= 0.0 && t2 <= 1.0) || !clamp) { result.emplace_back(p1.X + t2 * dx, p1.Y + t2 * dy); } return !result.empty(); @@ -968,6 +952,7 @@ private: PerfCounter Perf_ProcessPolyNode("ProcessPolyNode"); PerfCounter Perf_CalcCutAreaCirc("CalcCutArea"); +PerfCounter Perf_CalcCutAreaTest("CalcCutAreaTest"); PerfCounter Perf_CalcCutAreaClip("CalcCutAreaClip"); PerfCounter Perf_NextEngagePoint("NextEngagePoint"); PerfCounter Perf_PointIterations("PointIterations"); @@ -1014,73 +999,46 @@ public: Perf_ExpandCleared.Stop(); } - // gets the path sections inside the ext. tool bounding box - Paths& GetBoundedClearedPaths(const IntPoint& toolPos) - { - BoundBox toolBB(toolPos, toolRadiusScaled); - if (!bboxPathsInvalid && clearedBBPathsInFocus.Contains(toolBB)) { - return clearedBoundedPaths; - } - ClipperLib::cInt delta = focusBBFactor1 * toolRadiusScaled; - clearedBBPathsInFocus.SetFirstPoint(IntPoint(toolPos.X - delta, toolPos.Y - delta)); - clearedBBPathsInFocus.AddPoint(IntPoint(toolPos.X + delta, toolPos.Y + delta)); - - BoundBox bb(toolPos, focusBBFactor2 * toolRadiusScaled); - clearedBoundedPaths.clear(); - for (const auto& pth : clearedPaths) { - if (pth.size() < 2) { - continue; - } - Path bPath; - size_t size = pth.size(); - for (size_t i = 0; i < size + 1; i++) { - IntPoint last = (i > 0 ? pth[i - 1] : pth.back()); - IntPoint next = i < size ? pth[i] : pth.front(); - BoundBox ptbox(last, next); - if (ptbox.CollidesWith(bb)) { - if (bPath.empty() || bPath.back() != last) { - bPath.push_back(last); - } - bPath.push_back(next); - } - else { - if (!bPath.empty()) { - clearedBoundedPaths.push_back(bPath); - bPath.clear(); - } - } - } - if (!bPath.empty()) { - clearedBoundedPaths.push_back(bPath); - bPath.clear(); - } - } - bboxPathsInvalid = false; - return clearedBoundedPaths; - } - // get cleared area/poly bounded to toolbox - Paths& GetBoundedClearedAreaClipped(const IntPoint& toolPos) + Paths& GetBoundedClearedAreaClipped(const IntPoint& toolPos, int delta) { - BoundBox toolBB(toolPos, toolRadiusScaled); + // first, attempt to serve this query from cache + BoundBox toolBB(toolPos, delta); if (!bboxClippedInvalid && clearedBBClippedInFocus.Contains(toolBB)) { return clearedBoundedClipped; } - ClipperLib::cInt delta = focusBBFactor1 * toolRadiusScaled; + + // second, check if the window needs to be recomputed + if (bboxClippedInvalid || !clearedBBWindow.Contains(toolBB)) { + const int deltaWindow = delta * clearedBoundedWindowScale; + clearedBBWindow.SetFirstPoint(IntPoint(toolPos.X - deltaWindow, toolPos.Y - deltaWindow)); + clearedBBWindow.AddPoint(IntPoint(toolPos.X + deltaWindow, toolPos.Y + deltaWindow)); + + Path bbPath; + bbPath.push_back(IntPoint(toolPos.X - deltaWindow, toolPos.Y - deltaWindow)); + bbPath.push_back(IntPoint(toolPos.X + deltaWindow, toolPos.Y - deltaWindow)); + bbPath.push_back(IntPoint(toolPos.X + deltaWindow, toolPos.Y + deltaWindow)); + bbPath.push_back(IntPoint(toolPos.X - deltaWindow, toolPos.Y + deltaWindow)); + clip.Clear(); + clip.AddPath(bbPath, PolyType::ptSubject, true); + clip.AddPaths(clearedPaths, PolyType::ptClip, true); + clip.Execute(ClipType::ctIntersection, clearedBoundedWindow); + } + + // finally, perform the query using data from the window clearedBBClippedInFocus.SetFirstPoint(IntPoint(toolPos.X - delta, toolPos.Y - delta)); clearedBBClippedInFocus.AddPoint(IntPoint(toolPos.X + delta, toolPos.Y + delta)); - // a little larger area is bounded than checked - ClipperLib::cInt delta2 = focusBBFactor2 * toolRadiusScaled; Path bbPath; - bbPath.push_back(IntPoint(toolPos.X - delta2, toolPos.Y - delta2)); - bbPath.push_back(IntPoint(toolPos.X + delta2, toolPos.Y - delta2)); - bbPath.push_back(IntPoint(toolPos.X + delta2, toolPos.Y + delta2)); - bbPath.push_back(IntPoint(toolPos.X - delta2, toolPos.Y + delta2)); + bbPath.push_back(IntPoint(toolPos.X - delta, toolPos.Y - delta)); + bbPath.push_back(IntPoint(toolPos.X + delta, toolPos.Y - delta)); + bbPath.push_back(IntPoint(toolPos.X + delta, toolPos.Y + delta)); + bbPath.push_back(IntPoint(toolPos.X - delta, toolPos.Y + delta)); clip.Clear(); clip.AddPath(bbPath, PolyType::ptSubject, true); - clip.AddPaths(clearedPaths, PolyType::ptClip, true); + clip.AddPaths(clearedBoundedWindow, PolyType::ptClip, true); clip.Execute(ClipType::ctIntersection, clearedBoundedClipped); + bboxClippedInvalid = false; return clearedBoundedClipped; } @@ -1095,18 +1053,18 @@ private: Clipper clip; ClipperOffset clipof; Paths clearedPaths; + Paths clearedBoundedWindow; Paths clearedBoundedClipped; Paths clearedBoundedPaths; ClipperLib::cInt toolRadiusScaled; + BoundBox clearedBBWindow; BoundBox clearedBBClippedInFocus; BoundBox clearedBBPathsInFocus; bool bboxClippedInvalid = false; bool bboxPathsInvalid = false; - // size of the focus BB - const ClipperLib::cInt focusBBFactor1 = 8; - const ClipperLib::cInt focusBBFactor2 = 9; + int clearedBoundedWindowScale = 10; }; //*************************************** @@ -1189,10 +1147,6 @@ public: return max(min(angle, MAX_ANGLE), MIN_ANGLE); } - double getRandomAngle() - { - return MIN_ANGLE + (MAX_ANGLE - MIN_ANGLE) * double(rand()) / double(RAND_MAX); - } size_t getPointCount() { return (m_min ? 1 : 0) + (m_max ? 1 : 0); @@ -1208,6 +1162,8 @@ public: // Engage Point //*************************************** +const IntPoint dummyPrevPoint(-1000000000, -1000000000); + class EngagePoint { public: @@ -1272,8 +1228,8 @@ public: Paths toChain = toolBoundPaths; toolBoundPaths.clear(); // if(toChain.size()>0) { - // toolBoundPaths.push_back(toChain.front()); - // toChain.erase(toChain.begin()); + // toolBoundPaths.push_back(toChain.front()); + // toChain.erase(toChain.begin()); // } while (PopPathWithClosestPoint(toChain, current, result)) { toolBoundPaths.push_back(result); @@ -1316,25 +1272,32 @@ public: int maxPases = 2 ) { + (*parent->fout) << "Finding Next Engage Point! " + << "step=" << step << " minCutArea=" << minCutArea + << " maxCutArea=" << maxCutArea << "\n"; Perf_NextEngagePoint.Start(); double prevArea = 0; // we want to make sure that we catch the point where the area is on // raising slope - IntPoint initialPoint(-1000000000, -1000000000); for (;;) { if (!moveForward(step)) { if (!nextPath()) { state.passes++; if (state.passes >= maxPases) { Perf_NextEngagePoint.Stop(); + (*parent->fout) << "Done, none found.\n"; return false; // nothing more to cut } prevArea = 0; } } IntPoint cpt = getCurrentPoint(); - double area = parent->CalcCutArea(clip, initialPoint, cpt, clearedArea); + double area = parent->CalcCutArea(clip, dummyPrevPoint, cpt, clearedArea); + (*parent->fout) << "Testint point:" + << " cpt=(" << cpt.X << "," << cpt.Y << ")" + << " area=" << area << "\n"; if (area > minCutArea && area < maxCutArea && area > prevArea) { Perf_NextEngagePoint.Stop(); + (*parent->fout) << "Done, accepted point.\n"; return true; } prevArea = area; @@ -1435,6 +1398,40 @@ private: Adaptive2d::Adaptive2d() {} +// Algorithm to compute area inside circle c2 but outside circle c1 and polygons clearedArea: +// All computations are done on doubles (at some point we can transition off of clipper and operate +// on curves!) +// +// Re-express the problem: find area inside all circles and polygons, where c1 and polygons are +// inverted +// +// 0) Extract from clearedArea a set of polygons close enough to potentially affect the bounded area +// 1) Find all x-coordinates of interest: +// a) All polygon vertices +// b) Intersection of all polygons with c1 +// c) Intersection of all polygons with c2 +// d) There are no self-intersections or intersection points with other polygons (guarantee from +// clipper), so we don't have to compute those e) Compute intersection points between c1 and c2 f) +// Add c1's and c2's vertical tangents to the list +// 2) Sort these x-coordinates. Discard all values before c2-r or after c2+r. We will consider +// ranges of x-values between these points 3) For each non-empty range in x, construct a vertical +// line through its midpoint +// Over the full (open) x-range, vertical lines cross all polygons/circles in the same order (no +// topology changes!) Also note that this line is guaranteed to not pass through any polygon +// vertex, or tangent to any circle. No funny business! a) Compute the intersection point(s) +// between the line and each polygon and circle. Keep track of which shape crossing each parameter +// came from (polygon index and edge index, or circle index and top/bottom flag) b) Sort these +// intersection on their y-coordinate. c) Loop over y-coordinates. At each, we will update state +// to account for stepping over that crossing: +// Init (i.e. y=-inf): outsideCount = 1 (outside c2 and inside all other shapes) +// 1) Identify the shape we are crossing; determine check in->out vs out->in +// 2) Update outsideCount and the list of what we're outside. +// a) If outsideCount=0, totalArea += integral(x0, x1, crossed boundary) +// b) If outsideCount was 0 and just changed to 1, totalArea -= integral(x0, x1, crossed +// boundary) +// ...careful with the signs on those integrals; TODO be sure to add area from c2 and +// subtract from other shapes +// 4) Return accumulated area double Adaptive2d::CalcCutArea( Clipper& clip, const IntPoint& c1, @@ -1450,272 +1447,291 @@ double Adaptive2d::CalcCutArea( } Perf_CalcCutAreaCirc.Start(); + Perf_CalcCutAreaTest.Start(); - /// new alg - double rsqrd = toolRadiusScaled * toolRadiusScaled; - double area = 0; - Paths interPaths; - IntPoint clp; // to hold closest point - vector inters; // to hold intersection results - BoundBox c2BB(c2, toolRadiusScaled); - BoundBox c1BB(c1, toolRadiusScaled); - Paths& clearedBounded = clearedArea.GetBoundedClearedAreaClipped(c2); + // 0) Extract from clearedArea a set of polygons close enough to potentially affect the bounded area + vector> polygons; + vector inters; // temporary, to hold intersection results + const BoundBox c2BB(c2, toolRadiusScaled); + // get curves from slightly enlarged region that will cover all points tested in this iteration + const Paths& clearedBounded = clearedArea.GetBoundedClearedAreaClipped( + c1 == dummyPrevPoint ? c2 : c1, + toolRadiusScaled + (c1 == dummyPrevPoint ? 0 : (int)dist) + 4 + ); for (const Path& path : clearedBounded) { - size_t size = path.size(); - if (size == 0) { + if (path.size() == 0) { continue; } - //** bound box check - // construct bound box for path + // bound box check BoundBox pathBB(path.front()); for (const auto& pt : path) { pathBB.AddPoint(pt); } - if (!c2BB.CollidesWith(c2)) { + if (!pathBB.CollidesWith(c2BB)) { continue; // this path cannot colide with tool } //** end of BB check - size_t curPtIndex = 0; - bool found = false; - // step 1: we find the starting point on the cleared path that is outside new tool shape - // (c2) - for (size_t i = 0; i < size; i++) { - if (DistanceSqrd(path[curPtIndex], c2) > rsqrd) { - found = true; - break; - } - curPtIndex++; - if (curPtIndex >= size) { - curPtIndex = 0; - } + vector polygon; + for (const auto p : path) { + polygon.push_back({p.X, p.Y}); } - if (!found) { - continue; // try another path + polygons.push_back(polygon); + } + + // (*fout) << "AREA[" + // << " c1=(" << c1.X << "," << c1.Y << ")" + // << " c2=(" << c2.X << "," << c2.Y << ")"; + // for (const auto poly : polygons) { + // (*fout) << "POLY["; + // for (const auto p : poly) { + // (*fout) << "(" << p.X << "," << p.Y << ")"; + // } + // (*fout) << "] "; + // } + // (*fout) << "] "; + + // 1) Find all x-coordinates of interest: + vector xs; + for (const auto polygon : polygons) { + // 1.a) All polygon vertices + for (const auto p : polygon) { + xs.push_back(p.X); } - // step 2: iterate through path from starting point and find the part of the path inside the - // c2 - size_t prevPtIndex = curPtIndex; - Path* interPath = NULL; - bool prev_inside = false; - const IntPoint* p1 = &path[prevPtIndex]; - double par; // to hold parameter output - for (size_t i = 0; i < size; i++) { - curPtIndex++; - if (curPtIndex >= size) { - curPtIndex = 0; - } - const IntPoint* p2 = &path[curPtIndex]; - BoundBox segBB(*p1, *p2); - if (!prev_inside) { // prev state: outside, find first point inside C2 - if (segBB.CollidesWith(c2BB) - && DistancePointToLineSegSquared(*p1, *p2, c2, clp, par) - <= rsqrd) { // current segment inside, start - prev_inside = true; - interPaths.push_back(Path()); - if (interPaths.size() > 1) { - break; // we will use poly clipping alg. if there are more intersecting - // paths - } - interPath = &interPaths.back(); - // current segment inside c2, prev point outside, find intersection: - if (Line2CircleIntersect(c2, toolRadiusScaled, *p1, *p2, inters)) { - interPath->push_back(IntPoint(long(inters[0].X), long(inters[0].Y))); - if (inters.size() > 1) { - interPath->push_back(IntPoint(long(inters[1].X), long(inters[1].Y))); - prev_inside = false; - } - else { - interPath->push_back(IntPoint(*p2)); - } - } - else { // no intersection - must be edge case, add p2 - interPath->push_back(IntPoint(*p2)); - } + // 1.b) Intersection of all polygons with c1 + // 1.c) Intersection of all polygons with c2 + for (int i = 0; i < polygon.size(); i++) { + const auto p0 = polygon[i]; + const auto p1 = polygon[(i + 1) % polygon.size()]; + if (Line2CircleIntersect(c1, toolRadiusScaled, p0, p1, inters)) { + for (const auto p : inters) { + xs.push_back(p.X); } } - else if (interPath != NULL) { // state: inside - if ((DistanceSqrd(c2, *p2) <= rsqrd)) { // next point still inside, add it and - // continue, no state change - interPath->push_back(IntPoint(*p2)); - } - else { // prev point inside, current point outside, find intersection - if (Line2CircleIntersect(c2, toolRadiusScaled, *p1, *p2, inters)) { - if (inters.size() > 1) { - interPath->push_back(IntPoint(long(inters[1].X), long(inters[1].Y))); - } - else { - interPath->push_back(IntPoint(long(inters[0].X), long(inters[0].Y))); - } - } - prev_inside = false; + if (Line2CircleIntersect(c2, toolRadiusScaled, p0, p1, inters)) { + for (const auto p : inters) { + xs.push_back(p.X); } } - prevPtIndex = curPtIndex; - p1 = p2; - } - if (interPaths.size() > 1) { - break; // we will use poly clipping alg. if there are more intersecting paths with the - // tool (rare case) } } + + // 1.e) Compute intersection points between c1 and c2 + { + pair res; + if (Circle2CircleIntersect(c1, c2, toolRadiusScaled, res)) { + xs.push_back(res.first.X); + xs.push_back(res.second.X); + } + } + + // 1.f) Add c1's and c2's vertical tangents to the list + xs.push_back((double)c1.X - toolRadiusScaled); + xs.push_back((double)c1.X + toolRadiusScaled); + + const double xmin = (double)c2.X - toolRadiusScaled; + const double xmax = (double)c2.X + toolRadiusScaled; + xs.push_back(xmin); + xs.push_back(xmax); + + // 2) Sort these x-coordinates. Discard all values before c2-r or after c2+r + { + vector xfilter; + for (const double x : xs) { + if (xmin <= x && x <= xmax) { + xfilter.push_back(x); + } + } + xs = xfilter; + std::sort(xs.begin(), xs.end()); + } + + const auto interpX = [](const DoublePoint p0, const DoublePoint p1, double x) { + const double interp = (x - p0.X) / (p1.X - p0.X); + const double y = p1.Y * interp + p0.Y * (1 - interp); + return y; + }; + Perf_CalcCutAreaTest.Stop(); + + // 3) For each non-empty range in x, construct a vertical line through its midpoint + const vector circles = {c2, c1}; + double area = 0; + for (int ix = 0; ix < xs.size() - 1; ix++) { + const double x0 = xs[ix]; + const double x1 = xs[ix + 1]; + if (x0 == x1) { + continue; + } + const double xtest = (x0 + x1) / 2; + + // 3.a) Compute the intersection point(s) between the line and each polygon and circle. Keep + // track of which shape crossing each parameter came from (polygon index and edge index, or + // circle index and top/bottom flag) + + // y, polygon index (or polygons.size() + circle index), edge index (or 0/1 for top/bottom half) + vector> ys; + + for (int ipolygon = 0; ipolygon < polygons.size(); ipolygon++) { + const auto polygon = polygons[ipolygon]; + for (int iedge = 0; iedge < polygon.size(); iedge++) { + const auto p0 = polygon[iedge]; + const auto p1 = polygon[(iedge + 1) % polygon.size()]; + // note: we skip if the edge is vertical, p0.X == p1.X == xtest + if (min(p0.X, p1.X) < xtest && max(p0.X, p1.X) > xtest) { + const double y = interpX(p0, p1, xtest); + ys.push_back({y, ipolygon, iedge}); + } + } + } + + for (int icircle = 0; icircle < circles.size(); icircle++) { + const DoublePoint c = circles[icircle]; + const double dx = abs(xtest - c.X); + if (dx < toolRadiusScaled) { // skip tangent; xtest can't be a tangent anyway + const double dy = sqrt(toolRadiusScaled * toolRadiusScaled - dx * dx); + ys.push_back({c.Y + dy, polygons.size() + icircle, 0}); + ys.push_back({c.Y - dy, polygons.size() + icircle, 1}); + } + } + + // 3.b) Sort these intersection on their y-coordinate. + std::sort( + ys.begin(), + ys.end(), + [](std::tuple a, std::tuple b) { + return std::get<0>(a) < std::get<0>(b); + } + ); + + // 3.c) Loop over y-coordinates. At each, we will update state to account for stepping over + // that crossing: + // Init (i.e. y=-inf): outsideCount = 1 (outside c2 and inside all other shapes) + std::vector outside; + for (int i = 0; i < polygons.size() + circles.size(); i++) { + outside.push_back(i == (polygons.size())); // poly_0, ..., poly_n-1, c2, c1 + } + int outsideCount = 1; + for (int iy = 0; iy < ys.size(); iy++) { + const int ishape = std::get<1>(ys[iy]); + const int ipart = std::get<2>(ys[iy]); + + const bool prevOutside = outside[ishape]; + const int prevCount = outsideCount; + outside[ishape] = !outside[ishape]; + outsideCount += prevOutside ? -1 : 1; + + // Sign + // We want to compute integral(exitY - entranceY) + // We do this in two steps: -integral(entranceY - 0) + integral(exitY - 0) + const double entranceExitSign = prevOutside ? -1 : 1; + + if (outsideCount == 0 || prevCount == 0) { + if (ishape < polygons.size()) { + // crossed a polygon + const auto polygon = polygons[ishape]; + const auto p0 = polygon[ipart]; + const auto p1 = polygon[(ipart + 1) % polygon.size()]; + const auto y0 = interpX(p0, p1, x0); + const auto y1 = interpX(p0, p1, x1); + const double newArea = (y0 + y1) / 2 * (x1 - x0); + area += entranceExitSign * newArea; + //(*fout) << "Pgon[eeSign=" << entranceExitSign + // << " x0=(" << x0 << " x1=" << x1 << ")" + // << " p0=(" << p0.X << "," << p0.Y << ")" + // << " p1=(" << p1.X << "," << p1.Y << ")" + // << " newArea(w/o sign)=" << newArea + // << " totalArea=" << area << "] "; + } + else { + // crossed a circle + const auto c = circles[ishape - polygons.size()]; + const double circleSign = ipart == 0 ? 1 : -1; + + // first, compute area of sector - area of triangle = area of segment + const auto clamp = [](double a) { + return max(-1.0, min(1.0, a)); + }; + // clamp is required only because of floating point rounding errors + const double phi0 = acos(clamp((x0 - c.X) / toolRadiusScaled)) * circleSign; + const double phi1 = acos(clamp((x1 - c.X) / toolRadiusScaled)) * circleSign; + const double areaSector = toolRadiusScaled * toolRadiusScaled / 2 + * abs(phi1 - phi0); + + const double y0 = c.Y + + circleSign + * sqrt(toolRadiusScaled * toolRadiusScaled - (x0 - c.X) * (x0 - c.X)); + const double y1 = c.Y + + circleSign + * sqrt(toolRadiusScaled * toolRadiusScaled - (x1 - c.X) * (x1 - c.X)); + const double tbase = sqrt((x1 - x0) * (x1 - x0) + (y1 - y0) * (y1 - y0)); + const double tmidx = (x0 + x1) / 2; + const double tmidy = (y0 + y1) / 2; + const double th = sqrt( + (tmidx - c.X) * (tmidx - c.X) + (tmidy - c.Y) * (tmidy - c.Y) + ); + const double areaTriangle = tbase * th / 2; + const double areaSegment = areaSector - areaTriangle; + + // then add on trapezoid between the segment and 0 + // the sign of the segment area is negative for bottom half of the circle, + // positive for top half + const double areaTrapezoid = (x1 - x0) * (y0 + y1) / 2; + const double newArea = circleSign * areaSegment + areaTrapezoid; + area += entranceExitSign * newArea; + //(*fout) << "Circle[eeSign=" << entranceExitSign + // << " x0=(" << x0 << " x1=" << x1 << ")" + // << " c=(" << c.X << "," << c.Y << ")" + // << " circleSign=" << circleSign + // << " areaSector=" << areaSector + // << " areaTriangle=" << areaTriangle + // << " areaSegment=" << areaSegment + // << " areaTrapezoid=" << areaTrapezoid + // << " newArea(w/o sign)=" << newArea + // << " totalArea=" << area << "] "; + } + } + } + } + + // TODO temporary: compute area using clipper, and print both my area and clipper's area. + // Return... clipper's, for now Perf_CalcCutAreaCirc.Stop(); - if (interPaths.size() == 1 && interPaths.front().size() > 1) { - Perf_CalcCutAreaCirc.Start(); - Path* interPath = &interPaths.front(); - // interPath - now contains the part of cleared path inside the C2 - size_t ipc2_size = interPath->size(); - const IntPoint& fpc2 = interPath->front(); // first point - const IntPoint& lpc2 = interPath->back(); // last point - // path length - double interPathLen = 0; - for (size_t j = 1; j < ipc2_size; j++) { - interPathLen += sqrt(DistanceSqrd(interPath->at(j - 1), interPath->at(j))); - } + double clipperArea = 0; + // { + // Perf_CalcCutAreaClip.Start(); + // // old way of calculating cut area based on polygon clipping + // // used in case when there are multiple intersections of tool with cleared poly (very rare + // // case, but important) + // // 1. find difference between old and new tool shape + // Path oldTool; + // Path newTool; + // TranslatePath(toolGeometry, oldTool, c1); + // TranslatePath(toolGeometry, newTool, c2); + // clip.Clear(); + // clip.AddPath(newTool, PolyType::ptSubject, true); + // clip.AddPath(oldTool, PolyType::ptClip, true); + // Paths toolDiff; + // clip.Execute(ClipType::ctDifference, toolDiff); - Paths inPaths; - inPaths.reserve(200); - inPaths.push_back(*interPath); - Path pthToSubtract; - pthToSubtract.push_back(fpc2); + // // 2. difference to cleared + // clip.Clear(); + // clip.AddPaths(toolDiff, PolyType::ptSubject, true); + // clip.AddPaths(clearedBounded, PolyType::ptClip, true); + // Paths cutAreaPoly; + // clip.Execute(ClipType::ctDifference, cutAreaPoly); - double fi1 = atan2(fpc2.Y - c2.Y, fpc2.X - c2.X); - double fi2 = atan2(lpc2.Y - c2.Y, lpc2.X - c2.X); - double minFi = fi1; - double maxFi = fi2; - if (maxFi < minFi) { - maxFi += 2 * std::numbers::pi; - } + // // calculate resulting area + // for (Path& path : cutAreaPoly) { + // clipperArea += fabs(Area(path)); + // } + // Perf_CalcCutAreaClip.Stop(); + // } + (*fout) << "Area=" << area << " clipperArea=" << clipperArea << " " + << int(abs(clipperArea - area) / area * 100) << "%" << endl; - if (preventConventional && interPathLen >= MIN_STEP_CLIPPER) { - // detect conventional mode cut - we want only climb mode - IntPoint midPoint( - long(c2.X + toolRadiusScaled * cos(0.5 * (maxFi + minFi))), - long(c2.Y + toolRadiusScaled * sin(0.5 * (maxFi + minFi))) - ); - if (PointSideOfLine(c1, c2, midPoint) < 0) { - area = __DBL_MAX__; - Perf_CalcCutAreaCirc.Stop(); - // #ifdef DEV_MODE - // cout << "Break: @(" << double(c2.X)/scaleFactor << "," << - // double(c2.Y)/scaleFactor << ") conventional mode" << endl; #endif - return area; - } - } - - double scanDistance = 2.5 * toolRadiusScaled; - // stepping through path discretized to stepDistance - double stepDistance = min(double(MIN_STEP_CLIPPER), interPathLen / 24) + 1; - const IntPoint* prevPt = &interPath->front(); - double distance = 0; - for (size_t j = 1; j < ipc2_size; j++) { - const IntPoint* cpt = &interPath->at(j); - double segLen = sqrt(DistanceSqrd(*cpt, *prevPt)); - if (segLen < NTOL) { - continue; // skip point - segment too short - } - for (double pos_unclamped = 0.0; pos_unclamped < segLen + stepDistance; - pos_unclamped += stepDistance) { - double pos = pos_unclamped; - if (pos > segLen) { - distance += stepDistance - (pos - segLen); - pos = segLen; // make sure we get exact end point - } - else { - distance += stepDistance; - } - double dx = double(cpt->X - prevPt->X); - double dy = double(cpt->Y - prevPt->Y); - IntPoint segPoint( - long(prevPt->X + dx * pos / segLen), - long(prevPt->Y + dy * pos / segLen) - ); - IntPoint scanPoint( - long(c2.X + scanDistance * cos(minFi + distance * (maxFi - minFi) / interPathLen)), - long(c2.Y + scanDistance * sin(minFi + distance * (maxFi - minFi) / interPathLen)) - ); - - IntPoint intersC2(segPoint.X, segPoint.Y); - IntPoint intersC1(segPoint.X, segPoint.Y); - - // there should be intersection with C2 - if (Line2CircleIntersect(c2, toolRadiusScaled, segPoint, scanPoint, inters)) { - if (inters.size() > 1) { - intersC2.X = long(inters[1].X); - intersC2.Y = long(inters[1].Y); - } - else { - intersC2.X = long(inters[0].X); - intersC2.Y = long(inters[0].Y); - } - } - else { - pthToSubtract.push_back(segPoint); - } - - if (Line2CircleIntersect(c1, toolRadiusScaled, segPoint, scanPoint, inters)) { - if (inters.size() > 1) { - intersC1.X = long(inters[1].X); - intersC1.Y = long(inters[1].Y); - } - else { - intersC1.X = long(inters[0].X); - intersC1.Y = long(inters[0].Y); - } - if (DistanceSqrd(segPoint, intersC2) < DistanceSqrd(segPoint, intersC1)) { - pthToSubtract.push_back(intersC2); - } - else { - pthToSubtract.push_back(intersC1); - } - } - else { // add the segpoint if no intersection with C1 - pthToSubtract.push_back(segPoint); - } - } - prevPt = cpt; - } - - pthToSubtract.push_back(lpc2); // add last point - pthToSubtract.push_back(c2); - - double segArea = Area(pthToSubtract); - double A = (maxFi - minFi) * rsqrd / 2; // sector area - area += A - fabs(segArea); - Perf_CalcCutAreaCirc.Stop(); - } - else if (interPaths.size() > 1) { - Perf_CalcCutAreaClip.Start(); - // old way of calculating cut area based on polygon clipping - // used in case when there are multiple intersections of tool with cleared poly (very rare - // case, but important) - // 1. find difference between old and new tool shape - Path oldTool; - Path newTool; - TranslatePath(toolGeometry, oldTool, c1); - TranslatePath(toolGeometry, newTool, c2); - clip.Clear(); - clip.AddPath(newTool, PolyType::ptSubject, true); - clip.AddPath(oldTool, PolyType::ptClip, true); - Paths toolDiff; - clip.Execute(ClipType::ctDifference, toolDiff); - - // 2. difference to cleared - clip.Clear(); - clip.AddPaths(toolDiff, PolyType::ptSubject, true); - clip.AddPaths(clearedBounded, PolyType::ptClip, true); - Paths cutAreaPoly; - clip.Execute(ClipType::ctDifference, cutAreaPoly); - - // calculate resulting area - area = 0; - for (Path& path : cutAreaPoly) { - area += fabs(Area(path)); - } - Perf_CalcCutAreaClip.Stop(); - } + // return clipperArea; return area; } @@ -1765,7 +1781,8 @@ std::list Adaptive2d::Execute( tolerance = min(tolerance, 1.0); // 1/"tolerance" = number of min-size adaptive steps per stepover - scaleFactor = MIN_STEP_CLIPPER / tolerance / min(1.0, stepOverFactor * toolDiameter); + scaleFactor = MIN_STEP_CLIPPER / tolerance / min(1., stepOverFactor * toolDiameter); + // scaleFactor = MIN_STEP_CLIPPER / tolerance / (stepOverFactor * toolDiameter); current_region = 0; cout << "Tool Diameter: " << toolDiameter << endl; @@ -1858,7 +1875,7 @@ std::list Adaptive2d::Execute( // CleanPolygons(stockInputPaths,0.707); //*************************************** - // Resolve hierarchy and run processing + // Resolve hierarchy and run processing //*************************************** double cornerRoundingOffset = 0.15 * toolRadiusScaled / 2; if (opType == OperationType::otClearingInside || opType == OperationType::otClearingOutside) { @@ -2795,6 +2812,7 @@ struct nostream void Adaptive2d::ProcessPolyNode(Paths boundPaths, Paths toolBoundPaths) { ofstream fout("adaptive_debug.txt"); + this->fout = &fout; // nostream fout("adaptive_debug.txt"); fout << "\n" << "\n" << "----------------------" << "\n"; fout << "Start ProcessPolyNode" << "\n"; @@ -2915,6 +2933,22 @@ void Adaptive2d::ProcessPolyNode(Paths boundPaths, Paths toolBoundPaths) ClearedArea clearedBeforePass(toolRadiusScaled); clearedBeforePass.SetClearedPaths(cleared.GetCleared()); fout << "Tool radius scaled: " << toolRadiusScaled << "\n"; + fout << "toolBoundPaths:"; + for (auto path : toolBoundPaths) { + fout << " ["; + for (auto p : path) { + fout << "(" << p.X << "," << p.Y << ")_"; + } + fout << "]"; + } + fout << "\n"; + + // This constant is chosen so that when cutting a small strip (i.e. at the + // end of helixing out), the strip size at which the cut is too small to + // continue is _also_ too small to be worth starting a new engagement + // elsewhere in the strip + const double CORRECT_MIN_CUT_VS_ENGAGE = MIN_STEP_CLIPPER * 1. + / toolRadiusScaled; // tolerance * stepOverFactor; //******************************* // LOOP - PASSES @@ -3141,18 +3175,51 @@ void Adaptive2d::ProcessPolyNode(Paths boundPaths, Paths toolBoundPaths) if (relDistToBoundary <= 1.0 && passLength > 2 * stepOverFactor && distanceToEngage > 2 * stepOverScaled && distBoundaryPointToEngage > 2 * stepOverScaled) { - double wb = 1 - relDistToBoundary; - newToolDir = DoublePoint( - newToolDir.X + wb * boundaryDir.X, - newToolDir.Y + wb * boundaryDir.Y - ); - NormalizeV(newToolDir); - newToolPos = IntPoint( - long(toolPos.X + newToolDir.X * stepScaled), - long(toolPos.Y + newToolDir.Y * stepScaled) - ); - recalcArea = true; - fout << "\tRewrote tooldir/toolpos for boundary approach" << "\n"; + // double wb = 1 - relDistToBoundary; + // newToolDir = DoublePoint( + // newToolDir.X + wb * boundaryDir.X, + // newToolDir.Y + wb * boundaryDir.Y + // ); + // NormalizeV(newToolDir); + // newToolPos = IntPoint( + // long(toolPos.X + newToolDir.X * stepScaled), + // long(toolPos.Y + newToolDir.Y * stepScaled) + // ); + // recalcArea = true; + // fout << "\tRewrote tooldir/toolpos for boundary approach" + // << "(" << newToolPos.X << ", " << newToolPos.Y << ")" << "\n"; + + // 10 degrees per stepOver? idk, let's try it + double maxAngleToBoundary = 10 * std::numbers::pi / 180 + * max(1., distanceToBoundary / stepOverScaled); + + // compute current approach angle + double cosAngle = newToolDir.X * boundaryDir.X + newToolDir.Y * boundaryDir.Y; + DoublePoint bdir = boundaryDir; + if (cosAngle < 0) { + // approaching the edge backwards: recompute for flipped boundary edge + bdir = {-bdir.X, -bdir.Y}; + cosAngle = -cosAngle; + } + double angle = acos(min(1., max(0., cosAngle))); + if (abs(angle) > maxAngleToBoundary) { + double sign = bdir.X * newToolDir.Y - bdir.Y * newToolDir.X > 0 ? 1 : -1; + double desiredAngle = maxAngleToBoundary * sign; + newToolDir = rotate(bdir, desiredAngle); + NormalizeV(newToolDir); + newToolPos = IntPoint( + long(toolPos.X + newToolDir.X * stepScaled), + long(toolPos.Y + newToolDir.Y * stepScaled) + ); + recalcArea = true; + fout << "\tRewrote tooldir/toolpos for boundary approach" + << " a=" << angle << " maxA=" << maxAngleToBoundary << " (" << newToolPos.X + << ", " << newToolPos.Y << ")" << "\n"; + } + else { + fout << "\tRewrote tooldir/toolpos for boundary approach BUT NOT ACTUALLY" + << "\n"; + } } //********************************************** @@ -3168,7 +3235,8 @@ void Adaptive2d::ProcessPolyNode(Paths boundPaths, Paths toolBoundPaths) long(toolPos.X + newToolDir.X * stepScaled), long(toolPos.Y + newToolDir.Y * stepScaled) ); - fout << "\tMoving tool back within boundary..." << "\n"; + fout << "\tMoving tool back within boundary..." + << "(" << newToolPos.X << ", " << newToolPos.Y << ")" << "\n"; } if (rotateStep >= 180) { #ifdef DEV_MODE @@ -3179,6 +3247,7 @@ void Adaptive2d::ProcessPolyNode(Paths boundPaths, Paths toolBoundPaths) if (recalcArea) { area = CalcCutArea(clip, toolPos, newToolPos, cleared); + fout << "\tRecalc area: " << area << "\n"; } // safety condition @@ -3200,8 +3269,9 @@ void Adaptive2d::ProcessPolyNode(Paths boundPaths, Paths toolBoundPaths) prevDistTrend = distanceTrend; prevDistFromStart = distFromStart; - if (area > 0.5 * MIN_CUT_AREA_FACTOR * optimalCutAreaPD) { // cut is ok - record it - fout << "\tFinal cut acceptance" << "\n"; + if (area > 0 * 0.5 * MIN_CUT_AREA_FACTOR * optimalCutAreaPD) { // cut is ok - record it + fout << "\tFinal cut acceptance (" << newToolPos.X << "," << newToolPos.Y << ")" + << "\n"; noCutDistance = 0; if (toClearPath.empty()) { toClearPath.push_back(toolPos); @@ -3247,8 +3317,8 @@ void Adaptive2d::ProcessPolyNode(Paths boundPaths, Paths toolBoundPaths) else { #ifdef DEV_MODE // if(point_index==0) { - // engage_no_cut_count++; - // cout<<"Break:no cut #" << engage_no_cut_count << ", bad engage, pass:" << pass + // engage_no_cut_count++; + // cout<<"Break:no cut #" << engage_no_cut_count << ", bad engage, pass:" << pass // << " over_cut_count:" << over_cut_count << endl; // } #endif @@ -3266,7 +3336,8 @@ void Adaptive2d::ProcessPolyNode(Paths boundPaths, Paths toolBoundPaths) cleared.ExpandCleared(toClearPath); toClearPath.clear(); } - if (cumulativeCutArea > MIN_CUT_AREA_FACTOR * optimalCutAreaPD) { + const double minArea = MIN_CUT_AREA_FACTOR * optimalCutAreaPD; + if (cumulativeCutArea > minArea) { Path cleaned; CleanPath(passToolPath, cleaned, CLEAN_PATH_TOLERANCE); total_output_points += long(cleaned.size()); @@ -3274,8 +3345,12 @@ void Adaptive2d::ProcessPolyNode(Paths boundPaths, Paths toolBoundPaths) CheckReportProgress(progressPaths); bad_engage_count = 0; engage.ResetPasses(); + fout << "Accepted pass, area " << cumulativeCutArea << " more than minimum " << minArea + << "\n\n"; } else { + fout << "Rejected pass, too little area " << cumulativeCutArea << " < " << minArea + << "\n\n"; bad_engage_count++; } @@ -3289,14 +3364,15 @@ void Adaptive2d::ProcessPolyNode(Paths boundPaths, Paths toolBoundPaths) engage.moveToClosestPoint(newToolPos, stepScaled + 1); firstEngagePoint = false; } - else { + + { double moveDistance = ENGAGE_SCAN_DISTANCE_FACTOR * stepOverScaled * refinement_factor; if (!engage.nextEngagePoint( this, cleared, moveDistance, - ENGAGE_AREA_THR_FACTOR * optimalCutAreaPD, + ENGAGE_AREA_THR_FACTOR * optimalCutAreaPD / CORRECT_MIN_CUT_VS_ENGAGE, 4 * referenceCutArea * stepOverFactor )) { // check if there are any uncleared area left @@ -3334,7 +3410,7 @@ void Adaptive2d::ProcessPolyNode(Paths boundPaths, Paths toolBoundPaths) this, cleared, moveDistance, - ENGAGE_AREA_THR_FACTOR * optimalCutAreaPD, + ENGAGE_AREA_THR_FACTOR * optimalCutAreaPD / CORRECT_MIN_CUT_VS_ENGAGE, 4 * referenceCutArea * stepOverFactor )) { break; @@ -3446,6 +3522,7 @@ void Adaptive2d::ProcessPolyNode(Paths boundPaths, Paths toolBoundPaths) Perf_ProcessPolyNode.DumpResults(); Perf_PointIterations.DumpResults(); Perf_CalcCutAreaCirc.DumpResults(); + Perf_CalcCutAreaTest.DumpResults(); Perf_CalcCutAreaClip.DumpResults(); Perf_NextEngagePoint.DumpResults(); Perf_ExpandCleared.DumpResults(); diff --git a/src/Mod/CAM/libarea/Adaptive.hpp b/src/Mod/CAM/libarea/Adaptive.hpp index 03b63ad270..6c2f5490fe 100644 --- a/src/Mod/CAM/libarea/Adaptive.hpp +++ b/src/Mod/CAM/libarea/Adaptive.hpp @@ -26,6 +26,7 @@ #include #include #include +#include #ifndef ADAPTIVE_HPP # define ADAPTIVE_HPP @@ -199,8 +200,10 @@ private: void AddPathToProgress(TPaths& progressPaths, const Path pth, MotionType mt = MotionType::mtCutting); void ApplyStockToLeave(Paths& inputPaths); + std::ofstream* fout; + private: // constants for fine tuning - const double MIN_STEP_CLIPPER = 16.0; + const double MIN_STEP_CLIPPER = 16.0 * 3; const int MAX_ITERATIONS = 10; const double AREA_ERROR_FACTOR = 0.05; /* how precise to match the cut area to optimal, reasonable value: 0.05 = 5%*/ @@ -208,9 +211,9 @@ private: // constants for fine tuning const int DIRECTION_SMOOTHING_BUFLEN = 3; // gyro points - used for angle smoothing - const double MIN_CUT_AREA_FACTOR = 0.1 + const double MIN_CUT_AREA_FACTOR = 0.4 * 16; // used for filtering out of insignificant cuts (should be < ENGAGE_AREA_THR_FACTOR) - const double ENGAGE_AREA_THR_FACTOR = 0.5 * 16; // influences minimal engage area + const double ENGAGE_AREA_THR_FACTOR = .3; // 0.5 * 16; // influences minimal engage area const double ENGAGE_SCAN_DISTANCE_FACTOR = 0.2; // influences the engage scan/stepping distance const double CLEAN_PATH_TOLERANCE = 1.41; // should be >1 From 3c74c995f03634d2e01434626a189fd4b7a1cb7f Mon Sep 17 00:00:00 2001 From: David Kaufman Date: Fri, 19 Dec 2025 20:28:20 -0500 Subject: [PATCH 3/9] WIP - starts finishing pass farther down, to avoid looping back for it This now has all the features to address #19768, but I think I want to pick out just what is required for that PR --- src/Mod/CAM/libarea/Adaptive.cpp | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/Mod/CAM/libarea/Adaptive.cpp b/src/Mod/CAM/libarea/Adaptive.cpp index 5894b0c07d..dfd8a70837 100644 --- a/src/Mod/CAM/libarea/Adaptive.cpp +++ b/src/Mod/CAM/libarea/Adaptive.cpp @@ -762,7 +762,8 @@ bool PopPathWithClosestPoint( start with closest point */ , IntPoint p1, - Path& result + Path& result, + double extraDistanceAround = 0 ) { @@ -785,14 +786,18 @@ bool PopPathWithClosestPoint( } } + Path& closestPath = paths.at(closestPathIndex); + while (extraDistanceAround > 0) { + long nexti = (closestPointIndex + 1) % closestPath.size(); + extraDistanceAround -= sqrt(DistanceSqrd(closestPath[closestPointIndex], closestPath[nexti])); + closestPointIndex = nexti; + } + result.clear(); // make new path starting with that point - Path& closestPath = paths.at(closestPathIndex); for (size_t i = 0; i < closestPath.size(); i++) { long index = closestPointIndex + long(i); - if (index >= long(closestPath.size())) { - index -= long(closestPath.size()); - } + index = index % closestPath.size(); result.push_back(closestPath.at(index)); } // remove the closest path @@ -3438,7 +3443,8 @@ void Adaptive2d::ProcessPolyNode(Paths boundPaths, Paths toolBoundPaths) Path finShiftedPath; bool allCutsAllowed = true; - while (!stopProcessing && PopPathWithClosestPoint(finishingPaths, lastPoint, finShiftedPath)) { + while (!stopProcessing + && PopPathWithClosestPoint(finishingPaths, lastPoint, finShiftedPath, stepOverScaled)) { if (finShiftedPath.empty()) { continue; } From 5cabee2b939970c1b3a7c3e644dd2bdba6caf6e3 Mon Sep 17 00:00:00 2001 From: David Kaufman Date: Tue, 23 Dec 2025 15:46:05 -0500 Subject: [PATCH 4/9] clean up code --- src/Mod/CAM/libarea/Adaptive.cpp | 78 ++++++++++---------------------- src/Mod/CAM/libarea/Adaptive.hpp | 5 +- 2 files changed, 27 insertions(+), 56 deletions(-) diff --git a/src/Mod/CAM/libarea/Adaptive.cpp b/src/Mod/CAM/libarea/Adaptive.cpp index dfd8a70837..20271898f6 100644 --- a/src/Mod/CAM/libarea/Adaptive.cpp +++ b/src/Mod/CAM/libarea/Adaptive.cpp @@ -424,7 +424,7 @@ void CleanPath(const Path& inp, Path& outpt, double tolerance) long size = long(tmp.size()); // CleanPolygon will have empty result if all points are collinear, - // need to add first and last point to the output + // need to add first and last point to the output if (size <= 2) { outpt.push_back(inp.front()); outpt.push_back(inp.back()); @@ -957,7 +957,6 @@ private: PerfCounter Perf_ProcessPolyNode("ProcessPolyNode"); PerfCounter Perf_CalcCutAreaCirc("CalcCutArea"); -PerfCounter Perf_CalcCutAreaTest("CalcCutAreaTest"); PerfCounter Perf_CalcCutAreaClip("CalcCutAreaClip"); PerfCounter Perf_NextEngagePoint("NextEngagePoint"); PerfCounter Perf_PointIterations("PointIterations"); @@ -1158,7 +1157,7 @@ public: } public: - //{{angle, clipper point}, error} + // {{angle, clipper point}, error} std::optional, double>> m_min; std::optional, double>> m_max; }; @@ -1167,8 +1166,6 @@ public: // Engage Point //*************************************** -const IntPoint dummyPrevPoint(-1000000000, -1000000000); - class EngagePoint { public: @@ -1233,8 +1230,8 @@ public: Paths toChain = toolBoundPaths; toolBoundPaths.clear(); // if(toChain.size()>0) { - // toolBoundPaths.push_back(toChain.front()); - // toChain.erase(toChain.begin()); + // toolBoundPaths.push_back(toChain.front()); + // toChain.erase(toChain.begin()); // } while (PopPathWithClosestPoint(toChain, current, result)) { toolBoundPaths.push_back(result); @@ -1283,6 +1280,7 @@ public: Perf_NextEngagePoint.Start(); double prevArea = 0; // we want to make sure that we catch the point where the area is on // raising slope + const IntPoint dummyInitialPoint(-1000000000, -1000000000); for (;;) { if (!moveForward(step)) { if (!nextPath()) { @@ -1296,7 +1294,7 @@ public: } } IntPoint cpt = getCurrentPoint(); - double area = parent->CalcCutArea(clip, dummyPrevPoint, cpt, clearedArea); + double area = parent->CalcCutArea(clip, dummyInitialPoint, cpt, clearedArea); (*parent->fout) << "Testint point:" << " cpt=(" << cpt.X << "," << cpt.Y << ")" << " area=" << area << "\n"; @@ -1446,23 +1444,24 @@ double Adaptive2d::CalcCutArea( ) { - double dist = DistanceSqrd(c1, c2); + double dist = sqrt(DistanceSqrd(c1, c2)); if (dist < NTOL) { return 0; } Perf_CalcCutAreaCirc.Start(); - Perf_CalcCutAreaTest.Start(); // 0) Extract from clearedArea a set of polygons close enough to potentially affect the bounded area vector> polygons; vector inters; // temporary, to hold intersection results const BoundBox c2BB(c2, toolRadiusScaled); // get curves from slightly enlarged region that will cover all points tested in this iteration + const bool useC2 = dist > 2 * toolRadiusScaled; const Paths& clearedBounded = clearedArea.GetBoundedClearedAreaClipped( - c1 == dummyPrevPoint ? c2 : c1, - toolRadiusScaled + (c1 == dummyPrevPoint ? 0 : (int)dist) + 4 + useC2 ? c2 : c1, + toolRadiusScaled + (useC2 ? 0 : (int)dist) + 4 ); + for (const Path& path : clearedBounded) { if (path.size() == 0) { continue; @@ -1476,7 +1475,6 @@ double Adaptive2d::CalcCutArea( if (!pathBB.CollidesWith(c2BB)) { continue; // this path cannot colide with tool } - //** end of BB check vector polygon; for (const auto p : path) { @@ -1558,7 +1556,6 @@ double Adaptive2d::CalcCutArea( const double y = p1.Y * interp + p0.Y * (1 - interp); return y; }; - Perf_CalcCutAreaTest.Stop(); // 3) For each non-empty range in x, construct a vertical line through its midpoint const vector circles = {c2, c1}; @@ -1786,8 +1783,7 @@ std::list Adaptive2d::Execute( tolerance = min(tolerance, 1.0); // 1/"tolerance" = number of min-size adaptive steps per stepover - scaleFactor = MIN_STEP_CLIPPER / tolerance / min(1., stepOverFactor * toolDiameter); - // scaleFactor = MIN_STEP_CLIPPER / tolerance / (stepOverFactor * toolDiameter); + scaleFactor = MIN_STEP_CLIPPER / tolerance / min(1.0, stepOverFactor * toolDiameter); current_region = 0; cout << "Tool Diameter: " << toolDiameter << endl; @@ -1880,7 +1876,7 @@ std::list Adaptive2d::Execute( // CleanPolygons(stockInputPaths,0.707); //*************************************** - // Resolve hierarchy and run processing + // Resolve hierarchy and run processing //*************************************** double cornerRoundingOffset = 0.15 * toolRadiusScaled / 2; if (opType == OperationType::otClearingInside || opType == OperationType::otClearingOutside) { @@ -2948,13 +2944,6 @@ void Adaptive2d::ProcessPolyNode(Paths boundPaths, Paths toolBoundPaths) } fout << "\n"; - // This constant is chosen so that when cutting a small strip (i.e. at the - // end of helixing out), the strip size at which the cut is too small to - // continue is _also_ too small to be worth starting a new engagement - // elsewhere in the strip - const double CORRECT_MIN_CUT_VS_ENGAGE = MIN_STEP_CLIPPER * 1. - / toolRadiusScaled; // tolerance * stepOverFactor; - //******************************* // LOOP - PASSES //******************************* @@ -3180,19 +3169,6 @@ void Adaptive2d::ProcessPolyNode(Paths boundPaths, Paths toolBoundPaths) if (relDistToBoundary <= 1.0 && passLength > 2 * stepOverFactor && distanceToEngage > 2 * stepOverScaled && distBoundaryPointToEngage > 2 * stepOverScaled) { - // double wb = 1 - relDistToBoundary; - // newToolDir = DoublePoint( - // newToolDir.X + wb * boundaryDir.X, - // newToolDir.Y + wb * boundaryDir.Y - // ); - // NormalizeV(newToolDir); - // newToolPos = IntPoint( - // long(toolPos.X + newToolDir.X * stepScaled), - // long(toolPos.Y + newToolDir.Y * stepScaled) - // ); - // recalcArea = true; - // fout << "\tRewrote tooldir/toolpos for boundary approach" - // << "(" << newToolPos.X << ", " << newToolPos.Y << ")" << "\n"; // 10 degrees per stepOver? idk, let's try it double maxAngleToBoundary = 10 * std::numbers::pi / 180 @@ -3222,8 +3198,7 @@ void Adaptive2d::ProcessPolyNode(Paths boundPaths, Paths toolBoundPaths) << ", " << newToolPos.Y << ")" << "\n"; } else { - fout << "\tRewrote tooldir/toolpos for boundary approach BUT NOT ACTUALLY" - << "\n"; + fout << "\tRewrote tooldir/toolpos for boundary approach BUT NOT ACTUALLY \n"; } } @@ -3274,9 +3249,8 @@ void Adaptive2d::ProcessPolyNode(Paths boundPaths, Paths toolBoundPaths) prevDistTrend = distanceTrend; prevDistFromStart = distFromStart; - if (area > 0 * 0.5 * MIN_CUT_AREA_FACTOR * optimalCutAreaPD) { // cut is ok - record it - fout << "\tFinal cut acceptance (" << newToolPos.X << "," << newToolPos.Y << ")" - << "\n"; + if (area > 0) { // cut is ok - record it + fout << "\tFinal cut acceptance (" << newToolPos.X << "," << newToolPos.Y << ")\n"; noCutDistance = 0; if (toClearPath.empty()) { toClearPath.push_back(toolPos); @@ -3320,13 +3294,6 @@ void Adaptive2d::ProcessPolyNode(Paths boundPaths, Paths toolBoundPaths) CheckReportProgress(progressPaths); } else { -#ifdef DEV_MODE - // if(point_index==0) { - // engage_no_cut_count++; - // cout<<"Break:no cut #" << engage_no_cut_count << ", bad engage, pass:" << pass - // << " over_cut_count:" << over_cut_count << endl; - // } -#endif // cout<<"Break: no cut @" << point_index << endl; if (noCutDistance > stepOverScaled) { fout << "Points: " << point_index << "\n"; @@ -3341,7 +3308,7 @@ void Adaptive2d::ProcessPolyNode(Paths boundPaths, Paths toolBoundPaths) cleared.ExpandCleared(toClearPath); toClearPath.clear(); } - const double minArea = MIN_CUT_AREA_FACTOR * optimalCutAreaPD; + const double minArea = MIN_CUT_AREA_FACTOR * MIN_STEP_CLIPPER * optimalCutAreaPD; if (cumulativeCutArea > minArea) { Path cleaned; CleanPath(passToolPath, cleaned, CLEAN_PATH_TOLERANCE); @@ -3371,13 +3338,19 @@ void Adaptive2d::ProcessPolyNode(Paths boundPaths, Paths toolBoundPaths) } { + // This constant is chosen so that when cutting a small strip (i.e. when + // approaching a boundary), the strip size at which the cut is too small to + // continue is _also_ too small to be worth starting a new engagement + // elsewhere in the strip + const double CORRECT_MIN_CUT_VS_ENGAGE = toolRadiusScaled * 1. / MIN_STEP_CLIPPER; + double moveDistance = ENGAGE_SCAN_DISTANCE_FACTOR * stepOverScaled * refinement_factor; if (!engage.nextEngagePoint( this, cleared, moveDistance, - ENGAGE_AREA_THR_FACTOR * optimalCutAreaPD / CORRECT_MIN_CUT_VS_ENGAGE, + ENGAGE_AREA_THR_FACTOR * optimalCutAreaPD * CORRECT_MIN_CUT_VS_ENGAGE, 4 * referenceCutArea * stepOverFactor )) { // check if there are any uncleared area left @@ -3415,7 +3388,7 @@ void Adaptive2d::ProcessPolyNode(Paths boundPaths, Paths toolBoundPaths) this, cleared, moveDistance, - ENGAGE_AREA_THR_FACTOR * optimalCutAreaPD / CORRECT_MIN_CUT_VS_ENGAGE, + ENGAGE_AREA_THR_FACTOR * optimalCutAreaPD * CORRECT_MIN_CUT_VS_ENGAGE, 4 * referenceCutArea * stepOverFactor )) { break; @@ -3528,7 +3501,6 @@ void Adaptive2d::ProcessPolyNode(Paths boundPaths, Paths toolBoundPaths) Perf_ProcessPolyNode.DumpResults(); Perf_PointIterations.DumpResults(); Perf_CalcCutAreaCirc.DumpResults(); - Perf_CalcCutAreaTest.DumpResults(); Perf_CalcCutAreaClip.DumpResults(); Perf_NextEngagePoint.DumpResults(); Perf_ExpandCleared.DumpResults(); diff --git a/src/Mod/CAM/libarea/Adaptive.hpp b/src/Mod/CAM/libarea/Adaptive.hpp index 6c2f5490fe..6ae5d967c9 100644 --- a/src/Mod/CAM/libarea/Adaptive.hpp +++ b/src/Mod/CAM/libarea/Adaptive.hpp @@ -211,9 +211,8 @@ private: // constants for fine tuning const int DIRECTION_SMOOTHING_BUFLEN = 3; // gyro points - used for angle smoothing - const double MIN_CUT_AREA_FACTOR = 0.4 - * 16; // used for filtering out of insignificant cuts (should be < ENGAGE_AREA_THR_FACTOR) - const double ENGAGE_AREA_THR_FACTOR = .3; // 0.5 * 16; // influences minimal engage area + const double MIN_CUT_AREA_FACTOR = 0.1; // used for filtering out of insignificant cuts + const double ENGAGE_AREA_THR_FACTOR = .3; // influences minimal engage area const double ENGAGE_SCAN_DISTANCE_FACTOR = 0.2; // influences the engage scan/stepping distance const double CLEAN_PATH_TOLERANCE = 1.41; // should be >1 From 0f189909444bbe3daea43360ab9d42edb921de77 Mon Sep 17 00:00:00 2001 From: David Kaufman Date: Tue, 23 Dec 2025 15:48:51 -0500 Subject: [PATCH 5/9] delete unused code to calculate cut area with clipper --- src/Mod/CAM/libarea/Adaptive.cpp | 38 +------------------------------- 1 file changed, 1 insertion(+), 37 deletions(-) diff --git a/src/Mod/CAM/libarea/Adaptive.cpp b/src/Mod/CAM/libarea/Adaptive.cpp index 20271898f6..b344c813f1 100644 --- a/src/Mod/CAM/libarea/Adaptive.cpp +++ b/src/Mod/CAM/libarea/Adaptive.cpp @@ -957,7 +957,6 @@ private: PerfCounter Perf_ProcessPolyNode("ProcessPolyNode"); PerfCounter Perf_CalcCutAreaCirc("CalcCutArea"); -PerfCounter Perf_CalcCutAreaClip("CalcCutAreaClip"); PerfCounter Perf_NextEngagePoint("NextEngagePoint"); PerfCounter Perf_PointIterations("PointIterations"); PerfCounter Perf_ExpandCleared("ExpandCleared"); @@ -1697,43 +1696,9 @@ double Adaptive2d::CalcCutArea( } } - // TODO temporary: compute area using clipper, and print both my area and clipper's area. - // Return... clipper's, for now Perf_CalcCutAreaCirc.Stop(); - double clipperArea = 0; - // { - // Perf_CalcCutAreaClip.Start(); - // // old way of calculating cut area based on polygon clipping - // // used in case when there are multiple intersections of tool with cleared poly (very rare - // // case, but important) - // // 1. find difference between old and new tool shape - // Path oldTool; - // Path newTool; - // TranslatePath(toolGeometry, oldTool, c1); - // TranslatePath(toolGeometry, newTool, c2); - // clip.Clear(); - // clip.AddPath(newTool, PolyType::ptSubject, true); - // clip.AddPath(oldTool, PolyType::ptClip, true); - // Paths toolDiff; - // clip.Execute(ClipType::ctDifference, toolDiff); + (*fout) << "Area=" << area << endl; - // // 2. difference to cleared - // clip.Clear(); - // clip.AddPaths(toolDiff, PolyType::ptSubject, true); - // clip.AddPaths(clearedBounded, PolyType::ptClip, true); - // Paths cutAreaPoly; - // clip.Execute(ClipType::ctDifference, cutAreaPoly); - - // // calculate resulting area - // for (Path& path : cutAreaPoly) { - // clipperArea += fabs(Area(path)); - // } - // Perf_CalcCutAreaClip.Stop(); - // } - (*fout) << "Area=" << area << " clipperArea=" << clipperArea << " " - << int(abs(clipperArea - area) / area * 100) << "%" << endl; - - // return clipperArea; return area; } @@ -3501,7 +3466,6 @@ void Adaptive2d::ProcessPolyNode(Paths boundPaths, Paths toolBoundPaths) Perf_ProcessPolyNode.DumpResults(); Perf_PointIterations.DumpResults(); Perf_CalcCutAreaCirc.DumpResults(); - Perf_CalcCutAreaClip.DumpResults(); Perf_NextEngagePoint.DumpResults(); Perf_ExpandCleared.DumpResults(); Perf_DistanceToBoundary.DumpResults(); From c29c18e0e1e226001cff7aef56b83b760bc4693f Mon Sep 17 00:00:00 2001 From: David Kaufman Date: Tue, 23 Dec 2025 16:54:29 -0500 Subject: [PATCH 6/9] Remove debug code --- src/Mod/CAM/Path/Op/Adaptive.py | 3 - src/Mod/CAM/libarea/Adaptive.cpp | 120 ------------------------------- src/Mod/CAM/libarea/Adaptive.hpp | 3 - 3 files changed, 126 deletions(-) diff --git a/src/Mod/CAM/Path/Op/Adaptive.py b/src/Mod/CAM/Path/Op/Adaptive.py index 0b1a104cc6..4d8fb39dce 100644 --- a/src/Mod/CAM/Path/Op/Adaptive.py +++ b/src/Mod/CAM/Path/Op/Adaptive.py @@ -784,10 +784,7 @@ def ExecuteModelAware(op, obj): "orderCutsByRegion": obj.OrderCutsByRegion, } - import random - insideInputStateObject = { - "TODO TESTING": random.random(), "tool": op.tool.Diameter.Value, "tolerance": obj.Tolerance, "geometry": [ diff --git a/src/Mod/CAM/libarea/Adaptive.cpp b/src/Mod/CAM/libarea/Adaptive.cpp index b344c813f1..409c6ba241 100644 --- a/src/Mod/CAM/libarea/Adaptive.cpp +++ b/src/Mod/CAM/libarea/Adaptive.cpp @@ -24,7 +24,6 @@ #include "Adaptive.hpp" #include -#include #include #include #include @@ -916,7 +915,6 @@ public: } inline void Start() { -#define DEV_MODE #ifdef DEV_MODE start_ticks = clock(); if (running) { @@ -1273,9 +1271,6 @@ public: int maxPases = 2 ) { - (*parent->fout) << "Finding Next Engage Point! " - << "step=" << step << " minCutArea=" << minCutArea - << " maxCutArea=" << maxCutArea << "\n"; Perf_NextEngagePoint.Start(); double prevArea = 0; // we want to make sure that we catch the point where the area is on // raising slope @@ -1286,7 +1281,6 @@ public: state.passes++; if (state.passes >= maxPases) { Perf_NextEngagePoint.Stop(); - (*parent->fout) << "Done, none found.\n"; return false; // nothing more to cut } prevArea = 0; @@ -1294,12 +1288,8 @@ public: } IntPoint cpt = getCurrentPoint(); double area = parent->CalcCutArea(clip, dummyInitialPoint, cpt, clearedArea); - (*parent->fout) << "Testint point:" - << " cpt=(" << cpt.X << "," << cpt.Y << ")" - << " area=" << area << "\n"; if (area > minCutArea && area < maxCutArea && area > prevArea) { Perf_NextEngagePoint.Stop(); - (*parent->fout) << "Done, accepted point.\n"; return true; } prevArea = area; @@ -1482,18 +1472,6 @@ double Adaptive2d::CalcCutArea( polygons.push_back(polygon); } - // (*fout) << "AREA[" - // << " c1=(" << c1.X << "," << c1.Y << ")" - // << " c2=(" << c2.X << "," << c2.Y << ")"; - // for (const auto poly : polygons) { - // (*fout) << "POLY["; - // for (const auto p : poly) { - // (*fout) << "(" << p.X << "," << p.Y << ")"; - // } - // (*fout) << "] "; - // } - // (*fout) << "] "; - // 1) Find all x-coordinates of interest: vector xs; for (const auto polygon : polygons) { @@ -1638,12 +1616,6 @@ double Adaptive2d::CalcCutArea( const auto y1 = interpX(p0, p1, x1); const double newArea = (y0 + y1) / 2 * (x1 - x0); area += entranceExitSign * newArea; - //(*fout) << "Pgon[eeSign=" << entranceExitSign - // << " x0=(" << x0 << " x1=" << x1 << ")" - // << " p0=(" << p0.X << "," << p0.Y << ")" - // << " p1=(" << p1.X << "," << p1.Y << ")" - // << " newArea(w/o sign)=" << newArea - // << " totalArea=" << area << "] "; } else { // crossed a circle @@ -1681,24 +1653,12 @@ double Adaptive2d::CalcCutArea( const double areaTrapezoid = (x1 - x0) * (y0 + y1) / 2; const double newArea = circleSign * areaSegment + areaTrapezoid; area += entranceExitSign * newArea; - //(*fout) << "Circle[eeSign=" << entranceExitSign - // << " x0=(" << x0 << " x1=" << x1 << ")" - // << " c=(" << c.X << "," << c.Y << ")" - // << " circleSign=" << circleSign - // << " areaSector=" << areaSector - // << " areaTriangle=" << areaTriangle - // << " areaSegment=" << areaSegment - // << " areaTrapezoid=" << areaTrapezoid - // << " newArea(w/o sign)=" << newArea - // << " totalArea=" << area << "] "; } } } } Perf_CalcCutAreaCirc.Stop(); - (*fout) << "Area=" << area << endl; - return area; } @@ -2757,31 +2717,8 @@ void Adaptive2d::AddPathToProgress(TPaths& progressPaths, const Path pth, Motion } } -struct nostream -{ - nostream(string a) - {} - nostream operator<<(string a) - { - return *this; - } - nostream operator<<(double a) - { - return *this; - } - nostream operator<<(IntPoint a) - { - return *this; - } -}; - void Adaptive2d::ProcessPolyNode(Paths boundPaths, Paths toolBoundPaths) { - ofstream fout("adaptive_debug.txt"); - this->fout = &fout; - // nostream fout("adaptive_debug.txt"); - fout << "\n" << "\n" << "----------------------" << "\n"; - fout << "Start ProcessPolyNode" << "\n"; Perf_ProcessPolyNode.Start(); current_region++; cout << "** Processing region: " << current_region << endl; @@ -2834,14 +2771,12 @@ void Adaptive2d::ProcessPolyNode(Paths boundPaths, Paths toolBoundPaths) engageBounds.push_back(p); } outsideEntry = true; - fout << "Outside entry " << entryPoint << "\n"; } else { if (!FindEntryPoint(progressPaths, toolBoundPaths, boundPaths, cleared, entryPoint, toolPos, toolDir)) { Perf_ProcessPolyNode.Stop(); return; } - fout << "Helix entry " << entryPoint << "\n"; } EngagePoint engage(engageBounds); // engage point stepping instance @@ -2898,22 +2833,11 @@ void Adaptive2d::ProcessPolyNode(Paths boundPaths, Paths toolBoundPaths) #endif ClearedArea clearedBeforePass(toolRadiusScaled); clearedBeforePass.SetClearedPaths(cleared.GetCleared()); - fout << "Tool radius scaled: " << toolRadiusScaled << "\n"; - fout << "toolBoundPaths:"; - for (auto path : toolBoundPaths) { - fout << " ["; - for (auto p : path) { - fout << "(" << p.X << "," << p.Y << ")_"; - } - fout << "]"; - } - fout << "\n"; //******************************* // LOOP - PASSES //******************************* for (long pass = 0; pass < PASSES_LIMIT; pass++) { - fout << "New pass! " << pass << "\n"; if (stopProcessing) { break; } @@ -2952,7 +2876,6 @@ void Adaptive2d::ProcessPolyNode(Paths boundPaths, Paths toolBoundPaths) // LOOP - POINTS //******************************* for (long point_index = 0; point_index < POINTS_PER_PASS_LIMIT; point_index++) { - fout << "\n" << "Point " << point_index << "\n"; if (stopProcessing) { break; } @@ -2991,14 +2914,12 @@ void Adaptive2d::ProcessPolyNode(Paths boundPaths, Paths toolBoundPaths) if (stepScaled < MIN_STEP_CLIPPER) { stepScaled = long(MIN_STEP_CLIPPER); } - fout << "\tstepScaled " << stepScaled << "\n"; //***************************** // ANGLE vs AREA ITERATIONS //***************************** double predictedAngle = averageDV(angleHistory); double maxError = AREA_ERROR_FACTOR * optimalCutAreaPD; - fout << "optimal area " << optimalCutAreaPD << " maxError " << maxError << "\n"; double area = 0; double areaPD = 0; interp.clear(); @@ -3009,50 +2930,35 @@ void Adaptive2d::ProcessPolyNode(Paths boundPaths, Paths toolBoundPaths) bool pointNotInterp; for (iteration = 0; iteration < MAX_ITERATIONS; iteration++) { total_iterations++; - fout << "It " << iteration << " "; if (iteration == 0) { angle = predictedAngle; pointNotInterp = true; - fout << "case predicted "; } else if (iteration == 1) { angle = interp.MIN_ANGLE; // max engage pointNotInterp = true; - fout << "case minimum "; } else if (iteration == 2) { if (interp.bothSides()) { angle = interp.interpolateAngle(); - fout << "(" << interp.m_min->first.first << ", " << interp.m_min->second - << ") ~ (" << interp.m_max->first.first << ", " << interp.m_max->second - << ") "; pointNotInterp = false; - fout << "case interp "; } else { angle = interp.MAX_ANGLE; // min engage - fout << "case maximum "; pointNotInterp = true; } } else { angle = interp.interpolateAngle(); - fout << "(" << interp.m_min->first.first << ", " << interp.m_min->second - << ") ~ (" << interp.m_max->first.first << ", " << interp.m_max->second - << ") "; pointNotInterp = false; - fout << "case interp "; } - fout << "raw " << angle << " "; angle = interp.clampAngle(angle); - fout << "clamped " << angle << " "; newToolDir = rotate(toolDir, angle); newToolPos = IntPoint( long(toolPos.X + newToolDir.X * stepScaled), long(toolPos.Y + newToolDir.Y * stepScaled) ); - fout << "int pos " << newToolPos << " "; // Skip iteration if this IntPoint has already been processed bool intRepeat = false; @@ -3075,7 +2981,6 @@ void Adaptive2d::ProcessPolyNode(Paths boundPaths, Paths toolBoundPaths) // integers continue; } - fout << "hit integer floor" << "\n"; // exit early, selecting the better of the two adjacent integers double error; if (abs(interp.m_min->second) < abs(interp.m_max->second)) { @@ -3092,17 +2997,14 @@ void Adaptive2d::ProcessPolyNode(Paths boundPaths, Paths toolBoundPaths) area = areaPD * double(stepScaled); break; } - fout << "skip area calc " << "\n"; continue; } area = CalcCutArea(clip, toolPos, newToolPos, cleared); areaPD = area / double(stepScaled); // area per distance - fout << "addPoint " << areaPD << " " << angle << " "; double error = areaPD - targetAreaPD; interp.addPoint(error, {angle, newToolPos}, pointNotInterp); - fout << "areaPD " << areaPD << " error " << error << " "; // cout << " iter:" << iteration << " angle:" << angle << " area:" << areaPD // << " target:" << targetAreaPD << " error:" << error << " max:" << maxError // << endl; @@ -3111,22 +3013,17 @@ void Adaptive2d::ProcessPolyNode(Paths boundPaths, Paths toolBoundPaths) if (angleHistory.size() > ANGLE_HISTORY_POINTS) { angleHistory.erase(angleHistory.begin()); } - fout << "small enough" << "\n"; break; } if (iteration > 5 && fabs(error - prev_error) < 0.001) { - fout << "no change" << "\n"; break; } if (iteration == MAX_ITERATIONS - 1) { - fout << "too many iterations!" << "\n"; total_exceeded++; } - fout << "\n"; prev_error = error; } Perf_PointIterations.Stop(); - fout << "Iterations: " << iteration << "\n"; recalcArea = false; // approach end boundary tangentially @@ -3158,12 +3055,6 @@ void Adaptive2d::ProcessPolyNode(Paths boundPaths, Paths toolBoundPaths) long(toolPos.Y + newToolDir.Y * stepScaled) ); recalcArea = true; - fout << "\tRewrote tooldir/toolpos for boundary approach" - << " a=" << angle << " maxA=" << maxAngleToBoundary << " (" << newToolPos.X - << ", " << newToolPos.Y << ")" << "\n"; - } - else { - fout << "\tRewrote tooldir/toolpos for boundary approach BUT NOT ACTUALLY \n"; } } @@ -3180,8 +3071,6 @@ void Adaptive2d::ProcessPolyNode(Paths boundPaths, Paths toolBoundPaths) long(toolPos.X + newToolDir.X * stepScaled), long(toolPos.Y + newToolDir.Y * stepScaled) ); - fout << "\tMoving tool back within boundary..." - << "(" << newToolPos.X << ", " << newToolPos.Y << ")" << "\n"; } if (rotateStep >= 180) { #ifdef DEV_MODE @@ -3192,13 +3081,11 @@ void Adaptive2d::ProcessPolyNode(Paths boundPaths, Paths toolBoundPaths) if (recalcArea) { area = CalcCutArea(clip, toolPos, newToolPos, cleared); - fout << "\tRecalc area: " << area << "\n"; } // safety condition if (area > stepScaled * optimalCutAreaPD && areaPD > 2 * optimalCutAreaPD) { over_cut_count++; - fout << "\tCut area too big!!!" << "\n"; break; } @@ -3215,7 +3102,6 @@ void Adaptive2d::ProcessPolyNode(Paths boundPaths, Paths toolBoundPaths) prevDistFromStart = distFromStart; if (area > 0) { // cut is ok - record it - fout << "\tFinal cut acceptance (" << newToolPos.X << "," << newToolPos.Y << ")\n"; noCutDistance = 0; if (toClearPath.empty()) { toClearPath.push_back(toolPos); @@ -3261,11 +3147,9 @@ void Adaptive2d::ProcessPolyNode(Paths boundPaths, Paths toolBoundPaths) else { // cout<<"Break: no cut @" << point_index << endl; if (noCutDistance > stepOverScaled) { - fout << "Points: " << point_index << "\n"; break; } noCutDistance += stepScaled; - fout << "\tFailed to accept point??" << "\n"; } } /* end of points loop*/ @@ -3282,12 +3166,8 @@ void Adaptive2d::ProcessPolyNode(Paths boundPaths, Paths toolBoundPaths) CheckReportProgress(progressPaths); bad_engage_count = 0; engage.ResetPasses(); - fout << "Accepted pass, area " << cumulativeCutArea << " more than minimum " << minArea - << "\n\n"; } else { - fout << "Rejected pass, too little area " << cumulativeCutArea << " < " << minArea - << "\n\n"; bad_engage_count++; } diff --git a/src/Mod/CAM/libarea/Adaptive.hpp b/src/Mod/CAM/libarea/Adaptive.hpp index 6ae5d967c9..2e906f6561 100644 --- a/src/Mod/CAM/libarea/Adaptive.hpp +++ b/src/Mod/CAM/libarea/Adaptive.hpp @@ -26,7 +26,6 @@ #include #include #include -#include #ifndef ADAPTIVE_HPP # define ADAPTIVE_HPP @@ -200,8 +199,6 @@ private: void AddPathToProgress(TPaths& progressPaths, const Path pth, MotionType mt = MotionType::mtCutting); void ApplyStockToLeave(Paths& inputPaths); - std::ofstream* fout; - private: // constants for fine tuning const double MIN_STEP_CLIPPER = 16.0 * 3; const int MAX_ITERATIONS = 10; From 0432dec3c395e67f8f9aa104fa87d98f0fd506b3 Mon Sep 17 00:00:00 2001 From: David Kaufman Date: Tue, 23 Dec 2025 17:14:19 -0500 Subject: [PATCH 7/9] fix code accidentally reintroduced --- src/Mod/CAM/libarea/Adaptive.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Mod/CAM/libarea/Adaptive.cpp b/src/Mod/CAM/libarea/Adaptive.cpp index 409c6ba241..d4056f8312 100644 --- a/src/Mod/CAM/libarea/Adaptive.cpp +++ b/src/Mod/CAM/libarea/Adaptive.cpp @@ -3015,9 +3015,6 @@ void Adaptive2d::ProcessPolyNode(Paths boundPaths, Paths toolBoundPaths) } break; } - if (iteration > 5 && fabs(error - prev_error) < 0.001) { - break; - } if (iteration == MAX_ITERATIONS - 1) { total_exceeded++; } From e642409981751876bfa9aeeb6e4e90e311b88f1f Mon Sep 17 00:00:00 2001 From: David Kaufman Date: Thu, 25 Dec 2025 19:19:27 -0500 Subject: [PATCH 8/9] fix warning that breaks the pixi build --- src/Mod/CAM/libarea/Adaptive.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Mod/CAM/libarea/Adaptive.cpp b/src/Mod/CAM/libarea/Adaptive.cpp index d4056f8312..82b9e0944d 100644 --- a/src/Mod/CAM/libarea/Adaptive.cpp +++ b/src/Mod/CAM/libarea/Adaptive.cpp @@ -1467,7 +1467,7 @@ double Adaptive2d::CalcCutArea( vector polygon; for (const auto p : path) { - polygon.push_back({p.X, p.Y}); + polygon.push_back({(double)p.X, (double)p.Y}); } polygons.push_back(polygon); } From 9f9ac6c0c2e71fbbfddbf61d48034621327d6717 Mon Sep 17 00:00:00 2001 From: David Kaufman Date: Thu, 25 Dec 2025 23:41:16 -0500 Subject: [PATCH 9/9] Document derivation of the new value for MIN_STEP_CLIPPER --- src/Mod/CAM/libarea/Adaptive.hpp | 46 +++++++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/src/Mod/CAM/libarea/Adaptive.hpp b/src/Mod/CAM/libarea/Adaptive.hpp index 2e906f6561..dd29cdb707 100644 --- a/src/Mod/CAM/libarea/Adaptive.hpp +++ b/src/Mod/CAM/libarea/Adaptive.hpp @@ -199,7 +199,51 @@ private: void AddPathToProgress(TPaths& progressPaths, const Path pth, MotionType mt = MotionType::mtCutting); void ApplyStockToLeave(Paths& inputPaths); -private: // constants for fine tuning +private: + // Derivation for MIN_STEP_CLIPPPER (MSC for short in this derivation): + // Diagram: + // - circle C1 from previous pass, radius R + // - circle C2 from current pass MSC away, horizontal + // - line Lprev from previous pass, step over x + // - line Llong from tool position through C1/Lprev intersection to C2 + // - line L1 from previous tool position to C1/Lprev intersection + // + // Length of Llong = R + y, where y is the longest protrusion into the cut area + // When selecting MIN_STEP_CLIPPER, we need to ensure that the computed + // value for y > 1 when using stepover x equal to the size of the finishing + // pass. Finishing pass stepover is + // x = stepover/10 + // x = 2 * R * stepoverFactor / 10 (Eq1). + // + // Construct right triangle with (R-x) of vertical radius from C1 and + // L1. Third length (horizontal) = a + // (R-x)^2 + a^2 = R^2 + // a^2 = 2*R*x - x^2 (Eq2) + // a ~= sqrt(2*R*x) (Eq3; x< 1. The endpoints of y may be perturbed by up to sqrt(2)/2 each + // due to integer rounding, so the true value of y must be at least 1+sqrt(2) ~= 2.4. + // StepoverFactor may be as small as 1% = 0.01. Evaluating Eq4 with these values: + // + // MSC > 2.4/sqrt(2*.01/5) + // MSC > 38. + // + // Historically we have used MSC = 16. It might be convenient that MSC is + // many-times divisible by 2, so I have chosen 16*3 (>38) for its new value. const double MIN_STEP_CLIPPER = 16.0 * 3; const int MAX_ITERATIONS = 10; const double AREA_ERROR_FACTOR = 0.05; /* how precise to match the cut area to optimal,