diff --git a/crates/re_data_store/src/entity_properties.rs b/crates/re_data_store/src/entity_properties.rs
index 9b4560aa953d..131bc85dfb65 100644
--- a/crates/re_data_store/src/entity_properties.rs
+++ b/crates/re_data_store/src/entity_properties.rs
@@ -1,5 +1,6 @@
#[cfg(feature = "serde")]
use re_log_types::EntityPath;
+use re_log_types::TimeInt;
#[cfg(feature = "serde")]
use crate::EditableAutoValue;
@@ -172,25 +173,110 @@ impl EntityProperties {
// ----------------------------------------------------------------------------
+/// One of the boundaries of the visible history.
+///
+/// For [`VisibleHistoryBoundary::RelativeToTimeCursor`] and [`VisibleHistoryBoundary::Absolute`],
+/// the value are either nanos or frames, depending on the type of timeline.
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
+pub enum VisibleHistoryBoundary {
+ /// Boundary is a value relative to the time cursor
+ RelativeToTimeCursor(i64),
+
+ /// Boundary is an absolute value
+ Absolute(i64),
+
+ /// The boundary extends to infinity.
+ Infinite,
+}
+
+impl VisibleHistoryBoundary {
+ /// UI label to use when [`VisibleHistoryBoundary::RelativeToTimeCursor`] is selected.
+ pub const RELATIVE_LABEL: &'static str = "Relative";
+
+ /// UI label to use when [`VisibleHistoryBoundary::Absolute`] is selected.
+ pub const ABSOLUTE_LABEL: &'static str = "Absolute";
+
+ /// UI label to use when [`VisibleHistoryBoundary::Infinite`] is selected.
+ pub const INFINITE_LABEL: &'static str = "Infinite";
+
+ /// Value when the boundary is set to the current time cursor.
+ pub const AT_CURSOR: Self = Self::RelativeToTimeCursor(0);
+
+ /// Label to use in the UI.
+ pub fn label(&self) -> &'static str {
+ match self {
+ Self::RelativeToTimeCursor(_) => Self::RELATIVE_LABEL,
+ Self::Absolute(_) => Self::ABSOLUTE_LABEL,
+ Self::Infinite => Self::INFINITE_LABEL,
+ }
+ }
+}
+
+impl Default for VisibleHistoryBoundary {
+ fn default() -> Self {
+ Self::AT_CURSOR
+ }
+}
+
+/// Visible history bounds.
+#[derive(Clone, Copy, Default, Debug, PartialEq, Eq)]
+#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
+pub struct VisibleHistory {
+ /// Low time boundary.
+ pub from: VisibleHistoryBoundary,
+
+ /// High time boundary.
+ pub to: VisibleHistoryBoundary,
+}
+
+impl VisibleHistory {
+ /// Value with the visible history feature is disabled.
+ pub const OFF: Self = Self {
+ from: VisibleHistoryBoundary::AT_CURSOR,
+ to: VisibleHistoryBoundary::AT_CURSOR,
+ };
+
+ pub fn from(&self, cursor: TimeInt) -> TimeInt {
+ match self.from {
+ VisibleHistoryBoundary::Absolute(value) => TimeInt::from(value),
+ VisibleHistoryBoundary::RelativeToTimeCursor(value) => cursor + TimeInt::from(value),
+ VisibleHistoryBoundary::Infinite => TimeInt::MIN,
+ }
+ }
+
+ pub fn to(&self, cursor: TimeInt) -> TimeInt {
+ match self.to {
+ VisibleHistoryBoundary::Absolute(value) => TimeInt::from(value),
+ VisibleHistoryBoundary::RelativeToTimeCursor(value) => cursor + TimeInt::from(value),
+ VisibleHistoryBoundary::Infinite => TimeInt::MAX,
+ }
+ }
+}
+
/// When showing an entity in the history view, add this much history to it.
#[derive(Clone, Copy, Default, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", serde(default))]
pub struct ExtraQueryHistory {
- /// Zero = off.
- pub nanos: i64,
+ /// Is the feature enabled?
+ pub enabled: bool,
+
+ /// Visible history settings for time timelines
+ pub nanos: VisibleHistory,
- /// Zero = off.
- pub sequences: i64,
+ /// Visible history settings for frame timelines
+ pub sequences: VisibleHistory,
}
impl ExtraQueryHistory {
/// Multiply/and these together.
#[allow(dead_code)]
fn with_child(&self, child: &Self) -> Self {
- Self {
- nanos: self.nanos.max(child.nanos),
- sequences: self.sequences.max(child.sequences),
+ if child.enabled {
+ *child
+ } else {
+ *self
}
}
}
diff --git a/crates/re_data_store/src/store_db.rs b/crates/re_data_store/src/store_db.rs
index c61708b5bf30..09c0ed99eaaa 100644
--- a/crates/re_data_store/src/store_db.rs
+++ b/crates/re_data_store/src/store_db.rs
@@ -296,6 +296,10 @@ impl StoreDb {
&self.entity_db.times_per_timeline
}
+ pub fn time_histogram(&self, timeline: &Timeline) -> Option<&crate::TimeHistogram> {
+ self.entity_db().tree.prefix_times.get(timeline)
+ }
+
pub fn num_timeless_messages(&self) -> usize {
self.entity_db.tree.num_timeless_messages()
}
diff --git a/crates/re_query/src/util.rs b/crates/re_query/src/util.rs
index 1d1da7a755c8..3022e88ace72 100644
--- a/crates/re_query/src/util.rs
+++ b/crates/re_query/src/util.rs
@@ -1,5 +1,5 @@
use re_arrow_store::{DataStore, LatestAtQuery, RangeQuery, TimeInt, TimeRange, Timeline};
-use re_data_store::ExtraQueryHistory;
+use re_data_store::{ExtraQueryHistory, VisibleHistory};
use re_log_types::EntityPath;
use re_types_core::Archetype;
@@ -17,14 +17,15 @@ pub fn query_archetype_with_history<'a, A: Archetype + 'a, const N: usize>(
re_log_types::TimeType::Sequence => history.sequences,
};
- if visible_history == 0 {
+ if !history.enabled || visible_history == VisibleHistory::OFF {
let latest_query = LatestAtQuery::new(*timeline, *time);
let latest = query_archetype::(store, &latest_query, ent_path)?;
Ok(itertools::Either::Left(std::iter::once(latest)))
} else {
- let min_time = *time - TimeInt::from(visible_history);
- let range_query = RangeQuery::new(*timeline, TimeRange::new(min_time, *time));
+ let min_time = visible_history.from(*time);
+ let max_time = visible_history.to(*time);
+ let range_query = RangeQuery::new(*timeline, TimeRange::new(min_time, max_time));
let range = range_archetype::(store, &range_query, ent_path);
diff --git a/crates/re_space_view_spatial/src/space_view_2d.rs b/crates/re_space_view_spatial/src/space_view_2d.rs
index 1feaf4e9b38c..63e126accf7a 100644
--- a/crates/re_space_view_spatial/src/space_view_2d.rs
+++ b/crates/re_space_view_spatial/src/space_view_2d.rs
@@ -16,11 +16,15 @@ use crate::{
#[derive(Default)]
pub struct SpatialSpaceView2D;
+impl SpatialSpaceView2D {
+ pub const NAME: &'static str = "2D";
+}
+
impl SpaceViewClass for SpatialSpaceView2D {
type State = SpatialSpaceViewState;
fn name(&self) -> re_viewer_context::SpaceViewClassName {
- "2D".into()
+ Self::NAME.into()
}
fn icon(&self) -> &'static re_ui::Icon {
diff --git a/crates/re_space_view_spatial/src/space_view_3d.rs b/crates/re_space_view_spatial/src/space_view_3d.rs
index 93da23aceaed..6873972f31ac 100644
--- a/crates/re_space_view_spatial/src/space_view_3d.rs
+++ b/crates/re_space_view_spatial/src/space_view_3d.rs
@@ -16,11 +16,15 @@ use crate::{
#[derive(Default)]
pub struct SpatialSpaceView3D;
+impl SpatialSpaceView3D {
+ pub const NAME: &'static str = "3D";
+}
+
impl SpaceViewClass for SpatialSpaceView3D {
type State = SpatialSpaceViewState;
fn name(&self) -> re_viewer_context::SpaceViewClassName {
- "3D".into()
+ Self::NAME.into()
}
fn icon(&self) -> &'static re_ui::Icon {
diff --git a/crates/re_time_panel/src/lib.rs b/crates/re_time_panel/src/lib.rs
index 1f1f61c05a26..4a47ac9e2aed 100644
--- a/crates/re_time_panel/src/lib.rs
+++ b/crates/re_time_panel/src/lib.rs
@@ -822,10 +822,7 @@ fn initialize_time_ranges_ui(
if let Some(times) = ctx
.store_db
- .entity_db()
- .tree
- .prefix_times
- .get(ctx.rec_cfg.time_ctrl.timeline())
+ .time_histogram(ctx.rec_cfg.time_ctrl.timeline())
{
// NOTE: `times` can be empty if a GC wiped everything.
if !times.is_empty() {
diff --git a/crates/re_ui/src/lib.rs b/crates/re_ui/src/lib.rs
index 4efd0f77b7ce..86271c055c67 100644
--- a/crates/re_ui/src/lib.rs
+++ b/crates/re_ui/src/lib.rs
@@ -15,6 +15,7 @@ pub use command_palette::CommandPalette;
pub use design_tokens::DesignTokens;
pub use icons::Icon;
pub use layout_job_builder::LayoutJobBuilder;
+use std::ops::RangeInclusive;
pub use toggle_switch::toggle_switch;
// ---------------------------------------------------------------------------
@@ -445,6 +446,41 @@ impl ReUi {
.inner
}
+ /// Shows a `egui::DragValue` for a time in nanoseconds.
+ ///
+ /// The value and unit displayed adapts to the provided allowable time range.
+ #[allow(clippy::unused_self)]
+ pub fn time_drag_value(
+ &self,
+ ui: &mut egui::Ui,
+ value: &mut i64,
+ time_range: &RangeInclusive,
+ ) {
+ let span = time_range.end() - time_range.start();
+
+ let (unit, factor) = if span / 1_000_000_000 > 0 {
+ ("s", 1_000_000_000.)
+ } else if span / 1_000_000 > 0 {
+ ("ms", 1_000_000.)
+ } else if span / 1_000 > 0 {
+ ("μs", 1_000.)
+ } else {
+ ("ns", 1.)
+ };
+
+ let mut time_unit = *value as f32 / factor;
+ let time_range = *time_range.start() as f32 / factor..=*time_range.end() as f32 / factor;
+ let speed = (time_range.end() - time_range.start()) * 0.005;
+
+ ui.add(
+ egui::DragValue::new(&mut time_unit)
+ .clamp_range(time_range)
+ .speed(speed)
+ .suffix(unit),
+ );
+ *value = (time_unit * factor).round() as _;
+ }
+
pub fn large_button(&self, ui: &mut egui::Ui, icon: &Icon) -> egui::Response {
self.large_button_impl(ui, icon, None, None)
}
diff --git a/crates/re_viewer/src/ui/selection_panel.rs b/crates/re_viewer/src/ui/selection_panel.rs
index 6351ce4f4cd4..ecbf5c6b4779 100644
--- a/crates/re_viewer/src/ui/selection_panel.rs
+++ b/crates/re_viewer/src/ui/selection_panel.rs
@@ -1,14 +1,20 @@
-use egui::NumExt as _;
+use egui::{NumExt as _, Ui};
+use std::ops::RangeInclusive;
-use re_data_store::{ColorMapper, Colormap, EditableAutoValue, EntityPath, EntityProperties};
+use re_data_store::{
+ ColorMapper, Colormap, EditableAutoValue, EntityPath, EntityProperties, VisibleHistoryBoundary,
+};
use re_data_ui::{image_meaning_for_entity, item_ui, DataUi};
use re_log_types::TimeType;
+use re_space_view_spatial::{SpatialSpaceView2D, SpatialSpaceView3D};
use re_types::{
components::{PinholeProjection, Transform3D},
tensor_data::TensorDataMeaning,
};
+use re_types_core::ComponentName;
use re_viewer_context::{
- gpu_bridge::colormap_dropdown_button_ui, Item, SpaceViewId, UiVerbosity, ViewerContext,
+ gpu_bridge::colormap_dropdown_button_ui, Item, SpaceViewClassName, SpaceViewId, UiVerbosity,
+ ViewerContext,
};
use re_viewport::{Viewport, ViewportBlueprint};
@@ -303,9 +309,16 @@ fn blueprint_ui(
// TODO(emilk): show the values of this specific instance (e.g. point in the point cloud)!
} else {
// splat - the whole entity
+ let space_view_class_name = *space_view.class_name();
let data_blueprint = space_view.contents.data_blueprints_individual();
let mut props = data_blueprint.get(&instance_path.entity_path);
- entity_props_ui(ctx, ui, Some(&instance_path.entity_path), &mut props);
+ entity_props_ui(
+ ctx,
+ ui,
+ &space_view_class_name,
+ Some(&instance_path.entity_path),
+ &mut props,
+ );
data_blueprint.set(instance_path.entity_path.clone(), props);
}
}
@@ -321,8 +334,15 @@ fn blueprint_ui(
Item::DataBlueprintGroup(space_view_id, data_blueprint_group_handle) => {
if let Some(space_view) = viewport.blueprint.space_view_mut(space_view_id) {
+ let space_view_class_name = *space_view.class_name();
if let Some(group) = space_view.contents.group_mut(*data_blueprint_group_handle) {
- entity_props_ui(ctx, ui, None, &mut group.properties_individual);
+ entity_props_ui(
+ ctx,
+ ui,
+ &space_view_class_name,
+ None,
+ &mut group.properties_individual,
+ );
} else {
ctx.selection_state_mut().clear_current();
}
@@ -364,6 +384,7 @@ fn list_existing_data_blueprints(
fn entity_props_ui(
ctx: &mut ViewerContext<'_>,
ui: &mut egui::Ui,
+ space_view_class_name: &SpaceViewClassName,
entity_path: Option<&EntityPath>,
entity_props: &mut EntityProperties,
) {
@@ -373,36 +394,11 @@ fn entity_props_ui(
.checkbox(ui, &mut entity_props.interactive, "Interactive")
.on_hover_text("If disabled, the entity will not react to any mouse interaction");
+ visible_history_ui(ctx, ui, space_view_class_name, entity_path, entity_props);
+
egui::Grid::new("entity_properties")
.num_columns(2)
.show(ui, |ui| {
- ui.label("Visible history");
- let visible_history = &mut entity_props.visible_history;
- match ctx.rec_cfg.time_ctrl.timeline().typ() {
- TimeType::Time => {
- let mut time_sec = visible_history.nanos as f32 / 1e9;
- let speed = (time_sec * 0.05).at_least(0.01);
- ui.add(
- egui::DragValue::new(&mut time_sec)
- .clamp_range(0.0..=f32::INFINITY)
- .speed(speed)
- .suffix("s"),
- )
- .on_hover_text("Include this much history of the Entity in the Space View");
- visible_history.nanos = (time_sec * 1e9).round() as _;
- }
- TimeType::Sequence => {
- let speed = (visible_history.sequences as f32 * 0.05).at_least(1.0);
- ui.add(
- egui::DragValue::new(&mut visible_history.sequences)
- .clamp_range(0.0..=f32::INFINITY)
- .speed(speed),
- )
- .on_hover_text("Include this much history of the Entity in the Space View");
- }
- }
- ui.end_row();
-
// TODO(wumpf): It would be nice to only show pinhole & depth properties in the context of a 3D view.
// if *view_state.state_spatial.nav_mode.get() == SpatialNavigationMode::ThreeD {
if let Some(entity_path) = entity_path {
@@ -413,6 +409,262 @@ fn entity_props_ui(
});
}
+fn visible_history_ui(
+ ctx: &mut ViewerContext<'_>,
+ ui: &mut Ui,
+ space_view_class_name: &SpaceViewClassName,
+ entity_path: Option<&EntityPath>,
+ entity_props: &mut EntityProperties,
+) {
+ //TODO(#4107): support more space view types.
+ if space_view_class_name != SpatialSpaceView3D::NAME
+ && space_view_class_name != SpatialSpaceView2D::NAME
+ {
+ return;
+ }
+
+ if !should_display_visible_history(ctx, entity_path) {
+ return;
+ }
+
+ let re_ui = ctx.re_ui;
+
+ re_ui
+ .checkbox(
+ ui,
+ &mut entity_props.visible_history.enabled,
+ "Visible history",
+ )
+ .on_hover_text(
+ "Enable Visible History.\n\nBy default, only the last state before the \
+ current time is shown. By activating Visible History, all data within a \
+ time window is shown instead.",
+ );
+
+ let time_range = if let Some(times) = ctx
+ .store_db
+ .time_histogram(ctx.rec_cfg.time_ctrl.timeline())
+ {
+ times.min_key().unwrap_or_default()..=times.max_key().unwrap_or_default()
+ } else {
+ 0..=0
+ };
+
+ let current_time = ctx
+ .rec_cfg
+ .time_ctrl
+ .time_i64()
+ .unwrap_or_default()
+ .at_least(*time_range.start()); // accounts for timeless time (TimeInt::BEGINNING)
+
+ let sequence_timeline = matches!(ctx.rec_cfg.time_ctrl.timeline().typ(), TimeType::Sequence);
+
+ let visible_history = if sequence_timeline {
+ &mut entity_props.visible_history.sequences
+ } else {
+ &mut entity_props.visible_history.nanos
+ };
+
+ let visible_history_time_range = visible_history.from(current_time.into()).as_i64()
+ ..=visible_history.to(current_time.into()).as_i64();
+
+ ui.add_enabled_ui(entity_props.visible_history.enabled, |ui| {
+ egui::Grid::new("visible_history_boundaries")
+ .num_columns(4)
+ .show(ui, |ui| {
+ ui.label("From");
+ visible_history_boundary_ui(
+ re_ui,
+ ui,
+ &mut visible_history.from,
+ sequence_timeline,
+ current_time,
+ time_range.clone(),
+ true,
+ *visible_history_time_range.end(),
+ );
+
+ ui.end_row();
+
+ ui.label("To");
+ visible_history_boundary_ui(
+ re_ui,
+ ui,
+ &mut visible_history.to,
+ sequence_timeline,
+ current_time,
+ time_range,
+ false,
+ *visible_history_time_range.start(),
+ );
+
+ ui.end_row();
+ });
+ });
+ ui.add(
+ egui::Label::new(
+ egui::RichText::new(if sequence_timeline {
+ "These settings apply to all sequence timelines."
+ } else {
+ "These settings apply to all temporal timelines."
+ })
+ .italics()
+ .weak(),
+ )
+ .wrap(true),
+ );
+}
+
+static VISIBLE_HISTORY_COMPONENT_NAMES: once_cell::sync::Lazy> =
+ once_cell::sync::Lazy::new(|| {
+ [
+ ComponentName::from("rerun.components.Position2D"),
+ ComponentName::from("rerun.components.Position3D"),
+ ComponentName::from("rerun.components.LineStrip2D"),
+ ComponentName::from("rerun.components.LineStrip3D"),
+ ComponentName::from("rerun.components.TensorData"),
+ ComponentName::from("rerun.components.Vector3D"),
+ ComponentName::from("rerun.components.HalfSizes2D"),
+ ComponentName::from("rerun.components.HalfSizes3D"),
+ ]
+ .into()
+ });
+
+// TODO(#4145): This method is obviously unfortunate. It's a temporary solution until the ViewPart
+// system is able to report its ability to handle the visible history feature.
+fn should_display_visible_history(
+ ctx: &mut ViewerContext<'_>,
+ entity_path: Option<&EntityPath>,
+) -> bool {
+ if let Some(entity_path) = entity_path {
+ let store = ctx.store_db.store();
+ let component_names = store.all_components(ctx.rec_cfg.time_ctrl.timeline(), entity_path);
+ if let Some(component_names) = component_names {
+ if !component_names
+ .iter()
+ .any(|name| VISIBLE_HISTORY_COMPONENT_NAMES.contains(name))
+ {
+ return false;
+ }
+ } else {
+ return false;
+ }
+ }
+
+ true
+}
+
+#[allow(clippy::too_many_arguments)]
+fn visible_history_boundary_ui(
+ re_ui: &re_ui::ReUi,
+ ui: &mut egui::Ui,
+ visible_history_boundary: &mut VisibleHistoryBoundary,
+ sequence_timeline: bool,
+ current_time: i64,
+ mut time_range: RangeInclusive,
+ low_bound: bool,
+ other_boundary_absolute: i64,
+) {
+ let span = time_range.end() - time_range.start();
+
+ // Hot "usability" area! This achieves two things:
+ // 1) It makes sure the time range in relative mode has enough margin beyond the current
+ // timeline's span to avoid the boundary value to be modified by changing the current time
+ // cursor
+ // 2) It makes sure the two boundaries don't cross in time (i.e. low > high). It does so by
+ // prioritizing the low boundary. Moving the low boundary against the high boundary will
+ // displace the high boundary. On the other hand, the high boundary cannot be moved against
+ // the low boundary. This asymmetry is intentional, and avoids both boundaries fighting each
+ // other in some corner cases (when the user interacts with the current time cursor).
+ #[allow(clippy::collapsible_else_if)] // for readability
+ if matches!(
+ visible_history_boundary,
+ VisibleHistoryBoundary::RelativeToTimeCursor(_)
+ ) {
+ if low_bound {
+ time_range = -span..=2 * span;
+ } else {
+ time_range =
+ (other_boundary_absolute.saturating_sub(current_time)).at_least(-span)..=2 * span;
+ }
+ } else {
+ if !low_bound {
+ time_range = other_boundary_absolute.at_least(-span)..=*time_range.end();
+ }
+ }
+
+ match visible_history_boundary {
+ VisibleHistoryBoundary::RelativeToTimeCursor(value)
+ | VisibleHistoryBoundary::Absolute(value) => {
+ if sequence_timeline {
+ let speed = (span as f32 * 0.005).at_least(1.0);
+
+ ui.add(
+ egui::DragValue::new(value)
+ .clamp_range(time_range)
+ .speed(speed),
+ );
+ } else {
+ re_ui.time_drag_value(ui, value, &time_range);
+ }
+ }
+ VisibleHistoryBoundary::Infinite => {
+ let mut unused = 0.0;
+ ui.add_enabled(
+ false,
+ egui::DragValue::new(&mut unused).custom_formatter(|_, _| "∞".to_owned()),
+ );
+ }
+ }
+
+ let (abs_time, rel_time) = match visible_history_boundary {
+ VisibleHistoryBoundary::RelativeToTimeCursor(value) => (*value + current_time, *value),
+ VisibleHistoryBoundary::Absolute(value) => (*value, *value - current_time),
+ VisibleHistoryBoundary::Infinite => (current_time, 0),
+ };
+
+ egui::ComboBox::from_id_source(if low_bound {
+ "time_history_low_bound"
+ } else {
+ "time_history_high_bound"
+ })
+ .selected_text(visible_history_boundary.label())
+ .show_ui(ui, |ui| {
+ ui.set_min_width(64.0);
+
+ ui.selectable_value(
+ visible_history_boundary,
+ VisibleHistoryBoundary::RelativeToTimeCursor(rel_time),
+ VisibleHistoryBoundary::RELATIVE_LABEL,
+ )
+ .on_hover_text(if low_bound {
+ "Show data from a time point relative to the current time."
+ } else {
+ "Show data until a time point relative to the current time."
+ });
+ ui.selectable_value(
+ visible_history_boundary,
+ VisibleHistoryBoundary::Absolute(abs_time),
+ VisibleHistoryBoundary::ABSOLUTE_LABEL,
+ )
+ .on_hover_text(if low_bound {
+ "Show data from an absolute time point."
+ } else {
+ "Show data until an absolute time point."
+ });
+ ui.selectable_value(
+ visible_history_boundary,
+ VisibleHistoryBoundary::Infinite,
+ VisibleHistoryBoundary::INFINITE_LABEL,
+ )
+ .on_hover_text(if low_bound {
+ "Show data from the beginning of the timeline"
+ } else {
+ "Show data until the end of the timeline"
+ });
+ });
+}
+
fn colormap_props_ui(
ctx: &mut ViewerContext<'_>,
ui: &mut egui::Ui,
diff --git a/tests/python/visible_history_playground/main.py b/tests/python/visible_history_playground/main.py
new file mode 100644
index 000000000000..1c9b97a7c4c7
--- /dev/null
+++ b/tests/python/visible_history_playground/main.py
@@ -0,0 +1,61 @@
+"""Playground to test the visible history feature."""
+from __future__ import annotations
+
+import argparse
+import math
+
+import numpy as np
+import rerun as rr
+
+parser = argparse.ArgumentParser(description=__doc__)
+rr.script_add_args(parser)
+args = parser.parse_args()
+rr.script_setup(args, "rerun_example_visible_history_playground")
+
+
+rr.log("bbox", rr.Boxes2D(centers=[50, 3.5], half_sizes=[50, 4.5], colors=[255, 0, 0]), timeless=True)
+rr.log("transform", rr.Transform3D(translation=[0, 0, 0]))
+rr.log("some/nested/pinhole", rr.Pinhole(focal_length=3, width=3, height=3), timeless=True)
+
+rr.log("3dworld/depthimage/pinhole", rr.Pinhole(focal_length=20, width=100, height=10), timeless=True)
+rr.log("3dworld/image", rr.Transform3D(translation=[0, 1, 0]), timeless=True)
+rr.log("3dworld/image/pinhole", rr.Pinhole(focal_length=20, width=100, height=10), timeless=True)
+
+for i in range(1, 100):
+ rr.set_time_seconds("seconds", i)
+ rr.set_time_seconds("ms", i / 1000)
+ rr.set_time_seconds("us", i / 1000000)
+ rr.set_time_sequence("frames_offset", 10000 + i)
+ rr.set_time_sequence("frames", i)
+
+ rr.log("world/data/nested/point", rr.Points2D([[i, 0], [i, 1]], radii=0.4))
+ rr.log("world/data/nested/point2", rr.Points2D([i, 2], radii=0.4))
+ rr.log("world/data/nested/box", rr.Boxes2D(centers=[i, 1], half_sizes=[0.5, 0.5]))
+ rr.log("world/data/nested/arrow", rr.Arrows3D(origins=[i, 4, 0], vectors=[0, 1.7, 0]))
+ rr.log(
+ "world/data/nested/linestrip",
+ rr.LineStrips2D([[[i - 0.4, 6], [i + 0.4, 6], [i - 0.4, 7], [i + 0.4, 7]], [[i - 0.2, 6.5], [i + 0.2, 6.5]]]),
+ )
+
+ rr.log("world/data/nested/transformed", rr.Transform3D(translation=[i, 0, 0]))
+ rr.log("world/data/nested/transformed/point", rr.Boxes2D(centers=[0, 3], half_sizes=[0.5, 0.5]))
+
+ rr.log("text_log", rr.TextLog(f"hello {i}"))
+ rr.log("scalar", rr.TimeSeriesScalar(math.sin(i / 100 * 2 * math.pi)))
+
+ depth_image = 100 * np.ones((10, 100), dtype=np.float32)
+ depth_image[:, i] = 50
+ rr.log("3dworld/depthimage/pinhole/data", rr.DepthImage(depth_image, meter=100))
+
+ image = 100 * np.ones((10, 100, 3), dtype=np.uint8)
+ image[:, i, :] = [255, 0, 0]
+ rr.log("3dworld/image/pinhole/data", rr.Image(image))
+
+ x_coord = (i - 50) / 5
+ rr.log(
+ "3dworld/mesh",
+ rr.Mesh3D(
+ vertex_positions=[[x_coord, 2, 0], [x_coord, 2, 1], [x_coord, 3, 0]],
+ vertex_colors=[[0, 0, 255], [0, 255, 0], [255, 0, 0]],
+ ),
+ )