CAM: Fix pure vertical linking move (#26195)
This commit is contained in:
@@ -82,6 +82,63 @@ class TestGetLinkingMoves(PathTestUtils.PathTestBase):
|
||||
solids=[blocking_box],
|
||||
)
|
||||
|
||||
def test_plunge_to_zero_depth(self):
|
||||
"""Test that plunge moves correctly go to Z=0 (regression test for depth==0 bug)"""
|
||||
start = FreeCAD.Vector(0, 0, 1) # Start below clearance
|
||||
target = FreeCAD.Vector(10, 10, 0) # Target depth is 0
|
||||
|
||||
cmds = generator.get_linking_moves(
|
||||
start_position=start,
|
||||
target_position=target,
|
||||
local_clearance=self.local_clearance,
|
||||
global_clearance=self.global_clearance,
|
||||
tool_shape=self.tool,
|
||||
solids=[],
|
||||
)
|
||||
|
||||
# Verify we got commands
|
||||
self.assertGreater(len(cmds), 0)
|
||||
|
||||
# All commands should have complete XYZ coordinates
|
||||
for cmd in cmds:
|
||||
self.assertIn("X", cmd.Parameters, "Command missing X coordinate")
|
||||
self.assertIn("Y", cmd.Parameters, "Command missing Y coordinate")
|
||||
self.assertIn("Z", cmd.Parameters, "Command missing Z coordinate")
|
||||
|
||||
# The last command should be the plunge to target depth (Z=0)
|
||||
last_cmd = cmds[-1]
|
||||
self.assertAlmostEqual(last_cmd.Parameters["X"], target.x, places=5)
|
||||
self.assertAlmostEqual(last_cmd.Parameters["Y"], target.y, places=5)
|
||||
self.assertAlmostEqual(
|
||||
last_cmd.Parameters["Z"],
|
||||
target.z,
|
||||
places=5,
|
||||
msg="Final plunge should go to target Z=0, not clearance height",
|
||||
)
|
||||
|
||||
def test_plunge_to_negative_depth(self):
|
||||
"""Test that plunge moves correctly go to negative Z depths"""
|
||||
start = FreeCAD.Vector(0, 0, 1) # Start below clearance
|
||||
target = FreeCAD.Vector(10, 10, -2) # Target depth is negative
|
||||
|
||||
cmds = generator.get_linking_moves(
|
||||
start_position=start,
|
||||
target_position=target,
|
||||
local_clearance=self.local_clearance,
|
||||
global_clearance=self.global_clearance,
|
||||
tool_shape=self.tool,
|
||||
solids=[],
|
||||
)
|
||||
|
||||
# The last command should be the plunge to target depth (Z=-2)
|
||||
last_cmd = cmds[-1]
|
||||
self.assertAlmostEqual(
|
||||
last_cmd.Parameters["Z"],
|
||||
target.z,
|
||||
places=5,
|
||||
msg="Final plunge should go to target Z=-2",
|
||||
)
|
||||
|
||||
@unittest.skip("not yet implemented")
|
||||
def test_zero_retract_offset_uses_local_clearance(self):
|
||||
cmds = generator.get_linking_moves(
|
||||
|
||||
@@ -79,9 +79,23 @@ def get_linking_moves(
|
||||
wire = make_linking_wire(start_position, target_position, height)
|
||||
if is_wire_collision_free(wire, collision_model):
|
||||
cmds = Path.fromShape(wire).Commands
|
||||
for cmd in cmds:
|
||||
cmd.Name = "G0"
|
||||
return cmds
|
||||
# Ensure all commands have complete XYZ coordinates
|
||||
# Path.fromShape() may omit coordinates that don't change
|
||||
current_pos = start_position
|
||||
complete_cmds = []
|
||||
for i, cmd in enumerate(cmds):
|
||||
params = dict(cmd.Parameters)
|
||||
# Fill in missing coordinates from current position
|
||||
x = params.get("X", current_pos.x)
|
||||
y = params.get("Y", current_pos.y)
|
||||
# For the last command (plunge to target), use target.z if Z is missing
|
||||
if "Z" not in params and i == len(cmds) - 1:
|
||||
z = target_position.z
|
||||
else:
|
||||
z = params.get("Z", current_pos.z)
|
||||
complete_cmds.append(Path.Command("G0", {"X": x, "Y": y, "Z": z}))
|
||||
current_pos = Vector(x, y, z)
|
||||
return complete_cmds
|
||||
|
||||
raise RuntimeError("No collision-free path found between start and target positions")
|
||||
|
||||
|
||||
@@ -508,13 +508,10 @@ class ObjectMillFacing(PathOp.ObjectOp):
|
||||
for k in ("X", "Y")
|
||||
):
|
||||
# But if Z is different, keep it (it's a plunge or retract)
|
||||
z_changed = (
|
||||
abs(
|
||||
new_params.get("Z", 0)
|
||||
- last_params.get("Z", new_params.get("Z", 0) + 1)
|
||||
)
|
||||
> 1e-9
|
||||
)
|
||||
# Use sentinel values that won't conflict with depth == 0
|
||||
z_new = new_params.get("Z", float("inf"))
|
||||
z_last = last_params.get("Z", float("-inf"))
|
||||
z_changed = abs(z_new - z_last) > 1e-9
|
||||
if not z_changed:
|
||||
continue
|
||||
self.commandlist.append(Path.Command(cmd.Name, new_params))
|
||||
@@ -596,11 +593,11 @@ class ObjectMillFacing(PathOp.ObjectOp):
|
||||
# Append linking moves, ensuring full XYZ continuity
|
||||
current = last_position
|
||||
for lc in link_commands:
|
||||
params = dict(lc.Parameters)
|
||||
X = params.get("X", current.x)
|
||||
Y = params.get("Y", current.y)
|
||||
Z = params.get("Z", current.z)
|
||||
# Skip zero-length
|
||||
params = lc.Parameters
|
||||
X = params["X"]
|
||||
Y = params["Y"]
|
||||
Z = params["Z"]
|
||||
# Skip zero-length moves
|
||||
if not (
|
||||
abs(X - current.x) <= 1e-9
|
||||
and abs(Y - current.y) <= 1e-9
|
||||
@@ -609,7 +606,7 @@ class ObjectMillFacing(PathOp.ObjectOp):
|
||||
self.commandlist.append(
|
||||
Path.Command(lc.Name, {"X": X, "Y": Y, "Z": Z})
|
||||
)
|
||||
current = FreeCAD.Vector(X, Y, Z)
|
||||
current = FreeCAD.Vector(X, Y, Z)
|
||||
|
||||
# Remove the entire initial G0 bundle (up, XY, down) from the copy
|
||||
del copy_commands[bundle_start:bundle_end]
|
||||
@@ -631,11 +628,13 @@ class ObjectMillFacing(PathOp.ObjectOp):
|
||||
cp["Z"] = depth # Cutting moves at depth
|
||||
else:
|
||||
cp.setdefault("Z", last.get("Z"))
|
||||
# Skip zero-length
|
||||
# Skip zero-length moves
|
||||
if self.commandlist:
|
||||
last = self.commandlist[-1].Parameters
|
||||
# Use sentinel values that won't conflict with depth == 0
|
||||
if all(
|
||||
abs(cp[k] - last.get(k, cp[k] + 1)) <= 1e-9 for k in ("X", "Y", "Z")
|
||||
abs(cp.get(k, float("inf")) - last.get(k, float("-inf"))) <= 1e-9
|
||||
for k in ("X", "Y", "Z")
|
||||
):
|
||||
continue
|
||||
self.commandlist.append(Path.Command(cc.Name, cp))
|
||||
|
||||
Reference in New Issue
Block a user