diff --git a/crates/re_space_view/src/lib.rs b/crates/re_space_view/src/lib.rs index f9738bb6ee6e..72988dc77660 100644 --- a/crates/re_space_view/src/lib.rs +++ b/crates/re_space_view/src/lib.rs @@ -8,13 +8,11 @@ mod data_query; mod data_query_blueprint; mod heuristics; mod screenshot; -mod unreachable_transform_reason; pub use data_query::{DataQuery, EntityOverrideContext, PropertyResolver}; pub use data_query_blueprint::DataQueryBlueprint; pub use heuristics::suggest_space_view_for_each_entity; pub use screenshot::ScreenshotMode; -pub use unreachable_transform_reason::UnreachableTransformReason; // ----------- diff --git a/crates/re_space_view/src/unreachable_transform_reason.rs b/crates/re_space_view/src/unreachable_transform_reason.rs deleted file mode 100644 index e44c77ab7278..000000000000 --- a/crates/re_space_view/src/unreachable_transform_reason.rs +++ /dev/null @@ -1,36 +0,0 @@ -#[derive(Clone, Copy)] -pub enum UnreachableTransformReason { - /// `SpaceInfoCollection` is outdated and can't find a corresponding space info for the given path. - /// - /// If at all, this should only happen for a single frame until space infos are rebuilt. - UnknownSpaceInfo, - - /// More than one pinhole camera between this and the reference space. - NestedPinholeCameras, - - /// Exiting out of a space with a pinhole camera that doesn't have a resolution is not supported. - InversePinholeCameraWithoutResolution, - - /// Unknown transform between this and the reference space. - DisconnectedSpace, - - /// View coordinates contained an invalid value - InvalidViewCoordinates, -} - -impl std::fmt::Display for UnreachableTransformReason { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(match self { - Self::UnknownSpaceInfo => - "Can't determine transform because internal data structures are not in a valid state. Please file an issue on https://github.com/rerun-io/rerun/", - Self::NestedPinholeCameras => - "Can't display entities under nested pinhole cameras.", - Self::DisconnectedSpace => - "Can't display entities that are in an explicitly disconnected space.", - Self::InversePinholeCameraWithoutResolution => - "Can't display entities that would require inverting a pinhole camera without a specified resolution.", - Self::InvalidViewCoordinates => - "Can't display entities that have invalid view coordinates." - }) - } -} diff --git a/crates/re_space_view_spatial/src/contexts/transform_context.rs b/crates/re_space_view_spatial/src/contexts/transform_context.rs index b11a73933a58..d84766173e95 100644 --- a/crates/re_space_view_spatial/src/contexts/transform_context.rs +++ b/crates/re_space_view_spatial/src/contexts/transform_context.rs @@ -2,7 +2,6 @@ use nohash_hasher::IntMap; use re_data_store::LatestAtQuery; use re_entity_db::{EntityPath, EntityPropertyMap, EntityTree}; -use re_space_view::UnreachableTransformReason; use re_types::{ components::{DisconnectedSpace, PinholeProjection, Transform3D, ViewCoordinates}, ComponentNameSet, Loggable as _, @@ -26,6 +25,15 @@ struct TransformInfo { pub parent_pinhole: Option, } +#[derive(Clone, Copy)] +enum UnreachableTransformReason { + /// More than one pinhole camera between this and the reference space. + NestedPinholeCameras, + + /// Unknown transform between this and the reference space. + DisconnectedSpace, +} + /// Provides transforms from an entity to a chosen reference space for all elements in the scene /// for the currently selected time & timeline. /// @@ -285,14 +293,6 @@ impl TransformContext { .get(ent_path) .and_then(|i| i.parent_pinhole.as_ref()) } - - // This method isn't currently implemented, but we might need it in the future. - // All the necessary data on why a subtree isn't reachable is already stored. - // - // Returns why (if actually) a path isn't reachable. - // pub fn unreachable_reason(&self, _entity_path: &EntityPath) -> Option { - // None - // } } fn transform_at( diff --git a/crates/re_viewer/src/app_state.rs b/crates/re_viewer/src/app_state.rs index 46367cb2eb21..04f5f057ecc6 100644 --- a/crates/re_viewer/src/app_state.rs +++ b/crates/re_viewer/src/app_state.rs @@ -10,10 +10,7 @@ use re_viewer_context::{ ComponentUiRegistry, PlayState, RecordingConfig, SpaceViewClassRegistry, StoreContext, SystemCommandSender as _, ViewerContext, }; -use re_viewport::{ - determine_visualizable_entities, SpaceInfoCollection, Viewport, ViewportBlueprint, - ViewportState, -}; +use re_viewport::{determine_visualizable_entities, Viewport, ViewportBlueprint, ViewportState}; use crate::ui::recordings_panel_ui; use crate::{app_blueprint::AppBlueprint, store_hub::StoreHub, ui::blueprint_panel_ui}; @@ -237,8 +234,6 @@ impl AppState { // First update the viewport and thus all active space views. // This may update their heuristics, so that all panels that are shown in this frame, // have the latest information. - let spaces_info = SpaceInfoCollection::new(ctx.entity_db); - viewport.on_frame_start(&ctx); { @@ -290,7 +285,6 @@ impl AppState { &ctx, ui, &mut viewport, - &spaces_info, app_blueprint.selection_panel_expanded, ); @@ -331,7 +325,7 @@ impl AppState { ui.add_space(4.0); } - blueprint_panel_ui(&mut viewport, &ctx, ui, &spaces_info); + blueprint_panel_ui(&mut viewport, &ctx, ui); }, ); diff --git a/crates/re_viewer/src/ui/blueprint_panel.rs b/crates/re_viewer/src/ui/blueprint_panel.rs index 9538c93290c3..38e1ba0cf23a 100644 --- a/crates/re_viewer/src/ui/blueprint_panel.rs +++ b/crates/re_viewer/src/ui/blueprint_panel.rs @@ -1,12 +1,11 @@ use re_viewer_context::{SystemCommandSender as _, ViewerContext}; -use re_viewport::{SpaceInfoCollection, Viewport}; +use re_viewport::Viewport; /// Show the Blueprint section of the left panel based on the current [`Viewport`] pub fn blueprint_panel_ui( viewport: &mut Viewport<'_, '_>, ctx: &ViewerContext<'_>, ui: &mut egui::Ui, - spaces_info: &SpaceInfoCollection, ) { ctx.re_ui.panel_content(ui, |_, ui| { ctx.re_ui.panel_title_bar_with_buttons( @@ -14,7 +13,7 @@ pub fn blueprint_panel_ui( "Blueprint", Some("The Blueprint is where you can configure the Rerun Viewer"), |ui| { - viewport.add_new_spaceview_button_ui(ctx, ui, spaces_info); + viewport.add_new_spaceview_button_ui(ctx, ui); reset_blueprint_button_ui(ctx, ui); }, ); diff --git a/crates/re_viewer/src/ui/selection_panel.rs b/crates/re_viewer/src/ui/selection_panel.rs index 0fb59459e476..5c8f60f07fd1 100644 --- a/crates/re_viewer/src/ui/selection_panel.rs +++ b/crates/re_viewer/src/ui/selection_panel.rs @@ -20,7 +20,7 @@ use re_viewer_context::{ }; use re_viewport::{ external::re_space_view::blueprint::components::QueryExpressions, icon_for_container_kind, - Contents, SpaceInfoCollection, Viewport, ViewportBlueprint, + Contents, Viewport, ViewportBlueprint, }; use crate::ui::add_space_view_or_container_modal::AddSpaceViewOrContainerModal; @@ -71,7 +71,6 @@ impl SelectionPanel { ctx: &ViewerContext<'_>, ui: &mut egui::Ui, viewport: &mut Viewport<'_, '_>, - spaces_info: &SpaceInfoCollection, expanded: bool, ) { let screen_width = ui.ctx().screen_rect().width(); @@ -121,7 +120,7 @@ impl SelectionPanel { .show(ui, |ui| { ui.add_space(ui.spacing().item_spacing.y); ctx.re_ui.panel_content(ui, |_, ui| { - self.contents(ctx, ui, viewport, spaces_info); + self.contents(ctx, ui, viewport); }); }); }); @@ -133,7 +132,6 @@ impl SelectionPanel { ctx: &ViewerContext<'_>, ui: &mut egui::Ui, viewport: &mut Viewport<'_, '_>, - spaces_info: &SpaceInfoCollection, ) { re_tracing::profile_function!(); @@ -167,13 +165,7 @@ impl SelectionPanel { } Item::SpaceView(space_view_id) => { - space_view_top_level_properties( - ui, - ctx, - viewport.blueprint, - spaces_info, - space_view_id, - ); + space_view_top_level_properties(ui, ctx, viewport.blueprint, space_view_id); } _ => {} @@ -522,7 +514,6 @@ fn space_view_top_level_properties( ui: &mut egui::Ui, ctx: &ViewerContext<'_>, viewport: &ViewportBlueprint, - spaces_info: &SpaceInfoCollection, space_view_id: &SpaceViewId, ) { if let Some(space_view) = viewport.space_view(space_view_id) { @@ -546,10 +537,7 @@ fn space_view_top_level_properties( ); super::space_view_space_origin_ui::space_view_space_origin_widget_ui( - ui, - ctx, - spaces_info, - space_view, + ui, ctx, space_view, ); ui.end_row(); diff --git a/crates/re_viewer/src/ui/space_view_space_origin_ui.rs b/crates/re_viewer/src/ui/space_view_space_origin_ui.rs index 4b1d67f2f15a..7174dead8167 100644 --- a/crates/re_viewer/src/ui/space_view_space_origin_ui.rs +++ b/crates/re_viewer/src/ui/space_view_space_origin_ui.rs @@ -3,7 +3,7 @@ use egui::{Key, Ui}; use re_ui::{ReUi, SyntaxHighlighting}; use re_viewer_context::ViewerContext; -use re_viewport::{SpaceInfoCollection, SpaceViewBlueprint}; +use re_viewport::SpaceViewBlueprint; /// State of the space origin widget. #[derive(Default, Clone)] @@ -27,7 +27,6 @@ enum SpaceOriginEditState { pub(crate) fn space_view_space_origin_widget_ui( ui: &mut Ui, ctx: &ViewerContext<'_>, - spaces_info: &SpaceInfoCollection, space_view: &SpaceViewBlueprint, ) { let is_editing_id = ui.make_persistent_id(space_view.id.hash()); @@ -55,7 +54,6 @@ pub(crate) fn space_view_space_origin_widget_ui( let keep_editing = space_view_space_origin_widget_editing_ui( ui, ctx, - spaces_info, origin_string, *entered_editing, space_view, @@ -77,7 +75,6 @@ pub(crate) fn space_view_space_origin_widget_ui( fn space_view_space_origin_widget_editing_ui( ui: &mut Ui, ctx: &ViewerContext<'_>, - spaces_info: &SpaceInfoCollection, space_origin_string: &mut String, entered_editing: bool, space_view: &SpaceViewBlueprint, @@ -92,9 +89,8 @@ fn space_view_space_origin_widget_editing_ui( // All suggestions for this class of space views. // TODO(#4895): we should have/use a much simpler heuristic API to get a list of compatible entity sub-tree let space_view_suggestions = - re_viewport::space_view_heuristics::all_possible_space_views(ctx, spaces_info) + re_viewport::space_view_heuristics::default_created_space_views(ctx) .into_iter() - .map(|(space_view, _)| space_view) .filter(|this_space_view| { this_space_view.class_identifier() == space_view.class_identifier() }) diff --git a/crates/re_viewport/src/lib.rs b/crates/re_viewport/src/lib.rs index b3c4017411fe..20ac8b24e322 100644 --- a/crates/re_viewport/src/lib.rs +++ b/crates/re_viewport/src/lib.rs @@ -6,7 +6,6 @@ pub const VIEWPORT_PATH: &str = "viewport"; mod auto_layout; mod container; -mod space_info; mod space_view; mod space_view_entity_picker; pub mod space_view_heuristics; @@ -24,7 +23,6 @@ mod viewport_blueprint_ui; pub mod blueprint; pub use container::{ContainerBlueprint, Contents}; -pub use space_info::SpaceInfoCollection; pub use space_view::{SpaceViewBlueprint, SpaceViewName}; pub use viewport::{Viewport, ViewportState}; pub use viewport_blueprint::ViewportBlueprint; @@ -41,28 +39,6 @@ use re_viewer_context::{ ApplicableEntities, DynSpaceViewClass, PerVisualizer, VisualizableEntities, }; -/// Utility for querying a pinhole archetype instance. -/// -/// TODO(andreas): It should be possible to convert `re_query::ArchetypeView` to its corresponding Archetype for situations like this. -/// TODO(andreas): This is duplicated into `re_space_view_spatial` -fn query_pinhole( - store: &re_data_store::DataStore, - query: &re_data_store::LatestAtQuery, - entity_path: &re_log_types::EntityPath, -) -> Option { - store - .query_latest_component(entity_path, query) - .map(|image_from_camera| re_types::archetypes::Pinhole { - image_from_camera: image_from_camera.value, - resolution: store - .query_latest_component(entity_path, query) - .map(|c| c.value), - camera_xyz: store - .query_latest_component(entity_path, query) - .map(|c| c.value), - }) -} - /// Determines the set of visible entities for a given space view. // TODO(andreas): This should be part of the SpaceView's (non-blueprint) state. // Updated whenever `applicable_entities_per_visualizer` or the space view blueprint changes. diff --git a/crates/re_viewport/src/space_info.rs b/crates/re_viewport/src/space_info.rs deleted file mode 100644 index 7520f933070c..000000000000 --- a/crates/re_viewport/src/space_info.rs +++ /dev/null @@ -1,319 +0,0 @@ -use nohash_hasher::IntSet; -use re_space_view::UnreachableTransformReason; -use std::collections::BTreeMap; - -use re_data_store::{LatestAtQuery, TimeInt, Timeline}; -use re_entity_db::{EntityDb, EntityPath, EntityTree}; -use re_types::{ - archetypes::Pinhole, - components::{DisconnectedSpace, Transform3D}, -}; - -use crate::query_pinhole; - -/// Transform connecting two space paths. -#[derive(Clone, Debug)] -pub enum SpaceInfoConnection { - Connected { - transform3d: Option, - pinhole: Option, - }, - - /// Explicitly disconnected via a [`DisconnectedSpace`] component. - Disconnected, -} - -/// Information about one "space". -/// -/// This is gathered by analyzing the transform hierarchy of the entities. -/// ⚠️ Transforms used for this are latest known, i.e. the "right most location in the timeline" ⚠️ -/// -/// Expected to be recreated every frame (or whenever new data is available). -#[derive(Debug)] -pub struct SpaceInfo { - pub path: EntityPath, - - /// All paths in this space (including self and children connected by the identity transform). - pub descendants_without_transform: IntSet, - - /// Nearest ancestor to whom we are not connected via an identity transform. - /// The transform is from parent to child, i.e. the *same* as in its [`Self::child_spaces`] array. - parent: Option<(EntityPath, SpaceInfoConnection)>, - - /// Nearest descendants to whom we are not connected with an identity transform. - pub child_spaces: BTreeMap, -} - -impl SpaceInfo { - pub fn new(path: EntityPath) -> Self { - Self { - path, - descendants_without_transform: Default::default(), - parent: Default::default(), - child_spaces: Default::default(), - } - } - - /// Invokes visitor for `self` and all descendants that are reachable with a valid transform recursively. - /// - /// Keep in mind that transforms are the newest on the currently chosen timeline. - pub fn visit_descendants_with_reachable_transform( - &self, - spaces_info: &SpaceInfoCollection, - visitor: &mut impl FnMut(&SpaceInfo), - ) { - fn visit_descendants_with_reachable_transform_recursively( - space_info: &SpaceInfo, - space_info_collection: &SpaceInfoCollection, - encountered_pinhole: bool, - visitor: &mut impl FnMut(&SpaceInfo), - ) { - visitor(space_info); - - for (child_path, connection) in &space_info.child_spaces { - if matches!(connection, &SpaceInfoConnection::Disconnected) { - continue; - } - - let Some(child_space) = space_info_collection.spaces.get(child_path) else { - re_log::warn_once!( - "Child space info {} not part of space info collection", - child_path - ); - continue; - }; - - // don't allow nested pinhole - let has_pinhole = matches!( - connection, - SpaceInfoConnection::Connected { - pinhole: Some(_), - .. - } - ); - if encountered_pinhole && has_pinhole { - continue; - } - - visit_descendants_with_reachable_transform_recursively( - child_space, - space_info_collection, - has_pinhole, - visitor, - ); - } - } - - visit_descendants_with_reachable_transform_recursively(self, spaces_info, false, visitor); - } -} - -/// Information about all spaces. -/// -/// This is gathered by analyzing the transform hierarchy of the entities: -/// For every child of the root there is a space info, as well as the root itself. -/// Each of these we walk down recursively, every time a transform is encountered, we create another space info. -/// -/// Expected to be recreated every frame (or whenever new data is available). -#[derive(Debug, Default)] -pub struct SpaceInfoCollection { - spaces: BTreeMap, -} - -impl SpaceInfoCollection { - /// Do a graph analysis of the transform hierarchy, and create cuts - /// wherever we find a non-identity transform. - pub fn new(entity_db: &EntityDb) -> Self { - re_tracing::profile_function!(); - - fn add_children( - entity_db: &EntityDb, - spaces_info: &mut SpaceInfoCollection, - parent_space: &mut SpaceInfo, - tree: &EntityTree, - query: &LatestAtQuery, - ) { - // Determine how the paths are connected. - let data_store = entity_db.data_store(); - let transform3d = data_store - .query_latest_component::(&tree.path, query) - .map(|c| c.value); - let pinhole = query_pinhole(data_store, query, &tree.path); - - let connection = if transform3d.is_some() || pinhole.is_some() { - Some(SpaceInfoConnection::Connected { - transform3d, - pinhole, - }) - } else if data_store - .query_latest_component::(&tree.path, query) - .map_or(false, |dp| dp.0) - { - Some(SpaceInfoConnection::Disconnected) - } else { - None - }; - - if let Some(connection) = connection { - // A set transform - create a new space. - parent_space - .child_spaces - .insert(tree.path.clone(), connection.clone()); - - let mut child_space_info = SpaceInfo::new(tree.path.clone()); - child_space_info.parent = Some((parent_space.path.clone(), connection)); - child_space_info - .descendants_without_transform - .insert(tree.path.clone()); // spaces includes self - - for child_tree in tree.children.values() { - add_children( - entity_db, - spaces_info, - &mut child_space_info, - child_tree, - query, - ); - } - spaces_info - .spaces - .insert(tree.path.clone(), child_space_info); - } else { - // no transform == implicit identity transform. - parent_space - .descendants_without_transform - .insert(tree.path.clone()); // spaces includes self - - for child_tree in tree.children.values() { - add_children(entity_db, spaces_info, parent_space, child_tree, query); - } - } - } - - // Use "right most"/latest available data. - let timeline = Timeline::log_time(); - let query_time = TimeInt::MAX; - let query = LatestAtQuery::new(timeline, query_time); - - let mut spaces_info = Self::default(); - - // Start at the root. The root is always part of the collection! - if entity_db - .data_store() - .query_latest_component::(&EntityPath::root(), &query) - .is_some() - { - re_log::warn_once!("The root entity has a 'transform' component! This will have no effect. Did you mean to apply the transform elsewhere?"); - } - let mut root_space_info = SpaceInfo::new(EntityPath::root()); - add_children( - entity_db, - &mut spaces_info, - &mut root_space_info, - entity_db.tree(), - &query, - ); - spaces_info - .spaces - .insert(EntityPath::root(), root_space_info); - - spaces_info - } - - pub fn get_first_parent_with_info(&self, path: &EntityPath) -> &SpaceInfo { - let mut path = path.clone(); - loop { - if let Some(space_info) = self.spaces.get(&path) { - return space_info; - } - path = path.parent().expect( - "The root path is part of SpaceInfoCollection, as such it's impossible to not have a space info parent!"); - } - } - - pub fn iter(&self) -> impl Iterator { - self.spaces.values() - } - - /// Answers if an entity path (`from`) is reachable via a transform from some reference space (at `to_reference`) - /// - /// For how, you need to check `TransformCache`! - /// Note that in any individual frame, entities may or may not be reachable. - pub fn is_reachable_by_transform( - &self, - from: &EntityPath, - to_reference: &EntityPath, - ) -> Result<(), UnreachableTransformReason> { - re_tracing::profile_function!(); - - // Get closest space infos for the given entity paths. - let mut from_space = self.get_first_parent_with_info(from); - let mut to_reference_space = self.get_first_parent_with_info(to_reference); - - // Reachability is (mostly) commutative! - // This means we can simply walk from both nodes up until we find a common ancestor! - // If we haven't encountered any obstacles, we're fine! - let mut encountered_pinhole = false; - while from_space.path != to_reference_space.path { - // Decide if we should walk up "from" or "to_reference" - // If "from" is a descendant of "to_reference", we walk up "from" - // Otherwise we walk up on "to_reference". - // - // If neither is a descendant of the other it doesn't matter which one we walk up, since we eventually going to hit common ancestor! - let walk_up_from = from_space.path.is_descendant_of(&to_reference_space.path); - - let parent = if walk_up_from { - &from_space.parent - } else { - &to_reference_space.parent - }; - - if let Some((parent_path, connection)) = parent { - // Matches the connectedness requirements in `inverse_transform_at`/`transform_at` in `transform_cache.rs` - match connection { - SpaceInfoConnection::Disconnected => { - Err(UnreachableTransformReason::DisconnectedSpace) - } - SpaceInfoConnection::Connected { - pinhole: Some(pinhole), - .. - } => { - if encountered_pinhole { - Err(UnreachableTransformReason::NestedPinholeCameras) - } else { - encountered_pinhole = true; - if pinhole.resolution.is_none() && !walk_up_from { - Err(UnreachableTransformReason::InversePinholeCameraWithoutResolution) - } else { - Ok(()) - } - } - } - SpaceInfoConnection::Connected { .. } => Ok(()), - }?; - - let Some(parent_space) = self.spaces.get(parent_path) else { - re_log::warn_once!("{} not part of space infos", parent_path); - return Err(UnreachableTransformReason::UnknownSpaceInfo); - }; - - if walk_up_from { - from_space = parent_space; - } else { - to_reference_space = parent_space; - }; - } else { - re_log::warn_once!( - "No space info connection between {} and {}", - from, - to_reference - ); - return Err(UnreachableTransformReason::UnknownSpaceInfo); - } - } - - Ok(()) - } -} - -// ---------------------------------------------------------------------------- diff --git a/crates/re_viewport/src/space_view_entity_picker.rs b/crates/re_viewport/src/space_view_entity_picker.rs index 822980d3d596..716fa1e5cfba 100644 --- a/crates/re_viewport/src/space_view_entity_picker.rs +++ b/crates/re_viewport/src/space_view_entity_picker.rs @@ -6,10 +6,7 @@ use re_entity_db::{EntityPath, EntityTree, InstancePath}; use re_log_types::{EntityPathFilter, EntityPathRule}; use re_viewer_context::{DataQueryResult, SpaceViewId, ViewerContext}; -use crate::{ - determine_visualizable_entities, space_info::SpaceInfoCollection, - space_view::SpaceViewBlueprint, ViewportBlueprint, -}; +use crate::{determine_visualizable_entities, space_view::SpaceViewBlueprint, ViewportBlueprint}; /// Window for adding/removing entities from a space view. /// @@ -59,13 +56,11 @@ impl SpaceViewEntityPicker { fn add_entities_ui(ctx: &ViewerContext<'_>, ui: &mut egui::Ui, space_view: &SpaceViewBlueprint) { re_tracing::profile_function!(); - let spaces_info = SpaceInfoCollection::new(ctx.entity_db); let tree = &ctx.entity_db.tree(); // TODO(jleibs): Avoid clone let query_result = ctx.lookup_query_result(space_view.query_id()).clone(); let entity_path_filter = space_view.entity_path_filter(); - let entities_add_info = - create_entity_add_info(ctx, tree, space_view, &query_result, &spaces_info); + let entities_add_info = create_entity_add_info(ctx, tree, space_view, &query_result); add_entities_tree_ui( ctx, @@ -317,7 +312,6 @@ fn create_entity_add_info( tree: &EntityTree, space_view: &SpaceViewBlueprint, query_result: &DataQueryResult, - spaces_info: &SpaceInfoCollection, ) -> IntMap { let mut meta_data: IntMap = IntMap::default(); @@ -335,20 +329,14 @@ fn create_entity_add_info( tree.visit_children_recursively(&mut |entity_path, _| { let can_add: CanAddToSpaceView = if visualizable_entities.iter().any(|(_, entities)| entities.contains(entity_path)) { - // TODO(andreas): (topological) reachability should be part of visualizability. - // Yes, this means that once an entity is no longer visualizable (due to pinhole etc.) it stays this way. - match spaces_info.is_reachable_by_transform(entity_path, &space_view.space_origin) { - Ok(()) => CanAddToSpaceView::Compatible { - already_added: query_result.contains_any(entity_path), - }, - Err(reason) => CanAddToSpaceView::No { - reason: reason.to_string(), - }, + CanAddToSpaceView::Compatible { + already_added: query_result.contains_any(entity_path), } } else { + // TODO(#4826): This shouldn't necessarily prevent us from adding it. 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.", + "Entity can't be displayed by any of the available visualizers in this class of Space View ({}).", space_view.class_identifier() ), } diff --git a/crates/re_viewport/src/space_view_heuristics.rs b/crates/re_viewport/src/space_view_heuristics.rs index adcd6efffe07..12e8c8fdfefa 100644 --- a/crates/re_viewport/src/space_view_heuristics.rs +++ b/crates/re_viewport/src/space_view_heuristics.rs @@ -1,95 +1,7 @@ -use itertools::Itertools; +use re_space_view::DataQueryBlueprint; +use re_viewer_context::ViewerContext; -use re_entity_db::EntityPath; -use re_log_types::EntityPathFilter; -use re_space_view::{DataQuery as _, DataQueryBlueprint}; -use re_viewer_context::{DataQueryResult, ViewerContext}; - -use crate::{ - determine_visualizable_entities, space_info::SpaceInfoCollection, - space_view::SpaceViewBlueprint, -}; - -fn candidate_space_view_paths<'a>( - ctx: &ViewerContext<'a>, - spaces_info: &'a SpaceInfoCollection, -) -> impl Iterator { - // Everything with a SpaceInfo is a candidate (that is root + whenever there is a transform), - // as well as all direct descendants of the root. - let root_children = &ctx.entity_db.tree().children; - spaces_info - .iter() - .map(|info| &info.path) - .chain(root_children.values().map(|sub_tree| &sub_tree.path)) - .unique() -} - -/// List out all space views we allow the user to create. -pub fn all_possible_space_views( - ctx: &ViewerContext<'_>, - spaces_info: &SpaceInfoCollection, -) -> Vec<(SpaceViewBlueprint, DataQueryResult)> { - re_tracing::profile_function!(); - - // For each candidate, create space views for all possible classes. - candidate_space_view_paths(ctx, spaces_info) - .flat_map(|candidate_space_path| { - ctx.space_view_class_registry - .iter_registry() - .filter_map(|entry| { - // We only want to run the query if there's at least one applicable entity under `candidate_space_path`. - if !entry.visualizer_system_ids.iter().any(|visualizer| { - let Some(entities) = ctx.applicable_entities_per_visualizer.get(visualizer) - else { - return false; - }; - entities - .iter() - .any(|entity| entity.starts_with(candidate_space_path)) - }) { - return None; - } - - let class_identifier = entry.class.identifier(); - - let mut entity_path_filter = EntityPathFilter::default(); - entity_path_filter.add_subtree(candidate_space_path.clone()); - - let candidate_query = - DataQueryBlueprint::new(class_identifier, entity_path_filter); - - let visualizable_entities = determine_visualizable_entities( - ctx.applicable_entities_per_visualizer, - ctx.entity_db, - &ctx.space_view_class_registry - .new_visualizer_collection(class_identifier), - entry.class.as_ref(), - candidate_space_path, - ); - - let results = candidate_query.execute_query( - ctx.store_context, - &visualizable_entities, - ctx.indicated_entities_per_visualizer, - ); - - if !results.is_empty() { - Some(( - SpaceViewBlueprint::new( - entry.class.identifier(), - candidate_space_path, - candidate_query, - ), - results, - )) - } else { - None - } - }) - .collect_vec() - }) - .collect_vec() -} +use crate::space_view::SpaceViewBlueprint; /// List out all space views we generate by default for the available data. /// diff --git a/crates/re_viewport/src/viewport_blueprint_ui.rs b/crates/re_viewport/src/viewport_blueprint_ui.rs index cad80df88867..2b1b01bbeb0d 100644 --- a/crates/re_viewport/src/viewport_blueprint_ui.rs +++ b/crates/re_viewport/src/viewport_blueprint_ui.rs @@ -11,9 +11,8 @@ use re_viewer_context::{ ContainerId, DataQueryResult, DataResultNode, HoverHighlight, Item, SpaceViewId, ViewerContext, }; -use crate::container::Contents; use crate::{ - space_view_heuristics::all_possible_space_views, SpaceInfoCollection, SpaceViewBlueprint, + container::Contents, space_view_heuristics::default_created_space_views, SpaceViewBlueprint, Viewport, }; @@ -407,12 +406,7 @@ impl Viewport<'_, '_> { } } - pub fn add_new_spaceview_button_ui( - &mut self, - ctx: &ViewerContext<'_>, - ui: &mut egui::Ui, - spaces_info: &SpaceInfoCollection, - ) { + pub fn add_new_spaceview_button_ui(&mut self, ctx: &ViewerContext<'_>, ui: &mut egui::Ui) { ui.menu_image_button( re_ui::icons::ADD .as_image() @@ -458,12 +452,11 @@ impl Viewport<'_, '_> { }; // Space view options proposed by heuristics - let mut possible_space_views = all_possible_space_views(ctx, spaces_info); - possible_space_views - .sort_by_key(|(space_view, _)| space_view.space_origin.to_string()); + let mut possible_space_views = default_created_space_views(ctx); + possible_space_views.sort_by_key(|space_view| space_view.space_origin.to_string()); let has_possible_space_views = !possible_space_views.is_empty(); - for (space_view, _) in possible_space_views { + for space_view in possible_space_views { add_space_view_item(ui, space_view, false); }