From e31a1b3da852f8a0106ac171051fbd165152fec1 Mon Sep 17 00:00:00 2001 From: David Kaufman Date: Tue, 16 Sep 2025 12:59:27 -0400 Subject: [PATCH 1/9] [CAM] WIP fix adaptive stepover bug, many debug statements remain --- src/Mod/CAM/Path/Op/Adaptive.py | 3 + src/Mod/CAM/libarea/Adaptive.cpp | 130 +++++++++++++++++++++---------- 2 files changed, 90 insertions(+), 43 deletions(-) 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 13497a6ff4..7f766db7cd 100644 --- a/src/Mod/CAM/libarea/Adaptive.cpp +++ b/src/Mod/CAM/libarea/Adaptive.cpp @@ -24,11 +24,13 @@ #include "Adaptive.hpp" #include +#include #include #include #include #include #include +#include namespace ClipperLib { @@ -1117,58 +1119,65 @@ public: void clear() { - angles.clear(); - areas.clear(); + m_min.reset(); + m_max.reset(); + } + bool bothSides() + { + return m_min && m_max && m_min->second < 0 && m_max->second >= 0; } // adds point keeping the incremental order of areas for interpolation to work correctly void addPoint(double area, double angle) { - std::size_t size = areas.size(); - if (size == 0 || area > areas[size - 1] + NTOL) { // first point or largest area point - areas.push_back(area); - angles.push_back(angle); - return; + if (!m_min) { + m_min = {angle, area}; } - - for (std::size_t i = 0; i < size; i++) { - if (area < areas[i] - NTOL && (i == 0 || area > areas[i - 1] + NTOL)) { - areas.insert(areas.begin() + i, area); - angles.insert(angles.begin() + i, angle); + else if (!m_max) { + m_max = {angle, area}; + if (m_min->second > m_max->second) { + auto tmp = m_min; + m_min = m_max; + m_max = tmp; } } + else if (bothSides()) { + if (area < 0) { + m_min = {angle, area}; + } + else { + m_max = {angle, area}; + } + } + else { + if (abs(m_min->second) > abs(m_max->second)) { + m_min.reset(); + } + else { + m_max.reset(); + } + addPoint(area, angle); + } } - double interpolateAngle(double targetArea) + double interpolateAngle(ofstream& fout) { - std::size_t size = areas.size(); - if (size < 2 || targetArea > areas[size - 1]) { - return MIN_ANGLE; // max engage angle - convenient value to initially measure cut area - } - if (targetArea < areas[0]) { - return MAX_ANGLE; // min engage angle + if (!m_min) { + return MIN_ANGLE; } - for (size_t i = 1; i < size; i++) { - // find 2 subsequent points where target area is between - if (areas[i - 1] <= targetArea && areas[i] > targetArea) { - // linear interpolation - double af = (targetArea - areas[i - 1]) / (areas[i] - areas[i - 1]); - double a = angles[i - 1] + af * (angles[i] - angles[i - 1]); - return a; - } + if (!m_max) { + return MAX_ANGLE; } - return MIN_ANGLE; + + fout << "(" << m_min->first << ", " << m_min->second << ") ~ (" << m_max->first << ", " + << m_max->second << ") "; + double p = (0 - m_min->second) / (m_max->second - m_min->second); + return m_min->first * (1 - p) + m_max->first * p; } double clampAngle(double angle) { - if (angle < MIN_ANGLE) { - return MIN_ANGLE; - } - if (angle > MAX_ANGLE) { - return MAX_ANGLE; - } - return angle; + return max(min(angle, MAX_ANGLE), MIN_ANGLE); } double getRandomAngle() @@ -1177,12 +1186,12 @@ public: } size_t getPointCount() { - return areas.size(); + return (m_min ? 1 : 0) + (m_max ? 1 : 0); } -private: - vector angles; - vector areas; +public: + std::optional> m_min; + std::optional> m_max; }; //*************************************** @@ -2771,6 +2780,9 @@ void Adaptive2d::AddPathToProgress(TPaths& progressPaths, const Path pth, Motion void Adaptive2d::ProcessPolyNode(Paths boundPaths, Paths toolBoundPaths) { + ofstream fout("adaptive_debug.txt"); + fout << endl << endl << "----------------------" << endl; + fout << "Start ProcessPolyNode" << endl; Perf_ProcessPolyNode.Start(); current_region++; cout << "** Processing region: " << current_region << endl; @@ -2823,12 +2835,14 @@ void Adaptive2d::ProcessPolyNode(Paths boundPaths, Paths toolBoundPaths) engageBounds.push_back(p); } outsideEntry = true; + fout << "Outside entry " << entryPoint << endl; } else { if (!FindEntryPoint(progressPaths, toolBoundPaths, boundPaths, cleared, entryPoint, toolPos, toolDir)) { Perf_ProcessPolyNode.Stop(); return; } + fout << "Helix entry " << entryPoint << endl; } EngagePoint engage(engageBounds); // engage point stepping instance @@ -2890,6 +2904,7 @@ void Adaptive2d::ProcessPolyNode(Paths boundPaths, Paths toolBoundPaths) // LOOP - PASSES //******************************* for (long pass = 0; pass < PASSES_LIMIT; pass++) { + fout << "New pass! " << pass << endl; if (stopProcessing) { break; } @@ -2928,6 +2943,7 @@ void Adaptive2d::ProcessPolyNode(Paths boundPaths, Paths toolBoundPaths) // LOOP - POINTS //******************************* for (long point_index = 0; point_index < POINTS_PER_PASS_LIMIT; point_index++) { + fout << endl << "Point " << point_index << endl; if (stopProcessing) { break; } @@ -2966,12 +2982,14 @@ void Adaptive2d::ProcessPolyNode(Paths boundPaths, Paths toolBoundPaths) if (stepScaled < RESOLUTION_FACTOR) { stepScaled = long(RESOLUTION_FACTOR); } + fout << "\tstepScaled " << stepScaled << endl; //***************************** // ANGLE vs AREA ITERATIONS //***************************** double predictedAngle = averageDV(angleHistory); double maxError = AREA_ERROR_FACTOR * optimalCutAreaPD; + fout << "optimal area " << optimalCutAreaPD << " maxError " << maxError << endl; double area = 0; double areaPD = 0; interp.clear(); @@ -2981,34 +2999,51 @@ void Adaptive2d::ProcessPolyNode(Paths boundPaths, Paths toolBoundPaths) double prev_error = __DBL_MAX__; for (iteration = 0; iteration < MAX_ITERATIONS; iteration++) { total_iterations++; + fout << "It " << iteration << " "; if (iteration == 0) { angle = predictedAngle; + fout << "case predicted "; } else if (iteration == 1) { angle = interp.MIN_ANGLE; // max engage + fout << "case minimum "; } - else if (iteration == 3) { - angle = interp.MAX_ANGLE; // min engage + else if (iteration == 2) { + if (interp.bothSides()) { + angle = interp.interpolateAngle(fout); + fout << "case interp "; + } + else { + angle = interp.MAX_ANGLE; // min engage + fout << "case maximum "; + } } else if (interp.getPointCount() < 2) { angle = interp.getRandomAngle(); + fout << "case random "; } else { - angle = interp.interpolateAngle(targetAreaPD); + angle = interp.interpolateAngle(fout); + 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 << " "; area = CalcCutArea(clip, toolPos, newToolPos, cleared); areaPD = area / double(stepScaled); // area per distance - interp.addPoint(areaPD, angle); + fout << "addPoint " << areaPD << " " << angle << " "; double error = areaPD - targetAreaPD; + interp.addPoint(error, angle); + fout << "areaPD " << areaPD << " error " << error << " "; // cout << " iter:" << iteration << " angle:" << angle << " area:" << areaPD // << " target:" << targetAreaPD << " error:" << error << " max:" << maxError // << endl; @@ -3017,14 +3052,18 @@ void Adaptive2d::ProcessPolyNode(Paths boundPaths, Paths toolBoundPaths) if (angleHistory.size() > ANGLE_HISTORY_POINTS) { angleHistory.erase(angleHistory.begin()); } + fout << "small enough" << endl; break; } if (iteration > 5 && fabs(error - prev_error) < 0.001) { + fout << "no change" << endl; break; } if (iteration == MAX_ITERATIONS - 1) { + fout << "too many iterations!" << endl; total_exceeded++; } + fout << endl; prev_error = error; } Perf_PointIterations.Stop(); @@ -3046,6 +3085,7 @@ void Adaptive2d::ProcessPolyNode(Paths boundPaths, Paths toolBoundPaths) long(toolPos.Y + newToolDir.Y * stepScaled) ); recalcArea = true; + fout << "\tRewrote tooldir/toolpos for boundary approach" << endl; } //********************************************** @@ -3061,6 +3101,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..." << endl; } if (rotateStep >= 180) { #ifdef DEV_MODE @@ -3076,6 +3117,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!!!" << endl; break; } @@ -3093,6 +3135,7 @@ void Adaptive2d::ProcessPolyNode(Paths boundPaths, Paths toolBoundPaths) if (area > 0.5 * MIN_CUT_AREA_FACTOR * optimalCutAreaPD * RESOLUTION_FACTOR) { // cut is ok - record it + fout << "\tFinal cut acceptance" << endl; noCutDistance = 0; if (toClearPath.empty()) { toClearPath.push_back(toolPos); @@ -3148,6 +3191,7 @@ void Adaptive2d::ProcessPolyNode(Paths boundPaths, Paths toolBoundPaths) break; } noCutDistance += stepScaled; + fout << "\tFailed to accept point??" << endl; } } /* end of points loop*/ From f622ae5f492450363e0b99ae8ec402faa9bc892c Mon Sep 17 00:00:00 2001 From: David Kaufman Date: Thu, 18 Sep 2025 13:11:25 -0400 Subject: [PATCH 2/9] refinements --- src/Mod/CAM/libarea/Adaptive.cpp | 39 ++++++++++++++++++++++++-------- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/src/Mod/CAM/libarea/Adaptive.cpp b/src/Mod/CAM/libarea/Adaptive.cpp index 7f766db7cd..ad999004ae 100644 --- a/src/Mod/CAM/libarea/Adaptive.cpp +++ b/src/Mod/CAM/libarea/Adaptive.cpp @@ -1127,13 +1127,13 @@ public: return m_min && m_max && m_min->second < 0 && m_max->second >= 0; } // adds point keeping the incremental order of areas for interpolation to work correctly - void addPoint(double area, double angle) + void addPoint(double error, double angle, bool allowSkip = false) { if (!m_min) { - m_min = {angle, area}; + m_min = {angle, error}; } else if (!m_max) { - m_max = {angle, area}; + m_max = {angle, error}; if (m_min->second > m_max->second) { auto tmp = m_min; m_min = m_max; @@ -1141,21 +1141,24 @@ public: } } else if (bothSides()) { - if (area < 0) { - m_min = {angle, area}; + if (error < 0) { + m_min = {angle, error}; } else { - m_max = {angle, area}; + m_max = {angle, error}; } } else { + if (allowSkip && abs(error) > abs(m_min->second) && abs(error) > abs(m_max->second)) { + return; + } if (abs(m_min->second) > abs(m_max->second)) { m_min.reset(); } else { m_max.reset(); } - addPoint(area, angle); + addPoint(error, angle); } } @@ -1172,6 +1175,14 @@ public: fout << "(" << m_min->first << ", " << m_min->second << ") ~ (" << m_max->first << ", " << m_max->second << ") "; double p = (0 - m_min->second) / (m_max->second - m_min->second); + + // Ensure search is sufficiently efficient -- this is a compromise + // between binary search (p = 0.5, guaranteed search completion in log + // time) and following linear interpolation completely (often faster + // since area cut is locally linear in movement angle) + const double minInterp = .2; + p = max(min(p, 1 - minInterp), minInterp); + return m_min->first * (1 - p) + m_max->first * p; } @@ -2899,6 +2910,7 @@ void Adaptive2d::ProcessPolyNode(Paths boundPaths, Paths toolBoundPaths) #endif ClearedArea clearedBeforePass(toolRadiusScaled); clearedBeforePass.SetClearedPaths(cleared.GetCleared()); + fout << "Tool radius scaled: " << toolRadiusScaled << "\n"; //******************************* // LOOP - PASSES @@ -2997,33 +3009,40 @@ void Adaptive2d::ProcessPolyNode(Paths boundPaths, Paths toolBoundPaths) Perf_PointIterations.Start(); int iteration; double prev_error = __DBL_MAX__; + 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); + pointNotInterp = false; fout << "case interp "; } else { angle = interp.MAX_ANGLE; // min engage fout << "case maximum "; + pointNotInterp = true; } } - else if (interp.getPointCount() < 2) { + else if (!interp.bothSides()) { angle = interp.getRandomAngle(); fout << "case random "; + pointNotInterp = true; } else { angle = interp.interpolateAngle(fout); + pointNotInterp = false; fout << "case interp "; } fout << "raw " << angle << " "; @@ -3042,7 +3061,7 @@ void Adaptive2d::ProcessPolyNode(Paths boundPaths, Paths toolBoundPaths) areaPD = area / double(stepScaled); // area per distance fout << "addPoint " << areaPD << " " << angle << " "; double error = areaPD - targetAreaPD; - interp.addPoint(error, angle); + interp.addPoint(error, angle, pointNotInterp); fout << "areaPD " << areaPD << " error " << error << " "; // cout << " iter:" << iteration << " angle:" << angle << " area:" << areaPD // << " target:" << targetAreaPD << " error:" << error << " max:" << maxError @@ -3066,6 +3085,7 @@ void Adaptive2d::ProcessPolyNode(Paths boundPaths, Paths toolBoundPaths) fout << endl; prev_error = error; } + fout << "Iterations: " << iteration << endl; Perf_PointIterations.Stop(); recalcArea = false; @@ -3188,6 +3208,7 @@ 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; From c5db88b8c62cf9199dcc07f862beafc538c0a506 Mon Sep 17 00:00:00 2001 From: David Kaufman Date: Thu, 18 Sep 2025 13:12:34 -0400 Subject: [PATCH 3/9] Fix units/extract RESOLUTION_FACTOR from area constants Most of the code treats RESOLUTION_FACTOR as "number of clipper units in a single step" -- a linear distance unit. However, there are a few locations in which it is treated as unitless, multiplied by area constants. This commit folds the current value of RESOLUTION_FACTOR into these other contents in preparation for declaring it to have distance units and increasing its value. --- src/Mod/CAM/libarea/Adaptive.cpp | 9 ++++----- src/Mod/CAM/libarea/Adaptive.hpp | 6 +++--- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/Mod/CAM/libarea/Adaptive.cpp b/src/Mod/CAM/libarea/Adaptive.cpp index ad999004ae..2f34baa884 100644 --- a/src/Mod/CAM/libarea/Adaptive.cpp +++ b/src/Mod/CAM/libarea/Adaptive.cpp @@ -3153,8 +3153,7 @@ void Adaptive2d::ProcessPolyNode(Paths boundPaths, Paths toolBoundPaths) prevDistTrend = distanceTrend; prevDistFromStart = distFromStart; - if (area > 0.5 * MIN_CUT_AREA_FACTOR * optimalCutAreaPD - * RESOLUTION_FACTOR) { // cut is ok - record it + if (area > 0.5 * MIN_CUT_AREA_FACTOR * optimalCutAreaPD) { // cut is ok - record it fout << "\tFinal cut acceptance" << endl; noCutDistance = 0; if (toClearPath.empty()) { @@ -3220,7 +3219,7 @@ void Adaptive2d::ProcessPolyNode(Paths boundPaths, Paths toolBoundPaths) cleared.ExpandCleared(toClearPath); toClearPath.clear(); } - if (cumulativeCutArea > MIN_CUT_AREA_FACTOR * optimalCutAreaPD * RESOLUTION_FACTOR) { + if (cumulativeCutArea > MIN_CUT_AREA_FACTOR * optimalCutAreaPD) { Path cleaned; CleanPath(passToolPath, cleaned, CLEAN_PATH_TOLERANCE); total_output_points += long(cleaned.size()); @@ -3250,7 +3249,7 @@ void Adaptive2d::ProcessPolyNode(Paths boundPaths, Paths toolBoundPaths) this, cleared, moveDistance, - ENGAGE_AREA_THR_FACTOR * optimalCutAreaPD * RESOLUTION_FACTOR, + ENGAGE_AREA_THR_FACTOR * optimalCutAreaPD, 4 * referenceCutArea * stepOverFactor )) { // check if there are any uncleared area left @@ -3288,7 +3287,7 @@ void Adaptive2d::ProcessPolyNode(Paths boundPaths, Paths toolBoundPaths) this, cleared, moveDistance, - ENGAGE_AREA_THR_FACTOR * optimalCutAreaPD * RESOLUTION_FACTOR, + ENGAGE_AREA_THR_FACTOR * optimalCutAreaPD, 4 * referenceCutArea * stepOverFactor )) { break; diff --git a/src/Mod/CAM/libarea/Adaptive.hpp b/src/Mod/CAM/libarea/Adaptive.hpp index 6dc9ff9788..550dce3f07 100644 --- a/src/Mod/CAM/libarea/Adaptive.hpp +++ b/src/Mod/CAM/libarea/Adaptive.hpp @@ -208,9 +208,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; // used for filtering out of insignificant cuts (should - // be < ENGAGE_AREA_THR_FACTOR) - const double ENGAGE_AREA_THR_FACTOR = 0.5; // influences minimal engage area + const double MIN_CUT_AREA_FACTOR = 0.1 + * 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_SCAN_DISTANCE_FACTOR = 0.2; // influences the engage scan/stepping distance const double CLEAN_PATH_TOLERANCE = 1.41; // should be >1 From ccce111ea52575742a7dd6262cae4b63d3d129a2 Mon Sep 17 00:00:00 2001 From: David Kaufman Date: Thu, 18 Sep 2025 18:16:48 -0400 Subject: [PATCH 4/9] rename resolution_factor to min_step_clipper --- src/Mod/CAM/libarea/Adaptive.cpp | 51 ++++++++++++-------------------- src/Mod/CAM/libarea/Adaptive.hpp | 2 +- 2 files changed, 20 insertions(+), 33 deletions(-) diff --git a/src/Mod/CAM/libarea/Adaptive.cpp b/src/Mod/CAM/libarea/Adaptive.cpp index 2f34baa884..11bd7e489f 100644 --- a/src/Mod/CAM/libarea/Adaptive.cpp +++ b/src/Mod/CAM/libarea/Adaptive.cpp @@ -1590,7 +1590,7 @@ double Adaptive2d::CalcCutArea( maxFi += 2 * std::numbers::pi; } - if (preventConventional && interPathLen >= RESOLUTION_FACTOR) { + 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))), @@ -1608,7 +1608,7 @@ double Adaptive2d::CalcCutArea( double scanDistance = 2.5 * toolRadiusScaled; // stepping through path discretized to stepDistance - double stepDistance = min(double(RESOLUTION_FACTOR), interPathLen / 24) + 1; + 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++) { @@ -1762,29 +1762,16 @@ std::list Adaptive2d::Execute( //********************************** // keep the tolerance in workable range - if (tolerance < 0.01) { - tolerance = 0.01; - } - if (tolerance > 0.2) { - tolerance = 0.2; - } + tolerance = max(tolerance, 0.01); + tolerance = min(tolerance, 1.0); - scaleFactor = RESOLUTION_FACTOR / tolerance; - long maxScaleFactor = toolDiameter < 1.0 ? 10000 : 1000; - - if (stepOverFactor * toolDiameter < 1.0) { - scaleFactor *= 1.0 / (stepOverFactor * toolDiameter); - } - - - if (scaleFactor > maxScaleFactor) { - scaleFactor = maxScaleFactor; - } - // scaleFactor = round(scaleFactor); + // 1/"tolerance" = number of min-size adaptive steps per stepover + scaleFactor = MIN_STEP_CLIPPER / tolerance / (stepOverFactor * toolDiameter); current_region = 0; cout << "Tool Diameter: " << toolDiameter << endl; - cout << "Accuracy: " << round(10000.0 / scaleFactor) / 10 << " um" << endl; + cout << "Min step size: " << round(MIN_STEP_CLIPPER / scaleFactor * 1000 * 10) / 10 << " um" + << endl; cout << flush; toolRadiusScaled = long(toolDiameter * scaleFactor / 2); @@ -1932,8 +1919,8 @@ std::list Adaptive2d::Execute( if (opType == OperationType::otProfilingInside || opType == OperationType::otProfilingOutside) { double offset = opType == OperationType::otProfilingInside - ? -2 * (helixRampRadiusScaled + toolRadiusScaled) - RESOLUTION_FACTOR - : 2 * (helixRampRadiusScaled + toolRadiusScaled) + RESOLUTION_FACTOR; + ? -2 * (helixRampRadiusScaled + toolRadiusScaled) - MIN_STEP_CLIPPER + : 2 * (helixRampRadiusScaled + toolRadiusScaled) + MIN_STEP_CLIPPER; for (const auto& current : inputPaths) { int nesting = getPathNestingLevel(current, inputPaths); if (nesting % 2 != 0 && (polyTreeNestingLimit == 0 || nesting <= polyTreeNestingLimit)) { @@ -2010,7 +1997,7 @@ bool Adaptive2d::FindEntryPoint( for (int iter = 0; iter < 10; iter++) { clipof.Clear(); clipof.AddPaths(checkPaths, JoinType::jtSquare, EndType::etClosedPolygon); - double step = RESOLUTION_FACTOR; + double step = MIN_STEP_CLIPPER; double currentDelta = -1; clipof.Execute(incOffset, currentDelta); while (!incOffset.empty()) { @@ -2196,7 +2183,7 @@ bool Adaptive2d::IsAllowedToCutTrough( else { Clipper clip; double distance = sqrt(DistanceSqrd(p1, p2)); - double stepSize = min(0.5 * stepOverScaled, 8 * RESOLUTION_FACTOR); + double stepSize = min(0.5 * stepOverScaled, 8 * MIN_STEP_CLIPPER); if (distance < stepSize / 2) { // not significant cut Perf_IsAllowedToCutTrough.Stop(); return true; @@ -2246,7 +2233,7 @@ bool Adaptive2d::ResolveLinkPath( double directDistance = sqrt(DistanceSqrd(startPoint, endPoint)); Paths linkPaths; - double scanStep = 2 * RESOLUTION_FACTOR; + double scanStep = 2 * MIN_STEP_CLIPPER; if (scanStep > scaleFactor * 0.1) { scanStep = scaleFactor * 0.1; } @@ -2431,8 +2418,8 @@ bool Adaptive2d::MakeLeadPath( for (int i = 0; i < 10000; i++) { if (IsAllowedToCutTrough( IntPoint( - currentPoint.X + RESOLUTION_FACTOR * nextDir.X, - currentPoint.Y + RESOLUTION_FACTOR * nextDir.Y + currentPoint.X + MIN_STEP_CLIPPER * nextDir.X, + currentPoint.Y + MIN_STEP_CLIPPER * nextDir.Y ), nextPoint, clearedArea, @@ -2853,14 +2840,14 @@ void Adaptive2d::ProcessPolyNode(Paths boundPaths, Paths toolBoundPaths) Perf_ProcessPolyNode.Stop(); return; } - fout << "Helix entry " << entryPoint << endl; + fout << "Helix entry " << entryPoint << "\n"; } EngagePoint engage(engageBounds); // engage point stepping instance if (outsideEntry) { - engage.moveToClosestPoint(toolPos, 2 * RESOLUTION_FACTOR); - engage.moveForward(RESOLUTION_FACTOR); + engage.moveToClosestPoint(toolPos, 2 * MIN_STEP_CLIPPER); + engage.moveForward(MIN_STEP_CLIPPER); toolPos = engage.getCurrentPoint(); toolDir = engage.getCurrentDir(); entryPoint = toolPos; @@ -2874,7 +2861,7 @@ void Adaptive2d::ProcessPolyNode(Paths boundPaths, Paths toolBoundPaths) output.HelixCenterPoint.first = double(entryPoint.X) / scaleFactor; output.HelixCenterPoint.second = double(entryPoint.Y) / scaleFactor; - long stepScaled = long(RESOLUTION_FACTOR); + long stepScaled = long(MIN_STEP_CLIPPER); IntPoint engagePoint; IntPoint newToolPos; diff --git a/src/Mod/CAM/libarea/Adaptive.hpp b/src/Mod/CAM/libarea/Adaptive.hpp index 550dce3f07..03b63ad270 100644 --- a/src/Mod/CAM/libarea/Adaptive.hpp +++ b/src/Mod/CAM/libarea/Adaptive.hpp @@ -200,7 +200,7 @@ private: void ApplyStockToLeave(Paths& inputPaths); private: // constants for fine tuning - const double RESOLUTION_FACTOR = 16.0; + const double MIN_STEP_CLIPPER = 16.0; 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%*/ From cfe3437b9be9f4be09879a93c6ebc3c7152d5197 Mon Sep 17 00:00:00 2001 From: David Kaufman Date: Thu, 18 Sep 2025 18:18:19 -0400 Subject: [PATCH 5/9] avoid retrying same integer points in optimization routine --- src/Mod/CAM/libarea/Adaptive.cpp | 130 ++++++++++++++++++++----------- 1 file changed, 86 insertions(+), 44 deletions(-) diff --git a/src/Mod/CAM/libarea/Adaptive.cpp b/src/Mod/CAM/libarea/Adaptive.cpp index 11bd7e489f..554953bf79 100644 --- a/src/Mod/CAM/libarea/Adaptive.cpp +++ b/src/Mod/CAM/libarea/Adaptive.cpp @@ -927,6 +927,7 @@ public: } inline void Start() { +#define DEV_MODE #ifdef DEV_MODE start_ticks = clock(); if (running) { @@ -1127,7 +1128,7 @@ public: return m_min && m_max && m_min->second < 0 && m_max->second >= 0; } // adds point keeping the incremental order of areas for interpolation to work correctly - void addPoint(double error, double angle, bool allowSkip = false) + void addPoint(double error, std::pair angle, bool allowSkip = false) { if (!m_min) { m_min = {angle, error}; @@ -1162,7 +1163,7 @@ public: } } - double interpolateAngle(ofstream& fout) + double interpolateAngle() { if (!m_min) { return MIN_ANGLE; @@ -1171,9 +1172,6 @@ public: if (!m_max) { return MAX_ANGLE; } - - fout << "(" << m_min->first << ", " << m_min->second << ") ~ (" << m_max->first << ", " - << m_max->second << ") "; double p = (0 - m_min->second) / (m_max->second - m_min->second); // Ensure search is sufficiently efficient -- this is a compromise @@ -1183,7 +1181,7 @@ public: const double minInterp = .2; p = max(min(p, 1 - minInterp), minInterp); - return m_min->first * (1 - p) + m_max->first * p; + return m_min->first.first * (1 - p) + m_max->first.first * p; } double clampAngle(double angle) @@ -1201,8 +1199,9 @@ public: } public: - std::optional> m_min; - std::optional> m_max; + //{{angle, clipper point}, error} + std::optional, double>> m_min; + std::optional, double>> m_max; }; //*************************************** @@ -2485,7 +2484,6 @@ void Adaptive2d::AppendToolPath( ); IntPoint startPoint(long(lastTPoint.first * scaleFactor), long(lastTPoint.second * scaleFactor)); - ClipperOffset clipof; // first we try to cut through the linking move for short distances bool linkFound = false; double linkDistance = sqrt(DistanceSqrd(startPoint, endPoint)); @@ -2776,11 +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"); - fout << endl << endl << "----------------------" << endl; - fout << "Start ProcessPolyNode" << endl; + // 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; @@ -2833,7 +2850,7 @@ void Adaptive2d::ProcessPolyNode(Paths boundPaths, Paths toolBoundPaths) engageBounds.push_back(p); } outsideEntry = true; - fout << "Outside entry " << entryPoint << endl; + fout << "Outside entry " << entryPoint << "\n"; } else { if (!FindEntryPoint(progressPaths, toolBoundPaths, boundPaths, cleared, entryPoint, toolPos, toolDir)) { @@ -2903,7 +2920,7 @@ void Adaptive2d::ProcessPolyNode(Paths boundPaths, Paths toolBoundPaths) // LOOP - PASSES //******************************* for (long pass = 0; pass < PASSES_LIMIT; pass++) { - fout << "New pass! " << pass << endl; + fout << "New pass! " << pass << "\n"; if (stopProcessing) { break; } @@ -2942,7 +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 << endl << "Point " << point_index << endl; + fout << "\n" << "Point " << point_index << "\n"; if (stopProcessing) { break; } @@ -2962,33 +2979,33 @@ void Adaptive2d::ProcessPolyNode(Paths boundPaths, Paths toolBoundPaths) double targetAreaPD = optimalCutAreaPD; - // set the step size - double slowDownDistance = max(double(toolRadiusScaled) / 4, RESOLUTION_FACTOR * 8); + // set the step size: 1x to 8x base size + double slowDownDistance = max(double(toolRadiusScaled) / 4, MIN_STEP_CLIPPER * 8); if (distanceToBoundary < slowDownDistance || distanceToEngage < slowDownDistance) { - stepScaled = long(RESOLUTION_FACTOR); + stepScaled = long(MIN_STEP_CLIPPER); } else if (fabs(angle) > NTOL) { - stepScaled = long(RESOLUTION_FACTOR / fabs(angle)); + stepScaled = long(MIN_STEP_CLIPPER / fabs(angle)); } else { - stepScaled = long(RESOLUTION_FACTOR * 4); + stepScaled = long(MIN_STEP_CLIPPER * 8); } // clamp the step size - for stability - if (stepScaled > min(long(toolRadiusScaled / 4), long(RESOLUTION_FACTOR * 8))) { - stepScaled = min(long(toolRadiusScaled / 4), long(RESOLUTION_FACTOR * 8)); + if (stepScaled > min(long(toolRadiusScaled / 4), long(MIN_STEP_CLIPPER * 8))) { + stepScaled = min(long(toolRadiusScaled / 4), long(MIN_STEP_CLIPPER * 8)); } - if (stepScaled < RESOLUTION_FACTOR) { - stepScaled = long(RESOLUTION_FACTOR); + if (stepScaled < MIN_STEP_CLIPPER) { + stepScaled = long(MIN_STEP_CLIPPER); } - fout << "\tstepScaled " << stepScaled << endl; + fout << "\tstepScaled " << stepScaled << "\n"; //***************************** // ANGLE vs AREA ITERATIONS //***************************** double predictedAngle = averageDV(angleHistory); double maxError = AREA_ERROR_FACTOR * optimalCutAreaPD; - fout << "optimal area " << optimalCutAreaPD << " maxError " << maxError << endl; + fout << "optimal area " << optimalCutAreaPD << " maxError " << maxError << "\n"; double area = 0; double areaPD = 0; interp.clear(); @@ -3012,7 +3029,10 @@ void Adaptive2d::ProcessPolyNode(Paths boundPaths, Paths toolBoundPaths) } else if (iteration == 2) { if (interp.bothSides()) { - angle = interp.interpolateAngle(fout); + 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 "; } @@ -3022,13 +3042,11 @@ void Adaptive2d::ProcessPolyNode(Paths boundPaths, Paths toolBoundPaths) pointNotInterp = true; } } - else if (!interp.bothSides()) { - angle = interp.getRandomAngle(); - fout << "case random "; - pointNotInterp = true; - } else { - angle = interp.interpolateAngle(fout); + 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 "; } @@ -3043,12 +3061,36 @@ void Adaptive2d::ProcessPolyNode(Paths boundPaths, Paths toolBoundPaths) ); fout << "int pos " << newToolPos << " "; + // Skip iteration if this IntPoint has already been processed + if (interp.m_min && newToolPos == interp.m_min->first.second) { + interp.addPoint(interp.m_min->second, {angle, newToolPos}); + if (interp.m_max + && abs(interp.m_min->first.second.X - interp.m_max->first.second.X) <= 1 + && abs(interp.m_min->first.second.Y - interp.m_max->first.second.Y) <= 1) { + fout << "hit integer floor" << "\n"; + break; + } + fout << "skip area calc " << "\n"; + continue; + } + if (interp.m_max && newToolPos == interp.m_max->first.second) { + interp.addPoint(interp.m_max->second, {angle, newToolPos}); + if (interp.m_min + && abs(interp.m_min->first.second.X - interp.m_max->first.second.X) <= 1 + && abs(interp.m_min->first.second.Y - interp.m_max->first.second.Y) <= 1) { + fout << "hit integer floor" << "\n"; + 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, pointNotInterp); + interp.addPoint(error, {angle, newToolPos}, pointNotInterp); fout << "areaPD " << areaPD << " error " << error << " "; // cout << " iter:" << iteration << " angle:" << angle << " area:" << areaPD // << " target:" << targetAreaPD << " error:" << error << " max:" << maxError @@ -3058,22 +3100,22 @@ void Adaptive2d::ProcessPolyNode(Paths boundPaths, Paths toolBoundPaths) if (angleHistory.size() > ANGLE_HISTORY_POINTS) { angleHistory.erase(angleHistory.begin()); } - fout << "small enough" << endl; + fout << "small enough" << "\n"; break; } if (iteration > 5 && fabs(error - prev_error) < 0.001) { - fout << "no change" << endl; + fout << "no change" << "\n"; break; } if (iteration == MAX_ITERATIONS - 1) { - fout << "too many iterations!" << endl; + fout << "too many iterations!" << "\n"; total_exceeded++; } - fout << endl; + fout << "\n"; prev_error = error; } - fout << "Iterations: " << iteration << endl; Perf_PointIterations.Stop(); + fout << "Iterations: " << iteration << "\n"; recalcArea = false; // approach end boundary tangentially @@ -3092,7 +3134,7 @@ void Adaptive2d::ProcessPolyNode(Paths boundPaths, Paths toolBoundPaths) long(toolPos.Y + newToolDir.Y * stepScaled) ); recalcArea = true; - fout << "\tRewrote tooldir/toolpos for boundary approach" << endl; + fout << "\tRewrote tooldir/toolpos for boundary approach" << "\n"; } //********************************************** @@ -3108,7 +3150,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..." << endl; + fout << "\tMoving tool back within boundary..." << "\n"; } if (rotateStep >= 180) { #ifdef DEV_MODE @@ -3124,7 +3166,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!!!" << endl; + fout << "\tCut area too big!!!" << "\n"; break; } @@ -3141,7 +3183,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" << endl; + fout << "\tFinal cut acceptance" << "\n"; noCutDistance = 0; if (toClearPath.empty()) { toClearPath.push_back(toolPos); @@ -3198,7 +3240,7 @@ void Adaptive2d::ProcessPolyNode(Paths boundPaths, Paths toolBoundPaths) break; } noCutDistance += stepScaled; - fout << "\tFailed to accept point??" << endl; + fout << "\tFailed to accept point??" << "\n"; } } /* end of points loop*/ From 690a6cc993cc0bbe3a1b3fcde2843008d80c568f Mon Sep 17 00:00:00 2001 From: David Kaufman Date: Wed, 8 Oct 2025 13:14:54 -0400 Subject: [PATCH 6/9] fix adaptive early exit condition for testing repeat integer points --- src/Mod/CAM/libarea/Adaptive.cpp | 35 ++++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/src/Mod/CAM/libarea/Adaptive.cpp b/src/Mod/CAM/libarea/Adaptive.cpp index 554953bf79..036c630cb9 100644 --- a/src/Mod/CAM/libarea/Adaptive.cpp +++ b/src/Mod/CAM/libarea/Adaptive.cpp @@ -2794,8 +2794,8 @@ struct nostream void Adaptive2d::ProcessPolyNode(Paths boundPaths, Paths toolBoundPaths) { - // ofstream fout("adaptive_debug.txt"); - nostream fout("adaptive_debug.txt"); + ofstream fout("adaptive_debug.txt"); + // nostream fout("adaptive_debug.txt"); fout << "\n" << "\n" << "----------------------" << "\n"; fout << "Start ProcessPolyNode" << "\n"; Perf_ProcessPolyNode.Start(); @@ -3062,23 +3062,36 @@ void Adaptive2d::ProcessPolyNode(Paths boundPaths, Paths toolBoundPaths) fout << "int pos " << newToolPos << " "; // Skip iteration if this IntPoint has already been processed + bool intRepeat = false; if (interp.m_min && newToolPos == interp.m_min->first.second) { interp.addPoint(interp.m_min->second, {angle, newToolPos}); - if (interp.m_max - && abs(interp.m_min->first.second.X - interp.m_max->first.second.X) <= 1 - && abs(interp.m_min->first.second.Y - interp.m_max->first.second.Y) <= 1) { - fout << "hit integer floor" << "\n"; - break; - } - fout << "skip area calc " << "\n"; - continue; + intRepeat = true; } if (interp.m_max && newToolPos == interp.m_max->first.second) { interp.addPoint(interp.m_max->second, {angle, newToolPos}); - if (interp.m_min + intRepeat = true; + } + + if (intRepeat) { + if (interp.m_min && interp.m_max && abs(interp.m_min->first.second.X - interp.m_max->first.second.X) <= 1 && abs(interp.m_min->first.second.Y - interp.m_max->first.second.Y) <= 1) { + if (pointNotInterp) { + // if this happens while testing min/max of the range it doesn't mean + // anything; only exit early if interpolation is down to adjacent + // integers + continue; + } fout << "hit integer floor" << "\n"; + // exit early, selecting the better of the two adjacent integers + if (abs(interp.m_min->second) < abs(interp.m_max->second)) { + newToolDir = rotate(toolDir, interp.m_min->first.first); + newToolPos = interp.m_min->first.second; + } + else { + newToolDir = rotate(toolDir, interp.m_max->first.first); + newToolPos = interp.m_max->first.second; + } break; } fout << "skip area calc " << "\n"; From 677cffdc7a07c7fbe345fbae4ffe438a800073fd Mon Sep 17 00:00:00 2001 From: David Kaufman Date: Thu, 18 Sep 2025 18:46:46 -0400 Subject: [PATCH 7/9] cleanup --- src/Mod/CAM/Path/Op/Adaptive.py | 3 -- src/Mod/CAM/libarea/Adaptive.cpp | 70 +------------------------------- 2 files changed, 1 insertion(+), 72 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 036c630cb9..3ffe378566 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 @@ -927,7 +926,6 @@ public: } inline void Start() { -#define DEV_MODE #ifdef DEV_MODE start_ticks = clock(); if (running) { @@ -1189,17 +1187,13 @@ 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; }; @@ -2774,30 +2768,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"); - // nostream fout("adaptive_debug.txt"); - fout << "\n" << "\n" << "----------------------" << "\n"; - fout << "Start ProcessPolyNode" << "\n"; Perf_ProcessPolyNode.Start(); current_region++; cout << "** Processing region: " << current_region << endl; @@ -2850,14 +2822,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 @@ -2914,13 +2884,11 @@ 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; } @@ -2959,7 +2927,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; } @@ -2998,14 +2965,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(); @@ -3016,50 +2981,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; @@ -3082,7 +3032,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 if (abs(interp.m_min->second) < abs(interp.m_max->second)) { newToolDir = rotate(toolDir, interp.m_min->first.first); @@ -3094,17 +3043,14 @@ void Adaptive2d::ProcessPolyNode(Paths boundPaths, Paths toolBoundPaths) } 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; @@ -3113,22 +3059,14 @@ 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 @@ -3147,7 +3085,6 @@ void Adaptive2d::ProcessPolyNode(Paths boundPaths, Paths toolBoundPaths) long(toolPos.Y + newToolDir.Y * stepScaled) ); recalcArea = true; - fout << "\tRewrote tooldir/toolpos for boundary approach" << "\n"; } //********************************************** @@ -3163,7 +3100,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..." << "\n"; } if (rotateStep >= 180) { #ifdef DEV_MODE @@ -3179,7 +3115,6 @@ 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; } @@ -3196,7 +3131,6 @@ 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); @@ -3249,11 +3183,9 @@ 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 8c49638f5f16c09b2afbc9694ee62dca9161635b Mon Sep 17 00:00:00 2001 From: David Kaufman Date: Wed, 8 Oct 2025 17:00:37 -0400 Subject: [PATCH 8/9] fix cam tests tbh the test seems kind of unstable --- 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 3ffe378566..c225d057d0 100644 --- a/src/Mod/CAM/libarea/Adaptive.cpp +++ b/src/Mod/CAM/libarea/Adaptive.cpp @@ -1759,7 +1759,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 / (stepOverFactor * toolDiameter); + scaleFactor = MIN_STEP_CLIPPER / tolerance / min(1.0, stepOverFactor * toolDiameter); current_region = 0; cout << "Tool Diameter: " << toolDiameter << endl; From 56c3e76c6672c525447813198cdc2b8ac84cfbe8 Mon Sep 17 00:00:00 2001 From: David Kaufman Date: Fri, 7 Nov 2025 17:09:41 -0500 Subject: [PATCH 9/9] re-fix early exit condition for repeat integer points --- src/Mod/CAM/libarea/Adaptive.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Mod/CAM/libarea/Adaptive.cpp b/src/Mod/CAM/libarea/Adaptive.cpp index c225d057d0..5a3015a190 100644 --- a/src/Mod/CAM/libarea/Adaptive.cpp +++ b/src/Mod/CAM/libarea/Adaptive.cpp @@ -3014,11 +3014,11 @@ void Adaptive2d::ProcessPolyNode(Paths boundPaths, Paths toolBoundPaths) // Skip iteration if this IntPoint has already been processed bool intRepeat = false; if (interp.m_min && newToolPos == interp.m_min->first.second) { - interp.addPoint(interp.m_min->second, {angle, newToolPos}); + interp.m_min = {{angle, newToolPos}, interp.m_min->second}; intRepeat = true; } if (interp.m_max && newToolPos == interp.m_max->first.second) { - interp.addPoint(interp.m_max->second, {angle, newToolPos}); + interp.m_max = {{angle, newToolPos}, interp.m_max->second}; intRepeat = true; } @@ -3033,14 +3033,19 @@ void Adaptive2d::ProcessPolyNode(Paths boundPaths, Paths toolBoundPaths) continue; } // exit early, selecting the better of the two adjacent integers + double error; if (abs(interp.m_min->second) < abs(interp.m_max->second)) { newToolDir = rotate(toolDir, interp.m_min->first.first); newToolPos = interp.m_min->first.second; + error = interp.m_min->second; } else { newToolDir = rotate(toolDir, interp.m_max->first.first); newToolPos = interp.m_max->first.second; + error = interp.m_max->second; } + areaPD = error + targetAreaPD; + area = areaPD * double(stepScaled); break; } continue;