diff --git a/rerun_notebook/src/js/widget.ts b/rerun_notebook/src/js/widget.ts index f6333c00bcc3..795e755cf166 100644 --- a/rerun_notebook/src/js/widget.ts +++ b/rerun_notebook/src/js/widget.ts @@ -43,6 +43,8 @@ class ViewerWidget { model.on("msg:custom", this.on_custom_message); + model.on("change:_time_ctrl", (_, [timeline, time, play]) => this.on_time_ctrl(null, timeline, time, play)); + this.viewer.on("ready", () => { this.channel = this.viewer.open_channel("temp"); @@ -122,6 +124,33 @@ class ViewerWidget { console.log("unknown message type", msg, buffers); } }; + + on_time_ctrl = (_: unknown, timeline: string | null, time: number | null, play: boolean) => { + let recording_id = this.viewer.get_active_recording_id(); + if (recording_id === null) { + return; + } + + let active_timeline = this.viewer.get_active_timeline(recording_id); + + if (timeline === null) { + timeline = active_timeline; + } + + if (timeline === null) { + return; + } + + if (timeline !== active_timeline) { + this.viewer.set_active_timeline(recording_id, timeline); + } + + this.viewer.set_playing(recording_id, play); + + if (time !== null) { + this.viewer.set_current_time(recording_id, timeline, time); + } + } } const render: Render = ({ model, el }) => { diff --git a/rerun_notebook/src/rerun_notebook/__init__.py b/rerun_notebook/src/rerun_notebook/__init__.py index 763c056ffa0b..afb0996cdf62 100644 --- a/rerun_notebook/src/rerun_notebook/__init__.py +++ b/rerun_notebook/src/rerun_notebook/__init__.py @@ -68,6 +68,13 @@ class Viewer(anywidget.AnyWidget): _ready = False _data_queue: list[bytes] + _time_ctrl = traitlets.Tuple( + traitlets.Unicode(allow_none=True), + traitlets.Int(allow_none=True), + traitlets.Bool(), + allow_none=True, + ).tag(sync=True) + def __init__( self, *, @@ -122,3 +129,6 @@ def block_until_ready(self, timeout=5.0) -> None: return poll(1) time.sleep(0.1) + + def set_time_ctrl(self, timeline: str | None, time: int | None, play: bool) -> None: + self._time_ctrl = (timeline, time, play) diff --git a/rerun_py/rerun_sdk/rerun/notebook.py b/rerun_py/rerun_sdk/rerun/notebook.py index 0fc161ebe4d6..1bc5d5a6e398 100644 --- a/rerun_py/rerun_sdk/rerun/notebook.py +++ b/rerun_py/rerun_sdk/rerun/notebook.py @@ -151,6 +151,46 @@ def _repr_mimebundle_(self, **kwargs: dict) -> tuple[dict, dict] | None: # type def _repr_keys(self): # type: ignore[no-untyped-def] return self._viewer._repr_keys() + def set_time_ctrl( + self, + *, + sequence: int | None = None, + nanoseconds: int | None = None, + seconds: float | None = None, + timeline: str | None = None, + play: bool = False, + ) -> None: + """ + Set the time control for the viewer. + + Parameters + ---------- + sequence: int + The sequence number to set the viewer to. + seconds: float + The time in seconds to set the viewer to. + nanoseconds: int + The time in nanoseconds to set the viewer to. + play: bool + Whether to start playing from the specified time point. Defaults to paused. + timeline : str + The name of the timeline to switch to. If not provided, time will remain on the current timeline. + + """ + if sum([sequence is not None, nanoseconds is not None, seconds is not None]) > 1: + raise ValueError("At most one of sequence, nanoseconds, or seconds may be provided") + + if sequence is not None: + time = sequence + elif nanoseconds is not None: + time = nanoseconds + elif seconds is not None: + time = int(seconds * 1e9) + else: + time = None + + self._viewer.set_time_ctrl(timeline, time, play) + def notebook_show( *,