diff --git a/crates/re_entity_db/src/entity_properties.rs b/crates/re_entity_db/src/entity_properties.rs index fb50dc0d39bc..58e2646ab495 100644 --- a/crates/re_entity_db/src/entity_properties.rs +++ b/crates/re_entity_db/src/entity_properties.rs @@ -94,48 +94,53 @@ impl FromIterator<(EntityPath, EntityProperties)> for EntityPropertyMap { #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[cfg_attr(feature = "serde", serde(default))] pub struct EntityProperties { - pub interactive: bool, + pub interactive: bool, // TODO(andreas): similar to `visible`, needs to become a regular (slightly special - doesn't show up in archetypes) component. /// What kind of color mapping should be applied (none, map, texture, transfer..)? - pub color_mapper: EditableAutoValue, + pub color_mapper: EditableAutoValue, // TODO(andreas): should become a component and be part of the DepthImage and regular Images (with limitation to mono channel image). /// Distance of the projection plane (frustum far plane). /// /// Only applies to pinhole cameras when in a spatial view, using 3D navigation. - pub pinhole_image_plane_distance: EditableAutoValue, + pub pinhole_image_plane_distance: EditableAutoValue, // TODO(#6084): should be a regular component on the Pinhole archetype. /// Should the depth texture be backprojected into a point cloud? /// /// Only applies to tensors with meaning=depth that are affected by a pinhole transform. /// /// The default for 3D views is `true`, but for 2D views it is `false`. - pub backproject_depth: EditableAutoValue, + pub backproject_depth: EditableAutoValue, // TODO(andreas): should be a component on the DepthImage archetype. /// How many depth units per world-space unit. e.g. 1000 for millimeters. /// /// This corresponds to `re_components::Tensor::meter`. - pub depth_from_world_scale: EditableAutoValue, + pub depth_from_world_scale: EditableAutoValue, // TODO(andreas): Just remove once we can edit meter & be able to set semi-clever defaults per visualizer. /// Used to scale the radii of the points in the resulting point cloud. - pub backproject_radius_scale: EditableAutoValue, + pub backproject_radius_scale: EditableAutoValue, // TODO(andreas): should be a component on the DepthImage archetype. /// Whether to show the 3D transform visualization at all. + // TODO(andreas): should go away once we can disable visualizer. Revisit how to collectively enable/disable these on an entire view. + // To consider: Make a TransformAxis archetype whose indicator is what enables the visualizer + // -> size etc. are now part of this archetype, not the `Transform` archetype + // -> `TransformAxis` itself doesn't have a required component, but the visualizer has. Just like in SeriesLines & Scalar. + // TODO(andreas)/TODO(jleibs): There's a pattern here that we should capture & formalize in the API / codegen / definitions. pub transform_3d_visible: EditableAutoValue, /// The length of the arrows in the entity's own coordinate system (space). - pub transform_3d_size: EditableAutoValue, + pub transform_3d_size: EditableAutoValue, // TODO(andreas): should be a component on the Transform3D/TransformAxis archetype. /// Should the legend be shown (for plot space views). - pub show_legend: EditableAutoValue, + pub show_legend: EditableAutoValue, // TODO(andreas): BarChart is still using it, we already have the legend archteype! /// The location of the legend (for plot space views). /// /// This is an Option instead of an EditableAutoValue to let each space view class decide on /// what's the best default. - pub legend_location: Option, + pub legend_location: Option, // TODO(andreas): BarChart is still using it, we already have the legend archteype! /// What kind of data aggregation to perform (for plot space views). - pub time_series_aggregator: EditableAutoValue, + pub time_series_aggregator: EditableAutoValue, // TODO(andreas): Should be a component probably on SeriesLine, but today it would become a view property. } #[cfg(feature = "serde")] diff --git a/crates/re_query/src/latest_at/to_archetype/.gitattributes b/crates/re_query/src/latest_at/to_archetype/.gitattributes index e128a891fe94..dbd97e14fb15 100644 --- a/crates/re_query/src/latest_at/to_archetype/.gitattributes +++ b/crates/re_query/src/latest_at/to_archetype/.gitattributes @@ -36,4 +36,5 @@ text_log.rs linguist-generated=true transform3d.rs linguist-generated=true view_coordinates.rs linguist-generated=true viewport_blueprint.rs linguist-generated=true +visible_time_range.rs linguist-generated=true visual_bounds.rs linguist-generated=true diff --git a/crates/re_query/src/latest_at/to_archetype/mod.rs b/crates/re_query/src/latest_at/to_archetype/mod.rs index 4ce8223caddb..bedfffd12055 100644 --- a/crates/re_query/src/latest_at/to_archetype/mod.rs +++ b/crates/re_query/src/latest_at/to_archetype/mod.rs @@ -34,4 +34,5 @@ mod text_log; mod transform3d; mod view_coordinates; mod viewport_blueprint; +mod visible_time_range; mod visual_bounds; diff --git a/crates/re_query/src/latest_at/to_archetype/visible_time_range.rs b/crates/re_query/src/latest_at/to_archetype/visible_time_range.rs new file mode 100644 index 000000000000..b32ecacc8fee --- /dev/null +++ b/crates/re_query/src/latest_at/to_archetype/visible_time_range.rs @@ -0,0 +1,58 @@ +// DO NOT EDIT! This file was auto-generated by crates/re_types_builder/src/codegen/rust/to_archetype.rs + +#![allow(unused_imports)] +#![allow(unused_parens)] +#![allow(clippy::clone_on_copy)] +#![allow(clippy::cloned_instead_of_copied)] + +use crate::{LatestAtResults, PromiseResolver, PromiseResult}; +use re_types_core::{Archetype, Loggable as _}; +use std::sync::Arc; + +impl crate::ToArchetype for LatestAtResults { + #[inline] + fn to_archetype( + &self, + resolver: &PromiseResolver, + ) -> PromiseResult> { + re_tracing::profile_function!(::name()); + + // --- Required --- + + // --- Recommended/Optional --- + + use re_types::blueprint::components::VisibleTimeRangeSequence; + let sequence = if let Some(sequence) = self.get(::name()) { + match sequence.to_dense::(resolver) { + PromiseResult::Pending => return PromiseResult::Pending, + PromiseResult::Error(promise_err) => return PromiseResult::Error(promise_err), + PromiseResult::Ready(query_res) => match query_res { + Ok(data) => data.first().cloned(), + Err(query_err) => return PromiseResult::Ready(Err(query_err)), + }, + } + } else { + None + }; + + use re_types::blueprint::components::VisibleTimeRangeTime; + let time = if let Some(time) = self.get(::name()) { + match time.to_dense::(resolver) { + PromiseResult::Pending => return PromiseResult::Pending, + PromiseResult::Error(promise_err) => return PromiseResult::Error(promise_err), + PromiseResult::Ready(query_res) => match query_res { + Ok(data) => data.first().cloned(), + Err(query_err) => return PromiseResult::Ready(Err(query_err)), + }, + } + } else { + None + }; + + // --- + + let arch = re_types::blueprint::archetypes::VisibleTimeRange { sequence, time }; + + PromiseResult::Ready(Ok(arch)) + } +} diff --git a/crates/re_space_view/src/data_query.rs b/crates/re_space_view/src/data_query.rs index 7cf3fcb141fd..30372e7917ee 100644 --- a/crates/re_space_view/src/data_query.rs +++ b/crates/re_space_view/src/data_query.rs @@ -1,18 +1,15 @@ -use nohash_hasher::IntMap; - -use re_entity_db::{external::re_data_store::LatestAtQuery, EntityProperties, EntityPropertyMap}; -use re_types::ComponentName; +use re_entity_db::{external::re_data_store::LatestAtQuery, EntityDb, EntityProperties}; +use re_log_types::Timeline; use re_viewer_context::{ - DataQueryResult, OverridePath, PerVisualizer, StoreContext, VisualizableEntities, + DataQueryResult, PerVisualizer, QueryRange, SpaceViewClassRegistry, StoreContext, + VisualizableEntities, }; pub struct EntityOverrideContext { - pub root: EntityProperties, - pub individual: EntityPropertyMap, - pub recursive: EntityPropertyMap, + pub legacy_space_view_properties: EntityProperties, - /// Base component overrides that are inherited by all entities. - pub root_component_overrides: IntMap, + /// Query range that data results should fall back to if they don't specify their own. + pub default_query_range: QueryRange, } /// Trait for resolving properties needed by most implementations of [`DataQuery`] @@ -22,8 +19,10 @@ pub struct EntityOverrideContext { pub trait PropertyResolver { fn update_overrides( &self, - ctx: &StoreContext<'_>, - query: &LatestAtQuery, + blueprint: &EntityDb, + blueprint_query: &LatestAtQuery, + active_timeline: &Timeline, + space_view_class_registry: &SpaceViewClassRegistry, query_result: &mut DataQueryResult, ); } diff --git a/crates/re_space_view/src/lib.rs b/crates/re_space_view/src/lib.rs index 52b0dcc97b1d..2de1e10273ea 100644 --- a/crates/re_space_view/src/lib.rs +++ b/crates/re_space_view/src/lib.rs @@ -18,13 +18,13 @@ pub use screenshot::ScreenshotMode; pub use space_view::SpaceViewBlueprint; pub use space_view_contents::SpaceViewContents; pub use sub_archetypes::{ - edit_blueprint_component, entity_path_for_space_view_sub_archetype, get_blueprint_component, - query_space_view_sub_archetype, query_space_view_sub_archetype_or_default, - space_view_sub_archetype, + edit_blueprint_component, entity_path_for_view_property, get_blueprint_component, + query_view_property, query_view_property_or_default, view_property, }; pub use visual_time_range::{ - query_visual_history, time_range_boundary_to_visible_history_boundary, - visible_history_boundary_to_time_range_boundary, visible_time_range_to_time_range, + query_visual_history, time_range_from_visible_time_range, + visible_history_boundary_from_time_range_boundary, + visible_history_boundary_to_time_range_boundary, }; pub use visualizable::determine_visualizable_entities; diff --git a/crates/re_space_view/src/space_view.rs b/crates/re_space_view/src/space_view.rs index c0f965c23bc3..3ced06b1fa43 100644 --- a/crates/re_space_view/src/space_view.rs +++ b/crates/re_space_view/src/space_view.rs @@ -1,22 +1,23 @@ use itertools::{FoldWhile, Itertools}; -use nohash_hasher::IntMap; -use re_entity_db::external::re_query::PromiseResult; +use re_entity_db::{external::re_query::PromiseResult, EntityProperties}; use re_types::SpaceViewClassIdentifier; use crate::SpaceViewContents; use re_data_store::LatestAtQuery; use re_entity_db::{EntityDb, EntityPath, EntityPropertiesComponent, EntityPropertyMap}; -use re_log_types::{DataRow, EntityPathSubs, RowId}; -use re_types::blueprint::archetypes as blueprint_archetypes; +use re_log_types::{DataRow, EntityPathSubs, RowId, Timeline}; use re_types::{ - blueprint::components::{SpaceViewOrigin, Visible}, + blueprint::{ + archetypes::{self as blueprint_archetypes}, + components::{SpaceViewOrigin, Visible}, + }, components::Name, }; use re_types_core::archetypes::Clear; use re_types_core::Archetype as _; use re_viewer_context::{ - ContentsName, DataResult, OverridePath, PerSystemEntities, PropertyOverrides, - RecommendedSpaceView, SpaceViewClass, SpaceViewId, SpaceViewState, StoreContext, SystemCommand, + ContentsName, DataResult, PerSystemEntities, QueryRange, RecommendedSpaceView, SpaceViewClass, + SpaceViewClassRegistry, SpaceViewId, SpaceViewState, StoreContext, SystemCommand, SystemCommandSender as _, SystemExecutionOutput, ViewQuery, ViewerContext, }; @@ -368,12 +369,7 @@ impl SpaceViewBlueprint { let class = self.class(ctx.space_view_class_registry); - let space_view_data_result = - self.space_view_data_result(ctx.store_context, ctx.blueprint_query); - let props = space_view_data_result - .individual_properties() - .cloned() - .unwrap_or_default(); + let props = self.legacy_properties(ctx.store_context.blueprint, ctx.blueprint_query); ui.scope(|ui| { class @@ -393,68 +389,68 @@ impl SpaceViewBlueprint { self.id.as_entity_path() } - /// A special data result for the space view that sits "above" the [`SpaceViewContents`]. - /// - /// This allows us to use interfaces that take a [`DataResult`] to modify things on the - /// space view that can then be inherited into the contents. For example, controlling - /// visible history. - pub fn space_view_data_result( + /// Legacy `EntityProperties` used by a hand ful of view properties that aren't blueprint view properties yet. + pub fn legacy_properties( &self, - ctx: &StoreContext<'_>, - query: &LatestAtQuery, - ) -> DataResult { + blueprint: &EntityDb, + blueprint_query: &LatestAtQuery, + ) -> EntityProperties { let base_override_root = self.entity_path(); let individual_override_path = base_override_root.join(&DataResult::INDIVIDUAL_OVERRIDES_PREFIX.into()); - let recursive_override_path = - base_override_root.join(&DataResult::RECURSIVE_OVERRIDES_PREFIX.into()); - // TODO(#5607): what should happen if the promise is still pending? - let individual_properties = ctx - .blueprint + blueprint .latest_at_component_quiet::( &individual_override_path, - query, + blueprint_query, ) - .map(|result| result.value.0); - let accumulated_properties = individual_properties.clone().unwrap_or_default(); - - // Gather recursive component overrides. - // Ignore individual overrides on SpaceView root. - let mut recursive_component_overrides = IntMap::default(); - if let Some(recursive_override_subtree) = - ctx.blueprint.tree().subtree(&recursive_override_path) - { - for component in recursive_override_subtree.entity.components.keys() { - if let Some(component_data) = ctx - .blueprint - .store() - .latest_at(query, &recursive_override_path, *component, &[*component]) - .and_then(|(_, _, cells)| cells[0].clone()) - { - if !component_data.is_empty() { - recursive_component_overrides.insert( - *component, - OverridePath::blueprint_path(recursive_override_path.clone()), - ); - } - } - } - } + .map(|result| result.value.0) + .unwrap_or_default() + } + + pub fn save_legacy_properties(&self, ctx: &ViewerContext<'_>, props: EntityProperties) { + let base_override_root = self.entity_path(); + let individual_override_path = + base_override_root.join(&DataResult::INDIVIDUAL_OVERRIDES_PREFIX.into()); - DataResult { - entity_path: base_override_root, - visualizers: Default::default(), - tree_prefix_only: false, - property_overrides: Some(PropertyOverrides { - accumulated_properties, - individual_properties, - recursive_properties: Default::default(), - resolved_component_overrides: recursive_component_overrides, - recursive_override_path, - individual_override_path, - }), + ctx.save_blueprint_component(&individual_override_path, &EntityPropertiesComponent(props)); + } + + pub fn query_range( + &self, + blueprint: &EntityDb, + blueprint_query: &LatestAtQuery, + active_timeline: &Timeline, + space_view_class_registry: &SpaceViewClassRegistry, + ) -> QueryRange { + // Visual time range works with regular overrides for the most part but it's a bit special: + // * we need it for all entities unconditionally + // * default does not vary per visualizer + // * can't be specified in the data store + // Here, we query the visual time range that serves as the default for all entities in this space. + let (visible_time_range_archetype, _) = crate::query_view_property::< + blueprint_archetypes::VisibleTimeRange, + >(self.id, blueprint, blueprint_query); // Don't need to query the entire archetype but doesn't cost much and is convenient. + + let visible_time_range_archetype = visible_time_range_archetype.ok().flatten(); + + match active_timeline.typ() { + re_log_types::TimeType::Time => { + visible_time_range_archetype.map(|arch| arch.time.map(|s| s.0)) + } + re_log_types::TimeType::Sequence => { + visible_time_range_archetype.map(|arch| arch.sequence.map(|s| s.0)) + } } + .flatten() + .map_or_else( + || { + let space_view_class = + space_view_class_registry.get_class_or_log_error(&self.class_identifier); + space_view_class.default_query_range() + }, + QueryRange::TimeRange, + ) } } @@ -491,6 +487,7 @@ mod tests { #[test] fn test_entity_properties() { let space_view_class_registry = SpaceViewClassRegistry::default(); + let timeline = Timeline::new("time", re_log_types::TimeType::Time); let mut recording = EntityDb::new(StoreId::random(re_log_types::StoreKind::Recording)); let mut blueprint = EntityDb::new(StoreId::random(re_log_types::StoreKind::Blueprint)); @@ -513,8 +510,6 @@ mod tests { let space_view = SpaceViewBlueprint::new("3D".into(), recommended); - let auto_properties = Default::default(); - let mut visualizable_entities = PerVisualizer::::default(); visualizable_entities .0 @@ -543,7 +538,6 @@ mod tests { let resolver = contents.build_resolver( &space_view_class_registry, &space_view, - &auto_properties, &visualizable_entities, &indicated_entities_per_visualizer, ); @@ -560,7 +554,13 @@ mod tests { }; let mut query_result = contents.execute_query(&ctx, &visualizable_entities); - resolver.update_overrides(&ctx, &blueprint_query, &mut query_result); + resolver.update_overrides( + &blueprint, + &blueprint_query, + &timeline, + &space_view_class_registry, + &mut query_result, + ); let parent = query_result .tree @@ -605,7 +605,13 @@ mod tests { }; let mut query_result = contents.execute_query(&ctx, &visualizable_entities); - resolver.update_overrides(&ctx, &blueprint_query, &mut query_result); + resolver.update_overrides( + &blueprint, + &blueprint_query, + &timeline, + &space_view_class_registry, + &mut query_result, + ); let parent_group = query_result .tree @@ -656,7 +662,13 @@ mod tests { }; let mut query_result = contents.execute_query(&ctx, &visualizable_entities); - resolver.update_overrides(&ctx, &blueprint_query, &mut query_result); + resolver.update_overrides( + &blueprint, + &blueprint_query, + &timeline, + &space_view_class_registry, + &mut query_result, + ); let parent = query_result .tree @@ -680,6 +692,7 @@ mod tests { #[test] fn test_component_overrides() { let space_view_class_registry = SpaceViewClassRegistry::default(); + let timeline = Timeline::new("time", re_log_types::TimeType::Time); let mut recording = EntityDb::new(StoreId::random(re_log_types::StoreKind::Recording)); let mut visualizable_entities_per_visualizer = PerVisualizer::::default(); @@ -721,12 +734,10 @@ mod tests { .join(&DataResult::RECURSIVE_OVERRIDES_PREFIX.into()); // Things needed to resolve properties: - let auto_properties = EntityPropertyMap::default(); let indicated_entities_per_visualizer = PerVisualizer::::default(); // Don't care about indicated entities. let resolver = space_view.contents.build_resolver( &space_view_class_registry, &space_view, - &auto_properties, &visualizable_entities_per_visualizer, &indicated_entities_per_visualizer, ); @@ -932,7 +943,13 @@ mod tests { .contents .execute_query(&ctx, &visualizable_entities_per_visualizer); let blueprint_query = LatestAtQuery::latest(blueprint_timeline()); - resolver.update_overrides(&ctx, &blueprint_query, &mut query_result); + resolver.update_overrides( + &blueprint, + &blueprint_query, + &timeline, + &space_view_class_registry, + &mut query_result, + ); // Extract component overrides for testing. let mut visited: HashMap> = diff --git a/crates/re_space_view/src/space_view_contents.rs b/crates/re_space_view/src/space_view_contents.rs index fb55085ad1e2..7abbad351818 100644 --- a/crates/re_space_view/src/space_view_contents.rs +++ b/crates/re_space_view/src/space_view_contents.rs @@ -4,25 +4,27 @@ use smallvec::SmallVec; use re_entity_db::{ external::{re_data_store::LatestAtQuery, re_query::PromiseResult}, - EntityDb, EntityProperties, EntityPropertiesComponent, EntityPropertyMap, EntityTree, + EntityDb, EntityProperties, EntityPropertiesComponent, EntityTree, }; use re_log_types::{ - path::RuleEffect, EntityPath, EntityPathFilter, EntityPathRule, EntityPathSubs, + path::RuleEffect, EntityPath, EntityPathFilter, EntityPathRule, EntityPathSubs, Timeline, }; use re_types::{ - blueprint::{archetypes as blueprint_archetypes, components::QueryExpression}, + blueprint::{ + archetypes as blueprint_archetypes, components as blueprint_components, + components::QueryExpression, + }, Archetype as _, SpaceViewClassIdentifier, }; use re_types_core::{components::VisualizerOverrides, ComponentName}; use re_viewer_context::{ DataQueryResult, DataResult, DataResultHandle, DataResultNode, DataResultTree, - IndicatedEntities, OverridePath, PerVisualizer, PropertyOverrides, SpaceViewId, StoreContext, - ViewerContext, VisualizableEntities, + IndicatedEntities, OverridePath, PerVisualizer, PropertyOverrides, QueryRange, + SpaceViewClassRegistry, SpaceViewId, ViewerContext, VisualizableEntities, }; use crate::{ - query_space_view_sub_archetype, DataQuery, EntityOverrideContext, PropertyResolver, - SpaceViewBlueprint, + query_view_property, DataQuery, EntityOverrideContext, PropertyResolver, SpaceViewBlueprint, }; /// An implementation of [`DataQuery`] that is built from a [`blueprint_archetypes::SpaceViewContents`]. @@ -101,9 +103,8 @@ impl SpaceViewContents { space_view_class_identifier: SpaceViewClassIdentifier, space_env: &EntityPathSubs, ) -> Self { - let (contents, blueprint_entity_path) = query_space_view_sub_archetype::< - blueprint_archetypes::SpaceViewContents, - >(id, blueprint_db, query); + let (contents, blueprint_entity_path) = + query_view_property::(id, blueprint_db, query); let blueprint_archetypes::SpaceViewContents { query } = match contents { PromiseResult::Pending => { @@ -178,7 +179,6 @@ impl SpaceViewContents { &self, space_view_class_registry: &'a re_viewer_context::SpaceViewClassRegistry, space_view: &'a SpaceViewBlueprint, - auto_properties: &'a EntityPropertyMap, visualizable_entities_per_visualizer: &'a PerVisualizer, indicated_entities_per_visualizer: &'a PerVisualizer, ) -> DataQueryPropertyResolver<'a> { @@ -190,8 +190,6 @@ impl SpaceViewContents { DataQueryPropertyResolver { space_view_class_registry, space_view, - auto_properties, - default_stack: vec![space_view.entity_path(), self.blueprint_entity_path.clone()], individual_override_root, recursive_override_root, visualizable_entities_per_visualizer, @@ -374,8 +372,6 @@ impl<'a> QueryExpressionEvaluator<'a> { pub struct DataQueryPropertyResolver<'a> { space_view_class_registry: &'a re_viewer_context::SpaceViewClassRegistry, space_view: &'a SpaceViewBlueprint, - auto_properties: &'a EntityPropertyMap, - default_stack: Vec, individual_override_root: EntityPath, recursive_override_root: EntityPath, visualizable_entities_per_visualizer: &'a PerVisualizer, @@ -392,79 +388,29 @@ impl DataQueryPropertyResolver<'_> { /// - The recursive overrides are found by walking an override subtree under the `data_query//recursive_overrides` fn build_override_context( &self, - ctx: &StoreContext<'_>, - query: &LatestAtQuery, + blueprint: &EntityDb, + blueprint_query: &LatestAtQuery, + active_timeline: &Timeline, + space_view_class_registry: &SpaceViewClassRegistry, ) -> EntityOverrideContext { re_tracing::profile_function!(); - // TODO(#4194): We always start the override context with the root_data_result from - // the space-view. This isn't totally generic once we add container overrides, but it's a start. - let (mut root_entity_properties, root_component_overrides) = self + let legacy_space_view_properties = self .space_view - .space_view_data_result(ctx, query) - .property_overrides - .map(|p| (p.accumulated_properties, p.resolved_component_overrides)) - .unwrap_or_default(); - - for prefix in &self.default_stack { - // TODO(#5607): what should happen if the promise is still pending? - if let Some(overrides) = ctx - .blueprint - .latest_at_component::(prefix, query) - { - root_entity_properties = root_entity_properties.with_child(&overrides.value.0); - } - } - - // TODO(jleibs): Should pass through an initial `ComponentOverrides` here - // if we were to support incrementally inheriting overrides from parent - // contexts such as the `SpaceView` or `Container`. - EntityOverrideContext { - root: root_entity_properties, - individual: self.resolve_entity_overrides_for_path( - ctx, - query, - &self.individual_override_root, - ), - recursive: self.resolve_entity_overrides_for_path( - ctx, - query, - &self.recursive_override_root, - ), - root_component_overrides, - } - } - - /// Find all of the overrides for a given path. - /// - /// These overrides are full entity-paths prefixed by the override root. - /// - /// For example the individual override for `world/points` in the context of the query-id `1234` - /// would be found at: `data_query/1234/individual_overrides/world/points`. - fn resolve_entity_overrides_for_path( - &self, - ctx: &StoreContext<'_>, - query: &LatestAtQuery, - override_root: &EntityPath, - ) -> EntityPropertyMap { - re_tracing::profile_function!(); - let blueprint = ctx.blueprint; + .legacy_properties(blueprint, blueprint_query); - let mut prop_map = self.auto_properties.clone(); + let default_query_range = self.space_view.query_range( + blueprint, + blueprint_query, + active_timeline, + space_view_class_registry, + ); - if let Some(tree) = blueprint.tree().subtree(override_root) { - tree.visit_children_recursively(&mut |path: &EntityPath, _| { - // TODO(#5607): what should happen if the promise is still pending? - if let Some(props) = - blueprint.latest_at_component_quiet::(path, query) - { - let overridden_path = - EntityPath::from(&path.as_slice()[override_root.len()..path.len()]); - prop_map.update(overridden_path, props.value.0); - } - }); + // TODO(#4194): Once supported, default entity properties should be passe through here. + EntityOverrideContext { + legacy_space_view_properties, + default_query_range, } - prop_map } /// Recursively walk the [`DataResultTree`] and update the [`PropertyOverrides`] for each node. @@ -474,141 +420,181 @@ impl DataQueryPropertyResolver<'_> { #[allow(clippy::too_many_arguments)] // This will be a lot simpler and smaller once `EntityProperties` are gone! fn update_overrides_recursive( &self, - ctx: &StoreContext<'_>, - query: &LatestAtQuery, + blueprint: &EntityDb, + blueprint_query: &LatestAtQuery, + active_timeline: &Timeline, query_result: &mut DataQueryResult, override_context: &EntityOverrideContext, - accumulated: &EntityProperties, + recursive_accumulated_legacy_properties: &EntityProperties, recursive_property_overrides: &IntMap, handle: DataResultHandle, ) { - if let Some((child_handles, accumulated, recursive_property_overrides)) = - query_result.tree.lookup_node_mut(handle).map(|node| { - let recursive_properties = override_context - .recursive - .get_opt(&node.data_result.entity_path); - let accumulated_recursive_properties = - if let Some(overridden) = recursive_properties { - accumulated.with_child(overridden) - } else { - accumulated.clone() - }; - - let individual_properties = override_context - .individual - .get_opt(&node.data_result.entity_path); - let accumulated_properties = - if let Some(individual_override) = individual_properties { - accumulated_recursive_properties.with_child(individual_override) - } else { - accumulated_recursive_properties.clone() - }; - - let individual_override_path = self - .individual_override_root - .join(&node.data_result.entity_path); - let recursive_override_path = self - .recursive_override_root - .join(&node.data_result.entity_path); - - if !node.data_result.visualizers.is_empty() { - re_tracing::profile_scope!("Update visualizers from overrides"); - - // If the user has overridden the visualizers, update which visualizers are used. - // TODO(#5607): what should happen if the promise is still pending? - if let Some(viz_override) = ctx - .blueprint - .latest_at_component::( - &individual_override_path, - query, + if let Some(( + child_handles, + recursive_accumulated_legacy_properties, + recursive_property_overrides, + )) = query_result.tree.lookup_node_mut(handle).map(|node| { + let individual_override_path = self + .individual_override_root + .join(&node.data_result.entity_path); + let recursive_override_path = self + .recursive_override_root + .join(&node.data_result.entity_path); + + // Special handling for legacy overrides. + let recursive_legacy_properties = blueprint + .latest_at_component_quiet::( + &recursive_override_path, + blueprint_query, + ) + .map(|result| result.value.0); + let individual_legacy_properties = blueprint + .latest_at_component_quiet::( + &individual_override_path, + blueprint_query, + ) + .map(|result| result.value.0); + + let recursive_accumulated_legacy_properties = + if let Some(recursive_legacy_properties) = recursive_legacy_properties.as_ref() { + recursive_accumulated_legacy_properties.with_child(recursive_legacy_properties) + } else { + recursive_accumulated_legacy_properties.clone() + }; + let accumulated_legacy_properties = + if let Some(individual) = individual_legacy_properties.as_ref() { + recursive_accumulated_legacy_properties.with_child(individual) + } else { + recursive_accumulated_legacy_properties.clone() + }; + + // Update visualizers from overrides. + if !node.data_result.visualizers.is_empty() { + re_tracing::profile_scope!("Update visualizers from overrides"); + + // If the user has overridden the visualizers, update which visualizers are used. + // TODO(#5607): what should happen if the promise is still pending? + if let Some(viz_override) = blueprint + .latest_at_component::( + &individual_override_path, + blueprint_query, + ) + .map(|c| c.value) + { + node.data_result.visualizers = + viz_override.0.iter().map(|v| v.as_str().into()).collect(); + } else { + // Otherwise ask the `SpaceViewClass` to choose. + node.data_result.visualizers = self + .space_view + .class(self.space_view_class_registry) + .choose_default_visualizers( + &node.data_result.entity_path, + self.visualizable_entities_per_visualizer, + self.indicated_entities_per_visualizer, + ); + } + } + + // First, gather recursive overrides. Previous recursive overrides are the base for the next. + // We assume that most of the time there's no new recursive overrides, so clone the map lazily. + let mut recursive_property_overrides = + std::borrow::Cow::Borrowed(recursive_property_overrides); + if let Some(recursive_override_subtree) = + blueprint.tree().subtree(&recursive_override_path) + { + for component in recursive_override_subtree.entity.components.keys() { + if let Some(component_data) = blueprint + .store() + .latest_at( + blueprint_query, + &recursive_override_path, + *component, + &[*component], ) - .map(|c| c.value) + .and_then(|(_, _, cells)| cells[0].clone()) { - node.data_result.visualizers = - viz_override.0.iter().map(|v| v.as_str().into()).collect(); - } else { - // Otherwise ask the `SpaceViewClass` to choose. - node.data_result.visualizers = self - .space_view - .class(self.space_view_class_registry) - .choose_default_visualizers( - &node.data_result.entity_path, - self.visualizable_entities_per_visualizer, - self.indicated_entities_per_visualizer, + if !component_data.is_empty() { + recursive_property_overrides.to_mut().insert( + *component, + OverridePath::blueprint_path(recursive_override_path.clone()), ); - } - } - - // First, gather recursive overrides. Previous recursive overrides are the base for the next. - // We assume that most of the time there's no new recursive overrides, so clone the map lazily. - let mut recursive_property_overrides = - std::borrow::Cow::Borrowed(recursive_property_overrides); - if let Some(recursive_override_subtree) = - ctx.blueprint.tree().subtree(&recursive_override_path) - { - for component in recursive_override_subtree.entity.components.keys() { - if let Some(component_data) = ctx - .blueprint - .store() - .latest_at(query, &recursive_override_path, *component, &[*component]) - .and_then(|(_, _, cells)| cells[0].clone()) - { - if !component_data.is_empty() { - recursive_property_overrides.to_mut().insert( - *component, - OverridePath::blueprint_path(recursive_override_path.clone()), - ); - } } } } + } - // Then, gather individual overrides - these may override the recursive ones again, - // but recursive overrides are still inherited to children. - let mut resolved_component_overrides = (*recursive_property_overrides).clone(); - if let Some(individual_override_subtree) = - ctx.blueprint.tree().subtree(&individual_override_path) - { - for component in individual_override_subtree.entity.components.keys() { - if let Some(component_data) = ctx - .blueprint - .store() - .latest_at(query, &individual_override_path, *component, &[*component]) - .and_then(|(_, _, cells)| cells[0].clone()) - { - if !component_data.is_empty() { - resolved_component_overrides.insert( - *component, - OverridePath::blueprint_path(individual_override_path.clone()), - ); - } + // Then, gather individual overrides - these may override the recursive ones again, + // but recursive overrides are still inherited to children. + let mut resolved_component_overrides = (*recursive_property_overrides).clone(); + if let Some(individual_override_subtree) = + blueprint.tree().subtree(&individual_override_path) + { + for component in individual_override_subtree.entity.components.keys() { + if let Some(component_data) = blueprint + .store() + .latest_at( + blueprint_query, + &individual_override_path, + *component, + &[*component], + ) + .and_then(|(_, _, cells)| cells[0].clone()) + { + if !component_data.is_empty() { + resolved_component_overrides.insert( + *component, + OverridePath::blueprint_path(individual_override_path.clone()), + ); } } } + } - node.data_result.property_overrides = Some(PropertyOverrides { - accumulated_properties, - individual_properties: individual_properties.cloned(), - recursive_properties: recursive_properties.cloned(), - resolved_component_overrides, - recursive_override_path, - individual_override_path, - }); - - ( - node.children.clone(), - accumulated_recursive_properties, - recursive_property_overrides, - ) - }) - { + // Figure out relevant visual time range. + let visual_time_range_overrides = match active_timeline.typ() { + re_log_types::TimeType::Time => blueprint + .latest_at_component_quiet::( + &recursive_override_path, + blueprint_query, + ) + .map(|result| result.value.0), + re_log_types::TimeType::Sequence => blueprint + .latest_at_component_quiet::( + &recursive_override_path, + blueprint_query, + ) + .map(|result| result.value.0), + }; + let query_range = visual_time_range_overrides.map_or_else( + || override_context.default_query_range.clone(), + QueryRange::TimeRange, + ); + + node.data_result.property_overrides = Some(PropertyOverrides { + accumulated_properties: accumulated_legacy_properties, + individual_properties: individual_legacy_properties, + recursive_properties: recursive_legacy_properties, + resolved_component_overrides, + recursive_override_path, + individual_override_path, + query_range, + }); + + ( + node.children.clone(), + recursive_accumulated_legacy_properties, + recursive_property_overrides, + ) + }) { for child in child_handles { self.update_overrides_recursive( - ctx, - query, + blueprint, + blueprint_query, + active_timeline, query_result, override_context, - &accumulated, + &recursive_accumulated_legacy_properties, &recursive_property_overrides, child, ); @@ -621,21 +607,32 @@ impl<'a> PropertyResolver for DataQueryPropertyResolver<'a> { /// Recursively walk the [`DataResultTree`] and update the [`PropertyOverrides`] for each node. fn update_overrides( &self, - ctx: &StoreContext<'_>, - query: &LatestAtQuery, + blueprint: &EntityDb, + blueprint_query: &LatestAtQuery, + active_timeline: &Timeline, + space_view_class_registry: &SpaceViewClassRegistry, query_result: &mut DataQueryResult, ) { re_tracing::profile_function!(); - let entity_overrides = self.build_override_context(ctx, query); + let override_context = self.build_override_context( + blueprint, + blueprint_query, + active_timeline, + space_view_class_registry, + ); if let Some(root) = query_result.tree.root_handle() { + let accumulated_legacy_properties = EntityProperties::default(); + let recursive_property_overrides = Default::default(); + self.update_overrides_recursive( - ctx, - query, + blueprint, + blueprint_query, + active_timeline, query_result, - &entity_overrides, - &entity_overrides.root, - &entity_overrides.root_component_overrides, + &override_context, + &accumulated_legacy_properties, + &recursive_property_overrides, root, ); } diff --git a/crates/re_space_view/src/sub_archetypes.rs b/crates/re_space_view/src/sub_archetypes.rs index a19caa7ea33a..9a137a3688f1 100644 --- a/crates/re_space_view/src/sub_archetypes.rs +++ b/crates/re_space_view/src/sub_archetypes.rs @@ -7,7 +7,7 @@ use re_log_types::EntityPath; use re_types::Archetype; use re_viewer_context::{external::re_entity_db::EntityTree, SpaceViewId, ViewerContext}; -pub fn entity_path_for_space_view_sub_archetype( +pub fn entity_path_for_view_property( space_view_id: SpaceViewId, _blueprint_entity_tree: &EntityTree, ) -> EntityPath { @@ -23,7 +23,7 @@ pub fn entity_path_for_space_view_sub_archetype( } /// Return the archetype value for the given space view, or `None` if it doesn't exist. -pub fn space_view_sub_archetype( +pub fn view_property( ctx: &re_viewer_context::ViewerContext<'_>, space_view_id: re_viewer_context::SpaceViewId, ) -> Option @@ -32,7 +32,7 @@ where { let blueprint_db = ctx.store_context.blueprint; let blueprint_query = ctx.blueprint_query; - let path = entity_path_for_space_view_sub_archetype::(space_view_id, blueprint_db.tree()); + let path = entity_path_for_view_property::(space_view_id, blueprint_db.tree()); blueprint_db .latest_at_archetype(&path, blueprint_query) .ok() @@ -41,7 +41,7 @@ where } /// Returns `Ok(None)` if any of the required components are missing. -pub fn query_space_view_sub_archetype( +pub fn query_view_property( space_view_id: SpaceViewId, blueprint_db: &EntityDb, query: &LatestAtQuery, @@ -49,7 +49,7 @@ pub fn query_space_view_sub_archetype( where LatestAtResults: ToArchetype, { - let path = entity_path_for_space_view_sub_archetype::(space_view_id, blueprint_db.tree()); + let path = entity_path_for_view_property::(space_view_id, blueprint_db.tree()); ( blueprint_db .latest_at_archetype(&path, query) @@ -58,7 +58,7 @@ where ) } -pub fn query_space_view_sub_archetype_or_default( +pub fn query_view_property_or_default( space_view_id: SpaceViewId, blueprint_db: &EntityDb, query: &LatestAtQuery, @@ -66,7 +66,7 @@ pub fn query_space_view_sub_archetype_or_default( where LatestAtResults: ToArchetype, { - let (arch, path) = query_space_view_sub_archetype(space_view_id, blueprint_db, query); + let (arch, path) = query_view_property(space_view_id, blueprint_db, query); (arch.ok().flatten().unwrap_or_default(), path) } @@ -77,7 +77,7 @@ pub fn get_blueprint_component( ) -> Option { let blueprint_db = ctx.store_context.blueprint; let query = ctx.blueprint_query; - let path = entity_path_for_space_view_sub_archetype::(space_view_id, blueprint_db.tree()); + let path = entity_path_for_view_property::(space_view_id, blueprint_db.tree()); blueprint_db .latest_at_component::(&path, query) .map(|x| x.value) @@ -93,8 +93,7 @@ pub fn edit_blueprint_component) -> R, ) -> R { let active_blueprint = ctx.store_context.blueprint; - let active_path = - entity_path_for_space_view_sub_archetype::(space_view_id, active_blueprint.tree()); + let active_path = entity_path_for_view_property::(space_view_id, active_blueprint.tree()); let original_value: Option = active_blueprint .latest_at_component::(&active_path, ctx.blueprint_query) .map(|x| x.value); @@ -111,10 +110,8 @@ pub fn edit_blueprint_component( - space_view_id, - default_blueprint.tree(), - ); + let default_path = + entity_path_for_view_property::(space_view_id, default_blueprint.tree()); default_blueprint .latest_at_component::(&default_path, ctx.blueprint_query) .map(|x| x.value) diff --git a/crates/re_space_view/src/visual_time_range.rs b/crates/re_space_view/src/visual_time_range.rs index ccf1b2ecdd31..71754e259e8d 100644 --- a/crates/re_space_view/src/visual_time_range.rs +++ b/crates/re_space_view/src/visual_time_range.rs @@ -14,7 +14,7 @@ use re_types::blueprint::datatypes::{ }; use re_viewer_context::ViewerContext; -pub fn time_range_boundary_to_visible_history_boundary( +pub fn visible_history_boundary_from_time_range_boundary( boundary: &VisibleTimeRangeBoundary, ) -> VisibleHistoryBoundary { match boundary.kind { @@ -45,7 +45,7 @@ pub fn visible_history_boundary_to_time_range_boundary( } } -pub fn visible_time_range_to_time_range( +pub fn time_range_from_visible_time_range( range: &VisibleTimeRange, cursor: re_log_types::TimeInt, ) -> re_log_types::TimeRange { @@ -68,31 +68,40 @@ pub fn query_visual_history( ctx: &ViewerContext<'_>, data_result: &re_viewer_context::DataResult, ) -> ExtraQueryHistory { - let time_range = - data_result.lookup_override::(ctx); - let sequence_range = data_result - .lookup_override::(ctx); - - let mut history = ExtraQueryHistory { - enabled: false, - nanos: Default::default(), - sequences: Default::default(), + let Some(overrides) = data_result.property_overrides.as_ref() else { + re_log::error!("No overrides found for visual history"); + return ExtraQueryHistory { + enabled: false, + nanos: Default::default(), + sequences: Default::default(), + }; }; - if let Some(time_range) = time_range { - history.enabled = true; - history.nanos = VisibleHistory { - from: time_range_boundary_to_visible_history_boundary(&time_range.0.start), - to: time_range_boundary_to_visible_history_boundary(&time_range.0.end), - }; - } - if let Some(sequence_range) = sequence_range { - history.enabled = true; - history.sequences = VisibleHistory { - from: time_range_boundary_to_visible_history_boundary(&sequence_range.0.start), - to: time_range_boundary_to_visible_history_boundary(&sequence_range.0.end), - }; + match &overrides.query_range { + re_viewer_context::QueryRange::TimeRange(time_range) => { + match ctx.rec_cfg.time_ctrl.read().time_type() { + re_log_types::TimeType::Time => ExtraQueryHistory { + enabled: true, + nanos: VisibleHistory { + from: visible_history_boundary_from_time_range_boundary(&time_range.start), + to: visible_history_boundary_from_time_range_boundary(&time_range.end), + }, + sequences: Default::default(), + }, + re_log_types::TimeType::Sequence => ExtraQueryHistory { + enabled: true, + nanos: Default::default(), + sequences: VisibleHistory { + from: visible_history_boundary_from_time_range_boundary(&time_range.start), + to: visible_history_boundary_from_time_range_boundary(&time_range.end), + }, + }, + } + } + re_viewer_context::QueryRange::LatestAt => ExtraQueryHistory { + enabled: false, + nanos: Default::default(), + sequences: Default::default(), + }, } - - history } diff --git a/crates/re_space_view_spatial/src/ui.rs b/crates/re_space_view_spatial/src/ui.rs index 4d8c7198b3b0..d500775ee89c 100644 --- a/crates/re_space_view_spatial/src/ui.rs +++ b/crates/re_space_view_spatial/src/ui.rs @@ -779,7 +779,7 @@ pub fn background_ui( let blueprint_db = ctx.store_context.blueprint; let blueprint_query = ctx.blueprint_query; let (archetype, blueprint_path) = - re_space_view::query_space_view_sub_archetype(space_view_id, blueprint_db, blueprint_query); + re_space_view::query_view_property(space_view_id, blueprint_db, blueprint_query); let Background { color, mut kind } = archetype.ok().flatten().unwrap_or(default_background); diff --git a/crates/re_space_view_spatial/src/ui_2d.rs b/crates/re_space_view_spatial/src/ui_2d.rs index 6574da3e50e5..e67a99dcb48c 100644 --- a/crates/re_space_view_spatial/src/ui_2d.rs +++ b/crates/re_space_view_spatial/src/ui_2d.rs @@ -275,9 +275,8 @@ pub fn view_2d( view_builder.queue_draw(draw_data); } - let background = - re_space_view::space_view_sub_archetype::(ctx, query.space_view_id) - .unwrap_or(Background::DEFAULT_2D); + let background = re_space_view::view_property::(ctx, query.space_view_id) + .unwrap_or(Background::DEFAULT_2D); let (background_drawable, clear_color) = crate::configure_background(ctx, background); if let Some(background_drawable) = background_drawable { view_builder.queue_draw(background_drawable); diff --git a/crates/re_space_view_spatial/src/ui_3d.rs b/crates/re_space_view_spatial/src/ui_3d.rs index 6f882cd0de51..13b91d24d883 100644 --- a/crates/re_space_view_spatial/src/ui_3d.rs +++ b/crates/re_space_view_spatial/src/ui_3d.rs @@ -665,9 +665,8 @@ pub fn view_3d( // Commit ui induced lines. view_builder.queue_draw(line_builder.into_draw_data()?); - let background = - re_space_view::space_view_sub_archetype::(ctx, query.space_view_id) - .unwrap_or(Background::DEFAULT_3D); + let background = re_space_view::view_property::(ctx, query.space_view_id) + .unwrap_or(Background::DEFAULT_3D); let (background_drawable, clear_color) = crate::configure_background(ctx, background); if let Some(background_drawable) = background_drawable { diff --git a/crates/re_space_view_spatial/src/visualizers/entity_iterator.rs b/crates/re_space_view_spatial/src/visualizers/entity_iterator.rs index 6b5c39bf70ed..a5a6b7d68347 100644 --- a/crates/re_space_view_spatial/src/visualizers/entity_iterator.rs +++ b/crates/re_space_view_spatial/src/visualizers/entity_iterator.rs @@ -44,7 +44,7 @@ pub fn query_archetype_with_history( entity_db: &EntityDb, timeline: &Timeline, time: &TimeInt, - history: &ExtraQueryHistory, + history: &ExtraQueryHistory, // TODO(andreas): don't take extra history, take something like `re_viewer_context::QueryRange` so we don't need to convert. entity_path: &EntityPath, ) -> Results { let visible_history = match timeline.typ() { diff --git a/crates/re_space_view_time_series/src/line_visualizer_system.rs b/crates/re_space_view_time_series/src/line_visualizer_system.rs index 5525f0ec0db8..9e7fd7d869c2 100644 --- a/crates/re_space_view_time_series/src/line_visualizer_system.rs +++ b/crates/re_space_view_time_series/src/line_visualizer_system.rs @@ -183,8 +183,7 @@ fn load_series( let mut points; let time_range = determine_time_range( - ctx, - query, + query.latest_at, data_result, plot_bounds, ctx.app_options.experimental_plot_query_clamping, diff --git a/crates/re_space_view_time_series/src/point_visualizer_system.rs b/crates/re_space_view_time_series/src/point_visualizer_system.rs index 6bfeca20cd0e..f41153a15cda 100644 --- a/crates/re_space_view_time_series/src/point_visualizer_system.rs +++ b/crates/re_space_view_time_series/src/point_visualizer_system.rs @@ -136,8 +136,7 @@ impl SeriesPointSystem { let mut points; let time_range = determine_time_range( - ctx, - query, + query.latest_at, data_result, plot_bounds, ctx.app_options.experimental_plot_query_clamping, diff --git a/crates/re_space_view_time_series/src/space_view_class.rs b/crates/re_space_view_time_series/src/space_view_class.rs index 6eddd010b98a..479239f9239d 100644 --- a/crates/re_space_view_time_series/src/space_view_class.rs +++ b/crates/re_space_view_time_series/src/space_view_class.rs @@ -5,15 +5,17 @@ use egui_plot::{Legend, Line, Plot, PlotPoint, Points}; use re_data_store::TimeType; use re_format::next_grid_tick_magnitude_ns; use re_log_types::{EntityPath, TimeInt, TimeZone}; -use re_space_view::{controls, query_space_view_sub_archetype_or_default}; -use re_types::blueprint::datatypes::VisibleTimeRange; -use re_types::SpaceViewClassIdentifier; -use re_types::{blueprint::components::Corner2D, components::Range1D}; +use re_space_view::{controls, query_view_property_or_default}; +use re_types::{ + blueprint::{components::Corner2D, datatypes::VisibleTimeRange}, + components::Range1D, + SpaceViewClassIdentifier, View, +}; use re_viewer_context::external::re_entity_db::{ EditableAutoValue, EntityProperties, TimeSeriesAggregator, }; use re_viewer_context::{ - IdentifiedViewSystem, IndicatedEntities, PerVisualizer, RecommendedSpaceView, + IdentifiedViewSystem, IndicatedEntities, PerVisualizer, QueryRange, RecommendedSpaceView, SmallVisualizerSet, SpaceViewClass, SpaceViewClassRegistryError, SpaceViewId, SpaceViewSpawnHeuristics, SpaceViewState, SpaceViewStateExt as _, SpaceViewSystemExecutionError, SystemExecutionOutput, ViewQuery, ViewSystemIdentifier, @@ -73,15 +75,10 @@ impl SpaceViewState for TimeSeriesSpaceViewState { #[derive(Default)] pub struct TimeSeriesSpaceView; -use re_types::View; type ViewType = re_types::blueprint::views::TimeSeriesView; const DEFAULT_LEGEND_CORNER: egui_plot::Corner = egui_plot::Corner::RightBottom; -impl TimeSeriesSpaceView { - pub const DEFAULT_TIME_RANGE: VisibleTimeRange = VisibleTimeRange::EVERYTHING; -} - impl SpaceViewClass for TimeSeriesSpaceView { fn identifier() -> SpaceViewClassIdentifier { ViewType::identifier() @@ -145,8 +142,8 @@ impl SpaceViewClass for TimeSeriesSpaceView { re_viewer_context::SpaceViewClassLayoutPriority::Low } - fn default_visible_time_range(&self) -> VisibleTimeRange { - Self::DEFAULT_TIME_RANGE.clone() + fn default_query_range(&self) -> QueryRange { + QueryRange::TimeRange(VisibleTimeRange::EVERYTHING) } fn selection_ui( @@ -321,11 +318,7 @@ It can greatly improve performance (and readability) in such situations as it pr corner: legend_corner, }, _, - ) = query_space_view_sub_archetype_or_default( - query.space_view_id, - blueprint_db, - blueprint_query, - ); + ) = query_view_property_or_default(query.space_view_id, blueprint_db, blueprint_query); let ( re_types::blueprint::archetypes::ScalarAxis { @@ -333,11 +326,7 @@ It can greatly improve performance (and readability) in such situations as it pr lock_range_during_zoom: y_lock_range_during_zoom, }, _, - ) = query_space_view_sub_archetype_or_default( - query.space_view_id, - blueprint_db, - blueprint_query, - ); + ) = query_view_property_or_default(query.space_view_id, blueprint_db, blueprint_query); let (current_time, time_type, timeline) = { // Avoid holding the lock for long @@ -632,7 +621,7 @@ fn legend_ui(ctx: &ViewerContext<'_>, space_view_id: SpaceViewId, ui: &mut egui: let blueprint_db = ctx.store_context.blueprint; let blueprint_query = ctx.blueprint_query; let (re_types::blueprint::archetypes::PlotLegend { visible, corner }, blueprint_path) = - query_space_view_sub_archetype_or_default(space_view_id, blueprint_db, blueprint_query); + query_view_property_or_default(space_view_id, blueprint_db, blueprint_query); ctx.re_ui .selection_grid(ui, "time_series_selection_ui_legend") @@ -699,7 +688,7 @@ fn axis_ui( lock_range_during_zoom: y_lock_range_during_zoom, }, blueprint_path, - ) = query_space_view_sub_archetype_or_default( + ) = query_view_property_or_default( space_view_id, ctx.store_context.blueprint, ctx.blueprint_query, diff --git a/crates/re_space_view_time_series/src/util.rs b/crates/re_space_view_time_series/src/util.rs index cebe02da7baf..89cfd4560123 100644 --- a/crates/re_space_view_time_series/src/util.rs +++ b/crates/re_space_view_time_series/src/util.rs @@ -1,11 +1,14 @@ use re_log_types::{EntityPath, TimeRange}; -use re_space_view::visible_time_range_to_time_range; -use re_types::datatypes::Utf8; +use re_space_view::time_range_from_visible_time_range; +use re_types::{ + blueprint::datatypes::{VisibleTimeRange, VisibleTimeRangeBoundary}, + datatypes::Utf8, +}; use re_viewer_context::{external::re_entity_db::TimeSeriesAggregator, ViewQuery, ViewerContext}; use crate::{ aggregation::{AverageAggregator, MinMaxAggregator}, - PlotPoint, PlotSeries, PlotSeriesKind, ScatterAttrs, TimeSeriesSpaceView, + PlotPoint, PlotSeries, PlotSeriesKind, ScatterAttrs, }; /// Find the plot bounds and the per-ui-point delta from egui. @@ -29,24 +32,29 @@ pub fn determine_plot_bounds_and_time_per_pixel( } pub fn determine_time_range( - ctx: &ViewerContext<'_>, - query: &ViewQuery<'_>, + time_cursor: re_log_types::TimeInt, data_result: &re_viewer_context::DataResult, plot_bounds: Option, enable_query_clamping: bool, ) -> TimeRange { - let visible_time_range_override = match query.timeline.typ() { - re_log_types::TimeType::Time => data_result - .lookup_override::(ctx) - .map(|v| v.0), - re_log_types::TimeType::Sequence => data_result - .lookup_override::(ctx) - .map(|v| v.0), - } - .unwrap_or(TimeSeriesSpaceView::DEFAULT_TIME_RANGE); + let query_range = data_result.query_range(); + + // Latest-at doesn't make sense for time series and should also never happen. + let visible_time_range = match query_range { + re_viewer_context::QueryRange::TimeRange(time_range) => time_range.clone(), + re_viewer_context::QueryRange::LatestAt => { + re_log::error_once!( + "Unexexpected LatestAt query for time series data result at path {:?}", + data_result.entity_path + ); + VisibleTimeRange { + start: VisibleTimeRangeBoundary::AT_CURSOR, + end: VisibleTimeRangeBoundary::AT_CURSOR, + } + } + }; - let mut time_range = - visible_time_range_to_time_range(&visible_time_range_override, query.latest_at); + let mut time_range = time_range_from_visible_time_range(&visible_time_range, time_cursor); // TODO(cmc): We would love to reduce the query to match the actual plot bounds, but because // the plot widget handles zoom after we provide it with data for the current frame, diff --git a/crates/re_types/definitions/rerun/blueprint.fbs b/crates/re_types/definitions/rerun/blueprint.fbs index 5d8717d4f60f..7eeb9a27feba 100644 --- a/crates/re_types/definitions/rerun/blueprint.fbs +++ b/crates/re_types/definitions/rerun/blueprint.fbs @@ -30,6 +30,7 @@ include "./blueprint/archetypes/panel_blueprint.fbs"; include "./blueprint/archetypes/space_view_blueprint.fbs"; include "./blueprint/archetypes/space_view_contents.fbs"; include "./blueprint/archetypes/viewport_blueprint.fbs"; +include "./blueprint/archetypes/visible_time_range.fbs"; include "./blueprint/archetypes/visual_bounds.fbs"; include "./blueprint/archetypes/plot_legend.fbs"; diff --git a/crates/re_types/definitions/rerun/blueprint/archetypes/visible_time_range.fbs b/crates/re_types/definitions/rerun/blueprint/archetypes/visible_time_range.fbs new file mode 100644 index 000000000000..23c170476014 --- /dev/null +++ b/crates/re_types/definitions/rerun/blueprint/archetypes/visible_time_range.fbs @@ -0,0 +1,32 @@ +include "arrow/attributes.fbs"; +include "python/attributes.fbs"; +include "rust/attributes.fbs"; + +include "rerun/datatypes.fbs"; +include "rerun/attributes.fbs"; + +namespace rerun.blueprint.archetypes; + +// --- + +/// Configures what range of the timeline is shown on a view. +/// +/// Whenever no visual time range applies, queries are done with "latest at" semantics. +/// This means that the view will, starting from the time cursor position, +/// query the latest data available for each component type. +/// +/// The default visual time range depends on the type of view this property applies to: +/// - For time series views, the default is to show the entire timeline. +/// - For any other view, the default is to apply latest-at semantics. +/// +/// The visual time range can be overridden also individually per entity. +table VisibleTimeRange ( + "attr.rerun.scope": "blueprint", + "attr.rust.derive": "Default" +) { + /// The range of time to show for timelines based on sequence numbers. + sequence: rerun.blueprint.components.VisibleTimeRangeSequence ("attr.rerun.component_optional", nullable, order: 1000); + + /// The range of time to show for timelines based on time. + time: rerun.blueprint.components.VisibleTimeRangeTime ("attr.rerun.component_optional", nullable, order: 2000); +} diff --git a/crates/re_types/definitions/rerun/blueprint/views/spatial2d.fbs b/crates/re_types/definitions/rerun/blueprint/views/spatial2d.fbs index 86063fc7d097..1b76155252c6 100644 --- a/crates/re_types/definitions/rerun/blueprint/views/spatial2d.fbs +++ b/crates/re_types/definitions/rerun/blueprint/views/spatial2d.fbs @@ -14,4 +14,8 @@ table Spatial2DView ( /// Everything within these bounds are guaranteed to be visible. /// Somethings outside of these bounds may also be visible due to letterboxing. visual_bounds: rerun.blueprint.archetypes.VisualBounds (order: 2000); + + /// Configures the range on the timeline shown by this view (unless specified differently per entity). + time_range: rerun.blueprint.archetypes.VisibleTimeRange (order: 10000); + } diff --git a/crates/re_types/definitions/rerun/blueprint/views/spatial3d.fbs b/crates/re_types/definitions/rerun/blueprint/views/spatial3d.fbs index 1c1278c33a7a..27727f729b19 100644 --- a/crates/re_types/definitions/rerun/blueprint/views/spatial3d.fbs +++ b/crates/re_types/definitions/rerun/blueprint/views/spatial3d.fbs @@ -10,4 +10,7 @@ table Spatial3DView ( ) { /// Configuration for the background of the space view. background: rerun.blueprint.archetypes.Background (order: 1000); + + /// Configures the range on the timeline shown by this view (unless specified differently per entity). + time_range: rerun.blueprint.archetypes.VisibleTimeRange (order: 2000); } diff --git a/crates/re_types/definitions/rerun/blueprint/views/time_series.fbs b/crates/re_types/definitions/rerun/blueprint/views/time_series.fbs index a4391c61dc61..802aa9e7c399 100644 --- a/crates/re_types/definitions/rerun/blueprint/views/time_series.fbs +++ b/crates/re_types/definitions/rerun/blueprint/views/time_series.fbs @@ -11,4 +11,7 @@ table TimeSeriesView ( /// Configures the legend of the plot. plot_legend: rerun.blueprint.archetypes.PlotLegend (order: 2000); + + /// Configures the time range the plot covers (unless specified differently per entity). + time_range: rerun.blueprint.archetypes.VisibleTimeRange (order: 3000); } diff --git a/crates/re_types/src/blueprint/archetypes/.gitattributes b/crates/re_types/src/blueprint/archetypes/.gitattributes index 99631b4ea017..68aaa951e26e 100644 --- a/crates/re_types/src/blueprint/archetypes/.gitattributes +++ b/crates/re_types/src/blueprint/archetypes/.gitattributes @@ -7,4 +7,5 @@ plot_legend.rs linguist-generated=true scalar_axis.rs linguist-generated=true space_view_blueprint.rs linguist-generated=true space_view_contents.rs linguist-generated=true +visible_time_range.rs linguist-generated=true visual_bounds.rs linguist-generated=true diff --git a/crates/re_types/src/blueprint/archetypes/mod.rs b/crates/re_types/src/blueprint/archetypes/mod.rs index bca93c2d48e0..38f1706c4582 100644 --- a/crates/re_types/src/blueprint/archetypes/mod.rs +++ b/crates/re_types/src/blueprint/archetypes/mod.rs @@ -6,6 +6,7 @@ mod plot_legend; mod scalar_axis; mod space_view_blueprint; mod space_view_contents; +mod visible_time_range; mod visual_bounds; pub use self::background::Background; @@ -13,4 +14,5 @@ pub use self::plot_legend::PlotLegend; pub use self::scalar_axis::ScalarAxis; pub use self::space_view_blueprint::SpaceViewBlueprint; pub use self::space_view_contents::SpaceViewContents; +pub use self::visible_time_range::VisibleTimeRange; pub use self::visual_bounds::VisualBounds; diff --git a/crates/re_types/src/blueprint/archetypes/visible_time_range.rs b/crates/re_types/src/blueprint/archetypes/visible_time_range.rs new file mode 100644 index 000000000000..016a56c8dd65 --- /dev/null +++ b/crates/re_types/src/blueprint/archetypes/visible_time_range.rs @@ -0,0 +1,207 @@ +// DO NOT EDIT! This file was auto-generated by crates/re_types_builder/src/codegen/rust/api.rs +// Based on "crates/re_types/definitions/rerun/blueprint/archetypes/visible_time_range.fbs". + +#![allow(trivial_numeric_casts)] +#![allow(unused_imports)] +#![allow(unused_parens)] +#![allow(clippy::clone_on_copy)] +#![allow(clippy::cloned_instead_of_copied)] +#![allow(clippy::iter_on_single_items)] +#![allow(clippy::map_flatten)] +#![allow(clippy::match_wildcard_for_single_variants)] +#![allow(clippy::needless_question_mark)] +#![allow(clippy::new_without_default)] +#![allow(clippy::redundant_closure)] +#![allow(clippy::too_many_arguments)] +#![allow(clippy::too_many_lines)] +#![allow(clippy::unnecessary_cast)] + +use ::re_types_core::external::arrow2; +use ::re_types_core::ComponentName; +use ::re_types_core::SerializationResult; +use ::re_types_core::{ComponentBatch, MaybeOwnedComponentBatch}; +use ::re_types_core::{DeserializationError, DeserializationResult}; + +/// **Archetype**: Configures what range of the timeline is shown on a view. +/// +/// Whenever no visual time range applies, queries are done with "latest at" semantics. +/// This means that the view will, starting from the time cursor position, +/// query the latest data available for each component type. +/// +/// The default visual time range depends on the type of view this property applies to: +/// - For time series views, the default is to show the entire timeline. +/// - For any other view, the default is to apply latest-at semantics. +/// +/// The visual time range can be overridden also individually per entity. +#[derive(Clone, Debug, Default)] +pub struct VisibleTimeRange { + /// The range of time to show for timelines based on sequence numbers. + pub sequence: Option, + + /// The range of time to show for timelines based on time. + pub time: Option, +} + +impl ::re_types_core::SizeBytes for VisibleTimeRange { + #[inline] + fn heap_size_bytes(&self) -> u64 { + self.sequence.heap_size_bytes() + self.time.heap_size_bytes() + } + + #[inline] + fn is_pod() -> bool { + >::is_pod() + && >::is_pod() + } +} + +static REQUIRED_COMPONENTS: once_cell::sync::Lazy<[ComponentName; 0usize]> = + once_cell::sync::Lazy::new(|| []); + +static RECOMMENDED_COMPONENTS: once_cell::sync::Lazy<[ComponentName; 1usize]> = + once_cell::sync::Lazy::new(|| ["rerun.blueprint.components.VisibleTimeRangeIndicator".into()]); + +static OPTIONAL_COMPONENTS: once_cell::sync::Lazy<[ComponentName; 2usize]> = + once_cell::sync::Lazy::new(|| { + [ + "rerun.blueprint.components.VisibleTimeRangeSequence".into(), + "rerun.blueprint.components.VisibleTimeRangeTime".into(), + ] + }); + +static ALL_COMPONENTS: once_cell::sync::Lazy<[ComponentName; 3usize]> = + once_cell::sync::Lazy::new(|| { + [ + "rerun.blueprint.components.VisibleTimeRangeIndicator".into(), + "rerun.blueprint.components.VisibleTimeRangeSequence".into(), + "rerun.blueprint.components.VisibleTimeRangeTime".into(), + ] + }); + +impl VisibleTimeRange { + /// The total number of components in the archetype: 0 required, 1 recommended, 2 optional + pub const NUM_COMPONENTS: usize = 3usize; +} + +/// Indicator component for the [`VisibleTimeRange`] [`::re_types_core::Archetype`] +pub type VisibleTimeRangeIndicator = ::re_types_core::GenericIndicatorComponent; + +impl ::re_types_core::Archetype for VisibleTimeRange { + type Indicator = VisibleTimeRangeIndicator; + + #[inline] + fn name() -> ::re_types_core::ArchetypeName { + "rerun.blueprint.archetypes.VisibleTimeRange".into() + } + + #[inline] + fn indicator() -> MaybeOwnedComponentBatch<'static> { + static INDICATOR: VisibleTimeRangeIndicator = VisibleTimeRangeIndicator::DEFAULT; + MaybeOwnedComponentBatch::Ref(&INDICATOR) + } + + #[inline] + fn required_components() -> ::std::borrow::Cow<'static, [ComponentName]> { + REQUIRED_COMPONENTS.as_slice().into() + } + + #[inline] + fn recommended_components() -> ::std::borrow::Cow<'static, [ComponentName]> { + RECOMMENDED_COMPONENTS.as_slice().into() + } + + #[inline] + fn optional_components() -> ::std::borrow::Cow<'static, [ComponentName]> { + OPTIONAL_COMPONENTS.as_slice().into() + } + + #[inline] + fn all_components() -> ::std::borrow::Cow<'static, [ComponentName]> { + ALL_COMPONENTS.as_slice().into() + } + + #[inline] + fn from_arrow_components( + arrow_data: impl IntoIterator)>, + ) -> DeserializationResult { + re_tracing::profile_function!(); + use ::re_types_core::{Loggable as _, ResultExt as _}; + let arrays_by_name: ::std::collections::HashMap<_, _> = arrow_data + .into_iter() + .map(|(name, array)| (name.full_name(), array)) + .collect(); + let sequence = if let Some(array) = + arrays_by_name.get("rerun.blueprint.components.VisibleTimeRangeSequence") + { + ::from_arrow_opt(&**array) + .with_context("rerun.blueprint.archetypes.VisibleTimeRange#sequence")? + .into_iter() + .next() + .flatten() + } else { + None + }; + let time = if let Some(array) = + arrays_by_name.get("rerun.blueprint.components.VisibleTimeRangeTime") + { + ::from_arrow_opt(&**array) + .with_context("rerun.blueprint.archetypes.VisibleTimeRange#time")? + .into_iter() + .next() + .flatten() + } else { + None + }; + Ok(Self { sequence, time }) + } +} + +impl ::re_types_core::AsComponents for VisibleTimeRange { + fn as_component_batches(&self) -> Vec> { + re_tracing::profile_function!(); + use ::re_types_core::Archetype as _; + [ + Some(Self::indicator()), + self.sequence + .as_ref() + .map(|comp| (comp as &dyn ComponentBatch).into()), + self.time + .as_ref() + .map(|comp| (comp as &dyn ComponentBatch).into()), + ] + .into_iter() + .flatten() + .collect() + } +} + +impl VisibleTimeRange { + /// Create a new `VisibleTimeRange`. + #[inline] + pub fn new() -> Self { + Self { + sequence: None, + time: None, + } + } + + /// The range of time to show for timelines based on sequence numbers. + #[inline] + pub fn with_sequence( + mut self, + sequence: impl Into, + ) -> Self { + self.sequence = Some(sequence.into()); + self + } + + /// The range of time to show for timelines based on time. + #[inline] + pub fn with_time( + mut self, + time: impl Into, + ) -> Self { + self.time = Some(time.into()); + self + } +} diff --git a/crates/re_types/src/blueprint/datatypes/visible_time_range_ext.rs b/crates/re_types/src/blueprint/datatypes/visible_time_range_ext.rs index 7de1d58ce394..218dc2797777 100644 --- a/crates/re_types/src/blueprint/datatypes/visible_time_range_ext.rs +++ b/crates/re_types/src/blueprint/datatypes/visible_time_range_ext.rs @@ -1,12 +1,6 @@ use super::{VisibleTimeRange, VisibleTimeRangeBoundary}; impl VisibleTimeRange { - /// The empty range, set at the current time cursor. - pub const EMPTY: Self = Self { - start: VisibleTimeRangeBoundary::AT_CURSOR, - end: VisibleTimeRangeBoundary::AT_CURSOR, - }; - /// The range encompassing all time, from -∞ to +∞. pub const EVERYTHING: Self = Self { // This means -∞ diff --git a/crates/re_types/src/blueprint/views/spatial2d_view.rs b/crates/re_types/src/blueprint/views/spatial2d_view.rs index c77bca3cdbe8..75b6e96f4b36 100644 --- a/crates/re_types/src/blueprint/views/spatial2d_view.rs +++ b/crates/re_types/src/blueprint/views/spatial2d_view.rs @@ -33,18 +33,24 @@ pub struct Spatial2DView { /// Everything within these bounds are guaranteed to be visible. /// Somethings outside of these bounds may also be visible due to letterboxing. pub visual_bounds: crate::blueprint::archetypes::VisualBounds, + + /// Configures the range on the timeline shown by this view (unless specified differently per entity). + pub time_range: crate::blueprint::archetypes::VisibleTimeRange, } impl ::re_types_core::SizeBytes for Spatial2DView { #[inline] fn heap_size_bytes(&self) -> u64 { - self.background.heap_size_bytes() + self.visual_bounds.heap_size_bytes() + self.background.heap_size_bytes() + + self.visual_bounds.heap_size_bytes() + + self.time_range.heap_size_bytes() } #[inline] fn is_pod() -> bool { ::is_pod() && ::is_pod() + && ::is_pod() } } diff --git a/crates/re_types/src/blueprint/views/spatial3d_view.rs b/crates/re_types/src/blueprint/views/spatial3d_view.rs index b3fbf3be8a00..4311b60d514d 100644 --- a/crates/re_types/src/blueprint/views/spatial3d_view.rs +++ b/crates/re_types/src/blueprint/views/spatial3d_view.rs @@ -27,41 +27,21 @@ use ::re_types_core::{DeserializationError, DeserializationResult}; pub struct Spatial3DView { /// Configuration for the background of the space view. pub background: crate::blueprint::archetypes::Background, + + /// Configures the range on the timeline shown by this view (unless specified differently per entity). + pub time_range: crate::blueprint::archetypes::VisibleTimeRange, } impl ::re_types_core::SizeBytes for Spatial3DView { #[inline] fn heap_size_bytes(&self) -> u64 { - self.background.heap_size_bytes() + self.background.heap_size_bytes() + self.time_range.heap_size_bytes() } #[inline] fn is_pod() -> bool { ::is_pod() - } -} - -impl> From for Spatial3DView { - fn from(v: T) -> Self { - Self { - background: v.into(), - } - } -} - -impl std::borrow::Borrow for Spatial3DView { - #[inline] - fn borrow(&self) -> &crate::blueprint::archetypes::Background { - &self.background - } -} - -impl std::ops::Deref for Spatial3DView { - type Target = crate::blueprint::archetypes::Background; - - #[inline] - fn deref(&self) -> &crate::blueprint::archetypes::Background { - &self.background + && ::is_pod() } } diff --git a/crates/re_types/src/blueprint/views/time_series_view.rs b/crates/re_types/src/blueprint/views/time_series_view.rs index beba05c1634f..dd90930c629f 100644 --- a/crates/re_types/src/blueprint/views/time_series_view.rs +++ b/crates/re_types/src/blueprint/views/time_series_view.rs @@ -30,18 +30,24 @@ pub struct TimeSeriesView { /// Configures the legend of the plot. pub plot_legend: crate::blueprint::archetypes::PlotLegend, + + /// Configures the time range the plot covers (unless specified differently per entity). + pub time_range: crate::blueprint::archetypes::VisibleTimeRange, } impl ::re_types_core::SizeBytes for TimeSeriesView { #[inline] fn heap_size_bytes(&self) -> u64 { - self.axis_y.heap_size_bytes() + self.plot_legend.heap_size_bytes() + self.axis_y.heap_size_bytes() + + self.plot_legend.heap_size_bytes() + + self.time_range.heap_size_bytes() } #[inline] fn is_pod() -> bool { ::is_pod() && ::is_pod() + && ::is_pod() } } diff --git a/crates/re_viewer/src/app_state.rs b/crates/re_viewer/src/app_state.rs index 3898c891b12e..59ad0f8294e3 100644 --- a/crates/re_viewer/src/app_state.rs +++ b/crates/re_viewer/src/app_state.rs @@ -261,15 +261,19 @@ impl AppState { &space_view.space_origin, ); - let props = viewport.state.space_view_props(space_view.id); let resolver = space_view.contents.build_resolver( space_view_class_registry, space_view, - props, &visualizable_entities, &indicated_entities_per_visualizer, ); - resolver.update_overrides(store_context, &blueprint_query, query_result); + resolver.update_overrides( + store_context.blueprint, + &blueprint_query, + rec_cfg.time_ctrl.read().timeline(), + space_view_class_registry, + query_result, + ); } } }; diff --git a/crates/re_viewer/src/ui/mod.rs b/crates/re_viewer/src/ui/mod.rs index b9a48af02fc4..4afbd2cc5900 100644 --- a/crates/re_viewer/src/ui/mod.rs +++ b/crates/re_viewer/src/ui/mod.rs @@ -10,7 +10,7 @@ mod welcome_screen; pub(crate) mod memory_panel; pub(crate) mod selection_panel; pub(crate) mod space_view_space_origin_ui; -pub(crate) mod visible_history; +pub(crate) mod visual_time_range; pub use blueprint_panel::blueprint_panel_ui; pub use recordings_panel::recordings_panel_ui; diff --git a/crates/re_viewer/src/ui/selection_panel.rs b/crates/re_viewer/src/ui/selection_panel.rs index 97aef089d2f8..d1eb4de03bbf 100644 --- a/crates/re_viewer/src/ui/selection_panel.rs +++ b/crates/re_viewer/src/ui/selection_panel.rs @@ -13,7 +13,6 @@ use re_space_view_time_series::TimeSeriesSpaceView; use re_types::{ components::{PinholeProjection, Transform3D}, tensor_data::TensorDataMeaning, - SpaceViewClassIdentifier, }; use re_ui::{icons, list_item::ListItem}; use re_ui::{ReUi, SyntaxHighlighting as _}; @@ -29,7 +28,10 @@ use re_viewport::{ use crate::ui::override_ui::override_visualizer_ui; use crate::{app_state::default_selection_panel_width, ui::override_ui::override_ui}; -use super::{selection_history_ui::SelectionHistoryUi, visible_history::visual_time_range_ui}; +use super::{ + selection_history_ui::SelectionHistoryUi, visual_time_range::visual_time_range_ui_data_result, + visual_time_range::visual_time_range_ui_space_view, +}; // --- @@ -902,23 +904,12 @@ fn blueprint_ui_for_space_view( &class_identifier, ); - // Space View don't inherit properties. - let space_view_data_result = - space_view.space_view_data_result(ctx.store_context, ctx.blueprint_query); - - visual_time_range_ui( - ctx, - ui, - None, // There is no tree above the space view yet - &space_view_data_result, - class_identifier, - true, - ); + visual_time_range_ui_space_view(ctx, ui, space_view); - let mut props = space_view_data_result - .individual_properties() - .cloned() - .unwrap_or_default(); + // Space View don't inherit (legacy) properties. + let mut props = + space_view.legacy_properties(ctx.store_context.blueprint, ctx.blueprint_query); + let props_before = props.clone(); let space_view_class = space_view.class(ctx.space_view_class_registry); if let Err(err) = space_view_class.selection_ui( @@ -936,7 +927,9 @@ fn blueprint_ui_for_space_view( ); } - space_view_data_result.save_individual_override_properties(ctx, Some(props)); + if props_before != props { + space_view.save_legacy_properties(ctx, props); + } } } @@ -950,7 +943,6 @@ fn blueprint_ui_for_data_result( if let Some(space_view) = viewport.blueprint.space_view(&space_view_id) { if instance_path.instance.is_all() { // the whole entity - let space_view_class = *space_view.class_identifier(); let entity_path = &instance_path.entity_path; let query_result = ctx.lookup_query_result(space_view.id); @@ -968,7 +960,6 @@ fn blueprint_ui_for_data_result( ctx, ui, ctx.lookup_query_result(space_view_id), - &space_view_class, entity_path, &mut props, ); @@ -1143,7 +1134,6 @@ fn entity_props_ui( ctx: &ViewerContext<'_>, ui: &mut egui::Ui, query_result: &DataQueryResult, - space_view_class: &SpaceViewClassIdentifier, entity_path: &EntityPath, entity_props: &mut EntityProperties, ) { @@ -1156,7 +1146,7 @@ fn entity_props_ui( }; { - let visible_before = data_result.lookup_override_or_default::(ctx); + let visible_before = data_result.is_visible(ctx); let mut visible = visible_before; let override_source = @@ -1165,7 +1155,7 @@ fn entity_props_ui( override_source.is_some() && override_source.as_ref() != Some(entity_path); ui.horizontal(|ui| { - re_ui.checkbox(ui, &mut visible.0, "Visible"); + re_ui.checkbox(ui, &mut visible, "Visible"); if is_inherited { ui.label("(inherited)"); } @@ -1175,7 +1165,7 @@ fn entity_props_ui( data_result.save_recursive_override_or_clear_if_redundant( ctx, &query_result.tree, - &visible, + &Visible(visible), ); } } @@ -1184,14 +1174,7 @@ 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"); - visual_time_range_ui( - ctx, - ui, - Some(&query_result.tree), - data_result, - *space_view_class, - false, - ); + visual_time_range_ui_data_result(ctx, ui, &query_result.tree, data_result); egui::Grid::new("entity_properties") .num_columns(2) diff --git a/crates/re_viewer/src/ui/visible_history.rs b/crates/re_viewer/src/ui/visual_time_range.rs similarity index 66% rename from crates/re_viewer/src/ui/visible_history.rs rename to crates/re_viewer/src/ui/visual_time_range.rs index 50a2260dd1a6..beb69d6f4b1b 100644 --- a/crates/re_viewer/src/ui/visible_history.rs +++ b/crates/re_viewer/src/ui/visual_time_range.rs @@ -4,20 +4,25 @@ use std::ops::RangeInclusive; use egui::{NumExt as _, Response, Ui}; use re_entity_db::{TimeHistogram, VisibleHistory, VisibleHistoryBoundary}; -use re_log_types::{TimeInt, TimeType, TimeZone}; +use re_log_types::{EntityPath, TimeInt, TimeType, TimeZone}; use re_space_view::{ - time_range_boundary_to_visible_history_boundary, - visible_history_boundary_to_time_range_boundary, visible_time_range_to_time_range, + query_view_property, time_range_from_visible_time_range, + visible_history_boundary_from_time_range_boundary, + visible_history_boundary_to_time_range_boundary, SpaceViewBlueprint, }; use re_space_view_spatial::{SpatialSpaceView2D, SpatialSpaceView3D}; use re_space_view_time_series::TimeSeriesSpaceView; use re_types::{ - blueprint::components::{VisibleTimeRangeSequence, VisibleTimeRangeTime}, + blueprint::{ + archetypes::VisibleTimeRange, + components::{VisibleTimeRangeSequence, VisibleTimeRangeTime}, + datatypes::VisibleTimeRangeBoundary, + }, SpaceViewClassIdentifier, }; use re_types_core::Loggable as _; use re_ui::{markdown_ui, ReUi}; -use re_viewer_context::{SpaceViewClass, ViewerContext}; +use re_viewer_context::{QueryRange, SpaceViewClass, ViewerContext}; /// These space views support the Visible History feature. static VISIBLE_HISTORY_SUPPORTED_SPACE_VIEWS: once_cell::sync::Lazy< @@ -38,222 +43,294 @@ fn space_view_with_visible_history(space_view_class: SpaceViewClassIdentifier) - VISIBLE_HISTORY_SUPPORTED_SPACE_VIEWS.contains(&space_view_class) } -pub fn visual_time_range_ui( +pub fn visual_time_range_ui_space_view( ctx: &ViewerContext<'_>, ui: &mut Ui, - data_result_tree: Option<&re_viewer_context::DataResultTree>, - data_result: &re_viewer_context::DataResult, - space_view_class: SpaceViewClassIdentifier, - is_space_view: bool, + space_view: &SpaceViewBlueprint, ) { - let time_ctrl = ctx.rec_cfg.time_ctrl.read().clone(); - - if is_space_view && !space_view_with_visible_history(space_view_class) { + if !space_view_with_visible_history(*space_view.class_identifier()) { return; } - let re_ui = ctx.re_ui; - + let time_ctrl = ctx.rec_cfg.time_ctrl.read().clone(); let time_type = time_ctrl.timeline().typ(); - let mut interacting_with_controls = false; + let (property, property_path) = query_view_property::( + space_view.id, + ctx.store_context.blueprint, + ctx.blueprint_query, + ); + + let has_individual_range = match time_type { + TimeType::Time => property.ok().flatten().map_or(false, |v| v.time.is_some()), + TimeType::Sequence => property + .ok() + .flatten() + .map_or(false, |v| v.sequence.is_some()), + }; + + let query_range = space_view.query_range( + ctx.store_context.blueprint, + ctx.blueprint_query, + ctx.rec_cfg.time_ctrl.read().timeline(), + ctx.space_view_class_registry, + ); + + let is_space_view = true; + visual_time_range_ui( + ctx, + ui, + &query_range, + has_individual_range, + is_space_view, + &property_path, + ); +} - let space_view_class = ctx - .space_view_class_registry - .get_class_or_log_error(&space_view_class); +pub fn visual_time_range_ui_data_result( + ctx: &ViewerContext<'_>, + ui: &mut Ui, + data_result_tree: &re_viewer_context::DataResultTree, + data_result: &re_viewer_context::DataResult, +) { + let time_type = ctx.rec_cfg.time_ctrl.read().timeline().typ(); - let active_time_range_override = match time_type { - TimeType::Time => data_result - .lookup_override::(ctx) - .map(|v| v.0), + let has_individual_range = match time_type { + TimeType::Time => { + data_result.component_override_source(data_result_tree, &VisibleTimeRangeTime::name()) + } TimeType::Sequence => data_result - .lookup_override::(ctx) - .map(|v| v.0), + .component_override_source(data_result_tree, &VisibleTimeRangeSequence::name()), + } + .is_some(); + + let Some(override_path) = data_result.recursive_override_path() else { + re_log::error_once!("No override computed yet for entity"); + return; + }; + let Some(overrides) = data_result.property_overrides.as_ref() else { + re_log::error_once!("No override computed yet for entity"); + return; }; - let mut has_individual_range = if let Some(data_result_tree) = data_result_tree { - // If there is a data-tree, we know we have individual settings if we are our own source. - let component_override_source = match time_type { - TimeType::Time => data_result - .component_override_source(data_result_tree, &VisibleTimeRangeTime::name()), - TimeType::Sequence => data_result - .component_override_source(data_result_tree, &VisibleTimeRangeSequence::name()), - }; + let is_space_view = false; + visual_time_range_ui( + ctx, + ui, + &overrides.query_range, + has_individual_range, + is_space_view, + override_path, + ); +} - component_override_source.as_ref() == Some(&data_result.entity_path) - } else { - // Otherwise we can inspect directly. - active_time_range_override.is_some() - }; +fn visual_time_range_ui( + ctx: &ViewerContext<'_>, + ui: &mut Ui, + resolved_range: &QueryRange, + mut has_individual_range: bool, + is_space_view: bool, + property_override_path: &EntityPath, +) { + let re_ui = ctx.re_ui; + let time_ctrl = ctx.rec_cfg.time_ctrl.read().clone(); + let time_type = time_ctrl.timeline().typ(); - let mut resolved_range = - active_time_range_override.unwrap_or(space_view_class.default_visible_time_range()); + let mut interacting_with_controls = false; - let collapsing_response = re_ui.collapsing_header(ui, "Visible time range", false, |ui| { - let has_individual_range_before = has_individual_range; - let resolved_range_before = resolved_range.clone(); + let mut resolved_range = match resolved_range { + QueryRange::TimeRange(range) => range.clone(), + QueryRange::LatestAt => { + if has_individual_range { + re_log::error_once!("Visible time range is set but no time range is provided"); + } + // TODO(andreas): Should print a string that we're using the latest time. + re_types::blueprint::datatypes::VisibleTimeRange { + start: VisibleTimeRangeBoundary::AT_CURSOR, + end: VisibleTimeRangeBoundary::AT_CURSOR, + } + } + }; - ui.horizontal(|ui| { - re_ui - .radio_value(ui, &mut has_individual_range, false, "Default") - .on_hover_text(if is_space_view { - "Default visible time range settings for this kind of space view" - } else { - "Visible time range settings inherited from parent Entity or enclosing \ + let collapsing_response = ctx + .re_ui + .collapsing_header(ui, "Visible time range", false, |ui| { + let has_individual_range_before = has_individual_range; + let resolved_range_before = resolved_range.clone(); + + ui.horizontal(|ui| { + re_ui + .radio_value(ui, &mut has_individual_range, false, "Default") + .on_hover_text(if is_space_view { + "Default visible time range settings for this kind of space view" + } else { + "Visible time range settings inherited from parent Entity or enclosing \ space view" - }); - re_ui - .radio_value(ui, &mut has_individual_range, true, "Override") - .on_hover_text(if is_space_view { - "Set visible time range settings for the contents of this space view" + }); + re_ui + .radio_value(ui, &mut has_individual_range, true, "Override") + .on_hover_text(if is_space_view { + "Set visible time range settings for the contents of this space view" + } else { + "Set visible time range settings for this entity" + }); + }); + + let timeline_spec = + if let Some(times) = ctx.recording().time_histogram(time_ctrl.timeline()) { + TimelineSpec::from_time_histogram(times) } else { - "Set visible time range settings for this entity" - }); - }); + TimelineSpec::from_time_range(0..=0) + }; - let timeline_spec = - if let Some(times) = ctx.recording().time_histogram(time_ctrl.timeline()) { - TimelineSpec::from_time_histogram(times) - } else { - TimelineSpec::from_time_range(0..=0) - }; + let current_time = time_ctrl + .time_i64() + .unwrap_or_default() + .at_least(*timeline_spec.range.start()); // accounts for timeless time (TimeInt::MIN) - let current_time = time_ctrl - .time_i64() - .unwrap_or_default() - .at_least(*timeline_spec.range.start()); // accounts for timeless time (TimeInt::MIN) + let from = &mut resolved_range.start; + let to = &mut resolved_range.end; - let from = &mut resolved_range.start; - let to = &mut resolved_range.end; + // Convert to legacy visual history type. + let mut visible_history = VisibleHistory { + from: visible_history_boundary_from_time_range_boundary(from), + to: visible_history_boundary_from_time_range_boundary(to), + }; - // Convert to legacy visual history type. - let mut visible_history = VisibleHistory { - from: time_range_boundary_to_visible_history_boundary(from), - to: time_range_boundary_to_visible_history_boundary(to), - }; + if has_individual_range { + let current_from = visible_history + .range_start_from_cursor(TimeInt::new_temporal(current_time)) + .as_i64(); + let current_to = visible_history + .range_end_from_cursor(TimeInt::new_temporal(current_time)) + .as_i64(); + + egui::Grid::new("from_to_editable").show(ui, |ui| { + re_ui.grid_left_hand_label(ui, "From"); + interacting_with_controls |= ui + .horizontal(|ui| { + visible_history_boundary_ui( + ctx, + ui, + &mut visible_history.from, + time_type, + current_time, + &timeline_spec, + true, + current_to, + ) + }) + .inner; + ui.end_row(); - if has_individual_range { - let current_from = visible_history - .range_start_from_cursor(TimeInt::new_temporal(current_time)) - .as_i64(); - let current_to = visible_history - .range_end_from_cursor(TimeInt::new_temporal(current_time)) - .as_i64(); - - egui::Grid::new("from_to_editable").show(ui, |ui| { - re_ui.grid_left_hand_label(ui, "From"); - interacting_with_controls |= ui - .horizontal(|ui| { - visible_history_boundary_ui( + re_ui.grid_left_hand_label(ui, "To"); + interacting_with_controls |= ui + .horizontal(|ui| { + visible_history_boundary_ui( + ctx, + ui, + &mut visible_history.to, + time_type, + current_time, + &timeline_spec, + false, + current_from, + ) + }) + .inner; + ui.end_row(); + }); + + current_range_ui(ctx, ui, current_time, time_type, &visible_history); + } else { + // Show the resolved visible range as labels (user can't edit them): + + if visible_history.from == VisibleHistoryBoundary::Infinite + && visible_history.to == VisibleHistoryBoundary::Infinite + { + ui.label("Entire timeline"); + } else if visible_history.from == VisibleHistoryBoundary::AT_CURSOR + && visible_history.to == VisibleHistoryBoundary::AT_CURSOR + { + let current_time = time_type.format( + TimeInt::new_temporal(current_time), + ctx.app_options.time_zone, + ); + match time_type { + TimeType::Time => { + ui.label(format!("At current time: {current_time}")); + } + TimeType::Sequence => { + ui.label(format!("At current frame: {current_time}")); + } + } + } else { + egui::Grid::new("from_to_labels").show(ui, |ui| { + re_ui.grid_left_hand_label(ui, "From"); + resolved_visible_history_boundary_ui( ctx, ui, - &mut visible_history.from, + &visible_history.from, time_type, - current_time, - &timeline_spec, true, - current_to, - ) - }) - .inner; - ui.end_row(); - - re_ui.grid_left_hand_label(ui, "To"); - interacting_with_controls |= ui - .horizontal(|ui| { - visible_history_boundary_ui( + ); + ui.end_row(); + + re_ui.grid_left_hand_label(ui, "To"); + resolved_visible_history_boundary_ui( ctx, ui, - &mut visible_history.to, + &visible_history.to, time_type, - current_time, - &timeline_spec, false, - current_from, - ) - }) - .inner; - ui.end_row(); - }); - - current_range_ui(ctx, ui, current_time, time_type, &visible_history); - } else { - // Show the resolved visible range as labels (user can't edit them): + ); + ui.end_row(); + }); - if visible_history.from == VisibleHistoryBoundary::Infinite - && visible_history.to == VisibleHistoryBoundary::Infinite - { - ui.label("Entire timeline"); - } else if visible_history.from == VisibleHistoryBoundary::AT_CURSOR - && visible_history.to == VisibleHistoryBoundary::AT_CURSOR - { - let current_time = time_type.format( - TimeInt::new_temporal(current_time), - ctx.app_options.time_zone, - ); - match time_type { - TimeType::Time => { - ui.label(format!("At current time: {current_time}")); - } - TimeType::Sequence => { - ui.label(format!("At current frame: {current_time}")); - } + current_range_ui(ctx, ui, current_time, time_type, &visible_history); } - } else { - egui::Grid::new("from_to_labels").show(ui, |ui| { - re_ui.grid_left_hand_label(ui, "From"); - resolved_visible_history_boundary_ui( - ctx, - ui, - &visible_history.from, - time_type, - true, - ); - ui.end_row(); - - re_ui.grid_left_hand_label(ui, "To"); - resolved_visible_history_boundary_ui( - ctx, - ui, - &visible_history.to, - time_type, - false, - ); - ui.end_row(); - }); - - current_range_ui(ctx, ui, current_time, time_type, &visible_history); } - } - // Convert back from visual history type. - *from = visible_history_boundary_to_time_range_boundary(&visible_history.from); - *to = visible_history_boundary_to_time_range_boundary(&visible_history.to); + // Convert back from visual history type. + *from = visible_history_boundary_to_time_range_boundary(&visible_history.from); + *to = visible_history_boundary_to_time_range_boundary(&visible_history.to); - // Save to blueprint store if anything has changed. - if has_individual_range != has_individual_range_before - || resolved_range != resolved_range_before - { - if has_individual_range { - let resolved_range = resolved_range.clone(); - match time_type { - TimeType::Time => data_result - .save_recursive_override(ctx, &VisibleTimeRangeTime(resolved_range)), - TimeType::Sequence => data_result - .save_recursive_override(ctx, &VisibleTimeRangeSequence(resolved_range)), - }; - } else { - match time_type { - TimeType::Time => { - data_result.clear_recursive_override::(ctx); - } - TimeType::Sequence => { - data_result.clear_recursive_override::(ctx); + // Save to blueprint store if anything has changed. + if has_individual_range != has_individual_range_before + || resolved_range != resolved_range_before + { + if has_individual_range { + let resolved_range = resolved_range.clone(); + match time_type { + TimeType::Time => { + ctx.save_blueprint_component( + property_override_path, + &VisibleTimeRangeTime(resolved_range), + ); + } + TimeType::Sequence => { + ctx.save_blueprint_component( + property_override_path, + &VisibleTimeRangeSequence(resolved_range), + ); + } + }; + } else { + match time_type { + TimeType::Time => { + ctx.save_empty_blueprint_component::( + property_override_path, + ); + } + TimeType::Sequence => { + ctx.save_empty_blueprint_component::( + property_override_path, + ); + } } } } - } - }); + }); // Add spacer after the visible history section. //TODO(ab): figure out why `item_spacing.y` is added _only_ in collapsed state. @@ -278,7 +355,7 @@ pub fn visual_time_range_ui( if should_display_visible_history { if let Some(current_time) = time_ctrl.time_int() { - let range = visible_time_range_to_time_range(&resolved_range, current_time); + let range = time_range_from_visible_time_range(&resolved_range, current_time); ctx.rec_cfg.time_ctrl.write().highlighted_range = Some(range); } } diff --git a/crates/re_viewer_context/src/lib.rs b/crates/re_viewer_context/src/lib.rs index 5a2a767ac631..e35fa2ff24da 100644 --- a/crates/re_viewer_context/src/lib.rs +++ b/crates/re_viewer_context/src/lib.rs @@ -13,6 +13,7 @@ mod component_ui_registry; mod contents; mod item; mod query_context; +mod query_range; mod selection_history; mod selection_state; mod space_view; @@ -42,6 +43,7 @@ pub use component_ui_registry::{ComponentUiRegistry, UiVerbosity}; pub use contents::{blueprint_id_to_tile_id, Contents, ContentsName}; pub use item::Item; pub use query_context::{DataQueryResult, DataResultHandle, DataResultNode, DataResultTree}; +pub use query_range::QueryRange; pub use selection_history::SelectionHistory; pub use selection_state::{ ApplicationSelectionState, HoverHighlight, InteractionHighlight, ItemCollection, diff --git a/crates/re_viewer_context/src/query_range.rs b/crates/re_viewer_context/src/query_range.rs new file mode 100644 index 000000000000..e0415a6749f7 --- /dev/null +++ b/crates/re_viewer_context/src/query_range.rs @@ -0,0 +1,10 @@ +/// Range & type of data store query. +#[derive(Debug, Clone, PartialEq, Eq, Default)] +pub enum QueryRange { + /// Use a time range on the currently active timeline. + TimeRange(re_types::blueprint::datatypes::VisibleTimeRange), + + /// Use latest-at semantics. + #[default] + LatestAt, +} diff --git a/crates/re_viewer_context/src/space_view/space_view_class.rs b/crates/re_viewer_context/src/space_view/space_view_class.rs index 4a5a29f1330e..249767c0b6ca 100644 --- a/crates/re_viewer_context/src/space_view/space_view_class.rs +++ b/crates/re_viewer_context/src/space_view/space_view_class.rs @@ -1,10 +1,10 @@ use nohash_hasher::IntSet; use re_entity_db::{EntityProperties, EntityPropertyMap}; use re_log_types::EntityPath; -use re_types::{blueprint::datatypes::VisibleTimeRange, ComponentName, SpaceViewClassIdentifier}; +use re_types::{ComponentName, SpaceViewClassIdentifier}; use crate::{ - IndicatedEntities, PerSystemEntities, PerVisualizer, SmallVisualizerSet, + IndicatedEntities, PerSystemEntities, PerVisualizer, QueryRange, SmallVisualizerSet, SpaceViewClassRegistryError, SpaceViewId, SpaceViewSpawnHeuristics, SpaceViewSystemExecutionError, SpaceViewSystemRegistrator, SystemExecutionOutput, ViewQuery, ViewerContext, VisualizableEntities, @@ -93,10 +93,9 @@ pub trait SpaceViewClass: Send + Sync { /// Controls how likely this space view will get a large tile in the ui. fn layout_priority(&self) -> SpaceViewClassLayoutPriority; - /// Determines the default time range for this space view class. - // TODO(#4194): This should be generalized to allow arbitrary property defaults. - fn default_visible_time_range(&self) -> VisibleTimeRange { - VisibleTimeRange::EMPTY.clone() + /// Default query range for this space view. + fn default_query_range(&self) -> QueryRange { + QueryRange::LatestAt } /// Determines a suitable origin given the provided set of entities. diff --git a/crates/re_viewer_context/src/space_view/view_query.rs b/crates/re_viewer_context/src/space_view/view_query.rs index 98c3ef7dc4a9..30bc8568001d 100644 --- a/crates/re_viewer_context/src/space_view/view_query.rs +++ b/crates/re_viewer_context/src/space_view/view_query.rs @@ -3,15 +3,16 @@ use std::collections::BTreeMap; use itertools::Itertools; use nohash_hasher::IntMap; use once_cell::sync::Lazy; +use smallvec::SmallVec; use re_data_store::LatestAtQuery; use re_entity_db::{EntityPath, EntityProperties, EntityPropertiesComponent, TimeInt, Timeline}; use re_log_types::StoreKind; use re_types::ComponentName; -use smallvec::SmallVec; use crate::{ - DataResultTree, SpaceViewHighlights, SpaceViewId, ViewSystemIdentifier, ViewerContext, + DataResultTree, QueryRange, SpaceViewHighlights, SpaceViewId, ViewSystemIdentifier, + ViewerContext, }; /// Path to a specific entity in a specific store used for overrides. @@ -62,6 +63,9 @@ pub struct PropertyOverrides { /// `EntityPath` in the Blueprint store where updated overrides should be written back /// for properties that apply to the individual entity only. pub individual_override_path: EntityPath, + + /// What range is queried on the data store. + pub query_range: QueryRange, } pub type SmallVisualizerSet = SmallVec<[ViewSystemIdentifier; 4]>; @@ -70,9 +74,6 @@ pub type SmallVisualizerSet = SmallVec<[ViewSystemIdentifier; 4]>; /// /// It contains everything necessary to properly use this data in the context of the /// `ViewSystem`s that it is a part of. -/// -/// In the future `accumulated_properties` will be replaced by a `StoreView` that contains -/// the relevant data overrides for the given query. #[derive(Clone, Debug, PartialEq)] pub struct DataResult { /// Where to retrieve the data from. @@ -332,14 +333,6 @@ impl DataResult { .map(|c| c.value) } - #[inline] - pub fn lookup_override_or_default( - &self, - ctx: &ViewerContext<'_>, - ) -> C { - self.lookup_override(ctx).unwrap_or_default() - } - /// Returns from which entity path an override originates from. /// /// Returns None if there was no override at all. @@ -391,9 +384,18 @@ impl DataResult { // TODO(andreas): Should the result be cached, this might be a very common operation? #[inline] pub fn is_visible(&self, ctx: &ViewerContext<'_>) -> bool { - self.lookup_override_or_default::(ctx) + self.lookup_override::(ctx) + .unwrap_or_default() .0 } + + /// Returns the query range for this data result. + pub fn query_range(&self) -> &QueryRange { + const DEFAULT_RANGE: QueryRange = QueryRange::LatestAt; + self.property_overrides + .as_ref() + .map_or(&DEFAULT_RANGE, |p| &p.query_range) + } } pub type PerSystemDataResults<'a> = BTreeMap>; diff --git a/docs/content/reference/types/views/spatial2d_view.md b/docs/content/reference/types/views/spatial2d_view.md index 065e19d4996d..c55daa1e93a8 100644 --- a/docs/content/reference/types/views/spatial2d_view.md +++ b/docs/content/reference/types/views/spatial2d_view.md @@ -15,6 +15,21 @@ Configuration for the background of a view. Controls the visual bounds of a 2D space view. * visual_bounds: The visible parts of a 2D space view, in the coordinate space of the scene. +### `VisibleTimeRange` +Configures what range of the timeline is shown on a view. + +Whenever no visual time range applies, queries are done with "latest at" semantics. +This means that the view will, starting from the time cursor position, +query the latest data available for each component type. + +The default visual time range depends on the type of view this property applies to: +- For time series views, the default is to show the entire timeline. +- For any other view, the default is to apply latest-at semantics. + +The visual time range can be overridden also individually per entity. + +* sequence: The range of time to show for timelines based on sequence numbers. +* time: The range of time to show for timelines based on time. ## Links * 🐍 [Python API docs for `Spatial2DView`](https://ref.rerun.io/docs/python/stable/common/blueprint_views#rerun.blueprint.views.Spatial2DView) diff --git a/docs/content/reference/types/views/spatial3d_view.md b/docs/content/reference/types/views/spatial3d_view.md index c883a6c85ea8..351a1e71233a 100644 --- a/docs/content/reference/types/views/spatial3d_view.md +++ b/docs/content/reference/types/views/spatial3d_view.md @@ -11,6 +11,21 @@ Configuration for the background of a view. * kind: The type of the background. Defaults to BackgroundKind.GradientDark. * color: Color used for BackgroundKind.SolidColor. +### `VisibleTimeRange` +Configures what range of the timeline is shown on a view. + +Whenever no visual time range applies, queries are done with "latest at" semantics. +This means that the view will, starting from the time cursor position, +query the latest data available for each component type. + +The default visual time range depends on the type of view this property applies to: +- For time series views, the default is to show the entire timeline. +- For any other view, the default is to apply latest-at semantics. + +The visual time range can be overridden also individually per entity. + +* sequence: The range of time to show for timelines based on sequence numbers. +* time: The range of time to show for timelines based on time. ## Links * 🐍 [Python API docs for `Spatial3DView`](https://ref.rerun.io/docs/python/stable/common/blueprint_views#rerun.blueprint.views.Spatial3DView) diff --git a/docs/content/reference/types/views/time_series_view.md b/docs/content/reference/types/views/time_series_view.md index 5570bcf9e3cc..96db0d248fc8 100644 --- a/docs/content/reference/types/views/time_series_view.md +++ b/docs/content/reference/types/views/time_series_view.md @@ -16,6 +16,21 @@ Configuration for the legend of a plot. * corner: To what corner the legend is aligned. * visible: Whether the legend is shown at all. +### `VisibleTimeRange` +Configures what range of the timeline is shown on a view. + +Whenever no visual time range applies, queries are done with "latest at" semantics. +This means that the view will, starting from the time cursor position, +query the latest data available for each component type. + +The default visual time range depends on the type of view this property applies to: +- For time series views, the default is to show the entire timeline. +- For any other view, the default is to apply latest-at semantics. + +The visual time range can be overridden also individually per entity. + +* sequence: The range of time to show for timelines based on sequence numbers. +* time: The range of time to show for timelines based on time. ## Links * 🐍 [Python API docs for `TimeSeriesView`](https://ref.rerun.io/docs/python/stable/common/blueprint_views#rerun.blueprint.views.TimeSeriesView) diff --git a/rerun_cpp/src/rerun/blueprint/archetypes.hpp b/rerun_cpp/src/rerun/blueprint/archetypes.hpp index b9d8de9e0b21..c3f26abe3185 100644 --- a/rerun_cpp/src/rerun/blueprint/archetypes.hpp +++ b/rerun_cpp/src/rerun/blueprint/archetypes.hpp @@ -10,4 +10,5 @@ #include "blueprint/archetypes/space_view_blueprint.hpp" #include "blueprint/archetypes/space_view_contents.hpp" #include "blueprint/archetypes/viewport_blueprint.hpp" +#include "blueprint/archetypes/visible_time_range.hpp" #include "blueprint/archetypes/visual_bounds.hpp" diff --git a/rerun_cpp/src/rerun/blueprint/archetypes/.gitattributes b/rerun_cpp/src/rerun/blueprint/archetypes/.gitattributes index 5b5e203daeab..a6bc694440b2 100644 --- a/rerun_cpp/src/rerun/blueprint/archetypes/.gitattributes +++ b/rerun_cpp/src/rerun/blueprint/archetypes/.gitattributes @@ -17,5 +17,7 @@ space_view_contents.cpp linguist-generated=true space_view_contents.hpp linguist-generated=true viewport_blueprint.cpp linguist-generated=true viewport_blueprint.hpp linguist-generated=true +visible_time_range.cpp linguist-generated=true +visible_time_range.hpp linguist-generated=true visual_bounds.cpp linguist-generated=true visual_bounds.hpp linguist-generated=true diff --git a/rerun_cpp/src/rerun/blueprint/archetypes/visible_time_range.cpp b/rerun_cpp/src/rerun/blueprint/archetypes/visible_time_range.cpp new file mode 100644 index 000000000000..165ba5922fe6 --- /dev/null +++ b/rerun_cpp/src/rerun/blueprint/archetypes/visible_time_range.cpp @@ -0,0 +1,38 @@ +// DO NOT EDIT! This file was auto-generated by crates/re_types_builder/src/codegen/cpp/mod.rs +// Based on "crates/re_types/definitions/rerun/blueprint/archetypes/visible_time_range.fbs". + +#include "visible_time_range.hpp" + +#include "../../collection_adapter_builtins.hpp" + +namespace rerun::blueprint::archetypes {} + +namespace rerun { + + Result> AsComponents::serialize( + const blueprint::archetypes::VisibleTimeRange& archetype + ) { + using namespace blueprint::archetypes; + std::vector cells; + cells.reserve(3); + + if (archetype.sequence.has_value()) { + auto result = DataCell::from_loggable(archetype.sequence.value()); + RR_RETURN_NOT_OK(result.error); + cells.push_back(std::move(result.value)); + } + if (archetype.time.has_value()) { + auto result = DataCell::from_loggable(archetype.time.value()); + RR_RETURN_NOT_OK(result.error); + cells.push_back(std::move(result.value)); + } + { + auto indicator = VisibleTimeRange::IndicatorComponent(); + auto result = DataCell::from_loggable(indicator); + RR_RETURN_NOT_OK(result.error); + cells.emplace_back(std::move(result.value)); + } + + return cells; + } +} // namespace rerun diff --git a/rerun_cpp/src/rerun/blueprint/archetypes/visible_time_range.hpp b/rerun_cpp/src/rerun/blueprint/archetypes/visible_time_range.hpp new file mode 100644 index 000000000000..039d98a2358f --- /dev/null +++ b/rerun_cpp/src/rerun/blueprint/archetypes/visible_time_range.hpp @@ -0,0 +1,81 @@ +// DO NOT EDIT! This file was auto-generated by crates/re_types_builder/src/codegen/cpp/mod.rs +// Based on "crates/re_types/definitions/rerun/blueprint/archetypes/visible_time_range.fbs". + +#pragma once + +#include "../../blueprint/components/visible_time_range_sequence.hpp" +#include "../../blueprint/components/visible_time_range_time.hpp" +#include "../../collection.hpp" +#include "../../compiler_utils.hpp" +#include "../../data_cell.hpp" +#include "../../indicator_component.hpp" +#include "../../result.hpp" + +#include +#include +#include +#include + +namespace rerun::blueprint::archetypes { + /// **Archetype**: Configures what range of the timeline is shown on a view. + /// + /// Whenever no visual time range applies, queries are done with "latest at" semantics. + /// This means that the view will, starting from the time cursor position, + /// query the latest data available for each component type. + /// + /// The default visual time range depends on the type of view this property applies to: + /// - For time series views, the default is to show the entire timeline. + /// - For any other view, the default is to apply latest-at semantics. + /// + /// The visual time range can be overridden also individually per entity. + struct VisibleTimeRange { + /// The range of time to show for timelines based on sequence numbers. + std::optional sequence; + + /// The range of time to show for timelines based on time. + std::optional time; + + public: + static constexpr const char IndicatorComponentName[] = + "rerun.blueprint.components.VisibleTimeRangeIndicator"; + + /// Indicator component, used to identify the archetype when converting to a list of components. + using IndicatorComponent = rerun::components::IndicatorComponent; + + public: + VisibleTimeRange() = default; + VisibleTimeRange(VisibleTimeRange&& other) = default; + + /// The range of time to show for timelines based on sequence numbers. + VisibleTimeRange with_sequence( + rerun::blueprint::components::VisibleTimeRangeSequence _sequence + ) && { + sequence = std::move(_sequence); + // See: https://github.com/rerun-io/rerun/issues/4027 + RR_WITH_MAYBE_UNINITIALIZED_DISABLED(return std::move(*this);) + } + + /// The range of time to show for timelines based on time. + VisibleTimeRange with_time(rerun::blueprint::components::VisibleTimeRangeTime _time) && { + time = std::move(_time); + // See: https://github.com/rerun-io/rerun/issues/4027 + RR_WITH_MAYBE_UNINITIALIZED_DISABLED(return std::move(*this);) + } + }; + +} // namespace rerun::blueprint::archetypes + +namespace rerun { + /// \private + template + struct AsComponents; + + /// \private + template <> + struct AsComponents { + /// Serialize all set component batches. + static Result> serialize( + const blueprint::archetypes::VisibleTimeRange& archetype + ); + }; +} // namespace rerun diff --git a/rerun_py/rerun_sdk/rerun/blueprint/__init__.py b/rerun_py/rerun_sdk/rerun/blueprint/__init__.py index 8a2e7f0e2db0..d769547b059f 100644 --- a/rerun_py/rerun_sdk/rerun/blueprint/__init__.py +++ b/rerun_py/rerun_sdk/rerun/blueprint/__init__.py @@ -16,6 +16,7 @@ Background, PlotLegend, ScalarAxis, + VisibleTimeRange, VisualBounds, ) from .components import ( @@ -24,6 +25,7 @@ LockRangeDuringZoom, ) from .containers import Grid, Horizontal, Tabs, Vertical +from .datatypes import VisibleTimeRangeBoundary, VisibleTimeRangeBoundaryKind from .views import ( BarChartView, Spatial2DView, diff --git a/rerun_py/rerun_sdk/rerun/blueprint/archetypes/.gitattributes b/rerun_py/rerun_sdk/rerun/blueprint/archetypes/.gitattributes index 4b50b7734d14..d5870692f061 100644 --- a/rerun_py/rerun_sdk/rerun/blueprint/archetypes/.gitattributes +++ b/rerun_py/rerun_sdk/rerun/blueprint/archetypes/.gitattributes @@ -10,4 +10,5 @@ scalar_axis.py linguist-generated=true space_view_blueprint.py linguist-generated=true space_view_contents.py linguist-generated=true viewport_blueprint.py linguist-generated=true +visible_time_range.py linguist-generated=true visual_bounds.py linguist-generated=true diff --git a/rerun_py/rerun_sdk/rerun/blueprint/archetypes/__init__.py b/rerun_py/rerun_sdk/rerun/blueprint/archetypes/__init__.py index 45f5321955ab..2b35827d41c8 100644 --- a/rerun_py/rerun_sdk/rerun/blueprint/archetypes/__init__.py +++ b/rerun_py/rerun_sdk/rerun/blueprint/archetypes/__init__.py @@ -10,6 +10,7 @@ from .space_view_blueprint import SpaceViewBlueprint from .space_view_contents import SpaceViewContents from .viewport_blueprint import ViewportBlueprint +from .visible_time_range import VisibleTimeRange from .visual_bounds import VisualBounds __all__ = [ @@ -21,5 +22,6 @@ "SpaceViewBlueprint", "SpaceViewContents", "ViewportBlueprint", + "VisibleTimeRange", "VisualBounds", ] diff --git a/rerun_py/rerun_sdk/rerun/blueprint/archetypes/visible_time_range.py b/rerun_py/rerun_sdk/rerun/blueprint/archetypes/visible_time_range.py new file mode 100644 index 000000000000..4a09cf0aaf36 --- /dev/null +++ b/rerun_py/rerun_sdk/rerun/blueprint/archetypes/visible_time_range.py @@ -0,0 +1,93 @@ +# DO NOT EDIT! This file was auto-generated by crates/re_types_builder/src/codegen/python/mod.rs +# Based on "crates/re_types/definitions/rerun/blueprint/archetypes/visible_time_range.fbs". + +# You can extend this class by creating a "VisibleTimeRangeExt" class in "visible_time_range_ext.py". + +from __future__ import annotations + +from typing import Any + +from attrs import define, field + +from ..._baseclasses import Archetype +from ...blueprint import components as blueprint_components +from ...blueprint import datatypes as blueprint_datatypes +from ...error_utils import catch_and_log_exceptions + +__all__ = ["VisibleTimeRange"] + + +@define(str=False, repr=False, init=False) +class VisibleTimeRange(Archetype): + """ + **Archetype**: Configures what range of the timeline is shown on a view. + + Whenever no visual time range applies, queries are done with "latest at" semantics. + This means that the view will, starting from the time cursor position, + query the latest data available for each component type. + + The default visual time range depends on the type of view this property applies to: + - For time series views, the default is to show the entire timeline. + - For any other view, the default is to apply latest-at semantics. + + The visual time range can be overridden also individually per entity. + """ + + def __init__( + self: Any, + *, + sequence: blueprint_datatypes.VisibleTimeRangeLike | None = None, + time: blueprint_datatypes.VisibleTimeRangeLike | None = None, + ): + """ + Create a new instance of the VisibleTimeRange archetype. + + Parameters + ---------- + sequence: + The range of time to show for timelines based on sequence numbers. + time: + The range of time to show for timelines based on time. + + """ + + # You can define your own __init__ function as a member of VisibleTimeRangeExt in visible_time_range_ext.py + with catch_and_log_exceptions(context=self.__class__.__name__): + self.__attrs_init__(sequence=sequence, time=time) + return + self.__attrs_clear__() + + def __attrs_clear__(self) -> None: + """Convenience method for calling `__attrs_init__` with all `None`s.""" + self.__attrs_init__( + sequence=None, # type: ignore[arg-type] + time=None, # type: ignore[arg-type] + ) + + @classmethod + def _clear(cls) -> VisibleTimeRange: + """Produce an empty VisibleTimeRange, bypassing `__init__`.""" + inst = cls.__new__(cls) + inst.__attrs_clear__() + return inst + + sequence: blueprint_components.VisibleTimeRangeSequenceBatch | None = field( + metadata={"component": "optional"}, + default=None, + converter=blueprint_components.VisibleTimeRangeSequenceBatch._optional, # type: ignore[misc] + ) + # The range of time to show for timelines based on sequence numbers. + # + # (Docstring intentionally commented out to hide this field from the docs) + + time: blueprint_components.VisibleTimeRangeTimeBatch | None = field( + metadata={"component": "optional"}, + default=None, + converter=blueprint_components.VisibleTimeRangeTimeBatch._optional, # type: ignore[misc] + ) + # The range of time to show for timelines based on time. + # + # (Docstring intentionally commented out to hide this field from the docs) + + __str__ = Archetype.__str__ + __repr__ = Archetype.__repr__ # type: ignore[assignment] diff --git a/rerun_py/rerun_sdk/rerun/blueprint/views/spatial2d_view.py b/rerun_py/rerun_sdk/rerun/blueprint/views/spatial2d_view.py index fa065d25be70..c527278b1b63 100644 --- a/rerun_py/rerun_sdk/rerun/blueprint/views/spatial2d_view.py +++ b/rerun_py/rerun_sdk/rerun/blueprint/views/spatial2d_view.py @@ -29,6 +29,7 @@ def __init__( | blueprint_components.BackgroundKindLike | None = None, visual_bounds: blueprint_archetypes.VisualBounds | None = None, + time_range: blueprint_archetypes.VisibleTimeRange | None = None, ) -> None: """ Construct a blueprint for a new Spatial2DView view. @@ -55,6 +56,8 @@ def __init__( Everything within these bounds are guaranteed to be visible. Somethings outside of these bounds may also be visible due to letterboxing. + time_range: + Configures the range on the timeline shown by this view (unless specified differently per entity). """ @@ -69,6 +72,11 @@ def __init__( visual_bounds = blueprint_archetypes.VisualBounds(visual_bounds) properties["VisualBounds"] = visual_bounds + if time_range is not None: + if not isinstance(time_range, blueprint_archetypes.VisibleTimeRange): + time_range = blueprint_archetypes.VisibleTimeRange(time_range) + properties["VisibleTimeRange"] = time_range + super().__init__( class_identifier="2D", origin=origin, contents=contents, name=name, visible=visible, properties=properties ) diff --git a/rerun_py/rerun_sdk/rerun/blueprint/views/spatial3d_view.py b/rerun_py/rerun_sdk/rerun/blueprint/views/spatial3d_view.py index 3aa3213a2c03..a0a2b6ed385c 100644 --- a/rerun_py/rerun_sdk/rerun/blueprint/views/spatial3d_view.py +++ b/rerun_py/rerun_sdk/rerun/blueprint/views/spatial3d_view.py @@ -60,6 +60,7 @@ def __init__( | datatypes.Rgba32Like | blueprint_components.BackgroundKindLike | None = None, + time_range: blueprint_archetypes.VisibleTimeRange | None = None, ) -> None: """ Construct a blueprint for a new Spatial3DView view. @@ -81,6 +82,8 @@ def __init__( Defaults to true if not specified. background: Configuration for the background of the space view. + time_range: + Configures the range on the timeline shown by this view (unless specified differently per entity). """ @@ -90,6 +93,11 @@ def __init__( background = blueprint_archetypes.Background(background) properties["Background"] = background + if time_range is not None: + if not isinstance(time_range, blueprint_archetypes.VisibleTimeRange): + time_range = blueprint_archetypes.VisibleTimeRange(time_range) + properties["VisibleTimeRange"] = time_range + super().__init__( class_identifier="3D", origin=origin, contents=contents, name=name, visible=visible, properties=properties ) diff --git a/rerun_py/rerun_sdk/rerun/blueprint/views/time_series_view.py b/rerun_py/rerun_sdk/rerun/blueprint/views/time_series_view.py index 37b42559b0a0..a741f8d67670 100644 --- a/rerun_py/rerun_sdk/rerun/blueprint/views/time_series_view.py +++ b/rerun_py/rerun_sdk/rerun/blueprint/views/time_series_view.py @@ -25,6 +25,7 @@ def __init__( visible: blueprint_components.VisibleLike | None = None, axis_y: blueprint_archetypes.ScalarAxis | None = None, plot_legend: blueprint_archetypes.PlotLegend | blueprint_components.Corner2D | None = None, + time_range: blueprint_archetypes.VisibleTimeRange | None = None, ) -> None: """ Construct a blueprint for a new TimeSeriesView view. @@ -48,6 +49,8 @@ def __init__( Configures the vertical axis of the plot. plot_legend: Configures the legend of the plot. + time_range: + Configures the time range the plot covers (unless specified differently per entity). """ @@ -62,6 +65,11 @@ def __init__( plot_legend = blueprint_archetypes.PlotLegend(plot_legend) properties["PlotLegend"] = plot_legend + if time_range is not None: + if not isinstance(time_range, blueprint_archetypes.VisibleTimeRange): + time_range = blueprint_archetypes.VisibleTimeRange(time_range) + properties["VisibleTimeRange"] = time_range + super().__init__( class_identifier="TimeSeries", origin=origin,