diff --git a/parol6/client/dry_run_client.py b/parol6/client/dry_run_client.py index a60c6fb..8b04a49 100644 --- a/parol6/client/dry_run_client.py +++ b/parol6/client/dry_run_client.py @@ -238,14 +238,22 @@ def _snap_to_angles(self, angles_deg: list[float]) -> DryRunResult: """Snap to angles instantly (no trajectory) — used by Home and Teleport. Both establish position references, so subsequent planned moves pass - the homed gate.""" - self._planner.flush() + the homed gate. Blended moves still buffered in the planner are + planned first and lead the returned result, so their paths — and any + refusal — reach the caller exactly as the live controller would run + them before the snap.""" + pending = [ + r + for seg in self._planner.flush() + if (r := self._segment_to_result(seg)) is not None + ] deg = np.asarray(angles_deg, dtype=np.float64) deg_to_steps(deg, self._state.Position_in) self._planner.state.Position_in[:] = self._state.Position_in self._planner.state.Homed_in.fill(1) rad = np.radians(deg).reshape(1, -1) - return _build_result(rad, duration=0.0) + snap = _build_result(rad, duration=0.0) + return self._merge_results([*pending, snap]) if pending else snap def _dispatch(self, params: Any) -> DryRunResult | None: """Route a command struct through the trajectory planner.""" diff --git a/tests/unit/test_dry_run_script_compat.py b/tests/unit/test_dry_run_script_compat.py index 0a46714..b6e89e8 100644 --- a/tests/unit/test_dry_run_script_compat.py +++ b/tests/unit/test_dry_run_script_compat.py @@ -9,6 +9,7 @@ from the real client. """ +import numpy as np import pytest from parol6.client.dry_run_client import DryRunRobotClient @@ -161,3 +162,17 @@ def test_referenced_home_previews_as_return_move(self): result = client.home() assert result is not None and result.error is None assert result.duration == 0.0 + + def test_snap_carries_the_pending_blend_chain(self): + """A blended move still buffered when the script homes with calibrate + (or teleports) is planned and leads the returned result — the live + controller runs it before the snap, so the preview must show it.""" + client = DryRunRobotClient(initial_joints_deg=HOME, initial_homed=True) + assert client.move_j(ANGLES_A, speed=0.5, r=10) is None # buffered + + result = client.home(calibrate=True) + assert result is not None and result.error is None + assert result.duration > 0.0 + assert len(result.joint_trajectory_rad) > 1 + assert np.allclose(np.degrees(result.end_joints_rad), HOME, atol=0.5) + assert client.flush() == []