diff --git a/Cargo.lock b/Cargo.lock index 15cd21179f2d..fc9a5827e3ce 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4408,6 +4408,7 @@ version = "0.8.0-alpha.2" dependencies = [ "egui", "egui_extras", + "nohash-hasher", "re_arrow_store", "re_components", "re_data_store", diff --git a/crates/re_data_ui/src/item_ui.rs b/crates/re_data_ui/src/item_ui.rs index 091ef6f14c5e..b638acd79e6e 100644 --- a/crates/re_data_ui/src/item_ui.rs +++ b/crates/re_data_ui/src/item_ui.rs @@ -10,7 +10,7 @@ use re_viewer_context::{ use super::DataUi; -// TODO(andreas): This is where we want to go, but we need to figure out how get the `SpaceViewCategory` from the `SpaceViewId`. +// TODO(andreas): This is where we want to go, but we need to figure out how get the [`re_viewer_context::SpaceViewClass`] from the `SpaceViewId`. // Simply pass in optional icons? // // Show a button to an [`Item`] with a given text. diff --git a/crates/re_space_view_bar_chart/src/space_view_class.rs b/crates/re_space_view_bar_chart/src/space_view_class.rs index e944e2af883e..e8a6465c885e 100644 --- a/crates/re_space_view_bar_chart/src/space_view_class.rs +++ b/crates/re_space_view_bar_chart/src/space_view_class.rs @@ -24,7 +24,7 @@ impl SpaceViewClass for BarChartSpaceView { &re_ui::icons::SPACE_VIEW_HISTOGRAM } - fn help_text(&self, re_ui: &re_ui::ReUi, _state: &Self::State) -> egui::WidgetText { + fn help_text(&self, re_ui: &re_ui::ReUi) -> egui::WidgetText { let mut layout = re_ui::LayoutJobBuilder::new(re_ui); layout.add("Pan by dragging, or scroll (+ "); diff --git a/crates/re_space_view_bar_chart/src/view_part_system.rs b/crates/re_space_view_bar_chart/src/view_part_system.rs index 591ec109491a..2d427358e185 100644 --- a/crates/re_space_view_bar_chart/src/view_part_system.rs +++ b/crates/re_space_view_bar_chart/src/view_part_system.rs @@ -3,7 +3,7 @@ use std::collections::BTreeMap; use re_arrow_store::LatestAtQuery; use re_components::Tensor; use re_data_store::EntityPath; -use re_log_types::Component as _; +use re_log_types::{Component as _, ComponentName, TimeInt, Timeline}; use re_viewer_context::{ ArchetypeDefinition, SpaceViewSystemExecutionError, ViewContextCollection, ViewPartSystem, ViewQuery, ViewerContext, @@ -20,6 +20,26 @@ impl ViewPartSystem for BarChartViewPartSystem { vec1::vec1![Tensor::name()] } + fn queries_any_components_of( + &self, + store: &re_arrow_store::DataStore, + ent_path: &EntityPath, + components: &[ComponentName], + ) -> bool { + if !components.contains(&Tensor::name()) { + return false; + } + + if let Some(tensor) = store.query_latest_component::( + ent_path, + &LatestAtQuery::new(Timeline::log_time(), TimeInt::MAX), + ) { + tensor.is_vector() + } else { + false + } + } + fn execute( &mut self, ctx: &mut ViewerContext<'_>, diff --git a/crates/re_space_view_spatial/src/contexts/mod.rs b/crates/re_space_view_spatial/src/contexts/mod.rs index 165eccb51384..5cbdabeded51 100644 --- a/crates/re_space_view_spatial/src/contexts/mod.rs +++ b/crates/re_space_view_spatial/src/contexts/mod.rs @@ -23,16 +23,13 @@ pub struct SpatialSceneEntityContext<'a> { pub depth_offset: DepthOffset, pub annotations: std::sync::Arc, pub shared_render_builders: &'a SharedRenderBuilders, - pub counter: &'a PrimitiveCounter, pub highlight: &'a re_viewer_context::SpaceViewOutlineMasks, // Not part of the context, but convenient to have here. } -// TODO(andreas): num_3d_primitives is likely not needed in this form once 2D/3D separation is gone. num_primitives should be handled in a more general way? #[derive(Default)] pub struct PrimitiveCounter { pub num_primitives: AtomicUsize, - pub num_3d_primitives: AtomicUsize, } impl ViewContextSystem for PrimitiveCounter { diff --git a/crates/re_space_view_spatial/src/heuristics.rs b/crates/re_space_view_spatial/src/heuristics.rs new file mode 100644 index 000000000000..acc820e9b49d --- /dev/null +++ b/crates/re_space_view_spatial/src/heuristics.rs @@ -0,0 +1,47 @@ +use nohash_hasher::IntSet; +use re_log_types::{EntityPath, Timeline}; +use re_viewer_context::{AutoSpawnHeuristic, SpaceViewClassName, ViewerContext}; + +use crate::{parts::SpatialViewPartData, view_kind::SpatialSpaceViewKind}; + +pub fn auto_spawn_heuristic( + class: &SpaceViewClassName, + ctx: &ViewerContext<'_>, + ent_paths: &IntSet, + view_kind: SpatialSpaceViewKind, +) -> AutoSpawnHeuristic { + re_tracing::profile_function!(); + + let store = ctx.store_db.store(); + let timeline = Timeline::log_time(); + + let mut score = 0.0; + + let parts = ctx + .space_view_class_registry + .get_system_registry_or_log_error(class) + .new_part_collection(); + let parts_with_view_kind = parts + .iter() + .filter(|part| { + part.data() + .and_then(|d| d.downcast_ref::()) + .map_or(false, |data| data.preferred_view_kind == Some(view_kind)) + }) + .collect::>(); + + for ent_path in ent_paths { + let Some(components) = store.all_components(&timeline, ent_path) else { + continue; + }; + + for part in &parts_with_view_kind { + if part.queries_any_components_of(store, ent_path, &components) { + score += 1.0; + break; + } + } + } + + AutoSpawnHeuristic::SpawnClassWithHighestScoreForRoot(score) +} diff --git a/crates/re_space_view_spatial/src/lib.rs b/crates/re_space_view_spatial/src/lib.rs index 972641ec4476..834ab289f9d5 100644 --- a/crates/re_space_view_spatial/src/lib.rs +++ b/crates/re_space_view_spatial/src/lib.rs @@ -4,15 +4,28 @@ mod contexts; mod eye; +mod heuristics; mod instance_hash_conversions; mod mesh_cache; mod mesh_loader; mod parts; mod picking; mod space_camera_3d; -mod space_view_class; +mod space_view_2d; +mod space_view_3d; mod ui; mod ui_2d; mod ui_3d; -pub use space_view_class::SpatialSpaceView; +pub use space_view_2d::SpatialSpaceView2D; +pub use space_view_3d::SpatialSpaceView3D; + +// --- + +mod view_kind { + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + pub enum SpatialSpaceViewKind { + TwoD, + ThreeD, + } +} diff --git a/crates/re_space_view_spatial/src/parts/arrows3d.rs b/crates/re_space_view_spatial/src/parts/arrows3d.rs index 589a9820f175..bf302dfda33d 100644 --- a/crates/re_space_view_spatial/src/parts/arrows3d.rs +++ b/crates/re_space_view_spatial/src/parts/arrows3d.rs @@ -11,11 +11,17 @@ use super::{picking_id_from_instance_key, SpatialViewPartData}; use crate::{ contexts::{EntityDepthOffsets, SpatialSceneEntityContext}, parts::entity_iterator::process_entity_views, + view_kind::SpatialSpaceViewKind, }; -#[derive(Default)] pub struct Arrows3DPart(SpatialViewPartData); +impl Default for Arrows3DPart { + fn default() -> Self { + Self(SpatialViewPartData::new(Some(SpatialSpaceViewKind::ThreeD))) + } +} + impl Arrows3DPart { fn process_entity_view( &mut self, @@ -113,10 +119,6 @@ impl ViewPartSystem for Arrows3DPart { view_ctx.get::()?.points, self.archetype(), |_ctx, ent_path, entity_view, ent_context| { - ent_context - .counter - .num_3d_primitives - .fetch_add(1, std::sync::atomic::Ordering::Relaxed); self.process_entity_view(query, &entity_view, ent_path, ent_context) }, )?; diff --git a/crates/re_space_view_spatial/src/parts/boxes2d.rs b/crates/re_space_view_spatial/src/parts/boxes2d.rs index 814a58ea2097..40462c63371a 100644 --- a/crates/re_space_view_spatial/src/parts/boxes2d.rs +++ b/crates/re_space_view_spatial/src/parts/boxes2d.rs @@ -10,13 +10,19 @@ use re_viewer_context::{ use crate::{ contexts::{EntityDepthOffsets, SpatialSceneEntityContext}, parts::{entity_iterator::process_entity_views, UiLabel, UiLabelTarget}, + view_kind::SpatialSpaceViewKind, }; use super::{picking_id_from_instance_key, SpatialViewPartData}; -#[derive(Default)] pub struct Boxes2DPart(SpatialViewPartData); +impl Default for Boxes2DPart { + fn default() -> Self { + Self(SpatialViewPartData::new(Some(SpatialSpaceViewKind::TwoD))) + } +} + impl Boxes2DPart { fn process_entity_view( &mut self, diff --git a/crates/re_space_view_spatial/src/parts/boxes3d.rs b/crates/re_space_view_spatial/src/parts/boxes3d.rs index fe22545415a8..ac7026eede22 100644 --- a/crates/re_space_view_spatial/src/parts/boxes3d.rs +++ b/crates/re_space_view_spatial/src/parts/boxes3d.rs @@ -12,13 +12,19 @@ use re_viewer_context::{ use crate::{ contexts::SpatialSceneEntityContext, parts::{entity_iterator::process_entity_views, UiLabel, UiLabelTarget}, + view_kind::SpatialSpaceViewKind, }; use super::{picking_id_from_instance_key, SpatialViewPartData}; -#[derive(Default)] pub struct Boxes3DPart(SpatialViewPartData); +impl Default for Boxes3DPart { + fn default() -> Self { + Self(SpatialViewPartData::new(Some(SpatialSpaceViewKind::ThreeD))) + } +} + impl Boxes3DPart { fn process_entity_view( &mut self, @@ -124,10 +130,6 @@ impl ViewPartSystem for Boxes3DPart { 0, self.archetype(), |_ctx, ent_path, entity_view, ent_context| { - ent_context - .counter - .num_3d_primitives - .fetch_add(1, std::sync::atomic::Ordering::Relaxed); self.process_entity_view(query, &entity_view, ent_path, ent_context) }, )?; diff --git a/crates/re_space_view_spatial/src/parts/cameras.rs b/crates/re_space_view_spatial/src/parts/cameras.rs index 165d3cbfc4e9..b7c67ecdfbed 100644 --- a/crates/re_space_view_spatial/src/parts/cameras.rs +++ b/crates/re_space_view_spatial/src/parts/cameras.rs @@ -9,28 +9,35 @@ use re_viewer_context::{ use super::SpatialViewPartData; use crate::{ - contexts::{ - pinhole_camera_view_coordinates, PrimitiveCounter, SharedRenderBuilders, TransformContext, - }, + contexts::{pinhole_camera_view_coordinates, SharedRenderBuilders, TransformContext}, instance_hash_conversions::picking_layer_id_from_instance_path_hash, space_camera_3d::SpaceCamera3D, }; const CAMERA_COLOR: re_renderer::Color32 = re_renderer::Color32::from_rgb(150, 150, 150); -#[derive(Default)] pub struct CamerasPart { pub data: SpatialViewPartData, pub space_cameras: Vec, } +impl Default for CamerasPart { + fn default() -> Self { + Self { + // Cameras themselves aren't inherently 2D or 3D since they represent intrinsics. + // (extrinsics, represented by [`transform3d_arrow::Transform3DArrowsPart`] are 3D though) + data: (SpatialViewPartData::new(None)), + space_cameras: Vec::new(), + } + } +} + impl CamerasPart { #[allow(clippy::too_many_arguments)] fn visit_instance( &mut self, transforms: &TransformContext, shared_render_builders: &SharedRenderBuilders, - primitive_counter: &PrimitiveCounter, ent_path: &EntityPath, props: &EntityProperties, pinhole: Pinhole, @@ -152,10 +159,6 @@ impl CamerasPart { if let Some(outline_mask_ids) = entity_highlight.instances.get(&instance_key) { lines.outline_mask_ids(*outline_mask_ids); } - - primitive_counter - .num_3d_primitives - .fetch_add(1, std::sync::atomic::Ordering::Relaxed); } } @@ -174,7 +177,6 @@ impl ViewPartSystem for CamerasPart { let transforms = view_ctx.get::()?; let shared_render_builders = view_ctx.get::()?; - let primitive_counter = view_ctx.get::()?; let store = ctx.store_db.store(); for (ent_path, props) in query.iter_entities() { @@ -191,7 +193,6 @@ impl ViewPartSystem for CamerasPart { self.visit_instance( transforms, shared_render_builders, - primitive_counter, ent_path, &props, pinhole, diff --git a/crates/re_space_view_spatial/src/parts/entity_iterator.rs b/crates/re_space_view_spatial/src/parts/entity_iterator.rs index 5d29b0fbc30a..e48ede22e42a 100644 --- a/crates/re_space_view_spatial/src/parts/entity_iterator.rs +++ b/crates/re_space_view_spatial/src/parts/entity_iterator.rs @@ -62,7 +62,6 @@ where annotations: annotations.0.find(ent_path), shared_render_builders, highlight: query.highlights.entity_outline_mask(ent_path.hash()), - counter, }; match query_primary_with_history::( diff --git a/crates/re_space_view_spatial/src/parts/images.rs b/crates/re_space_view_spatial/src/parts/images.rs index cd53b0c8ad35..061b47e5e9c6 100644 --- a/crates/re_space_view_spatial/src/parts/images.rs +++ b/crates/re_space_view_spatial/src/parts/images.rs @@ -4,12 +4,13 @@ use egui::NumExt; use itertools::Itertools as _; use nohash_hasher::IntSet; +use re_arrow_store::LatestAtQuery; use re_components::{ ColorRGBA, Component as _, DecodedTensor, DrawOrder, InstanceKey, Pinhole, Tensor, TensorDataMeaning, }; use re_data_store::{EntityPath, EntityProperties}; -use re_log_types::EntityPathHash; +use re_log_types::{ComponentName, EntityPathHash, TimeInt, Timeline}; use re_query::{EntityView, QueryError}; use re_renderer::{ renderer::{DepthCloud, DepthClouds, RectangleOptions, TexturedRect}, @@ -24,6 +25,7 @@ use re_viewer_context::{ use crate::{ contexts::{EntityDepthOffsets, SpatialSceneEntityContext, TransformContext}, parts::{entity_iterator::process_entity_views, SIZE_BOOST_IN_POINTS_FOR_POINT_OUTLINES}, + view_kind::SpatialSpaceViewKind, }; use super::SpatialViewPartData; @@ -117,13 +119,22 @@ struct ImageGrouping { draw_order: DrawOrder, } -#[derive(Default)] pub struct ImagesPart { pub data: SpatialViewPartData, pub images: Vec, pub depth_cloud_entities: IntSet, } +impl Default for ImagesPart { + fn default() -> Self { + Self { + data: SpatialViewPartData::new(Some(SpatialSpaceViewKind::TwoD)), + images: Vec::new(), + depth_cloud_entities: IntSet::default(), + } + } +} + impl ImagesPart { fn handle_image_layering(&mut self) { re_tracing::profile_function!(); @@ -378,6 +389,22 @@ impl ViewPartSystem for ImagesPart { ] } + fn queries_any_components_of( + &self, + store: &re_arrow_store::DataStore, + ent_path: &EntityPath, + _components: &[ComponentName], + ) -> bool { + if let Some(tensor) = store.query_latest_component::( + ent_path, + &LatestAtQuery::new(Timeline::log_time(), TimeInt::MAX), + ) { + tensor.is_shaped_like_an_image() && !tensor.is_vector() + } else { + false + } + } + fn execute( &mut self, ctx: &mut ViewerContext<'_>, diff --git a/crates/re_space_view_spatial/src/parts/lines2d.rs b/crates/re_space_view_spatial/src/parts/lines2d.rs index 114ef1dd76b0..1779fe991e89 100644 --- a/crates/re_space_view_spatial/src/parts/lines2d.rs +++ b/crates/re_space_view_spatial/src/parts/lines2d.rs @@ -10,13 +10,19 @@ use re_viewer_context::{ use crate::{ contexts::{EntityDepthOffsets, SpatialSceneEntityContext}, parts::entity_iterator::process_entity_views, + view_kind::SpatialSpaceViewKind, }; use super::{picking_id_from_instance_key, SpatialViewPartData}; -#[derive(Default)] pub struct Lines2DPart(SpatialViewPartData); +impl Default for Lines2DPart { + fn default() -> Self { + Self(SpatialViewPartData::new(Some(SpatialSpaceViewKind::TwoD))) + } +} + impl Lines2DPart { fn process_entity_view( &mut self, diff --git a/crates/re_space_view_spatial/src/parts/lines3d.rs b/crates/re_space_view_spatial/src/parts/lines3d.rs index 63d95a4e4f8e..32cf58d995c9 100644 --- a/crates/re_space_view_spatial/src/parts/lines3d.rs +++ b/crates/re_space_view_spatial/src/parts/lines3d.rs @@ -7,13 +7,20 @@ use re_viewer_context::{ ViewPartSystem, ViewQuery, ViewerContext, }; -use crate::{contexts::SpatialSceneEntityContext, parts::entity_iterator::process_entity_views}; - use super::{picking_id_from_instance_key, SpatialViewPartData}; +use crate::{ + contexts::SpatialSceneEntityContext, parts::entity_iterator::process_entity_views, + view_kind::SpatialSpaceViewKind, +}; -#[derive(Default)] pub struct Lines3DPart(SpatialViewPartData); +impl Default for Lines3DPart { + fn default() -> Self { + Self(SpatialViewPartData::new(Some(SpatialSpaceViewKind::ThreeD))) + } +} + impl Lines3DPart { fn process_entity_view( &mut self, @@ -97,10 +104,6 @@ impl ViewPartSystem for Lines3DPart { 0, self.archetype(), |_ctx, ent_path, entity_view, ent_context| { - ent_context - .counter - .num_3d_primitives - .fetch_add(1, std::sync::atomic::Ordering::Relaxed); self.process_entity_view(query, &entity_view, ent_path, ent_context) }, )?; diff --git a/crates/re_space_view_spatial/src/parts/meshes.rs b/crates/re_space_view_spatial/src/parts/meshes.rs index e5f0a4446fbb..2e82d0834ecd 100644 --- a/crates/re_space_view_spatial/src/parts/meshes.rs +++ b/crates/re_space_view_spatial/src/parts/meshes.rs @@ -11,12 +11,17 @@ use super::SpatialViewPartData; use crate::{ contexts::SpatialSceneEntityContext, instance_hash_conversions::picking_layer_id_from_instance_path_hash, mesh_cache::MeshCache, - parts::entity_iterator::process_entity_views, + parts::entity_iterator::process_entity_views, view_kind::SpatialSpaceViewKind, }; -#[derive(Default)] pub struct MeshPart(SpatialViewPartData); +impl Default for MeshPart { + fn default() -> Self { + Self(SpatialViewPartData::new(Some(SpatialSpaceViewKind::ThreeD))) + } +} + impl MeshPart { fn process_entity_view( &mut self, @@ -85,10 +90,6 @@ impl ViewPartSystem for MeshPart { 0, self.archetype(), |ctx, ent_path, entity_view, ent_context| { - ent_context - .counter - .num_3d_primitives - .fetch_add(1, std::sync::atomic::Ordering::Relaxed); self.process_entity_view(ctx, &mut instances, &entity_view, ent_path, ent_context) }, )?; diff --git a/crates/re_space_view_spatial/src/parts/mod.rs b/crates/re_space_view_spatial/src/parts/mod.rs index 2b5f942c24ad..f66810e84605 100644 --- a/crates/re_space_view_spatial/src/parts/mod.rs +++ b/crates/re_space_view_spatial/src/parts/mod.rs @@ -28,11 +28,10 @@ use re_data_store::{EntityPath, InstancePathHash}; use re_viewer_context::SpaceViewClassRegistryError; use re_viewer_context::{ auto_color, Annotations, DefaultColor, ResolvedAnnotationInfo, SpaceViewSystemRegistry, - ViewContextCollection, ViewPartCollection, ViewQuery, + ViewPartCollection, ViewQuery, }; use super::contexts::SpatialSceneEntityContext; -use crate::{contexts::PrimitiveCounter, ui::SpatialNavigationMode}; /// Collection of keypoints for annotation context. pub type Keypoints = HashMap<(ClassId, i64), HashMap>; @@ -57,7 +56,10 @@ pub fn register_parts( Ok(()) } -pub fn calculate_bounding_box(parts: &ViewPartCollection) -> macaw::BoundingBox { +pub fn calculate_bounding_box( + parts: &ViewPartCollection, + bounding_box_accum: &mut macaw::BoundingBox, +) -> macaw::BoundingBox { let mut bounding_box = macaw::BoundingBox::nothing(); for part in parts.iter() { if let Some(data) = part @@ -67,6 +69,13 @@ pub fn calculate_bounding_box(parts: &ViewPartCollection) -> macaw::BoundingBox bounding_box = bounding_box.union(data.bounding_box); } } + + if bounding_box_accum.is_nothing() || !bounding_box_accum.size().is_finite() { + *bounding_box_accum = bounding_box; + } else { + *bounding_box_accum = bounding_box_accum.union(bounding_box); + } + bounding_box } @@ -213,49 +222,6 @@ pub struct UiLabel { pub labeled_instance: InstancePathHash, } -/// Heuristic whether the default way of looking at this scene should be 2d or 3d. -pub fn preferred_navigation_mode( - context: &ViewContextCollection, - parts: &ViewPartCollection, - space_info_path: &EntityPath, -) -> SpatialNavigationMode { - // If there's any space cameras that are not the root, we need to go 3D, otherwise we can't display them. - if parts - .get::() - .map(|cameras| { - cameras - .space_cameras - .iter() - .any(|camera| &camera.ent_path != space_info_path) - }) - .unwrap_or(false) - { - return SpatialNavigationMode::ThreeD; - } - - if parts - .get::() - .map(|images| !images.images.is_empty()) - .unwrap_or(false) - { - return SpatialNavigationMode::TwoD; - } - - if context - .get::() - .map(|c| { - c.num_3d_primitives - .load(std::sync::atomic::Ordering::Relaxed) - }) - .unwrap_or(0) - == 0 - { - return SpatialNavigationMode::TwoD; - } - - SpatialNavigationMode::ThreeD -} - pub fn load_keypoint_connections( ent_context: &SpatialSceneEntityContext<'_>, ent_path: &re_data_store::EntityPath, diff --git a/crates/re_space_view_spatial/src/parts/points2d.rs b/crates/re_space_view_spatial/src/parts/points2d.rs index 8fa1fad85e4b..e0404fd85e24 100644 --- a/crates/re_space_view_spatial/src/parts/points2d.rs +++ b/crates/re_space_view_spatial/src/parts/points2d.rs @@ -13,6 +13,7 @@ use crate::{ parts::{ entity_iterator::process_entity_views, load_keypoint_connections, UiLabel, UiLabelTarget, }, + view_kind::SpatialSpaceViewKind, }; use super::{ @@ -30,7 +31,7 @@ impl Default for Points2DPart { fn default() -> Self { Self { max_labels: 10, - data: Default::default(), + data: SpatialViewPartData::new(Some(SpatialSpaceViewKind::TwoD)), } } } diff --git a/crates/re_space_view_spatial/src/parts/points3d.rs b/crates/re_space_view_spatial/src/parts/points3d.rs index 8395fa3de86d..813afc7842d8 100644 --- a/crates/re_space_view_spatial/src/parts/points3d.rs +++ b/crates/re_space_view_spatial/src/parts/points3d.rs @@ -13,6 +13,7 @@ use crate::{ parts::{ entity_iterator::process_entity_views, load_keypoint_connections, UiLabel, UiLabelTarget, }, + view_kind::SpatialSpaceViewKind, }; use super::{ @@ -30,7 +31,7 @@ impl Default for Points3DPart { fn default() -> Self { Self { max_labels: 10, - data: Default::default(), + data: SpatialViewPartData::new(Some(SpatialSpaceViewKind::ThreeD)), } } } @@ -191,10 +192,6 @@ impl ViewPartSystem for Points3DPart { 0, self.archetype(), |_ctx, ent_path, entity_view, ent_context| { - ent_context - .counter - .num_3d_primitives - .fetch_add(1, std::sync::atomic::Ordering::Relaxed); self.process_entity_view(query, &entity_view, ent_path, ent_context) }, )?; diff --git a/crates/re_space_view_spatial/src/parts/spatial_view_part.rs b/crates/re_space_view_spatial/src/parts/spatial_view_part.rs index 9ce9795bc995..491610841530 100644 --- a/crates/re_space_view_spatial/src/parts/spatial_view_part.rs +++ b/crates/re_space_view_spatial/src/parts/spatial_view_part.rs @@ -1,4 +1,4 @@ -use crate::parts::UiLabel; +use crate::{parts::UiLabel, view_kind::SpatialSpaceViewKind}; /// Common data struct for all spatial scene elements. /// @@ -6,9 +6,18 @@ use crate::parts::UiLabel; pub struct SpatialViewPartData { pub ui_labels: Vec, pub bounding_box: macaw::BoundingBox, + pub preferred_view_kind: Option, } impl SpatialViewPartData { + pub fn new(preferred_view_kind: Option) -> Self { + Self { + ui_labels: Vec::new(), + bounding_box: macaw::BoundingBox::nothing(), + preferred_view_kind, + } + } + pub fn extend_bounding_box( &mut self, other: macaw::BoundingBox, @@ -32,12 +41,3 @@ impl SpatialViewPartData { self } } - -impl Default for SpatialViewPartData { - fn default() -> Self { - Self { - ui_labels: Vec::new(), - bounding_box: macaw::BoundingBox::nothing(), - } - } -} diff --git a/crates/re_space_view_spatial/src/parts/transform3d_arrows.rs b/crates/re_space_view_spatial/src/parts/transform3d_arrows.rs index ba3eba3a9b66..d3c20659a6fa 100644 --- a/crates/re_space_view_spatial/src/parts/transform3d_arrows.rs +++ b/crates/re_space_view_spatial/src/parts/transform3d_arrows.rs @@ -7,13 +7,21 @@ use re_viewer_context::{ ViewQuery, ViewerContext, }; -use crate::contexts::{PrimitiveCounter, SharedRenderBuilders, TransformContext}; +use crate::{ + contexts::{SharedRenderBuilders, TransformContext}, + view_kind::SpatialSpaceViewKind, +}; use super::SpatialViewPartData; -#[derive(Default)] pub struct Transform3DArrowsPart(SpatialViewPartData); +impl Default for Transform3DArrowsPart { + fn default() -> Self { + Self(SpatialViewPartData::new(Some(SpatialSpaceViewKind::ThreeD))) + } +} + impl ViewPartSystem for Transform3DArrowsPart { fn archetype(&self) -> ArchetypeDefinition { vec1::vec1![Transform3D::name()] @@ -29,7 +37,6 @@ impl ViewPartSystem for Transform3DArrowsPart { let mut line_builder = view_ctx.get::()?.lines(); let transforms = view_ctx.get::()?; - let counter = view_ctx.get::()?; let store = &ctx.store_db.entity_db.data_store; let latest_at_query = re_arrow_store::LatestAtQuery::new(query.timeline, query.latest_at); @@ -41,10 +48,6 @@ impl ViewPartSystem for Transform3DArrowsPart { continue; } - counter - .num_3d_primitives - .fetch_add(1, std::sync::atomic::Ordering::Relaxed); - if !props.transform_3d_visible.get() { continue; } diff --git a/crates/re_space_view_spatial/src/space_view_2d.rs b/crates/re_space_view_spatial/src/space_view_2d.rs new file mode 100644 index 000000000000..a97947007f6b --- /dev/null +++ b/crates/re_space_view_spatial/src/space_view_2d.rs @@ -0,0 +1,112 @@ +use nohash_hasher::IntSet; +use re_log_types::EntityPath; +use re_viewer_context::{ + AutoSpawnHeuristic, SpaceViewClass, SpaceViewClassRegistryError, SpaceViewId, + SpaceViewSystemExecutionError, ViewContextCollection, ViewPartCollection, ViewQuery, + ViewerContext, +}; + +use crate::{ + contexts::{register_contexts, PrimitiveCounter}, + heuristics::auto_spawn_heuristic, + parts::{calculate_bounding_box, register_parts}, + ui::SpatialSpaceViewState, + view_kind::SpatialSpaceViewKind, +}; + +#[derive(Default)] +pub struct SpatialSpaceView2D; + +impl SpaceViewClass for SpatialSpaceView2D { + type State = SpatialSpaceViewState; + + fn name(&self) -> re_viewer_context::SpaceViewClassName { + "2D".into() + } + + fn icon(&self) -> &'static re_ui::Icon { + &re_ui::icons::SPACE_VIEW_2D + } + + fn help_text(&self, re_ui: &re_ui::ReUi) -> egui::WidgetText { + super::ui_2d::help_text(re_ui) + } + + fn on_register( + &self, + system_registry: &mut re_viewer_context::SpaceViewSystemRegistry, + ) -> Result<(), SpaceViewClassRegistryError> { + register_contexts(system_registry)?; + register_parts(system_registry)?; + Ok(()) + } + + fn preferred_tile_aspect_ratio(&self, state: &Self::State) -> Option { + let size = state.scene_bbox_accum.size(); + Some(size.x / size.y) + } + + fn layout_priority(&self) -> re_viewer_context::SpaceViewClassLayoutPriority { + re_viewer_context::SpaceViewClassLayoutPriority::High + } + + fn prepare_ui( + &self, + ctx: &mut ViewerContext<'_>, + state: &Self::State, + ent_paths: &IntSet, + entity_properties: &mut re_data_store::EntityPropertyMap, + ) { + state.update_object_property_heuristics( + ctx, + ent_paths, + entity_properties, + SpatialSpaceViewKind::TwoD, + ); + } + + fn auto_spawn_heuristic( + &self, + ctx: &ViewerContext<'_>, + _space_origin: &EntityPath, + ent_paths: &IntSet, + ) -> AutoSpawnHeuristic { + auto_spawn_heuristic(&self.name(), ctx, ent_paths, SpatialSpaceViewKind::TwoD) + } + + fn selection_ui( + &self, + ctx: &mut re_viewer_context::ViewerContext<'_>, + ui: &mut egui::Ui, + state: &mut Self::State, + space_origin: &EntityPath, + space_view_id: SpaceViewId, + ) { + state.selection_ui( + ctx, + ui, + space_origin, + space_view_id, + SpatialSpaceViewKind::TwoD, + ); + } + + fn ui( + &self, + ctx: &mut ViewerContext<'_>, + ui: &mut egui::Ui, + state: &mut Self::State, + view_ctx: &ViewContextCollection, + parts: &ViewPartCollection, + query: &ViewQuery<'_>, + draw_data: Vec, + ) -> Result<(), SpaceViewSystemExecutionError> { + state.scene_bbox = calculate_bounding_box(parts, &mut state.scene_bbox_accum); + state.scene_num_primitives = view_ctx + .get::()? + .num_primitives + .load(std::sync::atomic::Ordering::Relaxed); + + crate::ui_2d::view_2d(ctx, ui, state, view_ctx, parts, query, draw_data) + } +} diff --git a/crates/re_space_view_spatial/src/space_view_3d.rs b/crates/re_space_view_spatial/src/space_view_3d.rs new file mode 100644 index 000000000000..17110b08bb01 --- /dev/null +++ b/crates/re_space_view_spatial/src/space_view_3d.rs @@ -0,0 +1,139 @@ +use nohash_hasher::IntSet; +use re_arrow_store::LatestAtQuery; +use re_components::Pinhole; +use re_log_types::{EntityPath, Timeline}; +use re_viewer_context::{ + AutoSpawnHeuristic, SpaceViewClass, SpaceViewClassRegistryError, SpaceViewId, + SpaceViewSystemExecutionError, ViewContextCollection, ViewPartCollection, ViewQuery, + ViewerContext, +}; + +use crate::{ + contexts::{register_contexts, PrimitiveCounter}, + heuristics::auto_spawn_heuristic, + parts::{calculate_bounding_box, register_parts}, + ui::SpatialSpaceViewState, + view_kind::SpatialSpaceViewKind, +}; + +#[derive(Default)] +pub struct SpatialSpaceView3D; + +impl SpaceViewClass for SpatialSpaceView3D { + type State = SpatialSpaceViewState; + + fn name(&self) -> re_viewer_context::SpaceViewClassName { + "3D".into() + } + + fn icon(&self) -> &'static re_ui::Icon { + &re_ui::icons::SPACE_VIEW_3D + } + + fn help_text(&self, re_ui: &re_ui::ReUi) -> egui::WidgetText { + super::ui_3d::help_text(re_ui) + } + + fn on_register( + &self, + system_registry: &mut re_viewer_context::SpaceViewSystemRegistry, + ) -> Result<(), SpaceViewClassRegistryError> { + register_contexts(system_registry)?; + register_parts(system_registry)?; + Ok(()) + } + + fn preferred_tile_aspect_ratio(&self, _state: &Self::State) -> Option { + None + } + + fn layout_priority(&self) -> re_viewer_context::SpaceViewClassLayoutPriority { + re_viewer_context::SpaceViewClassLayoutPriority::High + } + + fn auto_spawn_heuristic( + &self, + ctx: &ViewerContext<'_>, + space_origin: &EntityPath, + ent_paths: &IntSet, + ) -> AutoSpawnHeuristic { + let score = + auto_spawn_heuristic(&self.name(), ctx, ent_paths, SpatialSpaceViewKind::ThreeD); + + if let AutoSpawnHeuristic::SpawnClassWithHighestScoreForRoot(mut score) = score { + // If there's a camera at a non-root path, make 3D view higher priority. + for ent_path in ent_paths { + if ent_path == space_origin { + continue; + } + + if ctx + .store_db + .store() + .query_latest_component::( + ent_path, + &LatestAtQuery::latest(Timeline::log_time()), + ) + .is_some() + { + score += 100.0; + } + } + + AutoSpawnHeuristic::SpawnClassWithHighestScoreForRoot(score) + } else { + score + } + } + + fn prepare_ui( + &self, + ctx: &mut ViewerContext<'_>, + state: &Self::State, + ent_paths: &IntSet, + entity_properties: &mut re_data_store::EntityPropertyMap, + ) { + state.update_object_property_heuristics( + ctx, + ent_paths, + entity_properties, + SpatialSpaceViewKind::ThreeD, + ); + } + + fn selection_ui( + &self, + ctx: &mut re_viewer_context::ViewerContext<'_>, + ui: &mut egui::Ui, + state: &mut Self::State, + space_origin: &EntityPath, + space_view_id: SpaceViewId, + ) { + state.selection_ui( + ctx, + ui, + space_origin, + space_view_id, + SpatialSpaceViewKind::ThreeD, + ); + } + + fn ui( + &self, + ctx: &mut ViewerContext<'_>, + ui: &mut egui::Ui, + state: &mut Self::State, + view_ctx: &ViewContextCollection, + parts: &ViewPartCollection, + query: &ViewQuery<'_>, + draw_data: Vec, + ) -> Result<(), SpaceViewSystemExecutionError> { + state.scene_bbox = calculate_bounding_box(parts, &mut state.scene_bbox_accum); + state.scene_num_primitives = view_ctx + .get::()? + .num_primitives + .load(std::sync::atomic::Ordering::Relaxed); + + crate::ui_3d::view_3d(ctx, ui, state, view_ctx, parts, query, draw_data) + } +} diff --git a/crates/re_space_view_spatial/src/space_view_class.rs b/crates/re_space_view_spatial/src/space_view_class.rs deleted file mode 100644 index b4f1ca749cd8..000000000000 --- a/crates/re_space_view_spatial/src/space_view_class.rs +++ /dev/null @@ -1,88 +0,0 @@ -use nohash_hasher::IntSet; -use re_log_types::EntityPath; -use re_viewer_context::{ - SpaceViewClass, SpaceViewClassRegistryError, SpaceViewId, SpaceViewSystemExecutionError, - ViewContextCollection, ViewPartCollection, ViewQuery, ViewerContext, -}; - -use crate::{ - contexts::register_contexts, - parts::register_parts, - ui::{SpatialNavigationMode, SpatialSpaceViewState}, -}; - -#[derive(Default)] -pub struct SpatialSpaceView; - -impl SpaceViewClass for SpatialSpaceView { - type State = SpatialSpaceViewState; - - fn name(&self) -> re_viewer_context::SpaceViewClassName { - "Spatial".into() - } - - fn icon(&self) -> &'static re_ui::Icon { - &re_ui::icons::SPACE_VIEW_3D - } - - fn help_text(&self, re_ui: &re_ui::ReUi, state: &Self::State) -> egui::WidgetText { - state.help_text(re_ui) - } - - fn on_register( - &self, - system_registry: &mut re_viewer_context::SpaceViewSystemRegistry, - ) -> Result<(), SpaceViewClassRegistryError> { - register_contexts(system_registry)?; - register_parts(system_registry)?; - Ok(()) - } - - fn preferred_tile_aspect_ratio(&self, state: &Self::State) -> Option { - match state.nav_mode.get() { - SpatialNavigationMode::TwoD => { - let size = state.scene_bbox_accum.size(); - Some(size.x / size.y) - } - SpatialNavigationMode::ThreeD => None, - } - } - - fn layout_priority(&self) -> re_viewer_context::SpaceViewClassLayoutPriority { - re_viewer_context::SpaceViewClassLayoutPriority::High - } - - fn prepare_ui( - &self, - ctx: &mut ViewerContext<'_>, - state: &Self::State, - entity_paths: &IntSet, - entity_properties: &mut re_data_store::EntityPropertyMap, - ) { - state.update_object_property_heuristics(ctx, entity_paths, entity_properties); - } - - fn selection_ui( - &self, - ctx: &mut re_viewer_context::ViewerContext<'_>, - ui: &mut egui::Ui, - state: &mut Self::State, - space_origin: &EntityPath, - space_view_id: SpaceViewId, - ) { - state.selection_ui(ctx, ui, space_origin, space_view_id); - } - - fn ui( - &self, - ctx: &mut ViewerContext<'_>, - ui: &mut egui::Ui, - state: &mut Self::State, - view_ctx: &ViewContextCollection, - parts: &ViewPartCollection, - query: &ViewQuery<'_>, - draw_data: Vec, - ) -> Result<(), SpaceViewSystemExecutionError> { - state.view_spatial(ctx, ui, view_ctx, parts, query, draw_data) - } -} diff --git a/crates/re_space_view_spatial/src/ui.rs b/crates/re_space_view_spatial/src/ui.rs index aae484f4b421..c21ae0c3b15c 100644 --- a/crates/re_space_view_spatial/src/ui.rs +++ b/crates/re_space_view_spatial/src/ui.rs @@ -18,14 +18,11 @@ use re_viewer_context::{ }; use super::{eye::Eye, ui_2d::View2DState, ui_3d::View3DState}; -use crate::contexts::{AnnotationSceneContext, NonInteractiveEntities, PrimitiveCounter}; -use crate::parts::{calculate_bounding_box, CamerasPart, ImagesPart}; - use crate::{ - parts::{preferred_navigation_mode, UiLabel, UiLabelTarget}, + contexts::{AnnotationSceneContext, NonInteractiveEntities}, + parts::{CamerasPart, ImagesPart, UiLabel, UiLabelTarget}, picking::{PickableUiRect, PickingContext, PickingHitType, PickingResult}, - ui_2d::view_2d, - ui_3d::view_3d, + view_kind::SpatialSpaceViewKind, }; /// Default auto point radius in UI points. @@ -34,23 +31,6 @@ const AUTO_POINT_RADIUS: f32 = 1.5; /// Default auto line radius in UI points. const AUTO_LINE_RADIUS: f32 = 1.5; -/// Describes how the scene is navigated, determining if it is a 2D or 3D experience. -#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, serde::Deserialize, serde::Serialize)] -pub enum SpatialNavigationMode { - #[default] - TwoD, - ThreeD, -} - -impl From for WidgetText { - fn from(val: SpatialNavigationMode) -> Self { - match val { - SpatialNavigationMode::TwoD => "2D Pan & Zoom".into(), - SpatialNavigationMode::ThreeD => "3D Camera".into(), - } - } -} - #[derive(Clone, Copy, PartialEq, Eq)] pub enum AutoSizeUnit { Auto, @@ -68,11 +48,9 @@ impl From for WidgetText { } } +/// TODO(andreas): Should turn this "inside out" - [`SpatialSpaceViewState`] should be used by [`View2DState`]/[`View3DState`], not the other way round. #[derive(Clone)] pub struct SpatialSpaceViewState { - /// How the scene is navigated. - pub nav_mode: EditableAutoValue, - /// Estimated bounding box of all data. Accumulated over every time data is displayed. /// /// Specify default explicitly, otherwise it will be a box at 0.0 after deserialization. @@ -97,7 +75,6 @@ pub struct SpatialSpaceViewState { impl Default for SpatialSpaceViewState { fn default() -> Self { Self { - nav_mode: EditableAutoValue::Auto(SpatialNavigationMode::ThreeD), scene_bbox_accum: BoundingBox::nothing(), scene_bbox: BoundingBox::nothing(), scene_num_primitives: 0, @@ -134,17 +111,24 @@ impl SpatialSpaceViewState { config } + // TODO(andreas): Move to heuristics.rs pub fn update_object_property_heuristics( &self, ctx: &mut ViewerContext<'_>, - entity_paths: &IntSet, + ent_paths: &IntSet, entity_properties: &mut re_data_store::EntityPropertyMap, + spatial_kind: SpatialSpaceViewKind, ) { re_tracing::profile_function!(); - for entity_path in entity_paths { + for entity_path in ent_paths { self.update_pinhole_property_heuristics(ctx, entity_path, entity_properties); - self.update_depth_cloud_property_heuristics(ctx, entity_path, entity_properties); + Self::update_depth_cloud_property_heuristics( + ctx, + entity_path, + entity_properties, + spatial_kind, + ); self.update_transform3d_lines_heuristics(ctx, entity_path, entity_properties); } } @@ -198,10 +182,10 @@ impl SpatialSpaceViewState { } fn update_depth_cloud_property_heuristics( - &self, ctx: &mut ViewerContext<'_>, entity_path: &EntityPath, entity_properties: &mut re_data_store::EntityPropertyMap, + spatial_kind: SpatialSpaceViewKind, ) -> Option<()> { let store = &ctx.store_db.entity_db.data_store; let tensor = store.query_latest_component::(entity_path, &ctx.current_query())?; @@ -210,7 +194,7 @@ impl SpatialSpaceViewState { if properties.backproject_depth.is_auto() { properties.backproject_depth = EditableAutoValue::Auto( tensor.meaning == TensorDataMeaning::Depth - && *self.nav_mode.get() == SpatialNavigationMode::ThreeD, + && spatial_kind == SpatialSpaceViewKind::ThreeD, ); } @@ -308,6 +292,7 @@ impl SpatialSpaceViewState { ui: &mut egui::Ui, space_origin: &EntityPath, space_view_id: SpaceViewId, + spatial_kind: SpatialSpaceViewKind, ) { let view_coordinates = ctx .store_db @@ -359,31 +344,7 @@ impl SpatialSpaceViewState { ctx.re_ui.grid_left_hand_label(ui, "Camera") .on_hover_text("The virtual camera which controls what is shown on screen."); ui.vertical(|ui| { - let mut nav_mode = *self.nav_mode.get(); - let mut changed = false; - egui::ComboBox::from_id_source("nav_mode") - .selected_text(nav_mode) - .show_ui(ui, |ui| { - ui.style_mut().wrap = Some(false); - ui.set_min_width(64.0); - - changed |= ui.selectable_value( - &mut nav_mode, - SpatialNavigationMode::TwoD, - SpatialNavigationMode::TwoD, - ).changed(); - - changed |= ui.selectable_value( - &mut nav_mode, - SpatialNavigationMode::ThreeD, - SpatialNavigationMode::ThreeD, - ).changed(); - }); - if changed { - self.nav_mode = EditableAutoValue::UserEdited(nav_mode); - } - - if *self.nav_mode.get() == SpatialNavigationMode::ThreeD { + if spatial_kind == SpatialSpaceViewKind::ThreeD { if ui.button("Reset").on_hover_text( "Resets camera position & orientation.\nYou can also double-click the 3D view.") .clicked() @@ -396,7 +357,7 @@ impl SpatialSpaceViewState { }); ui.end_row(); - if *self.nav_mode.get() == SpatialNavigationMode::ThreeD { + if spatial_kind == SpatialSpaceViewKind::ThreeD { ctx.re_ui.grid_left_hand_label(ui, "Coordinates") .on_hover_text("The world coordinate system used for this view."); ui.vertical(|ui|{ @@ -434,7 +395,7 @@ impl SpatialSpaceViewState { format_f32(min.y), format_f32(max.y), )); - if *self.nav_mode.get() == SpatialNavigationMode::ThreeD { + if spatial_kind == SpatialSpaceViewKind::ThreeD { ui.label(format!( "z [{} - {}]", format_f32(min.z), @@ -445,64 +406,6 @@ impl SpatialSpaceViewState { ui.end_row(); }); } - - pub fn view_spatial( - &mut self, - ctx: &mut ViewerContext<'_>, - ui: &mut egui::Ui, - view_ctx: &ViewContextCollection, - parts: &ViewPartCollection, - query: &ViewQuery<'_>, - draw_data: Vec, - ) -> Result<(), SpaceViewSystemExecutionError> { - self.scene_bbox = calculate_bounding_box(parts); - if self.scene_bbox_accum.is_nothing() || !self.scene_bbox_accum.size().is_finite() { - self.scene_bbox_accum = self.scene_bbox; - } else { - self.scene_bbox_accum = self.scene_bbox_accum.union(self.scene_bbox); - } - - if self.nav_mode.is_auto() { - self.nav_mode = EditableAutoValue::Auto(preferred_navigation_mode( - view_ctx, - parts, - query.space_origin, - )); - } - self.scene_num_primitives = view_ctx - .get::()? - .num_3d_primitives - .load(std::sync::atomic::Ordering::Relaxed); - - match *self.nav_mode.get() { - SpatialNavigationMode::ThreeD => { - view_3d(ctx, ui, self, view_ctx, parts, query, draw_data) - } - SpatialNavigationMode::TwoD => { - let scene_rect_accum = egui::Rect::from_min_max( - self.scene_bbox_accum.min.truncate().to_array().into(), - self.scene_bbox_accum.max.truncate().to_array().into(), - ); - view_2d( - ctx, - ui, - self, - view_ctx, - parts, - query, - scene_rect_accum, - draw_data, - ) - } - } - } - - pub fn help_text(&self, re_ui: &re_ui::ReUi) -> egui::WidgetText { - match *self.nav_mode.get() { - SpatialNavigationMode::TwoD => super::ui_2d::help_text(re_ui), - SpatialNavigationMode::ThreeD => super::ui_3d::help_text(re_ui), - } - } } fn size_ui( @@ -574,7 +477,7 @@ pub fn create_labels( eye3d: &Eye, parent_ui: &mut egui::Ui, highlights: &SpaceViewHighlights, - nav_mode: SpatialNavigationMode, + spatial_kind: SpatialSpaceViewKind, ) -> (Vec, Vec) { re_tracing::profile_function!(); @@ -587,7 +490,7 @@ pub fn create_labels( let (wrap_width, text_anchor_pos) = match label.target { UiLabelTarget::Rect(rect) => { // TODO(#1640): 2D labels are not visible in 3D for now. - if nav_mode == SpatialNavigationMode::ThreeD { + if spatial_kind == SpatialSpaceViewKind::ThreeD { continue; } let rect_in_ui = ui_from_canvas.transform_rect(rect); @@ -599,7 +502,7 @@ pub fn create_labels( } UiLabelTarget::Point2D(pos) => { // TODO(#1640): 2D labels are not visible in 3D for now. - if nav_mode == SpatialNavigationMode::ThreeD { + if spatial_kind == SpatialSpaceViewKind::ThreeD { continue; } let pos_in_ui = ui_from_canvas.transform_pos(pos); @@ -607,7 +510,7 @@ pub fn create_labels( } UiLabelTarget::Position3D(pos) => { // TODO(#1640): 3D labels are not visible in 2D for now. - if nav_mode == SpatialNavigationMode::TwoD { + if spatial_kind == SpatialSpaceViewKind::TwoD { continue; } let pos_in_ui = ui_from_world_3d * pos.extend(1.0); @@ -712,7 +615,7 @@ pub fn screenshot_context_menu( } } -#[allow(clippy::too_many_arguments)] +#[allow(clippy::too_many_arguments)] // TODO(andreas): Make this method sane. pub fn picking( ctx: &mut ViewerContext<'_>, mut response: egui::Response, @@ -726,6 +629,7 @@ pub fn picking( parts: &ViewPartCollection, ui_rects: &[PickableUiRect], query: &ViewQuery<'_>, + spatial_kind: SpatialSpaceViewKind, ) -> Result { re_tracing::profile_function!(); @@ -873,7 +777,7 @@ pub fn picking( ui.separator(); ui.horizontal(|ui| { let (w, h) = (w as f32, h as f32); - if *state.nav_mode.get() == SpatialNavigationMode::TwoD { + if spatial_kind == SpatialSpaceViewKind::TwoD { let rect = egui::Rect::from_min_size( egui::Pos2::ZERO, egui::vec2(w, h), @@ -924,14 +828,14 @@ pub fn picking( item_ui::select_hovered_on_click(ctx, &response, &hovered_items); - let hovered_space = match state.nav_mode.get() { - SpatialNavigationMode::TwoD => HoveredSpace::TwoD { + let hovered_space = match spatial_kind { + SpatialSpaceViewKind::TwoD => HoveredSpace::TwoD { space_2d: query.space_origin.clone(), pos: picking_context .pointer_in_space2d .extend(depth_at_pointer.unwrap_or(f32::INFINITY)), }, - SpatialNavigationMode::ThreeD => { + SpatialSpaceViewKind::ThreeD => { let hovered_point = picking_result.space_position(); HoveredSpace::ThreeD { space_3d: query.space_origin.clone(), diff --git a/crates/re_space_view_spatial/src/ui_2d.rs b/crates/re_space_view_spatial/src/ui_2d.rs index 383847e0f79f..14a4c6bf42d2 100644 --- a/crates/re_space_view_spatial/src/ui_2d.rs +++ b/crates/re_space_view_spatial/src/ui_2d.rs @@ -17,7 +17,8 @@ use super::{ use crate::{ contexts::SharedRenderBuilders, parts::collect_ui_labels, - ui::{outline_config, SpatialNavigationMode, SpatialSpaceViewState}, + ui::{outline_config, SpatialSpaceViewState}, + view_kind::SpatialSpaceViewKind, }; // --- @@ -219,7 +220,6 @@ pub fn help_text(re_ui: &re_ui::ReUi) -> egui::WidgetText { } /// Create the outer 2D view, which consists of a scrollable region -#[allow(clippy::too_many_arguments)] pub fn view_2d( ctx: &mut ViewerContext<'_>, ui: &mut egui::Ui, @@ -227,15 +227,18 @@ pub fn view_2d( view_ctx: &ViewContextCollection, parts: &ViewPartCollection, query: &ViewQuery<'_>, - scene_rect_accum: Rect, mut draw_data: Vec, ) -> Result<(), SpaceViewSystemExecutionError> { re_tracing::profile_function!(); // Save off the available_size since this is used for some of the layout updates later let available_size = ui.available_size(); + let store = ctx.store_db.store(); - let store = &ctx.store_db.entity_db.data_store; + let scene_rect_accum = egui::Rect::from_min_max( + state.scene_bbox_accum.min.truncate().to_array().into(), + state.scene_bbox_accum.max.truncate().to_array().into(), + ); // Determine the canvas which determines the extent of the explorable scene coordinates, // and thus the size of the scroll area. @@ -312,7 +315,7 @@ pub fn view_2d( &eye, ui, query.highlights, - SpatialNavigationMode::TwoD, + SpatialSpaceViewKind::TwoD, ); if !re_ui::egui_helpers::is_anything_being_dragged(ui.ctx()) { @@ -329,6 +332,7 @@ pub fn view_2d( parts, &ui_rects, query, + SpatialSpaceViewKind::TwoD, )?; } diff --git a/crates/re_space_view_spatial/src/ui_3d.rs b/crates/re_space_view_spatial/src/ui_3d.rs index 83bc4a0d0b72..d39b23ee778a 100644 --- a/crates/re_space_view_spatial/src/ui_3d.rs +++ b/crates/re_space_view_spatial/src/ui_3d.rs @@ -25,10 +25,8 @@ use crate::{ SIZE_BOOST_IN_POINTS_FOR_LINE_OUTLINES, }, space_camera_3d::SpaceCamera3D, - ui::{ - create_labels, outline_config, picking, screenshot_context_menu, SpatialNavigationMode, - SpatialSpaceViewState, - }, + ui::{create_labels, outline_config, picking, screenshot_context_menu, SpatialSpaceViewState}, + view_kind::SpatialSpaceViewKind, }; use super::eye::{Eye, OrbitEye}; @@ -374,7 +372,7 @@ pub fn view_3d( &eye, ui, highlights, - SpatialNavigationMode::ThreeD, + SpatialSpaceViewKind::ThreeD, ); if !re_ui::egui_helpers::is_anything_being_dragged(ui.ctx()) { @@ -391,6 +389,7 @@ pub fn view_3d( parts, &ui_rects, query, + SpatialSpaceViewKind::ThreeD, )?; } diff --git a/crates/re_space_view_tensor/src/space_view_class.rs b/crates/re_space_view_tensor/src/space_view_class.rs index 69c4e2e689cb..af5b6980cc45 100644 --- a/crates/re_space_view_tensor/src/space_view_class.rs +++ b/crates/re_space_view_tensor/src/space_view_class.rs @@ -126,10 +126,10 @@ impl SpaceViewClass for TensorSpaceView { } fn icon(&self) -> &'static re_ui::Icon { - &re_ui::icons::SPACE_VIEW_HISTOGRAM + &re_ui::icons::SPACE_VIEW_TENSOR } - fn help_text(&self, _re_ui: &re_ui::ReUi, _state: &Self::State) -> egui::WidgetText { + fn help_text(&self, _re_ui: &re_ui::ReUi) -> egui::WidgetText { "Select the Space View to configure which dimensions are shown.".into() } @@ -255,11 +255,11 @@ fn view_tensor( let dm = &state.slice.dim_mapping; [ ( - dimension_name(&tensor.shape, dm.width.unwrap()), + dimension_name(&tensor.shape, dm.width.unwrap_or_default()), dm.invert_width, ), ( - dimension_name(&tensor.shape, dm.height.unwrap()), + dimension_name(&tensor.shape, dm.height.unwrap_or_default()), dm.invert_height, ), ] diff --git a/crates/re_space_view_tensor/src/view_part_system.rs b/crates/re_space_view_tensor/src/view_part_system.rs index 6f18fb365cbc..10eb625e365e 100644 --- a/crates/re_space_view_tensor/src/view_part_system.rs +++ b/crates/re_space_view_tensor/src/view_part_system.rs @@ -1,13 +1,12 @@ use re_arrow_store::LatestAtQuery; use re_components::{DecodedTensor, Tensor}; use re_data_store::{EntityPath, EntityProperties, InstancePath}; -use re_log_types::{Component as _, InstanceKey}; +use re_log_types::{Component as _, ComponentName, InstanceKey, TimeInt, Timeline}; use re_viewer_context::{ ArchetypeDefinition, SpaceViewSystemExecutionError, TensorDecodeCache, ViewContextCollection, ViewPartSystem, ViewQuery, ViewerContext, }; -/// A bar chart scene, with everything needed to render it. #[derive(Default)] pub struct TensorSystem { pub tensors: std::collections::BTreeMap, @@ -18,6 +17,27 @@ impl ViewPartSystem for TensorSystem { vec1::vec1![Tensor::name()] } + /// Tensor view doesn't handle 2D images, see [`TensorSystem::load_tensor_entity`] + fn queries_any_components_of( + &self, + store: &re_arrow_store::DataStore, + ent_path: &EntityPath, + components: &[ComponentName], + ) -> bool { + if !components.contains(&Tensor::name()) { + return false; + } + + if let Some(tensor) = store.query_latest_component::( + ent_path, + &LatestAtQuery::new(Timeline::log_time(), TimeInt::MAX), + ) { + !tensor.is_shaped_like_an_image() && !tensor.is_vector() + } else { + false + } + } + fn execute( &mut self, ctx: &mut ViewerContext<'_>, diff --git a/crates/re_space_view_text/Cargo.toml b/crates/re_space_view_text/Cargo.toml index 57e751c4f7d6..df689f75c837 100644 --- a/crates/re_space_view_text/Cargo.toml +++ b/crates/re_space_view_text/Cargo.toml @@ -29,6 +29,7 @@ re_tracing.workspace = true re_ui.workspace = true re_viewer_context.workspace = true -egui.workspace = true egui_extras.workspace = true +egui.workspace = true +nohash-hasher.workspace = true vec1.workspace = true diff --git a/crates/re_space_view_text/src/space_view_class.rs b/crates/re_space_view_text/src/space_view_class.rs index 1c37ab028775..a68f7a287e02 100644 --- a/crates/re_space_view_text/src/space_view_class.rs +++ b/crates/re_space_view_text/src/space_view_class.rs @@ -1,11 +1,12 @@ +use nohash_hasher::IntSet; use std::collections::BTreeMap; use re_data_ui::item_ui; use re_log_types::{EntityPath, TimePoint, Timeline}; use re_viewer_context::{ - level_to_rich_text, SpaceViewClass, SpaceViewClassName, SpaceViewClassRegistryError, - SpaceViewId, SpaceViewState, SpaceViewSystemExecutionError, ViewContextCollection, - ViewPartCollection, ViewQuery, ViewerContext, + level_to_rich_text, AutoSpawnHeuristic, SpaceViewClass, SpaceViewClassName, + SpaceViewClassRegistryError, SpaceViewId, SpaceViewState, SpaceViewSystemExecutionError, + ViewContextCollection, ViewPartCollection, ViewQuery, ViewerContext, }; use super::view_part_system::{TextEntry, TextSystem}; @@ -48,7 +49,7 @@ impl SpaceViewClass for TextSpaceView { &re_ui::icons::SPACE_VIEW_TEXTBOX } - fn help_text(&self, _re_ui: &re_ui::ReUi, _state: &Self::State) -> egui::WidgetText { + fn help_text(&self, _re_ui: &re_ui::ReUi) -> egui::WidgetText { "Shows text entries over time.\nSelect the Space View for filtering options.".into() } @@ -67,6 +68,20 @@ impl SpaceViewClass for TextSpaceView { re_viewer_context::SpaceViewClassLayoutPriority::Low } + fn auto_spawn_heuristic( + &self, + _ctx: &ViewerContext<'_>, + space_origin: &EntityPath, + ent_paths: &IntSet, + ) -> re_viewer_context::AutoSpawnHeuristic { + // Always spawn a single text view for the root and nothing else. + if space_origin.is_root() && !ent_paths.is_empty() { + AutoSpawnHeuristic::AlwaysSpawn + } else { + AutoSpawnHeuristic::NeverSpawn + } + } + fn selection_ui( &self, ctx: &mut ViewerContext<'_>, diff --git a/crates/re_space_view_text_box/src/space_view_class.rs b/crates/re_space_view_text_box/src/space_view_class.rs index fefba05d4567..e0035d6b8467 100644 --- a/crates/re_space_view_text_box/src/space_view_class.rs +++ b/crates/re_space_view_text_box/src/space_view_class.rs @@ -47,7 +47,7 @@ impl SpaceViewClass for TextBoxSpaceView { &re_ui::icons::SPACE_VIEW_TEXTBOX } - fn help_text(&self, _re_ui: &re_ui::ReUi, _state: &Self::State) -> egui::WidgetText { + fn help_text(&self, _re_ui: &re_ui::ReUi) -> egui::WidgetText { "Displays text from a text entry components.".into() } 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 6f7298909370..b1860b4f10f7 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 @@ -26,10 +26,10 @@ impl SpaceViewClass for TimeSeriesSpaceView { } fn icon(&self) -> &'static re_ui::Icon { - &re_ui::icons::SPACE_VIEW_HISTOGRAM + &re_ui::icons::SPACE_VIEW_CHART } - fn help_text(&self, re_ui: &re_ui::ReUi, _state: &Self::State) -> egui::WidgetText { + fn help_text(&self, re_ui: &re_ui::ReUi) -> egui::WidgetText { let mut layout = re_ui::LayoutJobBuilder::new(re_ui); layout.add("Pan by dragging, or scroll (+ "); diff --git a/crates/re_space_view_time_series/src/view_part_system.rs b/crates/re_space_view_time_series/src/view_part_system.rs index 1c103414063c..49c633551cee 100644 --- a/crates/re_space_view_time_series/src/view_part_system.rs +++ b/crates/re_space_view_time_series/src/view_part_system.rs @@ -62,7 +62,14 @@ pub struct TimeSeriesSystem { impl ViewPartSystem for TimeSeriesSystem { fn archetype(&self) -> re_viewer_context::ArchetypeDefinition { - vec1::Vec1::try_from(Self::archetype_array()).unwrap() // TODO(wumpf): `archetype` should return a fixed sized array. + vec1::Vec1::try_from_vec( + Self::archetype_array() + .into_iter() + .skip(1) // Skip [`InstanceKey`] + .collect::>(), + ) + .unwrap() + // TODO(wumpf): `archetype` should return a fixed sized array. } fn execute( diff --git a/crates/re_ui/data/icons/spaceview_2d.png b/crates/re_ui/data/icons/spaceview_2d.png new file mode 100644 index 000000000000..a0b48f7b3a88 Binary files /dev/null and b/crates/re_ui/data/icons/spaceview_2d.png differ diff --git a/crates/re_ui/src/icons.rs b/crates/re_ui/src/icons.rs index 26d95eb78c41..0139e4434ca8 100644 --- a/crates/re_ui/src/icons.rs +++ b/crates/re_ui/src/icons.rs @@ -63,6 +63,10 @@ pub const SPACE_VIEW_TEXTBOX: Icon = Icon::new( "spaceview_text", include_bytes!("../data/icons/spaceview_text.png"), ); +pub const SPACE_VIEW_2D: Icon = Icon::new( + "spaceview_2d", + include_bytes!("../data/icons/spaceview_2d.png"), +); pub const SPACE_VIEW_3D: Icon = Icon::new( "spaceview_3d", include_bytes!("../data/icons/spaceview_3d.png"), diff --git a/crates/re_viewer/src/app.rs b/crates/re_viewer/src/app.rs index 81ee8e09e69e..11938145f529 100644 --- a/crates/re_viewer/src/app.rs +++ b/crates/re_viewer/src/app.rs @@ -928,7 +928,8 @@ fn populate_space_view_class_registry_with_builtin( space_view_class_registry: &mut SpaceViewClassRegistry, ) -> Result<(), SpaceViewClassRegistryError> { space_view_class_registry.add_class::()?; - space_view_class_registry.add_class::()?; + space_view_class_registry.add_class::()?; + space_view_class_registry.add_class::()?; space_view_class_registry.add_class::()?; space_view_class_registry.add_class::()?; space_view_class_registry.add_class::()?; diff --git a/crates/re_viewer_context/src/lib.rs b/crates/re_viewer_context/src/lib.rs index c534f9dc7f1c..5503f5fea65d 100644 --- a/crates/re_viewer_context/src/lib.rs +++ b/crates/re_viewer_context/src/lib.rs @@ -35,11 +35,11 @@ pub use selection_state::{ HoverHighlight, HoveredSpace, InteractionHighlight, SelectionHighlight, SelectionState, }; pub use space_view::{ - ArchetypeDefinition, DynSpaceViewClass, SpaceViewClass, SpaceViewClassLayoutPriority, - SpaceViewClassName, SpaceViewClassRegistry, SpaceViewClassRegistryError, - SpaceViewEntityHighlight, SpaceViewHighlights, SpaceViewOutlineMasks, SpaceViewState, - SpaceViewSystemExecutionError, SpaceViewSystemRegistry, ViewContextCollection, - ViewContextSystem, ViewPartCollection, ViewPartSystem, ViewQuery, + ArchetypeDefinition, AutoSpawnHeuristic, DynSpaceViewClass, SpaceViewClass, + SpaceViewClassLayoutPriority, SpaceViewClassName, SpaceViewClassRegistry, + SpaceViewClassRegistryError, SpaceViewEntityHighlight, SpaceViewHighlights, + SpaceViewOutlineMasks, SpaceViewState, SpaceViewSystemExecutionError, SpaceViewSystemRegistry, + ViewContextCollection, ViewContextSystem, ViewPartCollection, ViewPartSystem, ViewQuery, }; pub use store_context::StoreContext; pub use tensor::{TensorDecodeCache, TensorStats, TensorStatsCache}; diff --git a/crates/re_viewer_context/src/space_view/auto_spawn_heuristic.rs b/crates/re_viewer_context/src/space_view/auto_spawn_heuristic.rs new file mode 100644 index 000000000000..88b690917f95 --- /dev/null +++ b/crates/re_viewer_context/src/space_view/auto_spawn_heuristic.rs @@ -0,0 +1,22 @@ +/// Heuristic result used to determine if a Space View with a given class should be automatically spawned. +/// +/// The details of how this is interpreted are up to the code determining candidates and performing +/// Space View spawning. +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum AutoSpawnHeuristic { + /// Always spawn the Space View if it is a candidate. + AlwaysSpawn, + + /// Never spawn the Space View if it is a candidate. + /// This means that the candidate can only be created manually. + NeverSpawn, + + /// If there's several candidates for the same root, spawn only one with the highest score. + /// + /// Right now there is an implicit assumption that all SpaceViews which return + /// [`AutoSpawnHeuristic::SpawnClassWithHighestScoreForRoot`] are in some sense equivalent. + /// + /// TODO(andreas): This might be true today but ends up being a bit limiting. + /// Adding something like a `equivalency_id` along with the score would let us be a bit more explicit. + SpawnClassWithHighestScoreForRoot(f32), +} diff --git a/crates/re_viewer_context/src/space_view/dyn_space_view_class.rs b/crates/re_viewer_context/src/space_view/dyn_space_view_class.rs index ed4728e3761b..0a8d709bc47a 100644 --- a/crates/re_viewer_context/src/space_view/dyn_space_view_class.rs +++ b/crates/re_viewer_context/src/space_view/dyn_space_view_class.rs @@ -3,7 +3,8 @@ use re_data_store::EntityPropertyMap; use re_log_types::{ComponentName, EntityPath}; use crate::{ - SpaceViewClassRegistryError, SpaceViewId, SpaceViewSystemRegistry, ViewQuery, ViewerContext, + AutoSpawnHeuristic, SpaceViewClassRegistryError, SpaceViewId, SpaceViewSystemRegistry, + ViewQuery, ViewerContext, }; /// First element is the primary component, all others are optional. @@ -52,7 +53,7 @@ pub trait DynSpaceViewClass { fn icon(&self) -> &'static re_ui::Icon; /// Help text describing how to interact with this space view in the ui. - fn help_text(&self, re_ui: &re_ui::ReUi, state: &dyn SpaceViewState) -> egui::WidgetText; + fn help_text(&self, re_ui: &re_ui::ReUi) -> egui::WidgetText; /// Called once upon registration of the class /// @@ -78,6 +79,17 @@ pub trait DynSpaceViewClass { /// Controls how likely this space view will get a large tile in the ui. fn layout_priority(&self) -> SpaceViewClassLayoutPriority; + /// Heuristic used to determine which space view is the best fit for a set of paths. + /// + /// For each path in `ent_paths`, at least one of the registered [`crate::ViewPartSystem`] for this class + /// returned true when calling [`crate::ViewPartSystem::queries_any_components_of`]. + fn auto_spawn_heuristic( + &self, + _ctx: &ViewerContext<'_>, + space_origin: &EntityPath, + ent_paths: &IntSet, + ) -> AutoSpawnHeuristic; + /// Ui shown when the user selects a space view of this class. /// /// TODO(andreas): Should this be instead implemented via a registered `data_ui` of all blueprint relevant types? @@ -98,7 +110,7 @@ pub trait DynSpaceViewClass { &self, ctx: &mut ViewerContext<'_>, state: &mut dyn SpaceViewState, - entity_paths: &IntSet, + ent_paths: &IntSet, entity_properties: &mut EntityPropertyMap, ); diff --git a/crates/re_viewer_context/src/space_view/mod.rs b/crates/re_viewer_context/src/space_view/mod.rs index 85bfbcd66079..4d952ccdb27b 100644 --- a/crates/re_viewer_context/src/space_view/mod.rs +++ b/crates/re_viewer_context/src/space_view/mod.rs @@ -4,6 +4,7 @@ //! Does not implement any concrete space view. // TODO(andreas): Can we move some of these to the `re_space_view` crate? +mod auto_spawn_heuristic; mod dyn_space_view_class; mod highlights; mod space_view_class; @@ -13,6 +14,7 @@ mod view_context_system; mod view_part_system; mod view_query; +pub use auto_spawn_heuristic::AutoSpawnHeuristic; pub use dyn_space_view_class::{ ArchetypeDefinition, DynSpaceViewClass, SpaceViewClassLayoutPriority, SpaceViewClassName, SpaceViewState, 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 97b3dae5dea1..c5a0c10be658 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 @@ -3,9 +3,9 @@ use re_data_store::EntityPropertyMap; use re_log_types::EntityPath; use crate::{ - ArchetypeDefinition, DynSpaceViewClass, SpaceViewClassName, SpaceViewClassRegistryError, - SpaceViewId, SpaceViewState, SpaceViewSystemExecutionError, SpaceViewSystemRegistry, - ViewContextCollection, ViewPartCollection, ViewQuery, ViewerContext, + ArchetypeDefinition, AutoSpawnHeuristic, DynSpaceViewClass, SpaceViewClassName, + SpaceViewClassRegistryError, SpaceViewId, SpaceViewState, SpaceViewSystemExecutionError, + SpaceViewSystemRegistry, ViewContextCollection, ViewPartCollection, ViewQuery, ViewerContext, }; /// Defines a class of space view. @@ -27,7 +27,7 @@ pub trait SpaceViewClass: std::marker::Sized { fn icon(&self) -> &'static re_ui::Icon; /// Help text describing how to interact with this space view in the ui. - fn help_text(&self, re_ui: &re_ui::ReUi, state: &Self::State) -> egui::WidgetText; + fn help_text(&self, re_ui: &re_ui::ReUi) -> egui::WidgetText; /// Called once upon registration of the class /// @@ -45,6 +45,19 @@ pub trait SpaceViewClass: std::marker::Sized { /// Controls how likely this space view will get a large tile in the ui. fn layout_priority(&self) -> crate::SpaceViewClassLayoutPriority; + /// Heuristic used to determine which space view is the best fit for a set of paths. + /// + /// For each path in `ent_paths`, at least one of the registered [`crate::ViewPartSystem`] for this class + /// returned true when calling [`crate::ViewPartSystem::queries_any_components_of`]. + fn auto_spawn_heuristic( + &self, + _ctx: &ViewerContext<'_>, + _space_origin: &EntityPath, + ent_paths: &IntSet, + ) -> AutoSpawnHeuristic { + AutoSpawnHeuristic::SpawnClassWithHighestScoreForRoot(ent_paths.len() as f32) + } + /// Optional archetype of the Space View's blueprint properties. /// /// Blueprint components that only apply to the space view itself, not to the entities it displays. @@ -72,7 +85,7 @@ pub trait SpaceViewClass: std::marker::Sized { &self, _ctx: &mut ViewerContext<'_>, _state: &Self::State, - _entity_paths: &IntSet, + _ent_paths: &IntSet, _entity_properties: &mut re_data_store::EntityPropertyMap, ) { } @@ -113,8 +126,8 @@ impl DynSpaceViewClass for T { } #[inline] - fn help_text(&self, re_ui: &re_ui::ReUi, state: &dyn SpaceViewState) -> egui::WidgetText { - typed_state_wrapper(state, |state| self.help_text(re_ui, state)) + fn help_text(&self, re_ui: &re_ui::ReUi) -> egui::WidgetText { + self.help_text(re_ui) } #[inline] @@ -138,6 +151,17 @@ impl DynSpaceViewClass for T { self.layout_priority() } + #[inline] + fn auto_spawn_heuristic( + &self, + ctx: &ViewerContext<'_>, + space_origin: &EntityPath, + ent_paths: &IntSet, + ) -> AutoSpawnHeuristic { + self.auto_spawn_heuristic(ctx, space_origin, ent_paths) + } + + #[inline] fn blueprint_archetype(&self) -> Option { self.blueprint_archetype() } @@ -146,11 +170,11 @@ impl DynSpaceViewClass for T { &self, ctx: &mut ViewerContext<'_>, state: &mut dyn SpaceViewState, - entity_paths: &IntSet, + ent_paths: &IntSet, entity_properties: &mut EntityPropertyMap, ) { typed_state_wrapper_mut(state, |state| { - self.prepare_ui(ctx, state, entity_paths, entity_properties); + self.prepare_ui(ctx, state, ent_paths, entity_properties); }); } diff --git a/crates/re_viewer_context/src/space_view/space_view_class_placeholder.rs b/crates/re_viewer_context/src/space_view/space_view_class_placeholder.rs index 684b47751573..480b2b552fbb 100644 --- a/crates/re_viewer_context/src/space_view/space_view_class_placeholder.rs +++ b/crates/re_viewer_context/src/space_view/space_view_class_placeholder.rs @@ -18,7 +18,7 @@ impl SpaceViewClass for SpaceViewClassPlaceholder { &re_ui::icons::SPACE_VIEW_UNKNOWN } - fn help_text(&self, _re_ui: &re_ui::ReUi, _state: &()) -> egui::WidgetText { + fn help_text(&self, _re_ui: &re_ui::ReUi) -> egui::WidgetText { "The Space View Class was not recognized.\nThis happens if either the Blueprint specifies an invalid Space View Class or this version of the Viewer does not know about this type.".into() } @@ -47,13 +47,13 @@ impl SpaceViewClass for SpaceViewClassPlaceholder { &self, ctx: &mut ViewerContext<'_>, ui: &mut egui::Ui, - state: &mut Self::State, + _state: &mut Self::State, _view_ctx: &ViewContextCollection, _parts: &ViewPartCollection, _query: &ViewQuery<'_>, _draw_data: Vec, ) -> Result<(), SpaceViewSystemExecutionError> { - ui.centered_and_justified(|ui| ui.label(self.help_text(ctx.re_ui, state))); + ui.centered_and_justified(|ui| ui.label(self.help_text(ctx.re_ui))); Ok(()) } } diff --git a/crates/re_viewer_context/src/space_view/space_view_class_registry.rs b/crates/re_viewer_context/src/space_view/space_view_class_registry.rs index 8a75efa938b3..5bebd4235702 100644 --- a/crates/re_viewer_context/src/space_view/space_view_class_registry.rs +++ b/crates/re_viewer_context/src/space_view/space_view_class_registry.rs @@ -88,7 +88,7 @@ impl SpaceViewSystemRegistry { } } - pub(crate) fn new_part_collection(&self) -> ViewPartCollection { + pub fn new_part_collection(&self) -> ViewPartCollection { ViewPartCollection { systems: self .parts diff --git a/crates/re_viewer_context/src/space_view/view_part_system.rs b/crates/re_viewer_context/src/space_view/view_part_system.rs index d67aef81d7e6..970cc31278c9 100644 --- a/crates/re_viewer_context/src/space_view/view_part_system.rs +++ b/crates/re_viewer_context/src/space_view/view_part_system.rs @@ -1,4 +1,5 @@ use ahash::HashMap; +use re_log_types::{ComponentName, EntityPath}; use crate::{ArchetypeDefinition, SpaceViewSystemExecutionError, ViewQuery, ViewerContext}; @@ -11,6 +12,25 @@ pub trait ViewPartSystem { /// The archetype queried by this scene element. fn archetype(&self) -> ArchetypeDefinition; + /// Returns true if the system queries given components on the given path in its [`Self::execute`] method. + /// + /// List of components is expected to be all components that have ever been logged on the entity path. + /// By default, this only checks if the primary components of the archetype are contained + /// in the list of components. + /// + /// Override this method only if a more detailed condition is required to inform heuristics whether + /// the given entity is relevant for this system. + fn queries_any_components_of( + &self, + _store: &re_arrow_store::DataStore, + _ent_path: &EntityPath, + components: &[ComponentName], + ) -> bool { + // TODO(andreas): Use new archetype definitions which also allows for several primaries. + let archetype = self.archetype(); + components.contains(archetype.first()) + } + /// Queries the data store and performs data conversions to make it ready for display. /// /// Musn't query any data outside of the archetype. diff --git a/crates/re_viewport/src/blueprint_components/space_view.rs b/crates/re_viewport/src/blueprint_components/space_view.rs index 8851293e14c2..7c393a6c1fc3 100644 --- a/crates/re_viewport/src/blueprint_components/space_view.rs +++ b/crates/re_viewport/src/blueprint_components/space_view.rs @@ -44,15 +44,9 @@ impl std::fmt::Debug for SpaceViewComponent { #[test] fn test_spaceview() { - use crate::view_category::ViewCategory; use arrow2_convert::{deserialize::TryIntoCollection, serialize::TryIntoArrow}; - let space_view = SpaceViewBlueprint::new( - "Spatial".into(), - ViewCategory::Spatial, - &"foo".into(), - &["foo/bar".into()], - ); + let space_view = SpaceViewBlueprint::new("Spatial".into(), &"foo".into(), &["foo/bar".into()]); let data = [SpaceViewComponent { space_view }]; let array: Box = data.try_into_arrow().unwrap(); diff --git a/crates/re_viewport/src/lib.rs b/crates/re_viewport/src/lib.rs index e15d1efb1e57..602354878b1d 100644 --- a/crates/re_viewport/src/lib.rs +++ b/crates/re_viewport/src/lib.rs @@ -8,7 +8,6 @@ mod space_view; mod space_view_entity_picker; mod space_view_heuristics; mod space_view_highlights; -mod view_category; mod viewport; mod viewport_blueprint; mod viewport_blueprint_ui; @@ -17,7 +16,6 @@ pub mod blueprint_components; pub use space_info::SpaceInfoCollection; pub use space_view::SpaceViewBlueprint; -pub use view_category::ViewCategory; pub use viewport::{Viewport, ViewportState}; pub use viewport_blueprint::ViewportBlueprint; diff --git a/crates/re_viewport/src/space_view.rs b/crates/re_viewport/src/space_view.rs index ddb80bd60bf8..8c4a6622e87a 100644 --- a/crates/re_viewport/src/space_view.rs +++ b/crates/re_viewport/src/space_view.rs @@ -1,4 +1,3 @@ -use re_arrow_store::Timeline; use re_data_store::{EntityPath, EntityTree, TimeInt}; use re_renderer::ScreenshotProcessor; use re_space_view::{DataBlueprintTree, ScreenshotMode}; @@ -9,8 +8,7 @@ use re_viewer_context::{ use crate::{ space_info::SpaceInfoCollection, - space_view_heuristics::default_queried_entities, - view_category::{categorize_entity_path, ViewCategory}, + space_view_heuristics::{default_queried_entities, is_entity_processed_by_class}, }; // ---------------------------------------------------------------------------- @@ -32,10 +30,6 @@ pub struct SpaceViewBlueprint { /// It determines which entities are part of the spaceview. pub data_blueprint: DataBlueprintTree, - /// We only show data that match this category. - /// TODO(andreas): This is obsolete and should be fully replaced by the space view type framework. - pub category: ViewCategory, - /// True if the user is expected to add entities themselves. False otherwise. pub entities_determined_by_user: bool, } @@ -49,7 +43,6 @@ impl SpaceViewBlueprint { class_name, space_origin, data_blueprint, - category, entities_determined_by_user, } = self; @@ -58,7 +51,6 @@ impl SpaceViewBlueprint { || class_name != &other.class_name || space_origin != &other.space_origin || data_blueprint.has_edits(&other.data_blueprint) - || category != &other.category || entities_determined_by_user != &other.entities_determined_by_user } } @@ -66,7 +58,6 @@ impl SpaceViewBlueprint { impl SpaceViewBlueprint { pub fn new( space_view_class: SpaceViewClassName, - category: ViewCategory, space_path: &EntityPath, queries_entities: &[EntityPath], ) -> Self { @@ -91,7 +82,6 @@ impl SpaceViewBlueprint { id: SpaceViewId::random(), space_origin: space_path.clone(), data_blueprint: data_blueprint_tree, - category, entities_determined_by_user: false, } } @@ -124,7 +114,7 @@ impl SpaceViewBlueprint { if !self.entities_determined_by_user { // Add entities that have been logged since we were created let queries_entities = - default_queried_entities(ctx, &self.space_origin, spaces_info, self.category); + default_queried_entities(ctx, &self.class_name, &self.space_origin, spaces_info); self.data_blueprint.insert_entities_according_to_hierarchy( queries_entities.iter(), &self.space_origin, @@ -202,7 +192,7 @@ impl SpaceViewBlueprint { class.prepare_ui( ctx, view_state, - &self.data_blueprint.entity_paths().clone(), // Clone to workaround borrow checker. + &self.data_blueprint.entity_paths().clone(), // Clone to work around borrow checker. self.data_blueprint.data_blueprints_individual(), ); @@ -238,18 +228,15 @@ impl SpaceViewBlueprint { /// Ignores all entities that can't be added or are already added. pub fn add_entity_subtree( &mut self, + ctx: &ViewerContext<'_>, tree: &EntityTree, spaces_info: &SpaceInfoCollection, - store_db: &re_data_store::StoreDb, ) { re_tracing::profile_function!(); let mut entities = Vec::new(); tree.visit_children_recursively(&mut |entity_path: &EntityPath| { - let entity_categories = - categorize_entity_path(Timeline::log_time(), store_db, entity_path); - - if entity_categories.contains(self.category) + if is_entity_processed_by_class(ctx, &self.class_name, entity_path) && !self.data_blueprint.contains_entity(entity_path) && spaces_info .is_reachable_by_transform(entity_path, &self.space_origin) diff --git a/crates/re_viewport/src/space_view_entity_picker.rs b/crates/re_viewport/src/space_view_entity_picker.rs index cf5b743e7d1b..ad7f3f6b2a95 100644 --- a/crates/re_viewport/src/space_view_entity_picker.rs +++ b/crates/re_viewport/src/space_view_entity_picker.rs @@ -1,14 +1,12 @@ use itertools::Itertools; use nohash_hasher::IntMap; -use re_arrow_store::Timeline; use re_data_store::{EntityPath, EntityTree, InstancePath}; use re_data_ui::item_ui; use re_viewer_context::{SpaceViewId, ViewerContext}; use crate::{ - space_info::SpaceInfoCollection, - space_view::SpaceViewBlueprint, - view_category::{categorize_entity_path, ViewCategory}, + space_info::SpaceInfoCollection, space_view::SpaceViewBlueprint, + space_view_heuristics::is_entity_processed_by_class, }; /// Window for adding/removing entities from a space view. @@ -215,7 +213,7 @@ fn add_entities_line_ui( |ui| { let response = ctx.re_ui.small_icon_button(ui, &re_ui::icons::ADD); if response.clicked() { - space_view.add_entity_subtree(entity_tree, spaces_info, ctx.store_db); + space_view.add_entity_subtree(ctx, entity_tree, spaces_info); } if add_info @@ -292,8 +290,6 @@ impl CanAddToSpaceView { #[derive(Default)] struct EntityAddInfo { - #[allow(dead_code)] - categories: enumset::EnumSet, can_add: CanAddToSpaceView, can_add_self_or_descendant: CanAddToSpaceView, } @@ -307,28 +303,24 @@ fn create_entity_add_info( let mut meta_data: IntMap = IntMap::default(); tree.visit_children_recursively(&mut |entity_path| { - let categories = categorize_entity_path(Timeline::log_time(), ctx.store_db, entity_path); - let can_add: CanAddToSpaceView = if categories.contains(space_view.category) { - match spaces_info.is_reachable_by_transform(entity_path, &space_view.space_origin) { - Ok(()) => CanAddToSpaceView::Compatible { - already_added: space_view.data_blueprint.contains_entity(entity_path), - }, - Err(reason) => CanAddToSpaceView::No { - reason: reason.to_string(), - }, - } - } else if categories.is_empty() { - CanAddToSpaceView::No { - reason: "Entity does not have any components".to_owned(), - } - } else { - CanAddToSpaceView::No { - reason: format!( - "Entity can't be displayed by this class of Space View ({})", - space_view.class_name() - ), - } - }; + let can_add: CanAddToSpaceView = + if is_entity_processed_by_class(ctx, space_view.class_name(), entity_path) { + match spaces_info.is_reachable_by_transform(entity_path, &space_view.space_origin) { + Ok(()) => CanAddToSpaceView::Compatible { + already_added: space_view.data_blueprint.contains_entity(entity_path), + }, + Err(reason) => CanAddToSpaceView::No { + reason: reason.to_string(), + }, + } + } else { + CanAddToSpaceView::No { + reason: format!( + "Entity can't be displayed by this class of Space View ({}), since it doesn't match any archetype that the Space View can process.", + space_view.class_name() + ), + } + }; if can_add.is_compatible() { // Mark parents aware that there is some descendant that is compatible @@ -344,7 +336,6 @@ fn create_entity_add_info( meta_data.insert( entity_path.clone(), EntityAddInfo { - categories, can_add, can_add_self_or_descendant, }, diff --git a/crates/re_viewport/src/space_view_heuristics.rs b/crates/re_viewport/src/space_view_heuristics.rs index 2ee52e20105c..6bafd3651baf 100644 --- a/crates/re_viewport/src/space_view_heuristics.rs +++ b/crates/re_viewport/src/space_view_heuristics.rs @@ -1,20 +1,30 @@ -use std::collections::BTreeMap; - use ahash::HashMap; use itertools::Itertools; use nohash_hasher::IntSet; -use re_arrow_store::{DataStore, LatestAtQuery, Timeline}; + +use re_arrow_store::{LatestAtQuery, Timeline}; use re_components::{DisconnectedSpace, Pinhole, Tensor}; -use re_data_store::{ComponentName, EntityPath}; -use re_log_types::Component as _; -use re_viewer_context::{SpaceViewClassName, ViewerContext}; - -use crate::{ - space_info::SpaceInfoCollection, - space_view::SpaceViewBlueprint, - view_category::{categorize_entity_path, ViewCategory, ViewCategorySet}, +use re_data_store::EntityPath; +use re_viewer_context::{ + AutoSpawnHeuristic, SpaceViewClassName, ViewPartCollection, ViewerContext, }; +use crate::{space_info::SpaceInfoCollection, space_view::SpaceViewBlueprint}; + +// --------------------------------------------------------------------------- +// TODO(andreas): Figure out how we can move heuristics based on concrete space view classes into the classes themselves. + +/// Returns true if a class is one of our spatial classes. +fn is_spatial_class(class: &SpaceViewClassName) -> bool { + class.as_str() == "3D" || class.as_str() == "2D" +} + +fn is_tensor_class(class: &SpaceViewClassName) -> bool { + class.as_str() == "Tensor" +} + +// --------------------------------------------------------------------------- + /// List out all space views we allow the user to create. pub fn all_possible_space_views( ctx: &ViewerContext<'_>, @@ -31,47 +41,31 @@ pub fn all_possible_space_views( .chain(root_children.values().map(|sub_tree| &sub_tree.path)) .unique(); - // TODO(andreas): Only needed for workaround for custom space views. - // This should go away together with ViewCategory. - let root_space = spaces_info.get_first_parent_with_info(&EntityPath::root()); - let root_entities = root_space - .descendants_without_transform - .iter() - .cloned() - .collect::>(); - - // For each candidate, create space views for all possible categories. + // For each candidate, create space views for all possible classes. + // TODO(andreas): Could save quite a view allocations here by re-using component- and parts arrays. candidate_space_paths .flat_map(|candidate_space_path| { - default_queried_entities_by_category(ctx, candidate_space_path, spaces_info) - .iter() - .map(|(category, entity_paths)| { - SpaceViewBlueprint::new( - class_name_from_category(*category), - *category, - candidate_space_path, - entity_paths, - ) - }) - .collect::>() - }) - // TODO(wumpf): Workaround to add custom space views. - .chain( ctx.space_view_class_registry .iter_classes() .filter_map(|class| { - if category_from_class_name(class.name()).is_none() { + let class_name = class.name(); + let entities = default_queried_entities( + ctx, + &class_name, + candidate_space_path, + spaces_info, + ); + if entities.is_empty() { + None + } else { Some(SpaceViewBlueprint::new( - class.name(), - ViewCategory::Text, - &EntityPath::root(), - &root_entities, + class_name, + &candidate_space_path.clone(), + &entities, )) - } else { - None } - }), - ) + }) + }) .collect() } @@ -112,12 +106,14 @@ fn is_interesting_space_view_at_root( fn is_interesting_space_view_not_at_root( store: &re_arrow_store::DataStore, candidate: &SpaceViewBlueprint, - categories_with_interesting_roots: &ViewCategorySet, + classes_with_interesting_roots: &[SpaceViewClassName], query: &LatestAtQuery, ) -> bool { + // TODO(andreas): Can we express this with [`AutoSpawnHeuristic`] instead? + // Consider children of the root interesting, *unless* a root with the same category was already considered interesting! if candidate.space_origin.len() == 1 - && !categories_with_interesting_roots.contains(candidate.category) + && !classes_with_interesting_roots.contains(candidate.class_name()) { return true; } @@ -126,7 +122,7 @@ fn is_interesting_space_view_not_at_root( // -> If there is .. // .. a disconnect transform, the children can't be shown otherwise // .. an pinhole transform, we'd like to see the world from this camera's pov as well! - if candidate.category == ViewCategory::Spatial + if is_spatial_class(candidate.class_name()) && (store .query_latest_component::(&candidate.space_origin, query) .is_some() @@ -145,65 +141,76 @@ fn is_interesting_space_view_not_at_root( pub fn default_created_space_views( ctx: &ViewerContext<'_>, spaces_info: &SpaceInfoCollection, -) -> Vec { - let candidates = all_possible_space_views(ctx, spaces_info); - default_created_space_views_from_candidates(&ctx.store_db.entity_db.data_store, candidates) -} - -fn default_created_space_views_from_candidates( - store: &re_arrow_store::DataStore, - candidates: Vec, ) -> Vec { re_tracing::profile_function!(); + let store = ctx.store_db.store(); + let candidates = all_possible_space_views(ctx, spaces_info) + .into_iter() + .map(|c| { + ( + c.class(ctx.space_view_class_registry).auto_spawn_heuristic( + ctx, + &c.space_origin, + c.data_blueprint.entity_paths(), + ), + c, + ) + }) + .collect::>(); + // All queries are "right most" on the log timeline. let query = LatestAtQuery::latest(Timeline::log_time()); // First pass to look for interesting roots, as their existence influences the heuristic for non-roots! - let categories_with_interesting_roots = candidates + let classes_with_interesting_roots = candidates .iter() - .filter_map(|space_view_candidate| { + .filter_map(|(_, space_view_candidate)| { (space_view_candidate.space_origin.is_root() && is_interesting_space_view_at_root(store, space_view_candidate, &query)) - .then_some(space_view_candidate.category) + .then_some(*space_view_candidate.class_name()) }) - .collect::(); + .collect::>(); - let mut space_views = Vec::new(); + let mut space_views: Vec<(SpaceViewBlueprint, AutoSpawnHeuristic)> = Vec::new(); // Main pass through all candidates. // We first check if a candidate is "interesting" and then split it up/modify it further if required. - for candidate in candidates { - if candidate.space_origin.is_root() { - if !categories_with_interesting_roots.contains(candidate.category) { + for (spawn_heuristic, candidate) in candidates { + if spawn_heuristic == AutoSpawnHeuristic::NeverSpawn { + continue; + } + if spawn_heuristic != AutoSpawnHeuristic::AlwaysSpawn { + if candidate.space_origin.is_root() { + if !classes_with_interesting_roots.contains(candidate.class_name()) { + continue; + } + } else if !is_interesting_space_view_not_at_root( + store, + &candidate, + &classes_with_interesting_roots, + &query, + ) { continue; } - } else if !is_interesting_space_view_not_at_root( - store, - &candidate, - &categories_with_interesting_roots, - &query, - ) { - continue; } // For tensors create one space view for each tensor (even though we're able to stack them in one view) - if candidate.category == ViewCategory::Tensor { + if is_tensor_class(candidate.class_name()) { for entity_path in candidate.data_blueprint.entity_paths() { let mut space_view = SpaceViewBlueprint::new( - class_name_from_category(ViewCategory::Tensor), - ViewCategory::Tensor, + *candidate.class_name(), entity_path, &[entity_path.clone()], ); space_view.entities_determined_by_user = true; // Suppress auto adding of entities. - space_views.push(space_view); + space_views.push((space_view, AutoSpawnHeuristic::AlwaysSpawn)); } continue; } // Spatial views with images get extra treatment as well. - if candidate.category == ViewCategory::Spatial { + if is_spatial_class(candidate.class_name()) { #[derive(Hash, PartialEq, Eq)] enum ImageBucketing { BySize((u64, u64)), @@ -239,7 +246,7 @@ fn default_created_space_views_from_candidates( if images_by_bucket.len() > 1 { // If all images end up in the same bucket, proceed as normal. Otherwise stack images as instructed. for bucket in images_by_bucket.keys() { - // Ignore every image from antoher bucket. Keep all other entities. + // Ignore every image from another bucket. Keep all other entities. let images_of_different_size = images_by_bucket .iter() .filter_map(|(other_bucket, images)| { @@ -258,86 +265,92 @@ fn default_created_space_views_from_candidates( let mut space_view = SpaceViewBlueprint::new( *candidate.class_name(), - candidate.category, &candidate.space_origin, &entities, ); space_view.entities_determined_by_user = true; // Suppress auto adding of entities. - space_views.push(space_view); + space_views.push((space_view, AutoSpawnHeuristic::AlwaysSpawn)); } continue; } } - // Take the candidate as is. - space_views.push(candidate); - } - - space_views -} + // TODO(andreas): Interaction of [`AutoSpawnHeuristic`] with above hardcoded heuristics is a bit wonky. + + // `AutoSpawnHeuristic::SpawnClassWithHighestScoreForRoot` means we're competing with other candidates for the same root. + if let AutoSpawnHeuristic::SpawnClassWithHighestScoreForRoot(score) = spawn_heuristic { + let mut should_spawn_new = true; + for (prev_candidate, prev_spawn_heuristic) in &mut space_views { + if prev_candidate.space_origin == candidate.space_origin { + #[allow(clippy::match_same_arms)] + match prev_spawn_heuristic { + AutoSpawnHeuristic::SpawnClassWithHighestScoreForRoot(prev_score) => { + // If we're competing with a candidate for the same root, we either replace a lower score, or we yield. + should_spawn_new = false; + if *prev_score < score { + // Replace the previous candidate with this one. + *prev_candidate = candidate.clone(); + *prev_spawn_heuristic = spawn_heuristic; + } else { + // We have a lower score, so we don't spawn. + break; + } + } + AutoSpawnHeuristic::AlwaysSpawn => { + // We can live side by side with always-spawn candidates. + } + AutoSpawnHeuristic::NeverSpawn => { + // Never spawn candidates should not be in the list, this is weird! + // But let's not fail on this since our heuristics are not perfect anyways. + } + } + } + } -fn has_any_component_except( - entity_path: &EntityPath, - data_store: &DataStore, - timeline: Timeline, - excluded_components: &[ComponentName], -) -> bool { - data_store - .all_components(&timeline, entity_path) - .map_or(false, |all_components| { - all_components - .iter() - .any(|comp| !excluded_components.contains(comp)) - }) -} + if should_spawn_new { + space_views.push((candidate, spawn_heuristic)); + } + } else { + space_views.push((candidate, spawn_heuristic)); + } + } -/// Whether an entity should be added to a space view at a given path (independent of its category!) -fn is_default_added_to_space_view( - entity_path: &EntityPath, - space_path: &EntityPath, - data_store: &DataStore, - timeline: Timeline, -) -> bool { - let ignored_components = [ - re_components::Transform3D::name(), - re_components::ViewCoordinates::name(), - re_components::InstanceKey::name(), - re_components::KeypointId::name(), - DataStore::insert_id_key(), - ]; - - entity_path.is_descendant_of(space_path) - || (entity_path == space_path - && has_any_component_except(entity_path, data_store, timeline, &ignored_components)) + space_views.into_iter().map(|(s, _)| s).collect() } /// List of entities a space view queries by default for a given category. /// -/// These are all entities in the given space which have the requested category and are reachable by a transform. +/// These are all entities which are reachable and +/// match at least one archetypes that is processed by at least one [`re_viewer_context::ViewPartSystem`] +/// of the given [`re_viewer_context::SpaceViewClass`] pub fn default_queried_entities( ctx: &ViewerContext<'_>, + class: &SpaceViewClassName, space_path: &EntityPath, spaces_info: &SpaceInfoCollection, - category: ViewCategory, ) -> Vec { re_tracing::profile_function!(); - let timeline = Timeline::log_time(); - let store_db = &ctx.store_db; - let data_store = &store_db.entity_db.data_store; - let mut entities = Vec::new(); let space_info = spaces_info.get_first_parent_with_info(space_path); + let parts = ctx + .space_view_class_registry + .get_system_registry_or_log_error(class) + .new_part_collection(); + space_info.visit_descendants_with_reachable_transform(spaces_info, &mut |space_info| { entities.extend( space_info .descendants_without_transform .iter() - .filter(|entity_path| { - is_default_added_to_space_view(entity_path, space_path, data_store, timeline) - && categorize_entity_path(timeline, store_db, entity_path) - .contains(category) + .filter(|ent_path| { + (ent_path.is_descendant_of(space_path) || ent_path == &space_path) + && is_entity_processed_by_part_collection( + ctx.store_db.store(), + &parts, + ent_path, + ) }) .cloned(), ); @@ -346,61 +359,34 @@ pub fn default_queried_entities( entities } -/// List of entities a space view queries by default for all possible category. -fn default_queried_entities_by_category( +/// Returns true if an entity is processed by any of the given [`re_viewer_context::ViewPartSystem`]s. +pub fn is_entity_processed_by_class( ctx: &ViewerContext<'_>, - space_path: &EntityPath, - space_info_collection: &SpaceInfoCollection, -) -> BTreeMap> { - re_tracing::profile_function!(); - - let timeline = Timeline::log_time(); - let store_db = &ctx.store_db; - let data_store = &store_db.entity_db.data_store; - - let mut groups: BTreeMap> = BTreeMap::default(); - let space_info = space_info_collection.get_first_parent_with_info(space_path); - - space_info.visit_descendants_with_reachable_transform( - space_info_collection, - &mut |space_info| { - for entity_path in &space_info.descendants_without_transform { - if is_default_added_to_space_view(entity_path, space_path, data_store, timeline) { - for category in categorize_entity_path(timeline, store_db, entity_path) { - groups - .entry(category) - .or_default() - .push(entity_path.clone()); - } - } - } - }, - ); - - groups + class: &SpaceViewClassName, + ent_path: &EntityPath, +) -> bool { + let parts = ctx + .space_view_class_registry + .get_system_registry_or_log_error(class) + .new_part_collection(); + is_entity_processed_by_part_collection(ctx.store_db.store(), &parts, ent_path) } -// TODO(andreas): This is for transitioning to types only. -fn class_name_from_category(category: ViewCategory) -> SpaceViewClassName { - match category { - ViewCategory::Text => "Text", - ViewCategory::TextBox => "Text Box", - ViewCategory::TimeSeries => "Time Series", - ViewCategory::BarChart => "Bar Chart", - ViewCategory::Spatial => "Spatial", - ViewCategory::Tensor => "Tensor", +/// Returns true if an entity is processed by any of the given [`re_viewer_context::ViewPartSystem`]s. +fn is_entity_processed_by_part_collection( + store: &re_arrow_store::DataStore, + parts: &ViewPartCollection, + ent_path: &EntityPath, +) -> bool { + let timeline = Timeline::log_time(); + let components = store + .all_components(&timeline, ent_path) + .unwrap_or_default(); + for part in parts.iter() { + if part.queries_any_components_of(store, ent_path, &components) { + return true; + } } - .into() -} -fn category_from_class_name(name: SpaceViewClassName) -> Option { - match name.as_str() { - "Text" => Some(ViewCategory::Text), - "Text Box" => Some(ViewCategory::TextBox), - "Time Series" => Some(ViewCategory::TimeSeries), - "Bar Chart" => Some(ViewCategory::BarChart), - "Spatial" => Some(ViewCategory::Spatial), - "Tensor" => Some(ViewCategory::Tensor), - _ => None, - } + false } diff --git a/crates/re_viewport/src/view_category.rs b/crates/re_viewport/src/view_category.rs deleted file mode 100644 index 29fe77885631..000000000000 --- a/crates/re_viewport/src/view_category.rs +++ /dev/null @@ -1,91 +0,0 @@ -use re_arrow_store::{LatestAtQuery, TimeInt}; -use re_components::{ - Arrow3D, Box3D, Component as _, LineStrip2D, LineStrip3D, Mesh3D, Pinhole, Point2D, Point3D, - Rect2D, Scalar, Tensor, TextBox, TextEntry, Transform3D, -}; -use re_data_store::{EntityPath, StoreDb, Timeline}; - -#[derive( - Debug, Default, PartialOrd, Ord, enumset::EnumSetType, serde::Deserialize, serde::Serialize, -)] -pub enum ViewCategory { - // Ordered by dimensionality - // - /// Text log view (text over time) - Text, - - /// Single textbox element - TextBox, - - /// Time series plot (scalar over time) - TimeSeries, - - /// Bar-chart plots made from 1D tensor data - BarChart, - - /// 2D or 3D view - #[default] - Spatial, - - /// High-dimensional tensor view - Tensor, -} - -pub type ViewCategorySet = enumset::EnumSet; - -// TODO(cmc): these `categorize_*` functions below are pretty dangerous: make sure you've covered -// all possible `ViewCategory` values, or you're in for a bad time..! - -pub fn categorize_entity_path( - timeline: Timeline, - store_db: &StoreDb, - entity_path: &EntityPath, -) -> ViewCategorySet { - re_tracing::profile_function!(); - - let mut set = ViewCategorySet::default(); - - for component in store_db - .entity_db - .data_store - .all_components(&timeline, entity_path) - .unwrap_or_default() - { - if component == TextEntry::name() { - set.insert(ViewCategory::Text); - } else if component == TextBox::name() { - set.insert(ViewCategory::TextBox); - } else if component == Scalar::name() { - set.insert(ViewCategory::TimeSeries); - } else if component == Point2D::name() - || component == Point3D::name() - || component == Rect2D::name() - || component == Box3D::name() - || component == LineStrip2D::name() - || component == LineStrip3D::name() - || component == Mesh3D::name() - || component == Arrow3D::name() - || component == Transform3D::name() - || component == Pinhole::name() - { - set.insert(ViewCategory::Spatial); - } else if component == Tensor::name() { - let timeline_query = LatestAtQuery::new(timeline, TimeInt::MAX); - - let store = &store_db.entity_db.data_store; - if let Some(tensor) = - store.query_latest_component::(entity_path, &timeline_query) - { - if tensor.is_vector() { - set.insert(ViewCategory::BarChart); - } else if tensor.is_shaped_like_an_image() { - set.insert(ViewCategory::Spatial); - } else { - set.insert(ViewCategory::Tensor); - } - } - } - } - - set -} diff --git a/crates/re_viewport/src/viewport.rs b/crates/re_viewport/src/viewport.rs index 22d9135ff0d1..848ea6cdf983 100644 --- a/crates/re_viewport/src/viewport.rs +++ b/crates/re_viewport/src/viewport.rs @@ -283,12 +283,6 @@ impl<'a, 'b> egui_tiles::Behavior for TabViewer<'a, 'b> { let space_view_id = *space_view_id; let Some(space_view) = self.space_views.get(&space_view_id) else { return; }; - let space_view_state = self.viewport_state.space_view_state_mut( - self.ctx.space_view_class_registry, - space_view_id, - space_view.class_name(), - ); - let num_space_views = tiles.tiles().filter(|tile| tile.is_pane()).count(); ui.add_space(8.0); // margin within the frame @@ -321,7 +315,7 @@ impl<'a, 'b> egui_tiles::Behavior for TabViewer<'a, 'b> { let help_text = space_view .class(self.ctx.space_view_class_registry) - .help_text(self.ctx.re_ui, space_view_state); + .help_text(self.ctx.re_ui); re_ui::help_hover_button(ui).on_hover_text(help_text); } diff --git a/examples/rust/custom_space_view/src/color_coordinates_space_view.rs b/examples/rust/custom_space_view/src/color_coordinates_space_view.rs index 3b1e1e85de62..4a67c9f5b278 100644 --- a/examples/rust/custom_space_view/src/color_coordinates_space_view.rs +++ b/examples/rust/custom_space_view/src/color_coordinates_space_view.rs @@ -77,7 +77,7 @@ impl SpaceViewClass for ColorCoordinatesSpaceView { &re_ui::icons::SPACE_VIEW_SCATTERPLOT } - fn help_text(&self, _re_ui: &re_ui::ReUi, _state: &Self::State) -> egui::WidgetText { + fn help_text(&self, _re_ui: &re_ui::ReUi) -> egui::WidgetText { "A demo space view that shows colors as coordinates on a 2D plane.".into() } diff --git a/rerun_py/src/python_bridge.rs b/rerun_py/src/python_bridge.rs index d8e4b5d0f26c..b5feb0036a1e 100644 --- a/rerun_py/src/python_bridge.rs +++ b/rerun_py/src/python_bridge.rs @@ -15,7 +15,7 @@ use re_viewer::blueprint_components::panel::PanelState; use re_viewer_context::SpaceViewId; use re_viewport::{ blueprint_components::{AutoSpaceViews, SpaceViewComponent, VIEWPORT_PATH}, - SpaceViewBlueprint, ViewCategory, + SpaceViewBlueprint, }; use re_log_types::{DataRow, StoreKind}; @@ -1167,7 +1167,6 @@ fn add_space_view( let mut space_view = SpaceViewBlueprint::new( "Spatial".into(), - ViewCategory::Spatial, &origin.into(), &entity_paths .into_iter()