diff --git a/src/Mod/CAM/Path/Op/Adaptive.py b/src/Mod/CAM/Path/Op/Adaptive.py index 4d8fb39dce..3e310d1e62 100644 --- a/src/Mod/CAM/Path/Op/Adaptive.py +++ b/src/Mod/CAM/Path/Op/Adaptive.py @@ -118,7 +118,7 @@ def discretize(edge, flipDirection=False): return pts -def GenerateGCode(op, obj, adaptiveResults, helixDiameter): +def GenerateGCode(op, obj, adaptiveResults): if not adaptiveResults or not adaptiveResults[0]["AdaptivePaths"]: return @@ -699,7 +699,8 @@ def ExecuteModelAware(op, obj): FreeCADGui.updateGui() try: - helixDiameter = obj.HelixDiameterLimit.Value + helixDiameter = obj.HelixIdealDiameterPercent / 100 * op.tool.Diameter.Value + helixMinDiameter = obj.HelixMinDiameterPercent / 100 * op.tool.Diameter.Value topZ = op.stock.Shape.BoundBox.ZMax obj.Stopped = False obj.StopProcessing = False @@ -774,6 +775,7 @@ def ExecuteModelAware(op, obj): "stockGeometry": stockPaths, "stepover": obj.StepOver, "effectiveHelixDiameter": helixDiameter, + "helixMinDiameter": helixMinDiameter, "operationType": "Clearing", "side": "Outside", "forceInsideOut": obj.ForceInsideOut, @@ -793,6 +795,7 @@ def ExecuteModelAware(op, obj): "stockGeometry": stockPaths, "stepover": obj.StepOver, "effectiveHelixDiameter": helixDiameter, + "helixMinDiameter": helixMinDiameter, "operationType": "Clearing", "side": "Inside", "forceInsideOut": obj.ForceInsideOut, @@ -857,7 +860,8 @@ def ExecuteModelAware(op, obj): a2d = area.Adaptive2d() a2d.stepOverFactor = 0.01 * obj.StepOver a2d.toolDiameter = op.tool.Diameter.Value - a2d.helixRampDiameter = helixDiameter + a2d.helixRampTargetDiameter = helixDiameter + a2d.helixRampMinDiameter = helixMinDiameter a2d.keepToolDownDistRatio = keepToolDownRatio # NOTE: Z stock is handled in our stepdowns a2d.stockToLeave = obj.StockToLeave.Value @@ -946,7 +950,7 @@ def ExecuteModelAware(op, obj): ) # GENERATE - GenerateGCode(op, obj, adaptiveResults, helixDiameter) + GenerateGCode(op, obj, adaptiveResults) if not obj.StopProcessing: Path.Log.info("*** Done. Elapsed time: %f sec\n\n" % (time.time() - start)) @@ -1810,12 +1814,21 @@ class PathAdaptive(PathOp.ObjectOp): ), ) obj.addProperty( - "App::PropertyLength", - "HelixDiameterLimit", + "App::PropertyPercent", + "HelixIdealDiameterPercent", "Adaptive", QT_TRANSLATE_NOOP( "App::Property", - "Limit helix entry diameter, if limit larger than tool diameter or 0, tool diameter is used", + "Ideal helix entry diameter, as a percentage of the tool diameter", + ), + ) + obj.addProperty( + "App::PropertyPercent", + "HelixMinDiameterPercent", + "Adaptive", + QT_TRANSLATE_NOOP( + "App::Property", + "Minimum acceptable helix entry diameter, as a percentage of the tool diameter", ), ) obj.addProperty( @@ -1874,7 +1887,8 @@ class PathAdaptive(PathOp.ObjectOp): obj.StopProcessing = False obj.HelixAngle = 5 obj.HelixConeAngle = 0 - obj.HelixDiameterLimit = 0.0 + obj.HelixIdealDiameterPercent = 100 + obj.HelixMinDiameterPercent = 10 obj.AdaptiveInputState = "" obj.AdaptiveOutputState = "" obj.StockToLeave = 0 @@ -1964,6 +1978,33 @@ class PathAdaptive(PathOp.ObjectOp): obj.addProperty("Part::PropertyPartShape", "removalshape", "Path", "") obj.setEditorMode("removalshape", 2) # hide + if hasattr(obj, "HelixDiameterLimit"): + oldD = obj.HelixDiameterLimit + obj.removeProperty("HelixDiameterLimit") + obj.addProperty( + "App::PropertyPercent", + "HelixIdealDiameterPercent", + "Adaptive", + QT_TRANSLATE_NOOP( + "App::Property", + "Ideal helix entry diameter, as a percentage of the tool diameter", + ), + ) + obj.addProperty( + "App::PropertyPercent", + "HelixMinDiameterPercent", + "Adaptive", + QT_TRANSLATE_NOOP( + "App::Property", + "Minimum acceptable helix entry diameter, as a percentage of the tool diameter", + ), + ) + obj.HelixMinDiameterPercent = 10 + if hasattr(obj, "ToolController"): + obj.HelixIdealDiameterPercent = ( + 75 if oldD == 0 else 100 * oldD / obj.ToolController.Tool.Diameter.Value + ) + FeatureExtensions.initialize_properties(obj) @@ -1986,7 +2027,8 @@ def SetupProperties(): "AdaptiveOutputState", "HelixAngle", "HelixConeAngle", - "HelixDiameterLimit", + "HelixIdealDiameterPercent", + "HelixMinDiameterPercent", "UseOutline", "OrderCutsByRegion", ] diff --git a/src/Mod/CAM/libarea/Adaptive.cpp b/src/Mod/CAM/libarea/Adaptive.cpp index 5a3015a190..d0a52c477d 100644 --- a/src/Mod/CAM/libarea/Adaptive.cpp +++ b/src/Mod/CAM/libarea/Adaptive.cpp @@ -1773,17 +1773,15 @@ std::list Adaptive2d::Execute( lastProgressTime = clock(); stopProcessing = false; - if (helixRampDiameter < NTOL) { - helixRampDiameter = 0.75 * toolDiameter; - } - if (helixRampDiameter > toolDiameter) { - helixRampDiameter = toolDiameter; - } - if (helixRampDiameter < toolDiameter / 8) { - helixRampDiameter = toolDiameter / 8; + if (helixRampTargetDiameter < NTOL) { + helixRampTargetDiameter = toolDiameter; } + helixRampTargetDiameter = min(helixRampTargetDiameter, toolDiameter); + helixRampMinDiameter = max(helixRampMinDiameter, toolDiameter / 8); + helixRampTargetDiameter = max(helixRampTargetDiameter, helixRampMinDiameter); - helixRampRadiusScaled = long(helixRampDiameter * scaleFactor / 2); + helixRampMaxRadiusScaled = long(helixRampTargetDiameter * scaleFactor / 2); + helixRampMinRadiusScaled = long(helixRampMinDiameter * scaleFactor / 2); if (finishingProfile) { finishPassOffsetScaled = long(stepOverScaled / 10); } @@ -1812,7 +1810,7 @@ std::list Adaptive2d::Execute( #ifdef DEV_MODE cout << "optimalCutAreaPD:" << optimalCutAreaPD << " scaleFactor:" << scaleFactor << " toolRadiusScaled:" << toolRadiusScaled - << " helixRampRadiusScaled:" << helixRampRadiusScaled << endl; + << " helixRampMaxRadiusScaled:" << helixRampMaxRadiusScaled << endl; #endif //****************************** // Convert input paths to clipper @@ -1912,8 +1910,8 @@ std::list Adaptive2d::Execute( if (opType == OperationType::otProfilingInside || opType == OperationType::otProfilingOutside) { double offset = opType == OperationType::otProfilingInside - ? -2 * (helixRampRadiusScaled + toolRadiusScaled) - MIN_STEP_CLIPPER - : 2 * (helixRampRadiusScaled + toolRadiusScaled) + MIN_STEP_CLIPPER; + ? -2 * (helixRampMaxRadiusScaled + toolRadiusScaled) - MIN_STEP_CLIPPER + : 2 * (helixRampMaxRadiusScaled + toolRadiusScaled) + MIN_STEP_CLIPPER; for (const auto& current : inputPaths) { int nesting = getPathNestingLevel(current, inputPaths); if (nesting % 2 != 0 && (polyTreeNestingLimit == 0 || nesting <= polyTreeNestingLimit)) { @@ -1977,7 +1975,8 @@ bool Adaptive2d::FindEntryPoint( ClearedArea& clearedArea /*output-initial cleared area by helix*/, IntPoint& entryPoint /*output*/, IntPoint& toolPos, - DoublePoint& toolDir + DoublePoint& toolDir, + long& helixRadiusScaled ) { Paths incOffset; @@ -2018,13 +2017,12 @@ bool Adaptive2d::FindEntryPoint( } } // check if helix fits - if (found) { - // make initial polygon cleared by helix ramp + const auto checkHelixFit = [&](long testHelixRadiusScaled) { clipof.Clear(); Path p1; p1.push_back(entryPoint); clipof.AddPath(p1, JoinType::jtRound, EndType::etOpenRound); - clipof.Execute(clearedPaths, helixRampRadiusScaled + toolRadiusScaled); + clipof.Execute(clearedPaths, (double)(testHelixRadiusScaled + toolRadiusScaled)); CleanPolygons(clearedPaths); // we got first cleared area - check if it is crossing boundary clip.Clear(); @@ -2032,11 +2030,32 @@ bool Adaptive2d::FindEntryPoint( clip.AddPaths(boundPaths, PolyType::ptClip, true); Paths crossing; clip.Execute(ClipType::ctDifference, crossing); - if (!crossing.empty()) { - // helix does not fit to the cutting area + + return crossing.empty(); + }; + + if (found) { + // check that helix fits, and make initial polygon cleared by helix ramp + if (!checkHelixFit(helixRampMinRadiusScaled)) { + // min-size helix does not fit found = false; } else { + // find the largest helix that fits + // minSize = largest known fit; maxSize = largest possible fit + long minSize = helixRampMinRadiusScaled; + long maxSize = helixRampMaxRadiusScaled; + while (minSize < maxSize) { + long testSize = (minSize + maxSize + 1) / 2; // always testSize > minSize + if (checkHelixFit(testSize)) { + minSize = testSize; + } + else { + maxSize = testSize - 1; // always maxSize >= minSize + } + } + helixRadiusScaled = minSize; + checkHelixFit(helixRadiusScaled); // set clearedPaths for final size clearedArea.SetClearedPaths(clearedPaths); } } @@ -2070,14 +2089,15 @@ bool Adaptive2d::FindEntryPoint( hp << entryPoint; clipof.AddPath(hp, JoinType::jtRound, EndType::etOpenRound); Paths hps; - clipof.Execute(hps, helixRampRadiusScaled); + clipof.Execute(hps, helixRadiusScaled); AddPathsToProgress(progressPaths, hps); - toolPos = IntPoint(entryPoint.X, entryPoint.Y - helixRampRadiusScaled); + toolPos = IntPoint(entryPoint.X, entryPoint.Y - helixRadiusScaled); toolDir = DoublePoint(1.0, 0.0); } return found; } + bool Adaptive2d::FindEntryPointOutside( TPaths& progressPaths, const Paths& toolBoundPaths, @@ -2780,6 +2800,7 @@ void Adaptive2d::ProcessPolyNode(Paths boundPaths, Paths toolBoundPaths) ClipperOffset clipof; IntPoint entryPoint; + long helixRadiusScaled; TPaths progressPaths; progressPaths.reserve(10000); @@ -2824,7 +2845,16 @@ void Adaptive2d::ProcessPolyNode(Paths boundPaths, Paths toolBoundPaths) outsideEntry = true; } else { - if (!FindEntryPoint(progressPaths, toolBoundPaths, boundPaths, cleared, entryPoint, toolPos, toolDir)) { + if (!FindEntryPoint( + progressPaths, + toolBoundPaths, + boundPaths, + cleared, + entryPoint, + toolPos, + toolDir, + helixRadiusScaled + )) { Perf_ProcessPolyNode.Stop(); return; } diff --git a/src/Mod/CAM/libarea/Adaptive.hpp b/src/Mod/CAM/libarea/Adaptive.hpp index 03b63ad270..4c0690eab8 100644 --- a/src/Mod/CAM/libarea/Adaptive.hpp +++ b/src/Mod/CAM/libarea/Adaptive.hpp @@ -87,7 +87,8 @@ class Adaptive2d public: Adaptive2d(); double toolDiameter = 5; - double helixRampDiameter = 0; + double helixRampTargetDiameter = 0; + double helixRampMinDiameter = 0; double stepOverFactor = 0.2; double tolerance = 0.1; double stockToLeave = 0; @@ -118,7 +119,8 @@ private: double stepOverScaled = 1; long toolRadiusScaled = 10; long finishPassOffsetScaled = 0; - long helixRampRadiusScaled = 0; + long helixRampMaxRadiusScaled = 0; + long helixRampMinRadiusScaled = 0; double referenceCutArea = 0; double optimalCutAreaPD = 0; bool stopProcessing = false; @@ -136,7 +138,8 @@ private: ClearedArea& cleared /*output*/, IntPoint& entryPoint /*output*/, IntPoint& toolPos, - DoublePoint& toolDir + DoublePoint& toolDir, + long& helixRadiusScaled ); bool FindEntryPointOutside( TPaths& progressPaths, diff --git a/src/Mod/CAM/libarea/pyarea.cpp b/src/Mod/CAM/libarea/pyarea.cpp index ee3cfd483b..5cecf5c1b3 100644 --- a/src/Mod/CAM/libarea/pyarea.cpp +++ b/src/Mod/CAM/libarea/pyarea.cpp @@ -426,7 +426,8 @@ void init_pyarea(py::module& m) .def_readwrite("stepOverFactor", &Adaptive2d::stepOverFactor) .def_readwrite("toolDiameter", &Adaptive2d::toolDiameter) .def_readwrite("stockToLeave", &Adaptive2d::stockToLeave) - .def_readwrite("helixRampDiameter", &Adaptive2d::helixRampDiameter) + .def_readwrite("helixRampTargetDiameter", &Adaptive2d::helixRampTargetDiameter) + .def_readwrite("helixRampMinDiameter", &Adaptive2d::helixRampMinDiameter) .def_readwrite("forceInsideOut", &Adaptive2d::forceInsideOut) .def_readwrite("finishingProfile", &Adaptive2d::finishingProfile) //.def_readwrite("polyTreeNestingLimit", &Adaptive2d::polyTreeNestingLimit)