fix(observatory): preserve camera across follow transitions
This commit is contained in:
@@ -52,7 +52,10 @@ from k1link.viewer.recorded import (
|
||||
recorded_blueprint_sessions,
|
||||
)
|
||||
from k1link.viewer.recorded_blueprint_lifecycle import BlueprintSessionReleased
|
||||
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,
|
||||
)
|
||||
from k1link.viewer.rerun_bridge import RerunSceneSettings
|
||||
|
||||
DEFAULT_REPLAY_ACTION_ID = "stream.start-replay"
|
||||
@@ -152,6 +155,7 @@ class RecordedBlueprintRequest(RecordedBlueprintIdentity):
|
||||
eye_position: EyeVector | None = None
|
||||
eye_look_target: EyeVector | None = None
|
||||
eye_up: EyeVector | None = None
|
||||
eye_relative_to_tracking: StrictBool = False
|
||||
current_time_ns: int | None = Field(default=None, strict=True, ge=0, le=MAX_SAFE_INTEGER)
|
||||
|
||||
@model_validator(mode="after")
|
||||
@@ -160,6 +164,8 @@ class RecordedBlueprintRequest(RecordedBlueprintIdentity):
|
||||
if any(vector is None for vector in vectors):
|
||||
if not all(vector is None for vector in vectors):
|
||||
raise ValueError("all eye vectors must be supplied together")
|
||||
if self.eye_relative_to_tracking:
|
||||
raise ValueError("a tracking-relative eye requires all eye vectors")
|
||||
return self
|
||||
assert self.eye_position is not None
|
||||
assert self.eye_look_target is not None
|
||||
@@ -168,6 +174,8 @@ class RecordedBlueprintRequest(RecordedBlueprintIdentity):
|
||||
raise ValueError("eye position and look target must differ")
|
||||
if sum(value * value for value in self.eye_up) <= 1.0e-12:
|
||||
raise ValueError("eye up vector must be non-zero")
|
||||
if self.eye_relative_to_tracking and self.current_time_ns is None:
|
||||
raise ValueError("a tracking-relative eye requires the playback cursor")
|
||||
return self
|
||||
|
||||
|
||||
@@ -1017,6 +1025,56 @@ def build_session_router(
|
||||
False,
|
||||
False,
|
||||
)
|
||||
camera_recording = None
|
||||
if request.current_time_ns is not None:
|
||||
if recording_preparation_manager is not None:
|
||||
snapshot = recording_preparation_manager.status(session_id)
|
||||
if (
|
||||
snapshot is not None
|
||||
and snapshot.state == "ready"
|
||||
and snapshot.recording is not None
|
||||
):
|
||||
camera_recording = snapshot.recording
|
||||
# A composition replay consumes the immutable base launch but
|
||||
# does not GET its recording. Its short launch reservation can
|
||||
# therefore expire while the combined RRD remains open. Restore
|
||||
# the already-published base descriptor with bounded stat checks
|
||||
# so later layer toggles still receive the native zoom limit.
|
||||
if camera_recording is None and recording_materializer is not None:
|
||||
camera_recording = await run_in_threadpool(
|
||||
recording_materializer.restore_published,
|
||||
command,
|
||||
)
|
||||
eye_position = request.eye_position
|
||||
eye_look_target = request.eye_look_target
|
||||
if request.eye_relative_to_tracking:
|
||||
if camera_recording is None or request.current_time_ns is None:
|
||||
raise HTTPException(
|
||||
status_code=409,
|
||||
detail="Позиция сканера для текущего кадра недоступна.",
|
||||
)
|
||||
tracking_position = await run_in_threadpool(
|
||||
recorded_tracking_position,
|
||||
camera_recording.path,
|
||||
current_time_ns=request.current_time_ns,
|
||||
)
|
||||
if tracking_position is None:
|
||||
raise HTTPException(
|
||||
status_code=409,
|
||||
detail="Позиция сканера для текущего кадра недоступна.",
|
||||
)
|
||||
assert eye_position is not None
|
||||
assert eye_look_target is not None
|
||||
eye_position = tuple(
|
||||
tracking + position - target
|
||||
for tracking, position, target in zip(
|
||||
tracking_position,
|
||||
eye_position,
|
||||
eye_look_target,
|
||||
strict=True,
|
||||
)
|
||||
)
|
||||
eye_look_target = tracking_position
|
||||
payload = await run_in_threadpool(
|
||||
recorded_blueprint_rrd,
|
||||
RerunSceneSettings(
|
||||
@@ -1045,39 +1103,19 @@ def build_session_router(
|
||||
show_costmap=request.show_costmap,
|
||||
reactivate_updates=request.reactivate_updates,
|
||||
follow_trajectory=request.follow_trajectory,
|
||||
eye_position=request.eye_position,
|
||||
eye_look_target=request.eye_look_target,
|
||||
eye_position=eye_position,
|
||||
eye_look_target=eye_look_target,
|
||||
eye_up=request.eye_up,
|
||||
)
|
||||
if request.current_time_ns is not None:
|
||||
camera_recording = None
|
||||
if recording_preparation_manager is not None:
|
||||
snapshot = recording_preparation_manager.status(session_id)
|
||||
if (
|
||||
snapshot is not None
|
||||
and snapshot.state == "ready"
|
||||
and snapshot.recording is not None
|
||||
):
|
||||
camera_recording = snapshot.recording
|
||||
# A composition replay consumes the immutable base launch but
|
||||
# does not GET its recording. Its short launch reservation can
|
||||
# therefore expire while the combined RRD remains open. Restore
|
||||
# the already-published base descriptor with bounded stat checks
|
||||
# so later layer toggles still receive the native zoom limit.
|
||||
if camera_recording is None and recording_materializer is not None:
|
||||
camera_recording = await run_in_threadpool(
|
||||
recording_materializer.restore_published,
|
||||
command,
|
||||
)
|
||||
if camera_recording is not None:
|
||||
camera_max_orbital_radius = await run_in_threadpool(
|
||||
recorded_orbital_radius_limit,
|
||||
camera_recording.path,
|
||||
current_time_ns=request.current_time_ns,
|
||||
accumulation_seconds=request.accumulation_seconds,
|
||||
show_points=request.show_points,
|
||||
show_trajectory=request.show_trajectory,
|
||||
)
|
||||
if request.current_time_ns is not None and camera_recording is not None:
|
||||
camera_max_orbital_radius = await run_in_threadpool(
|
||||
recorded_orbital_radius_limit,
|
||||
camera_recording.path,
|
||||
current_time_ns=request.current_time_ns,
|
||||
accumulation_seconds=request.accumulation_seconds,
|
||||
show_points=request.show_points,
|
||||
show_trajectory=request.show_trajectory,
|
||||
)
|
||||
except SessionNotFoundError as exc:
|
||||
raise HTTPException(status_code=404, detail=str(exc)) from exc
|
||||
except (SessionNotReplayableError, SessionIntegrityError) as exc:
|
||||
|
||||
Reference in New Issue
Block a user