From d83730b3be41032a84ce70ab609665426578ea74 Mon Sep 17 00:00:00 2001 From: David Kaufman Date: Tue, 6 May 2025 22:35:27 -0400 Subject: [PATCH 1/4] CAM: Allow helix angles between 0 and 1 degree --- src/Mod/CAM/Path/Op/Adaptive.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Mod/CAM/Path/Op/Adaptive.py b/src/Mod/CAM/Path/Op/Adaptive.py index f1305b9cb9..3262bca271 100644 --- a/src/Mod/CAM/Path/Op/Adaptive.py +++ b/src/Mod/CAM/Path/Op/Adaptive.py @@ -140,7 +140,7 @@ def GenerateGCode(op, obj, adaptiveResults, helixDiameter): length = 2 * math.pi * helixRadius - obj.HelixAngle = min(89, max(obj.HelixAngle.Value, 1)) + obj.HelixAngle = min(89.99, max(obj.HelixAngle.Value, 0.01)) obj.HelixConeAngle = max(obj.HelixConeAngle, 0) helixAngleRad = math.radians(obj.HelixAngle) From 31baab0870c5257cec54f99c95d62d985c769fd9 Mon Sep 17 00:00:00 2001 From: David Kaufman Date: Tue, 6 May 2025 22:37:58 -0400 Subject: [PATCH 2/4] CAM: delete dedicated cone helix implementation, merge into standard implementation --- src/Mod/CAM/Path/Op/Adaptive.py | 134 +++++--------------------------- 1 file changed, 19 insertions(+), 115 deletions(-) diff --git a/src/Mod/CAM/Path/Op/Adaptive.py b/src/Mod/CAM/Path/Op/Adaptive.py index 3262bca271..3a7c976f63 100644 --- a/src/Mod/CAM/Path/Op/Adaptive.py +++ b/src/Mod/CAM/Path/Op/Adaptive.py @@ -117,14 +117,6 @@ def discretize(edge, flipDirection=False): return pts -def CalcHelixConePoint(height, cur_z, radius, angle): - x = ((height - cur_z) / height) * radius * math.cos(math.radians(angle) * cur_z) - y = ((height - cur_z) / height) * radius * math.sin(math.radians(angle) * cur_z) - z = cur_z - - return {"X": x, "Y": y, "Z": z} - - def GenerateGCode(op, obj, adaptiveResults, helixDiameter): if not adaptiveResults or not adaptiveResults[0]["AdaptivePaths"]: return @@ -238,117 +230,29 @@ def GenerateGCode(op, obj, adaptiveResults, helixDiameter): ) ) - if obj.HelixConeAngle == 0: - while fi < maxfi: - x = region["HelixCenterPoint"][0] + r * math.cos(fi + offsetFi) - y = region["HelixCenterPoint"][1] + r * math.sin(fi + offsetFi) - z = passStartDepth - fi / maxfi * (passStartDepth - passEndDepth) - op.commandlist.append( - Path.Command("G1", {"X": x, "Y": y, "Z": z, "F": op.vertFeed}) - ) - fi = fi + math.pi / 16 + r_bottom = r - (passStartDepth - passEndDepth) * math.tan(math.radians(obj.HelixConeAngle.Value)) + r_bottom = max(r_bottom, r * .75) # put a limit on how small the cone tip can be - # one more circle at target depth to make sure center is cleared - maxfi = maxfi + 2 * math.pi - while fi < maxfi: - x = region["HelixCenterPoint"][0] + r * math.cos(fi + offsetFi) - y = region["HelixCenterPoint"][1] + r * math.sin(fi + offsetFi) - z = passEndDepth - op.commandlist.append( - Path.Command("G1", {"X": x, "Y": y, "Z": z, "F": op.horizFeed}) - ) - fi = fi + math.pi / 16 - - else: - # Cone - _HelixAngle = 360 - (obj.HelixAngle.Value * 4) - - if obj.HelixConeAngle > 6: - obj.HelixConeAngle = 6 - - helixRadius *= 0.9 - - # Calculate everything - helix_height = passStartDepth - passEndDepth - r_extra = helix_height * math.tan(math.radians(obj.HelixConeAngle)) - HelixTopRadius = helixRadius + r_extra - helix_full_height = HelixTopRadius * ( - math.cos(math.radians(obj.HelixConeAngle)) - / math.sin(math.radians(obj.HelixConeAngle)) - ) - - # Start height - z = passStartDepth - i = 0 - - # Default step down - z_step = 0.05 - - # Bigger angle, smaller step down - if _HelixAngle > 120: - z_step = 0.025 - if _HelixAngle > 240: - z_step = 0.015 - - p = None - # Calculate conical helix - while z >= passEndDepth: - if z < passEndDepth: - z = passEndDepth - - p = CalcHelixConePoint( - helix_full_height, i, HelixTopRadius, _HelixAngle - ) - op.commandlist.append( - Path.Command( - "G1", - { - "X": p["X"] + region["HelixCenterPoint"][0], - "Y": p["Y"] + region["HelixCenterPoint"][1], - "Z": z, - "F": op.vertFeed, - }, - ) - ) - z = z - z_step - i = i + z_step - - # Calculate some stuff for arcs at bottom - p["X"] = p["X"] + region["HelixCenterPoint"][0] - p["Y"] = p["Y"] + region["HelixCenterPoint"][1] - x_m = region["HelixCenterPoint"][0] - p["X"] + region["HelixCenterPoint"][0] - y_m = region["HelixCenterPoint"][1] - p["Y"] + region["HelixCenterPoint"][1] - i_off = (x_m - p["X"]) / 2 - j_off = (y_m - p["Y"]) / 2 - - # One more circle at target depth to make sure center is cleared + while fi < maxfi: + r_current = r * (1 - fi/maxfi) + r_bottom * (fi/maxfi) + x = region["HelixCenterPoint"][0] + r_current * math.cos(fi + offsetFi) + y = region["HelixCenterPoint"][1] + r_current * math.sin(fi + offsetFi) + z = passStartDepth - fi / maxfi * (passStartDepth - passEndDepth) op.commandlist.append( - Path.Command( - "G3", - { - "X": x_m, - "Y": y_m, - "Z": passEndDepth, - "I": i_off, - "J": j_off, - "F": op.horizFeed, - }, - ) - ) - op.commandlist.append( - Path.Command( - "G3", - { - "X": p["X"], - "Y": p["Y"], - "Z": passEndDepth, - "I": -i_off, - "J": -j_off, - "F": op.horizFeed, - }, - ) + Path.Command("G1", {"X": x, "Y": y, "Z": z, "F": op.vertFeed}) ) + fi = fi + math.pi / 16 + # one more circle at target depth to make sure center is cleared + maxfi = maxfi + 2 * math.pi + while fi < maxfi: + x = region["HelixCenterPoint"][0] + r_bottom * math.cos(fi + offsetFi) + y = region["HelixCenterPoint"][1] + r_bottom * math.sin(fi + offsetFi) + z = passEndDepth + op.commandlist.append( + Path.Command("G1", {"X": x, "Y": y, "Z": z, "F": op.horizFeed}) + ) + fi = fi + math.pi / 16 else: # Use arcs for helix - no conical shape support helixStart = [ From 9723d95d4c10cf31deccb8b877627155d46a8839 Mon Sep 17 00:00:00 2001 From: David Kaufman Date: Wed, 7 May 2025 11:15:15 -0400 Subject: [PATCH 3/4] CAM: add spiral-out to full radius at the bottom of the cone helix --- src/Mod/CAM/Path/Op/Adaptive.py | 59 +++++++++++++++++++-------------- 1 file changed, 34 insertions(+), 25 deletions(-) diff --git a/src/Mod/CAM/Path/Op/Adaptive.py b/src/Mod/CAM/Path/Op/Adaptive.py index 3a7c976f63..ef2e1ccd51 100644 --- a/src/Mod/CAM/Path/Op/Adaptive.py +++ b/src/Mod/CAM/Path/Op/Adaptive.py @@ -179,19 +179,24 @@ def GenerateGCode(op, obj, adaptiveResults, helixDiameter): # Helix ramp if helixRadius > 0.01: r = helixRadius - 0.01 - - maxfi = passDepth / depthPerOneCircle * 2 * math.pi - fi = 0 - offsetFi = -maxfi + startAngle - math.pi / 16 - - helixStart = [ - region["HelixCenterPoint"][0] + r * math.cos(offsetFi), - region["HelixCenterPoint"][1] + r * math.sin(offsetFi), - ] - op.commandlist.append(Path.Command("(Helix to depth: %f)" % passEndDepth)) if obj.UseHelixArcs is False: + helix_down_angle = passDepth / depthPerOneCircle * 2 * math.pi + + r_bottom = r - (passStartDepth - passEndDepth) * math.tan(math.radians(obj.HelixConeAngle.Value)) + r_bottom = max(r_bottom, r * .5) # put a limit on how small the cone tip can be + step_over = obj.StepOver * 0.01 * op.tool.Diameter.Value + spiral_out_angle = (r - r_bottom) / step_over * 2 * math.pi + + angle0 = startAngle - helix_down_angle - spiral_out_angle + angle = 0 + + helixStart = [ + region["HelixCenterPoint"][0] + r * math.cos(angle0), + region["HelixCenterPoint"][1] + r * math.sin(angle0), + ] + # rapid move to start point op.commandlist.append(Path.Command("G0", {"Z": obj.ClearanceHeight.Value})) op.commandlist.append( @@ -230,29 +235,33 @@ def GenerateGCode(op, obj, adaptiveResults, helixDiameter): ) ) - r_bottom = r - (passStartDepth - passEndDepth) * math.tan(math.radians(obj.HelixConeAngle.Value)) - r_bottom = max(r_bottom, r * .75) # put a limit on how small the cone tip can be - - while fi < maxfi: - r_current = r * (1 - fi/maxfi) + r_bottom * (fi/maxfi) - x = region["HelixCenterPoint"][0] + r_current * math.cos(fi + offsetFi) - y = region["HelixCenterPoint"][1] + r_current * math.sin(fi + offsetFi) - z = passStartDepth - fi / maxfi * (passStartDepth - passEndDepth) + # helix down + while angle < helix_down_angle: + progress = angle / helix_down_angle + r_current = r * (1 - progress) + r_bottom * progress + x = region["HelixCenterPoint"][0] + r_current * math.cos(angle + angle0) + y = region["HelixCenterPoint"][1] + r_current * math.sin(angle + angle0) + z = passStartDepth - progress * (passStartDepth - passEndDepth) op.commandlist.append( Path.Command("G1", {"X": x, "Y": y, "Z": z, "F": op.vertFeed}) ) - fi = fi + math.pi / 16 + angle = min(angle + math.pi / 16, helix_down_angle) - # one more circle at target depth to make sure center is cleared - maxfi = maxfi + 2 * math.pi - while fi < maxfi: - x = region["HelixCenterPoint"][0] + r_bottom * math.cos(fi + offsetFi) - y = region["HelixCenterPoint"][1] + r_bottom * math.sin(fi + offsetFi) + # spiral out, plus a full extra circle + max_angle = helix_down_angle + spiral_out_angle + 2 * math.pi + while angle < max_angle: + if spiral_out_angle == 0: + progress = 1 + else: + progress = min(1, (angle - helix_down_angle) / spiral_out_angle) + r_current = r_bottom * (1 - progress) + r * progress + x = region["HelixCenterPoint"][0] + r_current * math.cos(angle + angle0) + y = region["HelixCenterPoint"][1] + r_current * math.sin(angle + angle0) z = passEndDepth op.commandlist.append( Path.Command("G1", {"X": x, "Y": y, "Z": z, "F": op.horizFeed}) ) - fi = fi + math.pi / 16 + angle = min(angle + math.pi / 16, max_angle) else: # Use arcs for helix - no conical shape support helixStart = [ From cd3d03c41915c9c357c8c9e2962f6c9f8e2a37a6 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 7 May 2025 15:31:35 +0000 Subject: [PATCH 4/4] CAM: clean up code --- src/Mod/CAM/Path/Op/Adaptive.py | 52 ++++++++++++++++++++++----------- 1 file changed, 35 insertions(+), 17 deletions(-) diff --git a/src/Mod/CAM/Path/Op/Adaptive.py b/src/Mod/CAM/Path/Op/Adaptive.py index ef2e1ccd51..47fe3d883c 100644 --- a/src/Mod/CAM/Path/Op/Adaptive.py +++ b/src/Mod/CAM/Path/Op/Adaptive.py @@ -165,7 +165,7 @@ def GenerateGCode(op, obj, adaptiveResults, helixDiameter): ) for passEndDepth in depthParams.data: - startAngle = math.atan2( + pass_start_angle = math.atan2( region["StartPoint"][1] - region["HelixCenterPoint"][1], region["StartPoint"][0] - region["HelixCenterPoint"][0], ) @@ -184,17 +184,21 @@ def GenerateGCode(op, obj, adaptiveResults, helixDiameter): if obj.UseHelixArcs is False: helix_down_angle = passDepth / depthPerOneCircle * 2 * math.pi - r_bottom = r - (passStartDepth - passEndDepth) * math.tan(math.radians(obj.HelixConeAngle.Value)) - r_bottom = max(r_bottom, r * .5) # put a limit on how small the cone tip can be + r_bottom = r - (passStartDepth - passEndDepth) * math.tan( + math.radians(obj.HelixConeAngle.Value) + ) + r_bottom = max( + r_bottom, r * 0.5 + ) # put a limit on how small the cone tip can be step_over = obj.StepOver * 0.01 * op.tool.Diameter.Value spiral_out_angle = (r - r_bottom) / step_over * 2 * math.pi - angle0 = startAngle - helix_down_angle - spiral_out_angle - angle = 0 + helix_base_angle = pass_start_angle - helix_down_angle - spiral_out_angle + helix_angular_progress = 0 helixStart = [ - region["HelixCenterPoint"][0] + r * math.cos(angle0), - region["HelixCenterPoint"][1] + r * math.sin(angle0), + region["HelixCenterPoint"][0] + r * math.cos(helix_base_angle), + region["HelixCenterPoint"][1] + r * math.sin(helix_base_angle), ] # rapid move to start point @@ -236,32 +240,46 @@ def GenerateGCode(op, obj, adaptiveResults, helixDiameter): ) # helix down - while angle < helix_down_angle: - progress = angle / helix_down_angle + while helix_angular_progress < helix_down_angle: + progress = helix_angular_progress / helix_down_angle r_current = r * (1 - progress) + r_bottom * progress - x = region["HelixCenterPoint"][0] + r_current * math.cos(angle + angle0) - y = region["HelixCenterPoint"][1] + r_current * math.sin(angle + angle0) + x = region["HelixCenterPoint"][0] + r_current * math.cos( + helix_angular_progress + helix_base_angle + ) + y = region["HelixCenterPoint"][1] + r_current * math.sin( + helix_angular_progress + helix_base_angle + ) z = passStartDepth - progress * (passStartDepth - passEndDepth) op.commandlist.append( Path.Command("G1", {"X": x, "Y": y, "Z": z, "F": op.vertFeed}) ) - angle = min(angle + math.pi / 16, helix_down_angle) + helix_angular_progress = min( + helix_angular_progress + math.pi / 16, helix_down_angle + ) # spiral out, plus a full extra circle max_angle = helix_down_angle + spiral_out_angle + 2 * math.pi - while angle < max_angle: + while helix_angular_progress < max_angle: if spiral_out_angle == 0: progress = 1 else: - progress = min(1, (angle - helix_down_angle) / spiral_out_angle) + progress = min( + 1, (helix_angular_progress - helix_down_angle) / spiral_out_angle + ) r_current = r_bottom * (1 - progress) + r * progress - x = region["HelixCenterPoint"][0] + r_current * math.cos(angle + angle0) - y = region["HelixCenterPoint"][1] + r_current * math.sin(angle + angle0) + x = region["HelixCenterPoint"][0] + r_current * math.cos( + helix_angular_progress + helix_base_angle + ) + y = region["HelixCenterPoint"][1] + r_current * math.sin( + helix_angular_progress + helix_base_angle + ) z = passEndDepth op.commandlist.append( Path.Command("G1", {"X": x, "Y": y, "Z": z, "F": op.horizFeed}) ) - angle = min(angle + math.pi / 16, max_angle) + helix_angular_progress = min( + helix_angular_progress + math.pi / 16, max_angle + ) else: # Use arcs for helix - no conical shape support helixStart = [