fix(observatory): preserve camera across follow transitions
This commit is contained in:
@@ -5,7 +5,10 @@ import pytest
|
||||
import rerun as rr
|
||||
|
||||
import k1link.viewer.recorded_camera_bounds as camera_bounds
|
||||
from k1link.viewer.recorded_camera_bounds import recorded_orbital_radius_limit
|
||||
from k1link.viewer.recorded_camera_bounds import (
|
||||
recorded_orbital_radius_limit,
|
||||
recorded_tracking_position,
|
||||
)
|
||||
|
||||
|
||||
def _recording(path: Path) -> None:
|
||||
@@ -14,8 +17,10 @@ def _recording(path: Path) -> None:
|
||||
try:
|
||||
recording.set_time("session_time", duration=np.timedelta64(10, "s"))
|
||||
recording.log("/world/points", rr.Points3D([[0, 0, 0], [3, 4, 0]]))
|
||||
recording.log("/world/sensor_pose", rr.Transform3D(translation=[1, 2, 3]))
|
||||
recording.set_time("session_time", duration=np.timedelta64(20, "s"))
|
||||
recording.log("/world/points", rr.Points3D([[10, 0, 0], [10, 0, 12]]))
|
||||
recording.log("/world/sensor_pose", rr.Transform3D(translation=[10, 20, 30]))
|
||||
recording.flush(timeout_sec=5)
|
||||
finally:
|
||||
recording.disconnect()
|
||||
@@ -84,3 +89,18 @@ def test_recorded_camera_queries_reuse_the_generation_bounds_index(tmp_path: Pat
|
||||
assert second == pytest.approx(60, rel=1e-6)
|
||||
assert cache.misses == 1
|
||||
assert cache.hits == 1
|
||||
|
||||
|
||||
def test_recorded_tracking_position_uses_latest_pose_at_cursor(tmp_path: Path) -> None:
|
||||
path = tmp_path / "recording.rrd"
|
||||
_recording(path)
|
||||
|
||||
assert recorded_tracking_position(path, current_time_ns=9_000_000_000) is None
|
||||
assert recorded_tracking_position(
|
||||
path,
|
||||
current_time_ns=15_000_000_000,
|
||||
) == pytest.approx((1, 2, 3))
|
||||
assert recorded_tracking_position(
|
||||
path,
|
||||
current_time_ns=20_000_000_000,
|
||||
) == pytest.approx((10, 20, 30))
|
||||
|
||||
@@ -627,8 +627,9 @@ def test_recorded_blueprint_updates_reuse_source_store_and_opt_in_to_activation(
|
||||
assert eye_control_updates == [True, False, True, False, True]
|
||||
# Reactivating a blueprint clone is required for visible layer changes, but
|
||||
# those changes must not write position/look-target/eye-up and reset the
|
||||
# operator's camera. Only the follow transition writes a spatial preset.
|
||||
assert explicit_presets == [False, False, True, False, False]
|
||||
# operator's camera. Follow only changes tracking; it must not write the
|
||||
# startup eye. 3D/plan remains the explicit spatial preset transition.
|
||||
assert explicit_presets == [False, False, False, False, False]
|
||||
assert [activation[1:] for activation in activations] == [
|
||||
(True, False),
|
||||
(reactivate_updates, False),
|
||||
|
||||
@@ -1796,6 +1796,7 @@ def test_recorded_blueprint_restores_camera_bounds_after_base_launch_lease_expir
|
||||
store.reconcile_archive(xgrids_k1_archive_source(sessions))
|
||||
recording_path = tmp_path / "published.rrd"
|
||||
calls: list[tuple[Path, dict[str, object]]] = []
|
||||
blueprint_calls: list[dict[str, object]] = []
|
||||
|
||||
class PublishedMaterializer:
|
||||
def restore_published(self, command: ReplayCommand) -> object:
|
||||
@@ -1806,11 +1807,19 @@ def test_recorded_blueprint_restores_camera_bounds_after_base_launch_lease_expir
|
||||
calls.append((path, kwargs))
|
||||
return 123.5
|
||||
|
||||
def blueprint_payload(*_args: object, **kwargs: object) -> bytes:
|
||||
blueprint_calls.append(kwargs)
|
||||
return b"RRF2"
|
||||
|
||||
monkeypatch.setattr(
|
||||
"k1link.web.session_api.recorded_blueprint_rrd",
|
||||
lambda *_args, **_kwargs: b"RRF2",
|
||||
blueprint_payload,
|
||||
)
|
||||
monkeypatch.setattr("k1link.web.session_api.recorded_orbital_radius_limit", camera_bounds)
|
||||
monkeypatch.setattr(
|
||||
"k1link.web.session_api.recorded_tracking_position",
|
||||
lambda *_args, **_kwargs: (100.0, 200.0, 300.0),
|
||||
)
|
||||
route = endpoint(
|
||||
build_session_router(store, recording_materializer=PublishedMaterializer()), # type: ignore[arg-type]
|
||||
"/api/v1/observation-sessions/{session_id}/blueprint.rrd",
|
||||
@@ -1828,6 +1837,11 @@ def test_recorded_blueprint_restores_camera_bounds_after_base_launch_lease_expir
|
||||
show_points=True,
|
||||
show_trajectory=False,
|
||||
show_grid=True,
|
||||
follow_trajectory=False,
|
||||
eye_position=(3.0, 4.0, 5.0),
|
||||
eye_look_target=(1.0, 2.0, 0.0),
|
||||
eye_up=(0.0, 0.0, 1.0),
|
||||
eye_relative_to_tracking=True,
|
||||
current_time_ns=39_215_000_000,
|
||||
),
|
||||
)
|
||||
@@ -1845,6 +1859,9 @@ def test_recorded_blueprint_restores_camera_bounds_after_base_launch_lease_expir
|
||||
},
|
||||
)
|
||||
]
|
||||
assert blueprint_calls[0]["eye_position"] == pytest.approx((102.0, 202.0, 305.0))
|
||||
assert blueprint_calls[0]["eye_look_target"] == pytest.approx((100.0, 200.0, 300.0))
|
||||
assert blueprint_calls[0]["eye_up"] == (0.0, 0.0, 1.0)
|
||||
|
||||
|
||||
def test_recorded_perception_endpoint_returns_one_complete_optional_overlay(
|
||||
|
||||
Reference in New Issue
Block a user