From 5f03f15a03ed5d9ce2627d0f9ccee6f08ef55a05 Mon Sep 17 00:00:00 2001 From: Andreas Reich Date: Mon, 2 Mar 2026 12:17:39 +0100 Subject: [PATCH 001/513] Visibility control from scalar visualizer list ### Related * Part of RR-3672 ### What Figma design by @gavrelina : https://www.figma.com/design/eGATW7RubxdRrcEP9ITiVh/Any-scalars?node-id=956-7914&t=f0y2EU4wIAz2PYVt-1 My humble impl: https://github.com/user-attachments/assets/2e8fb01d-f93b-4ca2-92de-dd8ec1285685 https://github.com/user-attachments/assets/591b1753-3625-442b-b51c-45c54d3f9d1f Note that this changes the `invisible` & `visible` icon everywhere (the change on `visible` is insanely subtle). Shuffled a bit of code around which makes this change bigger than it is. Took me unfortunately a few iterations to get everything right, so a bit hard to untangle from that. Sorry! Updated the thorough integration tests (kudos to @aedm !) to document/pin the new behavior. Source-Ref: 5dddc829f88e65375eac8eb21a3b30442443c8f5 --- .../re_selection_panel/src/visualizer_ui.rs | 2 +- crates/viewer/re_ui/data/dark_theme.ron | 8 +- crates/viewer/re_ui/data/icons/invisible.svg | 11 +- crates/viewer/re_ui/data/icons/visible.svg | 2 +- crates/viewer/re_ui/data/light_theme.ron | 12 +- crates/viewer/re_ui/src/design_tokens.rs | 20 +- .../re_view/src/blueprint_resolved_results.rs | 31 +- crates/viewer/re_view/src/query.rs | 15 +- crates/viewer/re_view/src/visualizer_query.rs | 14 +- crates/viewer/re_view_time_series/src/lib.rs | 1 + .../src/line_visualizer_system.rs | 3 - .../src/point_visualizer_system.rs | 3 - .../re_view_time_series/src/series_query.rs | 21 +- .../re_view_time_series/src/view_class.rs | 360 +----------------- .../re_view_time_series/src/visualizer_ui.rs | 348 +++++++++++++++++ .../src/blueprint_helpers.rs | 55 +-- .../re_viewer_context/src/view/view_query.rs | 22 +- .../add_entity_to_view_bar_chart_2.png | 4 +- .../drag_view_to_other_view_left_1.png | 4 +- .../drag_view_to_other_view_left_2.png | 4 +- .../drag_view_to_other_view_right_1.png | 4 +- .../drag_view_to_other_view_right_2.png | 4 +- .../drag_view_to_other_view_top_1.png | 4 +- .../drag_view_to_other_view_top_2.png | 4 +- .../drop_multiple_streams_to_view_1.png | 4 +- .../drop_multiple_streams_to_view_2.png | 4 +- .../drop_multiple_streams_to_view_3.png | 4 +- .../drop_multiple_streams_to_view_4.png | 4 +- .../drop_multiple_streams_to_view_5.png | 4 +- .../drop_multiple_streams_to_view_6.png | 4 +- .../tests/snapshots/drop_stream_to_view_1.png | 4 +- .../tests/snapshots/drop_stream_to_view_2.png | 4 +- .../tests/snapshots/drop_stream_to_view_3.png | 4 +- .../tests/snapshots/drop_stream_to_view_4.png | 4 +- .../tests/snapshots/drop_stream_to_view_5.png | 4 +- .../tests/snapshots/drop_stream_to_view_6.png | 4 +- .../snapshots/view_visualizers_1_initial.png | 4 +- ...view_visualizers_2_plots_view_selected.png | 4 +- ...view_visualizers_3_other_view_selected.png | 4 +- .../view_visualizers_4_after_remove_first.png | 3 - .../view_visualizers_4_hover_sin_line.png | 3 + .../view_visualizers_5_after_hide.png | 3 + ...view_visualizers_5_after_remove_second.png | 3 - ...sualizers_6_hover_different_after_hide.png | 3 + .../view_visualizers_add_1_view_selected.png | 4 +- .../view_visualizers_add_2_popup_open.png | 4 +- ...iew_visualizers_add_3_visualizer_added.png | 3 - ...visualizers_add_multi_1_starting_state.png | 3 + ..._visualizers_add_multi_1_view_selected.png | 3 - ...iew_visualizers_add_multi_2_popup_open.png | 3 - ...lizers_add_multi_2_selected_visualizer.png | 3 + ...ew_visualizers_add_multi_3_first_added.png | 3 - ...alizers_add_multi_3_removed_visualizer.png | 3 + ...w_visualizers_add_multi_4_second_added.png | 3 - ...lizers_add_multi_4_view_selected_again.png | 3 + ...iew_visualizers_add_multi_5_popup_open.png | 3 + ...ew_visualizers_add_multi_6_first_added.png | 3 + ...w_visualizers_add_multi_7_second_added.png | 3 + ...visualizers_multi_scalar_view_selected.png | 4 +- .../tests/view_visualizers_test.rs | 91 +++-- 60 files changed, 619 insertions(+), 551 deletions(-) create mode 100644 crates/viewer/re_view_time_series/src/visualizer_ui.rs delete mode 100644 tests/rust/re_integration_test/tests/snapshots/view_visualizers_4_after_remove_first.png create mode 100644 tests/rust/re_integration_test/tests/snapshots/view_visualizers_4_hover_sin_line.png create mode 100644 tests/rust/re_integration_test/tests/snapshots/view_visualizers_5_after_hide.png delete mode 100644 tests/rust/re_integration_test/tests/snapshots/view_visualizers_5_after_remove_second.png create mode 100644 tests/rust/re_integration_test/tests/snapshots/view_visualizers_6_hover_different_after_hide.png delete mode 100644 tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_3_visualizer_added.png create mode 100644 tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_1_starting_state.png delete mode 100644 tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_1_view_selected.png delete mode 100644 tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_2_popup_open.png create mode 100644 tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_2_selected_visualizer.png delete mode 100644 tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_3_first_added.png create mode 100644 tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_3_removed_visualizer.png delete mode 100644 tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_4_second_added.png create mode 100644 tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_4_view_selected_again.png create mode 100644 tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_5_popup_open.png create mode 100644 tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_6_first_added.png create mode 100644 tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_7_second_added.png diff --git a/crates/viewer/re_selection_panel/src/visualizer_ui.rs b/crates/viewer/re_selection_panel/src/visualizer_ui.rs index 02823576aa0e..2816055317a1 100644 --- a/crates/viewer/re_selection_panel/src/visualizer_ui.rs +++ b/crates/viewer/re_selection_panel/src/visualizer_ui.rs @@ -112,7 +112,7 @@ pub fn visualizer_ui_impl( let override_base_path = data_result.override_base_path(); let remove_visualizer_button = |ui: &mut egui::Ui, visualizer_id: &VisualizerInstructionId| { - let response = ui.small_icon_button(&re_ui::icons::CLOSE, "Close"); + let response = ui.small_icon_button(&re_ui::icons::CLOSE, "Remove visualizer"); if response.clicked() { let active_visualizers = active_visualizers .iter() diff --git a/crates/viewer/re_ui/data/dark_theme.ron b/crates/viewer/re_ui/data/dark_theme.ron index 41397fd1575d..f8d4ccf75932 100644 --- a/crates/viewer/re_ui/data/dark_theme.ron +++ b/crates/viewer/re_ui/data/dark_theme.ron @@ -307,14 +307,20 @@ "width": 1, // 51/255 ≈ 20% "alpha": 51 - }, + }, "visualizer_list_title_text_color": { "color": "{Gray.1000}" }, + "visualizer_list_title_text_invisible_color": { + "color": "{Gray.550}" + }, "visualizer_list_path_text_color": { "color": "{Gray.550}" }, + "visualizer_list_path_text_invisible_color": { + "color": "{Gray.400}" + }, "visualizer_list_pill_bg_color": { "color": "{Gray.200}" }, diff --git a/crates/viewer/re_ui/data/icons/invisible.svg b/crates/viewer/re_ui/data/icons/invisible.svg index dfa0035fdb9b..f7d9bdd9ccf6 100644 --- a/crates/viewer/re_ui/data/icons/invisible.svg +++ b/crates/viewer/re_ui/data/icons/invisible.svg @@ -1,10 +1,3 @@ - - - - - - - - - + + diff --git a/crates/viewer/re_ui/data/icons/visible.svg b/crates/viewer/re_ui/data/icons/visible.svg index bf42ef0e1584..45ca47131f67 100644 --- a/crates/viewer/re_ui/data/icons/visible.svg +++ b/crates/viewer/re_ui/data/icons/visible.svg @@ -1,3 +1,3 @@ - + diff --git a/crates/viewer/re_ui/data/light_theme.ron b/crates/viewer/re_ui/data/light_theme.ron index 36218a1eebbd..06cfa9be4582 100644 --- a/crates/viewer/re_ui/data/light_theme.ron +++ b/crates/viewer/re_ui/data/light_theme.ron @@ -309,16 +309,22 @@ }, "visualizer_list_title_text_color": { - "color": "{Gray.0}" + "color": "{Gray.100}" + }, + "visualizer_list_title_text_invisible_color": { + "color": "{Gray.500}" }, "visualizer_list_path_text_color": { "color": "{Gray.500}" }, + "visualizer_list_path_text_invisible_color": { + "color": "{Gray.700}" + }, "visualizer_list_pill_bg_color": { - "color": "{Gray.850}" + "color": "{Gray.900}" }, "visualizer_list_pill_bg_color_hovered": { - "color": "{Gray.800}" + "color": "{Gray.850}" }, "code_index_color": { "color": "#894783" diff --git a/crates/viewer/re_ui/src/design_tokens.rs b/crates/viewer/re_ui/src/design_tokens.rs index 5de1359e8530..b30b50710859 100644 --- a/crates/viewer/re_ui/src/design_tokens.rs +++ b/crates/viewer/re_ui/src/design_tokens.rs @@ -224,12 +224,15 @@ pub struct DesignTokens { pub list_item_active_bg: Color32, pub list_item_collapse_default: Color32, - // Visualizer list (selection panel) - pub visualizer_list_title_text_color: Color32, - pub visualizer_list_path_text_color: Color32, pub color_swatch_size: f32, pub color_swatch_interactive_stroke: Stroke, pub color_swatch_noninteractive_stroke: Stroke, + + // Visualizer list (selection panel) + pub visualizer_list_title_text_color: Color32, + pub visualizer_list_title_text_invisible_color: Color32, + pub visualizer_list_path_text_color: Color32, + pub visualizer_list_path_text_invisible_color: Color32, pub visualizer_list_pill_bg_color: Color32, pub visualizer_list_pill_bg_color_hovered: Color32, @@ -400,11 +403,18 @@ impl DesignTokens { list_item_active_bg: get_color("list_item_active_bg"), list_item_collapse_default: get_color("list_item_collapse_default"), - visualizer_list_title_text_color: get_color("visualizer_list_title_text_color"), - visualizer_list_path_text_color: get_color("visualizer_list_path_text_color"), color_swatch_size: get_scalar("color_swatch_size")?, color_swatch_interactive_stroke: get_stroke("color_swatch_interactive_stroke"), color_swatch_noninteractive_stroke: get_stroke("color_swatch_noninteractive_stroke"), + + visualizer_list_title_text_color: get_color("visualizer_list_title_text_color"), + visualizer_list_title_text_invisible_color: get_color( + "visualizer_list_title_text_invisible_color", + ), + visualizer_list_path_text_color: get_color("visualizer_list_path_text_color"), + visualizer_list_path_text_invisible_color: get_color( + "visualizer_list_path_text_invisible_color", + ), visualizer_list_pill_bg_color: get_color("visualizer_list_pill_bg_color"), visualizer_list_pill_bg_color_hovered: get_color( "visualizer_list_pill_bg_color_hovered", diff --git a/crates/viewer/re_view/src/blueprint_resolved_results.rs b/crates/viewer/re_view/src/blueprint_resolved_results.rs index 61dcd3c83782..8ba8c46546ec 100644 --- a/crates/viewer/re_view/src/blueprint_resolved_results.rs +++ b/crates/viewer/re_view/src/blueprint_resolved_results.rs @@ -7,8 +7,8 @@ use re_chunk_store::external::re_chunk::external::arrow::array::ArrayRef; use re_chunk_store::{LatestAtQuery, RangeQuery, UnitChunkShared}; use re_log_types::hash::Hash64; use re_query::{LatestAtResults, RangeResults}; +use re_sdk_types::ComponentIdentifier; use re_sdk_types::blueprint::datatypes::ComponentSourceKind; -use re_sdk_types::{ComponentIdentifier, blueprint::components::VisualizerInstructionId}; use re_viewer_context::{QueryContext, typed_fallback_for}; use crate::{ @@ -103,12 +103,12 @@ impl<'a> BlueprintResolvedLatestAtResults<'a> { /// /// Although overrides are never temporal, when accessed via the [`crate::BlueprintResolvedResultsExt`] trait /// they will be merged into the results appropriately. -#[derive(Debug)] pub struct BlueprintResolvedRangeResults<'a> { pub(crate) overrides: LatestAtResults, pub(crate) store_results: RangeResults, pub(crate) view_defaults: &'a LatestAtResults, - pub(crate) _instruction_id: VisualizerInstructionId, + + pub(crate) query_context: QueryContext<'a>, pub(crate) component_sources: IntMap>, @@ -198,6 +198,16 @@ impl BlueprintResolvedRangeResults<'_> { .insert(0, chunk); } } + + /// Returns the [`QueryContext`] for this result. + pub fn query_context(&self) -> &QueryContext<'_> { + &self.query_context + } + + /// Returns the target entity path for this result. + pub fn entity_path(&self) -> &re_log_types::EntityPath { + self.query_context.target_entity_path + } } impl BlueprintResolvedLatestAtResults<'_> { @@ -376,6 +386,21 @@ impl BlueprintResolvedResults<'_> { } } } + + /// Returns the [`QueryContext`] for this result. + #[inline] + pub fn query_context(&self) -> &QueryContext<'_> { + match self { + Self::LatestAt(_, results) => &results.query_context, + Self::Range(_, results) => &results.query_context, + } + } + + /// Returns the target entity path for this result. + #[inline] + pub fn entity_path(&self) -> &re_log_types::EntityPath { + self.query_context().target_entity_path + } } // --- diff --git a/crates/viewer/re_view/src/query.rs b/crates/viewer/re_view/src/query.rs index ff3962dea383..2648dcbb38d0 100644 --- a/crates/viewer/re_view/src/query.rs +++ b/crates/viewer/re_view/src/query.rs @@ -159,10 +159,10 @@ fn component_not_found_error( /// Data should be accessed via the [`crate::BlueprintResolvedResultsExt`] trait which is implemented for /// [`crate::BlueprintResolvedResults`]. pub fn range_with_blueprint_resolved_data<'a>( - ctx: &ViewContext<'a>, + ctx: &'a ViewContext<'a>, _annotations: Option<&re_viewer_context::Annotations>, range_query: &RangeQuery, - data_result: &re_viewer_context::DataResult, + data_result: &'a re_viewer_context::DataResult, components: impl IntoIterator, visualizer_instruction: &re_viewer_context::VisualizerInstruction, ) -> BlueprintResolvedRangeResults<'a> { @@ -292,10 +292,19 @@ pub fn range_with_blueprint_resolved_data<'a>( } } + let query_context = QueryContext { + view_ctx: ctx, + target_entity_path: &data_result.entity_path, + instruction_id: Some(visualizer_instruction.id), + archetype_name: None, + // TODO(andreas): Rather strange to a have a latest-at query in here. + query: LatestAtQuery::new(range_query.timeline, range_query.range.min), + }; + BlueprintResolvedRangeResults { overrides, store_results, - _instruction_id: visualizer_instruction.id, + query_context, view_defaults: &ctx.query_result.view_defaults, component_sources, component_mappings_hash: Hash64::hash(&active_remappings), diff --git a/crates/viewer/re_view/src/visualizer_query.rs b/crates/viewer/re_view/src/visualizer_query.rs index f53d299396ff..eb1721cc3e65 100644 --- a/crates/viewer/re_view/src/visualizer_query.rs +++ b/crates/viewer/re_view/src/visualizer_query.rs @@ -1,7 +1,7 @@ use re_log_types::hash::Hash64; use re_sdk_types::blueprint::components::VisualizerInstructionId; use re_viewer_context::{ - VisualizerInstructionReport, VisualizerReportContext, VisualizerReportSeverity, + QueryContext, VisualizerInstructionReport, VisualizerReportContext, VisualizerReportSeverity, }; use crate::{ @@ -157,4 +157,16 @@ impl<'a> VisualizerInstructionQueryResults<'a> { }, ); } + + /// Returns the [`QueryContext`] for this result. + #[inline] + pub fn query_context(&self) -> &QueryContext<'_> { + self.query_results.query_context() + } + + /// Returns the target entity path for this result. + #[inline] + pub fn entity_path(&self) -> &re_log_types::EntityPath { + self.query_results.entity_path() + } } diff --git a/crates/viewer/re_view_time_series/src/lib.rs b/crates/viewer/re_view_time_series/src/lib.rs index 1826f8d49a74..a18124034d27 100644 --- a/crates/viewer/re_view_time_series/src/lib.rs +++ b/crates/viewer/re_view_time_series/src/lib.rs @@ -12,6 +12,7 @@ mod point_visualizer_system; mod series_query; mod util; mod view_class; +mod visualizer_ui; use re_sdk_types::{ blueprint::components::VisualizerInstructionId, diff --git a/crates/viewer/re_view_time_series/src/line_visualizer_system.rs b/crates/viewer/re_view_time_series/src/line_visualizer_system.rs index 7fa14923a75d..10f95a07986a 100644 --- a/crates/viewer/re_view_time_series/src/line_visualizer_system.rs +++ b/crates/viewer/re_view_time_series/src/line_visualizer_system.rs @@ -196,7 +196,6 @@ impl SeriesLinesSystem { collect_scalars(all_scalar_chunks, &mut points_per_series); collect_colors( - &query_ctx, &query, &results, all_scalar_chunks, @@ -273,13 +272,11 @@ impl SeriesLinesSystem { } let series_visibility = collect_series_visibility( - &query_ctx, &results, num_series, &archetypes::SeriesLines::descriptor_visible_series(), ); let series_names = collect_series_name( - &query_ctx, &results, num_series, &archetypes::SeriesLines::descriptor_names(), diff --git a/crates/viewer/re_view_time_series/src/point_visualizer_system.rs b/crates/viewer/re_view_time_series/src/point_visualizer_system.rs index 2588f70eff97..cd34b27d3e16 100644 --- a/crates/viewer/re_view_time_series/src/point_visualizer_system.rs +++ b/crates/viewer/re_view_time_series/src/point_visualizer_system.rs @@ -181,7 +181,6 @@ impl SeriesPointsSystem { collect_scalars(all_scalar_chunks, &mut points_per_series); collect_colors( - &query_ctx, &query, &results, all_scalar_chunks, @@ -297,13 +296,11 @@ impl SeriesPointsSystem { } let series_visibility = collect_series_visibility( - &query_ctx, &results, num_series, &archetypes::SeriesPoints::descriptor_visible_series(), ); let series_names = collect_series_name( - &query_ctx, &results, num_series, &archetypes::SeriesPoints::descriptor_names(), diff --git a/crates/viewer/re_view_time_series/src/series_query.rs b/crates/viewer/re_view_time_series/src/series_query.rs index e6849cd17095..8f52d5adb068 100644 --- a/crates/viewer/re_view_time_series/src/series_query.rs +++ b/crates/viewer/re_view_time_series/src/series_query.rs @@ -4,12 +4,13 @@ use itertools::Itertools as _; use re_chunk_store::RangeQuery; use re_log_types::TimeInt; -use re_log_types::external::arrow::array::{self, BooleanArray}; +use re_log_types::external::arrow::array::AsArray as _; use re_log_types::external::arrow::buffer::BooleanBuffer; +use re_log_types::external::arrow::datatypes::UInt32Type; use re_sdk_types::external::arrow::datatypes::DataType as ArrowDatatype; use re_sdk_types::{ComponentDescriptor, Loggable as _, RowId, components}; use re_view::clamped_or_nothing; -use re_viewer_context::{QueryContext, VisualizerReportSeverity}; +use re_viewer_context::VisualizerReportSeverity; use crate::{MAX_NUM_TIME_SERIES_SHOWN_PER_ENTITY_BY_DEFAULT, PlotPoint, PlotSeriesKind}; @@ -32,7 +33,8 @@ pub fn determine_num_series( }) .unwrap_or(1); if count > MAX_NUM_TIME_SERIES_SHOWN_PER_ENTITY_BY_DEFAULT { - results.report_unspecified_source(VisualizerReportSeverity::Error,format!("Number of series ({count}) exceeds the maximum ({MAX_NUM_TIME_SERIES_SHOWN_PER_ENTITY_BY_DEFAULT}). Only the first {MAX_NUM_TIME_SERIES_SHOWN_PER_ENTITY_BY_DEFAULT} series will be visualized.")); + results.report_unspecified_source(VisualizerReportSeverity::Error, + format!("Number of series ({count}) exceeds the maximum ({MAX_NUM_TIME_SERIES_SHOWN_PER_ENTITY_BY_DEFAULT}). Only the first {MAX_NUM_TIME_SERIES_SHOWN_PER_ENTITY_BY_DEFAULT} series will be visualized.")); MAX_NUM_TIME_SERIES_SHOWN_PER_ENTITY_BY_DEFAULT } else { count @@ -41,11 +43,11 @@ pub fn determine_num_series( /// Queries the visibility flags for all series in a query. pub fn collect_series_visibility( - query_ctx: &QueryContext<'_>, results: &re_view::VisualizerInstructionQueryResults<'_>, num_series: usize, visibility_descriptor: &ComponentDescriptor, ) -> Vec { + let query_ctx = results.query_context(); let boolean_buffer = results .iter_optional(visibility_descriptor.component) .slice::() @@ -56,8 +58,7 @@ pub fn collect_series_visibility( .viewer_ctx() .component_fallback_registry .fallback_for(visibility_descriptor, query_ctx) - .as_any() - .downcast_ref::() + .as_boolean_opt() .map(|arr| arr.values().clone()) .unwrap_or_else(|| { re_log::warn_once!( @@ -147,7 +148,6 @@ pub fn collect_scalars( /// Collects colors for the series into pre-allocated plot points. pub fn collect_colors( - query_ctx: &QueryContext<'_>, query: &RangeQuery, query_results: &re_view::VisualizerInstructionQueryResults<'_>, all_scalar_chunks: &re_view::ChunksWithComponent<'_>, @@ -156,6 +156,7 @@ pub fn collect_colors( ) { re_tracing::profile_function!(); + let query_ctx = query_results.query_context(); let num_series = points_per_series.len(); re_log::debug_assert_eq!(components::Color::arrow_datatype(), ArrowDatatype::UInt32); @@ -191,7 +192,7 @@ pub fn collect_colors( .component_fallback_registry .fallback_for(color_descriptor, query_ctx); - if let Some(color_array) = fallback_array.as_any().downcast_ref::() { + if let Some(color_array) = fallback_array.as_primitive_opt::() { let fallback_colors = color_array.values(); for (points, color) in points_per_series @@ -261,13 +262,13 @@ fn expand_series_names(names: &[String], num_series: usize) -> Vec { /// Collects series names for the series into pre-allocated plot points. pub fn collect_series_name( - query_ctx: &QueryContext<'_>, query_results: &re_view::VisualizerInstructionQueryResults<'_>, num_series: usize, name_descriptor: &ComponentDescriptor, ) -> Vec { re_tracing::profile_function!(); + let query_ctx = query_results.query_context(); let name_iter = query_results.iter_optional(name_descriptor.component); let all_name_chunks = name_iter.chunks().iter().collect_vec(); @@ -288,7 +289,7 @@ pub fn collect_series_name( .component_fallback_registry .fallback_for(name_descriptor, query_ctx); - if let Some(string_array) = fallback_array.as_any().downcast_ref::() { + if let Some(string_array) = fallback_array.as_string_opt::() { let fallback_names: Vec<_> = string_array .iter() .flatten() diff --git a/crates/viewer/re_view_time_series/src/view_class.rs b/crates/viewer/re_view_time_series/src/view_class.rs index e859e01b3e71..3138aa585657 100644 --- a/crates/viewer/re_view_time_series/src/view_class.rs +++ b/crates/viewer/re_view_time_series/src/view_class.rs @@ -1,38 +1,31 @@ -use arrayvec::ArrayVec; use egui::ahash::HashMap; use egui::{NumExt as _, Vec2, Vec2b}; use egui_plot::{ColorConflictHandling, Legend, Line, Plot, PlotPoint, Points}; use itertools::{Either, Itertools as _}; use nohash_hasher::IntSet; use re_chunk_store::TimeType; -use re_component_ui::color_swatch::ColorSwatch; use re_format::time::next_grid_tick_magnitude_nanos; use re_log_types::external::arrow::datatypes::DataType; use re_log_types::{AbsoluteTimeRange, EntityPath, TimeInt}; use re_sdk_types::archetypes::{Scalars, SeriesLines, SeriesPoints}; -use re_sdk_types::blueprint::archetypes::{ - ActiveVisualizers, PlotBackground, PlotLegend, ScalarAxis, TimeAxis, -}; +use re_sdk_types::blueprint::archetypes::{PlotBackground, PlotLegend, ScalarAxis, TimeAxis}; use re_sdk_types::blueprint::components::{ Corner2D, Enabled, LinkAxis, LockRangeDuringZoom, VisualizerInstructionId, }; -use re_sdk_types::components::{AggregationPolicy, Color, Name, Range1D, Visible}; +use re_sdk_types::components::{AggregationPolicy, Color, Range1D, Visible}; use re_sdk_types::datatypes::TimeRange; -use re_sdk_types::{ - ComponentBatch as _, ComponentIdentifier, Loggable as _, View as _, ViewClassIdentifier, -}; +use re_sdk_types::{ComponentBatch as _, ComponentIdentifier, View as _, ViewClassIdentifier}; use re_ui::{Help, IconText, MouseButtonText, UiExt as _, icons, list_item}; use re_view::controls::{MOVE_TIME_CURSOR_BUTTON, SELECTION_RECT_ZOOM_BUTTON}; use re_view::view_property_ui; -use re_viewer_context::external::re_entity_db::InstancePath; use re_viewer_context::{ BlueprintContext as _, DataResultInteractionAddress, DatatypeMatch, IdentifiedViewSystem as _, - IndicatedEntities, Item, PerVisualizerType, PerVisualizerTypeInViewClass, QueryRange, - RecommendedMappings, RecommendedView, RecommendedVisualizers, SystemCommandSender as _, - SystemExecutionOutput, TimeControlCommand, ViewClass, ViewClassExt as _, - ViewClassRegistryError, ViewHighlights, ViewId, ViewQuery, ViewSpawnHeuristics, ViewState, - ViewStateExt as _, ViewSystemExecutionError, ViewSystemIdentifier, ViewerContext, - VisualizableEntities, VisualizableReason, VisualizerComponentSource, + IndicatedEntities, PerVisualizerType, PerVisualizerTypeInViewClass, QueryRange, + RecommendedMappings, RecommendedView, RecommendedVisualizers, SystemExecutionOutput, + TimeControlCommand, ViewClass, ViewClassExt as _, ViewClassRegistryError, ViewHighlights, + ViewId, ViewQuery, ViewSpawnHeuristics, ViewState, ViewStateExt as _, ViewSystemExecutionError, + ViewSystemIdentifier, ViewerContext, VisualizableEntities, VisualizableReason, + VisualizerComponentSource, }; use re_viewport_blueprint::ViewProperty; use smallvec::SmallVec; @@ -46,9 +39,6 @@ use crate::util::data_result_time_range; // --- -/// We only show this many colors directly. -const NUM_SHOWN_VISUALIZER_COLORS: usize = 2; - #[derive(Clone)] pub struct TimeSeriesViewState { /// The range of the scalar values currently on screen. @@ -841,7 +831,13 @@ impl ViewClass for TimeSeriesView { ui.add_space(10.0); - visualizer_ui_element(ui, &ctx, node, pill_margin, instruction); + crate::visualizer_ui::visualizer_ui_element( + ui, + &ctx, + node, + pill_margin, + instruction, + ); } } }); @@ -851,140 +847,6 @@ impl ViewClass for TimeSeriesView { } } -fn visualizer_ui_element( - ui: &mut egui::Ui, - ctx: &re_viewer_context::ViewContext<'_>, - node: &re_viewer_context::DataResultNode, - pill_margin: egui::Margin, - instruction: &re_viewer_context::VisualizerInstruction, -) { - let entity_path = &node.data_result.entity_path; - - let full_path = entity_path.ui_string().trim_start_matches('/').to_owned(); - - let series_color = get_time_series_color(ctx, &node.data_result, instruction); - let display_name = get_time_series_name(ctx, &node.data_result, instruction); - - ui.scope(|ui| { - // Time travel: retrieve the height of the previous frame so we can set it to the right side of egui::Sides. - // In case of `shrink_left`, egui::Sides will render the right side first, and the content can't be centered - // vertically since the height of the left side is unknown at that point. By setting the height explicitly, - // we can vertically center the right side. It works since the height doesn't change. - let previous_frame_height = ui.response().rect.height(); - - egui::Sides::new().shrink_left().show( - ui, - |ui| { - let mut frame = egui::Frame::default() - .fill(ui.tokens().visualizer_list_pill_bg_color) - .corner_radius(4.0) - .inner_margin(pill_margin) - .begin(ui); - { - let ui = &mut frame.content_ui; - let previous_frame_height = ui.response().rect.height(); - - // Disable text selection so hovering the text only hovers the pill - ui.style_mut().interaction.selectable_labels = false; - egui::Sides::new().shrink_left().show( - ui, - |ui| { - // Visualizer name and entity path - ui.vertical(|ui| { - ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Truncate); - ui.label( - egui::RichText::new(&display_name) - .color(ui.tokens().visualizer_list_title_text_color), - ); - ui.label( - egui::RichText::new(&full_path) - .size(10.5) - .color(ui.tokens().visualizer_list_path_text_color), - ); - }); - }, - |ui| { - if previous_frame_height.is_sign_positive() { - ui.set_height(previous_frame_height); - } - // Color box(es) on the right, vertically centered on the labels. - series_color.ui(ui); - }, - ); - } - let response = frame - .allocate_space(ui) - .interact(egui::Sense::click()) - .on_hover_cursor(egui::CursorIcon::PointingHand); - - let is_highlighted = - ctx.viewer_ctx - .hovered() - .iter() - .any(|(hovered, _hover_ctx)| { - if let Item::DataResult(address) = hovered { - address.view_id == ctx.view_id - && address.instance_path.entity_path == *entity_path // Don't care about instance id here. - && address.visualizer.is_none_or(|vid| vid == instruction.id) - } else { - false - } - }); - if is_highlighted { - frame.frame.fill = ui.tokens().visualizer_list_pill_bg_color_hovered; - } - - let item = Item::DataResult(DataResultInteractionAddress { - view_id: ctx.view_id, - instance_path: InstancePath::from(entity_path.clone()), - visualizer: Some(instruction.id), - }); - - if response.hovered() { - frame.frame.fill = ui.tokens().visualizer_list_pill_bg_color_hovered; - ctx.viewer_ctx.selection_state().set_hovered(item.clone()); - } - if response.clicked() { - ctx.viewer_ctx - .command_sender() - .send_system(re_viewer_context::SystemCommand::set_selection(item)); - } - frame.paint(ui); - }, - |ui| { - // Trashcan button to remove this visualizer. - if previous_frame_height.is_sign_positive() { - ui.set_height(previous_frame_height); - } - - let remove_response = - ui.small_icon_button(&re_ui::icons::TRASH, "Remove visualizer"); - if remove_response.clicked() { - let override_base_path = &node.data_result.override_base_path; - - let active_visualizers = node - .data_result - .visualizer_instructions - .iter() - .filter(|v| v.id != instruction.id) - .collect::>(); - - let archetype = - ActiveVisualizers::new(active_visualizers.iter().map(|v| v.id.0)); - - ctx.save_blueprint_archetype(override_base_path.clone(), &archetype); - - // Ensure the remaining instructions are persisted so that their - // types and mappings are available on the next frame. - for visualizer_instruction in active_visualizers { - visualizer_instruction.write_instruction_to_blueprint(ctx.viewer_ctx); - } - } - }, - ); - }); -} - /// Returns a priority score for a given Arrow datatype. /// Lower scores are preferred. fn scalar_datatype_priority(datatype: &re_log_types::external::arrow::datatypes::DataType) -> u32 { @@ -1519,148 +1381,6 @@ pub fn make_range_sane(y_range: Range1D) -> Range1D { } } -fn strip_instance_number(str: &str) -> String { - if let Some(stripped) = str.strip_suffix(']').and_then(|s| { - let i = s.rfind('[')?; - s[i + 1..] - .bytes() - .all(|b| b.is_ascii_digit()) - .then_some(&s[..i]) - }) { - format!("{stripped}[]") - } else { - str.to_owned() - } -} - -/// Returns the name for a time series visualizer. -fn get_time_series_name( - ctx: &re_viewer_context::ViewContext<'_>, - data_result: &re_viewer_context::DataResult, - instruction: &re_viewer_context::VisualizerInstruction, -) -> String { - let component = if instruction.visualizer_type == SeriesLinesSystem::identifier() { - SeriesLines::descriptor_names().component - } else if instruction.visualizer_type == SeriesPointsSystem::identifier() { - SeriesPoints::descriptor_names().component - } else { - return instruction.visualizer_type.to_string(); - }; - - let query_result = re_view::latest_at_with_blueprint_resolved_data( - ctx, - None, - &ctx.current_query(), - data_result, - [component], - Some(instruction), - ); - - let first_name = query_result.get_mono_with_fallback::(component); - - // We might have already "injected" the instance number into the name of the series. - // So we re-normalize the series name again. - strip_instance_number(&first_name) -} - -/// List of colors for a time series visualizer. -#[derive(Default)] -struct TimeSeriesColors { - instance_count: usize, - colors: ArrayVec, -} - -impl TimeSeriesColors { - /// Draws color boxes (and an optional "+N" badge) right-aligned and vertically centered on - /// the given `center_y`. - fn ui(&self, ui: &mut egui::Ui) { - if self.colors.is_empty() { - return; - } - - ui.spacing_mut().item_spacing.x = 4.0; - - let num_boxes = if self.instance_count > 2 { - 1 - } else { - self.colors.len() - }; - - // Draw "+N" badge when there are more than 2 instances - if self.instance_count > 2 { - let badge_text = format!("+{}", self.instance_count - 1); - ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Truncate); - ui.label( - egui::RichText::new(&badge_text) - .color(ui.tokens().visualizer_list_path_text_color) - .size(10.5), - ); - } - - // Draw color boxes from right to left - for color in self.colors[..num_boxes].iter().rev() { - let rgba = re_sdk_types::datatypes::Rgba32::from(*color); - let mut color_ref = re_viewer_context::MaybeMutRef::Ref(&rgba); - ui.add(ColorSwatch::new(&mut color_ref)); - } - } -} - -/// Returns the colors of time series plots. -fn get_time_series_color( - ctx: &re_viewer_context::ViewContext<'_>, - data_result: &re_viewer_context::DataResult, - instruction: &re_viewer_context::VisualizerInstruction, -) -> TimeSeriesColors { - let color_descr = if instruction.visualizer_type == SeriesLinesSystem::identifier() { - SeriesLines::descriptor_colors() - } else if instruction.visualizer_type == SeriesPointsSystem::identifier() { - SeriesPoints::descriptor_colors() - } else { - // Unkownn visualizer type, don't show any colors - return TimeSeriesColors::default(); - }; - - // Get the colors for each instance - let query = ctx.current_query(); - let color_result = re_view::latest_at_with_blueprint_resolved_data( - ctx, - None, - &query, - data_result, - [color_descr.component], - Some(instruction), - ); - - let raw_color_cell = if let Some(color_cells) = color_result.get_raw_cell(color_descr.component) - { - // We have color data either in the store or as overrides - color_cells - } else { - // No color data in the store or overrides, use the fallback - ctx.viewer_ctx.component_fallback_registry.fallback_for( - &color_descr, - &ctx.query_context(data_result, query, instruction.id), - ) - }; - - let Ok(color_components) = Color::from_arrow(&raw_color_cell) else { - re_log::error_once!("Failed to cast color array to Color"); - return TimeSeriesColors::default(); - }; - - let colors = color_components - .iter() - .map(|&value| value.into()) // Color is ABGR, egui uses to RGBA, can't use bytemuck here. - .take(NUM_SHOWN_VISUALIZER_COLORS) - .collect(); - - TimeSeriesColors { - instance_count: color_components.len(), - colors, - } -} - #[cfg(test)] mod tests { use super::*; @@ -1741,51 +1461,3 @@ mod tests { ); } } - -#[test] -fn test_strip_instance_number() { - // Empty string - assert_eq!(strip_instance_number(""), ""); - - // No brackets at all - assert_eq!(strip_instance_number("foo"), "foo"); - assert_eq!(strip_instance_number("hello world"), "hello world"); - - // Valid instance numbers should be normalized to [] - assert_eq!(strip_instance_number("foo[0]"), "foo[]"); - assert_eq!(strip_instance_number("foo[1]"), "foo[]"); - assert_eq!(strip_instance_number("foo[123]"), "foo[]"); - - // Empty brackets (no digits) should remain unchanged - assert_eq!(strip_instance_number("foo[]"), "foo[]"); - - // Non-digit content in brackets should remain unchanged - assert_eq!(strip_instance_number("foo[abc]"), "foo[abc]"); - assert_eq!(strip_instance_number("foo[1a]"), "foo[1a]"); - assert_eq!(strip_instance_number("foo[a1]"), "foo[a1]"); - assert_eq!(strip_instance_number("foo[ ]"), "foo[ ]"); - assert_eq!(strip_instance_number("foo[1 2]"), "foo[1 2]"); - - // Half-open brackets (only `[`) should remain unchanged - assert_eq!(strip_instance_number("foo["), "foo["); - assert_eq!(strip_instance_number("["), "["); - assert_eq!(strip_instance_number("foo[123"), "foo[123"); - - // Half-closed brackets (only `]`) should remain unchanged - assert_eq!(strip_instance_number("foo]"), "foo]"); - assert_eq!(strip_instance_number("]"), "]"); - assert_eq!(strip_instance_number("123]"), "123]"); - - // Multiple bracket pairs - only the last valid instance number is stripped - assert_eq!(strip_instance_number("foo[0][1]"), "foo[0][]"); - assert_eq!(strip_instance_number("foo[abc][123]"), "foo[abc][]"); - - // Nested or malformed brackets - assert_eq!(strip_instance_number("foo[[0]]"), "foo[[0]]"); - assert_eq!(strip_instance_number("foo[0]["), "foo[0]["); - assert_eq!(strip_instance_number("foo][0]"), "foo][]"); - - // Edge cases with brackets in the middle - assert_eq!(strip_instance_number("foo[0]bar"), "foo[0]bar"); - assert_eq!(strip_instance_number("foo[0]bar[1]"), "foo[0]bar[]"); -} diff --git a/crates/viewer/re_view_time_series/src/visualizer_ui.rs b/crates/viewer/re_view_time_series/src/visualizer_ui.rs new file mode 100644 index 000000000000..316efe2db7e3 --- /dev/null +++ b/crates/viewer/re_view_time_series/src/visualizer_ui.rs @@ -0,0 +1,348 @@ +//! UI code for `TimeSeriesView::visualizer_ui`, i.e. the visualizer list you get when selecting the an instance of the view. + +use arrayvec::ArrayVec; +use re_component_ui::color_swatch::ColorSwatch; +use re_log_types::external::arrow::array::AsArray as _; +use re_sdk_types::archetypes::{SeriesLines, SeriesPoints}; +use re_sdk_types::components::{self, Color, Name}; +use re_sdk_types::{ComponentDescriptor, Loggable as _}; +use re_ui::UiExt as _; +use re_ui::egui_ext::response_ext::ResponseExt as _; +use re_viewer_context::external::re_entity_db::InstancePath; +use re_viewer_context::{ + DataResultInteractionAddress, IdentifiedViewSystem as _, Item, SystemCommandSender as _, +}; + +use crate::point_visualizer_system::SeriesPointsSystem; + +/// We only show this many colors directly. +const NUM_SHOWN_VISUALIZER_COLORS: usize = 2; + +// Figma design for this can be found here: +// https://www.figma.com/design/eGATW7RubxdRrcEP9ITiVh/Any-scalars?node-id=956-7840&t=L1YFvJijAXXLlaBZ-0 +// (accessible only by rerun team members) +pub fn visualizer_ui_element( + ui: &mut egui::Ui, + ctx: &re_viewer_context::ViewContext<'_>, + node: &re_viewer_context::DataResultNode, + pill_margin: egui::Margin, + instruction: &re_viewer_context::VisualizerInstruction, +) { + let entity_path = &node.data_result.entity_path; + + let (name_descr, color_descr, visibility_descr) = + if instruction.visualizer_type == SeriesPointsSystem::identifier() { + ( + SeriesPoints::descriptor_names(), + SeriesPoints::descriptor_colors(), + SeriesPoints::descriptor_visible_series(), + ) + } else { + // if instruction.visualizer_type == SeriesLinesSystem::identifier() { + ( + SeriesLines::descriptor_names(), + SeriesLines::descriptor_colors(), + SeriesLines::descriptor_visible_series(), + ) + }; + + let query_result = re_view::latest_at_with_blueprint_resolved_data( + ctx, + None, + &ctx.current_query(), + &node.data_result, + [ + name_descr.component, + color_descr.component, + visibility_descr.component, + ], + Some(instruction), + ); + + let display_name = extract_series_name(&query_result, &name_descr); + let series_colors = extract_series_colors(ctx, &query_result, &color_descr); + let visibility = extract_series_visibility(ctx, &query_result, &visibility_descr); + let all_series_invisible = visibility.iter().all(|&visible| !visible); + + let mut frame = egui::Frame::default() + .fill(ui.tokens().visualizer_list_pill_bg_color) + .corner_radius(4.0) + .inner_margin(pill_margin) + .begin(ui); + let frame_response = { + // Time travel: retrieve the height of the previous frame so we can set it to the right side of egui::Sides. + // In case of `shrink_left`, egui::Sides will render the right side first, and the content can't be centered + // vertically since the height of the left side is unknown at that point. By setting the height explicitly, + // we can vertically center the right side. It works since the height doesn't change. + let frame_rect = frame.content_ui.response().rect; + let frame_response = frame.content_ui.interact( + frame_rect, + ui.next_auto_id(), + egui::Sense::hover() | egui::Sense::click(), + ); + let previous_frame_height = frame_response.rect.height(); + + // Show *either* visibility icon or color boxes. + // Use `container_contains_pointer` instead of `container_hovered` because otherwise + // we change behavior on mouse press-down which we don't want to do here. + let show_visibility_icon = + frame_response.container_contains_pointer() || all_series_invisible; + + egui::Sides::new().shrink_left().show( + &mut frame.content_ui, + |ui| { + // Disable text selection so hovering the text only hovers the pill + ui.style_mut().interaction.selectable_labels = false; + + let (title_color, path_color) = if all_series_invisible { + ( + ui.tokens().visualizer_list_title_text_invisible_color, + ui.tokens().visualizer_list_path_text_invisible_color, + ) + } else { + ( + ui.tokens().visualizer_list_title_text_color, + ui.tokens().visualizer_list_path_text_color, + ) + }; + + // Visualizer name and entity path + let full_path = entity_path.ui_string().trim_start_matches('/').to_owned(); + ui.vertical(|ui| { + ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Truncate); + ui.label(egui::RichText::new(&display_name).color(title_color)); + ui.label(egui::RichText::new(&full_path).size(10.5).color(path_color)); + }); + }, + |ui| { + if previous_frame_height.is_sign_positive() { + ui.set_height(previous_frame_height); + } + + if show_visibility_icon { + let response = if all_series_invisible { + ui.small_icon_button(&re_ui::icons::INVISIBLE, "Show series") + } else { + ui.small_icon_button(&re_ui::icons::VISIBLE, "Hide series") + }; + + if response.clicked() { + instruction.save_override( + ctx.viewer_ctx, + &visibility_descr, + &components::Visible::from(all_series_invisible), + ); + } + } else { + series_colors.ui(ui); + } + }, + ); + + frame_response + }; + + let is_highlighted_via_item = ctx + .viewer_ctx + .hovered() + .iter() + .any(|(hovered, _hover_ctx)| { + if let Item::DataResult(address) = hovered { + address.view_id == ctx.view_id + && address.instance_path.entity_path == *entity_path // Don't care about instance id here. + && address.visualizer.is_none_or(|vid| vid == instruction.id) + } else { + false + } + }); + if is_highlighted_via_item || frame_response.container_contains_pointer() { + frame.frame.fill = ui.tokens().visualizer_list_pill_bg_color_hovered; + } + + frame.paint(ui); + frame.allocate_space(ui); + + let item = Item::DataResult(DataResultInteractionAddress { + view_id: ctx.view_id, + instance_path: InstancePath::from(entity_path.clone()), + visualizer: Some(instruction.id), + }); + + if frame_response.container_hovered() { + ctx.viewer_ctx.selection_state().set_hovered(item.clone()); + } + if frame_response.clicked() { + ctx.viewer_ctx + .command_sender() + .send_system(re_viewer_context::SystemCommand::set_selection(item)); + } +} + +/// List of colors for a time series visualizer. +#[derive(Default)] +struct TimeSeriesColors { + instance_count: usize, + colors: ArrayVec, +} + +impl TimeSeriesColors { + /// Draws color boxes (and an optional "+N" badge) right-aligned and vertically centered on + /// the given `center_y`. + fn ui(&self, ui: &mut egui::Ui) { + if self.colors.is_empty() { + return; + } + + let spacing = 4.0; + ui.spacing_mut().item_spacing.x = spacing; + + let num_boxes = if self.instance_count > 2 { + 1 + } else { + self.colors.len() + }; + + // Draw "+N" badge when there are more than 2 instances + if self.instance_count > 2 { + let badge_text = format!("+{}", self.instance_count - 1); + ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Truncate); + ui.label( + egui::RichText::new(&badge_text) + .color(ui.tokens().visualizer_list_path_text_color) + .size(10.5), + ); + } + + // Draw color boxes from right to left + for color in self.colors[..num_boxes].iter().rev() { + let rgba = re_sdk_types::datatypes::Rgba32::from(*color); + let mut color_ref = re_viewer_context::MaybeMutRef::Ref(&rgba); + ui.add(ColorSwatch::new(&mut color_ref)); + } + } +} + +/// Extracts the display name from already-queried results. +fn extract_series_name( + query_result: &re_view::BlueprintResolvedLatestAtResults<'_>, + name_descr: &ComponentDescriptor, +) -> String { + let first_name = query_result.get_mono_with_fallback::(name_descr.component); + // We might have already "injected" the instance number into the name of the series. + // So we re-normalize the series name again. + strip_instance_number(&first_name) +} + +/// Extracts colors from already-queried results. +fn extract_series_colors( + ctx: &re_viewer_context::ViewContext<'_>, + query_result: &re_view::BlueprintResolvedLatestAtResults<'_>, + color_descr: &ComponentDescriptor, +) -> TimeSeriesColors { + let raw_color_cell = if let Some(color_cells) = query_result.get_raw_cell(color_descr.component) + { + color_cells + } else { + ctx.viewer_ctx + .component_fallback_registry + .fallback_for(color_descr, query_result.query_context()) + }; + + let Ok(color_components) = Color::from_arrow(&raw_color_cell) else { + re_log::error_once!("Failed to cast color array to Color"); + return TimeSeriesColors::default(); + }; + + let colors = color_components + .iter() + .map(|&value| value.into()) + .take(NUM_SHOWN_VISUALIZER_COLORS) + .collect(); + + TimeSeriesColors { + instance_count: color_components.len(), + colors, + } +} + +/// Extracts visibility from already-queried results. +fn extract_series_visibility( + ctx: &re_viewer_context::ViewContext<'_>, + query_result: &re_view::BlueprintResolvedLatestAtResults<'_>, + visibility_descr: &ComponentDescriptor, +) -> Vec { + let raw_visibility = query_result + .get_raw_cell(visibility_descr.component) + .unwrap_or_else(|| { + ctx.viewer_ctx + .component_fallback_registry + .fallback_for(visibility_descr, query_result.query_context()) + }); + + raw_visibility + .as_boolean_opt() + .map(|bool_array| bool_array.iter().flatten().collect()) + .unwrap_or_default() +} + +fn strip_instance_number(str: &str) -> String { + if let Some(stripped) = str.strip_suffix(']').and_then(|s| { + let i = s.rfind('[')?; + s[i + 1..] + .bytes() + .all(|b| b.is_ascii_digit()) + .then_some(&s[..i]) + }) { + format!("{stripped}[]") + } else { + str.to_owned() + } +} + +#[test] +fn test_strip_instance_number() { + // Empty string + assert_eq!(strip_instance_number(""), ""); + + // No brackets at all + assert_eq!(strip_instance_number("foo"), "foo"); + assert_eq!(strip_instance_number("hello world"), "hello world"); + + // Valid instance numbers should be normalized to [] + assert_eq!(strip_instance_number("foo[0]"), "foo[]"); + assert_eq!(strip_instance_number("foo[1]"), "foo[]"); + assert_eq!(strip_instance_number("foo[123]"), "foo[]"); + + // Empty brackets (no digits) should remain unchanged + assert_eq!(strip_instance_number("foo[]"), "foo[]"); + + // Non-digit content in brackets should remain unchanged + assert_eq!(strip_instance_number("foo[abc]"), "foo[abc]"); + assert_eq!(strip_instance_number("foo[1a]"), "foo[1a]"); + assert_eq!(strip_instance_number("foo[a1]"), "foo[a1]"); + assert_eq!(strip_instance_number("foo[ ]"), "foo[ ]"); + assert_eq!(strip_instance_number("foo[1 2]"), "foo[1 2]"); + + // Half-open brackets (only `[`) should remain unchanged + assert_eq!(strip_instance_number("foo["), "foo["); + assert_eq!(strip_instance_number("["), "["); + assert_eq!(strip_instance_number("foo[123"), "foo[123"); + + // Half-closed brackets (only `]`) should remain unchanged + assert_eq!(strip_instance_number("foo]"), "foo]"); + assert_eq!(strip_instance_number("]"), "]"); + assert_eq!(strip_instance_number("123]"), "123]"); + + // Multiple bracket pairs - only the last valid instance number is stripped + assert_eq!(strip_instance_number("foo[0][1]"), "foo[0][]"); + assert_eq!(strip_instance_number("foo[abc][123]"), "foo[abc][]"); + + // Nested or malformed brackets + assert_eq!(strip_instance_number("foo[[0]]"), "foo[[0]]"); + assert_eq!(strip_instance_number("foo[0]["), "foo[0]["); + assert_eq!(strip_instance_number("foo][0]"), "foo][]"); + + // Edge cases with brackets in the middle + assert_eq!(strip_instance_number("foo[0]bar"), "foo[0]bar"); + assert_eq!(strip_instance_number("foo[0]bar[1]"), "foo[0]bar[]"); +} diff --git a/crates/viewer/re_viewer_context/src/blueprint_helpers.rs b/crates/viewer/re_viewer_context/src/blueprint_helpers.rs index 14024541a578..27e289bf31bf 100644 --- a/crates/viewer/re_viewer_context/src/blueprint_helpers.rs +++ b/crates/viewer/re_viewer_context/src/blueprint_helpers.rs @@ -133,25 +133,11 @@ pub trait BlueprintContext { entity_path: EntityPath, component_batch: SerializedComponentBatch, ) { - let blueprint = self.current_blueprint(); - let timepoint = blueprint_timepoint_for_writes(blueprint); - - let chunk = match Chunk::builder(entity_path) - .with_serialized_batch(RowId::new(), timepoint.clone(), component_batch) - .build() - { - Ok(chunk) => chunk, - Err(err) => { - re_log::error_once!("Failed to create Chunk for blueprint components: {err}"); - return; - } - }; - - self.command_sender() - .send_system(SystemCommand::AppendToStore( - blueprint.store_id().clone(), - vec![chunk], - )); + self.save_blueprint_array( + entity_path, + component_batch.descriptor, + component_batch.array, + ); } fn save_blueprint_array( @@ -171,22 +157,6 @@ pub trait BlueprintContext { ); } - fn save_static_blueprint_array( - &self, - entity_path: EntityPath, - component_descr: ComponentDescriptor, - array: ArrayRef, - ) { - let blueprint = self.current_blueprint(); - self.append_array_to_store( - blueprint.store_id().clone(), - TimePoint::STATIC, - entity_path, - component_descr, - array, - ); - } - /// Append an array to the given store. fn append_array_to_store( &self, @@ -250,21 +220,6 @@ pub trait BlueprintContext { } } - /// Resets a static blueprint component to the value it had in the default blueprint. - fn reset_static_blueprint_component( - &self, - entity_path: EntityPath, - component_descr: ComponentDescriptor, - ) { - if let Some(default_value) = - self.raw_latest_at_in_default_blueprint(&entity_path, component_descr.component) - { - self.save_static_blueprint_array(entity_path, component_descr, default_value); - } else { - self.clear_static_blueprint_component(entity_path, component_descr); - } - } - /// Clears a component in the blueprint store by logging an empty array if it exists. fn clear_blueprint_component( &self, diff --git a/crates/viewer/re_viewer_context/src/view/view_query.rs b/crates/viewer/re_viewer_context/src/view/view_query.rs index 70286e60c8a9..b74160c76d96 100644 --- a/crates/viewer/re_viewer_context/src/view/view_query.rs +++ b/crates/viewer/re_viewer_context/src/view/view_query.rs @@ -233,7 +233,7 @@ impl VisualizerInstruction { re_sdk_types::blueprint::archetypes::VisualizerInstruction::new( self.visualizer_type.as_str(), ) - // We always have ti write the component map because it we may need to clear out old mappings. + // We always have to write the component map because it we may need to clear out old mappings. // TODO(andreas): can we avoid writing out needless data here? Often there are no mappings, so we keep writing empty arrays. .with_component_map(self.component_mappings.iter().map(|(target, mapping)| { let target = target.as_str().into(); @@ -268,6 +268,26 @@ impl VisualizerInstruction { ctx.save_blueprint_archetype(self.override_path.clone(), &new_visualizer_instruction); } + + /// Saves an override for the given component and value, and updates the blueprint instruction if necessary. + pub fn save_override( + &self, + ctx: &ViewerContext<'_>, + descriptor: &re_sdk_types::ComponentDescriptor, + new_value: &dyn re_sdk_types::ComponentBatch, + ) { + if self.component_mappings.get(&descriptor.component) + != Some(&VisualizerComponentSource::Override) + { + let mut new_instruction = self.clone(); + new_instruction + .component_mappings + .insert(descriptor.component, VisualizerComponentSource::Override); + new_instruction.write_instruction_to_blueprint(ctx); + } + + ctx.save_blueprint_component(self.override_path.clone(), descriptor, new_value); + } } /// This is the primary mechanism through which data is passed to a `View`. diff --git a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_bar_chart_2.png b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_bar_chart_2.png index 93a7e060e26c..e5c4d80d4e3c 100644 --- a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_bar_chart_2.png +++ b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_bar_chart_2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1a0e266f89c1225e32827b40bb3321cbc847207a352506c1ce871996cf1d25b2 -size 190357 +oid sha256:51982d3dbbbde0290a6dde7969ba51c7213206abf6a962f0cbec704cefe339d7 +size 190618 diff --git a/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_left_1.png b/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_left_1.png index 011326b5f6b8..4f0668814bb9 100644 --- a/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_left_1.png +++ b/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_left_1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e0b9f75d5430ebc35e4e04daa85f409bf091eb45f47069e6f0b28f7f92acfcb0 -size 179989 +oid sha256:389a159eaaeb696f2db949d05544f20de44752cd6b2815825b30056d103e5032 +size 179682 diff --git a/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_left_2.png b/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_left_2.png index da95c6631ff7..f8530b460bf1 100644 --- a/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_left_2.png +++ b/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_left_2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b1a9530e5190d7c6b15380d227d964c16c1e7e996acaa55c5e93d28cb47ed334 -size 179483 +oid sha256:5d986c6902a78f1d1b1741d1b2a5c15ff3cfea8aec2725ae251ae33d4d7b3076 +size 179150 diff --git a/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_right_1.png b/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_right_1.png index 31b9271ce321..ecd049e09001 100644 --- a/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_right_1.png +++ b/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_right_1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:70290a2d00a54c3a1ea85a209b5a8028b8c933ff06921133e2f89066d36606d4 -size 180013 +oid sha256:617984003692b450232ddd2e686c0e19d2429a2b97802d317ff6bfecf1a8dfee +size 179708 diff --git a/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_right_2.png b/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_right_2.png index 0e3f009c552d..c11e5d2aa649 100644 --- a/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_right_2.png +++ b/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_right_2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6f389a04345dedcb9cc9bd3abbd9aec83b9aacad09467c5b598233a1a56f826f -size 179573 +oid sha256:9acf82c270b83698cb340dd135430b5992e00329eed020aca1d657202a97bd3c +size 179159 diff --git a/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_top_1.png b/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_top_1.png index 62e237df3dcf..7b5aab206d72 100644 --- a/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_top_1.png +++ b/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_top_1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f1d8ce55929457abf15ba1899f303dafb2dc166deeb6406897896e0f1acacd1a -size 179892 +oid sha256:36933098848831f351f03610a0a9e86e609a82623027bc6bb4c4274f9765848b +size 179586 diff --git a/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_top_2.png b/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_top_2.png index d83d4f0523e3..823c26b8e7d1 100644 --- a/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_top_2.png +++ b/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_top_2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0e733df7d7fa7e9036b97a6d9ea9f92bbe17d5f88e1d9d8d536224b4b0c86184 -size 181939 +oid sha256:6502821edb40e0a33982bd09a2d89b7eaa8d73ecd9c9f9b8beb8df2085ae9939 +size 181772 diff --git a/tests/rust/re_integration_test/tests/snapshots/drop_multiple_streams_to_view_1.png b/tests/rust/re_integration_test/tests/snapshots/drop_multiple_streams_to_view_1.png index e7bfdff224bc..07998844cb5d 100644 --- a/tests/rust/re_integration_test/tests/snapshots/drop_multiple_streams_to_view_1.png +++ b/tests/rust/re_integration_test/tests/snapshots/drop_multiple_streams_to_view_1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:94242d975352156245bae20a7f01cc92c03bd986ec51e0914fdb6db600b32a30 -size 128895 +oid sha256:4b323323c2f1ef60e1a7eeb6c854fc3ceee637336d4c6f9fb43dcfad3e0ba90f +size 128439 diff --git a/tests/rust/re_integration_test/tests/snapshots/drop_multiple_streams_to_view_2.png b/tests/rust/re_integration_test/tests/snapshots/drop_multiple_streams_to_view_2.png index 17ea1d004b7c..cf98b9bf7135 100644 --- a/tests/rust/re_integration_test/tests/snapshots/drop_multiple_streams_to_view_2.png +++ b/tests/rust/re_integration_test/tests/snapshots/drop_multiple_streams_to_view_2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:40d15a4cf9ef70ce6eeaef7c83cb432b1685b79452c11882f8ecc92eef98321e -size 101820 +oid sha256:80cef08ef4ad6e539114e422d943b482073712c90b4158c42aa4a74da48c84b3 +size 101783 diff --git a/tests/rust/re_integration_test/tests/snapshots/drop_multiple_streams_to_view_3.png b/tests/rust/re_integration_test/tests/snapshots/drop_multiple_streams_to_view_3.png index fc34700e0f6f..e3d02764fa68 100644 --- a/tests/rust/re_integration_test/tests/snapshots/drop_multiple_streams_to_view_3.png +++ b/tests/rust/re_integration_test/tests/snapshots/drop_multiple_streams_to_view_3.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5f34054d0fedb222b1940d305082814b6e91abb698b4daf23c9adb90067d163b -size 136046 +oid sha256:5ddd7bf9c0a80686204aabe6a59eb916428f9634ec944faf7381888fc5c13585 +size 135400 diff --git a/tests/rust/re_integration_test/tests/snapshots/drop_multiple_streams_to_view_4.png b/tests/rust/re_integration_test/tests/snapshots/drop_multiple_streams_to_view_4.png index f38fc0d1ede6..7a1e51661238 100644 --- a/tests/rust/re_integration_test/tests/snapshots/drop_multiple_streams_to_view_4.png +++ b/tests/rust/re_integration_test/tests/snapshots/drop_multiple_streams_to_view_4.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:721f5fcc82d7564196a27b62cc90e94b00ea7701e4580dd800bb4b8d7c60efe4 -size 109169 +oid sha256:8bb5f0066b27126be2fb14f417108d79bbf403c55513f18c0e646475ed7308ea +size 109147 diff --git a/tests/rust/re_integration_test/tests/snapshots/drop_multiple_streams_to_view_5.png b/tests/rust/re_integration_test/tests/snapshots/drop_multiple_streams_to_view_5.png index dc0949432568..8187f45bd16c 100644 --- a/tests/rust/re_integration_test/tests/snapshots/drop_multiple_streams_to_view_5.png +++ b/tests/rust/re_integration_test/tests/snapshots/drop_multiple_streams_to_view_5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6924fb8c90e6e499535b088f6317fc80d06601621bf109d924adf2f48fb4021e -size 108955 +oid sha256:05efa8f57de8629fba987e1d11b054e3de4f2c066e49f27d843f53cfe46eaf8e +size 108923 diff --git a/tests/rust/re_integration_test/tests/snapshots/drop_multiple_streams_to_view_6.png b/tests/rust/re_integration_test/tests/snapshots/drop_multiple_streams_to_view_6.png index fcb086f20b23..bb7f293fcb2f 100644 --- a/tests/rust/re_integration_test/tests/snapshots/drop_multiple_streams_to_view_6.png +++ b/tests/rust/re_integration_test/tests/snapshots/drop_multiple_streams_to_view_6.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bd91a84ca84b96c4c79ae7e042b84b5918599e380405b577c86152730b04e53b -size 152388 +oid sha256:27a78830d9c29a0772c0d7209f52c8de56d7d837504e735c991a663c792f4d19 +size 151763 diff --git a/tests/rust/re_integration_test/tests/snapshots/drop_stream_to_view_1.png b/tests/rust/re_integration_test/tests/snapshots/drop_stream_to_view_1.png index dcb437512fac..b1a6045cd6b8 100644 --- a/tests/rust/re_integration_test/tests/snapshots/drop_stream_to_view_1.png +++ b/tests/rust/re_integration_test/tests/snapshots/drop_stream_to_view_1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:43c4a9ba715a95edd8437a6741907189cd250cc00f18997933ab771d4953af45 -size 101388 +oid sha256:27fb7fe91562d0b2ae7b595dc1a70815c46df69fb832d077c576181b1d06e37d +size 101130 diff --git a/tests/rust/re_integration_test/tests/snapshots/drop_stream_to_view_2.png b/tests/rust/re_integration_test/tests/snapshots/drop_stream_to_view_2.png index e7bfdff224bc..07998844cb5d 100644 --- a/tests/rust/re_integration_test/tests/snapshots/drop_stream_to_view_2.png +++ b/tests/rust/re_integration_test/tests/snapshots/drop_stream_to_view_2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:94242d975352156245bae20a7f01cc92c03bd986ec51e0914fdb6db600b32a30 -size 128895 +oid sha256:4b323323c2f1ef60e1a7eeb6c854fc3ceee637336d4c6f9fb43dcfad3e0ba90f +size 128439 diff --git a/tests/rust/re_integration_test/tests/snapshots/drop_stream_to_view_3.png b/tests/rust/re_integration_test/tests/snapshots/drop_stream_to_view_3.png index c33047440536..2264020577c1 100644 --- a/tests/rust/re_integration_test/tests/snapshots/drop_stream_to_view_3.png +++ b/tests/rust/re_integration_test/tests/snapshots/drop_stream_to_view_3.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:dbf97d978db60f25de62e39d29fe13cbc4b895b136b0153ab6115090352c0a5b -size 132373 +oid sha256:995611124e6fc04fa2b0def4cf617825166ac40eafff627efd355a2dbf5b7669 +size 131951 diff --git a/tests/rust/re_integration_test/tests/snapshots/drop_stream_to_view_4.png b/tests/rust/re_integration_test/tests/snapshots/drop_stream_to_view_4.png index e7bfdff224bc..07998844cb5d 100644 --- a/tests/rust/re_integration_test/tests/snapshots/drop_stream_to_view_4.png +++ b/tests/rust/re_integration_test/tests/snapshots/drop_stream_to_view_4.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:94242d975352156245bae20a7f01cc92c03bd986ec51e0914fdb6db600b32a30 -size 128895 +oid sha256:4b323323c2f1ef60e1a7eeb6c854fc3ceee637336d4c6f9fb43dcfad3e0ba90f +size 128439 diff --git a/tests/rust/re_integration_test/tests/snapshots/drop_stream_to_view_5.png b/tests/rust/re_integration_test/tests/snapshots/drop_stream_to_view_5.png index aa973ca11322..9ac36cf10922 100644 --- a/tests/rust/re_integration_test/tests/snapshots/drop_stream_to_view_5.png +++ b/tests/rust/re_integration_test/tests/snapshots/drop_stream_to_view_5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1709813acbf770c999b5f330707bd3931979f776320f61194bb900f00bf4be7c -size 130196 +oid sha256:d46b7b18973254d9196bbcf4f3a42f3702f2ce0fa0abd0b8ecb8f58422a82f0f +size 129749 diff --git a/tests/rust/re_integration_test/tests/snapshots/drop_stream_to_view_6.png b/tests/rust/re_integration_test/tests/snapshots/drop_stream_to_view_6.png index fc34700e0f6f..e3d02764fa68 100644 --- a/tests/rust/re_integration_test/tests/snapshots/drop_stream_to_view_6.png +++ b/tests/rust/re_integration_test/tests/snapshots/drop_stream_to_view_6.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5f34054d0fedb222b1940d305082814b6e91abb698b4daf23c9adb90067d163b -size 136046 +oid sha256:5ddd7bf9c0a80686204aabe6a59eb916428f9634ec944faf7381888fc5c13585 +size 135400 diff --git a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_1_initial.png b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_1_initial.png index 4facec336095..b3250bbcc937 100644 --- a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_1_initial.png +++ b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_1_initial.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:71088dbfcba7c7e024846669b2c8bfccc4498bd81b21d0460a4f0cbba87dab85 -size 176456 +oid sha256:fd4065e0a331f0c2a80c78902b8f54e2d9f68969924f94b2c4f5d3b939af18de +size 176491 diff --git a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_2_plots_view_selected.png b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_2_plots_view_selected.png index d6b2882a9b17..07fc0de7e104 100644 --- a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_2_plots_view_selected.png +++ b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_2_plots_view_selected.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:191dba1e74a58ac9f0a2eab19a20d5db3acd0fb4940bb6f7d490818e81b21aba -size 238999 +oid sha256:3f18926d749e5659d8b5e397721e6ed031e15eb15b9bcb690a4469ef5a1bc56a +size 239257 diff --git a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_3_other_view_selected.png b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_3_other_view_selected.png index ff5bcbc3e64b..a4ecadc1f372 100644 --- a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_3_other_view_selected.png +++ b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_3_other_view_selected.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:83b19195712a24186421c555b9e6a43e7045253d75060fe8f1129767455ce484 -size 224459 +oid sha256:d54e0840579720d48573800747a21bfa7fe612f88a2074f596cc5c37c2e05853 +size 224296 diff --git a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_4_after_remove_first.png b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_4_after_remove_first.png deleted file mode 100644 index dc36d4fe95d3..000000000000 --- a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_4_after_remove_first.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:bb82a4eec810a34fd22d9ab9ae50b5cc3a92a9d23dfeabdbc14685f9f50122d8 -size 225877 diff --git a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_4_hover_sin_line.png b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_4_hover_sin_line.png new file mode 100644 index 000000000000..5688f40f33c9 --- /dev/null +++ b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_4_hover_sin_line.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1ad7e78ba0b6acee97a09b2231cce1f637b7c00fac301c3716e8a7dc5cfcac7f +size 239226 diff --git a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_5_after_hide.png b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_5_after_hide.png new file mode 100644 index 000000000000..54f9bde63dda --- /dev/null +++ b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_5_after_hide.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:54f80ef26989dcd7d0b5649730f502d7f8d6676bd321ae0d95342e6cd7c2b786 +size 233045 diff --git a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_5_after_remove_second.png b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_5_after_remove_second.png deleted file mode 100644 index a077de6dce06..000000000000 --- a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_5_after_remove_second.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9ade9b53dc074655d8b4556533664174192819a86dcf0ef473e596398211839b -size 205937 diff --git a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_6_hover_different_after_hide.png b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_6_hover_different_after_hide.png new file mode 100644 index 000000000000..54a4f81d7239 --- /dev/null +++ b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_6_hover_different_after_hide.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a82fb31e87860d40b9b1d26001cdcefd046cf8a69ca834cefadbc4ea92565a98 +size 236058 diff --git a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_1_view_selected.png b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_1_view_selected.png index 7190cae69483..ab41d01bac85 100644 --- a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_1_view_selected.png +++ b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_1_view_selected.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:42a99be673233fa747c2fb7203891ed50b7cf54741a75eec17568c0dfbf95b1f -size 173961 +oid sha256:da3bf690e42ad320c03e316abe78c6e574a92680196f18856dd1be90ad2a9ffe +size 173649 diff --git a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_2_popup_open.png b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_2_popup_open.png index c035e2b30537..cee55d70a2eb 100644 --- a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_2_popup_open.png +++ b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_2_popup_open.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d9b151d534d372d1af116b589c978e6d054db7915fb1128e67e7993858a9edaa -size 168041 +oid sha256:64d3bcbc044e5f67225abc60b5797b45b1291b3a09a2ebebe04959cf2bc7a61b +size 172595 diff --git a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_3_visualizer_added.png b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_3_visualizer_added.png deleted file mode 100644 index afde442655f2..000000000000 --- a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_3_visualizer_added.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:55ef7b7b60050d4b9168a49ab849731fd34d40ac0492050670cf9bff9ac42d3d -size 175966 diff --git a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_1_starting_state.png b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_1_starting_state.png new file mode 100644 index 000000000000..558af32035bb --- /dev/null +++ b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_1_starting_state.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:274bdbeb2e08fe2c582b0d91dc6e0472af7bb278e600bf5ce26ee65468ec474f +size 198459 diff --git a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_1_view_selected.png b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_1_view_selected.png deleted file mode 100644 index 19e1adc456eb..000000000000 --- a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_1_view_selected.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3f008a1a461fac3da55ccbc1eeeb49b9a1ac9936937342e5eb709c9c8df45624 -size 185454 diff --git a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_2_popup_open.png b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_2_popup_open.png deleted file mode 100644 index 3c71d548c5ac..000000000000 --- a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_2_popup_open.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e7bfeedb7b92afeeb2fa94239f84bbbcbbb7ddf4ab7a36fcab3818378c1fa5a5 -size 209390 diff --git a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_2_selected_visualizer.png b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_2_selected_visualizer.png new file mode 100644 index 000000000000..0831927e3aec --- /dev/null +++ b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_2_selected_visualizer.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b90b53ae6388ddf16fb4a1af783995e16dc1685b67766e761c1370fb90369e69 +size 202791 diff --git a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_3_first_added.png b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_3_first_added.png deleted file mode 100644 index dc4430bb3124..000000000000 --- a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_3_first_added.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:625334b9d020e428ac65d586f243e8acc9b76f79beabb31afe472e17d3ab39fe -size 197149 diff --git a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_3_removed_visualizer.png b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_3_removed_visualizer.png new file mode 100644 index 000000000000..695ff789ab2f --- /dev/null +++ b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_3_removed_visualizer.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e9f7c7526d650f631dc45656cd90cb92932faab4dd451ee4b496da646c499644 +size 141944 diff --git a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_4_second_added.png b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_4_second_added.png deleted file mode 100644 index 0cc7afbb4072..000000000000 --- a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_4_second_added.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:614176345f00d60f58053e6f7d8eae3bc2ab9492bd5e5885ff648f650571611e -size 201061 diff --git a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_4_view_selected_again.png b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_4_view_selected_again.png new file mode 100644 index 000000000000..6d814efa2e85 --- /dev/null +++ b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_4_view_selected_again.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:28088e31382cc45a2c9c3f79773b80a4fb23211fb9c367298810f5956cac01e9 +size 184443 diff --git a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_5_popup_open.png b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_5_popup_open.png new file mode 100644 index 000000000000..ed4db0825e07 --- /dev/null +++ b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_5_popup_open.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3bcf9b099fed14b0360872210a2602fd20d2f9342cdc0d70f8f6493f6a0c9966 +size 209439 diff --git a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_6_first_added.png b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_6_first_added.png new file mode 100644 index 000000000000..bc8be0a31b99 --- /dev/null +++ b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_6_first_added.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d90d49ccf11fd7d35fbe8bc4d0c7343397556904ac7ae4f57859eb87c4f5e6ec +size 196453 diff --git a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_7_second_added.png b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_7_second_added.png new file mode 100644 index 000000000000..bbc274376f02 --- /dev/null +++ b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_7_second_added.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2a428592b3252c3cfc68347d54c6986b77ae7fe0a629d61536a6ad5e138d4419 +size 198689 diff --git a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_multi_scalar_view_selected.png b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_multi_scalar_view_selected.png index 1667f9a447d2..749c6a19bd26 100644 --- a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_multi_scalar_view_selected.png +++ b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_multi_scalar_view_selected.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6675f4132ce13d037bbbf97c4302827931ce3ed272642d74d58ecdb643c7e3c3 -size 240440 +oid sha256:98e6df31f27a1a533939d8e853f0f04b9241a2cb8eb909ef9ab73d17ebf230e5 +size 239860 diff --git a/tests/rust/re_integration_test/tests/view_visualizers_test.rs b/tests/rust/re_integration_test/tests/view_visualizers_test.rs index e8b7d6889523..98abbefcdbfb 100644 --- a/tests/rust/re_integration_test/tests/view_visualizers_test.rs +++ b/tests/rust/re_integration_test/tests/view_visualizers_test.rs @@ -170,29 +170,36 @@ pub async fn test_view_visualizers_section() { // Snapshot 3: Selection panel showing visualizers for Other view harness.snapshot_app("view_visualizers_3_other_view_selected"); - // --- Test removing visualizers --- + // --- Test hiding visualizers --- // Select the Plots view again to see its 3 visualizers harness.blueprint_tree().click_label("Plots view"); harness.run(); - // Click the first "Remove visualizer" trash button (removes sin_line's first visualizer) + // Hover the first Visualizer for sin_line to reveal the hide button, but don't click it yet. harness .selection_panel() - .click_nth_label("Remove visualizer", 0); + .hover_nth_label("plots/sin_line", 0); harness.run(); - // Snapshot 4: After removing the first visualizer from sin_line - harness.snapshot_app("view_visualizers_4_after_remove_first"); + // Snapshot 4: Hovering the sin_line visualizer entry + harness.snapshot_app("view_visualizers_4_hover_sin_line"); + + // Click the hide button. + harness.selection_panel().click_nth_label("Hide series", 0); + harness.run(); - // Click the first trash button again (now removes whatever is first in the remaining list) + // Snapshot 5: After hiding another visualizer + harness.snapshot_app("view_visualizers_5_after_hide"); + + // Stop hovering. Hide button should still be there. harness .selection_panel() - .click_nth_label("Remove visualizer", 0); + .hover_nth_label("plots/sin_line", 1); harness.run(); - // Snapshot 5: After removing another visualizer - harness.snapshot_app("view_visualizers_5_after_remove_second"); + // Snapshot 6: After hiding another visualizer + harness.snapshot_app("view_visualizers_6_hover_different_after_hide"); } /// Test that shows the visualizer list when an entity logs multiple scalars per timestamp. @@ -288,8 +295,7 @@ pub async fn test_view_visualizers_multi_scalar() { /// This test: /// 1. Logs time series data /// 2. Creates a `TimeSeriesView` and selects it -/// 3. Removes a visualizer so that the "+" popup has an option to offer -/// 4. Opens the popup, adds the visualizer back, verifies it reappears +/// 3. Hides a visualizer which shouldn't affect the "+" popup options #[tokio::test(flavor = "multi_thread")] pub async fn test_view_visualizers_add_button() { let mut harness = viewer_test_utils::viewer_harness(&HarnessOptions { @@ -369,22 +375,20 @@ pub async fn test_view_visualizers_add_button() { // the "+" button is disabled (nothing new to add). harness.snapshot_app("view_visualizers_add_1_view_selected"); - // Remove the first visualizer so the popup has something to offer. - harness - .selection_panel() - .click_nth_label("Remove visualizer", 0); + // Hide the first visualizer so the popup has something to offer. + // Hover the first visualizer pill to reveal the hide button. + harness.selection_panel().hover_nth_label("trig/cos", 0); + harness.run(); + + // Click the hide button. + harness.selection_panel().click_nth_label("Hide series", 0); + harness.run(); // Click the "+" button to open the popup harness.selection_panel().click_label("Add new visualizer…"); - // Snapshot 2: Popup showing the removed entity's component as an option + // Snapshot 2: Popup not showing the hidden entity's component as an option harness.snapshot_app("view_visualizers_add_2_popup_open"); - - // Click the option to add the visualizer back. - harness.click_nth_label("Scalars:scalars", 0); - - // Snapshot 3: After adding, the visualizer should reappear in the list - harness.snapshot_app("view_visualizers_add_3_visualizer_added"); } /// Test adding multiple new visualizers to a view via the "+" button, using custom message formats. @@ -392,8 +396,8 @@ pub async fn test_view_visualizers_add_button() { /// This test: /// 1. Logs time series data for three entities using `DynamicArchetype` with custom component names /// 2. Creates a `TimeSeriesView` and selects it -/// 3. Removes two visualizers so the popup has options to offer -/// 4. Opens the popup — custom components should appear grouped under their respective entities +/// 3. Selects a visualizer pill and removes both waves/cos visualizers via the selection panel +/// 4. Opens the "+" popup — removed custom components appear grouped under their entity /// 5. Adds them back one at a time, verifying the list grows with each addition #[tokio::test(flavor = "multi_thread")] pub async fn test_view_visualizers_add_multiple() { @@ -500,40 +504,43 @@ pub async fn test_view_visualizers_add_multiple() { .right_click_label("Viewport (Grid container)"); harness.click_label("Expand all"); - // Select the view + // Select the view to see all visualizers in the selection panel. harness.blueprint_tree().click_label("Waves view"); + harness.snapshot_app("view_visualizers_add_multi_1_starting_state"); - // Remove the first two visualizers so the popup has options to offer. - // With entities sorted alphabetically, these are both from waves/cos. + // Select the first visualizer pill (waves/cos, first alphabetically) to bring up + // its details in the selection panel, which includes the "Remove visualizer" button. + harness.selection_panel().click_nth_label("waves/cos", 0); + harness.snapshot_app("view_visualizers_add_multi_2_selected_visualizer"); + + // Remove both waves/cos visualizers ("frequency" and "signal") so the popup has + // options to offer when we later click "+". harness .selection_panel() .click_nth_label("Remove visualizer", 0); harness .selection_panel() .click_nth_label("Remove visualizer", 0); + harness.snapshot_app("view_visualizers_add_multi_3_removed_visualizer"); - // Snapshot 1: View selected, two visualizers removed - harness.snapshot_app("view_visualizers_add_multi_1_view_selected"); + // Re-select the view to see the updated visualizer list (without waves/cos entries). + harness.blueprint_tree().click_label("Waves view"); + harness.snapshot_app("view_visualizers_add_multi_4_view_selected_again"); - // Open the add-visualizer popup + // Open the add-visualizer popup. harness.selection_panel().click_label("Add new visualizer…"); - // Snapshot 2: Popup showing custom components under their respective entity. - // Both removed fields ("frequency" and "signal") appear under waves/cos. - harness.snapshot_app("view_visualizers_add_multi_2_popup_open"); + // Both removed fields ("frequency" and "signal") should appear under waves/cos. + harness.snapshot_app("view_visualizers_add_multi_5_popup_open"); - // Add back one custom component (waves/cos → frequency) + // Add back one custom component (waves/cos → frequency). harness.click_label("wave_data:frequency"); + harness.snapshot_app("view_visualizers_add_multi_6_first_added"); - // Snapshot 3: First visualizer re-added to the list - harness.snapshot_app("view_visualizers_add_multi_3_first_added"); - - // Open the popup again and add the second custom component + // Open the popup again and add the second custom component. harness.selection_panel().click_label("Add new visualizer…"); - // Only one option remains (waves/cos → signal) + // Only one option remains (waves/cos → signal). harness.click_label("wave_data:signal"); - - // Snapshot 4: Second visualizer also re-added to the list - harness.snapshot_app("view_visualizers_add_multi_4_second_added"); + harness.snapshot_app("view_visualizers_add_multi_7_second_added"); } From 55fffd2623bacc1f41e9ff37a50e5a3afe08bc2b Mon Sep 17 00:00:00 2001 From: Nikolaus West Date: Mon, 2 Mar 2026 12:21:00 +0100 Subject: [PATCH 002/513] Expand and fix internal docs links Adds direct links to sub-sections of documentation that were missing and fixes some incorrect links that were forced to go through the redirect system (bad for local agents working on docs) --------- Source-Ref: f8b7662f88e4ec0d1db7544987e52c7d1b909462 Co-authored-by: Claude Opus 4.6 --- .../src/codegen/docs/website.rs | 2 +- .../content/concepts/logging-and-ingestion.md | 14 ++++++++++- .../logging-and-ingestion/transforms.md | 2 +- docs/content/concepts/visualization.md | 5 +++- .../concepts/visualization/blueprints.md | 4 +-- .../getting-started/configure-the-viewer.md | 2 +- .../navigating-the-viewer.md | 4 +-- .../build-a-blueprint-programmatically.md | 2 +- .../howto/visualization/plot-any-scalar.md | 2 +- docs/content/overview/resources.md | 25 ++++++++++++++----- docs/content/reference.md | 9 +++++++ .../reference/migration/migration-0-17.md | 4 +-- docs/content/reference/types/archetypes.md | 2 +- 13 files changed, 57 insertions(+), 20 deletions(-) diff --git a/crates/build/re_types_builder/src/codegen/docs/website.rs b/crates/build/re_types_builder/src/codegen/docs/website.rs index fbb91eef208f..dd5baba052b5 100644 --- a/crates/build/re_types_builder/src/codegen/docs/website.rs +++ b/crates/build/re_types_builder/src/codegen/docs/website.rs @@ -91,7 +91,7 @@ impl CodeGenerator for DocsCodeGenerator { 1, r"Archetypes are bundles of components for which the Rerun viewer has first-class built-in support. See [Entities and Components](../../concepts/logging-and-ingestion/entity-component.md) and -[Visualizers and Overrides](../../concepts/visualization/visualizers-and-overrides.md) for more information. +[Visualizers and Overrides](../../concepts/visualization/customize-views.md) for more information. This page lists all built-in archetypes.", &archetypes, diff --git a/docs/content/concepts/logging-and-ingestion.md b/docs/content/concepts/logging-and-ingestion.md index ead4aff02478..21868c520c28 100644 --- a/docs/content/concepts/logging-and-ingestion.md +++ b/docs/content/concepts/logging-and-ingestion.md @@ -5,4 +5,16 @@ order: 100 This section covers how data is structured and ingested into Rerun. - +- [Recordings](./logging-and-ingestion/recordings.md) - managing recordings and application IDs +- [Entities and Components](./logging-and-ingestion/entity-component.md) - Rerun's data model +- [The Entity Path Hierarchy](./logging-and-ingestion/entity-path.md) - organizing data hierarchically +- [Transforms & Coordinate Frames](./logging-and-ingestion/transforms.md) - working with coordinate systems +- [Events and Timelines](./logging-and-ingestion/timelines.md) - managing temporal data +- [Static data](./logging-and-ingestion/static.md) - data that exists across all timelines +- [Chunks](./logging-and-ingestion/chunks.md) - internal storage mechanism +- [Data-loaders](./logging-and-ingestion/data-loaders.md) - loading external file formats +- [Query semantics & partial updates](./logging-and-ingestion/latest-at.md) - how Rerun resolves data queries +- [MCAP files](./logging-and-ingestion/mcap.md) - working with MCAP files +- [Component Batches](./logging-and-ingestion/batches.md) - efficiently logging collections of data +- [Sinks](./logging-and-ingestion/sinks.md) - where logged data goes +- [Video](./logging-and-ingestion/video.md) - video data support diff --git a/docs/content/concepts/logging-and-ingestion/transforms.md b/docs/content/concepts/logging-and-ingestion/transforms.md index fa741fad5b7a..b4165d9cac78 100644 --- a/docs/content/concepts/logging-and-ingestion/transforms.md +++ b/docs/content/concepts/logging-and-ingestion/transforms.md @@ -51,7 +51,7 @@ This is useful to specify "default" transforms without yet knowing what timeline Named transform frames have several advantages over entity path based hierarchies: * topology may change over time -* association of entities with coordinate frames is explicit and may changed over time (it can also be [overridden via blueprint](../visualization/visualizers-and-overrides.md)) +* association of entities with coordinate frames is explicit and may changed over time (it can also be [overridden via blueprint](../visualization/customize-views.md)) * several entities may be associated with the same frame * frees up entity paths for semantic rather than geometric organization diff --git a/docs/content/concepts/visualization.md b/docs/content/concepts/visualization.md index d26b45e1ab07..4a6363e34542 100644 --- a/docs/content/concepts/visualization.md +++ b/docs/content/concepts/visualization.md @@ -5,4 +5,7 @@ order: 200 This section covers how data is visualized in the Rerun Viewer. - +- [Blueprints](./visualization/blueprints.md) - configuring visualization layouts and views +- [Customize views](./visualization/customize-views.md) - visualizers, overrides, and per-entity customization +- [Annotation Context](./visualization/annotation-context.md) - shared styling and labels +- [Entity Queries](./visualization/entity-queries.md) - controlling which entities appear in a view diff --git a/docs/content/concepts/visualization/blueprints.md b/docs/content/concepts/visualization/blueprints.md index c218db75732b..90882ff7294f 100644 --- a/docs/content/concepts/visualization/blueprints.md +++ b/docs/content/concepts/visualization/blueprints.md @@ -144,7 +144,7 @@ Under the hood, blueprints are just data. They are structured using the same [En - **Anything you modify in the Viewer can be saved and shared** as a blueprint file - **Blueprints can be produced programmatically** using just the Rerun SDK without depending on the Viewer -- **Blueprint data is fully expressive**, enabling [blueprint overrides](visualizers-and-overrides.md#per-entity-component-override) that are as powerful as logged data +- **Blueprint data is fully expressive**, enabling [blueprint overrides](customize-views.md#per-entity-component-override) that are as powerful as logged data - **The full time-series nature** simplifies future features like snapshots and undo/redo - **Debugging tools for Rerun data** can inspect blueprint state just like recording data @@ -163,5 +163,5 @@ This means the Viewer output is a deterministic function of the blueprint and th - **Learn to use blueprints**: See [Configure the Viewer](../../getting-started/configure-the-viewer.md) for hands-on tutorials covering interactive, file-based, and programmatic workflows - **Understand the UI**: Check the [Blueprint Panel Reference](../../reference/viewer/blueprints.md) for details on UI controls -- **Customize visualizations**: Learn about [Visualizers and Overrides](visualizers-and-overrides.md) for advanced per-entity customization +- **Customize visualizations**: Learn about [Visualizers and Overrides](customize-views.md) for advanced per-entity customization - **Explore the API**: Browse the [Blueprint API Reference](https://ref.rerun.io/docs/python/stable/common/blueprint_apis/) for programmatic control (Python) diff --git a/docs/content/getting-started/configure-the-viewer.md b/docs/content/getting-started/configure-the-viewer.md index a86cb3f70a07..f131c2ed452c 100644 --- a/docs/content/getting-started/configure-the-viewer.md +++ b/docs/content/getting-started/configure-the-viewer.md @@ -118,7 +118,7 @@ Let's explore an example of this hierarchy in our scene: The hierarchy of logged entity streams and their component streams is found under `Streams` in the Timeline panel. A similar list appears in the `Blueprint` panel, but the key difference is that the Blueprint panel focuses on how data is arranged and visualized in the Viewport, while the Streams panel shows when and what events were logged. In other words, an entity may be logged once but displayed in multiple views. -Visualizations can also be customized per each view using [Overrides](../concepts/visualization/visualizers-and-overrides.md) in the Selection panel. In the screenshot below, the same entity `keypoints` is displayed in different colors: yellow and magenta. This is reflected in Selection > Visualizers > Points2D > Color, where yellow is an overridden value, even though the logged color value was different. +Visualizations can also be customized per each view using [Overrides](../concepts/visualization/customize-views.md) in the Selection panel. In the screenshot below, the same entity `keypoints` is displayed in different colors: yellow and magenta. This is reflected in Selection > Visualizers > Points2D > Color, where yellow is an overridden value, even though the logged color value was different. diff --git a/docs/content/getting-started/configure-the-viewer/navigating-the-viewer.md b/docs/content/getting-started/configure-the-viewer/navigating-the-viewer.md index d4dfcdd2a184..ce35aa5dbcb4 100644 --- a/docs/content/getting-started/configure-the-viewer/navigating-the-viewer.md +++ b/docs/content/getting-started/configure-the-viewer/navigating-the-viewer.md @@ -168,7 +168,7 @@ When selecting a view, you can also set default component values that apply when -See [Visualizers and Overrides](../../concepts/visualization/visualizers-and-overrides.md) for detailed information. +See [Visualizers and Overrides](../../concepts/visualization/customize-views.md) for detailed information. --- @@ -211,5 +211,5 @@ This is particularly valuable for: ## Next steps - **Explore view types**: Check the [View Type Reference](../../reference/types/views/) to see all available views and their configuration options -- **Learn about overrides**: See [Visualizers and Overrides](../../concepts/visualization/visualizers-and-overrides.md) for per-entity customization +- **Learn about overrides**: See [Visualizers and Overrides](../../concepts/visualization/customize-views.md) for per-entity customization - **API Reference**: Browse the complete [Blueprint API](https://ref.rerun.io/docs/python/stable/common/blueprint_apis/) for programmatic control diff --git a/docs/content/howto/visualization/build-a-blueprint-programmatically.md b/docs/content/howto/visualization/build-a-blueprint-programmatically.md index 5a4d5f64d771..8adcfbaa2eb6 100644 --- a/docs/content/howto/visualization/build-a-blueprint-programmatically.md +++ b/docs/content/howto/visualization/build-a-blueprint-programmatically.md @@ -389,7 +389,7 @@ rrb.TimeSeriesView( ) ``` -See [Visualizers and Overrides](../../concepts/visualization/visualizers-and-overrides.md) for information on overriding component values and controlling visualizers from code. +See [Visualizers and Overrides](../../concepts/visualization/customize-views.md) for information on overriding component values and controlling visualizers from code. --- diff --git a/docs/content/howto/visualization/plot-any-scalar.md b/docs/content/howto/visualization/plot-any-scalar.md index e6740d47143e..033e4638037d 100644 --- a/docs/content/howto/visualization/plot-any-scalar.md +++ b/docs/content/howto/visualization/plot-any-scalar.md @@ -19,7 +19,7 @@ The supported data types are: - `Boolean` - Any of the above nested inside of [Arrow structs](https://arrow.apache.org/docs/format/Intro.html#struct). -For background on how visualizers resolve component values, see [Customize views](../../concepts/visualization/visualizers-and-overrides.md?speculative-link). +For background on how visualizers resolve component values, see [Customize views](../../concepts/visualization/customize-views.md?speculative-link). ## Logging custom data diff --git a/docs/content/overview/resources.md b/docs/content/overview/resources.md index 6bcbd71f153b..8cc6b94ab908 100644 --- a/docs/content/overview/resources.md +++ b/docs/content/overview/resources.md @@ -36,7 +36,7 @@ Understanding the foundational concepts behind Rerun: - **[Query Semantics](../concepts/logging-and-ingestion/latest-at.md)** - How Rerun resolves data queries - **[Annotation Context](../concepts/visualization/annotation-context.md)** - Shared styling and labels - **[Recordings](../concepts/logging-and-ingestion/recordings.md)** - Managing recordings, application IDs, and the Data Platform -- **[Visualizers and Overrides](../concepts/visualization/visualizers-and-overrides.md)** - Customizing rendering +- **[Visualizers and Overrides](../concepts/visualization/customize-views.md)** - Customizing rendering - **[Chunks](../concepts/logging-and-ingestion/chunks.md)** - Internal storage mechanism (advanced) ## How-to guides @@ -80,11 +80,24 @@ Practical guides for specific tasks and advanced features: Detailed API documentation and technical specifications: -- **[Reference Documentation](../reference.md)** - Complete API reference for all supported languages - - Types (Archetypes, Components, Datatypes) - - SDKs (Python, Rust, C++) - - Viewer and CLI commands - - Migration guides +### Types +- **[Archetypes](../reference/types/archetypes.md)** - Bundles of components with first-class viewer support +- **[Components](../reference/types/components.md)** - Individual data components used by archetypes +- **[Datatypes](../reference/types/datatypes.md)** - Fundamental data structures +- **[Views](../reference/types/views.md)** - Available visualization view types + +### SDKs +- **[Python APIs](https://ref.rerun.io/docs/python)** - Python SDK reference +- **[Rust APIs](https://docs.rs/rerun/)** - Rust SDK reference +- **[C++ APIs](https://ref.rerun.io/docs/cpp)** - C++ SDK reference +- **[Web Viewer API](https://ref.rerun.io/docs/js/)** - JavaScript/TypeScript web viewer API + +### Viewer & CLI +- **[Viewer](../reference/viewer/overview.md)** - Viewer UI overview and features +- **[CLI Manual](../reference/cli.md)** - Command-line interface reference + +### Migration guides +- **[Migration](../reference/migration.md)** - Guides for upgrading between Rerun versions ## Development diff --git a/docs/content/reference.md b/docs/content/reference.md index 24ba4696d86f..863a58acb4e2 100644 --- a/docs/content/reference.md +++ b/docs/content/reference.md @@ -4,3 +4,12 @@ order: 2 --- The reference docs detail how to use the logging APIs and the viewer. + +- [Types](./reference/types.md) - archetypes, components, and datatypes +- [Viewer](./reference/viewer/overview.md) - the Rerun Viewer UI +- [CLI manual](./reference/cli.md) - command-line interface reference +- [Python APIs](https://ref.rerun.io/docs/python) - Python SDK reference +- [Rust APIs](https://docs.rs/rerun/) - Rust SDK reference +- [C++ APIs](https://ref.rerun.io/docs/cpp) - C++ SDK reference +- [Web Viewer API](https://ref.rerun.io/docs/js/) - JavaScript/TypeScript web viewer API +- [Migration](./reference/migration.md) - guides for upgrading between versions diff --git a/docs/content/reference/migration/migration-0-17.md b/docs/content/reference/migration/migration-0-17.md index 4a0c209ef143..78146b8879a1 100644 --- a/docs/content/reference/migration/migration-0-17.md +++ b/docs/content/reference/migration/migration-0-17.md @@ -28,7 +28,7 @@ In 0.17.0: -See [Visualizers and Overrides](../../concepts/visualization/visualizers-and-overrides.md) for more information. +See [Visualizers and Overrides](../../concepts/visualization/customize-views.md) for more information. ## New blueprint API to specify component overrides, visualizer overrides, and component defaults @@ -69,7 +69,7 @@ rr.send_blueprint( ) ``` -The [Plots](https://rerun.io/examples/feature-showcase/plots) example has been updated to showcase the new APIs. See [Visualizers and Overrides](../../concepts/visualization/visualizers-and-overrides.md) for more information. +The [Plots](https://rerun.io/examples/feature-showcase/plots) example has been updated to showcase the new APIs. See [Visualizers and Overrides](../../concepts/visualization/customize-views.md) for more information. ## New and changed components diff --git a/docs/content/reference/types/archetypes.md b/docs/content/reference/types/archetypes.md index 4a97d91570da..b6fbefd562a1 100644 --- a/docs/content/reference/types/archetypes.md +++ b/docs/content/reference/types/archetypes.md @@ -6,7 +6,7 @@ order: 1 Archetypes are bundles of components for which the Rerun viewer has first-class built-in support. See [Entities and Components](../../concepts/logging-and-ingestion/entity-component.md) and -[Visualizers and Overrides](../../concepts/visualization/visualizers-and-overrides.md) for more information. +[Visualizers and Overrides](../../concepts/visualization/customize-views.md) for more information. This page lists all built-in archetypes. From 0b60d30732b5fbdb713bbe688fd07502c9f5dfb6 Mon Sep 17 00:00:00 2001 From: Tim Saucer Date: Mon, 2 Mar 2026 07:39:59 -0500 Subject: [PATCH 003/513] Upgrade to Lance 2, DataFusion 51, and Arrow 57 This PR is only a dependency update and the minimal changes required to support the upgrades. Closes https://linear.app/rerun/issue/RR-2855 --------- Source-Ref: 6a26cee45b45a6c72f055ddb589ed753a006b7bb Co-authored-by: Zeljko Mihaljcic <7150613+zehiko@users.noreply.github.com> Co-authored-by: Clement Rey --- Cargo.lock | 1301 +++++++++-------- Cargo.toml | 23 +- .../src/codegen/rust/arrow.rs | 4 +- .../src/codegen/rust/serializer.rs | 4 +- .../test_mcap_loader__tests__ros2.snap | 4 +- .../src/batch_coalescer/coalesce_exec.rs | 1 - .../src/datatypes/tensor_buffer.rs | 7 +- .../src/testing/components/affix_fuzzer15.rs | 5 +- .../src/testing/datatypes/affix_fuzzer3.rs | 7 +- .../src/testing/datatypes/affix_fuzzer4.rs | 7 +- .../store/re_server/src/chunk_index/search.rs | 4 +- .../src/datatypes/time_range_boundary.rs | 7 +- crates/utils/re_arrow_util/src/arrays.rs | 12 +- crates/utils/re_arrow_util/src/batches.rs | 16 +- .../re_arrow_ui/tests/arrow_test_data/mod.rs | 5 +- .../re_dataframe_ui/src/filters/boolean.rs | 8 +- ...uals_list_non_nullable@both_null_true.snap | 6 - ...als_list_non_nullable@inner_null_true.snap | 7 - ...equals_list_non_nullable@no_null_true.snap | 8 - ...als_list_non_nullable@outer_null_true.snap | 7 - ...s_list_nullable@both_null_is_not_true.snap | 6 - ...quals_list_nullable@both_null_is_null.snap | 6 - ...quals_list_nullable@both_null_is_true.snap | 6 - ..._list_nullable@inner_null_is_not_true.snap | 7 - ...uals_list_nullable@inner_null_is_null.snap | 7 - ...uals_list_nullable@inner_null_is_true.snap | 7 - ..._list_nullable@outer_null_is_not_true.snap | 7 - ...uals_list_nullable@outer_null_is_null.snap | 7 - ...uals_list_nullable@outer_null_is_true.snap | 7 - ...er_tests__float_all_types@Float32Type.snap | 4 - ...er_tests__float_all_types@Float64Type.snap | 4 - .../filter_tests__float_compares@eq_3.0.snap | 4 - .../filter_tests__float_compares@ge_3.0.snap | 4 - .../filter_tests__float_compares@gt_3.0.snap | 4 - .../filter_tests__float_compares@le_3.0.snap | 4 - .../filter_tests__float_compares@lt_3.0.snap | 4 - .../filter_tests__float_compares@ne_3.0.snap | 4 - ...lter_tests__float_compares@nulls_eq_4.snap | 3 - ...lter_tests__float_compares@nulls_ge_4.snap | 3 - ...lter_tests__float_compares@nulls_gt_4.snap | 3 - ...lter_tests__float_compares@nulls_le_4.snap | 3 - ...lter_tests__float_compares@nulls_lt_4.snap | 3 - ...lter_tests__float_compares@nulls_ne_4.snap | 3 - .../filter_tests__float_lists@eq_2.0.snap | 8 - .../filter_tests__float_lists@ge_2.0.snap | 8 - .../filter_tests__float_lists@gt_2.0.snap | 8 - .../filter_tests__float_lists@le_2.0.snap | 8 - .../filter_tests__float_lists@lt_2.0.snap | 8 - .../filter_tests__float_lists@ne_2.0.snap | 8 - ...ilter_tests__float_lists@nulls_eq_2.0.snap | 7 - ...ilter_tests__float_lists@nulls_ge_2.0.snap | 7 - ...ilter_tests__float_lists@nulls_gt_2.0.snap | 7 - ...ilter_tests__float_lists@nulls_le_2.0.snap | 7 - ...ilter_tests__float_lists@nulls_lt_2.0.snap | 7 - ...ilter_tests__float_lists@nulls_ne_2.0.snap | 7 - ...filter_tests__int_all_types@Int16Type.snap | 4 - ...filter_tests__int_all_types@Int32Type.snap | 4 - ...filter_tests__int_all_types@Int64Type.snap | 4 - .../filter_tests__int_all_types@Int8Type.snap | 4 - ...ilter_tests__int_all_types@UInt16Type.snap | 4 - ...ilter_tests__int_all_types@UInt32Type.snap | 4 - ...ilter_tests__int_all_types@UInt64Type.snap | 4 - ...filter_tests__int_all_types@UInt8Type.snap | 4 - .../filter_tests__int_compares@eq_3.snap | 4 - .../filter_tests__int_compares@ge_3.snap | 4 - .../filter_tests__int_compares@gt_3.snap | 4 - .../filter_tests__int_compares@le_3.snap | 4 - .../filter_tests__int_compares@lt_3.snap | 4 - .../filter_tests__int_compares@ne_3.snap | 4 - ...filter_tests__int_compares@nulls_eq_4.snap | 3 - ...ts__int_compares@nulls_eq_unspecified.snap | 3 - ...filter_tests__int_compares@nulls_ge_4.snap | 3 - ...ts__int_compares@nulls_ge_unspecified.snap | 3 - ...filter_tests__int_compares@nulls_gt_4.snap | 3 - ...ts__int_compares@nulls_gt_unspecified.snap | 3 - ...filter_tests__int_compares@nulls_le_4.snap | 3 - ...ts__int_compares@nulls_le_unspecified.snap | 3 - ...filter_tests__int_compares@nulls_lt_4.snap | 3 - ...ts__int_compares@nulls_lt_unspecified.snap | 3 - ...filter_tests__int_compares@nulls_ne_4.snap | 3 - ...ts__int_compares@nulls_ne_unspecified.snap | 3 - .../filter_tests__int_lists@eq_2.snap | 8 - .../filter_tests__int_lists@ge_2.snap | 8 - .../filter_tests__int_lists@gt_2.snap | 8 - .../filter_tests__int_lists@le_2.snap | 8 - .../filter_tests__int_lists@lt_2.snap | 8 - .../filter_tests__int_lists@ne_2.snap | 8 - .../filter_tests__int_lists@nulls_eq_2.snap | 7 - .../filter_tests__int_lists@nulls_ge_2.snap | 7 - .../filter_tests__int_lists@nulls_gt_2.snap | 7 - .../filter_tests__int_lists@nulls_le_2.snap | 7 - .../filter_tests__int_lists@nulls_lt_2.snap | 7 - .../filter_tests__int_lists@nulls_ne_2.snap | 7 - ...ts__non_nullable_boolean_equals@false.snap | 4 - ...n_nullable_boolean_equals@nulls_false.snap | 3 - ...on_nullable_boolean_equals@nulls_true.snap | 3 - ...sts__non_nullable_boolean_equals@true.snap | 4 - ...__nullable_boolean_equals@nulls_false.snap | 3 - ...able_boolean_equals@nulls_is_not_null.snap | 3 - ...able_boolean_equals@nulls_is_not_true.snap | 3 - ...s__nullable_boolean_equals@nulls_null.snap | 3 - ...s__nullable_boolean_equals@nulls_true.snap | 3 - .../filter_tests__string_contains@a.snap | 4 - ...er_tests__string_contains@a_uppercase.snap | 4 - .../filter_tests__string_contains@ab.snap | 4 - ...s__string_contains@does_not_contain_b.snap | 4 - .../filter_tests__string_contains@empty.snap | 4 - ...er_tests__string_contains@ends_with_c.snap | 4 - ...filter_tests__string_contains@nulls_a.snap | 3 - ...ing_contains@nulls_does_not_contain_b.snap | 3 - ...er_tests__string_contains@nulls_empty.snap | 3 - ...ts__string_contains@nulls_ends_with_c.snap | 3 - ...__string_contains@nulls_starts_with_b.snap | 3 - ..._tests__string_contains@starts_with_b.snap | 4 - ...ts__string_list@both_null_Contains_ab.snap | 6 - ...ring_list@both_null_DoesNotContain_ab.snap | 6 - ...ts__string_list@both_null_EndsWith_ab.snap | 6 - ...__string_list@both_null_StartsWith_ab.snap | 6 - ...s__string_list@inner_null_Contains_ab.snap | 7 - ...ing_list@inner_null_DoesNotContain_ab.snap | 7 - ...s__string_list@inner_null_EndsWith_ab.snap | 7 - ..._string_list@inner_null_StartsWith_ab.snap | 7 - ...ests__string_list@no_null_Contains_ab.snap | 8 - ...string_list@no_null_DoesNotContain_ab.snap | 8 - ...ests__string_list@no_null_EndsWith_ab.snap | 8 - ...ts__string_list@no_null_StartsWith_ab.snap | 8 - ...s__string_list@outer_null_Contains_ab.snap | 7 - ...ing_list@outer_null_DoesNotContain_ab.snap | 7 - ...s__string_list@outer_null_EndsWith_ab.snap | 7 - ..._string_list@outer_null_StartsWith_ab.snap | 7 - ...r_tests__timestamps@after_Microsecond.snap | 8 +- ...s__timestamps@after_Microsecond_nulls.snap | 7 +- ...r_tests__timestamps@after_Millisecond.snap | 8 +- ...s__timestamps@after_Millisecond_nulls.snap | 7 +- ...er_tests__timestamps@after_Nanosecond.snap | 8 +- ...ts__timestamps@after_Nanosecond_nulls.snap | 7 +- ...filter_tests__timestamps@after_Second.snap | 8 +- ..._tests__timestamps@after_Second_nulls.snap | 7 +- ...__timestamps@after_strict_Microsecond.snap | 8 +- ...stamps@after_strict_Microsecond_nulls.snap | 7 +- ...__timestamps@after_strict_Millisecond.snap | 8 +- ...stamps@after_strict_Millisecond_nulls.snap | 7 +- ...s__timestamps@after_strict_Nanosecond.snap | 8 +- ...estamps@after_strict_Nanosecond_nulls.snap | 7 +- ...tests__timestamps@after_strict_Second.snap | 8 +- ..._timestamps@after_strict_Second_nulls.snap | 7 +- ...tests__timestamps@between_Microsecond.snap | 8 +- ..._timestamps@between_Microsecond_nulls.snap | 7 +- ...tests__timestamps@between_Millisecond.snap | 8 +- ..._timestamps@between_Millisecond_nulls.snap | 7 +- ..._tests__timestamps@between_Nanosecond.snap | 8 +- ...__timestamps@between_Nanosecond_nulls.snap | 7 +- ...lter_tests__timestamps@between_Second.snap | 8 +- ...ests__timestamps@between_Second_nulls.snap | 7 +- ...sts__timestamps@not_after_Microsecond.snap | 8 +- ...imestamps@not_after_Microsecond_nulls.snap | 7 +- ...sts__timestamps@not_after_Millisecond.snap | 8 +- ...imestamps@not_after_Millisecond_nulls.snap | 7 +- ...ests__timestamps@not_after_Nanosecond.snap | 8 +- ...timestamps@not_after_Nanosecond_nulls.snap | 7 +- ...er_tests__timestamps@not_after_Second.snap | 8 +- ...ts__timestamps@not_after_Second_nulls.snap | 7 +- ...ests__timestamps_list@after_both_null.snap | 41 +- ...sts__timestamps_list@after_inner_null.snap | 42 +- ..._tests__timestamps_list@after_no_null.snap | 32 +- ...sts__timestamps_list@after_outer_null.snap | 31 +- ...imestamps_list@after_strict_both_null.snap | 33 +- ...mestamps_list@after_strict_inner_null.snap | 34 +- ..._timestamps_list@after_strict_no_null.snap | 28 +- ...mestamps_list@after_strict_outer_null.snap | 27 +- ...ts__timestamps_list@between_both_null.snap | 31 +- ...s__timestamps_list@between_inner_null.snap | 32 +- ...ests__timestamps_list@between_no_null.snap | 26 +- ...s__timestamps_list@between_outer_null.snap | 25 +- ...__timestamps_list@not_after_both_null.snap | 33 +- ..._timestamps_list@not_after_inner_null.snap | 34 +- ...ts__timestamps_list@not_after_no_null.snap | 26 +- ..._timestamps_list@not_after_outer_null.snap | 25 +- examples/python/server_tables/pyproject.toml | 4 +- .../rust/custom_callback/src/comms/viewer.rs | 7 - rerun_py/pyproject.toml | 8 +- rerun_py/src/catalog/registration_handle.rs | 2 +- rerun_py/src/python_bridge.rs | 74 +- rerun_py/src/recording/rrd.rs | 4 +- rerun_py/src/urdf.rs | 9 +- rerun_py/src/utils.rs | 4 +- uv.lock | 26 +- 187 files changed, 1083 insertions(+), 1804 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 76569ebadddf..0d1449c16d82 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -382,7 +382,7 @@ dependencies = [ "argh_shared", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -414,9 +414,9 @@ checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" [[package]] name = "arrow" -version = "56.1.0" +version = "57.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c26b57282a08ae92f727497805122fec964c6245cfa0e13f0e75452eaf3bc41f" +checksum = "2a2b10dcb159faf30d3f81f6d56c1211a5bea2ca424eabe477648a44b993320e" dependencies = [ "arrow-arith", "arrow-array", @@ -436,23 +436,23 @@ dependencies = [ [[package]] name = "arrow-arith" -version = "56.2.0" +version = "57.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad08897b81588f60ba983e3ca39bda2b179bdd84dced378e7df81a5313802ef8" +checksum = "288015089e7931843c80ed4032c5274f02b37bcb720c4a42096d50b390e70372" dependencies = [ "arrow-array", "arrow-buffer", "arrow-data", "arrow-schema", "chrono", - "num", + "num-traits", ] [[package]] name = "arrow-array" -version = "56.2.0" +version = "57.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8548ca7c070d8db9ce7aa43f37393e4bfcf3f2d3681df278490772fd1673d08d" +checksum = "65ca404ea6191e06bf30956394173337fa9c35f445bd447fe6c21ab944e1a23c" dependencies = [ "ahash", "arrow-buffer", @@ -462,29 +462,33 @@ dependencies = [ "chrono-tz", "half", "hashbrown 0.16.0", - "num", + "num-complex", + "num-integer", + "num-traits", ] [[package]] name = "arrow-buffer" -version = "56.2.0" +version = "57.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e003216336f70446457e280807a73899dd822feaf02087d31febca1363e2fccc" +checksum = "36356383099be0151dacc4245309895f16ba7917d79bdb71a7148659c9206c56" dependencies = [ "bytes", "half", - "num", + "num-bigint", + "num-traits", ] [[package]] name = "arrow-cast" -version = "56.1.0" +version = "57.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eed61d9d73eda8df9e3014843def37af3050b5080a9acbe108f045a316d5a0be" +checksum = "9c8e372ed52bd4ee88cc1e6c3859aa7ecea204158ac640b10e187936e7e87074" dependencies = [ "arrow-array", "arrow-buffer", "arrow-data", + "arrow-ord", "arrow-schema", "arrow-select", "atoi", @@ -493,15 +497,15 @@ dependencies = [ "comfy-table", "half", "lexical-core", - "num", + "num-traits", "ryu", ] [[package]] name = "arrow-csv" -version = "56.1.0" +version = "57.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa95b96ce0c06b4d33ac958370db8c0d31e88e54f9d6e08b0353d18374d9f991" +checksum = "8e4100b729fe656f2e4fb32bc5884f14acf9118d4ad532b7b33c1132e4dce896" dependencies = [ "arrow-array", "arrow-cast", @@ -514,21 +518,22 @@ dependencies = [ [[package]] name = "arrow-data" -version = "56.2.0" +version = "57.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5c64fff1d142f833d78897a772f2e5b55b36cb3e6320376f0961ab0db7bd6d0" +checksum = "bf87f4ff5fc13290aa47e499a8b669a82c5977c6a1fedce22c7f542c1fd5a597" dependencies = [ "arrow-buffer", "arrow-schema", "half", - "num", + "num-integer", + "num-traits", ] [[package]] name = "arrow-ipc" -version = "56.2.0" +version = "57.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d3594dcddccc7f20fd069bc8e9828ce37220372680ff638c5e00dea427d88f5" +checksum = "eb3ca63edd2073fcb42ba112f8ae165df1de935627ead6e203d07c99445f2081" dependencies = [ "arrow-array", "arrow-buffer", @@ -536,15 +541,15 @@ dependencies = [ "arrow-schema", "arrow-select", "flatbuffers", - "lz4_flex 0.11.5", + "lz4_flex 0.12.0", "zstd", ] [[package]] name = "arrow-json" -version = "56.1.0" +version = "57.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d747573390905905a2dc4c5a61a96163fe2750457f90a04ee2a88680758c79" +checksum = "a36b2332559d3310ebe3e173f75b29989b4412df4029a26a30cc3f7da0869297" dependencies = [ "arrow-array", "arrow-buffer", @@ -553,20 +558,22 @@ dependencies = [ "arrow-schema", "chrono", "half", - "indexmap 2.11.4", + "indexmap 2.12.0", + "itoa", "lexical-core", "memchr", - "num", - "serde", + "num-traits", + "ryu", + "serde_core", "serde_json", "simdutf8", ] [[package]] name = "arrow-ord" -version = "56.2.0" +version = "57.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c8f82583eb4f8d84d4ee55fd1cb306720cddead7596edce95b50ee418edf66f" +checksum = "13c4e0530272ca755d6814218dffd04425c5b7854b87fa741d5ff848bf50aa39" dependencies = [ "arrow-array", "arrow-buffer", @@ -577,9 +584,9 @@ dependencies = [ [[package]] name = "arrow-pyarrow" -version = "56.2.0" +version = "57.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d924b32e96f8bb74d94cd82bd97b313c432fcb0ea331689ef9e7c6b8be4b258" +checksum = "f45c7989cb70214b2f362eaa10266d15e1a433692f2ea1514018be3aace679f4" dependencies = [ "arrow-array", "arrow-data", @@ -589,9 +596,9 @@ dependencies = [ [[package]] name = "arrow-row" -version = "56.2.0" +version = "57.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d07ba24522229d9085031df6b94605e0f4b26e099fb7cdeec37abd941a73753" +checksum = "b07f52788744cc71c4628567ad834cadbaeb9f09026ff1d7a4120f69edf7abd3" dependencies = [ "arrow-array", "arrow-buffer", @@ -602,34 +609,34 @@ dependencies = [ [[package]] name = "arrow-schema" -version = "56.2.0" +version = "57.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3aa9e59c611ebc291c28582077ef25c97f1975383f1479b12f3b9ffee2ffabe" +checksum = "6bb63203e8e0e54b288d0d8043ca8fa1013820822a27692ef1b78a977d879f2c" dependencies = [ "bitflags 2.9.4", - "serde", + "serde_core", "serde_json", ] [[package]] name = "arrow-select" -version = "56.2.0" +version = "57.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c41dbbd1e97bfcaee4fcb30e29105fb2c75e4d82ae4de70b792a5d3f66b2e7a" +checksum = "c96d8a1c180b44ecf2e66c9a2f2bbcb8b1b6f14e165ce46ac8bde211a363411b" dependencies = [ "ahash", "arrow-array", "arrow-buffer", "arrow-data", "arrow-schema", - "num", + "num-traits", ] [[package]] name = "arrow-string" -version = "56.2.0" +version = "57.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53f5183c150fbc619eede22b861ea7c0eebed8eaac0333eaa7f6da5205fd504d" +checksum = "a8ad6a81add9d3ea30bf8374ee8329992c7fd246ffd8b7e2f48a3cea5aa0cc9a" dependencies = [ "arrow-array", "arrow-buffer", @@ -637,7 +644,7 @@ dependencies = [ "arrow-schema", "arrow-select", "memchr", - "num", + "num-traits", "regex", "regex-syntax", ] @@ -711,15 +718,11 @@ version = "0.4.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06575e6a9673580f52661c92107baabffbf41e2141373441cbcdc47cb733003c" dependencies = [ - "bzip2 0.5.2", "flate2", "futures-core", "memchr", "pin-project-lite", "tokio", - "xz2", - "zstd", - "zstd-safe", ] [[package]] @@ -800,7 +803,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -840,7 +843,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -857,7 +860,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -901,7 +904,7 @@ checksum = "49c98dba06b920588de7d63f6acc23f1e6a9fade5fd6198e564506334fb5a4f5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -1291,7 +1294,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -1355,7 +1358,7 @@ checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -1385,34 +1388,6 @@ dependencies = [ "bytes", ] -[[package]] -name = "bzip2" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49ecfb22d906f800d4fe833b6282cf4dc1c298f5057ca0b5445e5c209735ca47" -dependencies = [ - "bzip2-sys", -] - -[[package]] -name = "bzip2" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bea8dcd42434048e4f7a304411d9273a411f647446c1234a65ce0554923f4cff" -dependencies = [ - "libbz2-rs-sys", -] - -[[package]] -name = "bzip2-sys" -version = "0.1.13+1.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225bff33b2141874fe80d71e07d6eec4f85c5c216453dd96388240f96e1acc14" -dependencies = [ - "cc", - "pkg-config", -] - [[package]] name = "cacache" version = "13.1.0" @@ -1735,7 +1710,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -1838,7 +1813,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f76990911f2267d837d9d0ad060aa63aaad170af40904b29461734c339030d4d" dependencies = [ "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -2302,7 +2277,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -2313,7 +2288,7 @@ checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" dependencies = [ "darling_core", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -2347,25 +2322,23 @@ dependencies = [ [[package]] name = "datafusion" -version = "50.3.0" +version = "51.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2af15bb3c6ffa33011ef579f6b0bcbe7c26584688bd6c994f548e44df67f011a" +checksum = "8ba7cb113e9c0bedf9e9765926031e132fa05a1b09ba6e93a6d1a4d7044457b8" dependencies = [ "arrow", - "arrow-ipc", "arrow-schema", "async-trait", "bytes", - "bzip2 0.6.0", "chrono", "datafusion-catalog", "datafusion-catalog-listing", "datafusion-common", "datafusion-common-runtime", "datafusion-datasource", + "datafusion-datasource-arrow", "datafusion-datasource-csv", "datafusion-datasource-json", - "datafusion-datasource-parquet", "datafusion-execution", "datafusion-expr", "datafusion-expr-common", @@ -2382,29 +2355,26 @@ dependencies = [ "datafusion-physical-plan", "datafusion-session", "datafusion-sql", - "flate2", "futures", "itertools 0.14.0", "log", "object_store", "parking_lot", - "parquet", "rand 0.9.2", "regex", + "rstest", "sqlparser", "tempfile", "tokio", "url", "uuid", - "xz2", - "zstd", ] [[package]] name = "datafusion-catalog" -version = "50.3.0" +version = "51.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "187622262ad8f7d16d3be9202b4c1e0116f1c9aa387e5074245538b755261621" +checksum = "66a3a799f914a59b1ea343906a0486f17061f39509af74e874a866428951130d" dependencies = [ "arrow", "async-trait", @@ -2417,7 +2387,6 @@ dependencies = [ "datafusion-physical-expr", "datafusion-physical-plan", "datafusion-session", - "datafusion-sql", "futures", "itertools 0.14.0", "log", @@ -2428,9 +2397,9 @@ dependencies = [ [[package]] name = "datafusion-catalog-listing" -version = "50.3.0" +version = "51.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9657314f0a32efd0382b9a46fdeb2d233273ece64baa68a7c45f5a192daf0f83" +checksum = "6db1b113c80d7a0febcd901476a57aef378e717c54517a163ed51417d87621b0" dependencies = [ "arrow", "async-trait", @@ -2440,10 +2409,11 @@ dependencies = [ "datafusion-execution", "datafusion-expr", "datafusion-physical-expr", + "datafusion-physical-expr-adapter", "datafusion-physical-expr-common", "datafusion-physical-plan", - "datafusion-session", "futures", + "itertools 0.14.0", "log", "object_store", "tokio", @@ -2451,24 +2421,22 @@ dependencies = [ [[package]] name = "datafusion-common" -version = "50.3.0" +version = "51.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a83760d9a13122d025fbdb1d5d5aaf93dd9ada5e90ea229add92aa30898b2d1" +checksum = "7c10f7659e96127d25e8366be7c8be4109595d6a2c3eac70421f380a7006a1b0" dependencies = [ "ahash", "arrow", "arrow-ipc", - "base64 0.22.1", "chrono", "half", "hashbrown 0.14.5", - "indexmap 2.11.4", + "indexmap 2.12.0", "libc", "log", "object_store", "parquet", "paste", - "recursive", "sqlparser", "tokio", "web-time", @@ -2476,9 +2444,9 @@ dependencies = [ [[package]] name = "datafusion-common-runtime" -version = "50.3.0" +version = "51.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b6234a6c7173fe5db1c6c35c01a12b2aa0f803a3007feee53483218817f8b1e" +checksum = "b92065bbc6532c6651e2f7dd30b55cba0c7a14f860c7e1d15f165c41a1868d95" dependencies = [ "futures", "log", @@ -2487,15 +2455,13 @@ dependencies = [ [[package]] name = "datafusion-datasource" -version = "50.3.0" +version = "51.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7256c9cb27a78709dd42d0c80f0178494637209cac6e29d5c93edd09b6721b86" +checksum = "fde13794244bc7581cd82f6fff217068ed79cdc344cafe4ab2c3a1c3510b38d6" dependencies = [ "arrow", - "async-compression", "async-trait", "bytes", - "bzip2 0.6.0", "chrono", "datafusion-common", "datafusion-common-runtime", @@ -2506,38 +2472,54 @@ dependencies = [ "datafusion-physical-expr-common", "datafusion-physical-plan", "datafusion-session", - "flate2", "futures", "glob", "itertools 0.14.0", "log", "object_store", - "parquet", "rand 0.9.2", - "tempfile", "tokio", - "tokio-util", "url", - "xz2", - "zstd", +] + +[[package]] +name = "datafusion-datasource-arrow" +version = "51.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "804fa9b4ecf3157982021770617200ef7c1b2979d57bec9044748314775a9aea" +dependencies = [ + "arrow", + "arrow-ipc", + "async-trait", + "bytes", + "datafusion-common", + "datafusion-common-runtime", + "datafusion-datasource", + "datafusion-execution", + "datafusion-expr", + "datafusion-physical-expr-common", + "datafusion-physical-plan", + "datafusion-session", + "futures", + "itertools 0.14.0", + "object_store", + "tokio", ] [[package]] name = "datafusion-datasource-csv" -version = "50.3.0" +version = "51.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64533a90f78e1684bfb113d200b540f18f268134622d7c96bbebc91354d04825" +checksum = "61a1641a40b259bab38131c5e6f48fac0717bedb7dc93690e604142a849e0568" dependencies = [ "arrow", "async-trait", "bytes", - "datafusion-catalog", "datafusion-common", "datafusion-common-runtime", "datafusion-datasource", "datafusion-execution", "datafusion-expr", - "datafusion-physical-expr", "datafusion-physical-expr-common", "datafusion-physical-plan", "datafusion-session", @@ -2549,49 +2531,44 @@ dependencies = [ [[package]] name = "datafusion-datasource-json" -version = "50.3.0" +version = "51.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d7ebeb12c77df0aacad26f21b0d033aeede423a64b2b352f53048a75bf1d6e6" +checksum = "adeacdb00c1d37271176f8fb6a1d8ce096baba16ea7a4b2671840c5c9c64fe85" dependencies = [ "arrow", "async-trait", "bytes", - "datafusion-catalog", "datafusion-common", "datafusion-common-runtime", "datafusion-datasource", "datafusion-execution", "datafusion-expr", - "datafusion-physical-expr", "datafusion-physical-expr-common", "datafusion-physical-plan", "datafusion-session", "futures", "object_store", - "serde_json", "tokio", ] [[package]] name = "datafusion-datasource-parquet" -version = "50.3.0" +version = "51.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09e783c4c7d7faa1199af2df4761c68530634521b176a8d1331ddbc5a5c75133" +checksum = "43d0b60ffd66f28bfb026565d62b0a6cbc416da09814766a3797bba7d85a3cd9" dependencies = [ "arrow", "async-trait", "bytes", - "datafusion-catalog", "datafusion-common", "datafusion-common-runtime", "datafusion-datasource", "datafusion-execution", "datafusion-expr", - "datafusion-functions-aggregate", + "datafusion-functions-aggregate-common", "datafusion-physical-expr", "datafusion-physical-expr-adapter", "datafusion-physical-expr-common", - "datafusion-physical-optimizer", "datafusion-physical-plan", "datafusion-pruning", "datafusion-session", @@ -2601,21 +2578,20 @@ dependencies = [ "object_store", "parking_lot", "parquet", - "rand 0.9.2", "tokio", ] [[package]] name = "datafusion-doc" -version = "50.3.0" +version = "51.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99ee6b1d9a80d13f9deb2291f45c07044b8e62fb540dbde2453a18be17a36429" +checksum = "2b99e13947667b36ad713549237362afb054b2d8f8cc447751e23ec61202db07" [[package]] name = "datafusion-execution" -version = "50.3.0" +version = "51.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4cec0a57653bec7b933fb248d3ffa3fa3ab3bd33bd140dc917f714ac036f531" +checksum = "63695643190679037bc946ad46a263b62016931547bf119859c511f7ff2f5178" dependencies = [ "arrow", "async-trait", @@ -2633,9 +2609,9 @@ dependencies = [ [[package]] name = "datafusion-expr" -version = "50.3.0" +version = "51.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef76910bdca909722586389156d0aa4da4020e1631994d50fadd8ad4b1aa05fe" +checksum = "f9a4787cbf5feb1ab351f789063398f67654a6df75c4d37d7f637dc96f951a91" dependencies = [ "arrow", "async-trait", @@ -2646,31 +2622,31 @@ dependencies = [ "datafusion-functions-aggregate-common", "datafusion-functions-window-common", "datafusion-physical-expr-common", - "indexmap 2.11.4", + "indexmap 2.12.0", + "itertools 0.14.0", "paste", - "recursive", "serde_json", "sqlparser", ] [[package]] name = "datafusion-expr-common" -version = "50.3.0" +version = "51.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d155ccbda29591ca71a1344dd6bed26c65a4438072b400df9db59447f590bb6" +checksum = "5ce2fb1b8c15c9ac45b0863c30b268c69dc9ee7a1ee13ecf5d067738338173dc" dependencies = [ "arrow", "datafusion-common", - "indexmap 2.11.4", + "indexmap 2.12.0", "itertools 0.14.0", "paste", ] [[package]] name = "datafusion-ffi" -version = "50.3.0" +version = "51.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25ddb7c4e645df080c27dad13a198d191da328dd1c98e198664a7a0f64b335cc" +checksum = "ec510e7787641279b0336e8b79e4b7bd1385d5976875ff9b97f4269ce5231a67" dependencies = [ "abi_stable", "arrow", @@ -2678,21 +2654,22 @@ dependencies = [ "async-ffi", "async-trait", "datafusion", + "datafusion-common", "datafusion-functions-aggregate-common", "datafusion-proto", "datafusion-proto-common", "futures", "log", - "prost 0.13.5", + "prost", "semver", "tokio", ] [[package]] name = "datafusion-functions" -version = "50.3.0" +version = "51.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7de2782136bd6014670fd84fe3b0ca3b3e4106c96403c3ae05c0598577139977" +checksum = "794a9db7f7b96b3346fc007ff25e994f09b8f0511b4cf7dff651fadfe3ebb28f" dependencies = [ "arrow", "arrow-buffer", @@ -2710,6 +2687,7 @@ dependencies = [ "itertools 0.14.0", "log", "md-5", + "num-traits", "rand 0.9.2", "regex", "sha2", @@ -2719,9 +2697,9 @@ dependencies = [ [[package]] name = "datafusion-functions-aggregate" -version = "50.3.0" +version = "51.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07331fc13603a9da97b74fd8a273f4238222943dffdbbed1c4c6f862a30105bf" +checksum = "1c25210520a9dcf9c2b2cbbce31ebd4131ef5af7fc60ee92b266dc7d159cb305" dependencies = [ "ahash", "arrow", @@ -2740,9 +2718,9 @@ dependencies = [ [[package]] name = "datafusion-functions-aggregate-common" -version = "50.3.0" +version = "51.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5951e572a8610b89968a09b5420515a121fbc305c0258651f318dc07c97ab17" +checksum = "62f4a66f3b87300bb70f4124b55434d2ae3fe80455f3574701d0348da040b55d" dependencies = [ "ahash", "arrow", @@ -2753,9 +2731,9 @@ dependencies = [ [[package]] name = "datafusion-functions-nested" -version = "50.3.0" +version = "51.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdacca9302c3d8fc03f3e94f338767e786a88a33f5ebad6ffc0e7b50364b9ea3" +checksum = "ae5c06eed03918dc7fe7a9f082a284050f0e9ecf95d72f57712d1496da03b8c4" dependencies = [ "arrow", "arrow-ord", @@ -2763,6 +2741,7 @@ dependencies = [ "datafusion-doc", "datafusion-execution", "datafusion-expr", + "datafusion-expr-common", "datafusion-functions", "datafusion-functions-aggregate", "datafusion-functions-aggregate-common", @@ -2775,9 +2754,9 @@ dependencies = [ [[package]] name = "datafusion-functions-table" -version = "50.3.0" +version = "51.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c37ff8a99434fbbad604a7e0669717c58c7c4f14c472d45067c4b016621d981" +checksum = "db4fed1d71738fbe22e2712d71396db04c25de4111f1ec252b8f4c6d3b25d7f5" dependencies = [ "arrow", "async-trait", @@ -2791,9 +2770,9 @@ dependencies = [ [[package]] name = "datafusion-functions-window" -version = "50.3.0" +version = "51.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48e2aea7c79c926cffabb13dc27309d4eaeb130f4a21c8ba91cdd241c813652b" +checksum = "1d92206aa5ae21892f1552b4d61758a862a70956e6fd7a95cb85db1de74bc6d1" dependencies = [ "arrow", "datafusion-common", @@ -2809,9 +2788,9 @@ dependencies = [ [[package]] name = "datafusion-functions-window-common" -version = "50.3.0" +version = "51.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fead257ab5fd2ffc3b40fda64da307e20de0040fe43d49197241d9de82a487f" +checksum = "53ae9bcc39800820d53a22d758b3b8726ff84a5a3e24cecef04ef4e5fdf1c7cc" dependencies = [ "datafusion-common", "datafusion-physical-expr-common", @@ -2819,20 +2798,20 @@ dependencies = [ [[package]] name = "datafusion-macros" -version = "50.3.0" +version = "51.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec6f637bce95efac05cdfb9b6c19579ed4aa5f6b94d951cfa5bb054b7bb4f730" +checksum = "1063ad4c9e094b3f798acee16d9a47bd7372d9699be2de21b05c3bd3f34ab848" dependencies = [ - "datafusion-expr", + "datafusion-doc", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] name = "datafusion-optimizer" -version = "50.3.0" +version = "51.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6583ef666ae000a613a837e69e456681a9faa96347bf3877661e9e89e141d8a" +checksum = "9f35f9ec5d08b87fd1893a30c2929f2559c2f9806ca072d8fefca5009dc0f06a" dependencies = [ "arrow", "chrono", @@ -2840,19 +2819,18 @@ dependencies = [ "datafusion-expr", "datafusion-expr-common", "datafusion-physical-expr", - "indexmap 2.11.4", + "indexmap 2.12.0", "itertools 0.14.0", "log", - "recursive", "regex", "regex-syntax", ] [[package]] name = "datafusion-physical-expr" -version = "50.3.0" +version = "51.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8668103361a272cbbe3a61f72eca60c9b7c706e87cc3565bcf21e2b277b84f6" +checksum = "c30cc8012e9eedcb48bbe112c6eff4ae5ed19cf3003cb0f505662e88b7014c5d" dependencies = [ "ahash", "arrow", @@ -2863,19 +2841,18 @@ dependencies = [ "datafusion-physical-expr-common", "half", "hashbrown 0.14.5", - "indexmap 2.11.4", + "indexmap 2.12.0", "itertools 0.14.0", - "log", "parking_lot", "paste", - "petgraph 0.8.2", + "petgraph 0.8.3", ] [[package]] name = "datafusion-physical-expr-adapter" -version = "50.3.0" +version = "51.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "815acced725d30601b397e39958e0e55630e0a10d66ef7769c14ae6597298bb0" +checksum = "7f9ff2dbd476221b1f67337699eff432781c4e6e1713d2aefdaa517dfbf79768" dependencies = [ "arrow", "datafusion-common", @@ -2888,9 +2865,9 @@ dependencies = [ [[package]] name = "datafusion-physical-expr-common" -version = "50.3.0" +version = "51.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6652fe7b5bf87e85ed175f571745305565da2c0b599d98e697bcbedc7baa47c3" +checksum = "90da43e1ec550b172f34c87ec68161986ced70fd05c8d2a2add66eef9c276f03" dependencies = [ "ahash", "arrow", @@ -2902,9 +2879,9 @@ dependencies = [ [[package]] name = "datafusion-physical-optimizer" -version = "50.3.0" +version = "51.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49b7d623eb6162a3332b564a0907ba00895c505d101b99af78345f1acf929b5c" +checksum = "ce9804f799acd7daef3be7aaffe77c0033768ed8fdbf5fb82fc4c5f2e6bc14e6" dependencies = [ "arrow", "datafusion-common", @@ -2916,15 +2893,13 @@ dependencies = [ "datafusion-physical-plan", "datafusion-pruning", "itertools 0.14.0", - "log", - "recursive", ] [[package]] name = "datafusion-physical-plan" -version = "50.3.0" +version = "51.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2f7f778a1a838dec124efb96eae6144237d546945587557c9e6936b3414558c" +checksum = "0acf0ad6b6924c6b1aa7d213b181e012e2d3ec0a64ff5b10ee6282ab0f8532ac" dependencies = [ "ahash", "arrow", @@ -2943,7 +2918,7 @@ dependencies = [ "futures", "half", "hashbrown 0.14.5", - "indexmap 2.11.4", + "indexmap 2.12.0", "itertools 0.14.0", "log", "parking_lot", @@ -2953,39 +2928,49 @@ dependencies = [ [[package]] name = "datafusion-proto" -version = "50.3.0" +version = "51.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7df9f606892e6af45763d94d210634eec69b9bb6ced5353381682ff090028a3" +checksum = "d368093a98a17d1449b1083ac22ed16b7128e4c67789991869480d8c4a40ecb9" dependencies = [ "arrow", "chrono", - "datafusion", + "datafusion-catalog", + "datafusion-catalog-listing", "datafusion-common", + "datafusion-datasource", + "datafusion-datasource-arrow", + "datafusion-datasource-csv", + "datafusion-datasource-json", + "datafusion-datasource-parquet", + "datafusion-execution", "datafusion-expr", + "datafusion-functions-table", + "datafusion-physical-expr", + "datafusion-physical-expr-common", + "datafusion-physical-plan", "datafusion-proto-common", "object_store", - "prost 0.13.5", + "prost", ] [[package]] name = "datafusion-proto-common" -version = "50.3.0" +version = "51.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4b14f288ca4ef77743d9672cafecf3adfffff0b9b04af9af79ecbeaaf736901" +checksum = "3b6aef3d5e5c1d2bc3114c4876730cb76a9bdc5a8df31ef1b6db48f0c1671895" dependencies = [ "arrow", "datafusion-common", - "prost 0.13.5", + "prost", ] [[package]] name = "datafusion-pruning" -version = "50.3.0" +version = "51.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd1e59e2ca14fe3c30f141600b10ad8815e2856caa59ebbd0e3e07cd3d127a65" +checksum = "ac2c2498a1f134a9e11a9f5ed202a2a7d7e9774bd9249295593053ea3be999db" dependencies = [ "arrow", - "arrow-schema", "datafusion-common", "datafusion-datasource", "datafusion-expr-common", @@ -2998,41 +2983,31 @@ dependencies = [ [[package]] name = "datafusion-session" -version = "50.3.0" +version = "51.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21ef8e2745583619bd7a49474e8f45fbe98ebb31a133f27802217125a7b3d58d" +checksum = "8f96eebd17555386f459037c65ab73aae8df09f464524c709d6a3134ad4f4776" dependencies = [ - "arrow", "async-trait", - "dashmap", "datafusion-common", - "datafusion-common-runtime", "datafusion-execution", "datafusion-expr", - "datafusion-physical-expr", "datafusion-physical-plan", - "datafusion-sql", - "futures", - "itertools 0.14.0", - "log", - "object_store", "parking_lot", - "tokio", ] [[package]] name = "datafusion-sql" -version = "50.3.0" +version = "51.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89abd9868770386fede29e5a4b14f49c0bf48d652c3b9d7a8a0332329b87d50b" +checksum = "3fc195fe60634b2c6ccfd131b487de46dc30eccae8a3c35a13f136e7f440414f" dependencies = [ "arrow", "bigdecimal", + "chrono", "datafusion-common", "datafusion-expr", - "indexmap 2.11.4", + "indexmap 2.12.0", "log", - "recursive", "regex", "sqlparser", ] @@ -3075,7 +3050,7 @@ checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -3164,7 +3139,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -3173,7 +3148,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" dependencies = [ - "libloading 0.7.4", + "libloading 0.8.9", ] [[package]] @@ -3218,10 +3193,20 @@ version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6add3b8cff394282be81f3fc1a0605db594ed69890078ca6e2cab1c408bcf04" +[[package]] +name = "earcutr" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79127ed59a85d7687c409e9978547cffb7dc79675355ed22da6b66fd5f6ead01" +dependencies = [ + "itertools 0.11.0", + "num-traits", +] + [[package]] name = "ecolor" version = "0.33.3" -source = "git+https://github.com/emilk/egui.git?branch=main#fd257b2e95972f2bfe08cbde710f248076a08ee6" +source = "git+https://github.com/emilk/egui.git?branch=main#c89a4d1b38460cd58e25db5761355b837e57b719" dependencies = [ "bytemuck", "color-hex", @@ -3238,7 +3223,7 @@ checksum = "18aade80d5e09429040243ce1143ddc08a92d7a22820ac512610410a4dd5214f" [[package]] name = "eframe" version = "0.33.3" -source = "git+https://github.com/emilk/egui.git?branch=main#fd257b2e95972f2bfe08cbde710f248076a08ee6" +source = "git+https://github.com/emilk/egui.git?branch=main#c89a4d1b38460cd58e25db5761355b837e57b719" dependencies = [ "ahash", "bytemuck", @@ -3276,7 +3261,7 @@ dependencies = [ [[package]] name = "egui" version = "0.33.3" -source = "git+https://github.com/emilk/egui.git?branch=main#fd257b2e95972f2bfe08cbde710f248076a08ee6" +source = "git+https://github.com/emilk/egui.git?branch=main#c89a4d1b38460cd58e25db5761355b837e57b719" dependencies = [ "accesskit", "ahash", @@ -3296,7 +3281,7 @@ dependencies = [ [[package]] name = "egui-wgpu" version = "0.33.3" -source = "git+https://github.com/emilk/egui.git?branch=main#fd257b2e95972f2bfe08cbde710f248076a08ee6" +source = "git+https://github.com/emilk/egui.git?branch=main#c89a4d1b38460cd58e25db5761355b837e57b719" dependencies = [ "ahash", "bytemuck", @@ -3315,7 +3300,7 @@ dependencies = [ [[package]] name = "egui-winit" version = "0.33.3" -source = "git+https://github.com/emilk/egui.git?branch=main#fd257b2e95972f2bfe08cbde710f248076a08ee6" +source = "git+https://github.com/emilk/egui.git?branch=main#c89a4d1b38460cd58e25db5761355b837e57b719" dependencies = [ "accesskit_winit", "arboard", @@ -3383,7 +3368,7 @@ dependencies = [ [[package]] name = "egui_extras" version = "0.33.3" -source = "git+https://github.com/emilk/egui.git?branch=main#fd257b2e95972f2bfe08cbde710f248076a08ee6" +source = "git+https://github.com/emilk/egui.git?branch=main#c89a4d1b38460cd58e25db5761355b837e57b719" dependencies = [ "ahash", "egui", @@ -3400,7 +3385,7 @@ dependencies = [ [[package]] name = "egui_glow" version = "0.33.3" -source = "git+https://github.com/emilk/egui.git?branch=main#fd257b2e95972f2bfe08cbde710f248076a08ee6" +source = "git+https://github.com/emilk/egui.git?branch=main#c89a4d1b38460cd58e25db5761355b837e57b719" dependencies = [ "bytemuck", "egui", @@ -3416,7 +3401,7 @@ dependencies = [ [[package]] name = "egui_kittest" version = "0.33.3" -source = "git+https://github.com/emilk/egui.git?branch=main#fd257b2e95972f2bfe08cbde710f248076a08ee6" +source = "git+https://github.com/emilk/egui.git?branch=main#c89a4d1b38460cd58e25db5761355b837e57b719" dependencies = [ "dify", "eframe", @@ -3495,7 +3480,7 @@ checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "emath" version = "0.33.3" -source = "git+https://github.com/emilk/egui.git?branch=main#fd257b2e95972f2bfe08cbde710f248076a08ee6" +source = "git+https://github.com/emilk/egui.git?branch=main#c89a4d1b38460cd58e25db5761355b837e57b719" dependencies = [ "bytemuck", "serde", @@ -3540,7 +3525,7 @@ checksum = "f282cfdfe92516eb26c2af8589c274c7c17681f5ecc03c18255fe741c6aa64eb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -3561,7 +3546,7 @@ checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -3572,7 +3557,7 @@ checksum = "2f9ed6b3789237c8a0c1c505af1c7eb2c560df6186f01b098c3a1064ea532f38" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -3593,7 +3578,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -3621,7 +3606,7 @@ dependencies = [ [[package]] name = "epaint" version = "0.33.3" -source = "git+https://github.com/emilk/egui.git?branch=main#fd257b2e95972f2bfe08cbde710f248076a08ee6" +source = "git+https://github.com/emilk/egui.git?branch=main#c89a4d1b38460cd58e25db5761355b837e57b719" dependencies = [ "ahash", "bytemuck", @@ -3642,7 +3627,7 @@ dependencies = [ [[package]] name = "epaint_default_fonts" version = "0.33.3" -source = "git+https://github.com/emilk/egui.git?branch=main#fd257b2e95972f2bfe08cbde710f248076a08ee6" +source = "git+https://github.com/emilk/egui.git?branch=main#c89a4d1b38460cd58e25db5761355b837e57b719" [[package]] name = "equivalent" @@ -3839,6 +3824,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "float_next_after" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bf7cc16383c4b8d58b9905a8509f02926ce3058053c056376248d958c9df1e8" + [[package]] name = "fnv" version = "1.0.7" @@ -3884,7 +3875,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -3929,9 +3920,9 @@ dependencies = [ [[package]] name = "fsst" -version = "0.38.3" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "295735676bb13caa4d42e0ec4a9f683f2c879570b22925128288bf363c703a8b" +checksum = "5f9e5c0b1c67a38cb92b41535d44623483beb9511592ae23a3bf42ddec758690" dependencies = [ "arrow-array", "rand 0.9.2", @@ -4021,7 +4012,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -4036,6 +4027,12 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" +[[package]] +name = "futures-timer" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" + [[package]] name = "futures-util" version = "0.3.31" @@ -4074,7 +4071,7 @@ dependencies = [ "libc", "log", "rustversion", - "windows 0.61.3", + "windows 0.58.0", ] [[package]] @@ -4087,15 +4084,126 @@ dependencies = [ "version_check", ] +[[package]] +name = "geo" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fc1a1678e54befc9b4bcab6cd43b8e7f834ae8ea121118b0fd8c42747675b4a" +dependencies = [ + "earcutr", + "float_next_after", + "geo-types", + "geographiclib-rs", + "i_overlay", + "log", + "num-traits", + "robust", + "rstar", + "spade", +] + +[[package]] +name = "geo-traits" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e7c353d12a704ccfab1ba8bfb1a7fe6cb18b665bf89d37f4f7890edcd260206" +dependencies = [ + "geo-types", +] + [[package]] name = "geo-types" -version = "0.7.16" +version = "0.7.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62ddb1950450d67efee2bbc5e429c68d052a822de3aad010d28b351fbb705224" +checksum = "24f8647af4005fa11da47cd56252c6ef030be8fa97bdbf355e7dfb6348f0a82c" dependencies = [ "approx", "num-traits", + "rayon", + "rstar", + "serde", +] + +[[package]] +name = "geoarrow-array" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1cc4106ac0a0a512c398961ce95d8150475c84a84e17c4511c3643fa120a17" +dependencies = [ + "arrow-array", + "arrow-buffer", + "arrow-schema", + "geo-traits", + "geoarrow-schema", + "num-traits", + "wkb", + "wkt", +] + +[[package]] +name = "geoarrow-expr-geo" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa84300361ce57fb875bcaa6e32b95b0aff5c6b1af692b936bdd58ff343f4394" +dependencies = [ + "arrow-array", + "arrow-buffer", + "geo", + "geo-traits", + "geoarrow-array", + "geoarrow-schema", +] + +[[package]] +name = "geoarrow-schema" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e97be4e9f523f92bd6a0e0458323f4b783d073d011664decd8dbf05651704f34" +dependencies = [ + "arrow-schema", + "geo-traits", "serde", + "serde_json", + "thiserror 1.0.69", +] + +[[package]] +name = "geodatafusion" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773cfa1fb0d7f7661b76b3fde00f3ffd8e0ff7b3635096f0ff6294fe5ca62a2b" +dependencies = [ + "arrow-arith", + "arrow-array", + "arrow-schema", + "datafusion", + "geo", + "geo-traits", + "geoarrow-array", + "geoarrow-expr-geo", + "geoarrow-schema", + "geohash", + "thiserror 1.0.69", + "wkt", +] + +[[package]] +name = "geographiclib-rs" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f611040a2bb37eaa29a78a128d1e92a378a03e0b6e66ae27398d42b1ba9a7841" +dependencies = [ + "libm", +] + +[[package]] +name = "geohash" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fb94b1a65401d6cbf22958a9040aa364812c26674f841bee538b12c135db1e6" +dependencies = [ + "geo-types", + "libm", ] [[package]] @@ -4237,7 +4345,7 @@ dependencies = [ "inflections", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -4391,7 +4499,7 @@ dependencies = [ "futures-core", "futures-sink", "http", - "indexmap 2.11.4", + "indexmap 2.12.0", "slab", "tokio", "tokio-util", @@ -4413,14 +4521,24 @@ dependencies = [ [[package]] name = "half" -version = "2.6.0" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" dependencies = [ "bytemuck", "cfg-if", "crunchy", "num-traits", + "zerocopy 0.8.27", +] + +[[package]] +name = "hash32" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" +dependencies = [ + "byteorder", ] [[package]] @@ -4462,6 +4580,16 @@ dependencies = [ "foldhash 0.2.0", ] +[[package]] +name = "heapless" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" +dependencies = [ + "hash32", + "stable_deref_trait", +] + [[package]] name = "heck" version = "0.5.0" @@ -4722,6 +4850,49 @@ dependencies = [ "serde", ] +[[package]] +name = "i_float" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "010025c2c532c8d82e42d0b8bb5184afa449fa6f06c709ea9adcb16c49ae405b" +dependencies = [ + "libm", +] + +[[package]] +name = "i_key_sort" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9190f86706ca38ac8add223b2aed8b1330002b5cdbbce28fb58b10914d38fc27" + +[[package]] +name = "i_overlay" +version = "4.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413183068e6e0289e18d7d0a1f661b81546e6918d5453a44570b9ab30cbed1b3" +dependencies = [ + "i_float", + "i_key_sort", + "i_shape", + "i_tree", + "rayon", +] + +[[package]] +name = "i_shape" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ea154b742f7d43dae2897fcd5ead86bc7b5eefcedd305a7ebf9f69d44d61082" +dependencies = [ + "i_float", +] + +[[package]] +name = "i_tree" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35e6d558e6d4c7b82bc51d9c771e7a927862a161a7d87bf2b0541450e0e20915" + [[package]] name = "iana-time-zone" version = "0.1.64" @@ -4734,7 +4905,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core 0.62.2", + "windows-core 0.58.0", ] [[package]] @@ -4917,9 +5088,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.11.4" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" +checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" dependencies = [ "equivalent", "hashbrown 0.16.0", @@ -5004,17 +5175,6 @@ version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8bb03732005da905c88227371639bf1ad885cc712789c011c31c5fb3ab3ccf02" -[[package]] -name = "io-uring" -version = "0.7.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b" -dependencies = [ - "bitflags 2.9.4", - "cfg-if", - "libc", -] - [[package]] name = "ipnet" version = "2.11.0" @@ -5069,9 +5229,18 @@ checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] name = "itertools" -version = "0.10.5" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" dependencies = [ "either", ] @@ -5125,7 +5294,7 @@ checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -5295,9 +5464,9 @@ dependencies = [ [[package]] name = "lance" -version = "0.38.3" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71555813afa19d7eadfb8adf20609d0c62bbc39e18c66fddc2f7e8ee5d7a107f" +checksum = "2b7f07b905df393a5554eba19055c620f9ea25a3e40a013bda4bd8dc4ca66f01" dependencies = [ "arrow", "arrow-arith", @@ -5331,6 +5500,7 @@ dependencies = [ "lance-datafusion", "lance-encoding", "lance-file", + "lance-geo", "lance-index", "lance-io", "lance-linalg", @@ -5341,10 +5511,11 @@ dependencies = [ "object_store", "permutation", "pin-project", - "prost 0.13.5", - "prost-types 0.13.5", + "prost", + "prost-types", "rand 0.9.2", "roaring", + "semver", "serde", "serde_json", "snafu", @@ -5358,14 +5529,15 @@ dependencies = [ [[package]] name = "lance-arrow" -version = "0.38.3" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40766c050b3295fe4be49d79f80fe217a0b5f8626561bf077a864817ba72e9f3" +checksum = "100e076cb81c8f0c24cd2881c706fc53e037c7d6e81eb320e929e265d157effb" dependencies = [ "arrow-array", "arrow-buffer", "arrow-cast", "arrow-data", + "arrow-ord", "arrow-schema", "arrow-select", "bytes", @@ -5378,9 +5550,9 @@ dependencies = [ [[package]] name = "lance-bitpacking" -version = "0.38.3" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e50c8911ea56acf2294d88259cc7208649502e2582b85e8bfc9ec8b958b1957" +checksum = "588318d3d1ba0f97162fab39a323a0a49866bb35b32af42572c6b6a12296fa27" dependencies = [ "arrayref", "paste", @@ -5389,9 +5561,9 @@ dependencies = [ [[package]] name = "lance-core" -version = "0.38.3" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "621efcb8ef8ecd4790f91db3bef8a3882692ba42a30cbeb4ad5fcb0ab5bdd2b4" +checksum = "6fa01d1cf490ccfd3b8eaeee2781415d0419e6be8366040e57e43677abf2644e" dependencies = [ "arrow-array", "arrow-buffer", @@ -5404,6 +5576,7 @@ dependencies = [ "datafusion-sql", "deepsize", "futures", + "itertools 0.13.0", "lance-arrow", "libc", "log", @@ -5412,7 +5585,7 @@ dependencies = [ "num_cpus", "object_store", "pin-project", - "prost 0.13.5", + "prost", "rand 0.9.2", "roaring", "serde_json", @@ -5427,9 +5600,9 @@ dependencies = [ [[package]] name = "lance-datafusion" -version = "0.38.3" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63f138b828abf6a571ad4fab179532d3a383275df9ee5005ad34d86b22bb9fd7" +checksum = "ef89a39e3284eef76f79e63f23de8881a0583ad6feb20ed39f47eadd847a2b88" dependencies = [ "arrow", "arrow-array", @@ -5448,9 +5621,10 @@ dependencies = [ "lance-arrow", "lance-core", "lance-datagen", + "lance-geo", "log", "pin-project", - "prost 0.13.5", + "prost", "snafu", "tokio", "tracing", @@ -5458,9 +5632,9 @@ dependencies = [ [[package]] name = "lance-datagen" -version = "0.38.3" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24bff460ae73d5a722f993afa0309d3c14848b0695a2021c0e1bd70de134f24a" +checksum = "fc2a60eef5c47e65d91e2ffa8e7e1629c52e7190c8b88a371a1a60601dc49371" dependencies = [ "arrow", "arrow-array", @@ -5471,15 +5645,16 @@ dependencies = [ "half", "hex", "rand 0.9.2", + "rand_distr 0.5.1", "rand_xoshiro", "random_word", ] [[package]] name = "lance-encoding" -version = "0.38.3" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3cdeae2eb2ec5f4dc20c65332cb68d54bb81cde9ee79fd5b92fbacf1c554888" +checksum = "95ce4a6631308aa681b2671af8f2a845ff781f8d4e755a2a7ccd012379467094" dependencies = [ "arrow-arith", "arrow-array", @@ -5502,9 +5677,9 @@ dependencies = [ "log", "lz4", "num-traits", - "prost 0.13.5", - "prost-build 0.13.5", - "prost-types 0.13.5", + "prost", + "prost-build", + "prost-types", "rand 0.9.2", "snafu", "strum", @@ -5516,9 +5691,9 @@ dependencies = [ [[package]] name = "lance-file" -version = "0.38.3" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3baf6dcef3fa2dcdcad17a22e075a7040dfada4b03179aaf54e0fe86c44e88eb" +checksum = "e2d4d82357cbfaa1a18494226c15b1cb3c8ed0b6c84b91146323c82047ede419" dependencies = [ "arrow-arith", "arrow-array", @@ -5540,19 +5715,35 @@ dependencies = [ "log", "num-traits", "object_store", - "prost 0.13.5", - "prost-build 0.13.5", - "prost-types 0.13.5", + "prost", + "prost-build", + "prost-types", "snafu", "tokio", "tracing", ] +[[package]] +name = "lance-geo" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7183fc870da62826f0f97df8007b634da053eb310157856efe1dc74f446951c" +dependencies = [ + "datafusion", + "geo-traits", + "geo-types", + "geoarrow-array", + "geoarrow-schema", + "geodatafusion", + "lance-core", + "serde", +] + [[package]] name = "lance-index" -version = "0.38.3" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b77e902c163f86e3721e93968c31f72f398bdf50b86bbf8603202413d45e6c6c" +checksum = "20e9c5aa7024a63af9ae89ee8c0f23c8421b7896742e5cd4a271a60f9956cb80" dependencies = [ "arrow", "arrow-arith", @@ -5576,6 +5767,9 @@ dependencies = [ "dirs", "fst", "futures", + "geo-types", + "geoarrow-array", + "geoarrow-schema", "half", "itertools 0.13.0", "jsonb", @@ -5585,6 +5779,7 @@ dependencies = [ "lance-datagen", "lance-encoding", "lance-file", + "lance-geo", "lance-io", "lance-linalg", "lance-table", @@ -5593,15 +5788,17 @@ dependencies = [ "ndarray", "num-traits", "object_store", - "prost 0.13.5", - "prost-build 0.13.5", - "prost-types 0.13.5", + "prost", + "prost-build", + "prost-types", "rand 0.9.2", "rand_distr 0.5.1", + "rangemap", "rayon", "roaring", "serde", "serde_json", + "smallvec", "snafu", "tantivy", "tempfile", @@ -5613,9 +5810,9 @@ dependencies = [ [[package]] name = "lance-io" -version = "0.38.3" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7efd718983fb15257aa58496eb070d6296b78ef4b738a3a0bd7519e2fe918105" +checksum = "c7d2af0b17fb374a8181bcf1a10bce5703ae3ee4373c1587ce4bba23e15e45c8" dependencies = [ "arrow", "arrow-arith", @@ -5639,7 +5836,7 @@ dependencies = [ "object_store", "path_abs", "pin-project", - "prost 0.13.5", + "prost", "rand 0.9.2", "serde", "shellexpand", @@ -5651,9 +5848,9 @@ dependencies = [ [[package]] name = "lance-linalg" -version = "0.38.3" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5209810d4b1da1db73b9058a20c6f4dca098b6af75c565793f27caaf3b1b5cec" +checksum = "5125aa62696e75a7475807564b4921f252d8815be606b84bc00e6def0f5c24bb" dependencies = [ "arrow-array", "arrow-buffer", @@ -5669,9 +5866,9 @@ dependencies = [ [[package]] name = "lance-namespace" -version = "0.38.3" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4af29a44105e773cb7f466c44ed73e71d2d3636a6d7803c55d5452de94d11d54" +checksum = "70545c2676ce954dfd801da5c6a631a70bba967826cd3a8f31b47d1f04bbfed3" dependencies = [ "arrow", "async-trait", @@ -5683,9 +5880,9 @@ dependencies = [ [[package]] name = "lance-namespace-reqwest-client" -version = "0.0.18" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ea349999bcda4eea53fc05d334b3775ec314761e6a706555c777d7a29b18d19" +checksum = "a2acdba67f84190067532fce07b51a435dd390d7cdc1129a05003e5cb3274cf0" dependencies = [ "reqwest", "serde", @@ -5696,9 +5893,9 @@ dependencies = [ [[package]] name = "lance-table" -version = "0.38.3" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e72b3d7617368b43849b309a8ee1357154f00757a7e99eaed775a9832b0004a2" +checksum = "b06ad37bd90045de8ef533df170c6098e6ff6ecb427aade47d7db8e2c86f2678" dependencies = [ "arrow", "arrow-array", @@ -5717,12 +5914,13 @@ dependencies = [ "lance-io", "log", "object_store", - "prost 0.13.5", - "prost-build 0.13.5", - "prost-types 0.13.5", + "prost", + "prost-build", + "prost-types", "rand 0.9.2", "rangemap", "roaring", + "semver", "serde", "serde_json", "snafu", @@ -5816,17 +6014,11 @@ dependencies = [ "lexical-util", ] -[[package]] -name = "libbz2-rs-sys" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c4a545a15244c7d945065b5d392b2d2d7f21526fba56ce51467b06ed445e8f7" - [[package]] name = "libc" -version = "0.2.176" +version = "0.2.180" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174" +checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" [[package]] name = "libloading" @@ -5989,7 +6181,7 @@ dependencies = [ "quote", "regex-syntax", "rustc_version", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -6062,9 +6254,6 @@ name = "lz4_flex" version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08ab2867e3eeeca90e844d1940eab391c9dc5228783db2ed999acbc0a9ed375a" -dependencies = [ - "twox-hash", -] [[package]] name = "lz4_flex" @@ -6075,17 +6264,6 @@ dependencies = [ "twox-hash", ] -[[package]] -name = "lzma-sys" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fda04ab3764e6cde78b9974eec4f779acaba7c4e84b36eca3cf77c581b85d27" -dependencies = [ - "cc", - "libc", - "pkg-config", -] - [[package]] name = "macaw" version = "0.30.0" @@ -6251,7 +6429,7 @@ checksum = "49e7bc1560b95a3c4a25d03de42fe76ca718ab92d1a22a55b9b4cf67b3ae635c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -6427,7 +6605,7 @@ dependencies = [ "half", "hashbrown 0.16.0", "hexf-parse", - "indexmap 2.11.4", + "indexmap 2.12.0", "libm", "log", "num-traits", @@ -6585,20 +6763,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "num" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" -dependencies = [ - "num-bigint", - "num-complex", - "num-integer", - "num-iter", - "num-rational", - "num-traits", -] - [[package]] name = "num-bigint" version = "0.4.6" @@ -6632,7 +6796,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -6644,17 +6808,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "num-iter" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - [[package]] name = "num-rational" version = "0.4.2" @@ -6706,14 +6859,14 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] name = "numpy" -version = "0.25.0" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29f1dee9aa8d3f6f8e8b9af3803006101bb3653866ef056d530d53ae68587191" +checksum = "9b2dba356160b54f5371b550575b78130a54718b4c6e46b3f33a6da74a27e78b" dependencies = [ "libc", "ndarray", @@ -7121,8 +7274,8 @@ dependencies = [ "anyhow", "clap", "glam", - "prost 0.14.1", - "prost-build 0.14.1", + "prost", + "prost-build", "protoc-prebuilt", "re_build_tools", "rerun", @@ -7220,7 +7373,7 @@ dependencies = [ "opentelemetry-http", "opentelemetry-proto", "opentelemetry_sdk", - "prost 0.14.1", + "prost", "reqwest", "thiserror 2.0.17", "tokio", @@ -7236,7 +7389,7 @@ checksum = "a7175df06de5eaee9909d4805a3d07e28bb752c34cab57fa9cff549da596b30f" dependencies = [ "opentelemetry", "opentelemetry_sdk", - "prost 0.14.1", + "prost", "tonic", "tonic-prost", ] @@ -7358,9 +7511,9 @@ dependencies = [ [[package]] name = "parquet" -version = "56.1.0" +version = "57.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89b56b41d1bd36aae415e42f91cae70ee75cf6cba74416b14dce3e958d5990ec" +checksum = "5f6a2926a30477c0b95fea6c28c3072712b139337a242c2cc64817bdc20a8854" dependencies = [ "ahash", "arrow-array", @@ -7377,13 +7530,13 @@ dependencies = [ "flate2", "futures", "half", - "hashbrown 0.15.5", - "lz4_flex 0.11.5", - "num", + "hashbrown 0.16.0", + "lz4_flex 0.12.0", "num-bigint", + "num-integer", + "num-traits", "object_store", "paste", - "ring", "seq-macro", "simdutf8", "snap", @@ -7499,7 +7652,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -7519,7 +7672,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ "fixedbitset 0.4.2", - "indexmap 2.11.4", + "indexmap 2.12.0", ] [[package]] @@ -7529,18 +7682,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" dependencies = [ "fixedbitset 0.5.7", - "indexmap 2.11.4", + "indexmap 2.12.0", ] [[package]] name = "petgraph" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54acf3a685220b533e437e264e4d932cfbdc4cc7ec0cd232ed73c08d03b8a7ca" +checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455" dependencies = [ "fixedbitset 0.5.7", "hashbrown 0.15.5", - "indexmap 2.11.4", + "indexmap 2.12.0", "serde", ] @@ -7583,7 +7736,7 @@ dependencies = [ "phf_shared 0.11.3", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", "unicase", ] @@ -7629,7 +7782,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -7708,7 +7861,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe55bbee2b70d1c1e58d8340eda9a80c5ce11fb9b1bc10b5fc1575c490d38fa9" dependencies = [ "byteorder", - "indexmap 2.11.4", + "indexmap 2.12.0", "peg", ] @@ -7810,7 +7963,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -7848,7 +8001,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52717f9a02b6965224f95ca2a81e2e0c5c43baacd28ca057577988930b6c3d5b" dependencies = [ "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -7871,17 +8024,7 @@ checksum = "9adf1691c04c0a5ff46ff8f262b58beb07b0dbb61f96f9f54f6cbd82106ed87f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", -] - -[[package]] -name = "prost" -version = "0.13.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5" -dependencies = [ - "bytes", - "prost-derive 0.13.5", + "syn 2.0.114", ] [[package]] @@ -7891,27 +8034,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7231bd9b3d3d33c86b58adbac74b5ec0ad9f496b19d22801d773636feaa95f3d" dependencies = [ "bytes", - "prost-derive 0.14.1", -] - -[[package]] -name = "prost-build" -version = "0.13.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be769465445e8c1474e9c5dac2018218498557af32d9ed057325ec9a41ae81bf" -dependencies = [ - "heck", - "itertools 0.14.0", - "log", - "multimap", - "once_cell", - "petgraph 0.7.1", - "prettyplease", - "prost 0.13.5", - "prost-types 0.13.5", - "regex", - "syn 2.0.106", - "tempfile", + "prost-derive", ] [[package]] @@ -7927,26 +8050,13 @@ dependencies = [ "once_cell", "petgraph 0.7.1", "prettyplease", - "prost 0.14.1", - "prost-types 0.14.1", + "prost", + "prost-types", "regex", - "syn 2.0.106", + "syn 2.0.114", "tempfile", ] -[[package]] -name = "prost-derive" -version = "0.13.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" -dependencies = [ - "anyhow", - "itertools 0.14.0", - "proc-macro2", - "quote", - "syn 2.0.106", -] - [[package]] name = "prost-derive" version = "0.14.1" @@ -7957,7 +8067,7 @@ dependencies = [ "itertools 0.14.0", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -7967,17 +8077,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89a3ac73ec9a9118131a4594c9d336631a07852220a1d0ae03ee36b04503a063" dependencies = [ "logos", - "prost 0.14.1", - "prost-types 0.14.1", -] - -[[package]] -name = "prost-types" -version = "0.13.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52c2c1bf36ddb1a1c396b3601a3cec27c2462e45f07c386894ec3ccf5332bd16" -dependencies = [ - "prost 0.13.5", + "prost", + "prost-types", ] [[package]] @@ -7986,7 +8087,7 @@ version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9b4db3d6da204ed77bb26ba83b6122a73aeb2e87e25fbf7ad2e84c4ccbf8f72" dependencies = [ - "prost 0.14.1", + "prost", ] [[package]] @@ -7999,15 +8100,6 @@ dependencies = [ "zip 0.6.6", ] -[[package]] -name = "psm" -version = "0.1.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e944464ec8536cd1beb0bbfd96987eb5e3b72f2ecdafdc5c769a37f1fa2ae1f" -dependencies = [ - "cc", -] - [[package]] name = "puffin" version = "0.19.1" @@ -8051,9 +8143,9 @@ dependencies = [ [[package]] name = "pyo3" -version = "0.25.1" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8970a78afe0628a3e3430376fc5fd76b6b45c4d43360ffd6cdd40bdde72b682a" +checksum = "7ba0117f4212101ee6544044dae45abe1083d30ce7b29c4b5cbdfa2354e07383" dependencies = [ "chrono", "indoc", @@ -8069,19 +8161,18 @@ dependencies = [ [[package]] name = "pyo3-build-config" -version = "0.25.1" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "458eb0c55e7ece017adeba38f2248ff3ac615e53660d7c71a238d7d2a01c7598" +checksum = "4fc6ddaf24947d12a9aa31ac65431fb1b851b8f4365426e182901eabfb87df5f" dependencies = [ - "once_cell", "target-lexicon", ] [[package]] name = "pyo3-ffi" -version = "0.25.1" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7114fe5457c61b276ab77c5055f206295b812608083644a5c5b2640c3102565c" +checksum = "025474d3928738efb38ac36d4744a74a400c901c7596199e20e45d98eb194105" dependencies = [ "libc", "pyo3-build-config", @@ -8089,27 +8180,27 @@ dependencies = [ [[package]] name = "pyo3-macros" -version = "0.25.1" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8725c0a622b374d6cb051d11a0983786448f7785336139c3c94f5aa6bef7e50" +checksum = "2e64eb489f22fe1c95911b77c44cc41e7c19f3082fc81cce90f657cdc42ffded" dependencies = [ "proc-macro2", "pyo3-macros-backend", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] name = "pyo3-macros-backend" -version = "0.25.1" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4109984c22491085343c05b0dbc54ddc405c3cf7b4374fc533f5c3313a572ccc" +checksum = "100246c0ecf400b475341b8455a9213344569af29a3c841d29270e53102e0fcf" dependencies = [ "heck", "proc-macro2", "pyo3-build-config", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -8730,7 +8821,7 @@ dependencies = [ "cfg-if", "crossbeam", "image", - "indexmap 2.11.4", + "indexmap 2.12.0", "insta", "itertools 0.14.0", "mcap", @@ -8961,7 +9052,7 @@ dependencies = [ "arrow", "document-features", "emath", - "indexmap 2.11.4", + "indexmap 2.12.0", "itertools 0.14.0", "nohash-hasher", "poll-promise", @@ -9355,8 +9446,8 @@ dependencies = [ "jiff", "lz4_flex 0.12.0", "pin-project-lite", - "prost 0.14.1", - "prost-types 0.14.1", + "prost", + "prost-types", "pyo3", "re_arrow_util", "re_build_info", @@ -9559,7 +9650,7 @@ dependencies = [ "insta", "itertools 0.14.0", "lance", - "prost-types 0.14.1", + "prost-types", "re_arrow_util", "re_chunk", "re_chunk_store", @@ -9730,7 +9821,7 @@ dependencies = [ "glam", "half", "image", - "indexmap 2.11.4", + "indexmap 2.12.0", "infer", "itertools 0.14.0", "macaw", @@ -10053,7 +10144,7 @@ dependencies = [ "re_tracing", "regex-lite", "serde", - "syn 2.0.106", + "syn 2.0.114", "tempfile", "toml 0.9.8", "unindent", @@ -10574,7 +10665,7 @@ dependencies = [ "half", "home", "image", - "indexmap 2.11.4", + "indexmap 2.12.0", "itertools 0.14.0", "linked-hash-map", "macaw", @@ -10705,26 +10796,6 @@ dependencies = [ "font-types", ] -[[package]] -name = "recursive" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0786a43debb760f491b1bc0269fe5e84155353c67482b9e60d0cfb596054b43e" -dependencies = [ - "recursive-proc-macro-impl", - "stacker", -] - -[[package]] -name = "recursive-proc-macro-impl" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76009fbe0614077fc1a2ce255e3a1881a2e3a3527097d5dc6d8212c585e7e38b" -dependencies = [ - "quote", - "syn 2.0.106", -] - [[package]] name = "redox_syscall" version = "0.4.1" @@ -10771,7 +10842,7 @@ checksum = "1165225c21bff1f3bbce98f5a1f889949bc902d3575308cc7b0de30b4f6d27c7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -10788,9 +10859,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.11.3" +version = "1.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b5288124840bee7b386bc413c487869b360b2b4ec421ea56425128692f2a82c" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" dependencies = [ "aho-corasick", "memchr", @@ -10800,9 +10871,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.11" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "833eb9ce86d40ef33cb1306d8accf7bc8ec2bfea4355cbdebb3df68b40925cad" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" dependencies = [ "aho-corasick", "memchr", @@ -10821,6 +10892,12 @@ version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" +[[package]] +name = "relative-path" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" + [[package]] name = "renderdoc-sys" version = "1.1.0" @@ -10912,7 +10989,7 @@ dependencies = [ "crossbeam", "document-features", "env_filter", - "indexmap 2.11.4", + "indexmap 2.12.0", "indicatif", "itertools 0.14.0", "jiff", @@ -11147,6 +11224,12 @@ dependencies = [ "byteorder", ] +[[package]] +name = "robust" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e27ee8bb91ca0adcf0ecb116293afa12d393f9c2b9b9cd54d33e8078fe19839" + [[package]] name = "ron" version = "0.11.0" @@ -11166,6 +11249,46 @@ version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97" +[[package]] +name = "rstar" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "421400d13ccfd26dfa5858199c30a5d76f9c54e0dba7575273025b43c5175dbb" +dependencies = [ + "heapless", + "num-traits", + "smallvec", +] + +[[package]] +name = "rstest" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5a3193c063baaa2a95a33f03035c8a72b83d97a54916055ba22d35ed3839d49" +dependencies = [ + "futures-timer", + "futures-util", + "rstest_macros", +] + +[[package]] +name = "rstest_macros" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c845311f0ff7951c5506121a9ad75aec44d083c31583b2ea5a30bcb0b0abba0" +dependencies = [ + "cfg-if", + "glob", + "proc-macro-crate", + "proc-macro2", + "quote", + "regex", + "relative-path", + "rustc_version", + "syn 2.0.114", + "unicode-ident", +] + [[package]] name = "run_wasm" version = "0.30.0-alpha.1+dev" @@ -11521,7 +11644,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -11556,7 +11679,7 @@ checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -11822,7 +11945,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -11865,6 +11988,18 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "spade" +version = "2.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb313e1c8afee5b5647e00ee0fe6855e3d529eb863a0fdae1d60006c4d1e9990" +dependencies = [ + "hashbrown 0.15.5", + "num-traits", + "robust", + "smallvec", +] + [[package]] name = "spawn_viewer" version = "0.30.0-alpha.1+dev" @@ -11883,12 +12018,11 @@ dependencies = [ [[package]] name = "sqlparser" -version = "0.58.0" +version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec4b661c54b1e4b603b37873a18c59920e4c51ea8ea2cf527d925424dbd4437c" +checksum = "4591acadbcf52f0af60eafbb2c003232b2b4cd8de5f0e9437cb8b1b59046cc0f" dependencies = [ "log", - "recursive", "sqlparser_derive", ] @@ -11900,7 +12034,7 @@ checksum = "da5fc6819faabb412da764b99d3b713bb55083c11e7e0c00144d386cd6a1939c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -11926,19 +12060,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" -[[package]] -name = "stacker" -version = "0.1.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cddb07e32ddb770749da91081d8d0ac3a16f1a569a18b20348cd371f5dead06b" -dependencies = [ - "cc", - "cfg-if", - "libc", - "psm", - "windows-sys 0.59.0", -] - [[package]] name = "static_assertions" version = "1.1.0" @@ -12008,7 +12129,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -12046,9 +12167,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.106" +version = "2.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" dependencies = [ "proc-macro2", "quote", @@ -12072,7 +12193,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -12349,7 +12470,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -12360,7 +12481,7 @@ checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -12533,32 +12654,29 @@ dependencies = [ [[package]] name = "tokio" -version = "1.47.1" +version = "1.49.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" +checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" dependencies = [ - "backtrace", "bytes", - "io-uring", "libc", "mio", "pin-project-lite", "signal-hook-registry", - "slab", "socket2 0.6.0", "tokio-macros", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "tokio-macros" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -12602,7 +12720,7 @@ version = "0.8.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" dependencies = [ - "indexmap 2.11.4", + "indexmap 2.12.0", "serde", "serde_spanned 0.6.9", "toml_datetime 0.6.11", @@ -12615,7 +12733,7 @@ version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8" dependencies = [ - "indexmap 2.11.4", + "indexmap 2.12.0", "serde_core", "serde_spanned 1.0.3", "toml_datetime 0.7.3", @@ -12648,7 +12766,7 @@ version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ - "indexmap 2.11.4", + "indexmap 2.12.0", "serde", "serde_spanned 0.6.9", "toml_datetime 0.6.11", @@ -12662,7 +12780,7 @@ version = "0.23.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3effe7c0e86fdff4f69cdd2ccc1b96f933e24811c5441d44904e8683e27184b" dependencies = [ - "indexmap 2.11.4", + "indexmap 2.12.0", "toml_datetime 0.7.3", "toml_parser", "winnow", @@ -12730,7 +12848,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -12740,7 +12858,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "66bd50ad6ce1252d87ef024b3d64fe4c3cf54a86fb9ef4c631fdd0ded7aeaa67" dependencies = [ "bytes", - "prost 0.14.1", + "prost", "tonic", ] @@ -12752,10 +12870,10 @@ checksum = "b4a16cba4043dc3ff43fcb3f96b4c5c154c64cbd18ca8dce2ab2c6a451d058a2" dependencies = [ "prettyplease", "proc-macro2", - "prost-build 0.14.1", - "prost-types 0.14.1", + "prost-build", + "prost-types", "quote", - "syn 2.0.106", + "syn 2.0.114", "tempfile", "tonic-build", ] @@ -12811,7 +12929,7 @@ checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" dependencies = [ "futures-core", "futures-util", - "indexmap 2.11.4", + "indexmap 2.12.0", "pin-project-lite", "slab", "sync_wrapper", @@ -12873,7 +12991,7 @@ checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -12976,7 +13094,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c5f7c95348f20c1c913d72157b3c6dee6ea3e30b3d19502c5a7f6d3f160dacbf" dependencies = [ "cc", - "windows-targets 0.52.6", + "windows-targets 0.48.5", ] [[package]] @@ -13321,7 +13439,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -13379,7 +13497,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", "wasm-bindgen-shared", ] @@ -13447,7 +13565,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -13539,7 +13657,7 @@ dependencies = [ "ahash", "bitflags 2.9.4", "hashbrown 0.14.5", - "indexmap 2.11.4", + "indexmap 2.12.0", "semver", "serde", ] @@ -13756,7 +13874,7 @@ dependencies = [ "cfg_aliases", "document-features", "hashbrown 0.16.0", - "indexmap 2.11.4", + "indexmap 2.12.0", "log", "naga", "once_cell", @@ -13982,19 +14100,6 @@ dependencies = [ "windows-strings 0.4.2", ] -[[package]] -name = "windows-core" -version = "0.62.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" -dependencies = [ - "windows-implement 0.60.2", - "windows-interface 0.59.3", - "windows-link 0.2.1", - "windows-result 0.4.1", - "windows-strings 0.5.1", -] - [[package]] name = "windows-future" version = "0.2.1" @@ -14014,7 +14119,7 @@ checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -14025,7 +14130,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -14036,7 +14141,7 @@ checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -14047,7 +14152,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -14090,15 +14195,6 @@ dependencies = [ "windows-link 0.1.3", ] -[[package]] -name = "windows-result" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" -dependencies = [ - "windows-link 0.2.1", -] - [[package]] name = "windows-strings" version = "0.1.0" @@ -14118,15 +14214,6 @@ dependencies = [ "windows-link 0.1.3", ] -[[package]] -name = "windows-strings" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" -dependencies = [ - "windows-link 0.2.1", -] - [[package]] name = "windows-sys" version = "0.45.0" @@ -14490,6 +14577,31 @@ version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +[[package]] +name = "wkb" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a120b336c7ad17749026d50427c23d838ecb50cd64aaea6254b5030152f890a9" +dependencies = [ + "byteorder", + "geo-traits", + "num_enum", + "thiserror 1.0.69", +] + +[[package]] +name = "wkt" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efb2b923ccc882312e559ffaa832a055ba9d1ac0cc8e86b3e25453247e4b81d7" +dependencies = [ + "geo-traits", + "geo-types", + "log", + "num-traits", + "thiserror 1.0.69", +] + [[package]] name = "writeable" version = "0.6.1" @@ -14595,15 +14707,6 @@ version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdd20c5420375476fbd4394763288da7eb0cc0b8c11deed431a91562af7335d3" -[[package]] -name = "xz2" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "388c44dc09d76f1536602ead6d325eb532f5c122f17782bd57fb47baeeb767e2" -dependencies = [ - "lzma-sys", -] - [[package]] name = "yoke" version = "0.8.0" @@ -14624,7 +14727,7 @@ checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", "synstructure", ] @@ -14679,7 +14782,7 @@ checksum = "dc6821851fa840b708b4cbbaf6241868cabc85a2dc22f426361b0292bfc0b836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", "zbus-lockstep", "zbus_xml", "zvariant", @@ -14694,7 +14797,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", "zbus_names", "zvariant", "zvariant_utils", @@ -14752,7 +14855,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -14763,7 +14866,7 @@ checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -14783,7 +14886,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", "synstructure", ] @@ -14823,7 +14926,7 @@ checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -14849,7 +14952,7 @@ dependencies = [ "crossbeam-utils", "displaydoc", "flate2", - "indexmap 2.11.4", + "indexmap 2.12.0", "memchr", "thiserror 2.0.17", "zopfli", @@ -14939,7 +15042,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", "zvariant_utils", ] @@ -14952,6 +15055,6 @@ dependencies = [ "proc-macro2", "quote", "serde", - "syn 2.0.106", + "syn 2.0.114", "winnow", ] diff --git a/Cargo.toml b/Cargo.toml index af7187f27572..dfe90c0f3529 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -193,7 +193,7 @@ anyhow = { version = "1.0", default-features = false } argh = "0.1.13" arrayvec = "0.7" array-init = "2.1" -arrow = { version = "56.1", default-features = false, features = [ +arrow = { version = "57.0.0", default-features = false, features = [ # NOTE: Similar to `datafusion`, we enable many features on a workspace level # to avoid re-compilation when changing compile targets. "ffi", @@ -230,20 +230,21 @@ criterion = "0.5.0" cros-codecs = "0.0.6" crossbeam = "0.8.0" dae-parser = "0.11.0" -datafusion = { version = "50.1.0", default-features = false, features = [ +datafusion = { version = "51.0.0", default-features = false, features = [ # NOTE: we enable the same features everywhere # because otherwise we will recompile datafusion all the time based on our current compile target. - # The features here are the same as in https://github.com/lance-format/lance/blob/v0.38.0/Cargo.toml#L99-L107 + # The features here are the same as in https://github.com/lance-format/lance/blob/v2.0.0/Cargo.toml#L111-L118 # This is very hacky, and I don't like it. "crypto_expressions", "datetime_expressions", "encoding_expressions", "nested_expressions", "regex_expressions", + "sql", "string_expressions", "unicode_expressions", ] } -datafusion-ffi = "50.1.0" +datafusion-ffi = "51.0.0" directories = "6.0" document-features = "0.2.11" econtext = "0.2.0" # Prints error contexts on crashes @@ -281,9 +282,9 @@ itertools = "0.14.0" jiff = { version = "0.2.15", features = ["js"] } js-sys = "0.3.77" jsonwebtoken = { version = "9.3", default-features = false } -lance = { version = "0.38.2", default-features = false } # When you update this, also update the list of features enabled for `datafusion` (~50 lines up) -lance-index = { version = "0.38.2", default-features = false } -lance-linalg = { version = "0.38.2", default-features = false } +lance = { version = "2.0.0", default-features = false } # When you update this, also update the list of features enabled for `datafusion` (~50 lines up) +lance-index = { version = "2.0.0", default-features = false } +lance-linalg = { version = "2.0.0", default-features = false } libc = "0.2.176" linked-hash-map = { version = "0.5.6", default-features = false } log = "0.4.28" @@ -303,7 +304,7 @@ nohash-hasher = "0.2.0" notify = { version = "8.2", features = ["macos_kqueue"] } num-derive = "0.4.2" num-traits = "0.2.19" -numpy = "0.25.0" +numpy = "0.26.0" objc2-app-kit = "0.3.2" opentelemetry = { version = "0.31.0", features = ["metrics"] } opentelemetry-appender-tracing = "0.31.0" @@ -312,7 +313,7 @@ opentelemetry-otlp = { version = "0.31.0", features = ["gzip-tonic"] } opentelemetry_sdk = { version = "0.31.0", features = ["rt-tokio"] } ordered-float = "5.1.0" parking_lot = { version = "0.12.5", features = ["serde"] } -parquet = { version = "56.1", default-features = false } +parquet = { version = "57.0.0", default-features = false } paste = "1.0" pathdiff = "0.2.3" percent-encoding = "2.3" @@ -332,8 +333,8 @@ prost-types = "0.14.1" protoc-prebuilt = "0.3.0" puffin = "0.19.1" puffin_http = "0.16.1" -pyo3 = "0.25.1" -pyo3-build-config = "0.25.1" +pyo3 = "0.26.0" +pyo3-build-config = "0.26.0" quote = "1.0" rand = { version = "0.9.2", default-features = false, features = [ "small_rng", diff --git a/crates/build/re_types_builder/src/codegen/rust/arrow.rs b/crates/build/re_types_builder/src/codegen/rust/arrow.rs index 28e6dce50327..0df7e6b603c8 100644 --- a/crates/build/re_types_builder/src/codegen/rust/arrow.rs +++ b/crates/build/re_types_builder/src/codegen/rust/arrow.rs @@ -63,10 +63,10 @@ impl quote::ToTokens for ArrowDataTypeTokenizer<'_> { })) }); quote!(DataType::Union( - UnionFields::new( + UnionFields::try_new( vec![ #(#types,)* ], vec![ #(#fields,)* ], - ), + ).expect("UnionFields::try_new should be infallible"), #mode, )) } diff --git a/crates/build/re_types_builder/src/codegen/rust/serializer.rs b/crates/build/re_types_builder/src/codegen/rust/serializer.rs index 16e64bd2a765..59526a5c18ec 100644 --- a/crates/build/re_types_builder/src/codegen/rust/serializer.rs +++ b/crates/build/re_types_builder/src/codegen/rust/serializer.rs @@ -271,7 +271,7 @@ pub fn quote_arrow_serializer( re_log::debug_assert_eq!(fields.len(), children.len()); as_array_ref(UnionArray::try_new( - UnionFields::new(field_type_ids, fields), + UnionFields::try_new(field_type_ids, fields)?, ScalarBuffer::from(type_ids), None, children, @@ -437,7 +437,7 @@ pub fn quote_arrow_serializer( re_log::debug_assert_eq!(fields.len(), children.len()); as_array_ref(UnionArray::try_new( - UnionFields::new(field_type_ids, fields), + UnionFields::try_new(field_type_ids, fields)?, ScalarBuffer::from(type_ids), Some(offsets), children, diff --git a/crates/store/re_data_loader/tests/snapshots/test_mcap_loader__tests__ros2.snap b/crates/store/re_data_loader/tests/snapshots/test_mcap_loader__tests__ros2.snap index 0fecad4e9106..3c6b501d986d 100644 --- a/crates/store/re_data_loader/tests/snapshots/test_mcap_loader__tests__ros2.snap +++ b/crates/store/re_data_loader/tests/snapshots/test_mcap_loader__tests__ros2.snap @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c97095b25f8cc8d9ae4d0a4a705f971db31c99ba5bf48977048e1ce60303e064 -size 370577 +oid sha256:bba7c07804bddf7af15748520973b4a1065f67cdabc4cb50bd8a7db860d1607b +size 264983 diff --git a/crates/store/re_datafusion/src/batch_coalescer/coalesce_exec.rs b/crates/store/re_datafusion/src/batch_coalescer/coalesce_exec.rs index 7a77e80efb2d..3ca41742ff83 100644 --- a/crates/store/re_datafusion/src/batch_coalescer/coalesce_exec.rs +++ b/crates/store/re_datafusion/src/batch_coalescer/coalesce_exec.rs @@ -172,7 +172,6 @@ impl ExecutionPlan for SizedCoalesceBatchesExec { fn partition_statistics(&self, partition: Option) -> Result { self.input.partition_statistics(partition)?.with_fetch( - self.schema(), self.coalescer_options.max_rows, 0, 1, diff --git a/crates/store/re_sdk_types/src/datatypes/tensor_buffer.rs b/crates/store/re_sdk_types/src/datatypes/tensor_buffer.rs index d0d37395e6bb..1e0440638e40 100644 --- a/crates/store/re_sdk_types/src/datatypes/tensor_buffer.rs +++ b/crates/store/re_sdk_types/src/datatypes/tensor_buffer.rs @@ -67,7 +67,7 @@ impl ::re_types_core::Loggable for TensorBuffer { fn arrow_datatype() -> arrow::datatypes::DataType { use arrow::datatypes::*; DataType::Union( - UnionFields::new( + UnionFields::try_new( vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], vec![ Field::new("_null_markers", DataType::Null, true), @@ -171,7 +171,8 @@ impl ::re_types_core::Loggable for TensorBuffer { false, ), ], - ), + ) + .expect("UnionFields::try_new should be infallible"), UnionMode::Dense, ) } @@ -750,7 +751,7 @@ impl ::re_types_core::Loggable for TensorBuffer { re_log::debug_assert_eq!(field_type_ids.len(), fields.len()); re_log::debug_assert_eq!(fields.len(), children.len()); as_array_ref(UnionArray::try_new( - UnionFields::new(field_type_ids, fields), + UnionFields::try_new(field_type_ids, fields)?, ScalarBuffer::from(type_ids), Some(offsets), children, diff --git a/crates/store/re_sdk_types/src/testing/components/affix_fuzzer15.rs b/crates/store/re_sdk_types/src/testing/components/affix_fuzzer15.rs index a5e68eb7a9f1..2b869e8c56b9 100644 --- a/crates/store/re_sdk_types/src/testing/components/affix_fuzzer15.rs +++ b/crates/store/re_sdk_types/src/testing/components/affix_fuzzer15.rs @@ -38,7 +38,7 @@ impl ::re_types_core::Loggable for AffixFuzzer15 { fn arrow_datatype() -> arrow::datatypes::DataType { use arrow::datatypes::*; DataType::Union( - UnionFields::new( + UnionFields::try_new( vec![0, 1, 2, 3, 4], vec![ Field::new("_null_markers", DataType::Null, true), @@ -62,7 +62,8 @@ impl ::re_types_core::Loggable for AffixFuzzer15 { ), Field::new("empty_variant", DataType::Null, true), ], - ), + ) + .expect("UnionFields::try_new should be infallible"), UnionMode::Dense, ) } diff --git a/crates/store/re_sdk_types/src/testing/datatypes/affix_fuzzer3.rs b/crates/store/re_sdk_types/src/testing/datatypes/affix_fuzzer3.rs index 7e9f838c283e..dc2d2a1317af 100644 --- a/crates/store/re_sdk_types/src/testing/datatypes/affix_fuzzer3.rs +++ b/crates/store/re_sdk_types/src/testing/datatypes/affix_fuzzer3.rs @@ -36,7 +36,7 @@ impl ::re_types_core::Loggable for AffixFuzzer3 { fn arrow_datatype() -> arrow::datatypes::DataType { use arrow::datatypes::*; DataType::Union( - UnionFields::new( + UnionFields::try_new( vec![0, 1, 2, 3, 4], vec![ Field::new("_null_markers", DataType::Null, true), @@ -60,7 +60,8 @@ impl ::re_types_core::Loggable for AffixFuzzer3 { ), Field::new("empty_variant", DataType::Null, true), ], - ), + ) + .expect("UnionFields::try_new should be infallible"), UnionMode::Dense, ) } @@ -240,7 +241,7 @@ impl ::re_types_core::Loggable for AffixFuzzer3 { re_log::debug_assert_eq!(field_type_ids.len(), fields.len()); re_log::debug_assert_eq!(fields.len(), children.len()); as_array_ref(UnionArray::try_new( - UnionFields::new(field_type_ids, fields), + UnionFields::try_new(field_type_ids, fields)?, ScalarBuffer::from(type_ids), Some(offsets), children, diff --git a/crates/store/re_sdk_types/src/testing/datatypes/affix_fuzzer4.rs b/crates/store/re_sdk_types/src/testing/datatypes/affix_fuzzer4.rs index 5538d4279680..81768d69de02 100644 --- a/crates/store/re_sdk_types/src/testing/datatypes/affix_fuzzer4.rs +++ b/crates/store/re_sdk_types/src/testing/datatypes/affix_fuzzer4.rs @@ -34,7 +34,7 @@ impl ::re_types_core::Loggable for AffixFuzzer4 { fn arrow_datatype() -> arrow::datatypes::DataType { use arrow::datatypes::*; DataType::Union( - UnionFields::new( + UnionFields::try_new( vec![0, 1, 2], vec![ Field::new("_null_markers", DataType::Null, true), @@ -53,7 +53,8 @@ impl ::re_types_core::Loggable for AffixFuzzer4 { false, ), ], - ), + ) + .expect("UnionFields::try_new should be infallible"), UnionMode::Dense, ) } @@ -181,7 +182,7 @@ impl ::re_types_core::Loggable for AffixFuzzer4 { re_log::debug_assert_eq!(field_type_ids.len(), fields.len()); re_log::debug_assert_eq!(fields.len(), children.len()); as_array_ref(UnionArray::try_new( - UnionFields::new(field_type_ids, fields), + UnionFields::try_new(field_type_ids, fields)?, ScalarBuffer::from(type_ids), Some(offsets), children, diff --git a/crates/store/re_server/src/chunk_index/search.rs b/crates/store/re_server/src/chunk_index/search.rs index 3b0144a592f4..13b2a0572c6a 100644 --- a/crates/store/re_server/src/chunk_index/search.rs +++ b/crates/store/re_server/src/chunk_index/search.rs @@ -110,7 +110,7 @@ async fn apply_parameters( if let Some(filter) = filter.filter(|f| !f.is_empty()) { let filter = lance::io::exec::Planner::new(scanner.schema().await?).parse_filter(&filter)?; - match scanner.get_filter()? { + match scanner.get_expr_filter()? { Some(existing_filter) => { scanner.filter_expr(existing_filter.and(filter)); } @@ -146,7 +146,7 @@ async fn apply_parameters( } if explain_filter { - match scanner.get_filter() { + match scanner.get_expr_filter() { Ok(Some(filter)) => { info!(%filter); } diff --git a/crates/store/re_types_core/src/datatypes/time_range_boundary.rs b/crates/store/re_types_core/src/datatypes/time_range_boundary.rs index 73444385beda..94277df97567 100644 --- a/crates/store/re_types_core/src/datatypes/time_range_boundary.rs +++ b/crates/store/re_types_core/src/datatypes/time_range_boundary.rs @@ -41,7 +41,7 @@ impl crate::Loggable for TimeRangeBoundary { fn arrow_datatype() -> arrow::datatypes::DataType { use arrow::datatypes::*; DataType::Union( - UnionFields::new( + UnionFields::try_new( vec![0, 1, 2, 3], vec![ Field::new("_null_markers", DataType::Null, true), @@ -57,7 +57,8 @@ impl crate::Loggable for TimeRangeBoundary { ), Field::new("Infinite", DataType::Null, true), ], - ), + ) + .expect("UnionFields::try_new should be infallible"), UnionMode::Dense, ) } @@ -183,7 +184,7 @@ impl crate::Loggable for TimeRangeBoundary { re_log::debug_assert_eq!(field_type_ids.len(), fields.len()); re_log::debug_assert_eq!(fields.len(), children.len()); as_array_ref(UnionArray::try_new( - UnionFields::new(field_type_ids, fields), + UnionFields::try_new(field_type_ids, fields)?, ScalarBuffer::from(type_ids), Some(offsets), children, diff --git a/crates/utils/re_arrow_util/src/arrays.rs b/crates/utils/re_arrow_util/src/arrays.rs index 900c54392332..a4ee64d76335 100644 --- a/crates/utils/re_arrow_util/src/arrays.rs +++ b/crates/utils/re_arrow_util/src/arrays.rs @@ -658,7 +658,8 @@ mod tests { Arc::new(Field::new("f32", f32s.data_type().clone(), true)), Arc::new(Field::new("i64", i64s.data_type().clone(), true)), ]; - let union_fields = UnionFields::new(type_ids, fields); + let union_fields = + UnionFields::try_new(type_ids, fields).expect("UnionFields should be infallible"); let type_id_buffer = ScalarBuffer::from( (0..NUM_TOTAL as i32) @@ -700,7 +701,8 @@ mod tests { Arc::new(Field::new("f32", f32s.data_type().clone(), true)), Arc::new(Field::new("i64", i64s.data_type().clone(), true)), ]; - let union_fields = UnionFields::new(type_ids, fields); + let union_fields = + UnionFields::try_new(type_ids, fields).expect("UnionFields should be infallible"); let type_id_buffer = ScalarBuffer::from( (0..NUM_TOTAL as i32) @@ -780,7 +782,8 @@ mod tests { Arc::new(Field::new("f32_list", list_f32s.data_type().clone(), true)), Arc::new(Field::new("i64_list", list_i64s.data_type().clone(), true)), ]; - let union_fields = UnionFields::new(type_ids, fields); + let union_fields = + UnionFields::try_new(type_ids, fields).expect("UnionFields should be infallible"); let type_id_buffer = ScalarBuffer::from( (0..(NUM_TOTAL / NUM_PER_BATCH) as i32) @@ -869,7 +872,8 @@ mod tests { Arc::new(Field::new("f32_list", list_f32s.data_type().clone(), true)), Arc::new(Field::new("i64_list", list_i64s.data_type().clone(), true)), ]; - let union_fields = UnionFields::new(type_ids, fields); + let union_fields = UnionFields::try_new(type_ids, fields) + .expect("UnionFields::try_new should be infallible"); let type_id_buffer = ScalarBuffer::from( (0..(NUM_TOTAL / NUM_PER_BATCH) as i32) diff --git a/crates/utils/re_arrow_util/src/batches.rs b/crates/utils/re_arrow_util/src/batches.rs index 556648581ea8..acdefaa133ae 100644 --- a/crates/utils/re_arrow_util/src/batches.rs +++ b/crates/utils/re_arrow_util/src/batches.rs @@ -498,7 +498,7 @@ mod tests { ); batch_concat.schema_metadata_mut().clear(); - insta::assert_debug_snapshot!(batch_concat, @r###" + insta::assert_debug_snapshot!(batch_concat, @r#" RecordBatch { schema: Schema { fields: [ @@ -506,33 +506,21 @@ mod tests { name: "col1", data_type: Int32, nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, Field { name: "col2", data_type: Utf8, nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, Field { name: "col3", data_type: Boolean, nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, Field { name: "col4", data_type: UInt64, nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, ], metadata: {}, @@ -565,7 +553,7 @@ mod tests { ], row_count: 3, } - "###); + "#); } #[test] diff --git a/crates/viewer/re_arrow_ui/tests/arrow_test_data/mod.rs b/crates/viewer/re_arrow_ui/tests/arrow_test_data/mod.rs index 8c322db353aa..5c72b14fca25 100644 --- a/crates/viewer/re_arrow_ui/tests/arrow_test_data/mod.rs +++ b/crates/viewer/re_arrow_ui/tests/arrow_test_data/mod.rs @@ -147,7 +147,7 @@ pub fn create_union_array(step: i32) -> Arc { ]), ]); - let union_fields = UnionFields::new( + let union_fields = UnionFields::try_new( vec![0, 1, 2, 3, 4], vec![ Field::new("int_variant", DataType::Int32, false), @@ -160,7 +160,8 @@ pub fn create_union_array(step: i32) -> Arc { false, ), ], - ); + ) + .expect("UnionFields should be infallible"); let children = vec![ Arc::new(int_values) as Arc, diff --git a/crates/viewer/re_dataframe_ui/src/filters/boolean.rs b/crates/viewer/re_dataframe_ui/src/filters/boolean.rs index 27f42fd8dc08..47c020b49fea 100644 --- a/crates/viewer/re_dataframe_ui/src/filters/boolean.rs +++ b/crates/viewer/re_dataframe_ui/src/filters/boolean.rs @@ -2,6 +2,7 @@ use std::fmt::Formatter; use arrow::datatypes::{DataType, Field}; use datafusion::common::Column; +use datafusion::functions::core::expr_fn::arrow_cast; use datafusion::logical_expr::{Expr, col, lit, not}; use datafusion::prelude::{array_element, array_has, array_sort}; use re_ui::syntax_highlighting::SyntaxHighlightedBuilder; @@ -198,8 +199,13 @@ impl Filter for NullableBooleanFilter { if let Some(value) = self.value.as_bool() { array_has(col(column), lit(value)) } else { + // TODO(apache/datafusion#19947): Remove arrow_cast when resolved col(column.clone()).is_null().or(array_element( - array_sort(col(column), lit("ASC"), lit("NULLS FIRST")), + array_sort( + arrow_cast(col(column), lit("List(Boolean)")), + lit("ASC"), + lit("NULLS FIRST"), + ), lit(1), ) .is_null()) diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__boolean_equals_list_non_nullable@both_null_true.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__boolean_equals_list_non_nullable@both_null_true.snap index 4a22152ff3a4..ca568d7065a7 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__boolean_equals_list_non_nullable@both_null_true.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__boolean_equals_list_non_nullable@both_null_true.snap @@ -13,15 +13,9 @@ TestResult { name: "column", data_type: Boolean, nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, ), nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: ListArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__boolean_equals_list_non_nullable@inner_null_true.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__boolean_equals_list_non_nullable@inner_null_true.snap index ab39eddd8689..e05ccf80ce2e 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__boolean_equals_list_non_nullable@inner_null_true.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__boolean_equals_list_non_nullable@inner_null_true.snap @@ -13,15 +13,8 @@ TestResult { name: "column", data_type: Boolean, nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, ), - nullable: false, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: ListArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__boolean_equals_list_non_nullable@no_null_true.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__boolean_equals_list_non_nullable@no_null_true.snap index 3103e5a62747..1c6b3aea4a21 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__boolean_equals_list_non_nullable@no_null_true.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__boolean_equals_list_non_nullable@no_null_true.snap @@ -12,16 +12,8 @@ TestResult { Field { name: "column", data_type: Boolean, - nullable: false, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, ), - nullable: false, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: ListArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__boolean_equals_list_non_nullable@outer_null_true.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__boolean_equals_list_non_nullable@outer_null_true.snap index 56cd711c207b..6a5eb2f443b7 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__boolean_equals_list_non_nullable@outer_null_true.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__boolean_equals_list_non_nullable@outer_null_true.snap @@ -12,16 +12,9 @@ TestResult { Field { name: "column", data_type: Boolean, - nullable: false, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, ), nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: ListArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__boolean_equals_list_nullable@both_null_is_not_true.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__boolean_equals_list_nullable@both_null_is_not_true.snap index 4d8f10989ee3..08f40634324d 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__boolean_equals_list_nullable@both_null_is_not_true.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__boolean_equals_list_nullable@both_null_is_not_true.snap @@ -13,15 +13,9 @@ TestResult { name: "column", data_type: Boolean, nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, ), nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: ListArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__boolean_equals_list_nullable@both_null_is_null.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__boolean_equals_list_nullable@both_null_is_null.snap index 1f9a7f96932c..ff0ebc49fe49 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__boolean_equals_list_nullable@both_null_is_null.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__boolean_equals_list_nullable@both_null_is_null.snap @@ -13,15 +13,9 @@ TestResult { name: "column", data_type: Boolean, nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, ), nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: ListArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__boolean_equals_list_nullable@both_null_is_true.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__boolean_equals_list_nullable@both_null_is_true.snap index 386a123ad6fe..e7d6a8000a5c 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__boolean_equals_list_nullable@both_null_is_true.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__boolean_equals_list_nullable@both_null_is_true.snap @@ -13,15 +13,9 @@ TestResult { name: "column", data_type: Boolean, nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, ), nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: ListArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__boolean_equals_list_nullable@inner_null_is_not_true.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__boolean_equals_list_nullable@inner_null_is_not_true.snap index 7cba3c91401e..816dbdd2d861 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__boolean_equals_list_nullable@inner_null_is_not_true.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__boolean_equals_list_nullable@inner_null_is_not_true.snap @@ -13,15 +13,8 @@ TestResult { name: "column", data_type: Boolean, nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, ), - nullable: false, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: ListArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__boolean_equals_list_nullable@inner_null_is_null.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__boolean_equals_list_nullable@inner_null_is_null.snap index 7705df3ce48a..cb5942702030 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__boolean_equals_list_nullable@inner_null_is_null.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__boolean_equals_list_nullable@inner_null_is_null.snap @@ -13,15 +13,8 @@ TestResult { name: "column", data_type: Boolean, nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, ), - nullable: false, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: ListArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__boolean_equals_list_nullable@inner_null_is_true.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__boolean_equals_list_nullable@inner_null_is_true.snap index 979a89ed7185..5775fc6815a0 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__boolean_equals_list_nullable@inner_null_is_true.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__boolean_equals_list_nullable@inner_null_is_true.snap @@ -13,15 +13,8 @@ TestResult { name: "column", data_type: Boolean, nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, ), - nullable: false, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: ListArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__boolean_equals_list_nullable@outer_null_is_not_true.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__boolean_equals_list_nullable@outer_null_is_not_true.snap index 62a43d43593c..c0b2778f5278 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__boolean_equals_list_nullable@outer_null_is_not_true.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__boolean_equals_list_nullable@outer_null_is_not_true.snap @@ -12,16 +12,9 @@ TestResult { Field { name: "column", data_type: Boolean, - nullable: false, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, ), nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: ListArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__boolean_equals_list_nullable@outer_null_is_null.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__boolean_equals_list_nullable@outer_null_is_null.snap index 66e4eea0ad24..778da01c5515 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__boolean_equals_list_nullable@outer_null_is_null.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__boolean_equals_list_nullable@outer_null_is_null.snap @@ -12,16 +12,9 @@ TestResult { Field { name: "column", data_type: Boolean, - nullable: false, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, ), nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: ListArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__boolean_equals_list_nullable@outer_null_is_true.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__boolean_equals_list_nullable@outer_null_is_true.snap index 06e2664d5c64..4120a7502fee 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__boolean_equals_list_nullable@outer_null_is_true.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__boolean_equals_list_nullable@outer_null_is_true.snap @@ -12,16 +12,9 @@ TestResult { Field { name: "column", data_type: Boolean, - nullable: false, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, ), nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: ListArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_all_types@Float32Type.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_all_types@Float32Type.snap index 31cb38ff9d15..3eb5183cc8c5 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_all_types@Float32Type.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_all_types@Float32Type.snap @@ -14,10 +14,6 @@ TestResult { field: Field { name: "column", data_type: Float32, - nullable: false, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: PrimitiveArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_all_types@Float64Type.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_all_types@Float64Type.snap index 2e8713214e65..50eb4fec370a 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_all_types@Float64Type.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_all_types@Float64Type.snap @@ -14,10 +14,6 @@ TestResult { field: Field { name: "column", data_type: Float64, - nullable: false, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: PrimitiveArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_compares@eq_3.0.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_compares@eq_3.0.snap index 2e8713214e65..50eb4fec370a 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_compares@eq_3.0.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_compares@eq_3.0.snap @@ -14,10 +14,6 @@ TestResult { field: Field { name: "column", data_type: Float64, - nullable: false, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: PrimitiveArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_compares@ge_3.0.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_compares@ge_3.0.snap index b68a471c61f1..2bd5784e5f95 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_compares@ge_3.0.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_compares@ge_3.0.snap @@ -14,10 +14,6 @@ TestResult { field: Field { name: "column", data_type: Float64, - nullable: false, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: PrimitiveArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_compares@gt_3.0.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_compares@gt_3.0.snap index 6e4a2fd19c51..9410a3171426 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_compares@gt_3.0.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_compares@gt_3.0.snap @@ -14,10 +14,6 @@ TestResult { field: Field { name: "column", data_type: Float64, - nullable: false, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: PrimitiveArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_compares@le_3.0.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_compares@le_3.0.snap index b6213fb55a79..1b94f90e4cf3 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_compares@le_3.0.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_compares@le_3.0.snap @@ -14,10 +14,6 @@ TestResult { field: Field { name: "column", data_type: Float64, - nullable: false, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: PrimitiveArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_compares@lt_3.0.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_compares@lt_3.0.snap index 7702157345e2..7dd80e9bc923 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_compares@lt_3.0.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_compares@lt_3.0.snap @@ -14,10 +14,6 @@ TestResult { field: Field { name: "column", data_type: Float64, - nullable: false, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: PrimitiveArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_compares@ne_3.0.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_compares@ne_3.0.snap index 4971eb6b6ae9..d1f63223e599 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_compares@ne_3.0.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_compares@ne_3.0.snap @@ -14,10 +14,6 @@ TestResult { field: Field { name: "column", data_type: Float64, - nullable: false, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: PrimitiveArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_compares@nulls_eq_4.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_compares@nulls_eq_4.snap index 717600425072..51ee4f274dba 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_compares@nulls_eq_4.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_compares@nulls_eq_4.snap @@ -15,9 +15,6 @@ TestResult { name: "column", data_type: Float64, nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: PrimitiveArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_compares@nulls_ge_4.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_compares@nulls_ge_4.snap index 26cd17f3ff7c..09559b1eea0a 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_compares@nulls_ge_4.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_compares@nulls_ge_4.snap @@ -15,9 +15,6 @@ TestResult { name: "column", data_type: Float64, nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: PrimitiveArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_compares@nulls_gt_4.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_compares@nulls_gt_4.snap index 2ec1766272cd..58764176c71b 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_compares@nulls_gt_4.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_compares@nulls_gt_4.snap @@ -15,9 +15,6 @@ TestResult { name: "column", data_type: Float64, nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: PrimitiveArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_compares@nulls_le_4.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_compares@nulls_le_4.snap index 47b5ed2f6d2d..fbd5625f866a 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_compares@nulls_le_4.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_compares@nulls_le_4.snap @@ -15,9 +15,6 @@ TestResult { name: "column", data_type: Float64, nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: PrimitiveArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_compares@nulls_lt_4.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_compares@nulls_lt_4.snap index 8f823b4490af..2248ba730126 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_compares@nulls_lt_4.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_compares@nulls_lt_4.snap @@ -15,9 +15,6 @@ TestResult { name: "column", data_type: Float64, nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: PrimitiveArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_compares@nulls_ne_4.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_compares@nulls_ne_4.snap index 7250c8bc9b8e..5e9c9eaf113e 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_compares@nulls_ne_4.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_compares@nulls_ne_4.snap @@ -15,9 +15,6 @@ TestResult { name: "column", data_type: Float64, nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: PrimitiveArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_lists@eq_2.0.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_lists@eq_2.0.snap index 1ed9a118eed5..64362dd883ac 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_lists@eq_2.0.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_lists@eq_2.0.snap @@ -15,18 +15,10 @@ TestResult { name: "column", data_type: List( Field { - name: "item", data_type: Float64, nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, ), - nullable: false, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: ListArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_lists@ge_2.0.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_lists@ge_2.0.snap index 1eea443861d3..f4a2c381c186 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_lists@ge_2.0.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_lists@ge_2.0.snap @@ -15,18 +15,10 @@ TestResult { name: "column", data_type: List( Field { - name: "item", data_type: Float64, nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, ), - nullable: false, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: ListArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_lists@gt_2.0.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_lists@gt_2.0.snap index c454f48e4fc2..33dac8c80084 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_lists@gt_2.0.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_lists@gt_2.0.snap @@ -15,18 +15,10 @@ TestResult { name: "column", data_type: List( Field { - name: "item", data_type: Float64, nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, ), - nullable: false, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: ListArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_lists@le_2.0.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_lists@le_2.0.snap index f85adfba2a56..fc2deace8d7e 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_lists@le_2.0.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_lists@le_2.0.snap @@ -15,18 +15,10 @@ TestResult { name: "column", data_type: List( Field { - name: "item", data_type: Float64, nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, ), - nullable: false, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: ListArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_lists@lt_2.0.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_lists@lt_2.0.snap index c3115d2384f9..dd7d460be0b4 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_lists@lt_2.0.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_lists@lt_2.0.snap @@ -15,18 +15,10 @@ TestResult { name: "column", data_type: List( Field { - name: "item", data_type: Float64, nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, ), - nullable: false, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: ListArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_lists@ne_2.0.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_lists@ne_2.0.snap index a3ebde9ec020..0ac1854cca2a 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_lists@ne_2.0.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_lists@ne_2.0.snap @@ -15,18 +15,10 @@ TestResult { name: "column", data_type: List( Field { - name: "item", data_type: Float64, nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, ), - nullable: false, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: ListArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_lists@nulls_eq_2.0.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_lists@nulls_eq_2.0.snap index 3fa2a0647a92..93dd53ab7575 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_lists@nulls_eq_2.0.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_lists@nulls_eq_2.0.snap @@ -15,18 +15,11 @@ TestResult { name: "column", data_type: List( Field { - name: "item", data_type: Float64, nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, ), nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: ListArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_lists@nulls_ge_2.0.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_lists@nulls_ge_2.0.snap index abf1e2b9467a..fb08d2cf6de0 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_lists@nulls_ge_2.0.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_lists@nulls_ge_2.0.snap @@ -15,18 +15,11 @@ TestResult { name: "column", data_type: List( Field { - name: "item", data_type: Float64, nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, ), nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: ListArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_lists@nulls_gt_2.0.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_lists@nulls_gt_2.0.snap index 96a6591e1f6e..971750ff7817 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_lists@nulls_gt_2.0.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_lists@nulls_gt_2.0.snap @@ -15,18 +15,11 @@ TestResult { name: "column", data_type: List( Field { - name: "item", data_type: Float64, nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, ), nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: ListArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_lists@nulls_le_2.0.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_lists@nulls_le_2.0.snap index 2378a417b2c7..2247011ff987 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_lists@nulls_le_2.0.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_lists@nulls_le_2.0.snap @@ -15,18 +15,11 @@ TestResult { name: "column", data_type: List( Field { - name: "item", data_type: Float64, nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, ), nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: ListArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_lists@nulls_lt_2.0.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_lists@nulls_lt_2.0.snap index c7732a93cc12..0721572f1463 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_lists@nulls_lt_2.0.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_lists@nulls_lt_2.0.snap @@ -15,18 +15,11 @@ TestResult { name: "column", data_type: List( Field { - name: "item", data_type: Float64, nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, ), nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: ListArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_lists@nulls_ne_2.0.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_lists@nulls_ne_2.0.snap index 3e69cb88b6a3..5794dcf05eef 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_lists@nulls_ne_2.0.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__float_lists@nulls_ne_2.0.snap @@ -15,18 +15,11 @@ TestResult { name: "column", data_type: List( Field { - name: "item", data_type: Float64, nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, ), nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: ListArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_all_types@Int16Type.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_all_types@Int16Type.snap index 576949f2439c..7e6e8e8785c1 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_all_types@Int16Type.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_all_types@Int16Type.snap @@ -14,10 +14,6 @@ TestResult { field: Field { name: "column", data_type: Int16, - nullable: false, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: PrimitiveArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_all_types@Int32Type.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_all_types@Int32Type.snap index 56abb0edd6ee..064f834d3580 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_all_types@Int32Type.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_all_types@Int32Type.snap @@ -14,10 +14,6 @@ TestResult { field: Field { name: "column", data_type: Int32, - nullable: false, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: PrimitiveArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_all_types@Int64Type.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_all_types@Int64Type.snap index 8ac2b0f2f453..8c898d5ae0cd 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_all_types@Int64Type.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_all_types@Int64Type.snap @@ -14,10 +14,6 @@ TestResult { field: Field { name: "column", data_type: Int64, - nullable: false, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: PrimitiveArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_all_types@Int8Type.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_all_types@Int8Type.snap index 01a9243a6fcb..3fceb0364a61 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_all_types@Int8Type.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_all_types@Int8Type.snap @@ -14,10 +14,6 @@ TestResult { field: Field { name: "column", data_type: Int8, - nullable: false, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: PrimitiveArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_all_types@UInt16Type.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_all_types@UInt16Type.snap index d14ac1825927..37e0bac14dd1 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_all_types@UInt16Type.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_all_types@UInt16Type.snap @@ -14,10 +14,6 @@ TestResult { field: Field { name: "column", data_type: UInt16, - nullable: false, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: PrimitiveArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_all_types@UInt32Type.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_all_types@UInt32Type.snap index 4b98508e0c42..645fd707d16d 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_all_types@UInt32Type.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_all_types@UInt32Type.snap @@ -14,10 +14,6 @@ TestResult { field: Field { name: "column", data_type: UInt32, - nullable: false, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: PrimitiveArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_all_types@UInt64Type.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_all_types@UInt64Type.snap index 591159574121..578a89ab9531 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_all_types@UInt64Type.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_all_types@UInt64Type.snap @@ -14,10 +14,6 @@ TestResult { field: Field { name: "column", data_type: UInt64, - nullable: false, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: PrimitiveArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_all_types@UInt8Type.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_all_types@UInt8Type.snap index 79bed8fb5c40..14339cb505b0 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_all_types@UInt8Type.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_all_types@UInt8Type.snap @@ -14,10 +14,6 @@ TestResult { field: Field { name: "column", data_type: UInt8, - nullable: false, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: PrimitiveArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_compares@eq_3.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_compares@eq_3.snap index 8ac2b0f2f453..8c898d5ae0cd 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_compares@eq_3.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_compares@eq_3.snap @@ -14,10 +14,6 @@ TestResult { field: Field { name: "column", data_type: Int64, - nullable: false, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: PrimitiveArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_compares@ge_3.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_compares@ge_3.snap index a42d51e815bd..0d006454d95b 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_compares@ge_3.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_compares@ge_3.snap @@ -14,10 +14,6 @@ TestResult { field: Field { name: "column", data_type: Int64, - nullable: false, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: PrimitiveArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_compares@gt_3.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_compares@gt_3.snap index d831940331b5..67f249e5dff2 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_compares@gt_3.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_compares@gt_3.snap @@ -14,10 +14,6 @@ TestResult { field: Field { name: "column", data_type: Int64, - nullable: false, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: PrimitiveArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_compares@le_3.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_compares@le_3.snap index 8c483ba5dc84..ecf51cc507d7 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_compares@le_3.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_compares@le_3.snap @@ -14,10 +14,6 @@ TestResult { field: Field { name: "column", data_type: Int64, - nullable: false, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: PrimitiveArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_compares@lt_3.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_compares@lt_3.snap index 2671b09a10e8..cd6dee3dbd07 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_compares@lt_3.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_compares@lt_3.snap @@ -14,10 +14,6 @@ TestResult { field: Field { name: "column", data_type: Int64, - nullable: false, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: PrimitiveArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_compares@ne_3.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_compares@ne_3.snap index 0d01b10f11f6..c099670af867 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_compares@ne_3.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_compares@ne_3.snap @@ -14,10 +14,6 @@ TestResult { field: Field { name: "column", data_type: Int64, - nullable: false, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: PrimitiveArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_compares@nulls_eq_4.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_compares@nulls_eq_4.snap index d24e1d830ec1..0dad19970741 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_compares@nulls_eq_4.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_compares@nulls_eq_4.snap @@ -15,9 +15,6 @@ TestResult { name: "column", data_type: Int64, nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: PrimitiveArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_compares@nulls_eq_unspecified.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_compares@nulls_eq_unspecified.snap index dceadc83d318..0591aad9c407 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_compares@nulls_eq_unspecified.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_compares@nulls_eq_unspecified.snap @@ -13,9 +13,6 @@ TestResult { name: "column", data_type: Int64, nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: PrimitiveArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_compares@nulls_ge_4.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_compares@nulls_ge_4.snap index 344c70f812bd..a28f160cd37e 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_compares@nulls_ge_4.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_compares@nulls_ge_4.snap @@ -15,9 +15,6 @@ TestResult { name: "column", data_type: Int64, nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: PrimitiveArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_compares@nulls_ge_unspecified.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_compares@nulls_ge_unspecified.snap index 8faf1b8ea6de..a84c61c34f3e 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_compares@nulls_ge_unspecified.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_compares@nulls_ge_unspecified.snap @@ -13,9 +13,6 @@ TestResult { name: "column", data_type: Int64, nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: PrimitiveArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_compares@nulls_gt_4.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_compares@nulls_gt_4.snap index efea629d9d96..a866d75fc736 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_compares@nulls_gt_4.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_compares@nulls_gt_4.snap @@ -15,9 +15,6 @@ TestResult { name: "column", data_type: Int64, nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: PrimitiveArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_compares@nulls_gt_unspecified.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_compares@nulls_gt_unspecified.snap index b65c2847fad0..4e21dd44f8cf 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_compares@nulls_gt_unspecified.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_compares@nulls_gt_unspecified.snap @@ -13,9 +13,6 @@ TestResult { name: "column", data_type: Int64, nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: PrimitiveArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_compares@nulls_le_4.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_compares@nulls_le_4.snap index 25adb63024b7..f236ed5dfa06 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_compares@nulls_le_4.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_compares@nulls_le_4.snap @@ -15,9 +15,6 @@ TestResult { name: "column", data_type: Int64, nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: PrimitiveArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_compares@nulls_le_unspecified.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_compares@nulls_le_unspecified.snap index c20b2baac36e..e2cea6911956 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_compares@nulls_le_unspecified.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_compares@nulls_le_unspecified.snap @@ -13,9 +13,6 @@ TestResult { name: "column", data_type: Int64, nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: PrimitiveArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_compares@nulls_lt_4.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_compares@nulls_lt_4.snap index 9180d2cd47a9..4e7bb1966bee 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_compares@nulls_lt_4.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_compares@nulls_lt_4.snap @@ -15,9 +15,6 @@ TestResult { name: "column", data_type: Int64, nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: PrimitiveArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_compares@nulls_lt_unspecified.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_compares@nulls_lt_unspecified.snap index 287a9b857aa7..bcce67908be8 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_compares@nulls_lt_unspecified.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_compares@nulls_lt_unspecified.snap @@ -13,9 +13,6 @@ TestResult { name: "column", data_type: Int64, nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: PrimitiveArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_compares@nulls_ne_4.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_compares@nulls_ne_4.snap index 1b7d367e6693..595e77af19a6 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_compares@nulls_ne_4.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_compares@nulls_ne_4.snap @@ -15,9 +15,6 @@ TestResult { name: "column", data_type: Int64, nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: PrimitiveArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_compares@nulls_ne_unspecified.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_compares@nulls_ne_unspecified.snap index 31b2ba7f9d74..d1920cb51b8f 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_compares@nulls_ne_unspecified.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_compares@nulls_ne_unspecified.snap @@ -13,9 +13,6 @@ TestResult { name: "column", data_type: Int64, nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: PrimitiveArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_lists@eq_2.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_lists@eq_2.snap index 3c9bd1e716e8..95f32d41ff7b 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_lists@eq_2.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_lists@eq_2.snap @@ -15,18 +15,10 @@ TestResult { name: "column", data_type: List( Field { - name: "item", data_type: Int64, nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, ), - nullable: false, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: ListArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_lists@ge_2.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_lists@ge_2.snap index 9b3192ba3521..af765d31d1e2 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_lists@ge_2.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_lists@ge_2.snap @@ -15,18 +15,10 @@ TestResult { name: "column", data_type: List( Field { - name: "item", data_type: Int64, nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, ), - nullable: false, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: ListArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_lists@gt_2.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_lists@gt_2.snap index c5b11cad03ab..7bc310550ca6 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_lists@gt_2.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_lists@gt_2.snap @@ -15,18 +15,10 @@ TestResult { name: "column", data_type: List( Field { - name: "item", data_type: Int64, nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, ), - nullable: false, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: ListArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_lists@le_2.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_lists@le_2.snap index 35f35b4528ff..16ec64a4f49c 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_lists@le_2.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_lists@le_2.snap @@ -15,18 +15,10 @@ TestResult { name: "column", data_type: List( Field { - name: "item", data_type: Int64, nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, ), - nullable: false, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: ListArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_lists@lt_2.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_lists@lt_2.snap index d5abd5433f09..c4b5c129ceec 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_lists@lt_2.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_lists@lt_2.snap @@ -15,18 +15,10 @@ TestResult { name: "column", data_type: List( Field { - name: "item", data_type: Int64, nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, ), - nullable: false, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: ListArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_lists@ne_2.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_lists@ne_2.snap index bf490e12c6d6..213982e829c4 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_lists@ne_2.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_lists@ne_2.snap @@ -15,18 +15,10 @@ TestResult { name: "column", data_type: List( Field { - name: "item", data_type: Int64, nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, ), - nullable: false, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: ListArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_lists@nulls_eq_2.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_lists@nulls_eq_2.snap index 05ba7c20af41..b60a52df7647 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_lists@nulls_eq_2.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_lists@nulls_eq_2.snap @@ -15,18 +15,11 @@ TestResult { name: "column", data_type: List( Field { - name: "item", data_type: Int64, nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, ), nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: ListArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_lists@nulls_ge_2.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_lists@nulls_ge_2.snap index 2ecf00c5a78c..cc90f63e1b21 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_lists@nulls_ge_2.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_lists@nulls_ge_2.snap @@ -15,18 +15,11 @@ TestResult { name: "column", data_type: List( Field { - name: "item", data_type: Int64, nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, ), nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: ListArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_lists@nulls_gt_2.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_lists@nulls_gt_2.snap index df2efe6dd6a7..9dc056b90d36 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_lists@nulls_gt_2.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_lists@nulls_gt_2.snap @@ -15,18 +15,11 @@ TestResult { name: "column", data_type: List( Field { - name: "item", data_type: Int64, nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, ), nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: ListArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_lists@nulls_le_2.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_lists@nulls_le_2.snap index bf437fb5396b..4b25d4ecb41f 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_lists@nulls_le_2.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_lists@nulls_le_2.snap @@ -15,18 +15,11 @@ TestResult { name: "column", data_type: List( Field { - name: "item", data_type: Int64, nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, ), nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: ListArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_lists@nulls_lt_2.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_lists@nulls_lt_2.snap index 404b02a0fca5..b697966fb904 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_lists@nulls_lt_2.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_lists@nulls_lt_2.snap @@ -15,18 +15,11 @@ TestResult { name: "column", data_type: List( Field { - name: "item", data_type: Int64, nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, ), nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: ListArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_lists@nulls_ne_2.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_lists@nulls_ne_2.snap index 3f3a46bd379c..8745f6964ceb 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_lists@nulls_ne_2.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__int_lists@nulls_ne_2.snap @@ -15,18 +15,11 @@ TestResult { name: "column", data_type: List( Field { - name: "item", data_type: Int64, nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, ), nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: ListArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__non_nullable_boolean_equals@false.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__non_nullable_boolean_equals@false.snap index 9c7498c73947..3fb8aee23c7c 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__non_nullable_boolean_equals@false.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__non_nullable_boolean_equals@false.snap @@ -9,10 +9,6 @@ TestResult { field: Field { name: "column", data_type: Boolean, - nullable: false, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: BooleanArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__non_nullable_boolean_equals@nulls_false.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__non_nullable_boolean_equals@nulls_false.snap index f042fe7f6350..467110cf0e5a 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__non_nullable_boolean_equals@nulls_false.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__non_nullable_boolean_equals@nulls_false.snap @@ -10,9 +10,6 @@ TestResult { name: "column", data_type: Boolean, nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: BooleanArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__non_nullable_boolean_equals@nulls_true.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__non_nullable_boolean_equals@nulls_true.snap index bcd1f00248f5..dcc7e2ce9039 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__non_nullable_boolean_equals@nulls_true.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__non_nullable_boolean_equals@nulls_true.snap @@ -10,9 +10,6 @@ TestResult { name: "column", data_type: Boolean, nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: BooleanArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__non_nullable_boolean_equals@true.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__non_nullable_boolean_equals@true.snap index 23e539872997..48b8a66d91ee 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__non_nullable_boolean_equals@true.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__non_nullable_boolean_equals@true.snap @@ -9,10 +9,6 @@ TestResult { field: Field { name: "column", data_type: Boolean, - nullable: false, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: BooleanArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__nullable_boolean_equals@nulls_false.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__nullable_boolean_equals@nulls_false.snap index 7b9fe8ce994d..40e7e5a4ef6c 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__nullable_boolean_equals@nulls_false.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__nullable_boolean_equals@nulls_false.snap @@ -10,9 +10,6 @@ TestResult { name: "column", data_type: Boolean, nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: BooleanArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__nullable_boolean_equals@nulls_is_not_null.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__nullable_boolean_equals@nulls_is_not_null.snap index a9ca7cdea553..7d1c7cf62ab7 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__nullable_boolean_equals@nulls_is_not_null.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__nullable_boolean_equals@nulls_is_not_null.snap @@ -10,9 +10,6 @@ TestResult { name: "column", data_type: Boolean, nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: BooleanArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__nullable_boolean_equals@nulls_is_not_true.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__nullable_boolean_equals@nulls_is_not_true.snap index 438049536061..90c8f735233f 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__nullable_boolean_equals@nulls_is_not_true.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__nullable_boolean_equals@nulls_is_not_true.snap @@ -10,9 +10,6 @@ TestResult { name: "column", data_type: Boolean, nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: BooleanArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__nullable_boolean_equals@nulls_null.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__nullable_boolean_equals@nulls_null.snap index 643ab0a970c0..2f56490cde78 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__nullable_boolean_equals@nulls_null.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__nullable_boolean_equals@nulls_null.snap @@ -10,9 +10,6 @@ TestResult { name: "column", data_type: Boolean, nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: BooleanArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__nullable_boolean_equals@nulls_true.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__nullable_boolean_equals@nulls_true.snap index 7d9fd78c4a1a..382b77cd38ed 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__nullable_boolean_equals@nulls_true.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__nullable_boolean_equals@nulls_true.snap @@ -10,9 +10,6 @@ TestResult { name: "column", data_type: Boolean, nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: BooleanArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_contains@a.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_contains@a.snap index 3cffb78638a4..cbcd197d7bcc 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_contains@a.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_contains@a.snap @@ -12,10 +12,6 @@ TestResult { field: Field { name: "column", data_type: Utf8, - nullable: false, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: StringArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_contains@a_uppercase.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_contains@a_uppercase.snap index 1a3ed5cb97f8..16913c10bf9f 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_contains@a_uppercase.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_contains@a_uppercase.snap @@ -12,10 +12,6 @@ TestResult { field: Field { name: "column", data_type: Utf8, - nullable: false, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: StringArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_contains@ab.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_contains@ab.snap index 3cffb78638a4..cbcd197d7bcc 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_contains@ab.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_contains@ab.snap @@ -12,10 +12,6 @@ TestResult { field: Field { name: "column", data_type: Utf8, - nullable: false, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: StringArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_contains@does_not_contain_b.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_contains@does_not_contain_b.snap index effe80286147..49d00bb4d6c4 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_contains@does_not_contain_b.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_contains@does_not_contain_b.snap @@ -12,10 +12,6 @@ TestResult { field: Field { name: "column", data_type: Utf8, - nullable: false, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: StringArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_contains@empty.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_contains@empty.snap index 0034c0164700..db9b88d13c87 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_contains@empty.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_contains@empty.snap @@ -12,10 +12,6 @@ TestResult { field: Field { name: "column", data_type: Utf8, - nullable: false, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: StringArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_contains@ends_with_c.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_contains@ends_with_c.snap index d2bb7b7b5cdf..d3bdc6e40341 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_contains@ends_with_c.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_contains@ends_with_c.snap @@ -12,10 +12,6 @@ TestResult { field: Field { name: "column", data_type: Utf8, - nullable: false, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: StringArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_contains@nulls_a.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_contains@nulls_a.snap index 0ac88334288c..56aad9c37301 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_contains@nulls_a.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_contains@nulls_a.snap @@ -13,9 +13,6 @@ TestResult { name: "column", data_type: Utf8, nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: StringArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_contains@nulls_does_not_contain_b.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_contains@nulls_does_not_contain_b.snap index 6ab0d17e541a..5fb41496e4da 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_contains@nulls_does_not_contain_b.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_contains@nulls_does_not_contain_b.snap @@ -13,9 +13,6 @@ TestResult { name: "column", data_type: Utf8, nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: StringArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_contains@nulls_empty.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_contains@nulls_empty.snap index 6e8a14b8a762..f294c66c6554 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_contains@nulls_empty.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_contains@nulls_empty.snap @@ -13,9 +13,6 @@ TestResult { name: "column", data_type: Utf8, nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: StringArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_contains@nulls_ends_with_c.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_contains@nulls_ends_with_c.snap index 0dd6634f33e9..9401eeb2ed64 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_contains@nulls_ends_with_c.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_contains@nulls_ends_with_c.snap @@ -13,9 +13,6 @@ TestResult { name: "column", data_type: Utf8, nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: StringArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_contains@nulls_starts_with_b.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_contains@nulls_starts_with_b.snap index ed11847c3192..4eeac2aa59b1 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_contains@nulls_starts_with_b.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_contains@nulls_starts_with_b.snap @@ -13,9 +13,6 @@ TestResult { name: "column", data_type: Utf8, nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: StringArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_contains@starts_with_b.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_contains@starts_with_b.snap index e9d3b4ef426c..ac823294e80f 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_contains@starts_with_b.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_contains@starts_with_b.snap @@ -12,10 +12,6 @@ TestResult { field: Field { name: "column", data_type: Utf8, - nullable: false, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: StringArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_list@both_null_Contains_ab.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_list@both_null_Contains_ab.snap index 1fd1d5936c57..cb1669ae641e 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_list@both_null_Contains_ab.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_list@both_null_Contains_ab.snap @@ -16,15 +16,9 @@ TestResult { name: "column", data_type: Utf8, nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, ), nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: ListArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_list@both_null_DoesNotContain_ab.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_list@both_null_DoesNotContain_ab.snap index 7c1ec0cba6d6..dec09b08193b 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_list@both_null_DoesNotContain_ab.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_list@both_null_DoesNotContain_ab.snap @@ -16,15 +16,9 @@ TestResult { name: "column", data_type: Utf8, nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, ), nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: ListArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_list@both_null_EndsWith_ab.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_list@both_null_EndsWith_ab.snap index f5b1ad94bc58..432f1c53ded7 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_list@both_null_EndsWith_ab.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_list@both_null_EndsWith_ab.snap @@ -16,15 +16,9 @@ TestResult { name: "column", data_type: Utf8, nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, ), nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: ListArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_list@both_null_StartsWith_ab.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_list@both_null_StartsWith_ab.snap index ea2b7bac74e0..95b96e55986a 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_list@both_null_StartsWith_ab.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_list@both_null_StartsWith_ab.snap @@ -16,15 +16,9 @@ TestResult { name: "column", data_type: Utf8, nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, ), nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: ListArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_list@inner_null_Contains_ab.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_list@inner_null_Contains_ab.snap index 00184cc4fa05..5e67122f1814 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_list@inner_null_Contains_ab.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_list@inner_null_Contains_ab.snap @@ -16,15 +16,8 @@ TestResult { name: "column", data_type: Utf8, nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, ), - nullable: false, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: ListArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_list@inner_null_DoesNotContain_ab.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_list@inner_null_DoesNotContain_ab.snap index fcf766b259ba..363cc908ab6d 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_list@inner_null_DoesNotContain_ab.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_list@inner_null_DoesNotContain_ab.snap @@ -16,15 +16,8 @@ TestResult { name: "column", data_type: Utf8, nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, ), - nullable: false, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: ListArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_list@inner_null_EndsWith_ab.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_list@inner_null_EndsWith_ab.snap index 6787c48efbef..14674734c9df 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_list@inner_null_EndsWith_ab.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_list@inner_null_EndsWith_ab.snap @@ -16,15 +16,8 @@ TestResult { name: "column", data_type: Utf8, nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, ), - nullable: false, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: ListArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_list@inner_null_StartsWith_ab.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_list@inner_null_StartsWith_ab.snap index 5d73f1b4f67d..703f689f5ef5 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_list@inner_null_StartsWith_ab.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_list@inner_null_StartsWith_ab.snap @@ -16,15 +16,8 @@ TestResult { name: "column", data_type: Utf8, nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, ), - nullable: false, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: ListArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_list@no_null_Contains_ab.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_list@no_null_Contains_ab.snap index e8499547400e..8fa96cc0ba0e 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_list@no_null_Contains_ab.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_list@no_null_Contains_ab.snap @@ -15,16 +15,8 @@ TestResult { Field { name: "column", data_type: Utf8, - nullable: false, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, ), - nullable: false, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: ListArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_list@no_null_DoesNotContain_ab.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_list@no_null_DoesNotContain_ab.snap index 4ee43048b27f..d921194cbf82 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_list@no_null_DoesNotContain_ab.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_list@no_null_DoesNotContain_ab.snap @@ -15,16 +15,8 @@ TestResult { Field { name: "column", data_type: Utf8, - nullable: false, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, ), - nullable: false, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: ListArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_list@no_null_EndsWith_ab.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_list@no_null_EndsWith_ab.snap index b11b40068cbe..cc2273965d7a 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_list@no_null_EndsWith_ab.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_list@no_null_EndsWith_ab.snap @@ -15,16 +15,8 @@ TestResult { Field { name: "column", data_type: Utf8, - nullable: false, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, ), - nullable: false, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: ListArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_list@no_null_StartsWith_ab.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_list@no_null_StartsWith_ab.snap index 5d49af124edb..319d333a4ce6 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_list@no_null_StartsWith_ab.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_list@no_null_StartsWith_ab.snap @@ -15,16 +15,8 @@ TestResult { Field { name: "column", data_type: Utf8, - nullable: false, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, ), - nullable: false, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: ListArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_list@outer_null_Contains_ab.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_list@outer_null_Contains_ab.snap index e440ef5d2f22..3cfe723aeaab 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_list@outer_null_Contains_ab.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_list@outer_null_Contains_ab.snap @@ -15,16 +15,9 @@ TestResult { Field { name: "column", data_type: Utf8, - nullable: false, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, ), nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: ListArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_list@outer_null_DoesNotContain_ab.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_list@outer_null_DoesNotContain_ab.snap index 29af96a946c3..367a5f107de6 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_list@outer_null_DoesNotContain_ab.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_list@outer_null_DoesNotContain_ab.snap @@ -15,16 +15,9 @@ TestResult { Field { name: "column", data_type: Utf8, - nullable: false, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, ), nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: ListArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_list@outer_null_EndsWith_ab.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_list@outer_null_EndsWith_ab.snap index 883c9ff149af..2540a5d8d503 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_list@outer_null_EndsWith_ab.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_list@outer_null_EndsWith_ab.snap @@ -15,16 +15,9 @@ TestResult { Field { name: "column", data_type: Utf8, - nullable: false, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, ), nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: ListArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_list@outer_null_StartsWith_ab.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_list@outer_null_StartsWith_ab.snap index f42698efde11..f6e2a41d019b 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_list@outer_null_StartsWith_ab.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__string_list@outer_null_StartsWith_ab.snap @@ -15,16 +15,9 @@ TestResult { Field { name: "column", data_type: Utf8, - nullable: false, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, ), nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: ListArray [ diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@after_Microsecond.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@after_Microsecond.snap index 4236a359696d..63cb6116d8bd 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@after_Microsecond.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@after_Microsecond.snap @@ -12,12 +12,8 @@ TestResult { Microsecond, None, ), - nullable: false, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, - unfiltered: PrimitiveArray + unfiltered: PrimitiveArray [ 2025-09-23T11:47:00, 2025-09-23T10:47:00, @@ -27,7 +23,7 @@ TestResult { 2025-09-24T11:47:00, 2026-09-23T11:47:00, ], - filtered: PrimitiveArray + filtered: PrimitiveArray [ 2025-09-23T11:47:00, 2025-09-23T12:47:00, diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@after_Microsecond_nulls.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@after_Microsecond_nulls.snap index 6ff330d57840..5bf5bfd47d0c 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@after_Microsecond_nulls.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@after_Microsecond_nulls.snap @@ -13,11 +13,8 @@ TestResult { None, ), nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, - unfiltered: PrimitiveArray + unfiltered: PrimitiveArray [ 2025-09-23T11:47:00, 2025-09-23T10:47:00, @@ -29,7 +26,7 @@ TestResult { null, 2026-09-23T11:47:00, ], - filtered: PrimitiveArray + filtered: PrimitiveArray [ 2025-09-23T11:47:00, 2025-09-23T12:47:00, diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@after_Millisecond.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@after_Millisecond.snap index 8fe284abff52..a7463331df8f 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@after_Millisecond.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@after_Millisecond.snap @@ -12,12 +12,8 @@ TestResult { Millisecond, None, ), - nullable: false, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, - unfiltered: PrimitiveArray + unfiltered: PrimitiveArray [ 2025-09-23T11:47:00, 2025-09-23T10:47:00, @@ -27,7 +23,7 @@ TestResult { 2025-09-24T11:47:00, 2026-09-23T11:47:00, ], - filtered: PrimitiveArray + filtered: PrimitiveArray [ 2025-09-23T11:47:00, 2025-09-23T12:47:00, diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@after_Millisecond_nulls.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@after_Millisecond_nulls.snap index 4916f36d66b0..3b84493912d5 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@after_Millisecond_nulls.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@after_Millisecond_nulls.snap @@ -13,11 +13,8 @@ TestResult { None, ), nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, - unfiltered: PrimitiveArray + unfiltered: PrimitiveArray [ 2025-09-23T11:47:00, 2025-09-23T10:47:00, @@ -29,7 +26,7 @@ TestResult { null, 2026-09-23T11:47:00, ], - filtered: PrimitiveArray + filtered: PrimitiveArray [ 2025-09-23T11:47:00, 2025-09-23T12:47:00, diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@after_Nanosecond.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@after_Nanosecond.snap index a4e089b41979..7eb9784a72a1 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@after_Nanosecond.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@after_Nanosecond.snap @@ -12,12 +12,8 @@ TestResult { Nanosecond, None, ), - nullable: false, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, - unfiltered: PrimitiveArray + unfiltered: PrimitiveArray [ 2025-09-23T11:47:00, 2025-09-23T10:47:00, @@ -27,7 +23,7 @@ TestResult { 2025-09-24T11:47:00, 2026-09-23T11:47:00, ], - filtered: PrimitiveArray + filtered: PrimitiveArray [ 2025-09-23T11:47:00, 2025-09-23T12:47:00, diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@after_Nanosecond_nulls.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@after_Nanosecond_nulls.snap index fddfdcadb706..cca4dfef3bfd 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@after_Nanosecond_nulls.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@after_Nanosecond_nulls.snap @@ -13,11 +13,8 @@ TestResult { None, ), nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, - unfiltered: PrimitiveArray + unfiltered: PrimitiveArray [ 2025-09-23T11:47:00, 2025-09-23T10:47:00, @@ -29,7 +26,7 @@ TestResult { null, 2026-09-23T11:47:00, ], - filtered: PrimitiveArray + filtered: PrimitiveArray [ 2025-09-23T11:47:00, 2025-09-23T12:47:00, diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@after_Second.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@after_Second.snap index eaeb17f1a306..e48e524ca4f4 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@after_Second.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@after_Second.snap @@ -12,12 +12,8 @@ TestResult { Second, None, ), - nullable: false, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, - unfiltered: PrimitiveArray + unfiltered: PrimitiveArray [ 2025-09-23T11:47:00, 2025-09-23T10:47:00, @@ -27,7 +23,7 @@ TestResult { 2025-09-24T11:47:00, 2026-09-23T11:47:00, ], - filtered: PrimitiveArray + filtered: PrimitiveArray [ 2025-09-23T11:47:00, 2025-09-23T12:47:00, diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@after_Second_nulls.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@after_Second_nulls.snap index 9a53b02563b3..2e2cc9877423 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@after_Second_nulls.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@after_Second_nulls.snap @@ -13,11 +13,8 @@ TestResult { None, ), nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, - unfiltered: PrimitiveArray + unfiltered: PrimitiveArray [ 2025-09-23T11:47:00, 2025-09-23T10:47:00, @@ -29,7 +26,7 @@ TestResult { null, 2026-09-23T11:47:00, ], - filtered: PrimitiveArray + filtered: PrimitiveArray [ 2025-09-23T11:47:00, 2025-09-23T12:47:00, diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@after_strict_Microsecond.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@after_strict_Microsecond.snap index 89f64ba21486..5ef694a681f6 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@after_strict_Microsecond.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@after_strict_Microsecond.snap @@ -12,12 +12,8 @@ TestResult { Microsecond, None, ), - nullable: false, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, - unfiltered: PrimitiveArray + unfiltered: PrimitiveArray [ 2025-09-23T11:47:00, 2025-09-23T10:47:00, @@ -27,7 +23,7 @@ TestResult { 2025-09-24T11:47:00, 2026-09-23T11:47:00, ], - filtered: PrimitiveArray + filtered: PrimitiveArray [ 2025-09-23T12:47:00, 2025-09-24T11:47:00, diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@after_strict_Microsecond_nulls.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@after_strict_Microsecond_nulls.snap index 064d774d9642..df09b3354e0c 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@after_strict_Microsecond_nulls.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@after_strict_Microsecond_nulls.snap @@ -13,11 +13,8 @@ TestResult { None, ), nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, - unfiltered: PrimitiveArray + unfiltered: PrimitiveArray [ 2025-09-23T11:47:00, 2025-09-23T10:47:00, @@ -29,7 +26,7 @@ TestResult { null, 2026-09-23T11:47:00, ], - filtered: PrimitiveArray + filtered: PrimitiveArray [ 2025-09-23T12:47:00, 2025-09-24T11:47:00, diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@after_strict_Millisecond.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@after_strict_Millisecond.snap index ab1d545cc842..fb2ce2c0294f 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@after_strict_Millisecond.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@after_strict_Millisecond.snap @@ -12,12 +12,8 @@ TestResult { Millisecond, None, ), - nullable: false, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, - unfiltered: PrimitiveArray + unfiltered: PrimitiveArray [ 2025-09-23T11:47:00, 2025-09-23T10:47:00, @@ -27,7 +23,7 @@ TestResult { 2025-09-24T11:47:00, 2026-09-23T11:47:00, ], - filtered: PrimitiveArray + filtered: PrimitiveArray [ 2025-09-23T12:47:00, 2025-09-24T11:47:00, diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@after_strict_Millisecond_nulls.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@after_strict_Millisecond_nulls.snap index d1fc62791b3b..7c5ed84e7964 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@after_strict_Millisecond_nulls.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@after_strict_Millisecond_nulls.snap @@ -13,11 +13,8 @@ TestResult { None, ), nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, - unfiltered: PrimitiveArray + unfiltered: PrimitiveArray [ 2025-09-23T11:47:00, 2025-09-23T10:47:00, @@ -29,7 +26,7 @@ TestResult { null, 2026-09-23T11:47:00, ], - filtered: PrimitiveArray + filtered: PrimitiveArray [ 2025-09-23T12:47:00, 2025-09-24T11:47:00, diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@after_strict_Nanosecond.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@after_strict_Nanosecond.snap index 6693de3bf2ab..92d47e603d25 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@after_strict_Nanosecond.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@after_strict_Nanosecond.snap @@ -12,12 +12,8 @@ TestResult { Nanosecond, None, ), - nullable: false, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, - unfiltered: PrimitiveArray + unfiltered: PrimitiveArray [ 2025-09-23T11:47:00, 2025-09-23T10:47:00, @@ -27,7 +23,7 @@ TestResult { 2025-09-24T11:47:00, 2026-09-23T11:47:00, ], - filtered: PrimitiveArray + filtered: PrimitiveArray [ 2025-09-23T12:47:00, 2025-09-24T11:47:00, diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@after_strict_Nanosecond_nulls.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@after_strict_Nanosecond_nulls.snap index 0c6a4fa04d45..87eeeaa9f5c1 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@after_strict_Nanosecond_nulls.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@after_strict_Nanosecond_nulls.snap @@ -13,11 +13,8 @@ TestResult { None, ), nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, - unfiltered: PrimitiveArray + unfiltered: PrimitiveArray [ 2025-09-23T11:47:00, 2025-09-23T10:47:00, @@ -29,7 +26,7 @@ TestResult { null, 2026-09-23T11:47:00, ], - filtered: PrimitiveArray + filtered: PrimitiveArray [ 2025-09-23T12:47:00, 2025-09-24T11:47:00, diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@after_strict_Second.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@after_strict_Second.snap index 87fefc031ed8..b3d6734671a3 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@after_strict_Second.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@after_strict_Second.snap @@ -12,12 +12,8 @@ TestResult { Second, None, ), - nullable: false, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, - unfiltered: PrimitiveArray + unfiltered: PrimitiveArray [ 2025-09-23T11:47:00, 2025-09-23T10:47:00, @@ -27,7 +23,7 @@ TestResult { 2025-09-24T11:47:00, 2026-09-23T11:47:00, ], - filtered: PrimitiveArray + filtered: PrimitiveArray [ 2025-09-23T12:47:00, 2025-09-24T11:47:00, diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@after_strict_Second_nulls.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@after_strict_Second_nulls.snap index d74dceb7bcab..5f65f7d3fb54 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@after_strict_Second_nulls.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@after_strict_Second_nulls.snap @@ -13,11 +13,8 @@ TestResult { None, ), nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, - unfiltered: PrimitiveArray + unfiltered: PrimitiveArray [ 2025-09-23T11:47:00, 2025-09-23T10:47:00, @@ -29,7 +26,7 @@ TestResult { null, 2026-09-23T11:47:00, ], - filtered: PrimitiveArray + filtered: PrimitiveArray [ 2025-09-23T12:47:00, 2025-09-24T11:47:00, diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@between_Microsecond.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@between_Microsecond.snap index 8ae9341f9024..3fcf0df9c972 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@between_Microsecond.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@between_Microsecond.snap @@ -12,12 +12,8 @@ TestResult { Microsecond, None, ), - nullable: false, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, - unfiltered: PrimitiveArray + unfiltered: PrimitiveArray [ 2025-09-23T11:47:00, 2025-09-23T10:47:00, @@ -27,7 +23,7 @@ TestResult { 2025-09-24T11:47:00, 2026-09-23T11:47:00, ], - filtered: PrimitiveArray + filtered: PrimitiveArray [ 2025-09-22T11:47:00, ], diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@between_Microsecond_nulls.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@between_Microsecond_nulls.snap index 486f0915e960..fc1bc3cd89a0 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@between_Microsecond_nulls.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@between_Microsecond_nulls.snap @@ -13,11 +13,8 @@ TestResult { None, ), nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, - unfiltered: PrimitiveArray + unfiltered: PrimitiveArray [ 2025-09-23T11:47:00, 2025-09-23T10:47:00, @@ -29,7 +26,7 @@ TestResult { null, 2026-09-23T11:47:00, ], - filtered: PrimitiveArray + filtered: PrimitiveArray [ 2025-09-22T11:47:00, ], diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@between_Millisecond.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@between_Millisecond.snap index 1f7eae7a9534..bd011ffe5876 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@between_Millisecond.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@between_Millisecond.snap @@ -12,12 +12,8 @@ TestResult { Millisecond, None, ), - nullable: false, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, - unfiltered: PrimitiveArray + unfiltered: PrimitiveArray [ 2025-09-23T11:47:00, 2025-09-23T10:47:00, @@ -27,7 +23,7 @@ TestResult { 2025-09-24T11:47:00, 2026-09-23T11:47:00, ], - filtered: PrimitiveArray + filtered: PrimitiveArray [ 2025-09-22T11:47:00, ], diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@between_Millisecond_nulls.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@between_Millisecond_nulls.snap index 1ad0be993b14..004b041eb5cc 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@between_Millisecond_nulls.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@between_Millisecond_nulls.snap @@ -13,11 +13,8 @@ TestResult { None, ), nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, - unfiltered: PrimitiveArray + unfiltered: PrimitiveArray [ 2025-09-23T11:47:00, 2025-09-23T10:47:00, @@ -29,7 +26,7 @@ TestResult { null, 2026-09-23T11:47:00, ], - filtered: PrimitiveArray + filtered: PrimitiveArray [ 2025-09-22T11:47:00, ], diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@between_Nanosecond.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@between_Nanosecond.snap index ad414ddca617..3801dc91c74d 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@between_Nanosecond.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@between_Nanosecond.snap @@ -12,12 +12,8 @@ TestResult { Nanosecond, None, ), - nullable: false, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, - unfiltered: PrimitiveArray + unfiltered: PrimitiveArray [ 2025-09-23T11:47:00, 2025-09-23T10:47:00, @@ -27,7 +23,7 @@ TestResult { 2025-09-24T11:47:00, 2026-09-23T11:47:00, ], - filtered: PrimitiveArray + filtered: PrimitiveArray [ 2025-09-22T11:47:00, ], diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@between_Nanosecond_nulls.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@between_Nanosecond_nulls.snap index 8a0fd575f945..9a6ae8b13b9c 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@between_Nanosecond_nulls.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@between_Nanosecond_nulls.snap @@ -13,11 +13,8 @@ TestResult { None, ), nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, - unfiltered: PrimitiveArray + unfiltered: PrimitiveArray [ 2025-09-23T11:47:00, 2025-09-23T10:47:00, @@ -29,7 +26,7 @@ TestResult { null, 2026-09-23T11:47:00, ], - filtered: PrimitiveArray + filtered: PrimitiveArray [ 2025-09-22T11:47:00, ], diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@between_Second.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@between_Second.snap index 812fed17fcfa..0c0b5bc175b2 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@between_Second.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@between_Second.snap @@ -12,12 +12,8 @@ TestResult { Second, None, ), - nullable: false, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, - unfiltered: PrimitiveArray + unfiltered: PrimitiveArray [ 2025-09-23T11:47:00, 2025-09-23T10:47:00, @@ -27,7 +23,7 @@ TestResult { 2025-09-24T11:47:00, 2026-09-23T11:47:00, ], - filtered: PrimitiveArray + filtered: PrimitiveArray [ 2025-09-22T11:47:00, ], diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@between_Second_nulls.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@between_Second_nulls.snap index bdacfe72f729..d31ebe579803 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@between_Second_nulls.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@between_Second_nulls.snap @@ -13,11 +13,8 @@ TestResult { None, ), nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, - unfiltered: PrimitiveArray + unfiltered: PrimitiveArray [ 2025-09-23T11:47:00, 2025-09-23T10:47:00, @@ -29,7 +26,7 @@ TestResult { null, 2026-09-23T11:47:00, ], - filtered: PrimitiveArray + filtered: PrimitiveArray [ 2025-09-22T11:47:00, ], diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@not_after_Microsecond.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@not_after_Microsecond.snap index 5c5c3e326d9d..12dcad949c45 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@not_after_Microsecond.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@not_after_Microsecond.snap @@ -12,12 +12,8 @@ TestResult { Microsecond, None, ), - nullable: false, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, - unfiltered: PrimitiveArray + unfiltered: PrimitiveArray [ 2025-09-23T11:47:00, 2025-09-23T10:47:00, @@ -27,7 +23,7 @@ TestResult { 2025-09-24T11:47:00, 2026-09-23T11:47:00, ], - filtered: PrimitiveArray + filtered: PrimitiveArray [ 2025-09-23T10:47:00, 2025-09-22T11:47:00, diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@not_after_Microsecond_nulls.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@not_after_Microsecond_nulls.snap index c25c289a1e8b..3f03e5f19734 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@not_after_Microsecond_nulls.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@not_after_Microsecond_nulls.snap @@ -13,11 +13,8 @@ TestResult { None, ), nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, - unfiltered: PrimitiveArray + unfiltered: PrimitiveArray [ 2025-09-23T11:47:00, 2025-09-23T10:47:00, @@ -29,7 +26,7 @@ TestResult { null, 2026-09-23T11:47:00, ], - filtered: PrimitiveArray + filtered: PrimitiveArray [ 2025-09-23T10:47:00, 2025-09-22T11:47:00, diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@not_after_Millisecond.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@not_after_Millisecond.snap index 37188add6f63..4840c1c4abf0 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@not_after_Millisecond.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@not_after_Millisecond.snap @@ -12,12 +12,8 @@ TestResult { Millisecond, None, ), - nullable: false, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, - unfiltered: PrimitiveArray + unfiltered: PrimitiveArray [ 2025-09-23T11:47:00, 2025-09-23T10:47:00, @@ -27,7 +23,7 @@ TestResult { 2025-09-24T11:47:00, 2026-09-23T11:47:00, ], - filtered: PrimitiveArray + filtered: PrimitiveArray [ 2025-09-23T10:47:00, 2025-09-22T11:47:00, diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@not_after_Millisecond_nulls.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@not_after_Millisecond_nulls.snap index 72d2a63e5564..9b4a887ccd81 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@not_after_Millisecond_nulls.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@not_after_Millisecond_nulls.snap @@ -13,11 +13,8 @@ TestResult { None, ), nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, - unfiltered: PrimitiveArray + unfiltered: PrimitiveArray [ 2025-09-23T11:47:00, 2025-09-23T10:47:00, @@ -29,7 +26,7 @@ TestResult { null, 2026-09-23T11:47:00, ], - filtered: PrimitiveArray + filtered: PrimitiveArray [ 2025-09-23T10:47:00, 2025-09-22T11:47:00, diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@not_after_Nanosecond.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@not_after_Nanosecond.snap index 078da23427d7..84323d19cd20 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@not_after_Nanosecond.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@not_after_Nanosecond.snap @@ -12,12 +12,8 @@ TestResult { Nanosecond, None, ), - nullable: false, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, - unfiltered: PrimitiveArray + unfiltered: PrimitiveArray [ 2025-09-23T11:47:00, 2025-09-23T10:47:00, @@ -27,7 +23,7 @@ TestResult { 2025-09-24T11:47:00, 2026-09-23T11:47:00, ], - filtered: PrimitiveArray + filtered: PrimitiveArray [ 2025-09-23T10:47:00, 2025-09-22T11:47:00, diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@not_after_Nanosecond_nulls.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@not_after_Nanosecond_nulls.snap index 7bb9087d835a..dfce84a1b121 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@not_after_Nanosecond_nulls.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@not_after_Nanosecond_nulls.snap @@ -13,11 +13,8 @@ TestResult { None, ), nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, - unfiltered: PrimitiveArray + unfiltered: PrimitiveArray [ 2025-09-23T11:47:00, 2025-09-23T10:47:00, @@ -29,7 +26,7 @@ TestResult { null, 2026-09-23T11:47:00, ], - filtered: PrimitiveArray + filtered: PrimitiveArray [ 2025-09-23T10:47:00, 2025-09-22T11:47:00, diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@not_after_Second.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@not_after_Second.snap index d43143f1f684..2a3f3df5e727 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@not_after_Second.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@not_after_Second.snap @@ -12,12 +12,8 @@ TestResult { Second, None, ), - nullable: false, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, - unfiltered: PrimitiveArray + unfiltered: PrimitiveArray [ 2025-09-23T11:47:00, 2025-09-23T10:47:00, @@ -27,7 +23,7 @@ TestResult { 2025-09-24T11:47:00, 2026-09-23T11:47:00, ], - filtered: PrimitiveArray + filtered: PrimitiveArray [ 2025-09-23T10:47:00, 2025-09-22T11:47:00, diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@not_after_Second_nulls.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@not_after_Second_nulls.snap index 1151317288d4..8171810431cc 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@not_after_Second_nulls.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps@not_after_Second_nulls.snap @@ -13,11 +13,8 @@ TestResult { None, ), nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, - unfiltered: PrimitiveArray + unfiltered: PrimitiveArray [ 2025-09-23T11:47:00, 2025-09-23T10:47:00, @@ -29,7 +26,7 @@ TestResult { null, 2026-09-23T11:47:00, ], - filtered: PrimitiveArray + filtered: PrimitiveArray [ 2025-09-23T10:47:00, 2025-09-22T11:47:00, diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps_list@after_both_null.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps_list@after_both_null.snap index 5f657ff4ee4b..095428801bd0 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps_list@after_both_null.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps_list@after_both_null.snap @@ -10,70 +10,63 @@ TestResult { name: "column", data_type: List( Field { - name: "item", data_type: Timestamp( Nanosecond, None, ), nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, ), nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: ListArray [ - PrimitiveArray + PrimitiveArray [ ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T10:47:00, 2025-09-22T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, 2025-09-16T11:47:00, 2024-09-23T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T12:47:00, 2025-09-24T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, 2025-09-30T11:47:00, 2026-09-23T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, 2024-09-23T11:47:00, 2026-09-23T11:47:00, ], null, - PrimitiveArray + PrimitiveArray [ null, ], - PrimitiveArray + PrimitiveArray [ null, 2025-09-23T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, null, @@ -81,39 +74,39 @@ TestResult { ], filtered: ListArray [ - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, 2025-09-16T11:47:00, 2024-09-23T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T12:47:00, 2025-09-24T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, 2025-09-30T11:47:00, 2026-09-23T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, 2024-09-23T11:47:00, 2026-09-23T11:47:00, ], - PrimitiveArray + PrimitiveArray [ null, 2025-09-23T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, null, diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps_list@after_inner_null.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps_list@after_inner_null.snap index 05988e12b343..bd0f924683f0 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps_list@after_inner_null.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps_list@after_inner_null.snap @@ -10,69 +10,61 @@ TestResult { name: "column", data_type: List( Field { - name: "item", data_type: Timestamp( Nanosecond, None, ), nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, ), - nullable: false, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: ListArray [ - PrimitiveArray + PrimitiveArray [ ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T10:47:00, 2025-09-22T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, 2025-09-16T11:47:00, 2024-09-23T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T12:47:00, 2025-09-24T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, 2025-09-30T11:47:00, 2026-09-23T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, 2024-09-23T11:47:00, 2026-09-23T11:47:00, ], - PrimitiveArray + PrimitiveArray [ null, ], - PrimitiveArray + PrimitiveArray [ null, 2025-09-23T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, null, @@ -80,39 +72,39 @@ TestResult { ], filtered: ListArray [ - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, 2025-09-16T11:47:00, 2024-09-23T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T12:47:00, 2025-09-24T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, 2025-09-30T11:47:00, 2026-09-23T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, 2024-09-23T11:47:00, 2026-09-23T11:47:00, ], - PrimitiveArray + PrimitiveArray [ null, 2025-09-23T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, null, diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps_list@after_no_null.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps_list@after_no_null.snap index 63bebc8aa2ae..526e3ffb2c81 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps_list@after_no_null.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps_list@after_no_null.snap @@ -10,54 +10,46 @@ TestResult { name: "column", data_type: List( Field { - name: "item", data_type: Timestamp( Nanosecond, None, ), nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, ), - nullable: false, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: ListArray [ - PrimitiveArray + PrimitiveArray [ ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T10:47:00, 2025-09-22T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, 2025-09-16T11:47:00, 2024-09-23T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T12:47:00, 2025-09-24T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, 2025-09-30T11:47:00, 2026-09-23T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, 2024-09-23T11:47:00, @@ -66,28 +58,28 @@ TestResult { ], filtered: ListArray [ - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, 2025-09-16T11:47:00, 2024-09-23T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T12:47:00, 2025-09-24T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, 2025-09-30T11:47:00, 2026-09-23T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, 2024-09-23T11:47:00, diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps_list@after_outer_null.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps_list@after_outer_null.snap index 68086dbe9553..7b4bd4e61a36 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps_list@after_outer_null.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps_list@after_outer_null.snap @@ -10,54 +10,47 @@ TestResult { name: "column", data_type: List( Field { - name: "item", data_type: Timestamp( Nanosecond, None, ), nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, ), nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: ListArray [ - PrimitiveArray + PrimitiveArray [ ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T10:47:00, 2025-09-22T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, 2025-09-16T11:47:00, 2024-09-23T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T12:47:00, 2025-09-24T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, 2025-09-30T11:47:00, 2026-09-23T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, 2024-09-23T11:47:00, @@ -67,28 +60,28 @@ TestResult { ], filtered: ListArray [ - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, 2025-09-16T11:47:00, 2024-09-23T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T12:47:00, 2025-09-24T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, 2025-09-30T11:47:00, 2026-09-23T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, 2024-09-23T11:47:00, diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps_list@after_strict_both_null.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps_list@after_strict_both_null.snap index 7db14ab2c0da..55fad25cd376 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps_list@after_strict_both_null.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps_list@after_strict_both_null.snap @@ -10,70 +10,63 @@ TestResult { name: "column", data_type: List( Field { - name: "item", data_type: Timestamp( Nanosecond, None, ), nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, ), nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: ListArray [ - PrimitiveArray + PrimitiveArray [ ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T10:47:00, 2025-09-22T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, 2025-09-16T11:47:00, 2024-09-23T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T12:47:00, 2025-09-24T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, 2025-09-30T11:47:00, 2026-09-23T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, 2024-09-23T11:47:00, 2026-09-23T11:47:00, ], null, - PrimitiveArray + PrimitiveArray [ null, ], - PrimitiveArray + PrimitiveArray [ null, 2025-09-23T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, null, @@ -81,18 +74,18 @@ TestResult { ], filtered: ListArray [ - PrimitiveArray + PrimitiveArray [ 2025-09-23T12:47:00, 2025-09-24T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, 2025-09-30T11:47:00, 2026-09-23T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, 2024-09-23T11:47:00, diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps_list@after_strict_inner_null.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps_list@after_strict_inner_null.snap index f36947f9cf7f..eb03ad4ad5ce 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps_list@after_strict_inner_null.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps_list@after_strict_inner_null.snap @@ -10,69 +10,61 @@ TestResult { name: "column", data_type: List( Field { - name: "item", data_type: Timestamp( Nanosecond, None, ), nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, ), - nullable: false, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: ListArray [ - PrimitiveArray + PrimitiveArray [ ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T10:47:00, 2025-09-22T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, 2025-09-16T11:47:00, 2024-09-23T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T12:47:00, 2025-09-24T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, 2025-09-30T11:47:00, 2026-09-23T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, 2024-09-23T11:47:00, 2026-09-23T11:47:00, ], - PrimitiveArray + PrimitiveArray [ null, ], - PrimitiveArray + PrimitiveArray [ null, 2025-09-23T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, null, @@ -80,18 +72,18 @@ TestResult { ], filtered: ListArray [ - PrimitiveArray + PrimitiveArray [ 2025-09-23T12:47:00, 2025-09-24T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, 2025-09-30T11:47:00, 2026-09-23T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, 2024-09-23T11:47:00, diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps_list@after_strict_no_null.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps_list@after_strict_no_null.snap index 75aa3d68f75f..2de9ce58ec52 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps_list@after_strict_no_null.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps_list@after_strict_no_null.snap @@ -10,54 +10,46 @@ TestResult { name: "column", data_type: List( Field { - name: "item", data_type: Timestamp( Nanosecond, None, ), nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, ), - nullable: false, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: ListArray [ - PrimitiveArray + PrimitiveArray [ ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T10:47:00, 2025-09-22T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, 2025-09-16T11:47:00, 2024-09-23T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T12:47:00, 2025-09-24T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, 2025-09-30T11:47:00, 2026-09-23T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, 2024-09-23T11:47:00, @@ -66,18 +58,18 @@ TestResult { ], filtered: ListArray [ - PrimitiveArray + PrimitiveArray [ 2025-09-23T12:47:00, 2025-09-24T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, 2025-09-30T11:47:00, 2026-09-23T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, 2024-09-23T11:47:00, diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps_list@after_strict_outer_null.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps_list@after_strict_outer_null.snap index 118502d5a16f..576104506383 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps_list@after_strict_outer_null.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps_list@after_strict_outer_null.snap @@ -10,54 +10,47 @@ TestResult { name: "column", data_type: List( Field { - name: "item", data_type: Timestamp( Nanosecond, None, ), nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, ), nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: ListArray [ - PrimitiveArray + PrimitiveArray [ ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T10:47:00, 2025-09-22T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, 2025-09-16T11:47:00, 2024-09-23T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T12:47:00, 2025-09-24T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, 2025-09-30T11:47:00, 2026-09-23T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, 2024-09-23T11:47:00, @@ -67,18 +60,18 @@ TestResult { ], filtered: ListArray [ - PrimitiveArray + PrimitiveArray [ 2025-09-23T12:47:00, 2025-09-24T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, 2025-09-30T11:47:00, 2026-09-23T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, 2024-09-23T11:47:00, diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps_list@between_both_null.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps_list@between_both_null.snap index a1c102f6f8e2..456c64d89074 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps_list@between_both_null.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps_list@between_both_null.snap @@ -10,70 +10,63 @@ TestResult { name: "column", data_type: List( Field { - name: "item", data_type: Timestamp( Nanosecond, None, ), nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, ), nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: ListArray [ - PrimitiveArray + PrimitiveArray [ ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T10:47:00, 2025-09-22T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, 2025-09-16T11:47:00, 2024-09-23T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T12:47:00, 2025-09-24T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, 2025-09-30T11:47:00, 2026-09-23T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, 2024-09-23T11:47:00, 2026-09-23T11:47:00, ], null, - PrimitiveArray + PrimitiveArray [ null, ], - PrimitiveArray + PrimitiveArray [ null, 2025-09-23T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, null, @@ -81,12 +74,12 @@ TestResult { ], filtered: ListArray [ - PrimitiveArray + PrimitiveArray [ 2025-09-23T10:47:00, 2025-09-22T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, 2025-09-16T11:47:00, diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps_list@between_inner_null.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps_list@between_inner_null.snap index 7be624d25c61..bf90328a1fc7 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps_list@between_inner_null.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps_list@between_inner_null.snap @@ -10,69 +10,61 @@ TestResult { name: "column", data_type: List( Field { - name: "item", data_type: Timestamp( Nanosecond, None, ), nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, ), - nullable: false, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: ListArray [ - PrimitiveArray + PrimitiveArray [ ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T10:47:00, 2025-09-22T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, 2025-09-16T11:47:00, 2024-09-23T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T12:47:00, 2025-09-24T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, 2025-09-30T11:47:00, 2026-09-23T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, 2024-09-23T11:47:00, 2026-09-23T11:47:00, ], - PrimitiveArray + PrimitiveArray [ null, ], - PrimitiveArray + PrimitiveArray [ null, 2025-09-23T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, null, @@ -80,12 +72,12 @@ TestResult { ], filtered: ListArray [ - PrimitiveArray + PrimitiveArray [ 2025-09-23T10:47:00, 2025-09-22T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, 2025-09-16T11:47:00, diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps_list@between_no_null.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps_list@between_no_null.snap index 2fb1039e37c2..62d9e4290922 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps_list@between_no_null.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps_list@between_no_null.snap @@ -10,54 +10,46 @@ TestResult { name: "column", data_type: List( Field { - name: "item", data_type: Timestamp( Nanosecond, None, ), nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, ), - nullable: false, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: ListArray [ - PrimitiveArray + PrimitiveArray [ ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T10:47:00, 2025-09-22T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, 2025-09-16T11:47:00, 2024-09-23T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T12:47:00, 2025-09-24T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, 2025-09-30T11:47:00, 2026-09-23T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, 2024-09-23T11:47:00, @@ -66,12 +58,12 @@ TestResult { ], filtered: ListArray [ - PrimitiveArray + PrimitiveArray [ 2025-09-23T10:47:00, 2025-09-22T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, 2025-09-16T11:47:00, diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps_list@between_outer_null.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps_list@between_outer_null.snap index 9433016d5fa1..70ef43eb8021 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps_list@between_outer_null.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps_list@between_outer_null.snap @@ -10,54 +10,47 @@ TestResult { name: "column", data_type: List( Field { - name: "item", data_type: Timestamp( Nanosecond, None, ), nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, ), nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: ListArray [ - PrimitiveArray + PrimitiveArray [ ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T10:47:00, 2025-09-22T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, 2025-09-16T11:47:00, 2024-09-23T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T12:47:00, 2025-09-24T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, 2025-09-30T11:47:00, 2026-09-23T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, 2024-09-23T11:47:00, @@ -67,12 +60,12 @@ TestResult { ], filtered: ListArray [ - PrimitiveArray + PrimitiveArray [ 2025-09-23T10:47:00, 2025-09-22T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, 2025-09-16T11:47:00, diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps_list@not_after_both_null.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps_list@not_after_both_null.snap index ca20e176ecbc..13d080e5af8c 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps_list@not_after_both_null.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps_list@not_after_both_null.snap @@ -10,70 +10,63 @@ TestResult { name: "column", data_type: List( Field { - name: "item", data_type: Timestamp( Nanosecond, None, ), nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, ), nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: ListArray [ - PrimitiveArray + PrimitiveArray [ ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T10:47:00, 2025-09-22T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, 2025-09-16T11:47:00, 2024-09-23T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T12:47:00, 2025-09-24T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, 2025-09-30T11:47:00, 2026-09-23T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, 2024-09-23T11:47:00, 2026-09-23T11:47:00, ], null, - PrimitiveArray + PrimitiveArray [ null, ], - PrimitiveArray + PrimitiveArray [ null, 2025-09-23T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, null, @@ -81,16 +74,16 @@ TestResult { ], filtered: ListArray [ - PrimitiveArray + PrimitiveArray [ ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T10:47:00, 2025-09-22T11:47:00, ], null, - PrimitiveArray + PrimitiveArray [ null, ], diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps_list@not_after_inner_null.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps_list@not_after_inner_null.snap index 6b2d8d656326..5cc5edbb037a 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps_list@not_after_inner_null.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps_list@not_after_inner_null.snap @@ -10,69 +10,61 @@ TestResult { name: "column", data_type: List( Field { - name: "item", data_type: Timestamp( Nanosecond, None, ), nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, ), - nullable: false, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: ListArray [ - PrimitiveArray + PrimitiveArray [ ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T10:47:00, 2025-09-22T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, 2025-09-16T11:47:00, 2024-09-23T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T12:47:00, 2025-09-24T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, 2025-09-30T11:47:00, 2026-09-23T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, 2024-09-23T11:47:00, 2026-09-23T11:47:00, ], - PrimitiveArray + PrimitiveArray [ null, ], - PrimitiveArray + PrimitiveArray [ null, 2025-09-23T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, null, @@ -80,15 +72,15 @@ TestResult { ], filtered: ListArray [ - PrimitiveArray + PrimitiveArray [ ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T10:47:00, 2025-09-22T11:47:00, ], - PrimitiveArray + PrimitiveArray [ null, ], diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps_list@not_after_no_null.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps_list@not_after_no_null.snap index c118b740d176..ec0f9ae217b3 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps_list@not_after_no_null.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps_list@not_after_no_null.snap @@ -10,54 +10,46 @@ TestResult { name: "column", data_type: List( Field { - name: "item", data_type: Timestamp( Nanosecond, None, ), nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, ), - nullable: false, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: ListArray [ - PrimitiveArray + PrimitiveArray [ ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T10:47:00, 2025-09-22T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, 2025-09-16T11:47:00, 2024-09-23T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T12:47:00, 2025-09-24T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, 2025-09-30T11:47:00, 2026-09-23T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, 2024-09-23T11:47:00, @@ -66,10 +58,10 @@ TestResult { ], filtered: ListArray [ - PrimitiveArray + PrimitiveArray [ ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T10:47:00, 2025-09-22T11:47:00, diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps_list@not_after_outer_null.snap b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps_list@not_after_outer_null.snap index f8eb6dbced61..8d3ba6447758 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps_list@not_after_outer_null.snap +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_tests__timestamps_list@not_after_outer_null.snap @@ -10,54 +10,47 @@ TestResult { name: "column", data_type: List( Field { - name: "item", data_type: Timestamp( Nanosecond, None, ), nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, ), nullable: true, - dict_id: 0, - dict_is_ordered: false, - metadata: {}, }, unfiltered: ListArray [ - PrimitiveArray + PrimitiveArray [ ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T10:47:00, 2025-09-22T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, 2025-09-16T11:47:00, 2024-09-23T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T12:47:00, 2025-09-24T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, 2025-09-30T11:47:00, 2026-09-23T11:47:00, ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T11:47:00, 2024-09-23T11:47:00, @@ -67,10 +60,10 @@ TestResult { ], filtered: ListArray [ - PrimitiveArray + PrimitiveArray [ ], - PrimitiveArray + PrimitiveArray [ 2025-09-23T10:47:00, 2025-09-22T11:47:00, diff --git a/examples/python/server_tables/pyproject.toml b/examples/python/server_tables/pyproject.toml index ef8cc1b3865a..a130e194ad2b 100644 --- a/examples/python/server_tables/pyproject.toml +++ b/examples/python/server_tables/pyproject.toml @@ -1,8 +1,8 @@ [project] name = "server_tables" -version = "0.1.0" +version = "0.1.1" readme = "README.md" -dependencies = ["rerun-sdk", "datafusion==50.1"] +dependencies = ["rerun-sdk", "datafusion==51.0.0"] [project.scripts] server_tables = "server_tables:main" diff --git a/examples/rust/custom_callback/src/comms/viewer.rs b/examples/rust/custom_callback/src/comms/viewer.rs index 5d3815b84ebf..672947970313 100644 --- a/examples/rust/custom_callback/src/comms/viewer.rs +++ b/examples/rust/custom_callback/src/comms/viewer.rs @@ -96,13 +96,6 @@ impl ControlViewer { loop { match TcpStream::connect(self.address.clone()).await { Ok(socket) => { - if let Err(err) = socket.set_linger(Some(Duration::from_secs(2))) { - re_log::error!( - "Failed to set socket linger: {}", - re_error::format_ref(&err) - ); - } - re_log::info!("Connected to {}", self.address); let (read_half, write_half) = tokio::io::split(socket); diff --git a/rerun_py/pyproject.toml b/rerun_py/pyproject.toml index 9b0ad52439ef..b15a6f502720 100644 --- a/rerun_py/pyproject.toml +++ b/rerun_py/pyproject.toml @@ -45,18 +45,18 @@ tests = [ "syrupy==5.0.0", "tomli==2.0.1", "torch>=2.5", # Needs numpy 2 support - "datafusion==50.1.0", + "datafusion==51.0.0", ] notebook = ["rerun-notebook==0.30.0-alpha.1+dev"] # TODO(RR-3786): when pyarrow is fixed, we should remove the pandas dependency -datafusion = ["datafusion==50.1.0", "pandas>=2"] # deprecated, replaced by `dataplatform` (NOLINT) +datafusion = ["datafusion==51.0.0", "pandas>=2"] # deprecated, replaced by `dataplatform` (NOLINT) dataplatform = [ # NOLINT - "datafusion==50.1.0", + "datafusion==51.0.0", "pandas>=2", ] # pandas is needed so pyarrow properly converts nanoseconds (RR-3532) # Note: We avoid self-referential extras like "rerun-sdk[notebook]" because they cause # uv to fetch rerun-sdk from PyPI when the local package isn't built yet. -all = ["rerun-notebook==0.28.0-alpha.1+dev", "datafusion==50.1.0"] +all = ["rerun-notebook==0.28.0-alpha.1+dev", "datafusion==51.0.0"] [project.urls] documentation = "https://www.rerun.io/docs" diff --git a/rerun_py/src/catalog/registration_handle.rs b/rerun_py/src/catalog/registration_handle.rs index 1e3fcb9d2632..c6a07a941be3 100644 --- a/rerun_py/src/catalog/registration_handle.rs +++ b/rerun_py/src/catalog/registration_handle.rs @@ -296,7 +296,7 @@ impl PyRegistrationIterator { let rx = slf.rx.clone(); // Release the GIL while waiting for data - let batch_result = py.allow_threads(|| { + let batch_result = py.detach(|| { let mut rx_guard = rx.lock(); rx_guard.blocking_recv() }); diff --git a/rerun_py/src/python_bridge.rs b/rerun_py/src/python_bridge.rs index 44a890cceb6f..618a97318e1f 100644 --- a/rerun_py/src/python_bridge.rs +++ b/rerun_py/src/python_bridge.rs @@ -288,7 +288,7 @@ fn flush_and_cleanup_orphaned_recordings(py: Python<'_>) -> PyResult<()> { // a reference to the recording, which prevents it from being dropped. set_global_data_recording(py, None); - py.allow_threads(|| -> Result<(), SinkFlushError> { + py.detach(|| -> Result<(), SinkFlushError> { // Now flush all recordings to handle weird cases where the data in the queue // is actually holding onto the ref to the recording. for recording in all_recordings().iter().chain(orphaned_recordings().iter()) { @@ -314,7 +314,7 @@ fn flush_and_cleanup_orphaned_recordings(py: Python<'_>) -> PyResult<()> { /// properly when all references have been dropped. #[pyfunction] fn disconnect_orphaned_recordings(py: Python<'_>) -> PyResult<()> { - py.allow_threads(|| -> Result<(), SinkFlushError> { + py.detach(|| -> Result<(), SinkFlushError> { // Disconnect any recordings that have a refcount of 1. This means they are // only referenced by the `all_recordings` list and thus can't be referred to by the Python SDK. let mut orphaned = Vec::new(); @@ -650,7 +650,7 @@ fn new_blueprint( fn shutdown(py: Python<'_>) { re_log::debug!("Shutting down the Rerun SDK"); // Release the GIL in case any flushing behavior needs to cleanup a python object. - py.allow_threads(|| { + py.detach(|| { // NOTE: Do **NOT** try and drain() `all_recordings` here. // // Doing so would drop the last remaining reference to these recordings, and therefore @@ -767,7 +767,7 @@ fn set_global_data_recording( // // NOTE: This cannot happen anymore with the new `ALL_RECORDINGS` thingy, but better safe than // sorry. - py.allow_threads(|| { + py.detach(|| { let rec = RecordingStream::set_global( re_sdk::StoreKind::Recording, recording.map(|rec| rec.0.clone()), @@ -800,7 +800,7 @@ fn set_thread_local_data_recording( // // NOTE: This cannot happen anymore with the new `ALL_RECORDINGS` thingy, but better safe than // sorry. - py.allow_threads(|| { + py.detach(|| { let rec = RecordingStream::set_thread_local( re_sdk::StoreKind::Recording, recording.map(|rec| rec.0.clone()), @@ -844,7 +844,7 @@ fn set_global_blueprint_recording( // // NOTE: This cannot happen anymore with the new `ALL_RECORDINGS` thingy, but better safe than // sorry. - py.allow_threads(|| { + py.detach(|| { let rec = RecordingStream::set_global( re_sdk::StoreKind::Blueprint, recording.map(|rec| rec.0.clone()), @@ -877,7 +877,7 @@ fn set_thread_local_blueprint_recording( // // NOTE: This cannot happen anymore with the new `ALL_RECORDINGS` thingy, but better safe than // sorry. - py.allow_threads(|| { + py.detach(|| { let rec = RecordingStream::set_thread_local( re_sdk::StoreKind::Blueprint, recording.map(|rec| rec.0.clone()), @@ -1022,11 +1022,11 @@ impl PyFileSink { /// Stream data to multiple sinks. #[pyfunction] #[pyo3(signature = (sinks, default_blueprint=None, recording=None))] -fn set_sinks( - sinks: Vec, +fn set_sinks<'py>( + sinks: Vec>, default_blueprint: Option<&PyMemorySinkStorage>, recording: Option<&PyRecordingStream>, - py: Python<'_>, + py: Python<'py>, ) -> PyResult<()> { let Some(recording) = get_data_recording(recording) else { return Ok(()); @@ -1039,24 +1039,24 @@ fn set_sinks( let mut resolved_sinks: Vec> = Vec::new(); for sink in sinks { - if let Ok(sink) = sink.downcast_bound::(py) { + if let Ok(sink) = sink.downcast::() { let sink = sink.get(); let sink = re_sdk::sink::GrpcSink::new(sink.uri.clone()); resolved_sinks.push(Box::new(sink)); - } else if let Ok(sink) = sink.downcast_bound::(py) { + } else if let Ok(sink) = sink.downcast::() { let sink = sink.get(); let sink = re_sdk::sink::FileSink::new(sink.path.clone()) .map_err(|err| PyRuntimeError::new_err(err.to_string()))?; resolved_sinks.push(Box::new(sink)); } else { - let type_name = sink.bind(py).get_type().name()?; + let type_name = sink.get_type().name()?; return Err(PyRuntimeError::new_err(format!( "{type_name} is not a valid LogSink, must be one of: GrpcSink, FileSink" ))); } } - py.allow_threads(|| { + py.detach(|| { let sink = re_sdk::sink::MultiSink::new(resolved_sinks); if let Some(default_blueprint) = default_blueprint { @@ -1094,7 +1094,7 @@ fn connect_grpc( return Ok(()); } - py.allow_threads(|| { + py.detach(|| { let sink = re_sdk::sink::GrpcSink::new(uri); if let Some(default_blueprint) = default_blueprint { @@ -1124,7 +1124,7 @@ fn connect_grpc_blueprint( if let Some(blueprint_id) = blueprint_stream.store_info().map(|info| info.store_id) { // The call to save, needs to flush. // Release the GIL in case any flushing behavior needs to cleanup a python object. - py.allow_threads(|| -> PyResult<()> { + py.detach(|| -> PyResult<()> { // Flush all the pending blueprint messages before we include the Ready message blueprint_stream .flush_blocking() @@ -1171,7 +1171,7 @@ fn save( // The call to save may internally flush. // Release the GIL in case any flushing behavior needs to cleanup a python object. - py.allow_threads(|| { + py.detach(|| { // We create the sink manually so we can send the default blueprint // first before the rest of the current recording stream. let sink = re_sdk::sink::FileSink::new(path) @@ -1200,7 +1200,7 @@ fn save_blueprint( if let Some(blueprint_id) = (*blueprint_stream).store_info().map(|info| info.store_id) { // The call to save, needs to flush. // Release the GIL in case any flushing behavior needs to cleanup a python object. - py.allow_threads(|| -> PyResult<_> { + py.detach(|| -> PyResult<_> { // Flush all the pending blueprint messages before we include the Ready message blueprint_stream .flush_blocking() @@ -1242,7 +1242,7 @@ fn stdout( // The call to stdout may internally flush. // Release the GIL in case any flushing behavior needs to cleanup a python object. - py.allow_threads(|| { + py.detach(|| { let sink: Box = if std::io::stdout().is_terminal() { re_log::debug!("Ignored call to stdout() because stdout is a terminal"); Box::new(re_sdk::sink::BufferedSink::new()) @@ -1275,7 +1275,7 @@ fn memory_recording( get_data_recording(recording).map(|rec| { // The call to memory may internally flush. // Release the GIL in case any flushing behavior needs to cleanup a python object. - let inner = py.allow_threads(|| { + let inner = py.detach(|| { let storage = rec.memory(); flush_garbage_queue(); storage @@ -1287,13 +1287,13 @@ fn memory_recording( /// Set callback sink. #[pyfunction] #[pyo3(signature = (callback, recording = None))] -fn set_callback_sink(callback: PyObject, recording: Option<&PyRecordingStream>, py: Python<'_>) { +fn set_callback_sink(callback: Py, recording: Option<&PyRecordingStream>, py: Python<'_>) { let Some(rec) = get_data_recording(recording) else { return; }; let callback = move |msgs: &[LogMsg]| { - Python::with_gil(|py| { + Python::attach(|py| { let data = Encoder::encode(msgs.iter().map(Ok)).ok_or_log_error()?; let bytes = PyBytes::new(py, &data); callback.bind(py).call1((bytes,)).ok_or_log_error()?; @@ -1303,7 +1303,7 @@ fn set_callback_sink(callback: PyObject, recording: Option<&PyRecordingStream>, // The call to `set_sink` may internally flush. // Release the GIL in case any flushing behavior needs to cleanup a python object. - py.allow_threads(|| { + py.detach(|| { rec.set_sink(Box::new(CallbackSink::new(callback))); flush_garbage_queue(); }); @@ -1313,7 +1313,7 @@ fn set_callback_sink(callback: PyObject, recording: Option<&PyRecordingStream>, #[pyfunction] #[pyo3(signature = (callback, make_active, make_default, blueprint_stream))] fn set_callback_sink_blueprint( - callback: PyObject, + callback: Py, make_active: bool, make_default: bool, blueprint_stream: &PyRecordingStream, @@ -1324,7 +1324,7 @@ fn set_callback_sink_blueprint( }; let callback = move |msgs: &[LogMsg]| { - Python::with_gil(|py| { + Python::attach(|py| { let data = Encoder::encode(msgs.iter().map(Ok)).ok_or_log_error()?; let bytes = PyBytes::new(py, &data); callback.bind(py).call1((bytes,)).ok_or_log_error()?; @@ -1334,7 +1334,7 @@ fn set_callback_sink_blueprint( // The call to `set_sink` may internally flush. // Release the GIL in case any flushing behavior needs to cleanup a python object. - py.allow_threads(|| -> PyResult<()> { + py.detach(|| -> PyResult<()> { blueprint_stream .flush_blocking() .map_err(|err| PyRuntimeError::new_err(err.to_string()))?; @@ -1364,7 +1364,7 @@ fn binary_stream( // The call to memory may internally flush. // Release the GIL in case any flushing behavior needs to cleanup a python object. - let inner = py.allow_threads(|| { + let inner = py.detach(|| { let storage = recording.binary_stream(); flush_garbage_queue(); storage @@ -1391,7 +1391,7 @@ impl PyMemorySinkStorage { py: Python<'p>, ) -> PyResult> { // Release the GIL in case any flushing behavior needs to cleanup a python object. - py.allow_threads(|| { + py.detach(|| { let concat_bytes = MemorySinkStorage::concat_memory_sinks_as_bytes( [Some(&self.inner), concat.map(|c| &c.inner)] .iter() @@ -1413,7 +1413,7 @@ impl PyMemorySinkStorage { /// This will do a blocking flush before returning! fn num_msgs(&self, py: Python<'_>) -> usize { // Release the GIL in case any flushing behavior needs to cleanup a python object. - py.allow_threads(|| { + py.detach(|| { let num = self.inner.num_msgs(); flush_garbage_queue(); @@ -1427,7 +1427,7 @@ impl PyMemorySinkStorage { /// This will do a blocking flush before returning! fn drain_as_bytes<'p>(&self, py: Python<'p>) -> PyResult> { // Release the GIL in case any flushing behavior needs to cleanup a python object. - py.allow_threads(|| { + py.detach(|| { let bytes = self.inner.drain_as_bytes(); flush_garbage_queue(); @@ -1472,7 +1472,7 @@ impl PyBinarySinkStorage { flush_timeout_sec: f32, ) -> PyResult>> { // Release the GIL in case any flushing behavior needs to cleanup a python object. - py.allow_threads(|| -> PyResult<_> { + py.detach(|| -> PyResult<_> { if flush { let timeout = duration_from_sec(flush_timeout_sec as _)?; self.inner @@ -1504,7 +1504,7 @@ impl PyBinarySinkStorage { #[pyo3(signature = (*, timeout_sec = 1e38))] // Can't use infinity here because of python_check_signatures.py fn flush(&self, py: Python<'_>, timeout_sec: f32) -> PyResult<()> { // Release the GIL in case any flushing behavior needs to cleanup a python object. - py.allow_threads(|| -> PyResult<_> { + py.detach(|| -> PyResult<_> { let timeout = duration_from_sec(timeout_sec as _)?; self.inner .flush(timeout) @@ -1700,7 +1700,7 @@ fn disconnect(py: Python<'_>, recording: Option<&PyRecordingStream>) { return; }; // Release the GIL in case any flushing behavior needs to cleanup a python object. - py.allow_threads(|| { + py.detach(|| { recording.disconnect(); flush_garbage_queue(); }); @@ -1715,7 +1715,7 @@ fn flush(py: Python<'_>, timeout_sec: f32, recording: Option<&PyRecordingStream> }; // Release the GIL in case any flushing behavior needs to cleanup a python object. - py.allow_threads(|| -> PyResult<()> { + py.detach(|| -> PyResult<()> { if timeout_sec == 0.0 { recording .flush_async() @@ -1915,7 +1915,7 @@ fn log_arrow_msg( let row = crate::arrow::build_row_from_components(&components, &TimePoint::default())?; - py.allow_threads(|| { + py.detach(|| { // The call to `allow_threads` releases the GIL. // It is important that we do so here, // because the destructor for the arrow data will acquire the GIL @@ -1964,7 +1964,7 @@ fn send_arrow_chunk( // a deadlock. let chunk = crate::arrow::build_chunk_from_components(entity_path, &timelines, &components)?; - py.allow_threads(|| { + py.detach(|| { // The call to `allow_threads` releases the GIL. // It is important that we do so here, // because the destructor for the arrow data will acquire the GIL @@ -2050,7 +2050,7 @@ fn log_file( .map_err(|err| PyRuntimeError::new_err(err.to_string()))?; } - py.allow_threads(flush_garbage_queue); + py.detach(flush_garbage_queue); Ok(()) } diff --git a/rerun_py/src/recording/rrd.rs b/rerun_py/src/recording/rrd.rs index fc61b2bd2cc8..7ee2a765f117 100644 --- a/rerun_py/src/recording/rrd.rs +++ b/rerun_py/src/recording/rrd.rs @@ -3,7 +3,7 @@ use std::collections::BTreeMap; use pyo3::exceptions::{PyRuntimeError, PyValueError}; use pyo3::prelude::PyAnyMethods as _; use pyo3::types::PyModule; -use pyo3::{PyObject, PyResult, Python, pyclass, pyfunction, pymethods}; +use pyo3::{Py, PyAny, PyResult, Python, pyclass, pyfunction, pymethods}; use re_chunk_store::{ChunkStore, ChunkStoreConfig, ChunkStoreHandle}; use re_log_types::StoreId; @@ -59,7 +59,7 @@ pub struct PyRecording { #[pymethods] // NOLINT: ignore[py-mthd-str] impl PyRecording { /// The schema describing all the columns available in the recording. - fn schema(&self, py: Python<'_>) -> PyResult { + fn schema(&self, py: Python<'_>) -> PyResult> { let schema_internal = PySchemaInternal { columns: self.store.read().schema().into(), metadata: Default::default(), diff --git a/rerun_py/src/urdf.rs b/rerun_py/src/urdf.rs index 19382a24092d..70a8b9c40ab8 100644 --- a/rerun_py/src/urdf.rs +++ b/rerun_py/src/urdf.rs @@ -168,7 +168,12 @@ impl PyUrdfJoint { /// If `clamp` is true, values outside joint limits will be clamped and a warning is generated. /// If `clamp` is false (default), values outside limits are used as-is without warnings. #[pyo3(signature = (value, clamp = false))] - pub fn compute_transform(&self, py: Python<'_>, value: f64, clamp: bool) -> PyResult { + pub fn compute_transform( + &self, + py: Python<'_>, + value: f64, + clamp: bool, + ) -> PyResult> { match urdf_joint_transform::internal::compute_joint_transform(&self.0, value, clamp) { Ok(result) => { let dict = pyo3::types::PyDict::new(py); @@ -216,7 +221,7 @@ impl PyUrdfJoint { py: Python<'_>, values: Vec, clamp: bool, - ) -> PyResult { + ) -> PyResult> { let mut translations = Vec::with_capacity(values.len()); let mut quaternions = Vec::with_capacity(values.len()); let mut warnings = Vec::new(); diff --git a/rerun_py/src/utils.rs b/rerun_py/src/utils.rs index ddd681d3dbf0..dd0fc7a329dd 100644 --- a/rerun_py/src/utils.rs +++ b/rerun_py/src/utils.rs @@ -30,12 +30,12 @@ where F::Output: Send, { let runtime: &Runtime = get_tokio_runtime(); - py.allow_threads(|| runtime.block_on(f)) + py.detach(|| runtime.block_on(f)) } /// Issues a warning to python runtime pub fn py_rerun_warn_cstr(msg: &std::ffi::CStr) -> PyResult<()> { - Python::with_gil(|py| { + Python::attach(|py| { let warning_type = PyModule::import(py, "rerun")? .getattr("error_utils")? .getattr("RerunWarning")?; diff --git a/uv.lock b/uv.lock index 4acb800535c1..7a4ce028cc51 100644 --- a/uv.lock +++ b/uv.lock @@ -867,19 +867,19 @@ requires-dist = [{ name = "rerun-sdk", editable = "rerun_py" }] [[package]] name = "datafusion" -version = "50.1.0" +version = "51.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyarrow" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fa/cc/e8e8f7c472e93e7a560203ac40ac319b926029007c0dad873dbba97f9f2d/datafusion-50.1.0.tar.gz", hash = "sha256:d8b8f027c7ce2498cda1589d3ce6d8720798963e031660fbe4d2e26e172442ec", size = 188103, upload-time = "2025-10-20T12:39:23.802Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2c/6d/d0e2632c93bbcca0687eeda672af3f92042ecd349df7be55da86253594a9/datafusion-51.0.0.tar.gz", hash = "sha256:1887c7d5ed3ae5d9f389e62ba869864afad4006a3f7c99ef0ca4707782a7838f", size = 193751, upload-time = "2026-01-09T13:23:41.562Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1f/6e/f9e2d5d935024a79fd549b5ce1d05549d26a027aab800727d492ac036504/datafusion-50.1.0-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:aeaa3c7bcf630bbea962b8fe75d300d98eaf7e2a5edf98e6a0130a1bec3543ea", size = 29280689, upload-time = "2025-10-20T12:39:06.913Z" }, - { url = "https://files.pythonhosted.org/packages/db/58/2dc473240f552d3620186b527c04397f82b36f02243afaf49f0813c84a17/datafusion-50.1.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:85727df82c818103092c3ee18d198365833d3e44c2921d2b378d4d682798e511", size = 26140751, upload-time = "2025-10-20T12:39:09.95Z" }, - { url = "https://files.pythonhosted.org/packages/00/ba/8d8aa1df96e0666752e5c9d406d440495df2014d315b2a95bbef9856b23e/datafusion-50.1.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49f5bd0edb2bf2d00625beeb46a115e1421db2e1b14b535f7c17cc0927f36b8a", size = 32165290, upload-time = "2025-10-20T12:39:13.713Z" }, - { url = "https://files.pythonhosted.org/packages/11/9a/afce9586145b3ed153d75364b21102a6a95260940352e06b7c6709e9d2db/datafusion-50.1.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:5c9c2f70922ddedf54d8abd4ba9585a5026c3409438f5aafc1ad0428a67a4d1f", size = 29982398, upload-time = "2025-10-20T12:39:16.823Z" }, - { url = "https://files.pythonhosted.org/packages/51/a3/41ef1c565770ef0c4060ee3fd50367dd06816f70a5be1ef41fbd7c3975e8/datafusion-50.1.0-cp39-abi3-win_amd64.whl", hash = "sha256:145c8f2e969c9cc51dc6af8a185ec39739ebeb5d680f9fe0020e005564ed40a8", size = 31258359, upload-time = "2025-10-20T12:39:21.731Z" }, + { url = "https://files.pythonhosted.org/packages/cf/a9/7717cec053a3309be3020fe3147e3f76e5bf21295fa8adf9b52dd44ea3ff/datafusion-51.0.0-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:0c0d265fe3ee0dcbfa7cc3c64c7cd94fc493f38418bd79debb7ec29f29b7176e", size = 30389413, upload-time = "2026-01-09T13:23:23.266Z" }, + { url = "https://files.pythonhosted.org/packages/55/45/72c9874fd3740a4cb9d55049fdbae0df512dc5433e9f1176f3cfd970f1a1/datafusion-51.0.0-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:43e6011db86e950bf9a21ed73cc089c2346b340a41a4f1044268af6c3a357acc", size = 26982206, upload-time = "2026-01-09T13:23:27.437Z" }, + { url = "https://files.pythonhosted.org/packages/21/ac/b32ba1f25d38fc16e7623cc4bfb7bd68db61be2ef27b2d9969ea5c865765/datafusion-51.0.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e76803907150159aa059d5cc9291645bbaac1b6a46d07e56035118d327b741ae", size = 33246117, upload-time = "2026-01-09T13:23:30.981Z" }, + { url = "https://files.pythonhosted.org/packages/0b/4e/437121422ef010690fc3cdd7f080203e986ba00e0e3c3b577e03f5b54ca2/datafusion-51.0.0-cp310-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:9d0cfabfe1853994adc2e6e9da5f36c1eb061102e34a2f1101fa935c6991c9e1", size = 31421867, upload-time = "2026-01-09T13:23:34.436Z" }, + { url = "https://files.pythonhosted.org/packages/db/fc/58cf27fcb85b2fd2a698253ae46213b1cbda784407e205c148f4006c1429/datafusion-51.0.0-cp310-abi3-win_amd64.whl", hash = "sha256:fd5f9abfd6669062debf0658d13e4583234c89d4df95faf381927b11cea411f5", size = 32517679, upload-time = "2026-01-09T13:23:39.615Z" }, ] [[package]] @@ -4701,10 +4701,10 @@ tests = [ [package.metadata] requires-dist = [ { name = "attrs", specifier = ">=23.1.0" }, - { name = "datafusion", marker = "extra == 'all'", specifier = "==50.1.0" }, - { name = "datafusion", marker = "extra == 'datafusion'", specifier = "==50.1.0" }, - { name = "datafusion", marker = "extra == 'dataplatform'", specifier = "==50.1.0" }, - { name = "datafusion", marker = "extra == 'tests'", specifier = "==50.1.0" }, + { name = "datafusion", marker = "extra == 'all'", specifier = "==51.0.0" }, + { name = "datafusion", marker = "extra == 'datafusion'", specifier = "==51.0.0" }, + { name = "datafusion", marker = "extra == 'dataplatform'", specifier = "==51.0.0" }, + { name = "datafusion", marker = "extra == 'tests'", specifier = "==51.0.0" }, { name = "inline-snapshot", marker = "extra == 'tests'", specifier = "==0.31.1" }, { name = "numpy", specifier = ">=2" }, { name = "opencv-python", marker = "extra == 'tests'", specifier = ">4.6" }, @@ -5337,7 +5337,7 @@ wheels = [ [[package]] name = "server-tables" -version = "0.1.0" +version = "0.1.1" source = { editable = "examples/python/server_tables" } dependencies = [ { name = "datafusion" }, @@ -5346,7 +5346,7 @@ dependencies = [ [package.metadata] requires-dist = [ - { name = "datafusion", specifier = "==50.1" }, + { name = "datafusion", specifier = "==51.0.0" }, { name = "rerun-sdk", editable = "rerun_py" }, ] From 496ce8ea3ee18b14aaed20e9e506dc1aa5448cf0 Mon Sep 17 00:00:00 2001 From: Lucas Meurer Date: Mon, 2 Mar 2026 14:18:41 +0100 Subject: [PATCH 004/513] Implement new form components ### Related - closes https://linear.app/rerun/issue/RR-3866/merge-the-new-helpers-from-re-ui-example ### What Implements the new form design as components. They don't affect the viewer yet, and there are a lot of todos, but it's a good starting point to split the work from here on. Adds some examples to re_ui_example: image Source-Ref: b5f39d1f6a1f12f252bcca5a0ba9bd2304892e44 --- crates/viewer/re_ui/data/dark_theme.ron | 10 + .../viewer/re_ui/data/icons/combo_arrow.svg | 3 + crates/viewer/re_ui/data/light_theme.ron | 10 + .../re_ui/examples/re_ui_example/main.rs | 16 +- .../examples/re_ui_example/right_panel.rs | 187 ++++++++++++++++-- crates/viewer/re_ui/src/design_tokens.rs | 7 + crates/viewer/re_ui/src/icons.rs | 1 + crates/viewer/re_ui/src/lib.rs | 1 + crates/viewer/re_ui/src/re_form/fields.rs | 67 +++++++ crates/viewer/re_ui/src/re_form/form_strip.rs | 132 +++++++++++++ crates/viewer/re_ui/src/re_form/mod.rs | 7 + crates/viewer/re_ui/src/re_form/selectable.rs | 97 +++++++++ 12 files changed, 514 insertions(+), 24 deletions(-) create mode 100644 crates/viewer/re_ui/data/icons/combo_arrow.svg create mode 100644 crates/viewer/re_ui/src/re_form/fields.rs create mode 100644 crates/viewer/re_ui/src/re_form/form_strip.rs create mode 100644 crates/viewer/re_ui/src/re_form/mod.rs create mode 100644 crates/viewer/re_ui/src/re_form/selectable.rs diff --git a/crates/viewer/re_ui/data/dark_theme.ron b/crates/viewer/re_ui/data/dark_theme.ron index f8d4ccf75932..aadf8b7ae3e1 100644 --- a/crates/viewer/re_ui/data/dark_theme.ron +++ b/crates/viewer/re_ui/data/dark_theme.ron @@ -118,6 +118,16 @@ "color": "{Gray.200}" }, + "form_field_bg_color": { + "color": "{Gray.200}" + }, + "form_selectable_bg_color": { + "color": "{Gray.250}" + }, + "form_selectable_stroke_color": { + "color": "{Gray.350}" + }, + "blueprint_time_panel_bg_fill": { "color": "#141326" }, diff --git a/crates/viewer/re_ui/data/icons/combo_arrow.svg b/crates/viewer/re_ui/data/icons/combo_arrow.svg new file mode 100644 index 000000000000..9dcba3701d16 --- /dev/null +++ b/crates/viewer/re_ui/data/icons/combo_arrow.svg @@ -0,0 +1,3 @@ + + + diff --git a/crates/viewer/re_ui/data/light_theme.ron b/crates/viewer/re_ui/data/light_theme.ron index 06cfa9be4582..87c1290fa2c2 100644 --- a/crates/viewer/re_ui/data/light_theme.ron +++ b/crates/viewer/re_ui/data/light_theme.ron @@ -118,6 +118,16 @@ "color": "{Gray.900}" }, + "form_field_bg_color": { + "color": "{Gray.900}" + }, + "form_selectable_bg_color": { + "color": "{Gray.975}" + }, + "form_selectable_stroke_color": { + "color": "{Gray.700}" + }, + "blueprint_time_panel_bg_fill": { "color": "{Blue.900}" }, diff --git a/crates/viewer/re_ui/examples/re_ui_example/main.rs b/crates/viewer/re_ui/examples/re_ui_example/main.rs index 0228c0ceb82b..a490d5ff839f 100644 --- a/crates/viewer/re_ui/examples/re_ui_example/main.rs +++ b/crates/viewer/re_ui/examples/re_ui_example/main.rs @@ -5,7 +5,7 @@ mod hierarchical_drag_and_drop; mod right_panel; use crossbeam::channel::Receiver; -use egui::{ComboBox, Modifiers, Widget as _, os}; +use egui::{ComboBox, Modifiers, Rect, ScrollArea, Widget as _, os}; use re_ui::filter_widget::{FilterState, format_matching_text}; use re_ui::list_item::ListItemContentButtonsExt as _; use re_ui::menu::menu_style; @@ -368,7 +368,9 @@ impl eframe::App for ExampleApp { .min_size(0.0) .show_animated_inside(ui, self.show_right_panel, |ui| { ui.spacing_mut().item_spacing.y = 0.0; - self.right_panel.ui(ui); + ScrollArea::vertical().show(ui, |ui| { + self.right_panel.ui(ui); + }); }); egui::CentralPanel::default() @@ -547,7 +549,15 @@ impl egui_tiles::Behavior for MyTileTreeBehavior { ui.success_label("This is an example of a long success label."); ui.info_label("This is an example of a long info label."); - ComboBox::new("combo_item_example", "") + ComboBox::from_id_salt("combo_item_example") + // TODO(RR-3863): Allow globally customizing combobox icon + .icon(|ui, rect, visuals, _open| { + let rect = Rect::from_center_size(rect.center(), ui.tokens().small_icon_size); + icons::COMBO_ARROW + .as_image() + .tint(visuals.text_color()) + .paint_at(ui, rect); + }) .selected_text("ComboItem Example") .popup_style(menu_style()) .height(300.0) diff --git a/crates/viewer/re_ui/examples/re_ui_example/right_panel.rs b/crates/viewer/re_ui/examples/re_ui_example/right_panel.rs index 96ff167dcb4d..78463270e338 100644 --- a/crates/viewer/re_ui/examples/re_ui_example/right_panel.rs +++ b/crates/viewer/re_ui/examples/re_ui_example/right_panel.rs @@ -1,8 +1,8 @@ -use egui::Ui; -use re_ui::list_item::ListItemContentButtonsExt as _; -use re_ui::{UiExt as _, list_item}; - use crate::{drag_and_drop, hierarchical_drag_and_drop}; +use egui::{Atom, Button, DragValue, RichText, TextEdit, Ui}; +use re_ui::list_item::{ListItemContentButtonsExt as _, PropertyContent}; +use re_ui::re_form::{ConstructFormStrip as _, FormFields, SelectableStrip, SelectableToggle}; +use re_ui::{UiExt as _, icons, list_item}; pub struct RightPanel { show_hierarchical_demo: bool, @@ -44,36 +44,38 @@ impl RightPanel { // ui.panel_content(|ui| { - ui.panel_title_bar_with_buttons("Demo: drag-and-drop", None, |ui| { - ui.toggle_switch(8.0, &mut self.show_hierarchical_demo); - ui.label("Hierarchical:"); - }); - list_item::list_item_scope(ui, "drag_and_drop", |ui| { - if self.show_hierarchical_demo { - self.hierarchical_drag_and_drop.ui(ui); - } else { - self.drag_and_drop.ui(ui); - } + let show_hierarchical = self.show_hierarchical_demo; + ui.section_collapsing_header("Drag and drop") + .with_buttons(|ui| { + ui.toggle_switch(8.0, &mut self.show_hierarchical_demo); + ui.label("Hierarchical:"); + }) + .show(ui, |ui| { + if show_hierarchical { + self.hierarchical_drag_and_drop.ui(ui); + } else { + self.drag_and_drop.ui(ui); + } + }); }); }); - ui.add_space(20.0); - // // Demo of `ListItem` API and features. // ui.panel_content(|ui| { - ui.panel_title_bar("Demo: ListItem APIs", None); - list_item::list_item_scope(ui, "list_item_api", |ui| { - self.list_item_api_demo(ui); + Self::new_form_fields_demo(ui); + + ui.section_collapsing_header("List item api") + .show(ui, |ui| { + self.list_item_api_demo(ui); + }); }); }); - ui.add_space(20.0); - // // Nested scroll area demo. Multiple `panel_content` must be used to ensure the scroll // bar appears nicely snug with the panel right border. @@ -351,4 +353,147 @@ impl RightPanel { }, ); } + + fn new_form_fields_demo(ui: &mut Ui) { + list_item::list_item_scope(ui, "form_fields", |ui| { + ui.section_collapsing_header("New form fields").show(ui, |ui| { + ui.small("SeriesLines"); + + ui.spacing_mut().item_spacing.y = 2.0; // TODO(lucas): 4 should be the correct value, why does 2 look right? + let demo_ui = |ui: &mut Ui, content: PropertyContent<'_>| { + ui.list_item().interactive(false).show_hierarchical_with_children( + ui, + ui.next_auto_id(), + false, + content.with_action_button(&icons::MORE, "more", || {}).with_always_show_buttons(true), + |_ui| {}, + ); + }; + + let subdued = ui.tokens().text_subdued; + let suffix = |suffix: &str| { + RichText::new(suffix).color(subdued).size(10.0) + }; + + demo_ui( + ui, + PropertyContent::new("width").value_fn(|ui, _vis| { + // TODO(RR-3883): Add atom support to dragvalue + FormFields::single( + ui, + Button::new(( + "0.75", + suffix("pt"), + )), + ); + }), + ); + + // TODO(RR-3864): ComboBox should allow customizing the icon globally + let fake_combobox = |label: &str| { + Button::new((label, Atom::grow(), icons::COMBO_ARROW)) + .image_tint_follows_text_color(true) + }; + + demo_ui( + ui, + PropertyContent::new("marker_size").value_fn(|ui, _vis| { + FormFields::relative(ui, [1.0, 2.0]) + .and(DragValue::new(&mut 11.17)) + .and(|ui: &mut Ui| { + // TODO(RR-3864): ComboBox shouldn't wrap button in a horizontal + // ComboBox::from_id_salt("123") + // .selected_text("ui points") + // .show_ui(ui, |ui| {}) + // .response + ui.add( + fake_combobox("ui points") + ) + }); + }), + ); + + + demo_ui( + ui, + PropertyContent::new("centers") + .value_fn(|ui, _vis| { + FormFields::same(ui, 2) + .and(Button::new((suffix("x"), "640"))) + .and(Button::new((suffix("y"), "640"))); + }), + ); + + demo_ui( + ui, + PropertyContent::new("range") + .value_fn(|ui, _vis| { + FormFields::same(ui, 2) + .and(Button::new(("640", Atom::grow(), suffix("min")))) + .and(Button::new(("640", Atom::grow(), suffix("max")))); + }), + ); + + demo_ui( + ui, + PropertyContent::new("centers") + .value_fn(|ui, _vis| { + FormFields::same(ui, 3) + .and(DragValue::new(&mut 2.0)) + .and(DragValue::new(&mut 1.0)) + .and(DragValue::new(&mut 0.0)); + }), + ); + + demo_ui( + ui, + PropertyContent::new("vertex_normals") + .value_fn(|ui, _vis| { + FormFields::same(ui, 3) + .and(Button::new((suffix("x"), "640"))) + .and(Button::new((suffix("y"), "640"))) + .and(Button::new((suffix("z"), "640"))); + }), + ); + + demo_ui( + ui, + PropertyContent::new("aggregation") + .value_fn(|ui, _vis| { + FormFields::single(ui, fake_combobox("Average")); + }), + ); + + demo_ui( + ui, + PropertyContent::new("visibility") + .value_fn(|ui, _vis| { + SelectableStrip::same(ui, 2) + .and(SelectableToggle::new("Show", true)) + .and(SelectableToggle::new("Hide", false)); + }), + ); + + demo_ui( + ui, + PropertyContent::new("name") + .value_fn(|ui, _vis| { + // TODO(RR-3859): TextEdit margin is hardcoded in egui. TextEdit also ignores + // interact size (should use it as min height) + FormFields::single(ui, TextEdit::singleline(&mut "Temperature".to_owned())); + }), + ); + + demo_ui( + ui, + PropertyContent::new("text") + .value_fn(|ui, _vis| { + // TODO(RR-3858): Listitems don't grow when their contents exceed their size + let mut text = "Give a bit of space to a longer text input fields, let’s say at least 3 lines and the rest …".to_owned(); + FormFields::single(ui, TextEdit::multiline(&mut text).desired_rows(1)); + }), + ); + }); + }); + } } diff --git a/crates/viewer/re_ui/src/design_tokens.rs b/crates/viewer/re_ui/src/design_tokens.rs index b30b50710859..363bc4e4f5f0 100644 --- a/crates/viewer/re_ui/src/design_tokens.rs +++ b/crates/viewer/re_ui/src/design_tokens.rs @@ -126,6 +126,10 @@ pub struct DesignTokens { pub text_edit_bg_color: Color32, + pub form_field_bg_color: Color32, + pub form_selectable_bg_color: Color32, + pub form_selectable_stroke_color: Color32, + /// Color for blueprint time panel background pub blueprint_time_panel_bg_fill: Color32, @@ -333,6 +337,9 @@ impl DesignTokens { panel_bg_color: get_color("panel_bg_color"), text_edit_bg_color: get_color("text_edit_bg_color"), + form_field_bg_color: get_color("form_field_bg_color"), + form_selectable_bg_color: get_color("form_selectable_bg_color"), + form_selectable_stroke_color: get_color("form_selectable_stroke_color"), blueprint_time_panel_bg_fill: get_color("blueprint_time_panel_bg_fill"), notification_panel_background_color: get_color("notification_panel_background_color"), notification_background_color: get_color("notification_background_color"), diff --git a/crates/viewer/re_ui/src/icons.rs b/crates/viewer/re_ui/src/icons.rs index bfefb62a1f80..e727c4beb1e8 100644 --- a/crates/viewer/re_ui/src/icons.rs +++ b/crates/viewer/re_ui/src/icons.rs @@ -129,6 +129,7 @@ pub const ARROW_LEFT: Icon = icon_from_path!("../data/icons/arrow_left.svg"); pub const ARROW_RIGHT: Icon = icon_from_path!("../data/icons/arrow_right.svg"); pub const ARROW_UP: Icon = icon_from_path!("../data/icons/arrow_up.svg"); pub const ARROW_DOWN: Icon = icon_from_path!("../data/icons/arrow_down.svg"); +pub const COMBO_ARROW: Icon = icon_from_path!("../data/icons/combo_arrow.svg"); pub const LOOP: Icon = icon_from_path!("../data/icons/loop.svg"); pub const FILTER: Icon = icon_from_path!("../data/icons/filter.svg"); diff --git a/crates/viewer/re_ui/src/lib.rs b/crates/viewer/re_ui/src/lib.rs index ba1ef0a068b5..1e9d3a76495a 100644 --- a/crates/viewer/re_ui/src/lib.rs +++ b/crates/viewer/re_ui/src/lib.rs @@ -32,6 +32,7 @@ mod ui_layout; mod button; mod combo_item; +pub mod re_form; #[cfg(feature = "testing")] pub mod testing; diff --git a/crates/viewer/re_ui/src/re_form/fields.rs b/crates/viewer/re_ui/src/re_form/fields.rs new file mode 100644 index 000000000000..479e88a1252e --- /dev/null +++ b/crates/viewer/re_ui/src/re_form/fields.rs @@ -0,0 +1,67 @@ +use crate::re_form::form_strip::FormStrip; +use crate::re_form::{ConstructFormStrip, Fractions}; +use crate::{DesignTokens, UiExt as _}; +use egui::{Align, Frame, Layout, Margin, Response, Style, Ui, Widget}; +use std::ops::{Deref, DerefMut}; + +/// Wrapper around [`FormStrip`] that applies the expected styling. +pub struct FormFields<'a> { + strip: FormStrip<'a>, +} + +impl<'a> FormFields<'a> { + pub fn single(ui: &'a mut Ui, widget: impl Widget) -> Response { + let mut fields = Self::same(ui, 1); + fields.add(widget) + } +} + +impl<'a> ConstructFormStrip<'a> for FormFields<'a> { + fn new(ui: &'a mut Ui, fields: Fractions) -> Self { + let mut strip = FormStrip::new(ui, fields).with_item_layout( + Layout::left_to_right(Align::Center) + .with_main_justify(true) + .with_main_align(Align::Min) + .with_cross_justify(true), + ); + let tokens = strip.child_ui.tokens(); + apply_form_field_style(strip.child_ui.style_mut(), tokens); + Self { strip } + } +} + +impl<'a> Deref for FormFields<'a> { + type Target = FormStrip<'a>; + + #[inline] + fn deref(&self) -> &Self::Target { + &self.strip + } +} + +impl DerefMut for FormFields<'_> { + #[inline] + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.strip + } +} + +pub fn apply_form_field_style(style: &mut Style, tokens: &DesignTokens) { + let frame = form_field_frame(tokens); + style.visuals.widgets.inactive.bg_fill = frame.fill; + style.visuals.widgets.inactive.weak_bg_fill = frame.fill; + style.visuals.widgets.active.expansion = 0.0; + style.visuals.widgets.hovered.expansion = 0.0; + style.visuals.widgets.open.expansion = 0.0; + style.spacing.button_padding = frame.inner_margin.left_top(); + style.spacing.item_spacing.x = 4.0; + style.spacing.item_spacing.y = 4.0; + style.spacing.interact_size.y = 24.0; +} + +pub fn form_field_frame(tokens: &DesignTokens) -> Frame { + Frame::new() + .corner_radius(4.0) + .fill(tokens.form_field_bg_color) + .inner_margin(Margin::symmetric(6, 0)) +} diff --git a/crates/viewer/re_ui/src/re_form/form_strip.rs b/crates/viewer/re_ui/src/re_form/form_strip.rs new file mode 100644 index 000000000000..af36ce9651bc --- /dev/null +++ b/crates/viewer/re_ui/src/re_form/form_strip.rs @@ -0,0 +1,132 @@ +use eframe::emath::{Align, Vec2}; +use egui::{Layout, Response, Sense, Ui, UiBuilder, Widget}; +use smallvec::{SmallVec, smallvec}; + +pub type Fractions = SmallVec<[f32; 3]>; + +/// Minimal, horizontal version of [`egui_extras::StripBuilder`] with a more convenient api. +pub struct FormStrip<'a> { + fields: Fractions, + available_width_without_gaps: f32, + current_index: usize, + pub(crate) ui: &'a mut Ui, + item_layout: Layout, + pub(crate) child_ui: Ui, + response: Option, +} + +impl FormStrip<'_> { + pub fn with_item_layout(mut self, layout: Layout) -> Self { + self.item_layout = layout; + self + } +} + +impl<'a> FormStrip<'a> { + pub fn single(ui: &'a mut Ui, widget: impl Widget) -> Response { + let mut fields = Self::same(ui, 1); + fields.add(widget) + } + + fn new(ui: &'a mut Ui, fields: Fractions) -> Self { + let item_layout = *ui.layout(); + + let gap_size = ui.spacing().item_spacing.x; + let total_available = ui.available_width(); + let available = total_available - gap_size * (fields.len() as f32 - 1.0) / 2.0; // TODO(lucas): Why is gap sized halved?? + + let child_ui = ui.new_child( + UiBuilder::new().layout(Layout::left_to_right(Align::Min).with_cross_align(Align::Min)), + ); + + // let child_ui: Ui = ui.new_child(UiBuilder::new()); + Self { + fields, + current_index: 0, + available_width_without_gaps: available, + item_layout, + ui, + child_ui, + response: None, + } + } + + pub fn and(&mut self, widget: impl Widget) -> &mut Self { + self.add(widget); + self + } + + pub fn add(&mut self, widget: impl Widget) -> Response { + let index = self.current_index; + self.current_index += 1; + let fraction = self.fields[index]; + + let width = self.available_width_without_gaps * fraction; + + let response = add_sized( + &mut self.child_ui, + Vec2::new(width, 24.0), + self.item_layout, + widget, + ); + if let Some(combined_child_response) = &mut self.response { + *combined_child_response |= response.clone(); + } else { + self.response = Some(response.clone()); + } + response + } + + /// Can be called to acquire the combined [`Response`] from the child ui + /// and the added widgets. Should be called last. + pub fn done(&mut self) -> Response { + let mut response = self.child_ui.response(); + if let Some(combined_child_response) = self.response.take() { + response |= combined_child_response; + } + response + } +} + +/// Trait to easily add the construction fns to a wrapper type +pub trait ConstructFormStrip<'a>: Sized { + fn same(ui: &'a mut Ui, count: usize) -> Self { + let each = 1.0 / count as f32; + Self::new(ui, smallvec![each; count]) + } + + fn relative(ui: &'a mut Ui, relative: impl IntoIterator) -> Self { + let mut fields: Fractions = relative.into_iter().collect(); + let sum: f32 = fields.iter().sum(); + fields.iter_mut().for_each(|fract| { + *fract /= sum; + }); + Self::new(ui, fields) + } + + fn new(ui: &'a mut Ui, fields: Fractions) -> Self; +} + +impl<'a> ConstructFormStrip<'a> for FormStrip<'a> { + fn new(ui: &'a mut Ui, fields: Fractions) -> Self { + FormStrip::new(ui, fields) + } +} + +impl Drop for FormStrip<'_> { + fn drop(&mut self) { + self.ui + .allocate_rect(self.child_ui.min_rect(), Sense::hover()); + } +} + +/// Copy of `ui.add_sized` that aligns items to the left +fn add_sized( + ui: &mut Ui, + max_size: impl Into, + layout: Layout, + widget: impl Widget, +) -> Response { + ui.allocate_ui_with_layout(max_size.into(), layout, |ui| ui.add(widget)) + .inner +} diff --git a/crates/viewer/re_ui/src/re_form/mod.rs b/crates/viewer/re_ui/src/re_form/mod.rs new file mode 100644 index 000000000000..1bcc24a6b087 --- /dev/null +++ b/crates/viewer/re_ui/src/re_form/mod.rs @@ -0,0 +1,7 @@ +mod fields; +mod form_strip; +mod selectable; + +pub use fields::*; +pub use form_strip::*; +pub use selectable::*; diff --git a/crates/viewer/re_ui/src/re_form/selectable.rs b/crates/viewer/re_ui/src/re_form/selectable.rs new file mode 100644 index 000000000000..f7d58a164ab5 --- /dev/null +++ b/crates/viewer/re_ui/src/re_form/selectable.rs @@ -0,0 +1,97 @@ +use crate::UiExt as _; +use crate::re_form::{ConstructFormStrip, FormStrip, Fractions, form_field_frame}; +use egui::epaint::RectShape; +use egui::layers::ShapeIdx; +use egui::{ + AtomLayout, Atoms, Direction, Frame, IntoAtoms, Layout, Response, Shape, Stroke, StrokeKind, + Ui, Widget, +}; +use std::ops::{Deref, DerefMut}; + +pub struct SelectableStrip<'a> { + strip: FormStrip<'a>, + bg_idx: ShapeIdx, +} + +impl<'a> SelectableStrip<'a> { + pub fn single(ui: &'a mut Ui, widget: impl Widget) -> Response { + let mut fields = Self::same(ui, 1); + fields.add(widget) + } +} + +impl<'a> ConstructFormStrip<'a> for SelectableStrip<'a> { + fn new(ui: &'a mut Ui, fields: Fractions) -> Self { + // FormStrip::new reads spacing during construction so we override it here + let item_spacing = std::mem::take(&mut ui.spacing_mut().item_spacing); + let strip = FormStrip::new(ui, fields) + .with_item_layout(Layout::centered_and_justified(Direction::LeftToRight)); + strip.ui.spacing_mut().item_spacing = item_spacing; + + let bg_idx = strip.child_ui.painter().add(Shape::Noop); + + Self { strip, bg_idx } + } +} + +impl<'a> Deref for SelectableStrip<'a> { + type Target = FormStrip<'a>; + + #[inline] + fn deref(&self) -> &Self::Target { + &self.strip + } +} + +impl DerefMut for SelectableStrip<'_> { + #[inline] + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.strip + } +} + +impl Drop for SelectableStrip<'_> { + fn drop(&mut self) { + let rect = self.strip.child_ui.min_rect(); + let frame = form_field_frame(self.strip.child_ui.tokens()); + self.strip.child_ui.painter().set( + self.bg_idx, + Shape::Rect(RectShape::new( + rect, + frame.corner_radius, + frame.fill, + frame.stroke, + StrokeKind::Inside, + )), + ); + } +} + +pub struct SelectableToggle<'a> { + atoms: Atoms<'a>, + selected: bool, +} + +impl<'a> SelectableToggle<'a> { + pub fn new(atoms: impl IntoAtoms<'a>, selected: bool) -> Self { + Self { + atoms: atoms.into_atoms(), + selected, + } + } +} + +impl Widget for SelectableToggle<'_> { + fn ui(self, ui: &mut Ui) -> Response { + let mut frame = Frame::new(); + if self.selected { + let tokens = ui.tokens(); + // TODO(RR-3885): Add interactivity / hover styles + frame = frame + .fill(tokens.form_selectable_bg_color) + .stroke(Stroke::new(1.0, tokens.form_selectable_stroke_color)) + .corner_radius(4.0); + } + AtomLayout::new(self.atoms).frame(frame).show(ui).response + } +} From 80e3ce680cfafb80d60e863d84b9c0de94e5f52f Mon Sep 17 00:00:00 2001 From: Lucas Meurer Date: Mon, 2 Mar 2026 14:24:01 +0100 Subject: [PATCH 005/513] Fix docs urls being loaded as data sources ### Related - This broke in #769 ### What Adds a new flag when parsing urls whether to accept extensionless urls or not. When clicking an egui link this should not be set but when a user explicitedly tries to open an url it should be set. ### Testing Tested locally so far Source-Ref: ecdd244d107efa7885eb6f65c77839b8cf3cc1f6 --- .../store/re_data_source/src/data_source.rs | 76 +++++++++++++++---- crates/store/re_data_source/src/lib.rs | 4 +- crates/top/rerun/src/commands/entrypoint.rs | 11 ++- crates/viewer/re_viewer/src/app.rs | 16 +++- crates/viewer/re_viewer/src/app_state.rs | 9 ++- .../re_viewer/src/open_url_description.rs | 11 ++- .../viewer/re_viewer/src/ui/open_url_modal.rs | 10 ++- .../viewer/re_viewer_context/src/open_url.rs | 25 ++++-- 8 files changed, 126 insertions(+), 36 deletions(-) diff --git a/crates/store/re_data_source/src/data_source.rs b/crates/store/re_data_source/src/data_source.rs index 011d2fb3d7ed..0c07e9b7efd6 100644 --- a/crates/store/re_data_source/src/data_source.rs +++ b/crates/store/re_data_source/src/data_source.rs @@ -60,6 +60,21 @@ pub enum LogDataSource { RedapProxy(re_uri::ProxyUri), } +/// Options for [`LogDataSource::from_uri`]. +#[derive(Clone, Debug, Default)] +pub struct FromUriOptions { + /// If `true`, keep reading `.rrd` files past EOF, tailing new data as it arrives. + pub follow: bool, + + /// If `true`, accept extensionless HTTP URLs for magic-bytes-based format detection. + /// + /// This should be `true` at external entry points (CLI, explicit user URL input), + /// but `false` when parsing URLs from viewer-internal links, where extensionless + /// URLs (e.g. `https://rerun.io/docs/getting-started/data-in`) should fall through to be opened in + /// the browser. + pub accept_extensionless_http: bool, +} + impl LogDataSource { /// Tries to classify a URI into a [`LogDataSource`]. /// @@ -71,7 +86,7 @@ impl LogDataSource { pub fn from_uri( _file_source: re_log_types::FileSource, url: &str, - follow: bool, + options: &FromUriOptions, ) -> Option { #[cfg(not(target_arch = "wasm32"))] { @@ -132,7 +147,7 @@ impl LogDataSource { return Some(Self::FilePath { file_source: _file_source, path, - follow, + follow: options.follow, }); } @@ -140,7 +155,7 @@ impl LogDataSource { return Some(Self::FilePath { file_source: _file_source, path, - follow, + follow: options.follow, }); } } @@ -180,12 +195,16 @@ impl LogDataSource { if re_data_loader::is_supported_file_extension(extension) { Some(Self::HttpUrl { url, follow: false }) - } else if extension.is_empty() + } else if options.accept_extensionless_http + && extension.is_empty() && was_proper_http_url && !contains_viewer_query_url_param { // No extension — accept the URL and try to detect format after download - Some(Self::HttpUrl { url, follow }) + Some(Self::HttpUrl { + url, + follow: options.follow, + }) } else { None // Has an extension but it's not one we support } @@ -454,14 +473,16 @@ mod tests { "https://example.com/scene.glb", "https://example.com/photo.png", "https://example.com/video.mp4", - // Extensionless URLs — accepted for magic bytes detection after download + // Since the path has an explicit extension, this will be parsed as a DataSource and + // not a `ViewerOpenUrl` (see invalid section below) + "https://example.com/some-file.rrd?url=recording.rrd", + ]; + // Extensionless URLs — only accepted when accept_extensionless_http is true + let extensionless_http = [ "https://example.com/download", "https://example.com/api/file?id=123", "https://storage.example.com/abc123?token=xyz", "https://example.com/files?my.id", - // Since the path has an explicit extension, this will be parsed as a DataSource and - // not a `ViewerOpenUrl` (see invalid section below) - "https://example.com/some-file.rrd?url=recording.rrd", ]; let grpc = [ // segment_id (new) @@ -492,9 +513,14 @@ mod tests { recommended_store_id: None, force_store_info: false, }; + let default_options = FromUriOptions::default(); + let extensionless_options = FromUriOptions { + accept_extensionless_http: true, + ..Default::default() + }; for uri in file { - let data_source = LogDataSource::from_uri(file_source.clone(), uri, false); + let data_source = LogDataSource::from_uri(file_source.clone(), uri, &default_options); if !matches!(data_source, Some(LogDataSource::FilePath { .. })) { eprintln!( "Expected {uri:?} to be categorized as FilePath. Instead it got parsed as {data_source:?}" @@ -504,7 +530,7 @@ mod tests { } for uri in http { - let data_source = LogDataSource::from_uri(file_source.clone(), uri, false); + let data_source = LogDataSource::from_uri(file_source.clone(), uri, &default_options); if !matches!(data_source, Some(LogDataSource::HttpUrl { .. })) { eprintln!( "Expected {uri:?} to be categorized as HttpUrl. Instead it got parsed as {data_source:?}" @@ -513,8 +539,29 @@ mod tests { } } + // Extensionless URLs are accepted when accept_extensionless_http is true + for uri in extensionless_http { + let data_source = + LogDataSource::from_uri(file_source.clone(), uri, &extensionless_options); + if !matches!(data_source, Some(LogDataSource::HttpUrl { .. })) { + eprintln!( + "Expected {uri:?} to be categorized as HttpUrl (with accept_extensionless_http=true). Instead it got parsed as {data_source:?}" + ); + failed = true; + } + + // …but rejected when accept_extensionless_http is false + let data_source = LogDataSource::from_uri(file_source.clone(), uri, &default_options); + if data_source.is_some() { + eprintln!( + "Expected {uri:?} to be None (with accept_extensionless_http=false). Instead it got parsed as {data_source:?}" + ); + failed = true; + } + } + for uri in grpc { - let data_source = LogDataSource::from_uri(file_source.clone(), uri, false); + let data_source = LogDataSource::from_uri(file_source.clone(), uri, &default_options); if !matches!(data_source, Some(LogDataSource::RedapDatasetSegment { .. })) { eprintln!( "Expected {uri:?} to be categorized as redap dataset segment. Instead it got parsed as {data_source:?}" @@ -524,7 +571,7 @@ mod tests { } for uri in proxy { - let data_source = LogDataSource::from_uri(file_source.clone(), uri, false); + let data_source = LogDataSource::from_uri(file_source.clone(), uri, &default_options); if !matches!(data_source, Some(LogDataSource::RedapProxy { .. })) { eprintln!( "Expected {uri:?} to be categorized as MessageProxy. Instead it got parsed as {data_source:?}" @@ -534,7 +581,8 @@ mod tests { } for uri in invalid { - let data_source = LogDataSource::from_uri(file_source.clone(), uri, false); + let data_source = + LogDataSource::from_uri(file_source.clone(), uri, &extensionless_options); if data_source.is_some() { eprintln!("Expected {uri:?} to be None. Instead it got parsed as {data_source:?}"); failed = true; diff --git a/crates/store/re_data_source/src/lib.rs b/crates/store/re_data_source/src/lib.rs index 38554f8ef8a1..6fd74948356a 100644 --- a/crates/store/re_data_source/src/lib.rs +++ b/crates/store/re_data_source/src/lib.rs @@ -13,7 +13,9 @@ mod stream_rrd_from_http; #[cfg(not(target_arch = "wasm32"))] mod load_stdin; -pub use self::data_source::{AuthErrorHandler, LogDataSource, LogDataSourceAnalytics}; +pub use self::data_source::{ + AuthErrorHandler, FromUriOptions, LogDataSource, LogDataSourceAnalytics, +}; // ---------------------------------------------------------------------------- diff --git a/crates/top/rerun/src/commands/entrypoint.rs b/crates/top/rerun/src/commands/entrypoint.rs index ee78862d3b97..f4871499ad47 100644 --- a/crates/top/rerun/src/commands/entrypoint.rs +++ b/crates/top/rerun/src/commands/entrypoint.rs @@ -1517,9 +1517,14 @@ impl ReceiversFromUrlParams { let mut urls_to_pass_on_to_viewer = Vec::new(); for url in input_urls { - if let Some(data_source) = - LogDataSource::from_uri(re_log_types::FileSource::Cli, &url, follow) - { + if let Some(data_source) = LogDataSource::from_uri( + re_log_types::FileSource::Cli, + &url, + &re_data_source::FromUriOptions { + follow, + accept_extensionless_http: true, + }, + ) { match &data_source { LogDataSource::HttpUrl { .. } => { if config.data_sources_from_http_urls { diff --git a/crates/viewer/re_viewer/src/app.rs b/crates/viewer/re_viewer/src/app.rs index 43c2b5e6dd69..e24ce266c205 100644 --- a/crates/viewer/re_viewer/src/app.rs +++ b/crates/viewer/re_viewer/src/app.rs @@ -505,7 +505,13 @@ impl App { /// Open a content URL in the viewer. pub fn open_url_or_file(&self, url: &str) { - match ViewerOpenUrl::from_str(url) { + match ViewerOpenUrl::parse_with_options( + url, + &re_data_source::FromUriOptions { + accept_extensionless_http: true, + ..Default::default() + }, + ) { Ok(url) => { url.open( &self.egui_ctx, @@ -3830,7 +3836,13 @@ impl eframe::App for App { self.command_sender.send_ui(cmd); } re_ui::CommandPaletteAction::OpenUrl(url_desc) => { - match url_desc.url.parse::() { + match ViewerOpenUrl::parse_with_options( + &url_desc.url, + &re_data_source::FromUriOptions { + accept_extensionless_http: true, + ..Default::default() + }, + ) { Ok(url) => { url.open( ui, diff --git a/crates/viewer/re_viewer/src/app_state.rs b/crates/viewer/re_viewer/src/app_state.rs index d245b1f35b21..2bcabc49a608 100644 --- a/crates/viewer/re_viewer/src/app_state.rs +++ b/crates/viewer/re_viewer/src/app_state.rs @@ -1,5 +1,4 @@ use std::borrow::Cow; -use std::str::FromStr as _; use ahash::HashMap; use egui::Ui; @@ -915,7 +914,13 @@ fn check_for_clicked_hyperlinks(egui_ctx: &egui::Context, command_sender: &Comma egui_ctx.output_mut(|o| { o.commands.retain_mut(|command| { if let egui::OutputCommand::OpenUrl(open_url) = command { - if let Ok(url) = open_url::ViewerOpenUrl::from_str(&open_url.url) { + if let Ok(url) = open_url::ViewerOpenUrl::parse_with_options( + &open_url.url, + &re_data_source::FromUriOptions { + accept_extensionless_http: false, + ..Default::default() + }, + ) { url.open( egui_ctx, &open_url::OpenUrlOptions { diff --git a/crates/viewer/re_viewer/src/open_url_description.rs b/crates/viewer/re_viewer/src/open_url_description.rs index 865f82e67e3f..1ae34e3a0275 100644 --- a/crates/viewer/re_viewer/src/open_url_description.rs +++ b/crates/viewer/re_viewer/src/open_url_description.rs @@ -96,9 +96,14 @@ impl ViewerOpenUrlDescription { } pub fn command_palette_parse_url(url: &str) -> Option { - let Ok(open_url) = url.parse::() else { - return None; - }; + let open_url = ViewerOpenUrl::parse_with_options( + url, + &re_data_source::FromUriOptions { + accept_extensionless_http: true, + ..Default::default() + }, + ) + .ok()?; Some(CommandPaletteUrl { url: url.to_owned(), diff --git a/crates/viewer/re_viewer/src/ui/open_url_modal.rs b/crates/viewer/re_viewer/src/ui/open_url_modal.rs index 2155fbe3176a..7db2661ddc48 100644 --- a/crates/viewer/re_viewer/src/ui/open_url_modal.rs +++ b/crates/viewer/re_viewer/src/ui/open_url_modal.rs @@ -1,5 +1,3 @@ -use std::str::FromStr as _; - use re_ui::modal::{ModalHandler, ModalWrapper}; use re_ui::{UICommand, UiExt as _}; use re_viewer_context::open_url::ViewerOpenUrl; @@ -55,7 +53,13 @@ impl OpenUrlModal { // ui.send_viewport_cmd(egui::ViewportCommand::RequestPaste); } - let open_url = ViewerOpenUrl::from_str(&self.url); + let open_url = ViewerOpenUrl::parse_with_options( + &self.url, + &re_data_source::FromUriOptions { + accept_extensionless_http: true, + ..Default::default() + }, + ); let can_import = match &open_url { Ok(url) => { let description = ViewerOpenUrlDescription::from_url(url); diff --git a/crates/viewer/re_viewer_context/src/open_url.rs b/crates/viewer/re_viewer_context/src/open_url.rs index 2a5f924cfef5..55817436e2df 100644 --- a/crates/viewer/re_viewer_context/src/open_url.rs +++ b/crates/viewer/re_viewer_context/src/open_url.rs @@ -136,15 +136,24 @@ impl std::str::FromStr for ViewerOpenUrl { /// Tries to parse a content URL or file inside the viewer. /// - /// This is for handling opening arbitrary URLs inside the viewer - /// (as opposed to opening them in a new tab) for both native and web. - /// Supported are: - /// * any URL or file path that can be interpreted as a [`LogDataSource`] - /// * intra-recording links (typically links to an entity) - /// * web event listeners + /// Uses conservative defaults: extensionless HTTP URLs are **not** accepted, + /// so plain URLs like `https://rerun.io/docs/getting-started/data-in` fall through to be opened + /// in the browser. Use [`Self::parse_with_options`] to control this behavior. fn from_str(url: &str) -> Result { - let follow = false; + Self::parse_with_options(url, &re_data_source::FromUriOptions::default()) + } +} +impl ViewerOpenUrl { + /// Like [`std::str::FromStr`], but with explicit control over URI parsing options. + /// + /// Use this at entry points where the user explicitly provides a URL + /// (e.g. the "Open URL" modal, command palette) with + /// [`re_data_source::FromUriOptions::accept_extensionless_http`] set to `true`. + pub fn parse_with_options( + url: &str, + from_uri_options: &re_data_source::FromUriOptions, + ) -> anyhow::Result { if url == SETTINGS_URL { Ok(Self::Settings) } else if url == CHUNK_STORE_BROWSER_URL { @@ -164,7 +173,7 @@ impl std::str::FromStr for ViewerOpenUrl { // Web event listener (legacy notebooks). Ok(Self::WebEventListener) } else if let Some(data_source) = - LogDataSource::from_uri(re_log_types::FileSource::Uri, url, follow) + LogDataSource::from_uri(re_log_types::FileSource::Uri, url, from_uri_options) { match data_source { LogDataSource::HttpUrl { url, .. } => Ok(Self::HttpUrl(url)), From 136a2d1c8fadc3ec5fde72888c616d4022ad4ead Mon Sep 17 00:00:00 2001 From: Andreas Reich Date: Mon, 2 Mar 2026 14:38:17 +0100 Subject: [PATCH 006/513] Add context menu to visualizer pills on time series view ### Related * Fixes [RR-3672](https://linear.app/rerun/issue/RR-3672) * Follow-up to https://github.com/rerun-io/reality/pull/923 ### What image Source-Ref: 78f3fb2df5566f2aebe29ef418359184cc940207 --- .../re_view_time_series/src/visualizer_ui.rs | 48 +++++++++++ .../view_visualizers_ctx_menu_1_open.png | 3 + ...view_visualizers_ctx_menu_2_after_hide.png | 3 + ...iew_visualizers_ctx_menu_3_show_option.png | 3 + ...view_visualizers_ctx_menu_4_after_show.png | 3 + ...ew_visualizers_ctx_menu_5_after_remove.png | 3 + .../tests/view_visualizers_test.rs | 79 ++++++++++++++++--- 7 files changed, 131 insertions(+), 11 deletions(-) create mode 100644 tests/rust/re_integration_test/tests/snapshots/view_visualizers_ctx_menu_1_open.png create mode 100644 tests/rust/re_integration_test/tests/snapshots/view_visualizers_ctx_menu_2_after_hide.png create mode 100644 tests/rust/re_integration_test/tests/snapshots/view_visualizers_ctx_menu_3_show_option.png create mode 100644 tests/rust/re_integration_test/tests/snapshots/view_visualizers_ctx_menu_4_after_show.png create mode 100644 tests/rust/re_integration_test/tests/snapshots/view_visualizers_ctx_menu_5_after_remove.png diff --git a/crates/viewer/re_view_time_series/src/visualizer_ui.rs b/crates/viewer/re_view_time_series/src/visualizer_ui.rs index 316efe2db7e3..9e3f3d0032f7 100644 --- a/crates/viewer/re_view_time_series/src/visualizer_ui.rs +++ b/crates/viewer/re_view_time_series/src/visualizer_ui.rs @@ -4,6 +4,7 @@ use arrayvec::ArrayVec; use re_component_ui::color_swatch::ColorSwatch; use re_log_types::external::arrow::array::AsArray as _; use re_sdk_types::archetypes::{SeriesLines, SeriesPoints}; +use re_sdk_types::blueprint::archetypes::ActiveVisualizers; use re_sdk_types::components::{self, Color, Name}; use re_sdk_types::{ComponentDescriptor, Loggable as _}; use re_ui::UiExt as _; @@ -176,6 +177,53 @@ pub fn visualizer_ui_element( .command_sender() .send_system(re_viewer_context::SystemCommand::set_selection(item)); } + + // Context menu with hide/show and remove actions. + frame_response.context_menu(|ui| { + context_menu_ui( + ui, + ctx, + node, + instruction, + &visibility_descr, + all_series_invisible, + ); + }); +} + +fn context_menu_ui( + ui: &mut egui::Ui, + ctx: &re_viewer_context::ViewContext<'_>, + node: &re_viewer_context::DataResultNode, + instruction: &re_viewer_context::VisualizerInstruction, + visibility_descr: &ComponentDescriptor, + all_series_invisible: bool, +) { + // Hide/show toggle + let label = if all_series_invisible { "Show" } else { "Hide" }; + if ui.button(label).clicked() { + instruction.save_override( + ctx.viewer_ctx, + visibility_descr, + &components::Visible::from(all_series_invisible), + ); + ui.close(); + } + + // Remove visualizer + if ui.button("Remove").clicked() { + let active_visualizers: Vec<_> = node + .data_result + .visualizer_instructions + .iter() + .filter(|v| v.id != instruction.id) + .collect(); + + let archetype = ActiveVisualizers::new(active_visualizers.iter().map(|v| v.id.0)); + let override_base_path = node.data_result.override_base_path().clone(); + ctx.save_blueprint_archetype(override_base_path, &archetype); + ui.close(); + } } /// List of colors for a time series visualizer. diff --git a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_ctx_menu_1_open.png b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_ctx_menu_1_open.png new file mode 100644 index 000000000000..6ea480ecd5d7 --- /dev/null +++ b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_ctx_menu_1_open.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bd7273dc7206bda3da6e3a99ce7c7f1fd966af7e08ac144f16c0f5010b772297 +size 179910 diff --git a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_ctx_menu_2_after_hide.png b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_ctx_menu_2_after_hide.png new file mode 100644 index 000000000000..585cc8d9e2aa --- /dev/null +++ b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_ctx_menu_2_after_hide.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6aa5684b67ce70d3f84eca4b82e0acdd276b06addcd823e3e84524ae9ef0708b +size 161711 diff --git a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_ctx_menu_3_show_option.png b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_ctx_menu_3_show_option.png new file mode 100644 index 000000000000..09a36e6b2df8 --- /dev/null +++ b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_ctx_menu_3_show_option.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:15cf121517187d0f145ff2f084cbe9fd65991650f2fbf692e54c652627c05341 +size 166285 diff --git a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_ctx_menu_4_after_show.png b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_ctx_menu_4_after_show.png new file mode 100644 index 000000000000..e084955e27f7 --- /dev/null +++ b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_ctx_menu_4_after_show.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3126dfb7a0c6631faca6d5b05e66e03556f0c6249ada70a77b362c169fd83a79 +size 173616 diff --git a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_ctx_menu_5_after_remove.png b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_ctx_menu_5_after_remove.png new file mode 100644 index 000000000000..aeb471c6f406 --- /dev/null +++ b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_ctx_menu_5_after_remove.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dd4b3481d06a8211b398323759cff5a7e566a3d96665c7a7ee7c4ee8ee4d7fa1 +size 158119 diff --git a/tests/rust/re_integration_test/tests/view_visualizers_test.rs b/tests/rust/re_integration_test/tests/view_visualizers_test.rs index 98abbefcdbfb..cce51cacc688 100644 --- a/tests/rust/re_integration_test/tests/view_visualizers_test.rs +++ b/tests/rust/re_integration_test/tests/view_visualizers_test.rs @@ -290,14 +290,10 @@ pub async fn test_view_visualizers_multi_scalar() { harness.snapshot_app("view_visualizers_multi_scalar_view_selected"); } -/// Test the "+" button on the Visualizers section when a view is selected. -/// -/// This test: -/// 1. Logs time series data -/// 2. Creates a `TimeSeriesView` and selects it -/// 3. Hides a visualizer which shouldn't affect the "+" popup options -#[tokio::test(flavor = "multi_thread")] -pub async fn test_view_visualizers_add_button() { +/// Sets up a harness with two `SeriesLines` entities (`trig/sin` and `trig/cos`) logged into a +/// single `TimeSeriesView` named "Trig view". The view is expanded and selected, ready for +/// interaction tests. +fn setup_trig_view() -> egui_kittest::Harness<'static, re_viewer::App> { let mut harness = viewer_test_utils::viewer_harness(&HarnessOptions { window_size: Some(egui::Vec2::new(1200.0, 1000.0)), max_steps: Some(100), @@ -362,14 +358,26 @@ pub async fn test_view_visualizers_add_button() { blueprint.add_views(std::iter::once(view), None, None); }); - // Expand the blueprint tree + // Expand the blueprint tree and select the view harness .blueprint_tree() .right_click_label("Viewport (Grid container)"); harness.click_label("Expand all"); - - // Select the view harness.blueprint_tree().click_label("Trig view"); + harness.run(); + + harness +} + +/// Test the "+" button on the Visualizers section when a view is selected. +/// +/// This test: +/// 1. Logs time series data +/// 2. Creates a `TimeSeriesView` and selects it +/// 3. Hides a visualizer which shouldn't affect the "+" popup options +#[tokio::test(flavor = "multi_thread")] +pub async fn test_view_visualizers_add_button() { + let mut harness = setup_trig_view(); // Snapshot 1: View selected — all entities already have visualizers, so // the "+" button is disabled (nothing new to add). @@ -391,6 +399,55 @@ pub async fn test_view_visualizers_add_button() { harness.snapshot_app("view_visualizers_add_2_popup_open"); } +/// Test the context menu on visualizer pills. +#[tokio::test(flavor = "multi_thread")] +pub async fn test_view_visualizers_context_menu() { + let mut harness = setup_trig_view(); + + // --- Context menu: Hide --- + + // Right-click a visualizer pill to open the context menu + harness + .selection_panel() + .right_click_nth_label("trig/cos", 0); + + // Snapshot 1: Context menu open with "Hide" and "Remove" options + harness.snapshot_app("view_visualizers_ctx_menu_1_open"); + + // Click "Hide" from the context menu + harness.click_label("Hide"); + + // Snapshot 2: After hiding via context menu — the pill shows the invisible icon + harness.snapshot_app("view_visualizers_ctx_menu_2_after_hide"); + + // --- Context menu: Show --- + + // Right-click the hidden pill to see "Show" in the context menu + harness + .selection_panel() + .right_click_nth_label("trig/cos", 0); + + // Snapshot 3: Context menu on hidden pill shows "Show" instead of "Hide" + harness.snapshot_app("view_visualizers_ctx_menu_3_show_option"); + + // Click "Show" to restore visibility + harness.click_label("Show"); + + // Snapshot 4: After restoring visibility via context menu + harness.snapshot_app("view_visualizers_ctx_menu_4_after_show"); + + // --- Context menu: Remove --- + + // Right-click a visualizer pill and remove it + harness + .selection_panel() + .right_click_nth_label("trig/cos", 0); + harness.click_label("Remove"); + + // Snapshot 5: After removing a visualizer via context menu — only sin remains + harness.snapshot_app("view_visualizers_ctx_menu_5_after_remove"); +} + /// Test adding multiple new visualizers to a view via the "+" button, using custom message formats. /// /// This test: From 75a6dbcaca457dc557fc1821bbb2deed65043a56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jochen=20G=C3=B6rtler?= Date: Mon, 2 Mar 2026 15:18:00 +0100 Subject: [PATCH 007/513] Protobuf schema evolution and optional field support in Lenses ### Related * Part of RR-3766 * Related to RR-3889 * Enables #527 * Enables #937 * Follow-up: #941 * #742 should not be needed anymore. ### What This PR does multiple things: * It makes the handling of `?` closer to how `jq` works internally. * It adds suppression logic to Lenses that will allow handling Protobuf schema evolution in the future. * Adds a `Transform` and corresponding Lenses `Op::promote_inner_nulls` that helps with handling the _optional_ nature of Protobuf fields. > [!IMPORTANT] > The execution model of Lenses is still super confusing and the abstraction leaks left and right, but at least this should unblock the remaining Foxglove Lenses support. I want to do another pass over Lenses to simplify the abstraction of Lenses, but that will come in a follow-up PR. --------- Source-Ref: c515d9c2fb79549b5cd5dd45a31990625098b22d Co-authored-by: Michael Grupp --- Cargo.lock | 1 + .../store/re_arrow_combinators/src/reshape.rs | 44 +++++++++- .../re_arrow_combinators/src/selector/mod.rs | 8 +- .../src/selector/parser.rs | 71 +++++++++------- .../src/selector/runtime.rs | 8 +- .../tests/test_selector.rs | 35 ++++++++ .../tests/test_transform.rs | 60 +++++++++++++- .../store/re_arrow_combinators/tests/util.rs | 24 +++--- crates/store/re_lenses/Cargo.toml | 1 + crates/store/re_lenses/src/ast.rs | 81 ++++++++++++++----- 10 files changed, 262 insertions(+), 71 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0d1449c16d82..2cedf2448644 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9208,6 +9208,7 @@ dependencies = [ "re_arrow_combinators", "re_arrow_util", "re_chunk", + "re_log", "re_log_types", "re_sdk_types", "thiserror 2.0.17", diff --git a/crates/store/re_arrow_combinators/src/reshape.rs b/crates/store/re_arrow_combinators/src/reshape.rs index 6b80c51cd552..47f097e3f373 100644 --- a/crates/store/re_arrow_combinators/src/reshape.rs +++ b/crates/store/re_arrow_combinators/src/reshape.rs @@ -3,10 +3,11 @@ use std::sync::Arc; use arrow::array::{ - Array, ArrayRef, FixedSizeListArray, ListArray, StructArray, UInt32Array, UInt64Array, + Array, ArrayRef, BooleanBufferBuilder, FixedSizeListArray, ListArray, StructArray, UInt32Array, + UInt64Array, }; use arrow::buffer::{NullBuffer, OffsetBuffer}; -use arrow::datatypes::Field; +use arrow::datatypes::{ArrowNativeType as _, Field}; use re_log::debug_assert_eq; @@ -316,6 +317,45 @@ impl Transform for StructToFixedList { } } +/// Promotes lists with all inner `nulls` to outer `nulls`. +pub struct PromoteInnerNulls; + +impl Transform for PromoteInnerNulls { + type Source = ListArray; + type Target = ListArray; + + fn transform(&self, source: &ListArray) -> Result { + let (field, offsets, values, nulls) = source.clone().into_parts(); + let inner_nulls = values.logical_nulls(); + + let mut null_buf = BooleanBufferBuilder::new(source.len()); + for i in 0..source.len() { + let valid = if nulls.as_ref().is_some_and(|nulls| !nulls.is_valid(i)) { + false + } else { + let start = offsets[i].as_usize(); + let end = offsets[i + 1].as_usize(); + if start == end { + true + } else { + match &inner_nulls { + Some(nulls) => (start..end).any(|j| nulls.is_valid(j)), + None => true, + } + } + }; + null_buf.append(valid); + } + + Ok(ListArray::new( + field, + offsets, + values, + Some(NullBuffer::from(null_buf.finish())), + )) + } +} + /// Explodes a list by scattering each inner element to a separate row. /// /// Takes a `List` and returns a flattened `List` where each inner element diff --git a/crates/store/re_arrow_combinators/src/selector/mod.rs b/crates/store/re_arrow_combinators/src/selector/mod.rs index 6ddae844b9a7..69421bdb1355 100644 --- a/crates/store/re_arrow_combinators/src/selector/mod.rs +++ b/crates/store/re_arrow_combinators/src/selector/mod.rs @@ -12,7 +12,7 @@ //! | `.field` | Access a named field in a struct | `.location` | //! | `[]` | Iterate over every element of a list | `.poses[]` | //! | `[N]` | Index into a list by position | `.[0]` | -//! | `?` | Optional: suppress errors if a field is missing | `.field?` | +//! | `?` | Error suppression / optional operator | `.field?` | //! | `\|` | Pipe the output of one expression to another | `.foo \| .bar` | //! //! Segments can be chained without an explicit pipe: `.poses[].x` is equivalent to `.poses[] | .x`. @@ -53,7 +53,7 @@ impl Selector { /// /// `[.[].poses[].x]` is the actual query, we only require writing the `.poses[].x` portion. /// - /// Returns `None` if the expression was suppressed by an optional segment (e.g. `.field?`). + /// Returns `None` if the expression's error was suppressed (e.g. `.field?`). pub fn execute_per_row(&self, source: &ListArray) -> Result, Error> { runtime::execute_per_row(&self.0, source).map_err(Into::into) } @@ -144,7 +144,7 @@ where let mut field_path = path.clone(); field_path.push(Segment { kind: SegmentKind::Field(field.name().clone()), - optional: false, + suppressed: false, }); match field.data_type() { @@ -156,7 +156,7 @@ where // Add the Each segment to unwrap the list field_path.push(Segment { kind: SegmentKind::Each, - optional: false, + suppressed: false, }); match inner.data_type() { diff --git a/crates/store/re_arrow_combinators/src/selector/parser.rs b/crates/store/re_arrow_combinators/src/selector/parser.rs index 44abe66378d8..19e0894a5c53 100644 --- a/crates/store/re_arrow_combinators/src/selector/parser.rs +++ b/crates/store/re_arrow_combinators/src/selector/parser.rs @@ -8,11 +8,13 @@ //! Simplified jq-like grammar with implicit piping: //! //! ```text -//! Expr → Term ( ( '|' | ε ) Term )* -//! Term → FIELD '?'? -//! | DOT -//! | '[' INTEGER ']' '?'? -//! | '[' ']' +//! Expr → Term ( ( '|' | ε ) Term )* +//! Term → Segment+ +//! | DOT +//! Segment → Primary '?'? +//! Primary → FIELD +//! | '[' INTEGER ']' +//! | '[' ']' //! ``` //! //! `UPPERCASE` symbols denote terminals, and `ε` denotes end of input. @@ -48,13 +50,13 @@ impl std::fmt::Display for SegmentKind { #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Segment { pub kind: SegmentKind, - pub optional: bool, + pub suppressed: bool, } impl std::fmt::Display for Segment { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.kind)?; - if self.optional { + if self.suppressed { write!(f, "?")?; } Ok(()) @@ -175,7 +177,7 @@ where ) } - fn peek_optional(&mut self) -> bool { + fn peek_question_mark(&mut self) -> bool { if let Some(token) = self.tokens.peek() && token.typ == TokenType::QuestionMark { @@ -186,16 +188,18 @@ where } fn segment(&mut self) -> Result { + let kind = self.primary()?; + let suppressed = self.peek_question_mark(); + Ok(Segment { kind, suppressed }) + } + + fn primary(&mut self) -> Result { match self.tokens.peek() { Some(token) => match &token.typ { TokenType::Field(s) => { let result = s.clone(); self.tokens.next(); - let optional = self.peek_optional(); - Ok(Segment { - kind: SegmentKind::Field(result), - optional, - }) + Ok(SegmentKind::Field(result)) } TokenType::LBracket => { self.tokens.next(); // Consume `[` @@ -204,20 +208,13 @@ where Some(token) => match &token.typ { TokenType::RBracket => { self.tokens.next(); // Consume `]` - Ok(Segment { - kind: SegmentKind::Each, - optional: false, - }) + Ok(SegmentKind::Each) } TokenType::Integer(n) => { let index = *n; self.tokens.next(); self.consume(TokenType::RBracket)?; - let optional = self.peek_optional(); - Ok(Segment { - kind: SegmentKind::Index(index), - optional, - }) + Ok(SegmentKind::Index(index)) } unexpected => Err(Error::UnexpectedSymbol { symbol: unexpected.clone(), @@ -262,35 +259,42 @@ mod test { fn field(name: &str) -> Segment { Segment { kind: SegmentKind::Field(name.into()), - optional: false, + suppressed: false, } } fn field_opt(name: &str) -> Segment { Segment { kind: SegmentKind::Field(name.into()), - optional: true, + suppressed: true, } } fn index(n: u64) -> Segment { Segment { kind: SegmentKind::Index(n), - optional: false, + suppressed: false, } } fn index_opt(n: u64) -> Segment { Segment { kind: SegmentKind::Index(n), - optional: true, + suppressed: true, } } fn each() -> Segment { Segment { kind: SegmentKind::Each, - optional: false, + suppressed: false, + } + } + + fn each_opt() -> Segment { + Segment { + kind: SegmentKind::Each, + suppressed: true, } } @@ -424,9 +428,13 @@ mod test { } #[test] - fn optional_each_not_supported() { - // `?` after `[]` should be a parse error (unexpected symbol) - assert!(parse(".[]?").is_err()); + fn optional_each() { + assert_eq!(parse(".[]?"), Ok(path(vec![each_opt()]))); + assert_eq!(parse(".[]?.foo"), Ok(path(vec![each_opt(), field("foo")]))); + assert_eq!( + parse(".foo[]?.bar"), + Ok(path(vec![field("foo"), each_opt(), field("bar")])) + ); } #[test] @@ -440,5 +448,8 @@ mod test { // Note: leading `.` is consumed by the path parser, not stored in segments. let expr = parse(".[0]?").unwrap(); assert_eq!(expr.to_string(), "[0]?"); + + let expr = parse(".[]?").unwrap(); + assert_eq!(expr.to_string(), "[]?"); } } diff --git a/crates/store/re_arrow_combinators/src/selector/runtime.rs b/crates/store/re_arrow_combinators/src/selector/runtime.rs index baac1ff2d4e2..ae1e0d7f9792 100644 --- a/crates/store/re_arrow_combinators/src/selector/runtime.rs +++ b/crates/store/re_arrow_combinators/src/selector/runtime.rs @@ -15,7 +15,7 @@ use super::parser::{Expr, Segment, SegmentKind}; /// Executes the given expression against the source array. /// -/// Returns `None` if the expression was suppressed by an optional segment (e.g. `.field?`). +/// Returns `None` if the expression was suppressed (e.g. `.field?`). /// The caller decides how to handle the absent result. pub fn execute_per_row(expr: &Expr, source: &ListArray) -> Result, crate::Error> { // TODO(grtlr): It would be much cleaner if `MapList` (or equivalent would be called on this level). @@ -67,9 +67,9 @@ impl Segment { fn execute(&self, source: &ListArray) -> Result, crate::Error> { match self.kind.execute(source) { Ok(result) => Ok(Some(result)), - // TODO(RR-3435): FixedSizeListArray errors must be suppressed via optional, but ListArray should not need it. - Err(err) if self.optional => { - re_log::trace!("Optional segment `{self}` suppressed error: {err}"); + // TODO(RR-3435): FixedSizeListArray errors must be suppressed via `?`, but ListArray should not need it. + Err(err) if self.suppressed => { + re_log::trace!("Suppressed segment `{self}` suppressed error: {err}"); Ok(None) } Err(err) => Err(err), diff --git a/crates/store/re_arrow_combinators/tests/test_selector.rs b/crates/store/re_arrow_combinators/tests/test_selector.rs index b7de5f385011..31170b1063d6 100644 --- a/crates/store/re_arrow_combinators/tests/test_selector.rs +++ b/crates/store/re_arrow_combinators/tests/test_selector.rs @@ -38,6 +38,8 @@ fn execute_nested_struct() -> Result<(), Error> { │ [3.0, 5.0] │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ [null, 7.0] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [null, null] │ └───────────────────────────────────┘ "); @@ -324,6 +326,13 @@ fn execute_optional_field() -> Result<(), Error> { re_arrow_combinators::Error::FieldNotFound { .. } )) )); + let err = ".foo.x".parse::()?.execute_per_row(&array); + assert!(matches!( + err, + Err(Error::Runtime( + re_arrow_combinators::Error::FieldNotFound { .. } + )) + ),); // With `?`, the missing field is suppressed and we get `None` instead. let result = ".location.z?" @@ -331,6 +340,32 @@ fn execute_optional_field() -> Result<(), Error> { .execute_per_row(&array)?; assert!(result.is_none(), "optional segment should return None"); + let result = ".foo?.x".parse::()?.execute_per_row(&array)?; + + assert!(result.is_none(), "optional segment should return None"); + + Ok(()) +} + +#[test] +fn execute_optional_each_suppressed() -> Result<(), Error> { + let array = fixtures::nested_struct_column(); + + // Without `?`, `[]` on a struct (non-list) inner type errors. + let err = ".[]".parse::()?.execute_per_row(&array); + assert!(matches!( + err, + Err(Error::Runtime( + re_arrow_combinators::Error::TypeMismatch { .. } + )) + )); + + // With `?`, the error is suppressed and we get `None`. + let result = ".[]?".parse::()?.execute_per_row(&array)?; + assert!( + result.is_none(), + "optional each should return None on non-list inner type" + ); Ok(()) } diff --git a/crates/store/re_arrow_combinators/tests/test_transform.rs b/crates/store/re_arrow_combinators/tests/test_transform.rs index 860e43307cef..1fb00403bbc8 100644 --- a/crates/store/re_arrow_combinators/tests/test_transform.rs +++ b/crates/store/re_arrow_combinators/tests/test_transform.rs @@ -7,7 +7,7 @@ use re_arrow_combinators::Selector; use re_arrow_combinators::Transform as _; use re_arrow_combinators::cast::{ListToFixedSizeList, PrimitiveCast}; use re_arrow_combinators::map::{MapFixedSizeList, MapList, MapPrimitive, ReplaceNull}; -use re_arrow_combinators::reshape::{RowMajorToColumnMajor, StructToFixedList}; +use re_arrow_combinators::reshape::{PromoteInnerNulls, RowMajorToColumnMajor, StructToFixedList}; use util::DisplayRB; use crate::util::fixtures; @@ -444,3 +444,61 @@ fn test_map_list_outer_nullability_identity() { └─────────────────────────┘ "); } + +#[test] +fn test_promote_inner_nulls_nested_struct() { + let array = fixtures::nested_struct_column(); + + let location = Selector::from_str(".location") + .unwrap() + .transform(&array) + .unwrap(); + + // Before: row 1 is `[null]` and row 5 is `[null, null]` + insta::assert_snapshot!(format!("{}", DisplayRB(location.clone())), @" + ┌─────────────────────────────────────────┐ + │ col │ + │ --- │ + │ type: nullable List[nullable Struct[2]] │ + ╞═════════════════════════════════════════╡ + │ [{x: 1.0, y: 2.0}] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [null] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ null │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [{x: 3.0, y: 4.0}, {x: 5.0, y: 6.0}] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [null, {x: 7.0, y: 8.0}] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [null, null] │ + └─────────────────────────────────────────┘ + "); + + // After: row 1 `[null]` and row 6 `[null, null]` are promoted to outer `null`s + let result = PromoteInnerNulls.transform(&location).unwrap(); + + insta::assert_snapshot!(format!("{}", DisplayRB(result)), @" + ┌─────────────────────────────────────────┐ + │ col │ + │ --- │ + │ type: nullable List[nullable Struct[2]] │ + ╞═════════════════════════════════════════╡ + │ [{x: 1.0, y: 2.0}] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ null │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ null │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [{x: 3.0, y: 4.0}, {x: 5.0, y: 6.0}] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [null, {x: 7.0, y: 8.0}] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ null │ + └─────────────────────────────────────────┘ + "); +} diff --git a/crates/store/re_arrow_combinators/tests/util.rs b/crates/store/re_arrow_combinators/tests/util.rs index eb3ad56b90dc..558853dbd500 100644 --- a/crates/store/re_arrow_combinators/tests/util.rs +++ b/crates/store/re_arrow_combinators/tests/util.rs @@ -62,6 +62,8 @@ pub mod fixtures { │ [{location: {x: 3.0, y: 4.0}}, {location: {x: 5.0, y: 6.0}}] │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ [null, {location: {x: 7.0, y: 8.0}}] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [{location: null}, {location: null}] │ └──────────────────────────────────────────────────────────────┘ "); } @@ -75,27 +77,28 @@ pub mod fixtures { Field::new("y", DataType::Float64, true), ]); - let x_nulls = NullBuffer::from(vec![true, false, true, true, false, true]); - let y_nulls = NullBuffer::from(vec![true, false, true, true, false, true]); + let x_nulls = NullBuffer::from(vec![true, false, true, true, false, true, false, false]); + let y_nulls = NullBuffer::from(vec![true, false, true, true, false, true, false, false]); let x_data = ArrayData::builder(DataType::Float64) - .len(6) + .len(8) .null_bit_buffer(Some(x_nulls.buffer().clone())) - .add_buffer(values_buffer.slice_with_length(0, 48)) + .add_buffer(values_buffer.slice_with_length(0, 64)) .build() .unwrap(); let y_data = ArrayData::builder(DataType::Float64) - .len(6) + .len(8) .null_bit_buffer(Some(y_nulls.buffer().clone())) - .add_buffer(values_buffer.slice_with_length(48, 48)) + .add_buffer(values_buffer.slice_with_length(48, 64)) .build() .unwrap(); let x_array = Float64Array::from(x_data); let y_array = Float64Array::from(y_data); - let inner_struct_nulls = NullBuffer::from(vec![true, false, true, true, false, true]); + let inner_struct_nulls = + NullBuffer::from(vec![true, false, true, true, false, true, false, false]); let inner_struct = StructArray::new( inner_struct_fields.clone(), vec![Arc::new(x_array), Arc::new(y_array)], @@ -108,16 +111,17 @@ pub mod fixtures { true, )]); - let outer_struct_nulls = NullBuffer::from(vec![true, true, true, true, false, true]); + let outer_struct_nulls = + NullBuffer::from(vec![true, true, true, true, false, true, true, true]); let outer_struct = StructArray::new( outer_struct_fields, vec![Arc::new(inner_struct)], Some(outer_struct_nulls), ); - let list_offsets = OffsetBuffer::from_lengths([1, 1, 0, 0, 2, 2]); + let list_offsets = OffsetBuffer::from_lengths([1, 1, 0, 0, 2, 2, 2]); - let list_nulls = NullBuffer::from(vec![true, true, true, false, true, true]); + let list_nulls = NullBuffer::from(vec![true, true, true, false, true, true, true]); ListArray::new( Arc::new(Field::new_list_field( diff --git a/crates/store/re_lenses/Cargo.toml b/crates/store/re_lenses/Cargo.toml index b837c734d17b..cc2be209f3fc 100644 --- a/crates/store/re_lenses/Cargo.toml +++ b/crates/store/re_lenses/Cargo.toml @@ -21,6 +21,7 @@ all-features = true re_arrow_combinators.workspace = true re_arrow_util.workspace = true re_chunk.workspace = true +re_log.workspace = true re_log_types.workspace = true re_sdk_types.workspace = true diff --git a/crates/store/re_lenses/src/ast.rs b/crates/store/re_lenses/src/ast.rs index 328d0b92fd0d..243e41830340 100644 --- a/crates/store/re_lenses/src/ast.rs +++ b/crates/store/re_lenses/src/ast.rs @@ -144,6 +144,9 @@ pub enum Op { /// Converts timestamp structs with `seconds` and `nanos` fields to total nanoseconds. TimeSpecToNanos, + /// Promotes lists where all inner values are null to outer nulls. + PromoteInnerNulls, + /// A user-defined arbitrary function to convert a component column. Func(CustomFn), } @@ -164,6 +167,7 @@ impl std::fmt::Debug for Op { f.debug_tuple("StringSuffixNonEmpty").field(suffix).finish() } Self::TimeSpecToNanos => f.debug_struct("TimeSpecToNanos").finish(), + Self::PromoteInnerNulls => f.debug_struct("PromoteInnerNulls").finish(), Self::Func(_) => f.debug_tuple("Func").field(&"").finish(), } } @@ -241,6 +245,11 @@ impl Op { Self::TimeSpecToNanos } + /// Promotes lists where all inner values are null to outer nulls. + pub fn promote_inner_nulls() -> Self { + Self::PromoteInnerNulls + } + /// A user-defined arbitrary function to convert a component column. pub fn func(func: F) -> Self where @@ -251,41 +260,52 @@ impl Op { } impl Op { - fn call(&self, list_array: &ListArray) -> Result { + fn call(&self, list_array: &ListArray) -> Result, OpError> { match self { Self::Selector(query) => { let selector = Selector::from_str(query)?; - selector.transform(list_array).map_err(Into::into) + Ok(selector.execute_per_row(list_array)?) } - Self::Cast(op) => op.call(list_array), + Self::Cast(op) => op.call(list_array).map(Some), Self::BinaryToListUInt8 => map::MapList::new(semantic::BinaryToListUInt8::::new()) .transform(list_array) - .map_err(Into::into), + .map_err(Into::into) + .map(Some), Self::StringToVideoCodecUInt32 => { map::MapList::new(semantic::StringToVideoCodecUInt32::default()) .transform(list_array) .map_err(Into::into) + .map(Some) } Self::StringPrefix(prefix) => map::MapList::new(map::StringPrefix::new(prefix.clone())) .transform(list_array) - .map_err(Into::into), + .map_err(Into::into) + .map(Some), Self::StringPrefixNonEmpty(prefix) => map::MapList::new( map::StringPrefix::new(prefix.clone()).with_prefix_empty_string(false), ) .transform(list_array) - .map_err(Into::into), + .map_err(Into::into) + .map(Some), Self::StringSuffix(suffix) => map::MapList::new(map::StringSuffix::new(suffix.clone())) .transform(list_array) - .map_err(Into::into), + .map_err(Into::into) + .map(Some), Self::StringSuffixNonEmpty(suffix) => map::MapList::new( map::StringSuffix::new(suffix.clone()).with_suffix_empty_string(false), ) .transform(list_array) - .map_err(Into::into), + .map_err(Into::into) + .map(Some), Self::TimeSpecToNanos => map::MapList::new(semantic::TimeSpecToNanos::default()) .transform(list_array) - .map_err(Into::into), - Self::Func(func) => func(list_array), + .map_err(Into::into) + .map(Some), + Self::PromoteInnerNulls => reshape::PromoteInnerNulls + .transform(list_array) + .map_err(Into::into) + .map(Some), + Self::Func(func) => func(list_array).map(Some), } } } @@ -366,21 +386,38 @@ impl PartialChunk { } } -fn apply_ops(initial: ListArray, ops: &[Op]) -> Result { - ops.iter().try_fold(initial, |array, op| op.call(&array)) +/// Sequentially applies a list of [`Op`]s to a list array. +/// +/// Exits early if an evaluation of an [`Op`] yields `None`, skipping subsequent ops. +fn apply_ops(initial: ListArray, ops: &[Op]) -> Result, OpError> { + let mut current = initial; + for op in ops { + match op.call(¤t)? { + Some(next) => current = next, + None => return Ok(None), + } + } + Ok(Some(current)) } fn collect_output_components_iter<'a>( input: &'a SerializedComponentColumn, components: &'a [ComponentOutput], ) -> impl Iterator> + 'a { - components.iter().map( + components.iter().filter_map( |output| match apply_ops(input.list_array.clone(), &output.ops) { - Ok(list_array) => Ok((output.component_descr.clone(), list_array)), - Err(source) => Err(LensError::ComponentOperationFailed { + Ok(Some(list_array)) => Some(Ok((output.component_descr.clone(), list_array))), + Ok(None) => { + re_log::debug_once!( + "Lens suppressed for component `{}`", + output.component_descr.component + ); + None + } + Err(source) => Some(Err(LensError::ComponentOperationFailed { component: output.component_descr.component, source: Box::new(source), - }), + })), }, ) } @@ -389,13 +426,17 @@ fn collect_output_times_iter<'a>( input: &'a SerializedComponentColumn, timelines: &'a [TimeOutput], ) -> impl Iterator> + 'a { - timelines.iter().map( + timelines.iter().filter_map( |time| match apply_ops(input.list_array.clone(), &time.ops) { - Ok(list_array) => Ok((time.timeline_name, time.timeline_type, list_array)), - Err(source) => Err(LensError::TimeOperationFailed { + Ok(Some(list_array)) => Some(Ok((time.timeline_name, time.timeline_type, list_array))), + Ok(None) => { + re_log::debug_once!("Lens suppressed for timeline `{}`", time.timeline_name); + None + } + Err(source) => Some(Err(LensError::TimeOperationFailed { timeline_name: time.timeline_name, source: Box::new(source), - }), + })), }, ) } From 5b3b2bac4d8b6d5330148223ad36a2ee10feaf5b Mon Sep 17 00:00:00 2001 From: Antoine Beyeler <49431240+abey79@users.noreply.github.com> Date: Mon, 2 Mar 2026 16:05:19 +0100 Subject: [PATCH 008/513] Add micro-benchmarks for the `rr.log` pipeline ### Related - inspired by https://github.com/rerun-io/rerun/pull/12656 ### What Add micro-benchmarks for the various layers of a `rr.log` call. Also cleaned-up a few pytest-benchmark related things and added some docs on how to run stuff. Source-Ref: c3489dfe43cdd201e8b505cb072c7cd0bef8dc14 --- .gitignore | 3 + pixi.toml | 4 +- tests/python/log_benchmark/README.md | 79 +++++++++++++++++++ .../log_benchmark/test_log_benchmark.py | 36 +-------- .../log_benchmark/test_micro_benchmark.py | 76 ++++++++++++++++++ 5 files changed, 162 insertions(+), 36 deletions(-) create mode 100644 tests/python/log_benchmark/README.md create mode 100644 tests/python/log_benchmark/test_micro_benchmark.py diff --git a/.gitignore b/.gitignore index 1172a7186cd7..567f0ebb6d89 100644 --- a/.gitignore +++ b/.gitignore @@ -100,3 +100,6 @@ justfile # Lychee link checker cache .lycheecache + +# Saved pytest-benchmark results +/.benchmarks \ No newline at end of file diff --git a/pixi.toml b/pixi.toml index 68c61860694e..eec827c08686 100644 --- a/pixi.toml +++ b/pixi.toml @@ -423,7 +423,9 @@ py-build-perf-release = { cmd = "uv run maturin develop --uv --release --manifes py-test = { cmd = "uvpy -m pytest -vv rerun_py/tests", depends-on = ["py-build"] } py-test-no-build = { cmd = "uvpy -m pytest -vv rerun_py/tests" } -py-bench = { cmd = "uvpy -m pytest --benchmark-only", depends-on = ["py-build-release"] } +py-bench = { cmd = "uvpy -m pytest --benchmark-only --benchmark-group-by=func --benchmark-sort=fullname", depends-on = [ + "py-build-release", +] } snapshots = "uvpy scripts/snapshots.py" diff --git a/tests/python/log_benchmark/README.md b/tests/python/log_benchmark/README.md new file mode 100644 index 000000000000..03962c9834d4 --- /dev/null +++ b/tests/python/log_benchmark/README.md @@ -0,0 +1,79 @@ +# Python SDK logging benchmarks + +Manual performance benchmarks for the Rerun Python SDK logging pipeline. +These are **not** run in CI — they are intended for local profiling and regression checks. + +## Running benchmarks + +From the `rerun/` directory: + +```bash +# Run all benchmarks: +pixi run py-bench + +# Run only throughput benchmarks: +pixi run py-bench -k "not micro" + +# Run only micro-benchmarks: +pixi run py-bench -k micro + +# Run a specific benchmark: +pixi run py-bench -k "micro_log-Points3D" +``` + +## Running standalone (for profiling) + +Enter the pixi shell first: + +```bash +pixi shell +``` + +Then run a benchmark directly: + +```bash +# Run the throughput benchmark standalone: +uvpy -m tests.python.log_benchmark.test_log_benchmark transform3d + +# With options: +uvpy -m tests.python.log_benchmark.test_log_benchmark transform3d --num-entities 10 --num-time-steps 10000 --static + +# Connect to a running Rerun viewer (start `rerun` first): +uvpy -m tests.python.log_benchmark.test_log_benchmark transform3d --connect +``` + +### Profiling with py-spy + +```bash +# Generate a flamegraph (on Linux, add --native for native stack traces): +sudo PYTHONPATH=rerun_py/rerun_sdk:rerun_py py-spy record -o flamegraph.svg -- \ + .venv/bin/python -m tests.python.log_benchmark.test_log_benchmark transform3d + +# Then open flamegraph.svg in a browser +``` + +## Comparing benchmark runs + +Use `--benchmark-save` to save benchmark results: + +```bash +# Save a baseline on the current branch: +pixi run py-bench -k micro --benchmark-save=before + +# Make changes, rebuild, then save again: +pixi run py-bench -k micro --benchmark-save=after +``` + +Saved results are stored in `.benchmarks/` under the project root and are automatically numbered, e.g. `0001_before` and `0002_after`. + +You can then compare using the `pytest-benchmark` CLI: +``` +uv run pytest-benchmark compare 0001 0002 +``` + + +## Test files + +- `__init__.py` — Shared data classes (`Point3DInput`, `Transform3DInput`) +- `test_log_benchmark.py` — Throughput benchmarks +- `test_micro_benchmark.py` — Per-call overhead micro-benchmarks diff --git a/tests/python/log_benchmark/test_log_benchmark.py b/tests/python/log_benchmark/test_log_benchmark.py index fd2d4226b0cb..ab472916ab80 100644 --- a/tests/python/log_benchmark/test_log_benchmark.py +++ b/tests/python/log_benchmark/test_log_benchmark.py @@ -1,38 +1,4 @@ -""" -Python logging benchmarks. - -Running benchmarks with pytest-benchmark ----------------------------------------- -From the `rerun/` directory: - - # Run all benchmarks: - pixi run py-bench - - # Run a specific benchmark: - pixi run py-bench -- -k transform3d_translation_mat3x3 - -Running standalone (for profiling) ----------------------------------- -From the `rerun/` directory, first enter the pixi shell: - - pixi shell - -Then run the benchmark: - - # Run directly: - uvpy -m tests.python.log_benchmark.test_log_benchmark transform3d - - # With options: - uvpy -m tests.python.log_benchmark.test_log_benchmark transform3d --num-entities 10 --num-time-steps 10000 --static - - # Connect to a running Rerun viewer (start `rerun` first): - uvpy -m tests.python.log_benchmark.test_log_benchmark transform3d --connect - - # With py-spy flamegraph (on Linux, add --native for native stack traces): - sudo PYTHONPATH=rerun_py/rerun_sdk:rerun_py py-spy record -o flamegraph.svg -- .venv/bin/python -m tests.python.log_benchmark.test_log_benchmark transform3d - - # Then open flamegraph.svg in a browser -""" +"""Python SDK logging throughput benchmarks. See README.md for usage.""" from __future__ import annotations diff --git a/tests/python/log_benchmark/test_micro_benchmark.py b/tests/python/log_benchmark/test_micro_benchmark.py new file mode 100644 index 000000000000..fa606421c89b --- /dev/null +++ b/tests/python/log_benchmark/test_micro_benchmark.py @@ -0,0 +1,76 @@ +"""Per-call overhead micro-benchmarks for the rr.log() pipeline. + +See README.md for usage. +""" + +from __future__ import annotations + +from typing import Any + +import numpy as np +import pytest +import rerun as rr +import rerun_bindings as bindings +from rerun._log import _log_components + +ARCHETYPE_CASES = [ + pytest.param(lambda: rr.Scalars(42.0), id="Scalars"), + pytest.param( + lambda: rr.Points3D([[1, 2, 3]], colors=[0xFF0000FF], radii=[0.1]), + id="Points3D", + ), + pytest.param( + lambda: rr.Transform3D(translation=[1, 2, 3], mat3x3=np.eye(3, dtype=np.float32)), + id="Transform3D", + ), + pytest.param( + lambda: rr.Boxes3D(half_sizes=[[1, 2, 3]], colors=[0xFF0000FF]), + id="Boxes3D", + ), +] + + +def _init() -> None: + """Common setup: init rerun + memory recording.""" + rr.init("rerun_example_micro_benchmark", spawn=False) + rr.memory_recording() + + +@pytest.mark.parametrize("make_archetype", ARCHETYPE_CASES) +def test_bench_micro_construct(benchmark: Any, make_archetype: Any) -> None: + _init() + benchmark(make_archetype) + + +@pytest.mark.parametrize("make_archetype", ARCHETYPE_CASES) +def test_bench_micro_as_component_batches(benchmark: Any, make_archetype: Any) -> None: + _init() + archetype = make_archetype() + benchmark(archetype.as_component_batches) + + +@pytest.mark.parametrize("make_archetype", ARCHETYPE_CASES) +def test_bench_micro_log_components(benchmark: Any, make_archetype: Any) -> None: + _init() + batches = make_archetype().as_component_batches() + benchmark(_log_components, "test_entity", batches) + + +@pytest.mark.parametrize("make_archetype", ARCHETYPE_CASES) +def test_bench_micro_log_arrow_msg(benchmark: Any, make_archetype: Any) -> None: + _init() + batches = make_archetype().as_component_batches() + instanced = {b.component_descriptor(): b.as_arrow_array() for b in batches if b.as_arrow_array() is not None} + benchmark(bindings.log_arrow_msg, "test_entity", components=instanced, static_=False, recording=None) + + +@pytest.mark.parametrize("make_archetype", ARCHETYPE_CASES) +def test_bench_micro_log(benchmark: Any, make_archetype: Any) -> None: + _init() + archetype = make_archetype() + benchmark(rr.log, "test_entity", archetype) + + +def test_bench_micro_set_time(benchmark: Any) -> None: + _init() + benchmark(rr.set_time, "frame", sequence=42) From d3c06193f5d6648c1475952e9cb523d34de35058 Mon Sep 17 00:00:00 2001 From: Isse Date: Mon, 2 Mar 2026 16:10:57 +0100 Subject: [PATCH 009/513] Store weaks in cache instead of owned arcs ### Related - Part of RR-3800 ### What Caches shouldn't own chunks, and instead we should let the chunk store control when they're dropped. Source-Ref: 3dea821047daef7e64457d0143edd59101cbf7cb --- .../re_time_panel/src/data_density_graph.rs | 8 ++--- ...ecursive_chunks_per_timeline_subscriber.rs | 29 ++++++++++++++++--- 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/crates/viewer/re_time_panel/src/data_density_graph.rs b/crates/viewer/re_time_panel/src/data_density_graph.rs index d73fbe076ae5..6eff0ae7d80b 100644 --- a/crates/viewer/re_time_panel/src/data_density_graph.rs +++ b/crates/viewer/re_time_panel/src/data_density_graph.rs @@ -645,12 +645,8 @@ pub fn build_density_graph<'a>( ( info.recursive_chunks_info .values() - .map(|info| { - ( - info.chunk.clone(), - info.resolved_time_range, - info.num_events, - ) + .filter_map(|info| { + Some((info.chunk()?, info.resolved_time_range, info.num_events)) }) .collect(), info.total_num_events, diff --git a/crates/viewer/re_time_panel/src/recursive_chunks_per_timeline_subscriber.rs b/crates/viewer/re_time_panel/src/recursive_chunks_per_timeline_subscriber.rs index 2d4111deb206..a327ce51b95b 100644 --- a/crates/viewer/re_time_panel/src/recursive_chunks_per_timeline_subscriber.rs +++ b/crates/viewer/re_time_panel/src/recursive_chunks_per_timeline_subscriber.rs @@ -1,4 +1,4 @@ -use std::sync::{Arc, OnceLock}; +use std::sync::{Arc, OnceLock, Weak}; use egui::ahash::HashMap; use nohash_hasher::IntMap; @@ -11,15 +11,36 @@ use re_log_types::{AbsoluteTimeRange, EntityPath, EntityPathHash, StoreId, Timel /// Cached information about a chunk in the context of a given timeline. #[derive(Debug, Clone)] pub struct ChunkTimelineInfo { - pub chunk: Arc, + chunk: Weak, pub num_events: u64, pub resolved_time_range: AbsoluteTimeRange, } +impl ChunkTimelineInfo { + pub fn chunk(&self) -> Option> { + let chunk = self.chunk.upgrade(); + + if chunk.is_none() { + re_log::debug_warn_once!( + "`recursive_chunks_for_entity_and_timeline` tracking dropped chunk" + ); + } + + chunk + } +} + #[cfg(test)] impl PartialEq for ChunkTimelineInfo { fn eq(&self, other: &Self) -> bool { - self.chunk.id() == other.chunk.id() + let Some(chunk) = self.chunk() else { + return false; + }; + let Some(other_chunk) = other.chunk() else { + return false; + }; + + chunk.id() == other_chunk.id() && self.num_events == other.num_events && self.resolved_time_range == other.resolved_time_range } @@ -84,7 +105,7 @@ impl PathRecursiveChunksPerTimelineStoreSubscriber { .or_default(); let chunk_info = ChunkTimelineInfo { - chunk: chunk.clone(), + chunk: Arc::downgrade(chunk), // TODO(andreas): Would `num_events_cumulative_per_unique_time` be more appropriate? num_events: chunk.num_events_cumulative(), resolved_time_range: time_column.time_range(), From 2f791adb8b5f370703178c6718d44317d7efd3a3 Mon Sep 17 00:00:00 2001 From: Andreas Reich Date: Mon, 2 Mar 2026 16:16:27 +0100 Subject: [PATCH 010/513] Make many more required components ui editable ### Related * Came up while working on RR-3837 * but is kinda unrelated :) ### What Ui editability is opt-out rather than opt-in now. https://github.com/user-attachments/assets/5bf52bde-aade-468f-8657-38369d58f888 For full list of what is now editable which wasn't before, check changes to reflection.rs Also added two missing component ui editors I noticed while testing out stuff Source-Ref: 0bb8c820ebd350a10efff0f94f01f7c9240d32d4 --- .../src/codegen/rust/reflection.rs | 21 +-------- crates/build/re_types_builder/src/lib.rs | 2 +- .../rerun/archetypes/annotation_context.fbs | 2 +- .../definitions/rerun/archetypes/asset3d.fbs | 2 +- .../rerun/archetypes/asset_video.fbs | 2 +- .../rerun/archetypes/bar_chart.fbs | 2 +- .../definitions/rerun/archetypes/clear.fbs | 2 +- .../rerun/archetypes/coordinate_frame.fbs | 2 +- .../rerun/archetypes/depth_image.fbs | 4 +- .../rerun/archetypes/encoded_depth_image.fbs | 2 +- .../rerun/archetypes/encoded_image.fbs | 2 +- .../rerun/archetypes/geo_line_strings.fbs | 2 +- .../rerun/archetypes/graph_edges.fbs | 2 +- .../rerun/archetypes/graph_nodes.fbs | 2 +- .../definitions/rerun/archetypes/image.fbs | 4 +- .../rerun/archetypes/instance_poses3d.fbs | 10 ++--- .../rerun/archetypes/line_strips2d.fbs | 2 +- .../rerun/archetypes/line_strips3d.fbs | 2 +- .../rerun/archetypes/mcap_channel.fbs | 6 +-- .../rerun/archetypes/mcap_message.fbs | 2 +- .../rerun/archetypes/mcap_schema.fbs | 8 ++-- .../rerun/archetypes/mcap_statistics.fbs | 16 +++---- .../definitions/rerun/archetypes/mesh3d.fbs | 2 +- .../definitions/rerun/archetypes/pinhole.fbs | 10 ++--- .../definitions/rerun/archetypes/scalars.fbs | 2 +- .../rerun/archetypes/segmentation_image.fbs | 4 +- .../rerun/archetypes/series_points.fbs | 2 +- .../definitions/rerun/archetypes/tensor.fbs | 2 +- .../rerun/archetypes/transform3d.fbs | 16 +++---- .../rerun/archetypes/transform_axes3d.fbs | 2 +- .../rerun/archetypes/video_stream.fbs | 2 +- .../rerun/archetypes/view_coordinates.fbs | 2 +- .../definitions/rerun/attributes.fbs | 10 ++--- .../store/re_sdk_types/src/reflection/mod.rs | 44 +++++++++---------- crates/store/re_types_core/src/reflection.rs | 2 +- .../re_component_ui/src/datatype_uis/mod.rs | 4 +- .../re_component_ui/src/datatype_uis/vec.rs | 21 +++++++-- crates/viewer/re_component_ui/src/lib.rs | 22 +++++----- crates/viewer/re_component_ui/src/plane3d.rs | 5 ++- .../add_entity_to_view_bar_chart_2.png | 4 +- .../add_entity_to_view_text_log_1.png | 4 +- .../tests/snapshots/source_component_7.png | 4 +- 42 files changed, 130 insertions(+), 133 deletions(-) diff --git a/crates/build/re_types_builder/src/codegen/rust/reflection.rs b/crates/build/re_types_builder/src/codegen/rust/reflection.rs index d118aaa44818..7809d476fa69 100644 --- a/crates/build/re_types_builder/src/codegen/rust/reflection.rs +++ b/crates/build/re_types_builder/src/codegen/rust/reflection.rs @@ -8,7 +8,7 @@ use quote::{format_ident, quote}; use super::util::{append_tokens, doc_as_lines}; use crate::codegen::{Target, autogen_warning}; use crate::{ - ATTR_RERUN_COMPONENT_REQUIRED, ATTR_RERUN_COMPONENT_UI_EDITABLE, ATTR_RUST_DERIVE, + ATTR_RERUN_COMPONENT_NO_UI_EDIT, ATTR_RERUN_COMPONENT_REQUIRED, ATTR_RUST_DERIVE, ATTR_RUST_DERIVE_ONLY, ObjectKind, Objects, Reporter, }; @@ -218,24 +218,7 @@ fn generate_archetype_reflection(reporter: &Reporter, objects: &Objects) -> Toke ) .join("\n"); let required = field.attrs.has(ATTR_RERUN_COMPONENT_REQUIRED); - let ui_editable = match field - .try_get_attr::(ATTR_RERUN_COMPONENT_UI_EDITABLE) - .as_deref() - { - Some("true") => true, - Some("false") => false, - Some(value) => { - reporter.error( - &field.virtpath, - &field.fqname, - format!( - "Invalid value for {ATTR_RERUN_COMPONENT_UI_EDITABLE}: {value:?}. Expected \"true\" or \"false\"." - ), - ); - !required - } - None => !required, - }; + let ui_editable = !field.attrs.has(ATTR_RERUN_COMPONENT_NO_UI_EDIT); let mut flag_tokens: Vec = Vec::new(); if required { diff --git a/crates/build/re_types_builder/src/lib.rs b/crates/build/re_types_builder/src/lib.rs index 73f452ec323a..ee3c05109d51 100644 --- a/crates/build/re_types_builder/src/lib.rs +++ b/crates/build/re_types_builder/src/lib.rs @@ -178,7 +178,7 @@ pub const ATTR_ARROW_SPARSE_UNION: &str = "attr.arrow.sparse_union"; pub const ATTR_RERUN_COMPONENT_OPTIONAL: &str = "attr.rerun.component_optional"; pub const ATTR_RERUN_COMPONENT_RECOMMENDED: &str = "attr.rerun.component_recommended"; pub const ATTR_RERUN_COMPONENT_REQUIRED: &str = "attr.rerun.component_required"; -pub const ATTR_RERUN_COMPONENT_UI_EDITABLE: &str = "attr.rerun.component_ui_editable"; +pub const ATTR_RERUN_COMPONENT_NO_UI_EDIT: &str = "attr.rerun.component_no_ui_edit"; pub const ATTR_RERUN_OVERRIDE_TYPE: &str = "attr.rerun.override_type"; pub const ATTR_RERUN_SCOPE: &str = "attr.rerun.scope"; pub const ATTR_RERUN_VIEW_IDENTIFIER: &str = "attr.rerun.view_identifier"; diff --git a/crates/store/re_sdk_types/definitions/rerun/archetypes/annotation_context.fbs b/crates/store/re_sdk_types/definitions/rerun/archetypes/annotation_context.fbs index 90a6553b0238..4673ac5e0562 100644 --- a/crates/store/re_sdk_types/definitions/rerun/archetypes/annotation_context.fbs +++ b/crates/store/re_sdk_types/definitions/rerun/archetypes/annotation_context.fbs @@ -21,5 +21,5 @@ table AnnotationContext ( "attr.rust.derive": "PartialEq" ) { /// List of class descriptions, mapping class indices to class names, colors etc. - context: rerun.components.AnnotationContext ("attr.rerun.component_required", order: 1000); + context: rerun.components.AnnotationContext ("attr.rerun.component_required", "attr.rerun.component_no_ui_edit", order: 1000); } diff --git a/crates/store/re_sdk_types/definitions/rerun/archetypes/asset3d.fbs b/crates/store/re_sdk_types/definitions/rerun/archetypes/asset3d.fbs index 6e3f6899693c..d812b93065fe 100644 --- a/crates/store/re_sdk_types/definitions/rerun/archetypes/asset3d.fbs +++ b/crates/store/re_sdk_types/definitions/rerun/archetypes/asset3d.fbs @@ -20,7 +20,7 @@ table Asset3D ( // --- Required --- /// The asset's bytes. - blob: rerun.components.Blob ("attr.rerun.component_required", order: 1000); + blob: rerun.components.Blob ("attr.rerun.component_required", "attr.rerun.component_no_ui_edit", order: 1000); // --- Recommended --- diff --git a/crates/store/re_sdk_types/definitions/rerun/archetypes/asset_video.fbs b/crates/store/re_sdk_types/definitions/rerun/archetypes/asset_video.fbs index 08ecc68ae8a0..e6624aa31734 100644 --- a/crates/store/re_sdk_types/definitions/rerun/archetypes/asset_video.fbs +++ b/crates/store/re_sdk_types/definitions/rerun/archetypes/asset_video.fbs @@ -20,7 +20,7 @@ table AssetVideo ( // --- Required --- /// The asset's bytes. - blob: rerun.components.Blob ("attr.rerun.component_required", order: 1000); + blob: rerun.components.Blob ("attr.rerun.component_required", "attr.rerun.component_no_ui_edit", order: 1000); // --- Recommended --- diff --git a/crates/store/re_sdk_types/definitions/rerun/archetypes/bar_chart.fbs b/crates/store/re_sdk_types/definitions/rerun/archetypes/bar_chart.fbs index 95702ca1d563..f5b168a3d098 100644 --- a/crates/store/re_sdk_types/definitions/rerun/archetypes/bar_chart.fbs +++ b/crates/store/re_sdk_types/definitions/rerun/archetypes/bar_chart.fbs @@ -17,7 +17,7 @@ table BarChart ( // --- Required --- /// The values. Should always be a 1-dimensional tensor (i.e. a vector). - values: rerun.components.TensorData ("attr.rerun.component_required", order: 1000); + values: rerun.components.TensorData ("attr.rerun.component_required", "attr.rerun.component_no_ui_edit", order: 1000); // --- Optional --- diff --git a/crates/store/re_sdk_types/definitions/rerun/archetypes/clear.fbs b/crates/store/re_sdk_types/definitions/rerun/archetypes/clear.fbs index 851dbe1cb7ea..c271b382b24d 100644 --- a/crates/store/re_sdk_types/definitions/rerun/archetypes/clear.fbs +++ b/crates/store/re_sdk_types/definitions/rerun/archetypes/clear.fbs @@ -24,5 +24,5 @@ table Clear ( "attr.rust.derive": "PartialEq", "attr.rust.override_crate": "re_types_core" ) { - is_recursive: rerun.components.ClearIsRecursive ("attr.rerun.component_required", order: 100); + is_recursive: rerun.components.ClearIsRecursive ("attr.rerun.component_required", "attr.rerun.component_no_ui_edit", order: 100); } diff --git a/crates/store/re_sdk_types/definitions/rerun/archetypes/coordinate_frame.fbs b/crates/store/re_sdk_types/definitions/rerun/archetypes/coordinate_frame.fbs index d38dadfbafa1..050aa955a60f 100644 --- a/crates/store/re_sdk_types/definitions/rerun/archetypes/coordinate_frame.fbs +++ b/crates/store/re_sdk_types/definitions/rerun/archetypes/coordinate_frame.fbs @@ -18,5 +18,5 @@ table CoordinateFrame ( "attr.rust.derive": "PartialEq" ) { /// The coordinate frame to use for the current entity. - frame: rerun.components.TransformFrameId ("attr.rerun.component_required", order: 1000); + frame: rerun.components.TransformFrameId ("attr.rerun.component_required", "attr.rerun.component_no_ui_edit", order: 1000); } diff --git a/crates/store/re_sdk_types/definitions/rerun/archetypes/depth_image.fbs b/crates/store/re_sdk_types/definitions/rerun/archetypes/depth_image.fbs index 7deb1fdd3207..4757c4f73414 100644 --- a/crates/store/re_sdk_types/definitions/rerun/archetypes/depth_image.fbs +++ b/crates/store/re_sdk_types/definitions/rerun/archetypes/depth_image.fbs @@ -22,10 +22,10 @@ table DepthImage ( // --- Required --- /// The raw depth image data. - buffer: rerun.components.ImageBuffer ("attr.rerun.component_required", order: 1000); + buffer: rerun.components.ImageBuffer ("attr.rerun.component_required", "attr.rerun.component_no_ui_edit", order: 1000); /// The format of the image. - format: rerun.components.ImageFormat ("attr.rerun.component_required", order: 1100); + format: rerun.components.ImageFormat ("attr.rerun.component_required", "attr.rerun.component_no_ui_edit", order: 1100); // --- Optional --- diff --git a/crates/store/re_sdk_types/definitions/rerun/archetypes/encoded_depth_image.fbs b/crates/store/re_sdk_types/definitions/rerun/archetypes/encoded_depth_image.fbs index f642f4b7eba7..a631eb05c682 100644 --- a/crates/store/re_sdk_types/definitions/rerun/archetypes/encoded_depth_image.fbs +++ b/crates/store/re_sdk_types/definitions/rerun/archetypes/encoded_depth_image.fbs @@ -19,7 +19,7 @@ table EncodedDepthImage( /// Supported are: /// * single channel PNG /// * RVL with ROS2 metadata (for details see ) - blob: rerun.components.Blob ("attr.rerun.component_required", order: 1000); + blob: rerun.components.Blob ("attr.rerun.component_required", "attr.rerun.component_no_ui_edit", order: 1000); // --- Recommended --- diff --git a/crates/store/re_sdk_types/definitions/rerun/archetypes/encoded_image.fbs b/crates/store/re_sdk_types/definitions/rerun/archetypes/encoded_image.fbs index 36d064a3f4d9..829eb66f3f57 100644 --- a/crates/store/re_sdk_types/definitions/rerun/archetypes/encoded_image.fbs +++ b/crates/store/re_sdk_types/definitions/rerun/archetypes/encoded_image.fbs @@ -20,7 +20,7 @@ table EncodedImage ( // --- Required --- /// The encoded content of some image file, e.g. a PNG or JPEG. - blob: rerun.components.Blob ("attr.rerun.component_required", order: 1000); + blob: rerun.components.Blob ("attr.rerun.component_required", "attr.rerun.component_no_ui_edit", order: 1000); // --- Recommended --- diff --git a/crates/store/re_sdk_types/definitions/rerun/archetypes/geo_line_strings.fbs b/crates/store/re_sdk_types/definitions/rerun/archetypes/geo_line_strings.fbs index 4ae2c23d5b6a..e9ba8da08bd1 100644 --- a/crates/store/re_sdk_types/definitions/rerun/archetypes/geo_line_strings.fbs +++ b/crates/store/re_sdk_types/definitions/rerun/archetypes/geo_line_strings.fbs @@ -18,7 +18,7 @@ table GeoLineStrings ( // --- Required --- /// The line strings, expressed in [EPSG:4326](https://epsg.io/4326) coordinates (North/East-positive degrees). - line_strings: [rerun.components.GeoLineString] ("attr.rerun.component_required", order: 1000); + line_strings: [rerun.components.GeoLineString] ("attr.rerun.component_required", "attr.rerun.component_no_ui_edit", order: 1000); // --- Recommended --- diff --git a/crates/store/re_sdk_types/definitions/rerun/archetypes/graph_edges.fbs b/crates/store/re_sdk_types/definitions/rerun/archetypes/graph_edges.fbs index 2eada2fa2876..8d92bff1784c 100644 --- a/crates/store/re_sdk_types/definitions/rerun/archetypes/graph_edges.fbs +++ b/crates/store/re_sdk_types/definitions/rerun/archetypes/graph_edges.fbs @@ -18,7 +18,7 @@ table GraphEdges ( // --- Required --- /// A list of node tuples. - edges: [rerun.components.GraphEdge] ("attr.rerun.component_required", order: 1000); + edges: [rerun.components.GraphEdge] ("attr.rerun.component_required", "attr.rerun.component_no_ui_edit", order: 1000); // --- Recommended --- diff --git a/crates/store/re_sdk_types/definitions/rerun/archetypes/graph_nodes.fbs b/crates/store/re_sdk_types/definitions/rerun/archetypes/graph_nodes.fbs index 2a12d7d5bc52..2243880259fb 100644 --- a/crates/store/re_sdk_types/definitions/rerun/archetypes/graph_nodes.fbs +++ b/crates/store/re_sdk_types/definitions/rerun/archetypes/graph_nodes.fbs @@ -16,7 +16,7 @@ table GraphNodes ( // --- Required --- /// A list of node IDs. - node_ids: [rerun.components.GraphNode] ("attr.rerun.component_required", order: 1000); + node_ids: [rerun.components.GraphNode] ("attr.rerun.component_required", "attr.rerun.component_no_ui_edit", order: 1000); // --- Optional --- diff --git a/crates/store/re_sdk_types/definitions/rerun/archetypes/image.fbs b/crates/store/re_sdk_types/definitions/rerun/archetypes/image.fbs index a3bb7be37475..f3ea3def6c63 100644 --- a/crates/store/re_sdk_types/definitions/rerun/archetypes/image.fbs +++ b/crates/store/re_sdk_types/definitions/rerun/archetypes/image.fbs @@ -36,10 +36,10 @@ table Image ( // --- Required --- /// The raw image data. - buffer: rerun.components.ImageBuffer ("attr.rerun.component_required", order: 1000); + buffer: rerun.components.ImageBuffer ("attr.rerun.component_required", "attr.rerun.component_no_ui_edit", order: 1000); /// The format of the image. - format: rerun.components.ImageFormat ("attr.rerun.component_required", order: 1100); + format: rerun.components.ImageFormat ("attr.rerun.component_required", "attr.rerun.component_no_ui_edit", order: 1100); // --- Optional --- diff --git a/crates/store/re_sdk_types/definitions/rerun/archetypes/instance_poses3d.fbs b/crates/store/re_sdk_types/definitions/rerun/archetypes/instance_poses3d.fbs index b90ef88f2362..0cbd3a410328 100644 --- a/crates/store/re_sdk_types/definitions/rerun/archetypes/instance_poses3d.fbs +++ b/crates/store/re_sdk_types/definitions/rerun/archetypes/instance_poses3d.fbs @@ -34,17 +34,17 @@ table InstancePoses3D ( // TODO(#6743): Transforms can't be affected by blueprints which is why all components of this archetype are non-ui editable. /// Translation vectors. - translations: [rerun.components.Translation3D] ("attr.rerun.component_optional", "attr.rerun.component_ui_editable": "false", nullable, order: 1100); + translations: [rerun.components.Translation3D] ("attr.rerun.component_optional", "attr.rerun.component_no_ui_edit", nullable, order: 1100); /// Rotations via axis + angle. - rotation_axis_angles: [rerun.components.RotationAxisAngle] ("attr.rerun.component_optional", "attr.rerun.component_ui_editable": "false", nullable, order: 1200); + rotation_axis_angles: [rerun.components.RotationAxisAngle] ("attr.rerun.component_optional", "attr.rerun.component_no_ui_edit", nullable, order: 1200); /// Rotations via quaternion. - quaternions: [rerun.components.RotationQuat] ("attr.rerun.component_optional", "attr.rerun.component_ui_editable": "false", nullable, order: 1300); + quaternions: [rerun.components.RotationQuat] ("attr.rerun.component_optional", "attr.rerun.component_no_ui_edit", nullable, order: 1300); /// Scaling factors. - scales: [rerun.components.Scale3D] ("attr.rerun.component_optional", "attr.rerun.component_ui_editable": "false", nullable, order: 1400); + scales: [rerun.components.Scale3D] ("attr.rerun.component_optional", "attr.rerun.component_no_ui_edit", nullable, order: 1400); /// 3x3 transformation matrices. - mat3x3: [rerun.components.TransformMat3x3] ("attr.rerun.component_optional", "attr.rerun.component_ui_editable": "false", nullable, order: 1500); + mat3x3: [rerun.components.TransformMat3x3] ("attr.rerun.component_optional", "attr.rerun.component_no_ui_edit", nullable, order: 1500); } diff --git a/crates/store/re_sdk_types/definitions/rerun/archetypes/line_strips2d.fbs b/crates/store/re_sdk_types/definitions/rerun/archetypes/line_strips2d.fbs index 4c76758bca86..7089266eb7b3 100644 --- a/crates/store/re_sdk_types/definitions/rerun/archetypes/line_strips2d.fbs +++ b/crates/store/re_sdk_types/definitions/rerun/archetypes/line_strips2d.fbs @@ -18,7 +18,7 @@ table LineStrips2D ( // --- Required --- /// All the actual 2D line strips that make up the batch. - strips: [rerun.components.LineStrip2D] ("attr.rerun.component_required", order: 1000); + strips: [rerun.components.LineStrip2D] ("attr.rerun.component_required", "attr.rerun.component_no_ui_edit", order: 1000); // --- Recommended --- diff --git a/crates/store/re_sdk_types/definitions/rerun/archetypes/line_strips3d.fbs b/crates/store/re_sdk_types/definitions/rerun/archetypes/line_strips3d.fbs index 4e1a03fa1443..9c532f55925d 100644 --- a/crates/store/re_sdk_types/definitions/rerun/archetypes/line_strips3d.fbs +++ b/crates/store/re_sdk_types/definitions/rerun/archetypes/line_strips3d.fbs @@ -18,7 +18,7 @@ table LineStrips3D ( // --- Required --- /// All the actual 3D line strips that make up the batch. - strips: [rerun.components.LineStrip3D] ("attr.rerun.component_required", order: 1000); + strips: [rerun.components.LineStrip3D] ("attr.rerun.component_required", "attr.rerun.component_no_ui_edit", order: 1000); // --- Recommended --- diff --git a/crates/store/re_sdk_types/definitions/rerun/archetypes/mcap_channel.fbs b/crates/store/re_sdk_types/definitions/rerun/archetypes/mcap_channel.fbs index 25e8628315a1..21423016b1f5 100644 --- a/crates/store/re_sdk_types/definitions/rerun/archetypes/mcap_channel.fbs +++ b/crates/store/re_sdk_types/definitions/rerun/archetypes/mcap_channel.fbs @@ -26,14 +26,14 @@ table McapChannel ( /// /// Channel IDs must be unique within a single MCAP file and are used to associate /// messages with their corresponding channel definition. - id: rerun.components.ChannelId ("attr.rerun.component_required", order: 1000); + id: rerun.components.ChannelId ("attr.rerun.component_required", "attr.rerun.component_no_ui_edit", order: 1000); /// The topic name that this channel publishes to. /// /// Topics are hierarchical paths from the original robotics system (e.g., "/sensors/camera/image") /// that categorize and organize different data streams. /// Topics are separate from Rerun's entity paths, but they often can be mapped to them. - topic: rerun.components.Text ("attr.rerun.component_required", order: 2000); + topic: rerun.components.Text ("attr.rerun.component_required", "attr.rerun.component_no_ui_edit", order: 2000); /// The encoding format used for messages in this channel. /// @@ -42,7 +42,7 @@ table McapChannel ( /// * `cdr` - Common Data Representation (CDR) message format, used by ROS2 /// * `protobuf` - Protocol Buffers /// * `json` - JSON encoding - message_encoding: rerun.components.Text ("attr.rerun.component_required", order: 3000); + message_encoding: rerun.components.Text ("attr.rerun.component_required", "attr.rerun.component_no_ui_edit", order: 3000); // --- Optional --- diff --git a/crates/store/re_sdk_types/definitions/rerun/archetypes/mcap_message.fbs b/crates/store/re_sdk_types/definitions/rerun/archetypes/mcap_message.fbs index 4156e3d5547c..f4d7d0b16f16 100644 --- a/crates/store/re_sdk_types/definitions/rerun/archetypes/mcap_message.fbs +++ b/crates/store/re_sdk_types/definitions/rerun/archetypes/mcap_message.fbs @@ -30,5 +30,5 @@ table McapMessage ( /// by the associated channel's `message_encoding` field. The structure and interpretation /// of this binary data depends on the encoding format (e.g., ros1, cdr, protobuf) /// and the message schema defined for the channel. - data: rerun.components.Blob ("attr.rerun.component_required", order: 1000); + data: rerun.components.Blob ("attr.rerun.component_required", "attr.rerun.component_no_ui_edit", order: 1000); } diff --git a/crates/store/re_sdk_types/definitions/rerun/archetypes/mcap_schema.fbs b/crates/store/re_sdk_types/definitions/rerun/archetypes/mcap_schema.fbs index 8bdc217f91d5..49e1d13715d7 100644 --- a/crates/store/re_sdk_types/definitions/rerun/archetypes/mcap_schema.fbs +++ b/crates/store/re_sdk_types/definitions/rerun/archetypes/mcap_schema.fbs @@ -27,13 +27,13 @@ table McapSchema ( /// /// Schema IDs must be unique within an MCAP file and are referenced by channels /// to specify their message structure. A single schema can be shared across multiple channels. - id: rerun.components.SchemaId ("attr.rerun.component_required", order: 1000); + id: rerun.components.SchemaId ("attr.rerun.component_required", "attr.rerun.component_no_ui_edit", order: 1000); /// Human-readable name identifying this schema. /// /// Schema names typically describe the message type or data structure /// (e.g., `"geometry_msgs/msg/Twist"`, `"sensor_msgs/msg/Image"`, `"MyCustomMessage"`). - name: rerun.components.Text ("attr.rerun.component_required", order: 2000); + name: rerun.components.Text ("attr.rerun.component_required", "attr.rerun.component_no_ui_edit", order: 2000); /// The schema definition format used to describe the message structure. /// @@ -43,7 +43,7 @@ table McapSchema ( /// * `ros2msg` - [ROS2](https://mcap.dev/spec/registry#ros2msg) message definition format /// * `jsonschema` - [JSON Schema](https://mcap.dev/spec/registry#jsonschema) specification /// * `flatbuffer` - [FlatBuffers](https://mcap.dev/spec/registry#flatbuffer) schema definition - encoding: rerun.components.Text ("attr.rerun.component_required", order: 3000); + encoding: rerun.components.Text ("attr.rerun.component_required", "attr.rerun.component_no_ui_edit", order: 3000); /// The schema definition content as binary data. /// @@ -51,5 +51,5 @@ table McapSchema ( /// `encoding` field. For text-based schemas (like ROS message definitions or JSON Schema), /// this is typically UTF-8 encoded text. For binary schema formats, this contains /// the serialized schema data. - data: rerun.components.Blob ("attr.rerun.component_required", order: 4000); + data: rerun.components.Blob ("attr.rerun.component_required", "attr.rerun.component_no_ui_edit", order: 4000); } diff --git a/crates/store/re_sdk_types/definitions/rerun/archetypes/mcap_statistics.fbs b/crates/store/re_sdk_types/definitions/rerun/archetypes/mcap_statistics.fbs index 460b1b5b0b89..4103df3e4311 100644 --- a/crates/store/re_sdk_types/definitions/rerun/archetypes/mcap_statistics.fbs +++ b/crates/store/re_sdk_types/definitions/rerun/archetypes/mcap_statistics.fbs @@ -26,43 +26,43 @@ table McapStatistics ( /// /// This count includes all timestamped data messages but excludes metadata records, /// schema definitions, and other non-message records. - message_count: rerun.components.Count ("attr.rerun.component_required", order: 1000); + message_count: rerun.components.Count ("attr.rerun.component_required", "attr.rerun.component_no_ui_edit", order: 1000); /// Number of unique schema definitions in the recording. /// /// Each schema defines the structure for one or more message types used by channels. - schema_count: rerun.components.Count ("attr.rerun.component_required", order: 2000); + schema_count: rerun.components.Count ("attr.rerun.component_required", "attr.rerun.component_no_ui_edit", order: 2000); /// Number of channels defined in the recording. /// /// Each channel represents a unique topic and encoding combination for publishing messages. - channel_count: rerun.components.Count ("attr.rerun.component_required", order: 3000); + channel_count: rerun.components.Count ("attr.rerun.component_required", "attr.rerun.component_no_ui_edit", order: 3000); /// Number of file attachments embedded in the recording. /// /// Attachments can include calibration files, configuration data, or other auxiliary files. - attachment_count: rerun.components.Count ("attr.rerun.component_required", order: 4000); + attachment_count: rerun.components.Count ("attr.rerun.component_required", "attr.rerun.component_no_ui_edit", order: 4000); /// Number of metadata records providing additional context about the recording. /// /// Metadata records contain key-value pairs with information about the recording environment, /// system configuration, or other contextual data. - metadata_count: rerun.components.Count ("attr.rerun.component_required", order: 5000); + metadata_count: rerun.components.Count ("attr.rerun.component_required", "attr.rerun.component_no_ui_edit", order: 5000); /// Number of data chunks used to organize messages in the file. /// /// Chunks group related messages together for efficient storage and indexed access. - chunk_count: rerun.components.Count ("attr.rerun.component_required", order: 6000); + chunk_count: rerun.components.Count ("attr.rerun.component_required", "attr.rerun.component_no_ui_edit", order: 6000); /// Timestamp of the earliest message in the recording. /// /// This marks the beginning of the recorded data timeline. - message_start_time: rerun.components.Timestamp ("attr.rerun.component_required", order: 7000); + message_start_time: rerun.components.Timestamp ("attr.rerun.component_required", "attr.rerun.component_no_ui_edit", order: 7000); /// Timestamp of the latest message in the recording. /// /// Together with `message_start_time`, this defines the total duration of the recording. - message_end_time: rerun.components.Timestamp ("attr.rerun.component_required", order: 8000); + message_end_time: rerun.components.Timestamp ("attr.rerun.component_required", "attr.rerun.component_no_ui_edit", order: 8000); // --- Optional --- diff --git a/crates/store/re_sdk_types/definitions/rerun/archetypes/mesh3d.fbs b/crates/store/re_sdk_types/definitions/rerun/archetypes/mesh3d.fbs index 33505b0a4b95..877ec5369b2e 100644 --- a/crates/store/re_sdk_types/definitions/rerun/archetypes/mesh3d.fbs +++ b/crates/store/re_sdk_types/definitions/rerun/archetypes/mesh3d.fbs @@ -27,7 +27,7 @@ table Mesh3D ( /// The positions of each vertex. /// /// If no `triangle_indices` are specified, then each triplet of positions is interpreted as a triangle. - vertex_positions: [rerun.components.Position3D] ("attr.rerun.component_required", order: 1000); + vertex_positions: [rerun.components.Position3D] ("attr.rerun.component_required", "attr.rerun.component_no_ui_edit", order: 1000); // --- Recommended --- diff --git a/crates/store/re_sdk_types/definitions/rerun/archetypes/pinhole.fbs b/crates/store/re_sdk_types/definitions/rerun/archetypes/pinhole.fbs index c798300ab6a5..d45484ab67ae 100644 --- a/crates/store/re_sdk_types/definitions/rerun/archetypes/pinhole.fbs +++ b/crates/store/re_sdk_types/definitions/rerun/archetypes/pinhole.fbs @@ -28,7 +28,7 @@ table Pinhole ( /// Camera projection, from image coordinates to view coordinates. /// /// Any update to this field will reset all other transform properties that aren't changed in the same log call or `send_columns` row. - image_from_camera: rerun.components.PinholeProjection ("attr.rerun.component_required", "attr.rerun.component_ui_editable": "false", order: 1000); + image_from_camera: rerun.components.PinholeProjection ("attr.rerun.component_required", "attr.rerun.component_no_ui_edit", order: 1000); /// Pixel resolution (usually integers) of child image space. Width and height. /// @@ -40,7 +40,7 @@ table Pinhole ( /// `image_from_camera` project onto the space spanned by `(0,0)` and `resolution - 1`. /// /// Any update to this field will reset all other transform properties that aren't changed in the same log call or `send_columns` row. - resolution: rerun.components.Resolution ("attr.rerun.component_recommended", "attr.rerun.component_ui_editable": "false", nullable, order: 2000); + resolution: rerun.components.Resolution ("attr.rerun.component_recommended", "attr.rerun.component_no_ui_edit", nullable, order: 2000); // --- Other --- @@ -74,7 +74,7 @@ table Pinhole ( // TODO(#2641): This should specify a default-value of `RDF` // TODO(andreas): this isn't part of the "atomic set" right now because we may also source this from `ViewCoordinates` and things get confusing quickly // Should it be reset when other transform properties are changed? - camera_xyz: rerun.components.ViewCoordinates ("attr.rerun.component_optional", "attr.rerun.component_ui_editable": "false", nullable, order: 3000); + camera_xyz: rerun.components.ViewCoordinates ("attr.rerun.component_optional", "attr.rerun.component_no_ui_edit", nullable, order: 3000); // --- Topology --- @@ -90,7 +90,7 @@ table Pinhole ( /// To set the frame an entity is part of see [archetypes.CoordinateFrame]. /// /// Any update to this field will reset all other transform properties that aren't changed in the same log call or `send_columns` row. - child_frame: rerun.components.TransformFrameId ("attr.rerun.component_optional", "attr.rerun.component_ui_editable": "false", nullable, order: 4000); + child_frame: rerun.components.TransformFrameId ("attr.rerun.component_optional", "attr.rerun.component_no_ui_edit", nullable, order: 4000); /// The parent frame this transform transforms into. /// @@ -100,7 +100,7 @@ table Pinhole ( /// To set the frame an entity is part of see [archetypes.CoordinateFrame]. /// /// Any update to this field will reset all other transform properties that aren't changed in the same log call or `send_columns` row. - parent_frame: rerun.components.TransformFrameId ("attr.rerun.component_optional", "attr.rerun.component_ui_editable": "false", nullable, order: 4100); + parent_frame: rerun.components.TransformFrameId ("attr.rerun.component_optional", "attr.rerun.component_no_ui_edit", nullable, order: 4100); // --- Visualization in 3D --- diff --git a/crates/store/re_sdk_types/definitions/rerun/archetypes/scalars.fbs b/crates/store/re_sdk_types/definitions/rerun/archetypes/scalars.fbs index 661a187dc484..1142e6560c85 100644 --- a/crates/store/re_sdk_types/definitions/rerun/archetypes/scalars.fbs +++ b/crates/store/re_sdk_types/definitions/rerun/archetypes/scalars.fbs @@ -25,7 +25,7 @@ table Scalars ( // --- Required --- /// The scalar values to log. - scalars: [rerun.components.Scalar] ("attr.rerun.component_required", order: 1000); + scalars: [rerun.components.Scalar] ("attr.rerun.component_required", "attr.rerun.component_no_ui_edit", order: 1000); // --- Recommended --- diff --git a/crates/store/re_sdk_types/definitions/rerun/archetypes/segmentation_image.fbs b/crates/store/re_sdk_types/definitions/rerun/archetypes/segmentation_image.fbs index f51804306049..c3d9b4be3054 100644 --- a/crates/store/re_sdk_types/definitions/rerun/archetypes/segmentation_image.fbs +++ b/crates/store/re_sdk_types/definitions/rerun/archetypes/segmentation_image.fbs @@ -25,10 +25,10 @@ table SegmentationImage ( // --- Required --- /// The raw image data. - buffer: rerun.components.ImageBuffer ("attr.rerun.component_required", order: 1000); + buffer: rerun.components.ImageBuffer ("attr.rerun.component_required", "attr.rerun.component_no_ui_edit", order: 1000); /// The format of the image. - format: rerun.components.ImageFormat ("attr.rerun.component_required", order: 1100); + format: rerun.components.ImageFormat ("attr.rerun.component_required", "attr.rerun.component_no_ui_edit", order: 1100); // --- Optional --- diff --git a/crates/store/re_sdk_types/definitions/rerun/archetypes/series_points.fbs b/crates/store/re_sdk_types/definitions/rerun/archetypes/series_points.fbs index d7d553c992fd..91bda2f66c45 100644 --- a/crates/store/re_sdk_types/definitions/rerun/archetypes/series_points.fbs +++ b/crates/store/re_sdk_types/definitions/rerun/archetypes/series_points.fbs @@ -21,7 +21,7 @@ table SeriesPoints ( /// What shape to use to represent the point /// /// May change over time. - markers: [rerun.components.MarkerShape] ("attr.rerun.component_required", "attr.rerun.component_ui_editable": "true", nullable, order: 2000); + markers: [rerun.components.MarkerShape] ("attr.rerun.component_required", nullable, order: 2000); // --- Optional --- diff --git a/crates/store/re_sdk_types/definitions/rerun/archetypes/tensor.fbs b/crates/store/re_sdk_types/definitions/rerun/archetypes/tensor.fbs index 434e46f83007..52979f56a398 100644 --- a/crates/store/re_sdk_types/definitions/rerun/archetypes/tensor.fbs +++ b/crates/store/re_sdk_types/definitions/rerun/archetypes/tensor.fbs @@ -20,7 +20,7 @@ table Tensor ( "attr.rust.derive": "PartialEq" ) { /// The tensor data - data: rerun.components.TensorData ("attr.rerun.component_required", order: 1000); + data: rerun.components.TensorData ("attr.rerun.component_required", "attr.rerun.component_no_ui_edit", order: 1000); // --- Optional --- diff --git a/crates/store/re_sdk_types/definitions/rerun/archetypes/transform3d.fbs b/crates/store/re_sdk_types/definitions/rerun/archetypes/transform3d.fbs index b554c2d8d935..210f76d4ad36 100644 --- a/crates/store/re_sdk_types/definitions/rerun/archetypes/transform3d.fbs +++ b/crates/store/re_sdk_types/definitions/rerun/archetypes/transform3d.fbs @@ -32,32 +32,32 @@ table Transform3D ( /// Translation vector. /// /// Any update to this field will reset all other transform properties that aren't changed in the same log call or `send_columns` row. - translation: rerun.components.Translation3D ("attr.rerun.component_optional", "attr.rerun.component_ui_editable": "false", nullable, order: 1100); + translation: rerun.components.Translation3D ("attr.rerun.component_optional", "attr.rerun.component_no_ui_edit", nullable, order: 1100); /// Rotation via axis + angle. /// /// Any update to this field will reset all other transform properties that aren't changed in the same log call or `send_columns` row. - rotation_axis_angle: rerun.components.RotationAxisAngle ("attr.rerun.component_optional", "attr.rerun.component_ui_editable": "false", nullable, order: 1200); + rotation_axis_angle: rerun.components.RotationAxisAngle ("attr.rerun.component_optional", "attr.rerun.component_no_ui_edit", nullable, order: 1200); /// Rotation via quaternion. /// /// Any update to this field will reset all other transform properties that aren't changed in the same log call or `send_columns` row. - quaternion: rerun.components.RotationQuat ("attr.rerun.component_optional", "attr.rerun.component_ui_editable": "false", nullable, order: 1300); + quaternion: rerun.components.RotationQuat ("attr.rerun.component_optional", "attr.rerun.component_no_ui_edit", nullable, order: 1300); /// Scaling factor. /// /// Any update to this field will reset all other transform properties that aren't changed in the same log call or `send_columns` row. - scale: rerun.components.Scale3D ("attr.rerun.component_optional", "attr.rerun.component_ui_editable": "false", nullable, order: 1400); + scale: rerun.components.Scale3D ("attr.rerun.component_optional", "attr.rerun.component_no_ui_edit", nullable, order: 1400); /// 3x3 transformation matrix. /// /// Any update to this field will reset all other transform properties that aren't changed in the same log call or `send_columns` row. - mat3x3: rerun.components.TransformMat3x3 ("attr.rerun.component_optional", "attr.rerun.component_ui_editable": "false", nullable, order: 1500); + mat3x3: rerun.components.TransformMat3x3 ("attr.rerun.component_optional", "attr.rerun.component_no_ui_edit", nullable, order: 1500); /// Specifies the relation this transform establishes between this entity and its parent. /// /// Any update to this field will reset all other transform properties that aren't changed in the same log call or `send_columns` row. - relation: rerun.components.TransformRelation ("attr.rerun.component_optional", "attr.rerun.component_ui_editable": "false", nullable, order: 1600); + relation: rerun.components.TransformRelation ("attr.rerun.component_optional", "attr.rerun.component_no_ui_edit", nullable, order: 1600); // --- transform frame @@ -73,7 +73,7 @@ table Transform3D ( /// To set the frame an entity is part of see [archetypes.CoordinateFrame]. /// /// Any update to this field will reset all other transform properties that aren't changed in the same log call or `send_columns` row. - child_frame: rerun.components.TransformFrameId ("attr.rerun.component_optional", "attr.rerun.component_ui_editable": "false", nullable, order: 2000); + child_frame: rerun.components.TransformFrameId ("attr.rerun.component_optional", "attr.rerun.component_no_ui_edit", nullable, order: 2000); /// The parent frame this transform transforms into. /// @@ -83,5 +83,5 @@ table Transform3D ( /// To set the frame an entity is part of see [archetypes.CoordinateFrame]. /// /// Any update to this field will reset all other transform properties that aren't changed in the same log call or `send_columns` row. - parent_frame: rerun.components.TransformFrameId ("attr.rerun.component_optional", "attr.rerun.component_ui_editable": "false", nullable, order: 2100); + parent_frame: rerun.components.TransformFrameId ("attr.rerun.component_optional", "attr.rerun.component_no_ui_edit", nullable, order: 2100); } diff --git a/crates/store/re_sdk_types/definitions/rerun/archetypes/transform_axes3d.fbs b/crates/store/re_sdk_types/definitions/rerun/archetypes/transform_axes3d.fbs index eab76588f288..c3dd65ce7ec4 100644 --- a/crates/store/re_sdk_types/definitions/rerun/archetypes/transform_axes3d.fbs +++ b/crates/store/re_sdk_types/definitions/rerun/archetypes/transform_axes3d.fbs @@ -14,7 +14,7 @@ table TransformAxes3D ( /// /// The length is interpreted in the local coordinate system of the transform. /// If the transform is scaled, the axes will be scaled accordingly. - axis_length: rerun.components.AxisLength ("attr.rerun.component_required", "attr.rerun.component_ui_editable": "true", order: 1000); + axis_length: rerun.components.AxisLength ("attr.rerun.component_required", order: 1000); /// Whether to show a text label with the corresponding frame. show_frame: rerun.components.ShowLabels ("attr.rerun.component_optional", nullable, order: 2000); diff --git a/crates/store/re_sdk_types/definitions/rerun/archetypes/video_stream.fbs b/crates/store/re_sdk_types/definitions/rerun/archetypes/video_stream.fbs index 7ee5b10b3cc8..538cdb6f5848 100644 --- a/crates/store/re_sdk_types/definitions/rerun/archetypes/video_stream.fbs +++ b/crates/store/re_sdk_types/definitions/rerun/archetypes/video_stream.fbs @@ -22,7 +22,7 @@ table VideoStream ( /// The codec used to encode the video chunks. /// /// This property is expected to be constant over time and is ideally logged statically once per stream. - codec: rerun.components.VideoCodec ("attr.rerun.component_required", order: 1000); + codec: rerun.components.VideoCodec ("attr.rerun.component_required", "attr.rerun.component_no_ui_edit", order: 1000); // --- Recommended --- diff --git a/crates/store/re_sdk_types/definitions/rerun/archetypes/view_coordinates.fbs b/crates/store/re_sdk_types/definitions/rerun/archetypes/view_coordinates.fbs index a975856852ce..e08b9ca74941 100644 --- a/crates/store/re_sdk_types/definitions/rerun/archetypes/view_coordinates.fbs +++ b/crates/store/re_sdk_types/definitions/rerun/archetypes/view_coordinates.fbs @@ -25,5 +25,5 @@ table ViewCoordinates ( "attr.rust.repr": "transparent" ) { /// The directions of the [x, y, z] axes. - xyz: rerun.components.ViewCoordinates ("attr.rerun.component_required", order: 1000); + xyz: rerun.components.ViewCoordinates ("attr.rerun.component_required", "attr.rerun.component_no_ui_edit", order: 1000); } diff --git a/crates/store/re_sdk_types/definitions/rerun/attributes.fbs b/crates/store/re_sdk_types/definitions/rerun/attributes.fbs index dc284a82ffb9..fd14581eb3bd 100644 --- a/crates/store/re_sdk_types/definitions/rerun/attributes.fbs +++ b/crates/store/re_sdk_types/definitions/rerun/attributes.fbs @@ -18,15 +18,11 @@ attribute "attr.rerun.component_recommended"; /// Only applies to the fields of an archetype. attribute "attr.rerun.component_optional"; -/// Marks a component as editable through the UI. -/// By default all required components are non-editable and all other components are editable. -/// -/// Must be set to true or false. Valid usage examples:" -/// - `axis_length: rerun.components.AxisLength ("attr.rerun.component_required", "attr.rerun.component_ui_editable": "true", order: 1000);` -/// - `translation: rerun.components.Translation3D ("attr.rerun.component_optional", "attr.rerun.component_ui_editable": "false", nullable, order: 1100);` +/// Opts a component out of being editable through the UI. +/// By default all components are editable. This is a boolean attribute (presence = true). /// /// Note this is only a hint to the Viewer. Blueprint code may always set overrides for any component! -attribute "attr.rerun.component_ui_editable"; +attribute "attr.rerun.component_no_ui_edit"; /// Override the type of a field. /// diff --git a/crates/store/re_sdk_types/src/reflection/mod.rs b/crates/store/re_sdk_types/src/reflection/mod.rs index 19a2d63da356..0639c1d431b6 100644 --- a/crates/store/re_sdk_types/src/reflection/mod.rs +++ b/crates/store/re_sdk_types/src/reflection/mod.rs @@ -1510,7 +1510,7 @@ fn generate_archetype_reflection() -> ArchetypeReflectionMap { display_name: "Vectors", component_type: "rerun.components.Vector2D".into(), docstring_md: "All the vectors for each arrow in the batch.", - flags: ArchetypeFieldFlags::REQUIRED, + flags: ArchetypeFieldFlags::REQUIRED | ArchetypeFieldFlags::UI_EDITABLE, }, ArchetypeFieldReflection { name: "origins", @@ -1577,7 +1577,7 @@ fn generate_archetype_reflection() -> ArchetypeReflectionMap { display_name: "Vectors", component_type: "rerun.components.Vector3D".into(), docstring_md: "All the vectors for each arrow in the batch.", - flags: ArchetypeFieldFlags::REQUIRED, + flags: ArchetypeFieldFlags::REQUIRED | ArchetypeFieldFlags::UI_EDITABLE, }, ArchetypeFieldReflection { name: "origins", @@ -1733,7 +1733,7 @@ fn generate_archetype_reflection() -> ArchetypeReflectionMap { display_name: "Half sizes", component_type: "rerun.components.HalfSize2D".into(), docstring_md: "All half-extents that make up the batch of boxes.", - flags: ArchetypeFieldFlags::REQUIRED, + flags: ArchetypeFieldFlags::REQUIRED | ArchetypeFieldFlags::UI_EDITABLE, }, ArchetypeFieldReflection { name: "centers", @@ -1800,7 +1800,7 @@ fn generate_archetype_reflection() -> ArchetypeReflectionMap { display_name: "Half sizes", component_type: "rerun.components.HalfSize3D".into(), docstring_md: "All half-extents that make up the batch of boxes.", - flags: ArchetypeFieldFlags::REQUIRED, + flags: ArchetypeFieldFlags::REQUIRED | ArchetypeFieldFlags::UI_EDITABLE, }, ArchetypeFieldReflection { name: "centers", @@ -1881,14 +1881,14 @@ fn generate_archetype_reflection() -> ArchetypeReflectionMap { display_name: "Lengths", component_type: "rerun.components.Length".into(), docstring_md: "Lengths of the capsules, defined as the distance between the centers of the endcaps.", - flags: ArchetypeFieldFlags::REQUIRED, + flags: ArchetypeFieldFlags::REQUIRED | ArchetypeFieldFlags::UI_EDITABLE, }, ArchetypeFieldReflection { name: "radii", display_name: "Radii", component_type: "rerun.components.Radius".into(), docstring_md: "Radii of the capsules.", - flags: ArchetypeFieldFlags::REQUIRED, + flags: ArchetypeFieldFlags::REQUIRED | ArchetypeFieldFlags::UI_EDITABLE, }, ArchetypeFieldReflection { name: "translations", @@ -2001,14 +2001,14 @@ fn generate_archetype_reflection() -> ArchetypeReflectionMap { display_name: "Lengths", component_type: "rerun.components.Length".into(), docstring_md: "The total axial length of the cylinder, measured as the straight-line distance between the centers of its two endcaps.", - flags: ArchetypeFieldFlags::REQUIRED, + flags: ArchetypeFieldFlags::REQUIRED | ArchetypeFieldFlags::UI_EDITABLE, }, ArchetypeFieldReflection { name: "radii", display_name: "Radii", component_type: "rerun.components.Radius".into(), docstring_md: "Radii of the cylinders.", - flags: ArchetypeFieldFlags::REQUIRED, + flags: ArchetypeFieldFlags::REQUIRED | ArchetypeFieldFlags::UI_EDITABLE, }, ArchetypeFieldReflection { name: "centers", @@ -2149,7 +2149,7 @@ fn generate_archetype_reflection() -> ArchetypeReflectionMap { display_name: "Half sizes", component_type: "rerun.components.HalfSize3D".into(), docstring_md: "For each ellipsoid, half of its size on its three axes.\n\nIf all components are equal, then it is a sphere with that radius.", - flags: ArchetypeFieldFlags::REQUIRED, + flags: ArchetypeFieldFlags::REQUIRED | ArchetypeFieldFlags::UI_EDITABLE, }, ArchetypeFieldReflection { name: "centers", @@ -2361,7 +2361,7 @@ fn generate_archetype_reflection() -> ArchetypeReflectionMap { display_name: "Positions", component_type: "rerun.components.LatLon".into(), docstring_md: "The [EPSG:4326](https://epsg.io/4326) coordinates for the points (North/East-positive degrees).", - flags: ArchetypeFieldFlags::REQUIRED, + flags: ArchetypeFieldFlags::REQUIRED | ArchetypeFieldFlags::UI_EDITABLE, }, ArchetypeFieldReflection { name: "radii", @@ -2985,7 +2985,7 @@ fn generate_archetype_reflection() -> ArchetypeReflectionMap { display_name: "Positions", component_type: "rerun.components.Position2D".into(), docstring_md: "All the 2D positions at which the point cloud shows points.", - flags: ArchetypeFieldFlags::REQUIRED, + flags: ArchetypeFieldFlags::REQUIRED | ArchetypeFieldFlags::UI_EDITABLE, }, ArchetypeFieldReflection { name: "radii", @@ -3052,7 +3052,7 @@ fn generate_archetype_reflection() -> ArchetypeReflectionMap { display_name: "Positions", component_type: "rerun.components.Position3D".into(), docstring_md: "All the 3D positions at which the point cloud shows points.", - flags: ArchetypeFieldFlags::REQUIRED, + flags: ArchetypeFieldFlags::REQUIRED | ArchetypeFieldFlags::UI_EDITABLE, }, ArchetypeFieldReflection { name: "radii", @@ -3316,7 +3316,7 @@ fn generate_archetype_reflection() -> ArchetypeReflectionMap { display_name: "Text", component_type: "rerun.components.Text".into(), docstring_md: "Contents of the text document.", - flags: ArchetypeFieldFlags::REQUIRED, + flags: ArchetypeFieldFlags::REQUIRED | ArchetypeFieldFlags::UI_EDITABLE, }, ArchetypeFieldReflection { name: "media_type", @@ -3341,7 +3341,7 @@ fn generate_archetype_reflection() -> ArchetypeReflectionMap { display_name: "Text", component_type: "rerun.components.Text".into(), docstring_md: "The body of the message.", - flags: ArchetypeFieldFlags::REQUIRED, + flags: ArchetypeFieldFlags::REQUIRED | ArchetypeFieldFlags::UI_EDITABLE, }, ArchetypeFieldReflection { name: "level", @@ -3465,7 +3465,7 @@ fn generate_archetype_reflection() -> ArchetypeReflectionMap { display_name: "Timestamp", component_type: "rerun.components.VideoTimestamp".into(), docstring_md: "References the closest video frame to this timestamp.\n\nNote that this uses the closest video frame instead of the latest at this timestamp\nin order to be more forgiving of rounding errors for inprecise timestamp types.\n\nTimestamps are relative to the start of the video, i.e. a timestamp of 0 always corresponds to the first frame.\nThis is oftentimes equivalent to presentation timestamps (known as PTS), but in the presence of B-frames\n(bidirectionally predicted frames) there may be an offset on the first presentation timestamp in the video.", - flags: ArchetypeFieldFlags::REQUIRED, + flags: ArchetypeFieldFlags::REQUIRED | ArchetypeFieldFlags::UI_EDITABLE, }, ArchetypeFieldReflection { name: "video_reference", @@ -3558,7 +3558,7 @@ fn generate_archetype_reflection() -> ArchetypeReflectionMap { display_name: "Instruction ids", component_type: "rerun.blueprint.components.VisualizerInstructionId".into(), docstring_md: "Id's of the visualizers that should be active.", - flags: ArchetypeFieldFlags::REQUIRED, + flags: ArchetypeFieldFlags::REQUIRED | ArchetypeFieldFlags::UI_EDITABLE, }], }, ), @@ -3575,7 +3575,7 @@ fn generate_archetype_reflection() -> ArchetypeReflectionMap { display_name: "Kind", component_type: "rerun.blueprint.components.BackgroundKind".into(), docstring_md: "The type of the background.", - flags: ArchetypeFieldFlags::REQUIRED, + flags: ArchetypeFieldFlags::REQUIRED | ArchetypeFieldFlags::UI_EDITABLE, }, ArchetypeFieldReflection { name: "color", @@ -3600,7 +3600,7 @@ fn generate_archetype_reflection() -> ArchetypeReflectionMap { display_name: "Container kind", component_type: "rerun.blueprint.components.ContainerKind".into(), docstring_md: "The class of the view.", - flags: ArchetypeFieldFlags::REQUIRED, + flags: ArchetypeFieldFlags::REQUIRED | ArchetypeFieldFlags::UI_EDITABLE, }, ArchetypeFieldReflection { name: "display_name", @@ -4428,7 +4428,7 @@ fn generate_archetype_reflection() -> ArchetypeReflectionMap { display_name: "Class identifier", component_type: "rerun.blueprint.components.ViewClass".into(), docstring_md: "The class of the view.", - flags: ArchetypeFieldFlags::REQUIRED, + flags: ArchetypeFieldFlags::REQUIRED | ArchetypeFieldFlags::UI_EDITABLE, }, ArchetypeFieldReflection { name: "display_name", @@ -4529,7 +4529,7 @@ fn generate_archetype_reflection() -> ArchetypeReflectionMap { display_name: "Ranges", component_type: "rerun.blueprint.components.VisibleTimeRange".into(), docstring_md: "The time ranges to show for each timeline unless specified otherwise on a per-entity basis.\n\nIf a timeline is specified more than once, the first entry will be used.", - flags: ArchetypeFieldFlags::REQUIRED, + flags: ArchetypeFieldFlags::REQUIRED | ArchetypeFieldFlags::UI_EDITABLE, }], }, ), @@ -4545,7 +4545,7 @@ fn generate_archetype_reflection() -> ArchetypeReflectionMap { display_name: "Range", component_type: "rerun.blueprint.components.VisualBounds2D".into(), docstring_md: "Controls the visible range of a 2D view.\n\nUse this to control pan & zoom of the view.", - flags: ArchetypeFieldFlags::REQUIRED, + flags: ArchetypeFieldFlags::REQUIRED | ArchetypeFieldFlags::UI_EDITABLE, }], }, ), @@ -4562,7 +4562,7 @@ fn generate_archetype_reflection() -> ArchetypeReflectionMap { display_name: "Visualizer type", component_type: "rerun.blueprint.components.VisualizerType".into(), docstring_md: "The type of the visualizer.", - flags: ArchetypeFieldFlags::REQUIRED, + flags: ArchetypeFieldFlags::REQUIRED | ArchetypeFieldFlags::UI_EDITABLE, }, ArchetypeFieldReflection { name: "component_map", diff --git a/crates/store/re_types_core/src/reflection.rs b/crates/store/re_types_core/src/reflection.rs index 739742e8a561..bcbf41158e9a 100644 --- a/crates/store/re_types_core/src/reflection.rs +++ b/crates/store/re_types_core/src/reflection.rs @@ -383,7 +383,7 @@ bitflags::bitflags! { /// The field should be editable through the UI. /// - /// By default, required components are non-editable and all other components are editable. + /// By default all components are editable. const UI_EDITABLE = 1 << 1; } } diff --git a/crates/viewer/re_component_ui/src/datatype_uis/mod.rs b/crates/viewer/re_component_ui/src/datatype_uis/mod.rs index 759420f6dc68..69da5a9c8390 100644 --- a/crates/viewer/re_component_ui/src/datatype_uis/mod.rs +++ b/crates/viewer/re_component_ui/src/datatype_uis/mod.rs @@ -22,7 +22,9 @@ pub use float_drag::{ pub use int_drag::edit_u64_range; pub use range1d::edit_view_range1d; pub use singleline_string::{edit_multiline_string, edit_singleline_string}; -pub use vec::{edit_or_view_vec2d, edit_or_view_vec3d, edit_or_view_vec3d_raw}; +pub use vec::{ + edit_or_view_vec2d, edit_or_view_vec3d, edit_or_view_vec3d_positive, edit_or_view_vec3d_raw, +}; pub use view_id::view_view_id; pub use view_timestamp::view_timestamp; pub use view_uuid::view_uuid; diff --git a/crates/viewer/re_component_ui/src/datatype_uis/vec.rs b/crates/viewer/re_component_ui/src/datatype_uis/vec.rs index 679a9bd976e7..e5488fa2d82a 100644 --- a/crates/viewer/re_component_ui/src/datatype_uis/vec.rs +++ b/crates/viewer/re_component_ui/src/datatype_uis/vec.rs @@ -26,7 +26,19 @@ pub fn edit_or_view_vec3d( MaybeMutRef::Ref(value) => MaybeMutRef::Ref(value), MaybeMutRef::MutRef(value) => MaybeMutRef::MutRef(value), }; - edit_or_view_vec3d_raw(ui, &mut value) + edit_or_view_vec3d_raw(ui, &mut value, f32::MIN..=f32::MAX) +} + +pub fn edit_or_view_vec3d_positive( + _ctx: &re_viewer_context::ViewerContext<'_>, + ui: &mut egui::Ui, + value: &mut MaybeMutRef<'_, impl std::ops::DerefMut>, +) -> egui::Response { + let mut value: MaybeMutRef<'_, datatypes::Vec3D> = match value { + MaybeMutRef::Ref(value) => MaybeMutRef::Ref(value), + MaybeMutRef::MutRef(value) => MaybeMutRef::MutRef(value), + }; + edit_or_view_vec3d_raw(ui, &mut value, 0.0..=f32::MAX) } fn drag<'a>(value: &'a mut f32, range: RangeInclusive, suffix: &str) -> egui::DragValue<'a> { @@ -77,6 +89,7 @@ pub fn edit_or_view_vec2d_raw( pub fn edit_or_view_vec3d_raw( ui: &mut egui::Ui, value: &mut MaybeMutRef<'_, datatypes::Vec3D>, + range: RangeInclusive, ) -> egui::Response { let x = value.0[0]; let y = value.0[1]; @@ -87,9 +100,9 @@ pub fn edit_or_view_vec3d_raw( let mut y_edit = y; let mut z_edit = z; - let response_x = ui.add(drag(&mut x_edit, f32::MIN..=f32::MAX, "")); - let response_y = ui.add(drag(&mut y_edit, f32::MIN..=f32::MAX, "")); - let response_z = ui.add(drag(&mut z_edit, f32::MIN..=f32::MAX, "")); + let response_x = ui.add(drag(&mut x_edit, range.clone(), "")); + let response_y = ui.add(drag(&mut y_edit, range.clone(), "")); + let response_z = ui.add(drag(&mut z_edit, range, "")); let response = response_y | response_x | response_z; diff --git a/crates/viewer/re_component_ui/src/lib.rs b/crates/viewer/re_component_ui/src/lib.rs index b93e7bc8ba0a..85f691aae978 100644 --- a/crates/viewer/re_component_ui/src/lib.rs +++ b/crates/viewer/re_component_ui/src/lib.rs @@ -35,9 +35,9 @@ mod zoom_level; use datatype_uis::{ edit_bool, edit_f32_min_to_max_float, edit_f32_zero_to_max, edit_f32_zero_to_one, edit_f64_min_to_max_float, edit_f64_zero_to_max, edit_multiline_string, edit_or_view_vec2d, - edit_or_view_vec3d, edit_singleline_string, edit_u64_range, edit_ui_points, edit_view_enum, - edit_view_enum_with_variant_available, edit_view_range1d, view_timestamp, view_uuid, - view_view_id, + edit_or_view_vec3d, edit_or_view_vec3d_positive, edit_singleline_string, edit_u64_range, + edit_ui_points, edit_view_enum, edit_view_enum_with_variant_available, edit_view_range1d, + view_timestamp, view_uuid, view_view_id, }; use re_sdk_types::blueprint::components::{ AngularSpeed, BackgroundKind, Corner2D, Enabled, Eye3DKind, ForceDistance, ForceIterations, @@ -46,10 +46,10 @@ use re_sdk_types::blueprint::components::{ }; use re_sdk_types::components::{ AggregationPolicy, AlbedoFactor, AxisLength, Color, DepthMeter, DrawOrder, FillMode, FillRatio, - GammaCorrection, GraphType, ImagePlaneDistance, InterpolationMode, LinearSpeed, - MagnificationFilter, MarkerSize, Name, Opacity, Position2D, Position3D, Range1D, Scale3D, - ShowLabels, StrokeWidth, Text, Timestamp, TransformRelation, Translation3D, ValueRange, - Vector3D, VideoCodec, Visible, + GammaCorrection, GraphType, HalfSize3D, ImagePlaneDistance, InterpolationMode, Length, + LinearSpeed, MagnificationFilter, MarkerSize, Name, Opacity, Position2D, Position3D, Range1D, + Scale3D, ShowLabels, StrokeWidth, Text, Timestamp, TransformRelation, Translation3D, + ValueRange, Vector3D, VideoCodec, Visible, }; use re_viewer_context::gpu_bridge::colormap_edit_or_view_ui; @@ -81,6 +81,7 @@ pub fn create_component_ui_registry() -> re_viewer_context::ComponentUiRegistry registry.add_singleline_edit_or_view::(color::edit_rgba32); // 0-inf float components: + registry.add_singleline_edit_or_view::(edit_f64_min_to_max_float); registry.add_singleline_edit_or_view::(edit_f32_zero_to_max); registry.add_singleline_edit_or_view::(edit_f32_zero_to_max); registry.add_singleline_edit_or_view::(edit_f32_zero_to_max); @@ -88,8 +89,8 @@ pub fn create_component_ui_registry() -> re_viewer_context::ComponentUiRegistry registry.add_singleline_edit_or_view::(edit_f32_zero_to_max); registry.add_singleline_edit_or_view::(edit_f32_zero_to_max); registry.add_singleline_edit_or_view::(edit_f32_zero_to_max); + registry.add_singleline_edit_or_view::(edit_f32_zero_to_max); registry.add_singleline_edit_or_view::(edit_f64_zero_to_max); - registry.add_singleline_edit_or_view::(edit_f64_min_to_max_float); registry.add_singleline_edit_or_view::(edit_ui_points); registry.add_singleline_edit_or_view::(edit_f32_zero_to_max); registry.add_singleline_edit_or_view::(edit_ui_points); @@ -157,9 +158,10 @@ pub fn create_component_ui_registry() -> re_viewer_context::ComponentUiRegistry registry.add_singleline_edit_or_view::(edit_or_view_vec2d); // Vec3 components: - registry.add_singleline_edit_or_view::(edit_or_view_vec3d); - registry.add_singleline_edit_or_view::(edit_or_view_vec3d); + registry.add_singleline_edit_or_view::(edit_or_view_vec3d_positive); registry.add_singleline_edit_or_view::(edit_or_view_vec3d); + registry.add_singleline_edit_or_view::(edit_or_view_vec3d); + registry.add_singleline_edit_or_view::(edit_or_view_vec3d); registry.add_singleline_edit_or_view::(edit_or_view_vec3d); // Components that refer to views: diff --git a/crates/viewer/re_component_ui/src/plane3d.rs b/crates/viewer/re_component_ui/src/plane3d.rs index 8ab7e73ad16b..bb7af17d0d7d 100644 --- a/crates/viewer/re_component_ui/src/plane3d.rs +++ b/crates/viewer/re_component_ui/src/plane3d.rs @@ -115,7 +115,7 @@ pub fn edit_or_view_plane3d( )) } else { let normal = value.normal(); - edit_or_view_vec3d_raw(ui, &mut MaybeMutRef::Ref(&normal)) + edit_or_view_vec3d_raw(ui, &mut MaybeMutRef::Ref(&normal), f32::MIN..=f32::MAX) }; ui.label("d"); @@ -145,7 +145,8 @@ pub fn multiline_edit_or_view_plane3d( MaybeMutRef::MutRef(_) => MaybeMutRef::MutRef(&mut normal), }; - any_edit |= edit_or_view_vec3d_raw(ui, &mut maybe_mut_normal).changed(); + any_edit |= + edit_or_view_vec3d_raw(ui, &mut maybe_mut_normal, f32::MIN..=f32::MAX).changed(); if let MaybeMutRef::MutRef(value) = value { **value = components::Plane3D::new(normal, value.distance()); diff --git a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_bar_chart_2.png b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_bar_chart_2.png index e5c4d80d4e3c..cf58aded8e14 100644 --- a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_bar_chart_2.png +++ b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_bar_chart_2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:51982d3dbbbde0290a6dde7969ba51c7213206abf6a962f0cbec704cefe339d7 -size 190618 +oid sha256:bcb83f57d7551ac0579524b85944fd9aa5a07f238c668b722e3bb06e8418cc3d +size 191402 diff --git a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_text_log_1.png b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_text_log_1.png index b4083e5eed33..ceaca25ec926 100644 --- a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_text_log_1.png +++ b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_text_log_1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f40078490bc1d7e7e01ad05aae8c12959dc9c5b583086b24e120d6f5587822be -size 179984 +oid sha256:868b454e8ea14e8545d5e1f5795c5a1ec6c1aef8d299bead68df8f545cde4d56 +size 180203 diff --git a/tests/rust/re_integration_test/tests/snapshots/source_component_7.png b/tests/rust/re_integration_test/tests/snapshots/source_component_7.png index 042b040dffd5..04463eb16ee1 100644 --- a/tests/rust/re_integration_test/tests/snapshots/source_component_7.png +++ b/tests/rust/re_integration_test/tests/snapshots/source_component_7.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:73bd20c4976b1fa3a7bb9c161f9c65eec49109d05a1c1ee6db80d7fee5950a9e -size 157167 +oid sha256:15cb9b29a2f5d79f2be0ac3b556d1bc5fb01500aabcea93a2c6b609bf2737f34 +size 159775 From 0b7cf0b23aa0f7a5f1b42c8bad22bf2e1e7f07d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Gyebn=C3=A1r?= Date: Mon, 2 Mar 2026 16:21:03 +0100 Subject: [PATCH 011/513] Tests re_component_ui on light and dark modes ### Related Closes RR-3867 ### What Makes "wide" tests of re_component_ui run in light mode. Source-Ref: 10f4047abea88d0dc498f9267fddb1bd99bd347b --- .../Colormap_placeholder.png | 3 -- .../AbsoluteTimeRange_placeholder.png | 0 .../AggregationPolicy_placeholder.png | 0 .../AlbedoFactor_placeholder.png | 0 .../AngularSpeed_placeholder.png | 0 .../ApplyLatestAt_placeholder.png | 0 .../AutoScroll_placeholder.png | 0 .../AxisLength_placeholder.png | 0 .../BackgroundKind_placeholder.png | 0 .../ChannelId_placeholder.png | 0 .../ChannelMessageCounts_placeholder.png | 0 .../ClearIsRecursive_placeholder.png | 0 .../Color_placeholder.png | 0 .../Colormap_placeholder.png | 3 ++ .../ColumnOrder_placeholder.png | 0 .../ComponentColumnSelector_simple.png | 0 .../ContainerKind_placeholder.png | 0 .../Corner2D_placeholder.png | 0 .../Count_placeholder.png | 0 .../DepthMeter_placeholder.png | 0 .../DrawOrder_placeholder.png | 0 .../Enabled_placeholder.png | 0 .../EntityPath_simple.png | 0 .../Eye3DKind_placeholder.png | 0 .../FillMode_placeholder.png | 0 .../FillRatio_placeholder.png | 0 .../FilterByRange_placeholder.png | 0 .../FilterIsNotNull_placeholder.png | 0 .../ForceDistance_placeholder.png | 0 .../ForceIterations_placeholder.png | 0 .../ForceStrength_placeholder.png | 0 .../Fps_placeholder.png | 0 .../GammaCorrection_placeholder.png | 0 .../GeoLineString_placeholder.png | 0 .../GraphEdge_simple.png | 0 .../GraphNode_simple.png | 0 .../GraphType_placeholder.png | 0 .../GridColumns_placeholder.png | 0 .../GridSpacing_placeholder.png | 0 .../HalfSize2D_placeholder.png | 0 .../HalfSize3D_placeholder.png | 0 .../ImageFormat_depth_f32.png | 0 .../ImageFormat_nv12.png | 0 .../ImageFormat_rgb8.png | 0 .../ImageFormat_rgba8.png | 0 .../ImageFormat_segmentation_u32.png | 0 .../ImagePlaneDistance_placeholder.png | 0 .../Interactive_placeholder.png | 0 .../InterpolationMode_placeholder.png | 0 .../KeyValuePairs_placeholder.png | 0 .../LatLon_placeholder.png | 0 .../Length_placeholder.png | 0 .../LineStrip2D_placeholder.png | 0 .../LineStrip3D_placeholder.png | 0 .../LinearSpeed_placeholder.png | 0 .../LinkAxis_placeholder.png | 0 .../LockRangeDuringZoom_placeholder.png | 0 .../LoopMode_placeholder.png | 0 .../MagnificationFilter_placeholder.png | 0 .../MapProvider_placeholder.png | 0 .../MarkerShape_placeholder.png | 0 .../MarkerSize_placeholder.png | 0 .../MediaType_placeholder.png | 0 .../Name_placeholder.png | 0 .../NearClipPlane_placeholder.png | 0 .../Opacity_placeholder.png | 0 .../PinholeProjection_placeholder.png | 0 .../Plane3D_placeholder.png | 0 .../PlayState_placeholder.png | 0 .../PlaybackSpeed_placeholder.png | 0 .../Position2D_placeholder.png | 0 .../Position3D_placeholder.png | 0 .../QueryExpression_simple.png | 0 .../Radius_placeholder.png | 0 .../Range1D_placeholder.png | 0 .../Resolution_placeholder.png | 0 .../RotationAxisAngle_placeholder.png | 0 .../RotationQuat_placeholder.png | 0 .../Scalar_placeholder.png | 0 .../Scale3D_placeholder.png | 0 .../SchemaId_placeholder.png | 0 .../SelectedColumns_placeholder.png | 0 .../ShowLabels_placeholder.png | 0 .../StrokeWidth_placeholder.png | 0 ...sorDimensionIndexSelection_placeholder.png | 0 ...TensorDimensionIndexSlider_placeholder.png | 0 .../TensorHeightDimension_placeholder.png | 0 .../TensorWidthDimension_placeholder.png | 0 .../Texcoord2D_placeholder.png | 0 .../TextLogColumn_placeholder.png | 0 .../TextLogLevel_placeholder.png | 0 .../Text_simple.png | 0 .../TimeInt_placeholder.png | 0 .../TimeRange_placeholder.png | 0 .../TimelineColumn_placeholder.png | 0 .../TimelineName_placeholder.png | 0 .../Timestamp_placeholder.png | 0 .../TransformFrameId_placeholder.png | 0 .../TransformMat3x3_placeholder.png | 0 .../TransformRelation_placeholder.png | 0 .../Translation3D_placeholder.png | 0 .../TriangleIndices_placeholder.png | 0 .../ValueRange_placeholder.png | 0 .../Vector2D_placeholder.png | 0 .../Vector3D_placeholder.png | 0 .../VideoCodec_placeholder.png | 0 .../VideoSample_placeholder.png | 0 .../VideoTimestamp_placeholder.png | 0 .../ViewClass_placeholder.png | 0 .../ViewCoordinates_placeholder.png | 0 .../ViewFit_placeholder.png | 0 .../VisibleTimeRange_placeholder.png | 0 .../Visible_placeholder.png | 0 .../VisualBounds2D_placeholder.png | 0 ...VisualizerComponentMapping_placeholder.png | 0 .../VisualizerType_placeholder.png | 0 .../ZoomLevel_placeholder.png | 0 .../any_value_any_value_f64.png | 0 ...om_catalog_string_any_value_url_string.png | 0 ...stom_empty_array_any_value_empty_array.png | 0 ...custom_large_blob_any_value_large_blob.png | 0 ...stom_small_array_any_value_small_array.png | 0 .../custom_string_any_value_string.png | 0 ...om_struct_array_any_value_struct_array.png | 0 ..._any_value_struct_array_single_element.png | 0 ...custom_url_string_any_value_url_string.png | 0 .../AbsoluteTimeRange_placeholder.png | 3 -- .../AggregationPolicy_placeholder.png | 3 -- .../AlbedoFactor_placeholder.png | 3 -- .../AngularSpeed_placeholder.png | 3 -- .../ApplyLatestAt_placeholder.png | 3 -- .../AutoScroll_placeholder.png | 3 -- .../AxisLength_placeholder.png | 3 -- .../BackgroundKind_placeholder.png | 3 -- .../ChannelId_placeholder.png | 3 -- .../ChannelMessageCounts_placeholder.png | 3 -- .../ClearIsRecursive_placeholder.png | 3 -- .../Color_placeholder.png | 3 -- .../Colormap_placeholder.png | 3 -- .../ColumnOrder_placeholder.png | 3 -- .../ComponentColumnSelector_simple.png | 3 -- .../ContainerKind_placeholder.png | 3 -- .../Corner2D_placeholder.png | 3 -- .../Count_placeholder.png | 3 -- .../DepthMeter_placeholder.png | 3 -- .../DrawOrder_placeholder.png | 3 -- .../Enabled_placeholder.png | 3 -- .../EntityPath_simple.png | 3 -- .../Eye3DKind_placeholder.png | 3 -- .../FillMode_placeholder.png | 3 -- .../FillRatio_placeholder.png | 3 -- .../FilterByRange_placeholder.png | 3 -- .../FilterIsNotNull_placeholder.png | 3 -- .../ForceDistance_placeholder.png | 3 -- .../ForceIterations_placeholder.png | 3 -- .../ForceStrength_placeholder.png | 3 -- .../Fps_placeholder.png | 3 -- .../GammaCorrection_placeholder.png | 3 -- .../GeoLineString_placeholder.png | 3 -- .../GraphEdge_simple.png | 3 -- .../GraphNode_simple.png | 3 -- .../GraphType_placeholder.png | 3 -- .../GridColumns_placeholder.png | 3 -- .../GridSpacing_placeholder.png | 3 -- .../HalfSize2D_placeholder.png | 3 -- .../HalfSize3D_placeholder.png | 3 -- .../ImageFormat_depth_f32.png | 3 -- .../ImageFormat_nv12.png | 3 -- .../ImageFormat_rgb8.png | 3 -- .../ImageFormat_rgba8.png | 3 -- .../ImageFormat_segmentation_u32.png | 3 -- .../ImagePlaneDistance_placeholder.png | 3 -- .../Interactive_placeholder.png | 3 -- .../InterpolationMode_placeholder.png | 3 -- .../KeyValuePairs_placeholder.png | 3 -- .../LatLon_placeholder.png | 3 -- .../Length_placeholder.png | 3 -- .../LineStrip2D_placeholder.png | 3 -- .../LineStrip3D_placeholder.png | 3 -- .../LinearSpeed_placeholder.png | 3 -- .../LinkAxis_placeholder.png | 3 -- .../LockRangeDuringZoom_placeholder.png | 3 -- .../LoopMode_placeholder.png | 3 -- .../MagnificationFilter_placeholder.png | 3 -- .../MapProvider_placeholder.png | 3 -- .../MarkerShape_placeholder.png | 3 -- .../MarkerSize_placeholder.png | 3 -- .../MediaType_placeholder.png | 3 -- .../Name_placeholder.png | 3 -- .../NearClipPlane_placeholder.png | 3 -- .../Opacity_placeholder.png | 3 -- .../PinholeProjection_placeholder.png | 3 -- .../Plane3D_placeholder.png | 3 -- .../PlayState_placeholder.png | 3 -- .../PlaybackSpeed_placeholder.png | 3 -- .../Position2D_placeholder.png | 3 -- .../Position3D_placeholder.png | 3 -- .../QueryExpression_simple.png | 3 -- .../Radius_placeholder.png | 3 -- .../Range1D_placeholder.png | 3 -- .../Resolution_placeholder.png | 3 -- .../RotationAxisAngle_placeholder.png | 3 -- .../RotationQuat_placeholder.png | 3 -- .../Scalar_placeholder.png | 3 -- .../Scale3D_placeholder.png | 3 -- .../SchemaId_placeholder.png | 3 -- .../SelectedColumns_placeholder.png | 3 -- .../ShowLabels_placeholder.png | 3 -- .../StrokeWidth_placeholder.png | 3 -- ...sorDimensionIndexSelection_placeholder.png | 3 -- ...TensorDimensionIndexSlider_placeholder.png | 3 -- .../TensorHeightDimension_placeholder.png | 3 -- .../TensorWidthDimension_placeholder.png | 3 -- .../Texcoord2D_placeholder.png | 3 -- .../TextLogColumn_placeholder.png | 3 -- .../TextLogLevel_placeholder.png | 3 -- .../Text_simple.png | 3 -- .../TimeInt_placeholder.png | 3 -- .../TimeRange_placeholder.png | 3 -- .../TimelineColumn_placeholder.png | 3 -- .../TimelineName_placeholder.png | 3 -- .../Timestamp_placeholder.png | 3 -- .../TransformFrameId_placeholder.png | 3 -- .../TransformMat3x3_placeholder.png | 3 -- .../TransformRelation_placeholder.png | 3 -- .../Translation3D_placeholder.png | 3 -- .../TriangleIndices_placeholder.png | 3 -- .../ValueRange_placeholder.png | 3 -- .../Vector2D_placeholder.png | 3 -- .../Vector3D_placeholder.png | 3 -- .../VideoCodec_placeholder.png | 3 -- .../VideoSample_placeholder.png | 3 -- .../VideoTimestamp_placeholder.png | 3 -- .../ViewClass_placeholder.png | 3 -- .../ViewCoordinates_placeholder.png | 3 -- .../ViewFit_placeholder.png | 3 -- .../VisibleTimeRange_placeholder.png | 3 -- .../Visible_placeholder.png | 3 -- .../VisualBounds2D_placeholder.png | 3 -- ...VisualizerComponentMapping_placeholder.png | 3 -- .../VisualizerType_placeholder.png | 3 -- .../ZoomLevel_placeholder.png | 3 -- .../any_value_any_value_f64.png | 3 -- ...om_catalog_string_any_value_url_string.png | 3 -- ...stom_empty_array_any_value_empty_array.png | 3 -- ...custom_large_blob_any_value_large_blob.png | 3 -- ...stom_small_array_any_value_small_array.png | 3 -- .../custom_string_any_value_string.png | 3 -- ...om_struct_array_any_value_struct_array.png | 3 -- ..._any_value_struct_array_single_element.png | 3 -- ...custom_url_string_any_value_url_string.png | 3 -- .../AbsoluteTimeRange_placeholder.png | 3 ++ .../AggregationPolicy_placeholder.png | 3 ++ .../AlbedoFactor_placeholder.png | 3 ++ .../AngularSpeed_placeholder.png | 3 ++ .../ApplyLatestAt_placeholder.png | 3 ++ .../AutoScroll_placeholder.png | 3 ++ .../AxisLength_placeholder.png | 3 ++ .../BackgroundKind_placeholder.png | 3 ++ .../ChannelId_placeholder.png | 3 ++ .../ChannelMessageCounts_placeholder.png | 3 ++ .../ClearIsRecursive_placeholder.png | 3 ++ .../Color_placeholder.png | 3 ++ .../Colormap_placeholder.png | 3 ++ .../ColumnOrder_placeholder.png | 3 ++ .../ComponentColumnSelector_simple.png | 3 ++ .../ContainerKind_placeholder.png | 3 ++ .../Corner2D_placeholder.png | 3 ++ .../Count_placeholder.png | 3 ++ .../DepthMeter_placeholder.png | 3 ++ .../DrawOrder_placeholder.png | 3 ++ .../Enabled_placeholder.png | 3 ++ .../EntityPath_simple.png | 3 ++ .../Eye3DKind_placeholder.png | 3 ++ .../FillMode_placeholder.png | 3 ++ .../FillRatio_placeholder.png | 3 ++ .../FilterByRange_placeholder.png | 3 ++ .../FilterIsNotNull_placeholder.png | 3 ++ .../ForceDistance_placeholder.png | 3 ++ .../ForceIterations_placeholder.png | 3 ++ .../ForceStrength_placeholder.png | 3 ++ .../Fps_placeholder.png | 3 ++ .../GammaCorrection_placeholder.png | 3 ++ .../GeoLineString_placeholder.png | 3 ++ .../GraphEdge_simple.png | 3 ++ .../GraphNode_simple.png | 3 ++ .../GraphType_placeholder.png | 3 ++ .../GridColumns_placeholder.png | 3 ++ .../GridSpacing_placeholder.png | 3 ++ .../HalfSize2D_placeholder.png | 3 ++ .../HalfSize3D_placeholder.png | 3 ++ .../ImageFormat_depth_f32.png | 3 ++ .../ImageFormat_nv12.png | 3 ++ .../ImageFormat_rgb8.png | 3 ++ .../ImageFormat_rgba8.png | 3 ++ .../ImageFormat_segmentation_u32.png | 3 ++ .../ImagePlaneDistance_placeholder.png | 3 ++ .../Interactive_placeholder.png | 3 ++ .../InterpolationMode_placeholder.png | 3 ++ .../KeyValuePairs_placeholder.png | 3 ++ .../LatLon_placeholder.png | 3 ++ .../Length_placeholder.png | 3 ++ .../LineStrip2D_placeholder.png | 3 ++ .../LineStrip3D_placeholder.png | 3 ++ .../LinearSpeed_placeholder.png | 3 ++ .../LinkAxis_placeholder.png | 3 ++ .../LockRangeDuringZoom_placeholder.png | 3 ++ .../LoopMode_placeholder.png | 3 ++ .../MagnificationFilter_placeholder.png | 3 ++ .../MapProvider_placeholder.png | 3 ++ .../MarkerShape_placeholder.png | 3 ++ .../MarkerSize_placeholder.png | 3 ++ .../MediaType_placeholder.png | 3 ++ .../Name_placeholder.png | 3 ++ .../NearClipPlane_placeholder.png | 3 ++ .../Opacity_placeholder.png | 3 ++ .../PinholeProjection_placeholder.png | 3 ++ .../Plane3D_placeholder.png | 3 ++ .../PlayState_placeholder.png | 3 ++ .../PlaybackSpeed_placeholder.png | 3 ++ .../Position2D_placeholder.png | 3 ++ .../Position3D_placeholder.png | 3 ++ .../QueryExpression_simple.png | 3 ++ .../Radius_placeholder.png | 3 ++ .../Range1D_placeholder.png | 3 ++ .../Resolution_placeholder.png | 3 ++ .../RotationAxisAngle_placeholder.png | 3 ++ .../RotationQuat_placeholder.png | 3 ++ .../Scalar_placeholder.png | 3 ++ .../Scale3D_placeholder.png | 3 ++ .../SchemaId_placeholder.png | 3 ++ .../SelectedColumns_placeholder.png | 3 ++ .../ShowLabels_placeholder.png | 3 ++ .../StrokeWidth_placeholder.png | 3 ++ ...sorDimensionIndexSelection_placeholder.png | 3 ++ ...TensorDimensionIndexSlider_placeholder.png | 3 ++ .../TensorHeightDimension_placeholder.png | 3 ++ .../TensorWidthDimension_placeholder.png | 3 ++ .../Texcoord2D_placeholder.png | 3 ++ .../TextLogColumn_placeholder.png | 3 ++ .../TextLogLevel_placeholder.png | 3 ++ .../Text_simple.png | 3 ++ .../TimeInt_placeholder.png | 3 ++ .../TimeRange_placeholder.png | 3 ++ .../TimelineColumn_placeholder.png | 3 ++ .../TimelineName_placeholder.png | 3 ++ .../Timestamp_placeholder.png | 3 ++ .../TransformFrameId_placeholder.png | 3 ++ .../TransformMat3x3_placeholder.png | 3 ++ .../TransformRelation_placeholder.png | 3 ++ .../Translation3D_placeholder.png | 3 ++ .../TriangleIndices_placeholder.png | 3 ++ .../ValueRange_placeholder.png | 3 ++ .../Vector2D_placeholder.png | 3 ++ .../Vector3D_placeholder.png | 3 ++ .../VideoCodec_placeholder.png | 3 ++ .../VideoSample_placeholder.png | 3 ++ .../VideoTimestamp_placeholder.png | 3 ++ .../ViewClass_placeholder.png | 3 ++ .../ViewCoordinates_placeholder.png | 3 ++ .../ViewFit_placeholder.png | 3 ++ .../VisibleTimeRange_placeholder.png | 3 ++ .../Visible_placeholder.png | 3 ++ .../VisualBounds2D_placeholder.png | 3 ++ ...VisualizerComponentMapping_placeholder.png | 3 ++ .../VisualizerType_placeholder.png | 3 ++ .../ZoomLevel_placeholder.png | 3 ++ .../any_value_any_value_f64.png | 3 ++ ...om_catalog_string_any_value_url_string.png | 3 ++ ...stom_empty_array_any_value_empty_array.png | 3 ++ ...custom_large_blob_any_value_large_blob.png | 3 ++ ...stom_small_array_any_value_small_array.png | 3 ++ .../custom_string_any_value_string.png | 3 ++ ...om_struct_array_any_value_struct_array.png | 3 ++ ..._any_value_struct_array_single_element.png | 3 ++ ...custom_url_string_any_value_url_string.png | 3 ++ .../tests/test_all_components_ui.rs | 46 ++++++++----------- 377 files changed, 397 insertions(+), 405 deletions(-) delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/Colormap_placeholder.png rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/AbsoluteTimeRange_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/AggregationPolicy_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/AlbedoFactor_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/AngularSpeed_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/ApplyLatestAt_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/AutoScroll_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/AxisLength_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/BackgroundKind_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/ChannelId_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/ChannelMessageCounts_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/ClearIsRecursive_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/Color_placeholder.png (100%) create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/Colormap_placeholder.png rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/ColumnOrder_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/ComponentColumnSelector_simple.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/ContainerKind_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/Corner2D_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/Count_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/DepthMeter_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/DrawOrder_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/Enabled_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/EntityPath_simple.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/Eye3DKind_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/FillMode_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/FillRatio_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/FilterByRange_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/FilterIsNotNull_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/ForceDistance_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/ForceIterations_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/ForceStrength_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/Fps_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/GammaCorrection_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/GeoLineString_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/GraphEdge_simple.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/GraphNode_simple.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/GraphType_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/GridColumns_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/GridSpacing_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/HalfSize2D_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/HalfSize3D_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/ImageFormat_depth_f32.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/ImageFormat_nv12.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/ImageFormat_rgb8.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/ImageFormat_rgba8.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/ImageFormat_segmentation_u32.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/ImagePlaneDistance_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/Interactive_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/InterpolationMode_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/KeyValuePairs_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/LatLon_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/Length_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/LineStrip2D_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/LineStrip3D_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/LinearSpeed_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/LinkAxis_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/LockRangeDuringZoom_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/LoopMode_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/MagnificationFilter_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/MapProvider_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/MarkerShape_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/MarkerSize_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/MediaType_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/Name_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/NearClipPlane_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/Opacity_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/PinholeProjection_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/Plane3D_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/PlayState_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/PlaybackSpeed_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/Position2D_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/Position3D_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/QueryExpression_simple.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/Radius_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/Range1D_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/Resolution_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/RotationAxisAngle_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/RotationQuat_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/Scalar_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/Scale3D_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/SchemaId_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/SelectedColumns_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/ShowLabels_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/StrokeWidth_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/TensorDimensionIndexSelection_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/TensorDimensionIndexSlider_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/TensorHeightDimension_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/TensorWidthDimension_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/Texcoord2D_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/TextLogColumn_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/TextLogLevel_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/Text_simple.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/TimeInt_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/TimeRange_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/TimelineColumn_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/TimelineName_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/Timestamp_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/TransformFrameId_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/TransformMat3x3_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/TransformRelation_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/Translation3D_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/TriangleIndices_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/ValueRange_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/Vector2D_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/Vector3D_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/VideoCodec_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/VideoSample_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/VideoTimestamp_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/ViewClass_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/ViewCoordinates_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/ViewFit_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/VisibleTimeRange_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/Visible_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/VisualBounds2D_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/VisualizerComponentMapping_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/VisualizerType_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/ZoomLevel_placeholder.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/any_value_any_value_f64.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/custom_catalog_string_any_value_url_string.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/custom_empty_array_any_value_empty_array.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/custom_large_blob_any_value_large_blob.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/custom_small_array_any_value_small_array.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/custom_string_any_value_string.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/custom_struct_array_any_value_struct_array.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/custom_struct_array_single_element_any_value_struct_array_single_element.png (100%) rename crates/viewer/re_component_ui/tests/snapshots/{all_components_list_item_narrow => all_components_list_item_narrow_dark}/custom_url_string_any_value_url_string.png (100%) delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/AbsoluteTimeRange_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/AggregationPolicy_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/AlbedoFactor_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/AngularSpeed_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/ApplyLatestAt_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/AutoScroll_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/AxisLength_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/BackgroundKind_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/ChannelId_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/ChannelMessageCounts_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/ClearIsRecursive_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Color_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Colormap_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/ColumnOrder_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/ComponentColumnSelector_simple.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/ContainerKind_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Corner2D_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Count_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/DepthMeter_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/DrawOrder_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Enabled_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/EntityPath_simple.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Eye3DKind_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/FillMode_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/FillRatio_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/FilterByRange_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/FilterIsNotNull_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/ForceDistance_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/ForceIterations_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/ForceStrength_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Fps_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/GammaCorrection_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/GeoLineString_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/GraphEdge_simple.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/GraphNode_simple.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/GraphType_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/GridColumns_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/GridSpacing_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/HalfSize2D_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/HalfSize3D_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/ImageFormat_depth_f32.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/ImageFormat_nv12.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/ImageFormat_rgb8.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/ImageFormat_rgba8.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/ImageFormat_segmentation_u32.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/ImagePlaneDistance_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Interactive_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/InterpolationMode_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/KeyValuePairs_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/LatLon_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Length_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/LineStrip2D_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/LineStrip3D_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/LinearSpeed_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/LinkAxis_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/LockRangeDuringZoom_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/LoopMode_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/MagnificationFilter_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/MapProvider_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/MarkerShape_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/MarkerSize_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/MediaType_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Name_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/NearClipPlane_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Opacity_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/PinholeProjection_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Plane3D_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/PlayState_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/PlaybackSpeed_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Position2D_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Position3D_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/QueryExpression_simple.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Radius_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Range1D_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Resolution_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/RotationAxisAngle_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/RotationQuat_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Scalar_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Scale3D_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/SchemaId_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/SelectedColumns_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/ShowLabels_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/StrokeWidth_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/TensorDimensionIndexSelection_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/TensorDimensionIndexSlider_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/TensorHeightDimension_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/TensorWidthDimension_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Texcoord2D_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/TextLogColumn_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/TextLogLevel_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Text_simple.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/TimeInt_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/TimeRange_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/TimelineColumn_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/TimelineName_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Timestamp_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/TransformFrameId_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/TransformMat3x3_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/TransformRelation_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Translation3D_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/TriangleIndices_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/ValueRange_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Vector2D_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Vector3D_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/VideoCodec_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/VideoSample_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/VideoTimestamp_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/ViewClass_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/ViewCoordinates_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/ViewFit_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/VisibleTimeRange_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Visible_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/VisualBounds2D_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/VisualizerComponentMapping_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/VisualizerType_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/ZoomLevel_placeholder.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/any_value_any_value_f64.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/custom_catalog_string_any_value_url_string.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/custom_empty_array_any_value_empty_array.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/custom_large_blob_any_value_large_blob.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/custom_small_array_any_value_small_array.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/custom_string_any_value_string.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/custom_struct_array_any_value_struct_array.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/custom_struct_array_single_element_any_value_struct_array_single_element.png delete mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/custom_url_string_any_value_url_string.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/AbsoluteTimeRange_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/AggregationPolicy_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/AlbedoFactor_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/AngularSpeed_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/ApplyLatestAt_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/AutoScroll_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/AxisLength_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/BackgroundKind_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/ChannelId_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/ChannelMessageCounts_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/ClearIsRecursive_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Color_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Colormap_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/ColumnOrder_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/ComponentColumnSelector_simple.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/ContainerKind_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Corner2D_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Count_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/DepthMeter_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/DrawOrder_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Enabled_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/EntityPath_simple.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Eye3DKind_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/FillMode_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/FillRatio_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/FilterByRange_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/FilterIsNotNull_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/ForceDistance_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/ForceIterations_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/ForceStrength_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Fps_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/GammaCorrection_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/GeoLineString_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/GraphEdge_simple.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/GraphNode_simple.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/GraphType_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/GridColumns_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/GridSpacing_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/HalfSize2D_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/HalfSize3D_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/ImageFormat_depth_f32.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/ImageFormat_nv12.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/ImageFormat_rgb8.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/ImageFormat_rgba8.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/ImageFormat_segmentation_u32.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/ImagePlaneDistance_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Interactive_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/InterpolationMode_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/KeyValuePairs_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/LatLon_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Length_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/LineStrip2D_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/LineStrip3D_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/LinearSpeed_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/LinkAxis_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/LockRangeDuringZoom_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/LoopMode_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/MagnificationFilter_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/MapProvider_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/MarkerShape_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/MarkerSize_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/MediaType_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Name_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/NearClipPlane_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Opacity_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/PinholeProjection_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Plane3D_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/PlayState_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/PlaybackSpeed_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Position2D_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Position3D_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/QueryExpression_simple.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Radius_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Range1D_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Resolution_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/RotationAxisAngle_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/RotationQuat_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Scalar_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Scale3D_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/SchemaId_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/SelectedColumns_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/ShowLabels_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/StrokeWidth_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/TensorDimensionIndexSelection_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/TensorDimensionIndexSlider_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/TensorHeightDimension_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/TensorWidthDimension_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Texcoord2D_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/TextLogColumn_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/TextLogLevel_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Text_simple.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/TimeInt_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/TimeRange_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/TimelineColumn_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/TimelineName_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Timestamp_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/TransformFrameId_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/TransformMat3x3_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/TransformRelation_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Translation3D_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/TriangleIndices_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/ValueRange_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Vector2D_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Vector3D_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/VideoCodec_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/VideoSample_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/VideoTimestamp_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/ViewClass_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/ViewCoordinates_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/ViewFit_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/VisibleTimeRange_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Visible_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/VisualBounds2D_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/VisualizerComponentMapping_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/VisualizerType_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/ZoomLevel_placeholder.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/any_value_any_value_f64.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/custom_catalog_string_any_value_url_string.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/custom_empty_array_any_value_empty_array.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/custom_large_blob_any_value_large_blob.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/custom_small_array_any_value_small_array.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/custom_string_any_value_string.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/custom_struct_array_any_value_struct_array.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/custom_struct_array_single_element_any_value_struct_array_single_element.png create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/custom_url_string_any_value_url_string.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/Colormap_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/Colormap_placeholder.png deleted file mode 100644 index ba623b28de59..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/Colormap_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7a12a7660bd50018324a57e2caf507167d78ba19ce654ec32beb17ff7824b31b -size 3011 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/AbsoluteTimeRange_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/AbsoluteTimeRange_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/AbsoluteTimeRange_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/AbsoluteTimeRange_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/AggregationPolicy_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/AggregationPolicy_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/AggregationPolicy_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/AggregationPolicy_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/AlbedoFactor_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/AlbedoFactor_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/AlbedoFactor_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/AlbedoFactor_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/AngularSpeed_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/AngularSpeed_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/AngularSpeed_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/AngularSpeed_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/ApplyLatestAt_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/ApplyLatestAt_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/ApplyLatestAt_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/ApplyLatestAt_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/AutoScroll_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/AutoScroll_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/AutoScroll_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/AutoScroll_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/AxisLength_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/AxisLength_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/AxisLength_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/AxisLength_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/BackgroundKind_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/BackgroundKind_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/BackgroundKind_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/BackgroundKind_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/ChannelId_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/ChannelId_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/ChannelId_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/ChannelId_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/ChannelMessageCounts_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/ChannelMessageCounts_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/ChannelMessageCounts_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/ChannelMessageCounts_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/ClearIsRecursive_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/ClearIsRecursive_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/ClearIsRecursive_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/ClearIsRecursive_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/Color_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/Color_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/Color_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/Color_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/Colormap_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/Colormap_placeholder.png new file mode 100644 index 000000000000..b734c7d32773 --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/Colormap_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b3f612e37f07b5a38908605581a0fea6815e5e78eee764c91cd19071f14a77d1 +size 3011 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/ColumnOrder_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/ColumnOrder_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/ColumnOrder_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/ColumnOrder_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/ComponentColumnSelector_simple.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/ComponentColumnSelector_simple.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/ComponentColumnSelector_simple.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/ComponentColumnSelector_simple.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/ContainerKind_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/ContainerKind_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/ContainerKind_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/ContainerKind_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/Corner2D_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/Corner2D_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/Corner2D_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/Corner2D_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/Count_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/Count_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/Count_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/Count_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/DepthMeter_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/DepthMeter_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/DepthMeter_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/DepthMeter_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/DrawOrder_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/DrawOrder_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/DrawOrder_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/DrawOrder_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/Enabled_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/Enabled_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/Enabled_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/Enabled_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/EntityPath_simple.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/EntityPath_simple.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/EntityPath_simple.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/EntityPath_simple.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/Eye3DKind_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/Eye3DKind_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/Eye3DKind_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/Eye3DKind_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/FillMode_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/FillMode_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/FillMode_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/FillMode_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/FillRatio_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/FillRatio_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/FillRatio_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/FillRatio_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/FilterByRange_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/FilterByRange_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/FilterByRange_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/FilterByRange_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/FilterIsNotNull_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/FilterIsNotNull_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/FilterIsNotNull_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/FilterIsNotNull_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/ForceDistance_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/ForceDistance_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/ForceDistance_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/ForceDistance_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/ForceIterations_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/ForceIterations_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/ForceIterations_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/ForceIterations_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/ForceStrength_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/ForceStrength_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/ForceStrength_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/ForceStrength_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/Fps_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/Fps_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/Fps_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/Fps_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/GammaCorrection_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/GammaCorrection_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/GammaCorrection_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/GammaCorrection_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/GeoLineString_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/GeoLineString_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/GeoLineString_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/GeoLineString_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/GraphEdge_simple.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/GraphEdge_simple.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/GraphEdge_simple.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/GraphEdge_simple.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/GraphNode_simple.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/GraphNode_simple.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/GraphNode_simple.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/GraphNode_simple.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/GraphType_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/GraphType_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/GraphType_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/GraphType_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/GridColumns_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/GridColumns_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/GridColumns_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/GridColumns_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/GridSpacing_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/GridSpacing_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/GridSpacing_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/GridSpacing_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/HalfSize2D_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/HalfSize2D_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/HalfSize2D_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/HalfSize2D_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/HalfSize3D_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/HalfSize3D_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/HalfSize3D_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/HalfSize3D_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/ImageFormat_depth_f32.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/ImageFormat_depth_f32.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/ImageFormat_depth_f32.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/ImageFormat_depth_f32.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/ImageFormat_nv12.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/ImageFormat_nv12.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/ImageFormat_nv12.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/ImageFormat_nv12.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/ImageFormat_rgb8.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/ImageFormat_rgb8.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/ImageFormat_rgb8.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/ImageFormat_rgb8.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/ImageFormat_rgba8.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/ImageFormat_rgba8.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/ImageFormat_rgba8.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/ImageFormat_rgba8.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/ImageFormat_segmentation_u32.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/ImageFormat_segmentation_u32.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/ImageFormat_segmentation_u32.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/ImageFormat_segmentation_u32.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/ImagePlaneDistance_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/ImagePlaneDistance_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/ImagePlaneDistance_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/ImagePlaneDistance_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/Interactive_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/Interactive_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/Interactive_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/Interactive_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/InterpolationMode_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/InterpolationMode_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/InterpolationMode_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/InterpolationMode_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/KeyValuePairs_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/KeyValuePairs_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/KeyValuePairs_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/KeyValuePairs_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/LatLon_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/LatLon_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/LatLon_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/LatLon_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/Length_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/Length_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/Length_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/Length_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/LineStrip2D_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/LineStrip2D_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/LineStrip2D_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/LineStrip2D_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/LineStrip3D_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/LineStrip3D_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/LineStrip3D_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/LineStrip3D_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/LinearSpeed_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/LinearSpeed_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/LinearSpeed_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/LinearSpeed_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/LinkAxis_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/LinkAxis_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/LinkAxis_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/LinkAxis_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/LockRangeDuringZoom_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/LockRangeDuringZoom_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/LockRangeDuringZoom_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/LockRangeDuringZoom_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/LoopMode_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/LoopMode_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/LoopMode_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/LoopMode_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/MagnificationFilter_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/MagnificationFilter_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/MagnificationFilter_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/MagnificationFilter_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/MapProvider_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/MapProvider_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/MapProvider_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/MapProvider_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/MarkerShape_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/MarkerShape_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/MarkerShape_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/MarkerShape_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/MarkerSize_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/MarkerSize_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/MarkerSize_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/MarkerSize_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/MediaType_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/MediaType_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/MediaType_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/MediaType_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/Name_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/Name_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/Name_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/Name_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/NearClipPlane_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/NearClipPlane_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/NearClipPlane_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/NearClipPlane_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/Opacity_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/Opacity_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/Opacity_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/Opacity_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/PinholeProjection_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/PinholeProjection_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/PinholeProjection_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/PinholeProjection_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/Plane3D_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/Plane3D_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/Plane3D_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/Plane3D_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/PlayState_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/PlayState_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/PlayState_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/PlayState_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/PlaybackSpeed_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/PlaybackSpeed_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/PlaybackSpeed_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/PlaybackSpeed_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/Position2D_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/Position2D_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/Position2D_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/Position2D_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/Position3D_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/Position3D_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/Position3D_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/Position3D_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/QueryExpression_simple.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/QueryExpression_simple.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/QueryExpression_simple.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/QueryExpression_simple.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/Radius_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/Radius_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/Radius_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/Radius_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/Range1D_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/Range1D_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/Range1D_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/Range1D_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/Resolution_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/Resolution_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/Resolution_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/Resolution_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/RotationAxisAngle_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/RotationAxisAngle_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/RotationAxisAngle_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/RotationAxisAngle_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/RotationQuat_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/RotationQuat_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/RotationQuat_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/RotationQuat_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/Scalar_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/Scalar_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/Scalar_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/Scalar_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/Scale3D_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/Scale3D_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/Scale3D_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/Scale3D_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/SchemaId_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/SchemaId_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/SchemaId_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/SchemaId_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/SelectedColumns_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/SelectedColumns_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/SelectedColumns_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/SelectedColumns_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/ShowLabels_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/ShowLabels_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/ShowLabels_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/ShowLabels_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/StrokeWidth_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/StrokeWidth_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/StrokeWidth_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/StrokeWidth_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/TensorDimensionIndexSelection_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/TensorDimensionIndexSelection_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/TensorDimensionIndexSelection_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/TensorDimensionIndexSelection_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/TensorDimensionIndexSlider_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/TensorDimensionIndexSlider_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/TensorDimensionIndexSlider_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/TensorDimensionIndexSlider_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/TensorHeightDimension_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/TensorHeightDimension_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/TensorHeightDimension_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/TensorHeightDimension_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/TensorWidthDimension_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/TensorWidthDimension_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/TensorWidthDimension_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/TensorWidthDimension_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/Texcoord2D_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/Texcoord2D_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/Texcoord2D_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/Texcoord2D_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/TextLogColumn_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/TextLogColumn_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/TextLogColumn_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/TextLogColumn_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/TextLogLevel_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/TextLogLevel_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/TextLogLevel_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/TextLogLevel_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/Text_simple.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/Text_simple.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/Text_simple.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/Text_simple.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/TimeInt_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/TimeInt_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/TimeInt_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/TimeInt_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/TimeRange_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/TimeRange_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/TimeRange_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/TimeRange_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/TimelineColumn_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/TimelineColumn_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/TimelineColumn_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/TimelineColumn_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/TimelineName_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/TimelineName_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/TimelineName_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/TimelineName_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/Timestamp_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/Timestamp_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/Timestamp_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/Timestamp_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/TransformFrameId_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/TransformFrameId_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/TransformFrameId_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/TransformFrameId_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/TransformMat3x3_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/TransformMat3x3_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/TransformMat3x3_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/TransformMat3x3_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/TransformRelation_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/TransformRelation_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/TransformRelation_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/TransformRelation_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/Translation3D_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/Translation3D_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/Translation3D_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/Translation3D_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/TriangleIndices_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/TriangleIndices_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/TriangleIndices_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/TriangleIndices_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/ValueRange_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/ValueRange_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/ValueRange_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/ValueRange_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/Vector2D_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/Vector2D_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/Vector2D_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/Vector2D_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/Vector3D_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/Vector3D_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/Vector3D_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/Vector3D_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/VideoCodec_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/VideoCodec_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/VideoCodec_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/VideoCodec_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/VideoSample_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/VideoSample_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/VideoSample_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/VideoSample_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/VideoTimestamp_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/VideoTimestamp_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/VideoTimestamp_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/VideoTimestamp_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/ViewClass_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/ViewClass_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/ViewClass_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/ViewClass_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/ViewCoordinates_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/ViewCoordinates_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/ViewCoordinates_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/ViewCoordinates_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/ViewFit_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/ViewFit_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/ViewFit_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/ViewFit_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/VisibleTimeRange_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/VisibleTimeRange_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/VisibleTimeRange_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/VisibleTimeRange_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/Visible_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/Visible_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/Visible_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/Visible_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/VisualBounds2D_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/VisualBounds2D_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/VisualBounds2D_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/VisualBounds2D_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/VisualizerComponentMapping_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/VisualizerComponentMapping_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/VisualizerComponentMapping_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/VisualizerComponentMapping_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/VisualizerType_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/VisualizerType_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/VisualizerType_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/VisualizerType_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/ZoomLevel_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/ZoomLevel_placeholder.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/ZoomLevel_placeholder.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/ZoomLevel_placeholder.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/any_value_any_value_f64.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/any_value_any_value_f64.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/any_value_any_value_f64.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/any_value_any_value_f64.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/custom_catalog_string_any_value_url_string.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/custom_catalog_string_any_value_url_string.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/custom_catalog_string_any_value_url_string.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/custom_catalog_string_any_value_url_string.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/custom_empty_array_any_value_empty_array.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/custom_empty_array_any_value_empty_array.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/custom_empty_array_any_value_empty_array.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/custom_empty_array_any_value_empty_array.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/custom_large_blob_any_value_large_blob.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/custom_large_blob_any_value_large_blob.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/custom_large_blob_any_value_large_blob.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/custom_large_blob_any_value_large_blob.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/custom_small_array_any_value_small_array.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/custom_small_array_any_value_small_array.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/custom_small_array_any_value_small_array.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/custom_small_array_any_value_small_array.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/custom_string_any_value_string.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/custom_string_any_value_string.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/custom_string_any_value_string.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/custom_string_any_value_string.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/custom_struct_array_any_value_struct_array.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/custom_struct_array_any_value_struct_array.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/custom_struct_array_any_value_struct_array.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/custom_struct_array_any_value_struct_array.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/custom_struct_array_single_element_any_value_struct_array_single_element.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/custom_struct_array_single_element_any_value_struct_array_single_element.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/custom_struct_array_single_element_any_value_struct_array_single_element.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/custom_struct_array_single_element_any_value_struct_array_single_element.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/custom_url_string_any_value_url_string.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/custom_url_string_any_value_url_string.png similarity index 100% rename from crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow/custom_url_string_any_value_url_string.png rename to crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/custom_url_string_any_value_url_string.png diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/AbsoluteTimeRange_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/AbsoluteTimeRange_placeholder.png deleted file mode 100644 index f447c05628f7..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/AbsoluteTimeRange_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ac2fe3345bdf8423d3d2790d7c89086cc3cdba6e9f61800ac5e9ebe50c984b08 -size 4777 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/AggregationPolicy_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/AggregationPolicy_placeholder.png deleted file mode 100644 index 4a2efab9f0f5..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/AggregationPolicy_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ba013932a0b6330dba5d8bedb050cd83c10e94829f42c956362bbc9c0138a6b6 -size 3760 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/AlbedoFactor_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/AlbedoFactor_placeholder.png deleted file mode 100644 index 93112b75da51..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/AlbedoFactor_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d4c02aa31dfaa335e6ee621d117df35d9dc3803f4eda367cb3f4a722a536f217 -size 3127 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/AngularSpeed_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/AngularSpeed_placeholder.png deleted file mode 100644 index ca9bf652ace7..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/AngularSpeed_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cd32060b8eea2bd13fca0f175eedfcaa478e9990743e515a8a8937c076f3c83c -size 3079 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/ApplyLatestAt_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/ApplyLatestAt_placeholder.png deleted file mode 100644 index d7a7cb8d7ef9..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/ApplyLatestAt_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:da1768017d2e9d83618443386afdba940f49d252bca307700eb10b555ab9aa51 -size 3632 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/AutoScroll_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/AutoScroll_placeholder.png deleted file mode 100644 index d7a7cb8d7ef9..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/AutoScroll_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:da1768017d2e9d83618443386afdba940f49d252bca307700eb10b555ab9aa51 -size 3632 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/AxisLength_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/AxisLength_placeholder.png deleted file mode 100644 index 67789498331d..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/AxisLength_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f352ad40ae0aeb3d768fc9cfbb5a143506689120e7092a7973a06ae50a1e7d4b -size 2993 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/BackgroundKind_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/BackgroundKind_placeholder.png deleted file mode 100644 index da9c74c2335d..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/BackgroundKind_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1520566a1a55591bc5247736d2927c655d6547c8354a8f2b516bad16e341ae3f -size 4668 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/ChannelId_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/ChannelId_placeholder.png deleted file mode 100644 index ca9bf652ace7..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/ChannelId_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cd32060b8eea2bd13fca0f175eedfcaa478e9990743e515a8a8937c076f3c83c -size 3079 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/ChannelMessageCounts_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/ChannelMessageCounts_placeholder.png deleted file mode 100644 index d3d861aa3ccd..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/ChannelMessageCounts_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ab62f2af4b5f74fb035ec73d3f450f7b76f6215046d44bdafd887d39e7fb5097 -size 2987 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/ClearIsRecursive_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/ClearIsRecursive_placeholder.png deleted file mode 100644 index d7a7cb8d7ef9..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/ClearIsRecursive_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:da1768017d2e9d83618443386afdba940f49d252bca307700eb10b555ab9aa51 -size 3632 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Color_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Color_placeholder.png deleted file mode 100644 index 93112b75da51..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Color_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d4c02aa31dfaa335e6ee621d117df35d9dc3803f4eda367cb3f4a722a536f217 -size 3127 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Colormap_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Colormap_placeholder.png deleted file mode 100644 index 1b1073d90c4a..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Colormap_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2aa810d20be5c088948f9a8ff7806327716e8825bf4d90cebaefb67fdeebe3f2 -size 4019 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/ColumnOrder_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/ColumnOrder_placeholder.png deleted file mode 100644 index d3d861aa3ccd..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/ColumnOrder_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ab62f2af4b5f74fb035ec73d3f450f7b76f6215046d44bdafd887d39e7fb5097 -size 2987 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/ComponentColumnSelector_simple.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/ComponentColumnSelector_simple.png deleted file mode 100644 index e10cc6da6a70..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/ComponentColumnSelector_simple.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:55e418ca16ec264cb3d28367f427db4c35c7be6cd6dcf45f23993bc9e52f6987 -size 10546 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/ContainerKind_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/ContainerKind_placeholder.png deleted file mode 100644 index cb7f0d4ec966..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/ContainerKind_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a6b0d0c394ad3f81c1d302a3f3918799fe6478b787cabee0770a06149a100853 -size 3034 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Corner2D_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Corner2D_placeholder.png deleted file mode 100644 index 1234e062611e..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Corner2D_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9810c87726acff02725afa3d1f1baa964bbf5b9fdb4f98149560865d9d62bb55 -size 4359 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Count_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Count_placeholder.png deleted file mode 100644 index ca9bf652ace7..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Count_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cd32060b8eea2bd13fca0f175eedfcaa478e9990743e515a8a8937c076f3c83c -size 3079 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/DepthMeter_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/DepthMeter_placeholder.png deleted file mode 100644 index 67789498331d..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/DepthMeter_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f352ad40ae0aeb3d768fc9cfbb5a143506689120e7092a7973a06ae50a1e7d4b -size 2993 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/DrawOrder_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/DrawOrder_placeholder.png deleted file mode 100644 index ca9bf652ace7..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/DrawOrder_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cd32060b8eea2bd13fca0f175eedfcaa478e9990743e515a8a8937c076f3c83c -size 3079 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Enabled_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Enabled_placeholder.png deleted file mode 100644 index 7f47bbe478be..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Enabled_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5873bb2fdb4d52282a274c38eb321f710416d92a97f49b4516cd55ccdeefcf13 -size 3013 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/EntityPath_simple.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/EntityPath_simple.png deleted file mode 100644 index 11937dea85e2..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/EntityPath_simple.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:13dfed50e61965752b96fcee355a2ab2a8ec13853c3270ca4321d8b2b45210da -size 5882 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Eye3DKind_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Eye3DKind_placeholder.png deleted file mode 100644 index 6bbdee28b6b6..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Eye3DKind_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:78c52a6694933e630e7845157d9daf0a6a0b18cf0f860ea8aa1f0a49c31d0ed0 -size 3716 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/FillMode_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/FillMode_placeholder.png deleted file mode 100644 index dfaeeb1b443d..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/FillMode_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:fb89e2f0b1db3f204369bf6c154b7f4c35b206f93c9a420c78ebb48e2164202b -size 4921 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/FillRatio_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/FillRatio_placeholder.png deleted file mode 100644 index 67789498331d..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/FillRatio_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f352ad40ae0aeb3d768fc9cfbb5a143506689120e7092a7973a06ae50a1e7d4b -size 2993 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/FilterByRange_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/FilterByRange_placeholder.png deleted file mode 100644 index 3a0c078f319b..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/FilterByRange_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6c05b4de94a442942b2c8f772ec5ced8905e81778fb1413cfd9294451cf2e67e -size 11658 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/FilterIsNotNull_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/FilterIsNotNull_placeholder.png deleted file mode 100644 index 8d9a3a3cb8f9..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/FilterIsNotNull_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:47688986ddd9b06508e04260f52fbc5bfe191b3bfc299df595594d06cb66dcfe -size 9299 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/ForceDistance_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/ForceDistance_placeholder.png deleted file mode 100644 index ca9bf652ace7..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/ForceDistance_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cd32060b8eea2bd13fca0f175eedfcaa478e9990743e515a8a8937c076f3c83c -size 3079 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/ForceIterations_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/ForceIterations_placeholder.png deleted file mode 100644 index ca9bf652ace7..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/ForceIterations_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cd32060b8eea2bd13fca0f175eedfcaa478e9990743e515a8a8937c076f3c83c -size 3079 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/ForceStrength_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/ForceStrength_placeholder.png deleted file mode 100644 index ca9bf652ace7..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/ForceStrength_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cd32060b8eea2bd13fca0f175eedfcaa478e9990743e515a8a8937c076f3c83c -size 3079 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Fps_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Fps_placeholder.png deleted file mode 100644 index ca9bf652ace7..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Fps_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cd32060b8eea2bd13fca0f175eedfcaa478e9990743e515a8a8937c076f3c83c -size 3079 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/GammaCorrection_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/GammaCorrection_placeholder.png deleted file mode 100644 index 67789498331d..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/GammaCorrection_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f352ad40ae0aeb3d768fc9cfbb5a143506689120e7092a7973a06ae50a1e7d4b -size 2993 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/GeoLineString_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/GeoLineString_placeholder.png deleted file mode 100644 index eef25b6e2866..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/GeoLineString_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ed8abbcc581551169a0f61d7286d8858a71d422d222dee75ce9ba455d147ae19 -size 4432 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/GraphEdge_simple.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/GraphEdge_simple.png deleted file mode 100644 index ee81a0eebae5..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/GraphEdge_simple.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a939500dc7ae7dc7c8d6fbf001ece299756b55e4faaa1e5db836d8ea6b846f26 -size 6800 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/GraphNode_simple.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/GraphNode_simple.png deleted file mode 100644 index 2ac7e2a38152..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/GraphNode_simple.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4da725082db0e9b20664468de81cc778d6a5e044c81efe47bfe4018b511f3cf0 -size 4440 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/GraphType_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/GraphType_placeholder.png deleted file mode 100644 index 72925be4490b..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/GraphType_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8ff780f0183ab90196b811013fc7f314e2f9b09ebe6921f58fd4fa656e5c27bf -size 4225 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/GridColumns_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/GridColumns_placeholder.png deleted file mode 100644 index ca9bf652ace7..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/GridColumns_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cd32060b8eea2bd13fca0f175eedfcaa478e9990743e515a8a8937c076f3c83c -size 3079 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/GridSpacing_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/GridSpacing_placeholder.png deleted file mode 100644 index 67789498331d..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/GridSpacing_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f352ad40ae0aeb3d768fc9cfbb5a143506689120e7092a7973a06ae50a1e7d4b -size 2993 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/HalfSize2D_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/HalfSize2D_placeholder.png deleted file mode 100644 index 4280642ad400..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/HalfSize2D_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:617e5bcfef91f83389c90903c22f8f449304add17a74eaf6ce0d45652e33fe3b -size 3237 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/HalfSize3D_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/HalfSize3D_placeholder.png deleted file mode 100644 index 20e0b02edda4..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/HalfSize3D_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e05477e252df1b6abb806a5ed576a702e095aca276c01bc997b9e464a5de5b24 -size 3421 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/ImageFormat_depth_f32.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/ImageFormat_depth_f32.png deleted file mode 100644 index 03796eb7a677..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/ImageFormat_depth_f32.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:57be08f15acb93cfecf876c689cfe45088bc88cd04369515fd9806a28347e415 -size 4844 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/ImageFormat_nv12.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/ImageFormat_nv12.png deleted file mode 100644 index 30da63e5ba0a..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/ImageFormat_nv12.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8a569a4c9f6868fffac901be38d90f8c5beea704caeb2ceaed30c0c2689d9862 -size 4918 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/ImageFormat_rgb8.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/ImageFormat_rgb8.png deleted file mode 100644 index 404f303c8ded..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/ImageFormat_rgb8.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2f4234a3f8a0584a9dda7249f179074247b130b2597b246207a1d5c1dad3d654 -size 5233 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/ImageFormat_rgba8.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/ImageFormat_rgba8.png deleted file mode 100644 index 9a85a566e662..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/ImageFormat_rgba8.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8c1fbd9bdabfcd1e6a60819c80adab7fb4a91e147e18e2c21a3e52a6924744df -size 5439 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/ImageFormat_segmentation_u32.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/ImageFormat_segmentation_u32.png deleted file mode 100644 index 99beccfc3680..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/ImageFormat_segmentation_u32.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1d8c3bf5c398e101f1f78130099c201d83130de16f1967a383252fc41eb16a32 -size 4805 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/ImagePlaneDistance_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/ImagePlaneDistance_placeholder.png deleted file mode 100644 index 67789498331d..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/ImagePlaneDistance_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f352ad40ae0aeb3d768fc9cfbb5a143506689120e7092a7973a06ae50a1e7d4b -size 2993 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Interactive_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Interactive_placeholder.png deleted file mode 100644 index bb8d2555d0fc..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Interactive_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4642554976de56d3372b42820a365bfd9b563048b3ada94b8e1b84daa66cface -size 3351 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/InterpolationMode_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/InterpolationMode_placeholder.png deleted file mode 100644 index 15b9aa847787..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/InterpolationMode_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ff1855ccce2120cf7671d2ed31ac14ba2abbf5e50ad695c2c17e5874e68fc669 -size 3507 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/KeyValuePairs_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/KeyValuePairs_placeholder.png deleted file mode 100644 index d3d861aa3ccd..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/KeyValuePairs_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ab62f2af4b5f74fb035ec73d3f450f7b76f6215046d44bdafd887d39e7fb5097 -size 2987 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/LatLon_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/LatLon_placeholder.png deleted file mode 100644 index 3524e4025e07..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/LatLon_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3da9081e52eef2a2ff107d6f4a1ea0d6153615a4128fde1168475ca46217d593 -size 6885 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Length_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Length_placeholder.png deleted file mode 100644 index 67789498331d..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Length_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f352ad40ae0aeb3d768fc9cfbb5a143506689120e7092a7973a06ae50a1e7d4b -size 2993 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/LineStrip2D_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/LineStrip2D_placeholder.png deleted file mode 100644 index eef25b6e2866..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/LineStrip2D_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ed8abbcc581551169a0f61d7286d8858a71d422d222dee75ce9ba455d147ae19 -size 4432 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/LineStrip3D_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/LineStrip3D_placeholder.png deleted file mode 100644 index eef25b6e2866..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/LineStrip3D_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ed8abbcc581551169a0f61d7286d8858a71d422d222dee75ce9ba455d147ae19 -size 4432 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/LinearSpeed_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/LinearSpeed_placeholder.png deleted file mode 100644 index ca9bf652ace7..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/LinearSpeed_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cd32060b8eea2bd13fca0f175eedfcaa478e9990743e515a8a8937c076f3c83c -size 3079 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/LinkAxis_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/LinkAxis_placeholder.png deleted file mode 100644 index 1a8b3f563296..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/LinkAxis_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6712797415310bef747243465ce165656ec5dfa8fa7c74964b6dc7a8a7bd18a9 -size 4485 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/LockRangeDuringZoom_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/LockRangeDuringZoom_placeholder.png deleted file mode 100644 index 7f47bbe478be..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/LockRangeDuringZoom_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5873bb2fdb4d52282a274c38eb321f710416d92a97f49b4516cd55ccdeefcf13 -size 3013 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/LoopMode_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/LoopMode_placeholder.png deleted file mode 100644 index 67789498331d..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/LoopMode_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f352ad40ae0aeb3d768fc9cfbb5a143506689120e7092a7973a06ae50a1e7d4b -size 2993 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/MagnificationFilter_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/MagnificationFilter_placeholder.png deleted file mode 100644 index 484bfd1f9ab2..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/MagnificationFilter_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e7b88a3cefb595ef76f2857c6ef7daa3834549b63a1aee0a899b56c0886b1e90 -size 3922 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/MapProvider_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/MapProvider_placeholder.png deleted file mode 100644 index c1f69217675e..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/MapProvider_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8f231d3e55af15e788bcab65bd4b2d9a1c317d0e329f3714c9083365981d7bb6 -size 5245 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/MarkerShape_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/MarkerShape_placeholder.png deleted file mode 100644 index 58553c93e6c8..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/MarkerShape_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:10f5f3d83114dbe0f7555b5c5ece2414c78de4131b86b73b0c01af6040355f64 -size 3989 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/MarkerSize_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/MarkerSize_placeholder.png deleted file mode 100644 index 7461d3c3c71b..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/MarkerSize_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cca2208db7fa47a20c01fdd0b74dc421268d42c01cfb3e70750d188bd7f24cdd -size 3487 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/MediaType_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/MediaType_placeholder.png deleted file mode 100644 index 3435d1373862..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/MediaType_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ffb28ab43057be634192feeb493f226cd0530c2e4c9cfc7e0c9be88b6c343fd4 -size 6335 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Name_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Name_placeholder.png deleted file mode 100644 index dc5d00c5769c..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Name_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c3ba16963dc2816f46873e4d882bc0fd3aeb500221680420a0ffcf6f3f5c1e61 -size 3863 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/NearClipPlane_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/NearClipPlane_placeholder.png deleted file mode 100644 index b6ebf02d6270..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/NearClipPlane_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0dc05072fa45d68067b4e827b33eebf081b1d23ac94f1e9f93a6dd4133e0f4cf -size 3192 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Opacity_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Opacity_placeholder.png deleted file mode 100644 index 67789498331d..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Opacity_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f352ad40ae0aeb3d768fc9cfbb5a143506689120e7092a7973a06ae50a1e7d4b -size 2993 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/PinholeProjection_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/PinholeProjection_placeholder.png deleted file mode 100644 index aa8b1008c9ce..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/PinholeProjection_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9a2c97dbd4b9146bab65278489654767da343a113058adac350c5672ccc204ce -size 8301 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Plane3D_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Plane3D_placeholder.png deleted file mode 100644 index fcd2a8dc37a0..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Plane3D_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1b9bf5270088bad5b69d44a15148922485e19c32178f4136803b63015464a2bb -size 4293 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/PlayState_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/PlayState_placeholder.png deleted file mode 100644 index 8f546f149790..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/PlayState_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:449d320b7503b0188abcfa070fd8fe24cd22b4a4dc59065c1c5481ef620bb1ee -size 3080 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/PlaybackSpeed_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/PlaybackSpeed_placeholder.png deleted file mode 100644 index ca9bf652ace7..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/PlaybackSpeed_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cd32060b8eea2bd13fca0f175eedfcaa478e9990743e515a8a8937c076f3c83c -size 3079 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Position2D_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Position2D_placeholder.png deleted file mode 100644 index f77c552e760b..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Position2D_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4ed0f6b2b80a7db65f55fb65ba5e225b09d839b8d4e4c27fa49449f262507c96 -size 3446 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Position3D_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Position3D_placeholder.png deleted file mode 100644 index 2f04a3d49420..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Position3D_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4c0bd7ea45dcce3269b4881030b24274b961da247dfc2b0197afcdaac5db163d -size 3763 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/QueryExpression_simple.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/QueryExpression_simple.png deleted file mode 100644 index 7abad4f95418..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/QueryExpression_simple.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:fc13468d12bc6bcd1bf257d81b9bab669e5d862282915df4130322dff9e389f1 -size 4330 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Radius_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Radius_placeholder.png deleted file mode 100644 index f8f77a52f84f..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Radius_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:fe77b9fbcbed1724d03bdfdbf176b028d6996e2b2229164b634b7b0d8592d7d2 -size 4264 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Range1D_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Range1D_placeholder.png deleted file mode 100644 index 2837cfc72671..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Range1D_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c7574773d9e352fde4bbb8d084ac040a25739ed9d560f1e770ab6e1a44be7d3c -size 3215 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Resolution_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Resolution_placeholder.png deleted file mode 100644 index 5067538371f0..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Resolution_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:eef163f529b0048c3932cf504d3b750003b9a51371e87711bddacc69558d9543 -size 3446 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/RotationAxisAngle_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/RotationAxisAngle_placeholder.png deleted file mode 100644 index e9da1c3b62ca..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/RotationAxisAngle_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0759faa4e5fa5243930202c22b4f01c2b9015d43a71c6926f9141d1e17ce7d98 -size 5932 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/RotationQuat_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/RotationQuat_placeholder.png deleted file mode 100644 index 15a05e3284f2..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/RotationQuat_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d5b54c0d338f044da203b62a64bc011dc45b3f152a4d77845d52b36e43a5c608 -size 3957 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Scalar_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Scalar_placeholder.png deleted file mode 100644 index ca9bf652ace7..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Scalar_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cd32060b8eea2bd13fca0f175eedfcaa478e9990743e515a8a8937c076f3c83c -size 3079 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Scale3D_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Scale3D_placeholder.png deleted file mode 100644 index 20e0b02edda4..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Scale3D_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e05477e252df1b6abb806a5ed576a702e095aca276c01bc997b9e464a5de5b24 -size 3421 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/SchemaId_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/SchemaId_placeholder.png deleted file mode 100644 index ca9bf652ace7..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/SchemaId_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cd32060b8eea2bd13fca0f175eedfcaa478e9990743e515a8a8937c076f3c83c -size 3079 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/SelectedColumns_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/SelectedColumns_placeholder.png deleted file mode 100644 index 3d98f58c2131..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/SelectedColumns_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:02c60bf646fca591f11108194fd8aa3e59e551d12ed991ef94060e5e3784b1fb -size 7329 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/ShowLabels_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/ShowLabels_placeholder.png deleted file mode 100644 index 7f47bbe478be..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/ShowLabels_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5873bb2fdb4d52282a274c38eb321f710416d92a97f49b4516cd55ccdeefcf13 -size 3013 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/StrokeWidth_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/StrokeWidth_placeholder.png deleted file mode 100644 index 7461d3c3c71b..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/StrokeWidth_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cca2208db7fa47a20c01fdd0b74dc421268d42c01cfb3e70750d188bd7f24cdd -size 3487 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/TensorDimensionIndexSelection_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/TensorDimensionIndexSelection_placeholder.png deleted file mode 100644 index 9a73894ab76b..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/TensorDimensionIndexSelection_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d956775fa2deecf448cbfda5ef11453431c43515c8c1649ff5d41b60d12b5275 -size 5775 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/TensorDimensionIndexSlider_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/TensorDimensionIndexSlider_placeholder.png deleted file mode 100644 index 9c6f04c94ceb..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/TensorDimensionIndexSlider_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b3cc0ddd506e3fdc7377a50e11c16bc70b83cae0df7e90c4fca04572274f5b0c -size 4658 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/TensorHeightDimension_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/TensorHeightDimension_placeholder.png deleted file mode 100644 index a0a347912874..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/TensorHeightDimension_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0c3d78caea10cf8a203dc38812058b00a4ae595dba95bd194779d55f99a2a36f -size 6327 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/TensorWidthDimension_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/TensorWidthDimension_placeholder.png deleted file mode 100644 index a0a347912874..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/TensorWidthDimension_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0c3d78caea10cf8a203dc38812058b00a4ae595dba95bd194779d55f99a2a36f -size 6327 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Texcoord2D_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Texcoord2D_placeholder.png deleted file mode 100644 index f77c552e760b..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Texcoord2D_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4ed0f6b2b80a7db65f55fb65ba5e225b09d839b8d4e4c27fa49449f262507c96 -size 3446 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/TextLogColumn_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/TextLogColumn_placeholder.png deleted file mode 100644 index f1e6e2f909c5..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/TextLogColumn_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:86153d6747b673c8134b619393b190eee4786e41d65805585adceb0791207e0c -size 4248 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/TextLogLevel_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/TextLogLevel_placeholder.png deleted file mode 100644 index 60ad54c7e577..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/TextLogLevel_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:79c29e5e9283b679a96c21750f3a3f905167f8cf0e7f226bfb49cbd10f459bd5 -size 3440 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Text_simple.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Text_simple.png deleted file mode 100644 index 1ea6cb7e0cee..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Text_simple.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:86bc111198a571db3f1651107d9afbe71543f1811da1f8d4307fd73f928f52ac -size 4394 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/TimeInt_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/TimeInt_placeholder.png deleted file mode 100644 index ca9bf652ace7..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/TimeInt_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cd32060b8eea2bd13fca0f175eedfcaa478e9990743e515a8a8937c076f3c83c -size 3079 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/TimeRange_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/TimeRange_placeholder.png deleted file mode 100644 index d3d861aa3ccd..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/TimeRange_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ab62f2af4b5f74fb035ec73d3f450f7b76f6215046d44bdafd887d39e7fb5097 -size 2987 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/TimelineColumn_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/TimelineColumn_placeholder.png deleted file mode 100644 index 53020587c17a..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/TimelineColumn_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:181f3f6437caec893c02c128220be9ebe75a80e6d52aa82bd59f1d2e0a5f6510 -size 3989 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/TimelineName_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/TimelineName_placeholder.png deleted file mode 100644 index e6877830e9fd..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/TimelineName_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b78852b5713b92a81ef997252dcc7cbdd16ed8f221ee27a72dd9024ccf9ae0ba -size 3954 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Timestamp_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Timestamp_placeholder.png deleted file mode 100644 index 5a95bd061e37..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Timestamp_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a4c151991868f2ecb88b1cf78941a3e6bb85da47c9d1d4c056c4d3ed720347c3 -size 5998 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/TransformFrameId_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/TransformFrameId_placeholder.png deleted file mode 100644 index 977102f5bd09..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/TransformFrameId_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2105d6de28d5ca4ce5c023d5911a3910a318e658792752f48bd65f98bbadbf1e -size 2936 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/TransformMat3x3_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/TransformMat3x3_placeholder.png deleted file mode 100644 index ce52455dcd23..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/TransformMat3x3_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e15730210a750181f4a26bf17efa6e1efbd97a764038241203604fc3e5f9107e -size 3878 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/TransformRelation_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/TransformRelation_placeholder.png deleted file mode 100644 index b6ec4b977d43..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/TransformRelation_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2b08062ef2ca55503ad428a4d66894b05c89da464a98c9161863512886ca8806 -size 4853 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Translation3D_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Translation3D_placeholder.png deleted file mode 100644 index 2f04a3d49420..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Translation3D_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4c0bd7ea45dcce3269b4881030b24274b961da247dfc2b0197afcdaac5db163d -size 3763 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/TriangleIndices_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/TriangleIndices_placeholder.png deleted file mode 100644 index 8bcd0037db57..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/TriangleIndices_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:398db688b3cb8310f6d4bcb02c5dfbc5bcbabc75d0397cdab69a69934e1c5dac -size 3632 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/ValueRange_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/ValueRange_placeholder.png deleted file mode 100644 index 2837cfc72671..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/ValueRange_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c7574773d9e352fde4bbb8d084ac040a25739ed9d560f1e770ab6e1a44be7d3c -size 3215 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Vector2D_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Vector2D_placeholder.png deleted file mode 100644 index f77c552e760b..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Vector2D_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4ed0f6b2b80a7db65f55fb65ba5e225b09d839b8d4e4c27fa49449f262507c96 -size 3446 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Vector3D_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Vector3D_placeholder.png deleted file mode 100644 index 2f04a3d49420..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Vector3D_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4c0bd7ea45dcce3269b4881030b24274b961da247dfc2b0197afcdaac5db163d -size 3763 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/VideoCodec_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/VideoCodec_placeholder.png deleted file mode 100644 index 258df772b74f..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/VideoCodec_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:82f24d1e190f0d0ec145ae7a7bdae3f44cd8b17b38f335e36e73b2d4556f2c3f -size 2870 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/VideoSample_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/VideoSample_placeholder.png deleted file mode 100644 index 8c12660c7b57..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/VideoSample_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cdda35ab5dc3251b898bae7ec25b14013cbb196d3380d8a4616ac927e9ee9e13 -size 3149 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/VideoTimestamp_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/VideoTimestamp_placeholder.png deleted file mode 100644 index 702c38e5bc9d..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/VideoTimestamp_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ac69651cb2b3705d47c00b7f0cc4fcc2f16f79f4fe0050bbbfc833c2e3a28061 -size 4371 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/ViewClass_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/ViewClass_placeholder.png deleted file mode 100644 index 46c77076317d..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/ViewClass_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:531e2793c7ceb7965638a8b061d7bed691d132a871d762dafb558fe1f24d537f -size 4291 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/ViewCoordinates_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/ViewCoordinates_placeholder.png deleted file mode 100644 index 00e35d69f7ae..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/ViewCoordinates_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f5117c381a55b2c8f98f11e8ee575616d976ab0c812216f9542f1f8dfbbb2b4b -size 6954 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/ViewFit_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/ViewFit_placeholder.png deleted file mode 100644 index ce1b773ed75b..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/ViewFit_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c701d50ec5a326b679ede41577bddce946e1c26085d026376a9afcdb7f32d909 -size 5760 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/VisibleTimeRange_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/VisibleTimeRange_placeholder.png deleted file mode 100644 index 5892793e6e63..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/VisibleTimeRange_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8ab66c1494cb808fe43c6b75cc5671e6070026514fd51ae6749964dfd362567b -size 9723 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Visible_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Visible_placeholder.png deleted file mode 100644 index 4b799743f642..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/Visible_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:11a67957c5ce4db33cb2d5af65ffa1b9f4521c1cd40d6312f7b1f0681a2f1f22 -size 3192 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/VisualBounds2D_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/VisualBounds2D_placeholder.png deleted file mode 100644 index a7a25e3915e7..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/VisualBounds2D_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9707f4c148315bcb8329bfdcdd9d4784b629e0a676a6ba50ea0c60d9b64026e9 -size 4124 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/VisualizerComponentMapping_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/VisualizerComponentMapping_placeholder.png deleted file mode 100644 index 2e14c2ee5024..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/VisualizerComponentMapping_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6a5e9d7a0b0e906cdb3c99833ebfd966db1766beecb3ff8f972b6d4616495189 -size 9787 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/VisualizerType_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/VisualizerType_placeholder.png deleted file mode 100644 index 977102f5bd09..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/VisualizerType_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2105d6de28d5ca4ce5c023d5911a3910a318e658792752f48bd65f98bbadbf1e -size 2936 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/ZoomLevel_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/ZoomLevel_placeholder.png deleted file mode 100644 index ca9bf652ace7..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/ZoomLevel_placeholder.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cd32060b8eea2bd13fca0f175eedfcaa478e9990743e515a8a8937c076f3c83c -size 3079 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/any_value_any_value_f64.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/any_value_any_value_f64.png deleted file mode 100644 index 8ed949f880eb..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/any_value_any_value_f64.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:90a415792a99dc093e4531a23f831f14f5b7124a422caad193608b12a0ae2ec1 -size 4304 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/custom_catalog_string_any_value_url_string.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/custom_catalog_string_any_value_url_string.png deleted file mode 100644 index e7ec7c8f3d2a..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/custom_catalog_string_any_value_url_string.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4b5ce87dc87c15f26991d997200206d44773025f3be66bda7b78b5f87d7d9f26 -size 6908 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/custom_empty_array_any_value_empty_array.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/custom_empty_array_any_value_empty_array.png deleted file mode 100644 index d3d861aa3ccd..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/custom_empty_array_any_value_empty_array.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ab62f2af4b5f74fb035ec73d3f450f7b76f6215046d44bdafd887d39e7fb5097 -size 2987 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/custom_large_blob_any_value_large_blob.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/custom_large_blob_any_value_large_blob.png deleted file mode 100644 index f92731069757..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/custom_large_blob_any_value_large_blob.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4464bce3b02b5124d7ddb6755e732043e1758c2b3c82c3cfb07f346bf76a9340 -size 5487 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/custom_small_array_any_value_small_array.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/custom_small_array_any_value_small_array.png deleted file mode 100644 index f92731069757..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/custom_small_array_any_value_small_array.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4464bce3b02b5124d7ddb6755e732043e1758c2b3c82c3cfb07f346bf76a9340 -size 5487 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/custom_string_any_value_string.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/custom_string_any_value_string.png deleted file mode 100644 index 1ea6cb7e0cee..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/custom_string_any_value_string.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:86bc111198a571db3f1651107d9afbe71543f1811da1f8d4307fd73f928f52ac -size 4394 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/custom_struct_array_any_value_struct_array.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/custom_struct_array_any_value_struct_array.png deleted file mode 100644 index d5ff779c8fe8..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/custom_struct_array_any_value_struct_array.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2284265d8e0fc664a255282182d1ba74a6f2fb15b0dc7cc9ce6b46a53307f694 -size 8520 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/custom_struct_array_single_element_any_value_struct_array_single_element.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/custom_struct_array_single_element_any_value_struct_array_single_element.png deleted file mode 100644 index 4cb8955b8211..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/custom_struct_array_single_element_any_value_struct_array_single_element.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a903ccda9a147496527fb95c0b1a812a766baeacaee67c0f423249e9069c2f1b -size 5514 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/custom_url_string_any_value_url_string.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/custom_url_string_any_value_url_string.png deleted file mode 100644 index 013f14fe0805..000000000000 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide/custom_url_string_any_value_url_string.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1bea2a04eb01390ebf8bfaa56a0598b4b0d42e5ffb29077f64569768889d41fe -size 5070 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/AbsoluteTimeRange_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/AbsoluteTimeRange_placeholder.png new file mode 100644 index 000000000000..ddccbecceee4 --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/AbsoluteTimeRange_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c36373dc8ecda83fe04147440d7f4b044e26c9f3d41e9cb74ebca08cd6267a27 +size 4732 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/AggregationPolicy_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/AggregationPolicy_placeholder.png new file mode 100644 index 000000000000..7f199c6ea24a --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/AggregationPolicy_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:22fa35e6ac5e6a53b85aa9d1e7482cf6990fa5d1352098205e4ba4b51a552196 +size 3745 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/AlbedoFactor_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/AlbedoFactor_placeholder.png new file mode 100644 index 000000000000..d1e5488c2784 --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/AlbedoFactor_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:680f2e76a223019140ddedf97e329b6d0baedbccc06b850106f0787fec712744 +size 2972 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/AngularSpeed_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/AngularSpeed_placeholder.png new file mode 100644 index 000000000000..2c83d3d11457 --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/AngularSpeed_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:159587f731217a6fc66947cc1aed2bad08bab133da30f6fe3a3b5c825d548a97 +size 3028 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/ApplyLatestAt_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/ApplyLatestAt_placeholder.png new file mode 100644 index 000000000000..90314d7dc7f8 --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/ApplyLatestAt_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:30893ea77dad651066e84eb543bf8f18a6f1529b1d18d81df478adbebad03956 +size 3553 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/AutoScroll_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/AutoScroll_placeholder.png new file mode 100644 index 000000000000..90314d7dc7f8 --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/AutoScroll_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:30893ea77dad651066e84eb543bf8f18a6f1529b1d18d81df478adbebad03956 +size 3553 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/AxisLength_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/AxisLength_placeholder.png new file mode 100644 index 000000000000..dfa332fe5f3b --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/AxisLength_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c2013665c4d01cbc9f2568fa03c467f5e22cfd78708421b8c1bccee5b96ee3a9 +size 2908 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/BackgroundKind_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/BackgroundKind_placeholder.png new file mode 100644 index 000000000000..aedd89084901 --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/BackgroundKind_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7f03e0a26c80e6369be65e0211c5b7e0809da72038b1fa96fe93792457e04952 +size 4651 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/ChannelId_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/ChannelId_placeholder.png new file mode 100644 index 000000000000..2c83d3d11457 --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/ChannelId_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:159587f731217a6fc66947cc1aed2bad08bab133da30f6fe3a3b5c825d548a97 +size 3028 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/ChannelMessageCounts_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/ChannelMessageCounts_placeholder.png new file mode 100644 index 000000000000..c7f2658b9f7d --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/ChannelMessageCounts_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:026087603ec404cf9a0f5fc91fb3795303237564a078f6f24c9cf1a031318a0b +size 2885 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/ClearIsRecursive_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/ClearIsRecursive_placeholder.png new file mode 100644 index 000000000000..90314d7dc7f8 --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/ClearIsRecursive_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:30893ea77dad651066e84eb543bf8f18a6f1529b1d18d81df478adbebad03956 +size 3553 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Color_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Color_placeholder.png new file mode 100644 index 000000000000..d1e5488c2784 --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Color_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:680f2e76a223019140ddedf97e329b6d0baedbccc06b850106f0787fec712744 +size 2972 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Colormap_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Colormap_placeholder.png new file mode 100644 index 000000000000..9cea3025d866 --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Colormap_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e3efcd5ddb66e8f29409d89f896b6796db8789bb18694e9c1093eca93a19e4f5 +size 3995 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/ColumnOrder_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/ColumnOrder_placeholder.png new file mode 100644 index 000000000000..c7f2658b9f7d --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/ColumnOrder_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:026087603ec404cf9a0f5fc91fb3795303237564a078f6f24c9cf1a031318a0b +size 2885 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/ComponentColumnSelector_simple.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/ComponentColumnSelector_simple.png new file mode 100644 index 000000000000..af08fa31ac9a --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/ComponentColumnSelector_simple.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:51b1209a3179b7a9269af01922ce8e12715020691b2cc0619442eba8f2008a25 +size 10618 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/ContainerKind_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/ContainerKind_placeholder.png new file mode 100644 index 000000000000..4f92500750f8 --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/ContainerKind_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f2fd861d575f9f4457adb4ac3d5f334a1b99fe8f1b632c3d24578bc7f8d202c1 +size 2941 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Corner2D_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Corner2D_placeholder.png new file mode 100644 index 000000000000..81854d6303b1 --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Corner2D_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:42d35d8a61d892a19014522e67f34cb7d08b41ebe4075504c50dd4f382810476 +size 4242 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Count_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Count_placeholder.png new file mode 100644 index 000000000000..2c83d3d11457 --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Count_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:159587f731217a6fc66947cc1aed2bad08bab133da30f6fe3a3b5c825d548a97 +size 3028 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/DepthMeter_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/DepthMeter_placeholder.png new file mode 100644 index 000000000000..dfa332fe5f3b --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/DepthMeter_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c2013665c4d01cbc9f2568fa03c467f5e22cfd78708421b8c1bccee5b96ee3a9 +size 2908 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/DrawOrder_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/DrawOrder_placeholder.png new file mode 100644 index 000000000000..2c83d3d11457 --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/DrawOrder_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:159587f731217a6fc66947cc1aed2bad08bab133da30f6fe3a3b5c825d548a97 +size 3028 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Enabled_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Enabled_placeholder.png new file mode 100644 index 000000000000..212d08d373ff --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Enabled_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:941e3bc6e501fff0e7a63ef89b25878ce6d9048bf0ff7f9f973e9641a09211ab +size 2985 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/EntityPath_simple.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/EntityPath_simple.png new file mode 100644 index 000000000000..ba74d5d4d9fa --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/EntityPath_simple.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9d1b9f3ebfc6c4a386391e789233cae5bbe48b7175e885d6166693dda4eda070 +size 5897 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Eye3DKind_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Eye3DKind_placeholder.png new file mode 100644 index 000000000000..75874004fc03 --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Eye3DKind_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:357feef5d056ccb27dbac3aab291e118f1394589278ee5d503e59b492ff2589e +size 3694 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/FillMode_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/FillMode_placeholder.png new file mode 100644 index 000000000000..a0a256874a46 --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/FillMode_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5cd0f23e9f605c00dbeafa05d6e15c2694ac7c9b32fef3555e5fb215c23e39b9 +size 4958 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/FillRatio_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/FillRatio_placeholder.png new file mode 100644 index 000000000000..dfa332fe5f3b --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/FillRatio_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c2013665c4d01cbc9f2568fa03c467f5e22cfd78708421b8c1bccee5b96ee3a9 +size 2908 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/FilterByRange_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/FilterByRange_placeholder.png new file mode 100644 index 000000000000..b45d5fbaef65 --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/FilterByRange_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3e06cf1bbea561dd1a869bd48d06134365a9f9078901da0889194b530c4aff37 +size 11944 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/FilterIsNotNull_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/FilterIsNotNull_placeholder.png new file mode 100644 index 000000000000..5cf2b49c4956 --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/FilterIsNotNull_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a42ed201a7332948dd0cb4c65d8d8929da08abec194b6248e0fa2e293205c055 +size 9238 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/ForceDistance_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/ForceDistance_placeholder.png new file mode 100644 index 000000000000..2c83d3d11457 --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/ForceDistance_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:159587f731217a6fc66947cc1aed2bad08bab133da30f6fe3a3b5c825d548a97 +size 3028 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/ForceIterations_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/ForceIterations_placeholder.png new file mode 100644 index 000000000000..2c83d3d11457 --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/ForceIterations_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:159587f731217a6fc66947cc1aed2bad08bab133da30f6fe3a3b5c825d548a97 +size 3028 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/ForceStrength_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/ForceStrength_placeholder.png new file mode 100644 index 000000000000..2c83d3d11457 --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/ForceStrength_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:159587f731217a6fc66947cc1aed2bad08bab133da30f6fe3a3b5c825d548a97 +size 3028 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Fps_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Fps_placeholder.png new file mode 100644 index 000000000000..2c83d3d11457 --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Fps_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:159587f731217a6fc66947cc1aed2bad08bab133da30f6fe3a3b5c825d548a97 +size 3028 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/GammaCorrection_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/GammaCorrection_placeholder.png new file mode 100644 index 000000000000..dfa332fe5f3b --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/GammaCorrection_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c2013665c4d01cbc9f2568fa03c467f5e22cfd78708421b8c1bccee5b96ee3a9 +size 2908 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/GeoLineString_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/GeoLineString_placeholder.png new file mode 100644 index 000000000000..fa6e0023168c --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/GeoLineString_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:27bdabd431c4e1f5957c4da25601a7d9296d5bdbf734ecb8e2279ea6bb8fdb54 +size 4375 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/GraphEdge_simple.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/GraphEdge_simple.png new file mode 100644 index 000000000000..25f7a73a47f4 --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/GraphEdge_simple.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f9ed8fcec05d689121bfc3bfa746452100b62f9270f5f1d804a04c9cee4421d1 +size 6796 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/GraphNode_simple.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/GraphNode_simple.png new file mode 100644 index 000000000000..26e31df4886c --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/GraphNode_simple.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:557b75b0df556cedb9e3d69d99b358f1b7ae14a0a3d895fbda53bd7d912b7888 +size 4353 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/GraphType_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/GraphType_placeholder.png new file mode 100644 index 000000000000..3eec7e56360b --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/GraphType_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e0ef7c0456488f0891bfc4c149be6b5cc7b42c5727e3a20464abcd084bf9ac0f +size 4213 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/GridColumns_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/GridColumns_placeholder.png new file mode 100644 index 000000000000..2c83d3d11457 --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/GridColumns_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:159587f731217a6fc66947cc1aed2bad08bab133da30f6fe3a3b5c825d548a97 +size 3028 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/GridSpacing_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/GridSpacing_placeholder.png new file mode 100644 index 000000000000..dfa332fe5f3b --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/GridSpacing_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c2013665c4d01cbc9f2568fa03c467f5e22cfd78708421b8c1bccee5b96ee3a9 +size 2908 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/HalfSize2D_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/HalfSize2D_placeholder.png new file mode 100644 index 000000000000..ef31bf3dbb1c --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/HalfSize2D_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:396ceafbefe50f7da354473c45ac18084cd62505573dc173d4c3b8dbf3f48ef7 +size 3208 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/HalfSize3D_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/HalfSize3D_placeholder.png new file mode 100644 index 000000000000..f8280044191e --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/HalfSize3D_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0bdde5c41916e841be8fced9dda0322a919bdd1f24ff5cff06cbbc7c2ddef488 +size 3388 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/ImageFormat_depth_f32.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/ImageFormat_depth_f32.png new file mode 100644 index 000000000000..236393127398 --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/ImageFormat_depth_f32.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:de51f135a9707e1eaa85eeadba4ce3eb7e25eb86801affff0d0eff4194722ecc +size 4760 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/ImageFormat_nv12.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/ImageFormat_nv12.png new file mode 100644 index 000000000000..3567982c65b2 --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/ImageFormat_nv12.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e49e0360d07848192e3b1b605c40a7720cbb1dddbf54044cb211d53f4ac8e608 +size 4889 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/ImageFormat_rgb8.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/ImageFormat_rgb8.png new file mode 100644 index 000000000000..ea1c9ed2644a --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/ImageFormat_rgb8.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:17b1629b84cdcb27a741b9d2e967208e040cb64301ef75cb114fa570427ae7a1 +size 5221 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/ImageFormat_rgba8.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/ImageFormat_rgba8.png new file mode 100644 index 000000000000..35d95f6bfff9 --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/ImageFormat_rgba8.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dcd0116f452295fc72cbc791b5abe19f926bc0ee578f16c68acbe363b8431954 +size 5438 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/ImageFormat_segmentation_u32.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/ImageFormat_segmentation_u32.png new file mode 100644 index 000000000000..fda0d5526ba5 --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/ImageFormat_segmentation_u32.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d8b717ed20e4cd52423e3841c8f81a995c10a61be3b0621b738be18f8d97edf9 +size 4769 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/ImagePlaneDistance_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/ImagePlaneDistance_placeholder.png new file mode 100644 index 000000000000..dfa332fe5f3b --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/ImagePlaneDistance_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c2013665c4d01cbc9f2568fa03c467f5e22cfd78708421b8c1bccee5b96ee3a9 +size 2908 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Interactive_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Interactive_placeholder.png new file mode 100644 index 000000000000..f160efba8832 --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Interactive_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4b2a0395bfd9058d59bcabcdbbb15d590db6d98d62fab87e44041688c6dcf022 +size 3285 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/InterpolationMode_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/InterpolationMode_placeholder.png new file mode 100644 index 000000000000..13f92976a83c --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/InterpolationMode_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a562283de16d2a7ad68a104f0c608695c852626fa15bedbb526fe562e05af872 +size 3428 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/KeyValuePairs_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/KeyValuePairs_placeholder.png new file mode 100644 index 000000000000..c7f2658b9f7d --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/KeyValuePairs_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:026087603ec404cf9a0f5fc91fb3795303237564a078f6f24c9cf1a031318a0b +size 2885 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/LatLon_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/LatLon_placeholder.png new file mode 100644 index 000000000000..71177b6d339a --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/LatLon_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e8a111092e977062e7cc322b6bf8b6a63d893ed4fb05a9310a43518438e7771b +size 7140 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Length_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Length_placeholder.png new file mode 100644 index 000000000000..dfa332fe5f3b --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Length_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c2013665c4d01cbc9f2568fa03c467f5e22cfd78708421b8c1bccee5b96ee3a9 +size 2908 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/LineStrip2D_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/LineStrip2D_placeholder.png new file mode 100644 index 000000000000..fa6e0023168c --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/LineStrip2D_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:27bdabd431c4e1f5957c4da25601a7d9296d5bdbf734ecb8e2279ea6bb8fdb54 +size 4375 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/LineStrip3D_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/LineStrip3D_placeholder.png new file mode 100644 index 000000000000..fa6e0023168c --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/LineStrip3D_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:27bdabd431c4e1f5957c4da25601a7d9296d5bdbf734ecb8e2279ea6bb8fdb54 +size 4375 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/LinearSpeed_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/LinearSpeed_placeholder.png new file mode 100644 index 000000000000..2c83d3d11457 --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/LinearSpeed_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:159587f731217a6fc66947cc1aed2bad08bab133da30f6fe3a3b5c825d548a97 +size 3028 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/LinkAxis_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/LinkAxis_placeholder.png new file mode 100644 index 000000000000..0128c18a3c8f --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/LinkAxis_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b1041ee83bb4ff3f7f3c9a40fc065e19420eab485ce81227a41e866c72a07cf7 +size 4416 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/LockRangeDuringZoom_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/LockRangeDuringZoom_placeholder.png new file mode 100644 index 000000000000..212d08d373ff --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/LockRangeDuringZoom_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:941e3bc6e501fff0e7a63ef89b25878ce6d9048bf0ff7f9f973e9641a09211ab +size 2985 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/LoopMode_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/LoopMode_placeholder.png new file mode 100644 index 000000000000..dfa332fe5f3b --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/LoopMode_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c2013665c4d01cbc9f2568fa03c467f5e22cfd78708421b8c1bccee5b96ee3a9 +size 2908 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/MagnificationFilter_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/MagnificationFilter_placeholder.png new file mode 100644 index 000000000000..b830fc2df0eb --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/MagnificationFilter_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4fcd5bc9ba6a63ed114b9ed3e30d9fd88ff1caa53a0c010cced938ad1cca5f3b +size 3875 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/MapProvider_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/MapProvider_placeholder.png new file mode 100644 index 000000000000..ab5d074dac45 --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/MapProvider_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a11c4785660b383c278e99b648b459368cdb61aebd59b3df471d8735c937c56e +size 5222 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/MarkerShape_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/MarkerShape_placeholder.png new file mode 100644 index 000000000000..5091900cbf10 --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/MarkerShape_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bc2280ee141ea0e9eba0316ab9bddb06f652e7840ca445f7def3f26d957912f1 +size 3805 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/MarkerSize_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/MarkerSize_placeholder.png new file mode 100644 index 000000000000..66924b6a7bcf --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/MarkerSize_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3c25d8f63a13576c614e0b44a52aeddebee3275279f33f65e7ab2ee7f4a66d2c +size 3365 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/MediaType_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/MediaType_placeholder.png new file mode 100644 index 000000000000..2836e2b3e082 --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/MediaType_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f9b4c1a5515d3f7cfb74117a9c910b35a3c33926780750cc51ad52c71b365f59 +size 6244 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Name_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Name_placeholder.png new file mode 100644 index 000000000000..4ef552b67587 --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Name_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:07a76b38ed1dd4a4a4ce0d929a1253367bebf117ac0c5629f2f65bd5763f5364 +size 3833 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/NearClipPlane_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/NearClipPlane_placeholder.png new file mode 100644 index 000000000000..3a58b6990a24 --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/NearClipPlane_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b9118455d8f5f9624c5e5d5029b9559e42b0b718dc943c401dddee1a9c11f092 +size 3161 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Opacity_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Opacity_placeholder.png new file mode 100644 index 000000000000..dfa332fe5f3b --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Opacity_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c2013665c4d01cbc9f2568fa03c467f5e22cfd78708421b8c1bccee5b96ee3a9 +size 2908 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/PinholeProjection_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/PinholeProjection_placeholder.png new file mode 100644 index 000000000000..5bb042e50cc2 --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/PinholeProjection_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f4cb07b076b82b007a5978bd8ad16a2033f7b725d86a130cd70275c49230f36e +size 8277 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Plane3D_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Plane3D_placeholder.png new file mode 100644 index 000000000000..1c0cfdbbba41 --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Plane3D_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6aa0aec453fc7f9b3ae6e5f08520be88ef5b537ed356bf11147b5ef99a5bc3a8 +size 4351 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/PlayState_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/PlayState_placeholder.png new file mode 100644 index 000000000000..a56f24eb076e --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/PlayState_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a5bfde5113acc5a325fbab99bb9927afd74e9b7c6baabb126cfe7220c7025001 +size 3036 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/PlaybackSpeed_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/PlaybackSpeed_placeholder.png new file mode 100644 index 000000000000..2c83d3d11457 --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/PlaybackSpeed_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:159587f731217a6fc66947cc1aed2bad08bab133da30f6fe3a3b5c825d548a97 +size 3028 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Position2D_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Position2D_placeholder.png new file mode 100644 index 000000000000..61d6844e5a0e --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Position2D_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8901d26c00fb6bf7d15532c5c43af4e80ce462bb82378a8adce38643689da963 +size 3457 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Position3D_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Position3D_placeholder.png new file mode 100644 index 000000000000..10b34d31fe8e --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Position3D_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:40c8b1ae4959292f36331b5984ab0d5a016aa08e7cf766e5c6c19ff11f141476 +size 3786 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/QueryExpression_simple.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/QueryExpression_simple.png new file mode 100644 index 000000000000..898e7220370b --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/QueryExpression_simple.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0971d072d17457aac1817cd7526bc8b926003d2357912b9cfbbd89c41531e0fe +size 4237 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Radius_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Radius_placeholder.png new file mode 100644 index 000000000000..d6e30563520b --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Radius_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:673c2e44d429f3d8bdd35e5efcc60b1d3a0c429e8d0babf01064d555d385d47a +size 4198 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Range1D_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Range1D_placeholder.png new file mode 100644 index 000000000000..7b96836e17d1 --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Range1D_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fc739546959b65bb8d48496dbf3aeabf63f4fe7e355fa5aaab9edeee6bc9dfd3 +size 3175 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Resolution_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Resolution_placeholder.png new file mode 100644 index 000000000000..abbcb0a43db0 --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Resolution_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:39b8141c0083b788e4e42c932baaa3b032b1603c2847eab40af6fdbea0960f14 +size 3408 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/RotationAxisAngle_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/RotationAxisAngle_placeholder.png new file mode 100644 index 000000000000..bab04ab63c0c --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/RotationAxisAngle_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:701ab03f3f1b9c0894d32e45449b5d953a73758fa346f70b3ae7c4286ba187cb +size 6016 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/RotationQuat_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/RotationQuat_placeholder.png new file mode 100644 index 000000000000..82926bfc44e9 --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/RotationQuat_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:357f4cf2397fd364973b937efba7f0081e180fbbd612a33a2c845e5740588001 +size 3973 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Scalar_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Scalar_placeholder.png new file mode 100644 index 000000000000..2c83d3d11457 --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Scalar_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:159587f731217a6fc66947cc1aed2bad08bab133da30f6fe3a3b5c825d548a97 +size 3028 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Scale3D_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Scale3D_placeholder.png new file mode 100644 index 000000000000..f8280044191e --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Scale3D_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0bdde5c41916e841be8fced9dda0322a919bdd1f24ff5cff06cbbc7c2ddef488 +size 3388 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/SchemaId_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/SchemaId_placeholder.png new file mode 100644 index 000000000000..2c83d3d11457 --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/SchemaId_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:159587f731217a6fc66947cc1aed2bad08bab133da30f6fe3a3b5c825d548a97 +size 3028 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/SelectedColumns_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/SelectedColumns_placeholder.png new file mode 100644 index 000000000000..7dd078672331 --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/SelectedColumns_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:62e809aa3542b778c57826ed26bb0bdd028a8f0466d28648c93941948b6e2c0f +size 7364 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/ShowLabels_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/ShowLabels_placeholder.png new file mode 100644 index 000000000000..212d08d373ff --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/ShowLabels_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:941e3bc6e501fff0e7a63ef89b25878ce6d9048bf0ff7f9f973e9641a09211ab +size 2985 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/StrokeWidth_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/StrokeWidth_placeholder.png new file mode 100644 index 000000000000..66924b6a7bcf --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/StrokeWidth_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3c25d8f63a13576c614e0b44a52aeddebee3275279f33f65e7ab2ee7f4a66d2c +size 3365 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/TensorDimensionIndexSelection_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/TensorDimensionIndexSelection_placeholder.png new file mode 100644 index 000000000000..5a3d91af4472 --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/TensorDimensionIndexSelection_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b98f396dc8678cd1deec778a38301aa034d748e3ae7ce2b48d366a6cdbfcde2f +size 5815 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/TensorDimensionIndexSlider_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/TensorDimensionIndexSlider_placeholder.png new file mode 100644 index 000000000000..5e3f79abd189 --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/TensorDimensionIndexSlider_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9332f3b055b0e4933694d8efa96700a63d2deac332662606fc9de1feb6195f9f +size 4664 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/TensorHeightDimension_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/TensorHeightDimension_placeholder.png new file mode 100644 index 000000000000..b4c59c401775 --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/TensorHeightDimension_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0a624ad32204f741601d6a973211148a6686d867169b2dbf63b78e15cb7fd9fe +size 6298 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/TensorWidthDimension_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/TensorWidthDimension_placeholder.png new file mode 100644 index 000000000000..b4c59c401775 --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/TensorWidthDimension_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0a624ad32204f741601d6a973211148a6686d867169b2dbf63b78e15cb7fd9fe +size 6298 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Texcoord2D_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Texcoord2D_placeholder.png new file mode 100644 index 000000000000..61d6844e5a0e --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Texcoord2D_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8901d26c00fb6bf7d15532c5c43af4e80ce462bb82378a8adce38643689da963 +size 3457 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/TextLogColumn_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/TextLogColumn_placeholder.png new file mode 100644 index 000000000000..02955d769886 --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/TextLogColumn_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:aaaa542c3cbf203d60a34a002155887894dcfb6924a61e7ab4e9c7f8dc5184f3 +size 4185 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/TextLogLevel_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/TextLogLevel_placeholder.png new file mode 100644 index 000000000000..6ad6939d7553 --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/TextLogLevel_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a456c1dffe482dc2542582764f435afe67b6505450e3da362ae199029056540f +size 3396 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Text_simple.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Text_simple.png new file mode 100644 index 000000000000..2221a9b2062a --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Text_simple.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4687815d6060f342397bd91b79f609dd92f23e4c7419ea4003486c0becacf0dd +size 4339 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/TimeInt_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/TimeInt_placeholder.png new file mode 100644 index 000000000000..2c83d3d11457 --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/TimeInt_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:159587f731217a6fc66947cc1aed2bad08bab133da30f6fe3a3b5c825d548a97 +size 3028 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/TimeRange_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/TimeRange_placeholder.png new file mode 100644 index 000000000000..c7f2658b9f7d --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/TimeRange_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:026087603ec404cf9a0f5fc91fb3795303237564a078f6f24c9cf1a031318a0b +size 2885 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/TimelineColumn_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/TimelineColumn_placeholder.png new file mode 100644 index 000000000000..5e5eb5f3c1a7 --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/TimelineColumn_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d253b3a0ed733c6c9185ae190c0ef502e818785b6f8dc3cca4413889cd443145 +size 3864 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/TimelineName_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/TimelineName_placeholder.png new file mode 100644 index 000000000000..2c88d2ef530d --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/TimelineName_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d41abc826694b8eca2dcd63965727e0c50947c573a59ce3739740d92fed9ebb5 +size 3930 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Timestamp_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Timestamp_placeholder.png new file mode 100644 index 000000000000..5f5591a72734 --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Timestamp_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f87c085fbee2f62aad23af803515c7f29377bf6e251036628ad990bf4d3b66c0 +size 6116 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/TransformFrameId_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/TransformFrameId_placeholder.png new file mode 100644 index 000000000000..e02ea67935be --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/TransformFrameId_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8be2d4f396580a6e653e388c65766d814772264f8f2889f6924ba4790a6395c3 +size 2899 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/TransformMat3x3_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/TransformMat3x3_placeholder.png new file mode 100644 index 000000000000..6f1797e6fca7 --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/TransformMat3x3_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:27caee9dda60c177f99916cd247ef617959b20d8b82068c34545a7a87da3da15 +size 3818 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/TransformRelation_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/TransformRelation_placeholder.png new file mode 100644 index 000000000000..d42f421c6ce0 --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/TransformRelation_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:55f49a6e54dfe059027f6b5f173ba15d3c359d69b102ace6d4a0cda974c62024 +size 4774 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Translation3D_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Translation3D_placeholder.png new file mode 100644 index 000000000000..10b34d31fe8e --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Translation3D_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:40c8b1ae4959292f36331b5984ab0d5a016aa08e7cf766e5c6c19ff11f141476 +size 3786 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/TriangleIndices_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/TriangleIndices_placeholder.png new file mode 100644 index 000000000000..44c1c7dc4d59 --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/TriangleIndices_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:18a70e52ed4ced3e8eb05ba538ceec23ea4fc0b26c5ef6c8e15e1efe947ea026 +size 3631 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/ValueRange_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/ValueRange_placeholder.png new file mode 100644 index 000000000000..7b96836e17d1 --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/ValueRange_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fc739546959b65bb8d48496dbf3aeabf63f4fe7e355fa5aaab9edeee6bc9dfd3 +size 3175 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Vector2D_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Vector2D_placeholder.png new file mode 100644 index 000000000000..61d6844e5a0e --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Vector2D_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8901d26c00fb6bf7d15532c5c43af4e80ce462bb82378a8adce38643689da963 +size 3457 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Vector3D_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Vector3D_placeholder.png new file mode 100644 index 000000000000..10b34d31fe8e --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Vector3D_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:40c8b1ae4959292f36331b5984ab0d5a016aa08e7cf766e5c6c19ff11f141476 +size 3786 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/VideoCodec_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/VideoCodec_placeholder.png new file mode 100644 index 000000000000..85392bb56fd5 --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/VideoCodec_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4e7c1a186c4bea4d7f1a6d0455288d13e815b86715ee7c52eb2fa916c95bbf47 +size 2836 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/VideoSample_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/VideoSample_placeholder.png new file mode 100644 index 000000000000..8d57d5b73931 --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/VideoSample_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2ee718cc911eefe6a80210e580550ef6719271bb7b501c7e7c4568f78922f5ca +size 3136 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/VideoTimestamp_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/VideoTimestamp_placeholder.png new file mode 100644 index 000000000000..69d2cf2d9891 --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/VideoTimestamp_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c703af26a497809f9de11b40fa3b254eef700e1c36c7c4b45e0901d05bf687c7 +size 4461 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/ViewClass_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/ViewClass_placeholder.png new file mode 100644 index 000000000000..4aed52a1dc14 --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/ViewClass_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dbc7648533b8f0fd50c9ad68de42920106b0377e012c7af43598c94a4c40f73c +size 4245 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/ViewCoordinates_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/ViewCoordinates_placeholder.png new file mode 100644 index 000000000000..4ee8e2039feb --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/ViewCoordinates_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7674492cf1e05f7be066553b63f743cb4b4d8ca4c20437e754c180c0993b530e +size 6954 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/ViewFit_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/ViewFit_placeholder.png new file mode 100644 index 000000000000..736efe69cf26 --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/ViewFit_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1b4c7c6e02757071bd346a5d22056b3ffd4d7d88a9f2a7c06d6ca812a97d2374 +size 5731 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/VisibleTimeRange_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/VisibleTimeRange_placeholder.png new file mode 100644 index 000000000000..079214977f7b --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/VisibleTimeRange_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:56ab24063c8d409c2bdb613520c0ec427b3ae6767621647d10ba5ba49bd26a0d +size 9725 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Visible_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Visible_placeholder.png new file mode 100644 index 000000000000..26f4d038757c --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Visible_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d701d40ad5d7f0179c5534c5867ed21840626e39ad1cc5176322b4b073852670 +size 3152 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/VisualBounds2D_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/VisualBounds2D_placeholder.png new file mode 100644 index 000000000000..9b33cf31d468 --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/VisualBounds2D_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1c5e6c5efe370cecb91eee9986cfde702ccda808e4c446e12629b90c3a0fa2cf +size 4131 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/VisualizerComponentMapping_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/VisualizerComponentMapping_placeholder.png new file mode 100644 index 000000000000..6e9b52a55687 --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/VisualizerComponentMapping_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e4253cb6204e752c8f88c2c4179712d9dbc82cbf84e15b3cb035efcf0ea5e719 +size 9829 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/VisualizerType_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/VisualizerType_placeholder.png new file mode 100644 index 000000000000..e02ea67935be --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/VisualizerType_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8be2d4f396580a6e653e388c65766d814772264f8f2889f6924ba4790a6395c3 +size 2899 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/ZoomLevel_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/ZoomLevel_placeholder.png new file mode 100644 index 000000000000..2c83d3d11457 --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/ZoomLevel_placeholder.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:159587f731217a6fc66947cc1aed2bad08bab133da30f6fe3a3b5c825d548a97 +size 3028 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/any_value_any_value_f64.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/any_value_any_value_f64.png new file mode 100644 index 000000000000..f917bda8b58c --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/any_value_any_value_f64.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:23ebb326317abecada81311475b12649e0ab11dadcec534fb0397f306cbfe10d +size 4265 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/custom_catalog_string_any_value_url_string.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/custom_catalog_string_any_value_url_string.png new file mode 100644 index 000000000000..a5c2db10a277 --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/custom_catalog_string_any_value_url_string.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b92b472d0f2619f5f06ca883c69af0d14bdba9477f501b95badb60fe87f01d3b +size 6869 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/custom_empty_array_any_value_empty_array.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/custom_empty_array_any_value_empty_array.png new file mode 100644 index 000000000000..c7f2658b9f7d --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/custom_empty_array_any_value_empty_array.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:026087603ec404cf9a0f5fc91fb3795303237564a078f6f24c9cf1a031318a0b +size 2885 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/custom_large_blob_any_value_large_blob.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/custom_large_blob_any_value_large_blob.png new file mode 100644 index 000000000000..d1bd0ce0eb87 --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/custom_large_blob_any_value_large_blob.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7bfe12ad89f298867c2cfcfcedffdb29a0ffeac4ed2f2a6a42d7685bf54b5938 +size 5431 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/custom_small_array_any_value_small_array.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/custom_small_array_any_value_small_array.png new file mode 100644 index 000000000000..d1bd0ce0eb87 --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/custom_small_array_any_value_small_array.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7bfe12ad89f298867c2cfcfcedffdb29a0ffeac4ed2f2a6a42d7685bf54b5938 +size 5431 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/custom_string_any_value_string.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/custom_string_any_value_string.png new file mode 100644 index 000000000000..2221a9b2062a --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/custom_string_any_value_string.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4687815d6060f342397bd91b79f609dd92f23e4c7419ea4003486c0becacf0dd +size 4339 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/custom_struct_array_any_value_struct_array.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/custom_struct_array_any_value_struct_array.png new file mode 100644 index 000000000000..7f29bb453f10 --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/custom_struct_array_any_value_struct_array.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8eff4136777227fc447a093bdaa5876e33ce811b450c3fe1f54b82bde74cc44d +size 8432 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/custom_struct_array_single_element_any_value_struct_array_single_element.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/custom_struct_array_single_element_any_value_struct_array_single_element.png new file mode 100644 index 000000000000..37e22beae5ec --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/custom_struct_array_single_element_any_value_struct_array_single_element.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:df7c0ab8662c48086d6fbdb6b5c16195667bf9f77be92895417bc6271defecd3 +size 5460 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/custom_url_string_any_value_url_string.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/custom_url_string_any_value_url_string.png new file mode 100644 index 000000000000..7722c7db3aa7 --- /dev/null +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/custom_url_string_any_value_url_string.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e05ed0e2df34b2e136e9e39f2516e704a7ca7056c47e21e007ccd36c03705b0f +size 5004 diff --git a/crates/viewer/re_component_ui/tests/test_all_components_ui.rs b/crates/viewer/re_component_ui/tests/test_all_components_ui.rs index f4a72382a6f3..c60e57a2b525 100644 --- a/crates/viewer/re_component_ui/tests/test_all_components_ui.rs +++ b/crates/viewer/re_component_ui/tests/test_all_components_ui.rs @@ -213,38 +213,27 @@ fn test_cases(reflection: &Reflection) -> Vec { // --- -/// Test all components UI in a narrow list item context. +/// Test all components UI as list items, in both narrow/wide and dark/light variants. #[test] -pub fn test_all_components_ui_as_list_items_narrow() { - let test_context = get_test_context(); - let test_cases = test_cases(&test_context.reflection); - let snapshot_options = SnapshotOptions::new() - .output_path("tests/snapshots/all_components_list_item_narrow") - .threshold(OsThreshold::default().macos(2.5)); - - let results = test_cases - .iter() - .map(|test_case| { - test_single_component_ui_as_list_item( - &test_context, - test_case, - 200.0, - &snapshot_options, - ) - }) - .collect_vec(); - - check_for_unused_snapshots(&test_cases, &snapshot_options); - check_and_print_results(&test_cases, &results); +pub fn test_all_components_ui_as_list_items() { + let test_cases = [ + (200.0, "narrow", egui::Theme::Dark, "dark"), + (600.0, "wide", egui::Theme::Light, "light"), + ]; + for (width, width_name, theme, theme_name) in test_cases { + run_all_component_tests( + width, + theme, + &format!("tests/snapshots/all_components_list_item_{width_name}_{theme_name}"), + ); + } } -/// Test all components UI in a wide list item context. -#[test] -pub fn test_all_components_ui_as_list_items_wide() { +fn run_all_component_tests(width: f32, theme: egui::Theme, output_dir: &str) { let test_context = get_test_context(); let test_cases = test_cases(&test_context.reflection); let snapshot_options = SnapshotOptions::new() - .output_path("tests/snapshots/all_components_list_item_wide") + .output_path(output_dir) .threshold(OsThreshold::default().macos(2.5)); let results = test_cases @@ -253,7 +242,8 @@ pub fn test_all_components_ui_as_list_items_wide() { test_single_component_ui_as_list_item( &test_context, test_case, - 600.0, + width, + theme, &snapshot_options, ) }) @@ -267,6 +257,7 @@ fn test_single_component_ui_as_list_item( test_context: &TestContext, test_case: &TestCase, ui_width: f32, + theme: egui::Theme, _snapshot_options: &SnapshotOptions, ) -> Result<(), SnapshotError> { let actual_ui = |ctx: &ViewerContext<'_>, ui: &mut egui::Ui| { @@ -297,6 +288,7 @@ fn test_single_component_ui_as_list_item( let mut harness = test_context .setup_kittest_for_rendering_ui([ui_width, 40.0]) + .with_theme(theme) .build_ui(|ui| { test_context.run(&ui.ctx().clone(), |ctx| { ui.full_span_scope(ui.max_rect().x_range(), |ui| { From 47596e0436de132662ce54753f975020a25e9ced Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jochen=20G=C3=B6rtler?= Date: Mon, 2 Mar 2026 16:50:18 +0100 Subject: [PATCH 012/513] Improve Lenses errors and debug output ### Related * [x] Requires #936 * [x] ~Provide similar context for `oneof` RR-2297~. Will do in follow-up PR. ### What Improve the error (and debug) output for Lenses. When a Lens fails we want to know exactly which one (and why). Source-Ref: 82cb9977000be5a67199fc379890982ad1ad68ae --- crates/store/re_lenses/src/ast.rs | 103 +++++++++++++++++----------- crates/store/re_lenses/src/error.rs | 12 +++- 2 files changed, 72 insertions(+), 43 deletions(-) diff --git a/crates/store/re_lenses/src/ast.rs b/crates/store/re_lenses/src/ast.rs index 243e41830340..ed919c18cbdc 100644 --- a/crates/store/re_lenses/src/ast.rs +++ b/crates/store/re_lenses/src/ast.rs @@ -403,37 +403,46 @@ fn apply_ops(initial: ListArray, ops: &[Op]) -> Result, OpErro fn collect_output_components_iter<'a>( input: &'a SerializedComponentColumn, components: &'a [ComponentOutput], + entity_path: &'a EntityPath, ) -> impl Iterator> + 'a { - components.iter().filter_map( - |output| match apply_ops(input.list_array.clone(), &output.ops) { + components.iter().filter_map(move |output| { + match apply_ops(input.list_array.clone(), &output.ops) { Ok(Some(list_array)) => Some(Ok((output.component_descr.clone(), list_array))), Ok(None) => { re_log::debug_once!( - "Lens suppressed for component `{}`", + "Lens suppressed for `{entity_path}` component `{}`", output.component_descr.component ); None } Err(source) => Some(Err(LensError::ComponentOperationFailed { + entity_path: entity_path.clone(), + input_component: input.descriptor.component, component: output.component_descr.component, source: Box::new(source), })), - }, - ) + } + }) } fn collect_output_times_iter<'a>( input: &'a SerializedComponentColumn, timelines: &'a [TimeOutput], + entity_path: &'a EntityPath, ) -> impl Iterator> + 'a { timelines.iter().filter_map( - |time| match apply_ops(input.list_array.clone(), &time.ops) { + move |time| match apply_ops(input.list_array.clone(), &time.ops) { Ok(Some(list_array)) => Some(Ok((time.timeline_name, time.timeline_type, list_array))), Ok(None) => { - re_log::debug_once!("Lens suppressed for timeline `{}`", time.timeline_name); + re_log::debug_once!( + "Lens suppressed for `{entity_path}` timeline `{}`", + time.timeline_name, + ); None } Err(source) => Some(Err(LensError::TimeOperationFailed { + entity_path: entity_path.clone(), + input_component: input.descriptor.component, timeline_name: time.timeline_name, source: Box::new(source), })), @@ -523,7 +532,7 @@ impl OneToOne { // Collect successful components directly into ChunkComponents, accumulate errors let component_results: re_chunk::ChunkComponents = - collect_output_components_iter(input, &self.components) + collect_output_components_iter(input, &self.components, entity_path) .filter_map(|result| match result { Ok(component) => Some(component), Err(err) => { @@ -538,19 +547,21 @@ impl OneToOne { // Collect successful time columns, accumulate errors chunk_times.extend( - collect_output_times_iter(input, &self.times).filter_map(|result| match result { - Ok((timeline_name, timeline_type, list_array)) => { - match try_convert_time_column(timeline_name, timeline_type, &list_array) { - Ok(time_col) => Some(time_col), - Err(err) => { - errors.push(err); - None + collect_output_times_iter(input, &self.times, entity_path).filter_map(|result| { + match result { + Ok((timeline_name, timeline_type, list_array)) => { + match try_convert_time_column(timeline_name, timeline_type, &list_array) { + Ok(time_col) => Some(time_col), + Err(err) => { + errors.push(err); + None + } } } - } - Err(err) => { - errors.push(err); - None + Err(err) => { + errors.push(err); + None + } } }), ); @@ -575,7 +586,7 @@ impl Static { // Collect successful components directly into ChunkComponents, accumulate errors let component_results: re_chunk::ChunkComponents = - collect_output_components_iter(input, &self.components) + collect_output_components_iter(input, &self.components, entity_path) .filter_map(|result| match result { Ok(component) => Some(component), Err(err) => { @@ -613,7 +624,7 @@ impl OneToMany { let mut errors = Vec::new(); let mut output_components = - collect_output_components_iter(input, &self.components).peekable(); + collect_output_components_iter(input, &self.components, entity_path).peekable(); // Peek at the first component to establish the scatter pattern (how many output rows // each input row produces). All components must have the same outer list structure. @@ -697,30 +708,38 @@ impl OneToMany { // Explode all output time columns and collect errors chunk_times.extend( - collect_output_times_iter(input, &self.times).filter_map(|result| match result { - Ok((timeline_name, timeline_type, list_array)) => { - match reshape::Explode.transform(&list_array) { - Ok(exploded) => { - match try_convert_time_column(timeline_name, timeline_type, &exploded) { - Ok(time_col) => Some(time_col), - Err(err) => { - errors.push(err); - None + collect_output_times_iter(input, &self.times, entity_path).filter_map(|result| { + match result { + Ok((timeline_name, timeline_type, list_array)) => { + match reshape::Explode.transform(&list_array) { + Ok(exploded) => { + match try_convert_time_column( + timeline_name, + timeline_type, + &exploded, + ) { + Ok(time_col) => Some(time_col), + Err(err) => { + errors.push(err); + None + } } } - } - Err(err) => { - errors.push(LensError::TimeOperationFailed { - timeline_name, - source: Box::new(err.into()), - }); - None + Err(err) => { + errors.push(LensError::TimeOperationFailed { + entity_path: entity_path.clone(), + input_component: input.descriptor.component, + timeline_name, + source: Box::new(err.into()), + }); + None + } } } - } - Err(err) => { - errors.push(err); - None + Err(err) => { + errors.push(err); + None + } } }), ); @@ -735,6 +754,8 @@ impl OneToMany { } Err(err) => { errors.push(LensError::ComponentOperationFailed { + entity_path: entity_path.clone(), + input_component: input.descriptor.component, component: component_descr.component, source: Box::new(err.into()), }); diff --git a/crates/store/re_lenses/src/error.rs b/crates/store/re_lenses/src/error.rs index fa32ddc20f24..2998cd0d61f9 100644 --- a/crates/store/re_lenses/src/error.rs +++ b/crates/store/re_lenses/src/error.rs @@ -28,15 +28,23 @@ pub enum LensError { #[error("Chunk validation failed: {0}")] ChunkValidationFailed(#[from] re_chunk::ChunkError), - #[error("Failed to apply operations to component '{component}': {source}")] + #[error( + "Failed to apply operations to component '{component}' (entity: `{entity_path}`, input: `{input_component}`): {source}" + )] ComponentOperationFailed { + entity_path: EntityPath, + input_component: ComponentIdentifier, component: ComponentIdentifier, #[source] source: Box, // Box because of size. }, - #[error("Failed to apply operations to timeline '{timeline_name}': {source}")] + #[error( + "Failed to apply operations to timeline '{timeline_name}' (entity: `{entity_path}`, input: `{input_component}`): {source}" + )] TimeOperationFailed { + entity_path: EntityPath, + input_component: ComponentIdentifier, timeline_name: TimelineName, #[source] source: Box, // Box because of size. From 0d66ceb08f20df4d007934f234568c2002d82517 Mon Sep 17 00:00:00 2001 From: Andreas Reich Date: Mon, 2 Mar 2026 16:53:34 +0100 Subject: [PATCH 013/513] Limit number of plots only for non-builtin components and increase the limit ### What In 0.30.0 we added a limitation to the number of time series we allow in a single plot if they come from a custom component. This is to address cases where this could cause the viewer to grind to a halt if a value is selected with extremely large arrays. This PR walks this back a little bit: if the scalar source is a rerun semanic `Scalar`, we assume this was very much intentional and don't apply this limitation. image Also: * we had the limitation on visibility as well which was redundant and not all that helpful imho * bump the limit to 100, after all this is only really to deal with the viewer starting to hang otherwise Tested with this throwaway script: ```py """Test script for MAX_NUM_SERIES_FOR_REMAPPED_SCALARS behavior. Logs two entities: - /native_scalars: 200 scalar series via identity mapping (should show all 200) - /custom_component: 200 series via a custom f64 component (should be capped at 100) """ import math import rerun as rr rr.init("test_series_limit", spawn=True) for t in range(200): rr.set_time("frame", sequence=t) # 100 native scalars (identity mapping, no limit expected) scalars = [math.sin(i * 0.1 + t * 0.05) for i in range(200)] rr.log("/native_scalars", rr.Scalars(scalars)) # 100 values on a custom component (non-identity mapping, should be capped at 100) values = [math.cos(i * 0.1 + t * 0.05) for i in range(200)] rr.log("/custom_component", rr.AnyValues(my_values=values)) ``` --------- Source-Ref: 80612668fd6ab95f926423e30c9ac3522d063fc0 Co-authored-by: Claude Opus 4.6 --- crates/viewer/re_view/src/visualizer_query.rs | 31 ++++++++---- .../src/visualizer_system.rs | 2 +- .../re_view_graph/src/visualizers/edges.rs | 2 +- .../re_view_graph/src/visualizers/nodes.rs | 2 +- .../src/visualizers/geo_line_strings.rs | 2 +- .../re_view_map/src/visualizers/geo_points.rs | 2 +- .../visualizers/utilities/entity_iterator.rs | 2 +- .../src/visualizers/video/video_stream.rs | 2 +- .../re_view_tensor/src/visualizer_system.rs | 2 +- .../re_view_text_log/src/visualizer_system.rs | 2 +- .../re_view_time_series/src/fallbacks.rs | 49 +------------------ crates/viewer/re_view_time_series/src/lib.rs | 7 ++- .../src/line_visualizer_system.rs | 2 +- .../src/point_visualizer_system.rs | 2 +- .../re_view_time_series/src/series_query.rs | 24 ++++++--- .../src/points3d_color_visualizer.rs | 2 +- .../src/height_field_visualizer.rs | 2 +- .../snapshots/series_count_exceeds_max.png | 4 +- .../visualizer_instruction_errors_test.rs | 45 +++++++++++++++-- 19 files changed, 102 insertions(+), 84 deletions(-) diff --git a/crates/viewer/re_view/src/visualizer_query.rs b/crates/viewer/re_view/src/visualizer_query.rs index eb1721cc3e65..e7a0d6f07525 100644 --- a/crates/viewer/re_view/src/visualizer_query.rs +++ b/crates/viewer/re_view/src/visualizer_query.rs @@ -1,7 +1,9 @@ use re_log_types::hash::Hash64; +use re_sdk_types::ComponentIdentifier; use re_sdk_types::blueprint::components::VisualizerInstructionId; use re_viewer_context::{ - QueryContext, VisualizerInstructionReport, VisualizerReportContext, VisualizerReportSeverity, + QueryContext, VisualizerInstruction, VisualizerInstructionReport, VisualizerReportContext, + VisualizerReportSeverity, }; use crate::{ @@ -11,7 +13,7 @@ use crate::{ /// Utility for processing queries while executing a visualizer instruction and reporting errors/warnings as they arise. pub struct VisualizerInstructionQueryResults<'a> { - instruction_id: VisualizerInstructionId, + instruction: &'a VisualizerInstruction, query_results: &'a BlueprintResolvedResults<'a>, output: &'a re_viewer_context::VisualizerExecutionOutput, } @@ -19,7 +21,7 @@ pub struct VisualizerInstructionQueryResults<'a> { impl<'a> VisualizerInstructionQueryResults<'a> { /// Create a new query results wrapper. pub fn new( - instruction_id: VisualizerInstructionId, + instruction: &'a VisualizerInstruction, query_results: &'a BlueprintResolvedResults<'a>, output: &'a re_viewer_context::VisualizerExecutionOutput, ) -> Self { @@ -27,7 +29,7 @@ impl<'a> VisualizerInstructionQueryResults<'a> { output.set_missing_chunks(); } Self { - instruction_id, + instruction, query_results, output, } @@ -35,7 +37,18 @@ impl<'a> VisualizerInstructionQueryResults<'a> { /// The visualizer instruction ID these results are associated with. pub fn instruction_id(&self) -> VisualizerInstructionId { - self.instruction_id + self.instruction.id + } + + /// Whether the given component has an identity mapping on this visualizer instruction. + /// + /// Identity means the component maps directly to itself with no selector, + /// which is also the default when no explicit mapping is present. + pub fn has_identity_mapping_for_component(&self, component: ComponentIdentifier) -> bool { + self.instruction + .component_mappings + .get(&component) + .is_none_or(|source| source.is_identity_mapping(component)) } /// Returns a zero-copy iterator over all the results for the given `(timeline, component)` pair. @@ -74,7 +87,7 @@ impl<'a> VisualizerInstructionQueryResults<'a> { details: err.details(), }; - self.output.report(self.instruction_id, report); + self.output.report(self.instruction.id, report); } ChunksWithComponent::empty(component) @@ -115,7 +128,7 @@ impl<'a> VisualizerInstructionQueryResults<'a> { details: err.details(), }; - self.output.report(self.instruction_id, report); + self.output.report(self.instruction.id, report); ChunksWithComponent::empty(component) } }; @@ -134,7 +147,7 @@ impl<'a> VisualizerInstructionQueryResults<'a> { message: impl Into, ) { self.output - .report_unspecified_source(self.instruction_id, severity, message); + .report_unspecified_source(self.instruction.id, severity, message); } /// Report a diagnostic tied to a specific component. @@ -145,7 +158,7 @@ impl<'a> VisualizerInstructionQueryResults<'a> { summary: impl Into, ) { self.output.report( - self.instruction_id, + self.instruction.id, VisualizerInstructionReport { severity, context: VisualizerReportContext { diff --git a/crates/viewer/re_view_bar_chart/src/visualizer_system.rs b/crates/viewer/re_view_bar_chart/src/visualizer_system.rs index d7d93ecb7156..e1536ab2a07d 100644 --- a/crates/viewer/re_view_bar_chart/src/visualizer_system.rs +++ b/crates/viewer/re_view_bar_chart/src/visualizer_system.rs @@ -81,7 +81,7 @@ impl VisualizerSystem for BarChartVisualizerSystem { let results = BlueprintResolvedResults::LatestAt(timeline_query.clone(), latest_at_results); let results = - VisualizerInstructionQueryResults::new(instruction.id, &results, &output); + VisualizerInstructionQueryResults::new(instruction, &results, &output); let widths = results.iter_optional(BarChart::descriptor_widths().component); let widths: &[f32] = widths diff --git a/crates/viewer/re_view_graph/src/visualizers/edges.rs b/crates/viewer/re_view_graph/src/visualizers/edges.rs index d8ed1e8fdd84..2898b409fe98 100644 --- a/crates/viewer/re_view_graph/src/visualizers/edges.rs +++ b/crates/viewer/re_view_graph/src/visualizers/edges.rs @@ -77,7 +77,7 @@ impl VisualizerSystem for EdgesVisualizer { timeline_query.clone(), latest_at_results, )); - let results = VisualizerInstructionQueryResults::new(instruction.id, &results, &output); + let results = VisualizerInstructionQueryResults::new(instruction, &results, &output); let all_edges = results.iter_required(GraphEdges::descriptor_edges().component); let graph_type = results diff --git a/crates/viewer/re_view_graph/src/visualizers/nodes.rs b/crates/viewer/re_view_graph/src/visualizers/nodes.rs index e8838f27aa0c..d92ad5056b05 100644 --- a/crates/viewer/re_view_graph/src/visualizers/nodes.rs +++ b/crates/viewer/re_view_graph/src/visualizers/nodes.rs @@ -90,7 +90,7 @@ impl VisualizerSystem for NodeVisualizer { timeline_query.clone(), latest_at_results, )); - let results = VisualizerInstructionQueryResults::new(instruction.id, &results, &output); + let results = VisualizerInstructionQueryResults::new(instruction, &results, &output); let all_nodes = results.iter_required(GraphNodes::descriptor_node_ids().component); let all_colors = results.iter_optional(GraphNodes::descriptor_colors().component); diff --git a/crates/viewer/re_view_map/src/visualizers/geo_line_strings.rs b/crates/viewer/re_view_map/src/visualizers/geo_line_strings.rs index 4491858635e5..248252596de0 100644 --- a/crates/viewer/re_view_map/src/visualizers/geo_line_strings.rs +++ b/crates/viewer/re_view_map/src/visualizers/geo_line_strings.rs @@ -54,7 +54,7 @@ impl VisualizerSystem for GeoLineStringsVisualizer { view_query, instruction, ); - let results = VisualizerInstructionQueryResults::new(instruction.id, &results, &output); + let results = VisualizerInstructionQueryResults::new(instruction, &results, &output); let mut batch_data = GeoLineStringsBatch::default(); diff --git a/crates/viewer/re_view_map/src/visualizers/geo_points.rs b/crates/viewer/re_view_map/src/visualizers/geo_points.rs index 11cf63cddfc3..d7a6d90a2ab2 100644 --- a/crates/viewer/re_view_map/src/visualizers/geo_points.rs +++ b/crates/viewer/re_view_map/src/visualizers/geo_points.rs @@ -56,7 +56,7 @@ impl VisualizerSystem for GeoPointsVisualizer { { let results = data_result.query_archetype_with_history::(ctx, view_query, instruction); - let results = VisualizerInstructionQueryResults::new(instruction.id, &results, &output); + let results = VisualizerInstructionQueryResults::new(instruction, &results, &output); let annotation_context = annotation_scene_context.0.find(&data_result.entity_path); diff --git a/crates/viewer/re_view_spatial/src/visualizers/utilities/entity_iterator.rs b/crates/viewer/re_view_spatial/src/visualizers/utilities/entity_iterator.rs index 452545658388..80b0ff2f50b8 100644 --- a/crates/viewer/re_view_spatial/src/visualizers/utilities/entity_iterator.rs +++ b/crates/viewer/re_view_spatial/src/visualizers/utilities/entity_iterator.rs @@ -79,7 +79,7 @@ where data_result.query_archetype_with_history::(ctx, query, visualizer_instruction); let visualizer_instruction_result = - VisualizerInstructionQueryResults::new(visualizer_instruction.id, &results, output); + VisualizerInstructionQueryResults::new(visualizer_instruction, &results, output); let mut query_ctx = ctx.query_context(data_result, latest_at.clone(), visualizer_instruction.id); diff --git a/crates/viewer/re_view_spatial/src/visualizers/video/video_stream.rs b/crates/viewer/re_view_spatial/src/visualizers/video/video_stream.rs index 64503ea39674..ca6785c8ea1a 100644 --- a/crates/viewer/re_view_spatial/src/visualizers/video/video_stream.rs +++ b/crates/viewer/re_view_spatial/src/visualizers/video/video_stream.rs @@ -104,7 +104,7 @@ impl VisualizerSystem for VideoStreamVisualizer { ), ); let opacity_result = re_view::VisualizerInstructionQueryResults::new( - instruction.id, + instruction, &opacity_result_wrapped, &output, ); diff --git a/crates/viewer/re_view_tensor/src/visualizer_system.rs b/crates/viewer/re_view_tensor/src/visualizer_system.rs index a88f275c0736..305a4e358a87 100644 --- a/crates/viewer/re_view_tensor/src/visualizer_system.rs +++ b/crates/viewer/re_view_tensor/src/visualizer_system.rs @@ -60,7 +60,7 @@ impl VisualizerSystem for TensorSystem { let results = re_view::BlueprintResolvedResults::from((timeline_query, latest_at_results)); let results = - re_view::VisualizerInstructionQueryResults::new(instruction.id, &results, &output); + re_view::VisualizerInstructionQueryResults::new(instruction, &results, &output); let all_tensor_chunks = results.iter_required(Tensor::descriptor_data().component); if all_tensor_chunks.is_empty() { diff --git a/crates/viewer/re_view_text_log/src/visualizer_system.rs b/crates/viewer/re_view_text_log/src/visualizer_system.rs index 6b441008d51d..1d1b45d5693c 100644 --- a/crates/viewer/re_view_text_log/src/visualizer_system.rs +++ b/crates/viewer/re_view_text_log/src/visualizer_system.rs @@ -94,7 +94,7 @@ impl TextLogSystem { // Convert to HybridResults for unified access let results = re_view::BlueprintResolvedResults::from((query.clone(), range_results)); let results = - re_view::VisualizerInstructionQueryResults::new(instruction.id, &results, output); + re_view::VisualizerInstructionQueryResults::new(instruction, &results, output); let all_texts = results.iter_required(TextLog::descriptor_text().component); if all_texts.is_empty() { diff --git a/crates/viewer/re_view_time_series/src/fallbacks.rs b/crates/viewer/re_view_time_series/src/fallbacks.rs index 50e9411398ad..5411567d3a44 100644 --- a/crates/viewer/re_view_time_series/src/fallbacks.rs +++ b/crates/viewer/re_view_time_series/src/fallbacks.rs @@ -6,10 +6,8 @@ use re_sdk_types::{ }; use re_viewer_context::ViewStateExt as _; +use crate::MAX_NUM_ITEMS_IN_PLOT_LEGEND_BEFORE_HIDDEN; use crate::view_class::{TimeSeriesViewState, make_range_sane}; -use crate::{ - MAX_NUM_ITEMS_IN_PLOT_LEGEND_BEFORE_HIDDEN, MAX_NUM_TIME_SERIES_SHOWN_PER_ENTITY_BY_DEFAULT, -}; /// Register fallback providers for TimeSeriesView-related components and view properties. pub fn register_fallbacks(system_registry: &mut re_viewer_context::ViewSystemRegistrator<'_>) { @@ -99,51 +97,6 @@ pub fn register_fallbacks(system_registry: &mut re_viewer_context::ViewSystemReg ); } - for component in [ - SeriesLines::descriptor_visible_series().component, - SeriesPoints::descriptor_visible_series().component, - ] { - system_registry.register_array_fallback_provider::( - component, - |ctx| { - let show_all = itertools::Either::Left(std::iter::once(true.into())); - - let Some(time_series_state) = ctx - .view_state() - .as_any() - .downcast_ref::() - else { - return itertools::Either::Left(std::iter::once(true.into())); - }; - - // Get the number of series for this specific instruction - let num_series = ctx - .instruction_id - .and_then(|id| { - time_series_state - .num_time_series_last_frame_per_instruction - .get(&id) - }) - .map_or(0, |set| set.len()); - - let num_shown = num_series.min(MAX_NUM_TIME_SERIES_SHOWN_PER_ENTITY_BY_DEFAULT); - let num_hidden = num_series.saturating_sub(num_shown); - - if num_hidden == 0 { - show_all // Prefer a single boolean if we can, it's nicer in the ui. - } else { - itertools::Either::Right( - std::iter::repeat_n( - true.into(), - MAX_NUM_TIME_SERIES_SHOWN_PER_ENTITY_BY_DEFAULT, - ) - .chain(std::iter::repeat_n(false.into(), num_hidden)), - ) - } - }, - ); - } - system_registry.register_fallback_provider(ScalarAxis::descriptor_range().component, |ctx| { ctx.view_state() .as_any() diff --git a/crates/viewer/re_view_time_series/src/lib.rs b/crates/viewer/re_view_time_series/src/lib.rs index a18124034d27..d5b8c0252b22 100644 --- a/crates/viewer/re_view_time_series/src/lib.rs +++ b/crates/viewer/re_view_time_series/src/lib.rs @@ -22,7 +22,12 @@ use re_viewer_context::external::re_entity_db::InstancePath; use re_viewport_blueprint::ViewPropertyQueryError; pub use view_class::TimeSeriesView; -pub(crate) const MAX_NUM_TIME_SERIES_SHOWN_PER_ENTITY_BY_DEFAULT: usize = 20; +/// Maximum number of time series shown per entity when the scalar component +/// has a non-identity mapping (e.g. sourced from a different component or using a selector). +/// +/// This limit is NOT applied when the scalar component has an identity mapping, +/// since in that case the user explicitly logged `Scalars` data and knows how many series to expect. +pub(crate) const MAX_NUM_SERIES_FOR_REMAPPED_SCALARS: usize = 100; pub(crate) const MAX_NUM_ITEMS_IN_PLOT_LEGEND_BEFORE_HIDDEN: usize = 20; /// Computes a deterministic, globally unique ID for the plot based on the ID of the view diff --git a/crates/viewer/re_view_time_series/src/line_visualizer_system.rs b/crates/viewer/re_view_time_series/src/line_visualizer_system.rs index 10f95a07986a..c5c9d383b0d9 100644 --- a/crates/viewer/re_view_time_series/src/line_visualizer_system.rs +++ b/crates/viewer/re_view_time_series/src/line_visualizer_system.rs @@ -150,7 +150,7 @@ impl SeriesLinesSystem { // Wrap results for convenient error-reporting iteration let results = re_view::BlueprintResolvedResults::Range(query.clone(), results); let results = - re_view::VisualizerInstructionQueryResults::new(instruction.id, &results, output); + re_view::VisualizerInstructionQueryResults::new(instruction, &results, output); // If we have no scalars, we can't do anything. let scalar_iter = diff --git a/crates/viewer/re_view_time_series/src/point_visualizer_system.rs b/crates/viewer/re_view_time_series/src/point_visualizer_system.rs index cd34b27d3e16..7298ee734d5e 100644 --- a/crates/viewer/re_view_time_series/src/point_visualizer_system.rs +++ b/crates/viewer/re_view_time_series/src/point_visualizer_system.rs @@ -146,7 +146,7 @@ impl SeriesPointsSystem { // Wrap results for convenient error-reporting iteration let results = re_view::BlueprintResolvedResults::Range(query.clone(), results); let results = - re_view::VisualizerInstructionQueryResults::new(instruction.id, &results, output); + re_view::VisualizerInstructionQueryResults::new(instruction, &results, output); // If we have no scalars, we can't do anything. let scalar_iter = diff --git a/crates/viewer/re_view_time_series/src/series_query.rs b/crates/viewer/re_view_time_series/src/series_query.rs index 8f52d5adb068..d1d1ef846976 100644 --- a/crates/viewer/re_view_time_series/src/series_query.rs +++ b/crates/viewer/re_view_time_series/src/series_query.rs @@ -8,15 +8,21 @@ use re_log_types::external::arrow::array::AsArray as _; use re_log_types::external::arrow::buffer::BooleanBuffer; use re_log_types::external::arrow::datatypes::UInt32Type; use re_sdk_types::external::arrow::datatypes::DataType as ArrowDatatype; -use re_sdk_types::{ComponentDescriptor, Loggable as _, RowId, components}; +use re_sdk_types::{ComponentDescriptor, Loggable as _, RowId, archetypes, components}; use re_view::clamped_or_nothing; use re_viewer_context::VisualizerReportSeverity; -use crate::{MAX_NUM_TIME_SERIES_SHOWN_PER_ENTITY_BY_DEFAULT, PlotPoint, PlotSeriesKind}; +use crate::{MAX_NUM_SERIES_FOR_REMAPPED_SCALARS, PlotPoint, PlotSeriesKind}; type PlotPointsPerSeries = smallvec::SmallVec<[Vec; 1]>; /// Determines how many series there are in the scalar chunks. +/// +/// If the scalar component has a non-identity mapping (i.e. it's sourced from a different +/// component or uses a selector), the number of series is capped at +/// [`MAX_NUM_SERIES_FOR_REMAPPED_SCALARS`]. +/// Identity mappings (direct `Scalars` data) are not capped since the user explicitly +/// logged that data. pub fn determine_num_series( all_scalar_chunks: &re_view::ChunksWithComponent<'_>, results: &re_view::VisualizerInstructionQueryResults<'_>, @@ -32,10 +38,16 @@ pub fn determine_num_series( .find_map(|slice| (!slice.is_empty()).then_some(slice.len())) }) .unwrap_or(1); - if count > MAX_NUM_TIME_SERIES_SHOWN_PER_ENTITY_BY_DEFAULT { - results.report_unspecified_source(VisualizerReportSeverity::Error, - format!("Number of series ({count}) exceeds the maximum ({MAX_NUM_TIME_SERIES_SHOWN_PER_ENTITY_BY_DEFAULT}). Only the first {MAX_NUM_TIME_SERIES_SHOWN_PER_ENTITY_BY_DEFAULT} series will be visualized.")); - MAX_NUM_TIME_SERIES_SHOWN_PER_ENTITY_BY_DEFAULT + + let scalar_component = archetypes::Scalars::descriptor_scalars().component; + let is_identity = results.has_identity_mapping_for_component(scalar_component); + + if !is_identity && count > MAX_NUM_SERIES_FOR_REMAPPED_SCALARS { + results.report_unspecified_source(VisualizerReportSeverity::Error, format!( + "Number of series ({count}) exceeds the maximum ({MAX_NUM_SERIES_FOR_REMAPPED_SCALARS}). \ + Only the first {MAX_NUM_SERIES_FOR_REMAPPED_SCALARS} series will be visualized." + )); + MAX_NUM_SERIES_FOR_REMAPPED_SCALARS } else { count } diff --git a/examples/rust/custom_view/src/points3d_color_visualizer.rs b/examples/rust/custom_view/src/points3d_color_visualizer.rs index 223c7d04b76c..82d54b722780 100644 --- a/examples/rust/custom_view/src/points3d_color_visualizer.rs +++ b/examples/rust/custom_view/src/points3d_color_visualizer.rs @@ -64,7 +64,7 @@ impl VisualizerSystem for Points3DColorVisualizer { [rerun::Points3D::descriptor_colors().component], instruction, ); - let results = VisualizerInstructionQueryResults::new(instruction.id, &results, &output); + let results = VisualizerInstructionQueryResults::new(instruction, &results, &output); // From the query result, get all the color arrays as `[u32]` slices. // For latest-at queries should be only a single slice`, diff --git a/examples/rust/custom_visualizer/src/height_field_visualizer.rs b/examples/rust/custom_visualizer/src/height_field_visualizer.rs index 67fa57a8eda9..01757de19149 100644 --- a/examples/rust/custom_visualizer/src/height_field_visualizer.rs +++ b/examples/rust/custom_visualizer/src/height_field_visualizer.rs @@ -53,7 +53,7 @@ impl VisualizerSystem for HeightFieldVisualizer { let results = data_result.query_archetype_with_history::(ctx, query, instruction); - let results = VisualizerInstructionQueryResults::new(instruction.id, &results, &output); + let results = VisualizerInstructionQueryResults::new(instruction, &results, &output); let transform = transform_info.single_transform_required_for_entity(ent_path, HeightField::name()); diff --git a/tests/rust/re_integration_test/tests/snapshots/series_count_exceeds_max.png b/tests/rust/re_integration_test/tests/snapshots/series_count_exceeds_max.png index c6b18b1652c3..81a74f51cfb5 100644 --- a/tests/rust/re_integration_test/tests/snapshots/series_count_exceeds_max.png +++ b/tests/rust/re_integration_test/tests/snapshots/series_count_exceeds_max.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0853e3f50e3465b35792323b8e14b35ee92f2ae08c3d95f79986280f3f9b84a1 -size 471479 +oid sha256:c0ac1b64fd920ab3633b112256010512afb06104aac2e215cf060a3a4442d842 +size 1425681 diff --git a/tests/rust/re_integration_test/tests/visualizer_instruction_errors_test.rs b/tests/rust/re_integration_test/tests/visualizer_instruction_errors_test.rs index 2968623b13fa..28fb2f3de986 100644 --- a/tests/rust/re_integration_test/tests/visualizer_instruction_errors_test.rs +++ b/tests/rust/re_integration_test/tests/visualizer_instruction_errors_test.rs @@ -110,10 +110,19 @@ pub async fn per_visualizer_instruction_errors() { harness.snapshot_app("per_visualizer_instruction_errors_3b_errors_only_menu"); } -/// Tests that logging more series than `MAX_SERIES_COUNT` doesn't crash and the extra series -/// are silently dropped. +/// Tests that logging more series than `MAX_NUM_SERIES_FOR_REMAPPED_SCALARS` via a non-identity +/// mapping doesn't crash and the extra series are silently dropped. +/// +/// The series cap only applies to non-identity mappings (i.e. when the scalar component +/// is sourced from a different component). Identity mappings (direct `Scalars` data) +/// are not capped. #[tokio::test(flavor = "multi_thread")] pub async fn series_count_exceeds_max() { + use std::sync::Arc; + + use re_sdk::external::arrow::array::Float64Array; + use re_viewer::external::re_sdk_types::DynamicArchetype; + let mut harness = viewer_test_utils::viewer_harness(&HarnessOptions::default()); harness.init_recording(); harness.set_blueprint_panel_opened(false); @@ -122,22 +131,48 @@ pub async fn series_count_exceeds_max() { let timeline = Timeline::new_sequence("t"); let num_scalars = 1001; - for t in 0..100 { + for t in 0..150 { let values: Vec = (0..num_scalars) .map(|i| ((t * 50 + i * 100) as f64 * 0.001).sin()) .collect(); + // Log as a custom f64 component (not the builtin `Scalar`). harness.log_entity("many_series", |builder| { - builder.with_archetype_auto_row([(timeline, t)], &Scalars::new(values)) + builder.with_archetype_auto_row( + [(timeline, t)], + &DynamicArchetype::new_without_archetype() + .with_component_from_data("my_values", Arc::new(Float64Array::from(values))), + ) }); } - harness.setup_viewport_blueprint(|_viewer_context, blueprint| { + // Set up a non-identity mapping: source `my_values` → target `rerun.components.Scalar`. + let remapped_scalar = VisualizerComponentMapping { + target: Scalars::descriptor_scalars().component.as_str().into(), + source_kind: ComponentSourceKind::SourceComponent, + source_component: Some("my_values".into()), + selector: None, + }; + + let view_id = harness.setup_viewport_blueprint(|_viewer_context, blueprint| { blueprint.add_view_at_root(ViewBlueprint::new( TimeSeriesView::identifier(), RecommendedView::new_single_entity("many_series"), )) }); + harness.setup_viewport_blueprint(move |viewer_context, _blueprint| { + viewer_context.save_visualizers( + &"many_series".into(), + view_id, + [SeriesLines::default() + .visualizer() + .with_mappings([remapped_scalar.into()])], + ); + }); + harness.click_label("View errors"); + + // TODO(#12450): macOS CI uses SwiftShader which produces images too different from the reference. + #[cfg(not(target_os = "macos"))] harness.snapshot_app("series_count_exceeds_max"); } From aa8d6891e44436316ca22f3d4a1faecb67796d7b Mon Sep 17 00:00:00 2001 From: Emad Akhras Date: Tue, 3 Mar 2026 07:06:33 +0800 Subject: [PATCH 014/513] Fix `rerun-build-web` failure when `CARGO_TARGET_DIR` is set outside workspace (#12675) ### Related * Closes #12664 ### What * replace `target_wasm_dir.parent().unwrap()` with `workspace_root()` when deriving the repository root directory Source-Ref: 4fcd0beef57f2062ce5b83e6105fcf1a21b1d209 --- crates/build/re_dev_tools/src/build_web_viewer/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/build/re_dev_tools/src/build_web_viewer/lib.rs b/crates/build/re_dev_tools/src/build_web_viewer/lib.rs index 6797d071adc9..7c4314efdff7 100644 --- a/crates/build/re_dev_tools/src/build_web_viewer/lib.rs +++ b/crates/build/re_dev_tools/src/build_web_viewer/lib.rs @@ -94,8 +94,8 @@ pub fn build( // in order to support recursive cargo builds (calling `cargo` from within a `build.rs`). let target_wasm_dir = Utf8PathBuf::from(format!("{}_wasm", target_directory())); - // Repository root - let root_dir = target_wasm_dir.parent().unwrap(); + // Workspace root + let root_dir = workspace_root(); // Where we will place the final .wasm and .js artifacts. assert!( @@ -149,7 +149,7 @@ pub fn build( eprintln!("{root_dir}> {cmd:?}"); let status = cmd - .current_dir(root_dir) + .current_dir(&root_dir) .status() .context("Failed to build Wasm")?; From 92529ca9ba227e5e629176da1ecd8dd29423866f Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Tue, 3 Mar 2026 10:25:51 +0100 Subject: [PATCH 015/513] Fix: fix clicking names of color maps The bug affected this drop-down box in particular: Screenshot 2026-03-03 at 09 00 43 Here, the text in the labels ("Turbo" etc) were selectable, so clicking them would move an invisible text cursor to them, but NOT select the actual color map. This now works. Source-Ref: 4dec87dd4c0629480b07ab6652a9ecc25610ec4a --- crates/viewer/re_ui/src/list_item/list_item.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/crates/viewer/re_ui/src/list_item/list_item.rs b/crates/viewer/re_ui/src/list_item/list_item.rs index a31e7d5d17d2..4d2a3fdceb70 100644 --- a/crates/viewer/re_ui/src/list_item/list_item.rs +++ b/crates/viewer/re_ui/src/list_item/list_item.rs @@ -435,6 +435,7 @@ impl ListItem { } } + /// Always call this from within a scope fn ui<'a>( self, ui: &mut Ui, @@ -625,6 +626,14 @@ impl ListItem { visuals, }; + if interactive { + // Clicks and drags on content labels should be ignored, + // and go to the parent instead: + ui.style_mut().interaction.selectable_labels = false; + // (we can just modify the parent `ui` here because + // this function is always called from within an `ui.scope`) + } + let prev_widgets = ui.style_mut().visuals.widgets.clone(); content.ui(ui, &content_ctx); ui.style_mut().visuals.widgets = prev_widgets; From e0939c693e1feee2de3350bbc8ba32956c90741f Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Tue, 3 Mar 2026 11:41:46 +0100 Subject: [PATCH 016/513] Refactor UI code: introduce `StoreViewContext` ### Related * Part of https://linear.app/rerun/issue/RR-3033/refactor-displaymode-navigation * Part of https://github.com/rerun-io/rerun/issues/9029 ### What This introduces `StoreViewContext` that has all the context of `AppContext`, plus the context of a single _store_ (a recording or a blueprint). This is now used in many places where we are looking at some data, and we need to understand the context of "where does this data live?". The most important point of this PR is: MUCH less use of the over-big `ViewerContext`. AND: much less argument repetition. ### The contexts, and how they relate to each other. Here are the contexts, ordered from smallest and most general, to largest and most specific: * `AppContext`: "we have an app" * `StoreViewContext`: "we have an app and a blueprint and a recording" * `ViewerContext`: "we have an app and a blueprint and a recording and a viewport with views" --------- Source-Ref: 33171cad98ea9a12f338dde8dfe55dd81ee4c481 Co-authored-by: Claude Opus 4.6 --- Cargo.lock | 1 - .../re_blueprint_tree/src/blueprint_tree.rs | 5 +- .../re_chunk_store_ui/src/chunk_store_ui.rs | 4 +- crates/viewer/re_component_ui/src/color.rs | 4 +- .../src/datatype_uis/bool_toggle.rs | 2 +- .../src/datatype_uis/enum_ui.rs | 12 +- .../src/datatype_uis/float_drag.rs | 14 +- .../src/datatype_uis/int_drag.rs | 2 +- .../src/datatype_uis/range1d.rs | 2 +- .../src/datatype_uis/singleline_string.rs | 4 +- .../re_component_ui/src/datatype_uis/vec.rs | 6 +- .../src/datatype_uis/view_id.rs | 6 +- .../src/datatype_uis/view_timestamp.rs | 4 +- .../src/datatype_uis/view_uuid.rs | 2 +- .../viewer/re_component_ui/src/entity_path.rs | 13 +- .../re_component_ui/src/geo_line_string.rs | 6 +- .../re_component_ui/src/image_format.rs | 4 +- crates/viewer/re_component_ui/src/lat_lon.rs | 4 +- .../viewer/re_component_ui/src/line_strip.rs | 10 +- .../re_component_ui/src/map_provider.rs | 6 +- .../re_component_ui/src/marker_shape.rs | 4 +- crates/viewer/re_component_ui/src/pinhole.rs | 6 +- crates/viewer/re_component_ui/src/plane3d.rs | 4 +- crates/viewer/re_component_ui/src/radius.rs | 4 +- .../viewer/re_component_ui/src/resolution.rs | 4 +- .../re_component_ui/src/text_log_columns.rs | 6 +- .../viewer/re_component_ui/src/time_range.rs | 34 +- .../re_component_ui/src/timeline_columns.rs | 44 +- .../re_component_ui/src/transform_frame_id.rs | 12 +- .../viewer/re_component_ui/src/transforms.rs | 4 +- .../src/variant_uis/redap_entry_kind.rs | 4 +- .../src/variant_uis/redap_thumbnail.rs | 9 +- .../src/variant_uis/redap_uri_button.rs | 13 +- .../re_component_ui/src/video_timestamp.rs | 4 +- .../re_component_ui/src/view_coordinates.rs | 4 +- .../re_component_ui/src/visual_bounds2d.rs | 6 +- .../viewer/re_component_ui/src/zoom_level.rs | 2 +- .../tests/test_all_components_ui.rs | 9 +- .../re_data_ui/src/annotation_context_ui.rs | 34 +- crates/viewer/re_data_ui/src/app_id_ui.rs | 20 +- crates/viewer/re_data_ui/src/blob_ui.rs | 25 +- .../re_data_ui/src/component_path_ui.rs | 24 +- .../re_data_ui/src/component_type_ui.rs | 14 +- .../re_data_ui/src/component_ui_registry.rs | 50 +-- .../viewer/re_data_ui/src/data_source_ui.rs | 21 +- crates/viewer/re_data_ui/src/entity_db_ui.rs | 103 +++-- .../viewer/re_data_ui/src/entity_path_ui.rs | 13 +- crates/viewer/re_data_ui/src/extra_data_ui.rs | 22 +- crates/viewer/re_data_ui/src/image_ui.rs | 53 ++- .../viewer/re_data_ui/src/instance_path_ui.rs | 98 ++--- crates/viewer/re_data_ui/src/item_ui.rs | 192 +++------ .../re_data_ui/src/latest_all_instance_ui.rs | 28 +- .../re_data_ui/src/latest_at_instance_ui.rs | 27 +- crates/viewer/re_data_ui/src/lib.rs | 50 ++- crates/viewer/re_data_ui/src/store_id_ui.rs | 15 +- crates/viewer/re_data_ui/src/tensor_ui.rs | 9 +- .../re_data_ui/src/transform_frames_ui.rs | 45 +- crates/viewer/re_data_ui/src/video_ui.rs | 88 ++-- crates/viewer/re_dataframe_ui/Cargo.toml | 1 - .../src/datafusion_table_widget.rs | 26 +- .../src/display_record_batch.rs | 13 +- .../tests/ascending_descending.rs | 16 +- .../src/recording_panel_ui.rs | 14 +- crates/viewer/re_redap_browser/src/servers.rs | 14 +- .../re_selection_panel/src/defaults_ui.rs | 46 +-- .../src/item_heading_no_breadcrumbs.rs | 2 +- .../src/item_heading_with_breadcrumbs.rs | 6 +- .../re_selection_panel/src/item_title.rs | 7 +- .../re_selection_panel/src/selection_panel.rs | 64 ++- .../src/view_entity_picker.rs | 6 +- .../re_selection_panel/src/visualizer_ui.rs | 18 +- crates/viewer/re_test_context/src/lib.rs | 24 +- crates/viewer/re_time_panel/Cargo.toml | 5 +- .../benches/bench_density_graph.rs | 4 +- .../re_time_panel/src/data_density_graph.rs | 47 ++- .../re_time_panel/src/streams_tree_data.rs | 18 +- crates/viewer/re_time_panel/src/time_panel.rs | 384 ++++++++---------- .../re_time_panel/src/time_selection_ui.rs | 2 +- .../tests/time_panel_filter_tests.rs | 4 +- .../re_time_panel/tests/time_panel_tests.rs | 4 +- crates/viewer/re_view/src/view_property_ui.rs | 18 +- .../re_view_dataframe/src/dataframe_ui.rs | 23 +- .../re_view_dataframe/src/view_class.rs | 2 +- crates/viewer/re_view_graph/src/ui/draw.rs | 17 +- .../viewer/re_view_graph/src/ui/selection.rs | 2 +- crates/viewer/re_view_graph/src/view.rs | 2 +- crates/viewer/re_view_map/src/map_view.rs | 29 +- .../viewer/re_view_spatial/src/picking_ui.rs | 12 +- .../re_view_spatial/src/picking_ui_pixel.rs | 13 +- .../visualizers/utilities/textured_rect.rs | 2 +- .../video/video_frame_reference.rs | 2 +- .../viewer/re_view_text_log/src/view_class.rs | 8 +- crates/viewer/re_viewer/src/app.rs | 25 +- crates/viewer/re_viewer/src/app_state.rs | 48 ++- crates/viewer/re_viewer/src/ui/rerun_menu.rs | 8 +- crates/viewer/re_viewer/src/ui/top_panel.rs | 6 +- crates/viewer/re_viewer/tests/app_kittest.rs | 4 +- ...ore_context.rs => active_store_context.rs} | 4 +- .../re_viewer_context/src/app_context.rs | 250 +++++++++++- .../src/blueprint_helpers.rs | 32 +- .../src/component_ui_registry.rs | 100 +++-- .../src/gpu_bridge/colormap.rs | 4 +- .../src/gpu_bridge/image_to_gpu.rs | 24 +- crates/viewer/re_viewer_context/src/lib.rs | 6 +- .../viewer/re_viewer_context/src/open_url.rs | 10 +- .../re_viewer_context/src/query_context.rs | 12 +- .../viewer/re_viewer_context/src/store_hub.rs | 12 +- .../src/store_view_context.rs | 75 ++++ .../re_viewer_context/src/time_control.rs | 20 +- .../re_viewer_context/src/utils/video.rs | 7 +- .../src/view/view_context.rs | 8 +- .../re_viewer_context/src/viewer_context.rs | 227 +++-------- .../benches/data_query.rs | 4 +- .../viewer/re_viewport_blueprint/src/view.rs | 8 +- .../src/view_contents.rs | 6 +- .../custom_view/src/points3d_color_view.rs | 18 +- 116 files changed, 1488 insertions(+), 1500 deletions(-) rename crates/viewer/re_viewer_context/src/{store_context.rs => active_store_context.rs} (93%) create mode 100644 crates/viewer/re_viewer_context/src/store_view_context.rs diff --git a/Cargo.lock b/Cargo.lock index 2cedf2448644..99edf223aed0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8961,7 +8961,6 @@ dependencies = [ "ordered-float 5.1.0", "parking_lot", "re_arrow_util", - "re_chunk_store", "re_component_ui", "re_dataframe", "re_format", diff --git a/crates/viewer/re_blueprint_tree/src/blueprint_tree.rs b/crates/viewer/re_blueprint_tree/src/blueprint_tree.rs index 8c666fd400b2..4687c9cacd6e 100644 --- a/crates/viewer/re_blueprint_tree/src/blueprint_tree.rs +++ b/crates/viewer/re_blueprint_tree/src/blueprint_tree.rs @@ -676,13 +676,10 @@ impl BlueprintTree { }; let response = response.on_hover_ui(|ui| { - let query = ctx.current_query(); let include_subtree = false; re_data_ui::item_ui::entity_hover_card_ui( ui, - ctx, - &query, - ctx.recording(), + &ctx.active_recording_store_view_context(), &data_result_data.entity_path, include_subtree, ); diff --git a/crates/viewer/re_chunk_store_ui/src/chunk_store_ui.rs b/crates/viewer/re_chunk_store_ui/src/chunk_store_ui.rs index 1bfcb01bbff3..7bd8be46a921 100644 --- a/crates/viewer/re_chunk_store_ui/src/chunk_store_ui.rs +++ b/crates/viewer/re_chunk_store_ui/src/chunk_store_ui.rs @@ -8,7 +8,7 @@ use re_log_types::{ AbsoluteTimeRange, StoreKind, TimeType, Timeline, TimelineName, TimestampFormat, }; use re_ui::{UiExt as _, list_item}; -use re_viewer_context::StoreContext; +use re_viewer_context::ActiveStoreContext; use crate::chunk_list_mode::{ChunkListMode, ChunkListQueryMode}; use crate::chunk_ui::ChunkUi; @@ -73,7 +73,7 @@ impl DatastoreUi { /// or `true` if the datastore UI should remain open. pub fn ui( &mut self, - ctx: &StoreContext<'_>, + ctx: &ActiveStoreContext<'_>, ui: &mut egui::Ui, timestamp_format: TimestampFormat, ) -> bool { diff --git a/crates/viewer/re_component_ui/src/color.rs b/crates/viewer/re_component_ui/src/color.rs index 6099d09428cb..293da6d6f45d 100644 --- a/crates/viewer/re_component_ui/src/color.rs +++ b/crates/viewer/re_component_ui/src/color.rs @@ -5,7 +5,7 @@ use re_viewer_context::MaybeMutRef; use crate::color_swatch::ColorSwatch; pub fn edit_rgba32( - _ctx: &re_viewer_context::ViewerContext<'_>, + _ctx: &re_viewer_context::StoreViewContext<'_>, ui: &mut egui::Ui, value: &mut MaybeMutRef<'_, impl std::ops::DerefMut>, ) -> egui::Response { @@ -24,7 +24,7 @@ fn edit_rgba32_impl<'a>( } pub fn edit_rgba32_array( - _ctx: &re_viewer_context::ViewerContext<'_>, + _ctx: &re_viewer_context::StoreViewContext<'_>, ui: &mut egui::Ui, colors: &mut MaybeMutRef<'_, Vec>, ) -> egui::Response { diff --git a/crates/viewer/re_component_ui/src/datatype_uis/bool_toggle.rs b/crates/viewer/re_component_ui/src/datatype_uis/bool_toggle.rs index c18b4e688052..6823f97d978b 100644 --- a/crates/viewer/re_component_ui/src/datatype_uis/bool_toggle.rs +++ b/crates/viewer/re_component_ui/src/datatype_uis/bool_toggle.rs @@ -3,7 +3,7 @@ use re_viewer_context::MaybeMutRef; /// Generic editor for a boolean value. pub fn edit_bool( - _ctx: &re_viewer_context::ViewerContext<'_>, + _ctx: &re_viewer_context::StoreViewContext<'_>, ui: &mut egui::Ui, value: &mut MaybeMutRef<'_, impl std::ops::DerefMut>, ) -> egui::Response { diff --git a/crates/viewer/re_component_ui/src/datatype_uis/enum_ui.rs b/crates/viewer/re_component_ui/src/datatype_uis/enum_ui.rs index 217c914b09a1..68dcc3f500ad 100644 --- a/crates/viewer/re_component_ui/src/datatype_uis/enum_ui.rs +++ b/crates/viewer/re_component_ui/src/datatype_uis/enum_ui.rs @@ -1,5 +1,5 @@ use re_ui::UiExt as _; -use re_viewer_context::{MaybeMutRef, ViewerContext}; +use re_viewer_context::{MaybeMutRef, StoreViewContext}; use crate::response_utils::response_with_changes_of_inner; @@ -19,7 +19,7 @@ pub enum VariantAvailable { /// Trait for a type that can provide information about whether a particular variant of an enum is /// available for selection. pub trait VariantAvailableProvider { - fn is_variant_enabled(ctx: &ViewerContext<'_>, variant: EnumT) -> VariantAvailable; + fn is_variant_enabled(ctx: &StoreViewContext<'_>, variant: EnumT) -> VariantAvailable; } /// A variant available provider that makes all variants available. @@ -30,14 +30,14 @@ struct AllEnumVariantAvailable { impl VariantAvailableProvider for AllEnumVariantAvailable { - fn is_variant_enabled(_ctx: &ViewerContext<'_>, _variant: EnumT) -> VariantAvailable { + fn is_variant_enabled(_ctx: &StoreViewContext<'_>, _variant: EnumT) -> VariantAvailable { VariantAvailable::Yes } } /// Edit or view an enum value. All variants are available. pub fn edit_view_enum( - ctx: &ViewerContext<'_>, + ctx: &StoreViewContext<'_>, ui: &mut egui::Ui, current_value: &mut MaybeMutRef<'_, EnumT>, ) -> egui::Response { @@ -54,7 +54,7 @@ pub fn edit_view_enum_with_variant_available< EnumT: re_types_core::reflection::Enum + re_types_core::Component, VariantAvailT: VariantAvailableProvider, >( - ctx: &ViewerContext<'_>, + ctx: &StoreViewContext<'_>, ui: &mut egui::Ui, current_value: &mut MaybeMutRef<'_, EnumT>, ) -> egui::Response { @@ -66,7 +66,7 @@ fn edit_view_enum_impl< EnumT: re_types_core::reflection::Enum, VariantAvailT: VariantAvailableProvider, >( - ctx: &ViewerContext<'_>, + ctx: &StoreViewContext<'_>, ui: &mut egui::Ui, id_salt: &str, current_value: &mut MaybeMutRef<'_, EnumT>, diff --git a/crates/viewer/re_component_ui/src/datatype_uis/float_drag.rs b/crates/viewer/re_component_ui/src/datatype_uis/float_drag.rs index 0a73ba6ca899..ca9057f8e5c8 100644 --- a/crates/viewer/re_component_ui/src/datatype_uis/float_drag.rs +++ b/crates/viewer/re_component_ui/src/datatype_uis/float_drag.rs @@ -7,7 +7,7 @@ use re_viewer_context::{MaybeMutRef, UiLayout}; /// Generic editor for a [`re_sdk_types::datatypes::Float32`] value from zero to max float. pub fn edit_f32_zero_to_max( - ctx: &re_viewer_context::ViewerContext<'_>, + ctx: &re_viewer_context::StoreViewContext<'_>, ui: &mut egui::Ui, value: &mut MaybeMutRef<'_, impl std::ops::DerefMut>, ) -> egui::Response { @@ -16,7 +16,7 @@ pub fn edit_f32_zero_to_max( /// Generic editor for a [`re_sdk_types::datatypes::Float32`] value from zero to max float with a suffix. pub fn edit_f32_zero_to_max_with_suffix( - _ctx: &re_viewer_context::ViewerContext<'_>, + _ctx: &re_viewer_context::StoreViewContext<'_>, ui: &mut egui::Ui, value: &mut MaybeMutRef<'_, impl std::ops::DerefMut>, suffix: &str, @@ -30,7 +30,7 @@ pub fn edit_f32_zero_to_max_with_suffix( /// Generic editor for a [`re_sdk_types::datatypes::Float32`] value representing a ui points value. pub fn edit_ui_points( - ctx: &re_viewer_context::ViewerContext<'_>, + ctx: &re_viewer_context::StoreViewContext<'_>, ui: &mut egui::Ui, value: &mut MaybeMutRef<'_, impl std::ops::DerefMut>, ) -> egui::Response { @@ -39,7 +39,7 @@ pub fn edit_ui_points( /// Generic editor for a [`re_sdk_types::datatypes::Float32`] value from min to max float. pub fn edit_f32_min_to_max_float( - _ctx: &re_viewer_context::ViewerContext<'_>, + _ctx: &re_viewer_context::StoreViewContext<'_>, ui: &mut egui::Ui, value: &mut MaybeMutRef<'_, impl std::ops::DerefMut>, ) -> egui::Response { @@ -89,7 +89,7 @@ pub fn edit_f32_float_raw_with_speed_impl( /// Generic editor for a [`re_sdk_types::datatypes::Float32`] value from zero to one float. pub fn edit_f32_zero_to_one( - _ctx: &re_viewer_context::ViewerContext<'_>, + _ctx: &re_viewer_context::StoreViewContext<'_>, ui: &mut egui::Ui, value: &mut MaybeMutRef<'_, impl std::ops::DerefMut>, ) -> egui::Response { @@ -119,7 +119,7 @@ fn edit_f32_zero_to_one_raw(ui: &mut egui::Ui, value: &mut MaybeMutRef<'_, f32>) /// Generic editor for a [`re_sdk_types::datatypes::Float64`] value from zero to max float. pub fn edit_f64_zero_to_max( - _ctx: &re_viewer_context::ViewerContext<'_>, + _ctx: &re_viewer_context::StoreViewContext<'_>, ui: &mut egui::Ui, value: &mut MaybeMutRef<'_, impl std::ops::DerefMut>, ) -> egui::Response { @@ -132,7 +132,7 @@ pub fn edit_f64_zero_to_max( /// Generic editor for a [`re_sdk_types::datatypes::Float64`] value from min to max float. pub fn edit_f64_min_to_max_float( - _ctx: &re_viewer_context::ViewerContext<'_>, + _ctx: &re_viewer_context::StoreViewContext<'_>, ui: &mut egui::Ui, value: &mut MaybeMutRef<'_, impl std::ops::DerefMut>, ) -> egui::Response { diff --git a/crates/viewer/re_component_ui/src/datatype_uis/int_drag.rs b/crates/viewer/re_component_ui/src/datatype_uis/int_drag.rs index 63d15921e7b7..14e9cfbb27e6 100644 --- a/crates/viewer/re_component_ui/src/datatype_uis/int_drag.rs +++ b/crates/viewer/re_component_ui/src/datatype_uis/int_drag.rs @@ -7,7 +7,7 @@ use re_viewer_context::{MaybeMutRef, UiLayout}; /// Generic editor for a [`re_sdk_types::datatypes::UInt64`] values within a given range. pub fn edit_u64_range( - _ctx: &re_viewer_context::ViewerContext<'_>, + _ctx: &re_viewer_context::StoreViewContext<'_>, ui: &mut egui::Ui, value: &mut MaybeMutRef<'_, impl std::ops::DerefMut>, range: RangeInclusive, diff --git a/crates/viewer/re_component_ui/src/datatype_uis/range1d.rs b/crates/viewer/re_component_ui/src/datatype_uis/range1d.rs index cbb9f16ea9d3..67e59a45629f 100644 --- a/crates/viewer/re_component_ui/src/datatype_uis/range1d.rs +++ b/crates/viewer/re_component_ui/src/datatype_uis/range1d.rs @@ -4,7 +4,7 @@ use re_ui::syntax_highlighting::SyntaxHighlightedBuilder; use re_viewer_context::{MaybeMutRef, UiLayout}; pub fn edit_view_range1d( - _ctx: &re_viewer_context::ViewerContext<'_>, + _ctx: &re_viewer_context::StoreViewContext<'_>, ui: &mut egui::Ui, value: &mut MaybeMutRef<'_, impl std::ops::DerefMut>, ) -> egui::Response { diff --git a/crates/viewer/re_component_ui/src/datatype_uis/singleline_string.rs b/crates/viewer/re_component_ui/src/datatype_uis/singleline_string.rs index 816c078b8f70..b2cb0a366307 100644 --- a/crates/viewer/re_component_ui/src/datatype_uis/singleline_string.rs +++ b/crates/viewer/re_component_ui/src/datatype_uis/singleline_string.rs @@ -4,7 +4,7 @@ use re_viewer_context::{MaybeMutRef, UiLayout}; /// Generic singleline string editor. pub fn edit_singleline_string( - _ctx: &re_viewer_context::ViewerContext<'_>, + _ctx: &re_viewer_context::StoreViewContext<'_>, ui: &mut egui::Ui, value: &mut MaybeMutRef<'_, impl std::ops::DerefMut>, ) -> egui::Response { @@ -36,7 +36,7 @@ fn edit_singleline_string_impl( /// Generic multiline string editor. pub fn edit_multiline_string( - _ctx: &re_viewer_context::ViewerContext<'_>, + _ctx: &re_viewer_context::StoreViewContext<'_>, ui: &mut egui::Ui, value: &mut MaybeMutRef<'_, impl std::ops::DerefMut>, ) -> egui::Response { diff --git a/crates/viewer/re_component_ui/src/datatype_uis/vec.rs b/crates/viewer/re_component_ui/src/datatype_uis/vec.rs index e5488fa2d82a..e3e8c1b9520f 100644 --- a/crates/viewer/re_component_ui/src/datatype_uis/vec.rs +++ b/crates/viewer/re_component_ui/src/datatype_uis/vec.rs @@ -6,7 +6,7 @@ use re_ui::syntax_highlighting::SyntaxHighlightedBuilder; use re_viewer_context::{MaybeMutRef, UiLayout}; pub fn edit_or_view_vec2d( - _ctx: &re_viewer_context::ViewerContext<'_>, + _ctx: &re_viewer_context::StoreViewContext<'_>, ui: &mut egui::Ui, value: &mut MaybeMutRef<'_, impl std::ops::DerefMut>, ) -> egui::Response { @@ -18,7 +18,7 @@ pub fn edit_or_view_vec2d( } pub fn edit_or_view_vec3d( - _ctx: &re_viewer_context::ViewerContext<'_>, + _ctx: &re_viewer_context::StoreViewContext<'_>, ui: &mut egui::Ui, value: &mut MaybeMutRef<'_, impl std::ops::DerefMut>, ) -> egui::Response { @@ -30,7 +30,7 @@ pub fn edit_or_view_vec3d( } pub fn edit_or_view_vec3d_positive( - _ctx: &re_viewer_context::ViewerContext<'_>, + _ctx: &re_viewer_context::StoreViewContext<'_>, ui: &mut egui::Ui, value: &mut MaybeMutRef<'_, impl std::ops::DerefMut>, ) -> egui::Response { diff --git a/crates/viewer/re_component_ui/src/datatype_uis/view_id.rs b/crates/viewer/re_component_ui/src/datatype_uis/view_id.rs index 61851c85def8..17b3d6da2966 100644 --- a/crates/viewer/re_component_ui/src/datatype_uis/view_id.rs +++ b/crates/viewer/re_component_ui/src/datatype_uis/view_id.rs @@ -3,7 +3,7 @@ use re_sdk_types::datatypes::Uuid; use re_viewer_context::{MaybeMutRef, ViewId}; pub fn view_view_id( - ctx: &re_viewer_context::ViewerContext<'_>, + ctx: &re_viewer_context::StoreViewContext<'_>, ui: &mut egui::Ui, value: &mut MaybeMutRef<'_, impl std::ops::DerefMut>, ) -> egui::Response { @@ -12,10 +12,10 @@ pub fn view_view_id( } fn view_view_id_impl( - ctx: &re_viewer_context::ViewerContext<'_>, + ctx: &re_viewer_context::StoreViewContext<'_>, ui: &mut egui::Ui, value: &Uuid, ) -> egui::Response { let view = ViewId::from(*value); - item_ui::blueprint_entity_path_button_to(ctx, ui, &view.as_entity_path(), view.to_string()) + item_ui::entity_path_button_to(ctx, ui, None, &view.as_entity_path(), view.to_string()) } diff --git a/crates/viewer/re_component_ui/src/datatype_uis/view_timestamp.rs b/crates/viewer/re_component_ui/src/datatype_uis/view_timestamp.rs index 627bd02dbeaa..7c27d51d3bb7 100644 --- a/crates/viewer/re_component_ui/src/datatype_uis/view_timestamp.rs +++ b/crates/viewer/re_component_ui/src/datatype_uis/view_timestamp.rs @@ -5,7 +5,7 @@ use re_ui::syntax_highlighting::SyntaxHighlightedBuilder; use re_viewer_context::MaybeMutRef; pub fn view_timestamp( - ctx: &re_viewer_context::ViewerContext<'_>, + ctx: &re_viewer_context::StoreViewContext<'_>, ui: &mut egui::Ui, value: &mut MaybeMutRef<'_, impl std::ops::DerefMut>, ) -> egui::Response { @@ -13,6 +13,6 @@ pub fn view_timestamp( UiLayout::List.data_label( ui, SyntaxHighlightedBuilder::new() - .with_primitive(&Timestamp::from(*value).format(ctx.app_options().timestamp_format)), + .with_primitive(&Timestamp::from(*value).format(ctx.app_options.timestamp_format)), ) } diff --git a/crates/viewer/re_component_ui/src/datatype_uis/view_uuid.rs b/crates/viewer/re_component_ui/src/datatype_uis/view_uuid.rs index b9f19962e4b2..cc22e2e1cac5 100644 --- a/crates/viewer/re_component_ui/src/datatype_uis/view_uuid.rs +++ b/crates/viewer/re_component_ui/src/datatype_uis/view_uuid.rs @@ -3,7 +3,7 @@ use re_ui::syntax_highlighting::SyntaxHighlightedBuilder; use re_viewer_context::{MaybeMutRef, UiLayout}; pub fn view_uuid( - _ctx: &re_viewer_context::ViewerContext<'_>, + _ctx: &re_viewer_context::StoreViewContext<'_>, ui: &mut egui::Ui, value: &mut MaybeMutRef<'_, impl std::ops::DerefMut>, ) -> egui::Response { diff --git a/crates/viewer/re_component_ui/src/entity_path.rs b/crates/viewer/re_component_ui/src/entity_path.rs index 081201555dc7..d3e619973274 100644 --- a/crates/viewer/re_component_ui/src/entity_path.rs +++ b/crates/viewer/re_component_ui/src/entity_path.rs @@ -1,8 +1,8 @@ use re_sdk_types::components::EntityPath; -use re_viewer_context::{MaybeMutRef, ViewerContext}; +use re_viewer_context::{MaybeMutRef, StoreViewContext}; pub(crate) fn edit_or_view_entity_path( - ctx: &ViewerContext<'_>, + ctx: &StoreViewContext<'_>, ui: &mut egui::Ui, path: &mut MaybeMutRef<'_, EntityPath>, ) -> egui::Response { @@ -14,14 +14,7 @@ pub(crate) fn edit_or_view_entity_path( response } else { - // Assume the current query for information shown in hover cards etc. - let query = ctx.current_query(); - - // Entity paths right now always refer to the current recording. - // This might change in the future at which point we need more context here. - let db = ctx.recording(); - let entity_path = path.as_ref().as_str().into(); - re_data_ui::item_ui::entity_path_button(ctx, &query, db, ui, None, &entity_path) + re_data_ui::item_ui::entity_path_button(ctx, ui, None, &entity_path) } } diff --git a/crates/viewer/re_component_ui/src/geo_line_string.rs b/crates/viewer/re_component_ui/src/geo_line_string.rs index 8b6632dba3c3..55ba192ecd19 100644 --- a/crates/viewer/re_component_ui/src/geo_line_string.rs +++ b/crates/viewer/re_component_ui/src/geo_line_string.rs @@ -1,10 +1,10 @@ use re_format::{format_lat_lon, format_uint}; use re_sdk_types::components::GeoLineString; use re_ui::UiExt as _; -use re_viewer_context::{MaybeMutRef, UiLayout, ViewerContext}; +use re_viewer_context::{MaybeMutRef, StoreViewContext, UiLayout}; fn singleline_view_geo_line_string( - _ctx: &ViewerContext<'_>, + _ctx: &StoreViewContext<'_>, ui: &mut egui::Ui, value: &mut MaybeMutRef<'_, GeoLineString>, ) -> egui::Response { @@ -15,7 +15,7 @@ fn singleline_view_geo_line_string( } fn multiline_view_geo_line_string( - _ctx: &ViewerContext<'_>, + _ctx: &StoreViewContext<'_>, ui: &mut egui::Ui, value: &mut MaybeMutRef<'_, GeoLineString>, ) -> egui::Response { diff --git a/crates/viewer/re_component_ui/src/image_format.rs b/crates/viewer/re_component_ui/src/image_format.rs index 3d0e5e3885f5..861c2ebd932a 100644 --- a/crates/viewer/re_component_ui/src/image_format.rs +++ b/crates/viewer/re_component_ui/src/image_format.rs @@ -1,9 +1,9 @@ use re_ui::UiLayout; use re_ui::syntax_highlighting::SyntaxHighlightedBuilder; -use re_viewer_context::{MaybeMutRef, ViewerContext}; +use re_viewer_context::{MaybeMutRef, StoreViewContext}; pub fn edit_or_view_image_format( - _ctx: &ViewerContext<'_>, + _ctx: &StoreViewContext<'_>, ui: &mut egui::Ui, format: &mut MaybeMutRef<'_, re_sdk_types::components::ImageFormat>, ) -> egui::Response { diff --git a/crates/viewer/re_component_ui/src/lat_lon.rs b/crates/viewer/re_component_ui/src/lat_lon.rs index c2ea9ebfa57d..2cc727df0077 100644 --- a/crates/viewer/re_component_ui/src/lat_lon.rs +++ b/crates/viewer/re_component_ui/src/lat_lon.rs @@ -1,10 +1,10 @@ use re_format::format_lat_lon; use re_sdk_types::components::LatLon; use re_ui::UiExt as _; -use re_viewer_context::{MaybeMutRef, UiLayout, ViewerContext}; +use re_viewer_context::{MaybeMutRef, StoreViewContext, UiLayout}; pub fn singleline_view_lat_lon( - _ctx: &ViewerContext<'_>, + _ctx: &StoreViewContext<'_>, ui: &mut egui::Ui, value: &mut MaybeMutRef<'_, LatLon>, ) -> egui::Response { diff --git a/crates/viewer/re_component_ui/src/line_strip.rs b/crates/viewer/re_component_ui/src/line_strip.rs index 42dcc23139e7..8dc053f89a32 100644 --- a/crates/viewer/re_component_ui/src/line_strip.rs +++ b/crates/viewer/re_component_ui/src/line_strip.rs @@ -1,12 +1,12 @@ use re_format::{format_f32, format_uint}; use re_sdk_types::components::{LineStrip2D, LineStrip3D}; use re_ui::UiExt as _; -use re_viewer_context::{MaybeMutRef, UiLayout, ViewerContext}; +use re_viewer_context::{MaybeMutRef, StoreViewContext, UiLayout}; use crate::DEFAULT_NUMBER_WIDTH; fn singleline_view_line_strip_3d( - _ctx: &ViewerContext<'_>, + _ctx: &StoreViewContext<'_>, ui: &mut egui::Ui, value: &mut MaybeMutRef<'_, LineStrip3D>, ) -> egui::Response { @@ -17,7 +17,7 @@ fn singleline_view_line_strip_3d( } fn multiline_view_line_strip_3d( - _ctx: &ViewerContext<'_>, + _ctx: &StoreViewContext<'_>, ui: &mut egui::Ui, value: &mut MaybeMutRef<'_, LineStrip3D>, ) -> egui::Response { @@ -71,7 +71,7 @@ fn multiline_view_line_strip_3d( } fn singleline_view_line_strip_2d( - _ctx: &ViewerContext<'_>, + _ctx: &StoreViewContext<'_>, ui: &mut egui::Ui, value: &mut MaybeMutRef<'_, LineStrip2D>, ) -> egui::Response { @@ -82,7 +82,7 @@ fn singleline_view_line_strip_2d( } fn multiline_view_line_strip_2d( - _ctx: &ViewerContext<'_>, + _ctx: &StoreViewContext<'_>, ui: &mut egui::Ui, value: &mut MaybeMutRef<'_, LineStrip2D>, ) -> egui::Response { diff --git a/crates/viewer/re_component_ui/src/map_provider.rs b/crates/viewer/re_component_ui/src/map_provider.rs index 0896bece3371..2fc9b98f5cfa 100644 --- a/crates/viewer/re_component_ui/src/map_provider.rs +++ b/crates/viewer/re_component_ui/src/map_provider.rs @@ -1,14 +1,14 @@ use re_sdk_types::blueprint::components::MapProvider; -use re_viewer_context::ViewerContext; +use re_viewer_context::StoreViewContext; use crate::datatype_uis::{VariantAvailable, VariantAvailableProvider}; pub struct MapProviderVariantAvailable; impl VariantAvailableProvider for MapProviderVariantAvailable { - fn is_variant_enabled(ctx: &ViewerContext<'_>, variant: MapProvider) -> VariantAvailable { + fn is_variant_enabled(ctx: &StoreViewContext<'_>, variant: MapProvider) -> VariantAvailable { let map_box_available = if ctx - .app_options() + .app_options .mapbox_access_token() .is_some_and(|token| !token.is_empty()) { diff --git a/crates/viewer/re_component_ui/src/marker_shape.rs b/crates/viewer/re_component_ui/src/marker_shape.rs index 0636fa63be65..878865811619 100644 --- a/crates/viewer/re_component_ui/src/marker_shape.rs +++ b/crates/viewer/re_component_ui/src/marker_shape.rs @@ -1,12 +1,12 @@ use re_sdk_types::components::MarkerShape; use re_sdk_types::reflection::Enum as _; use re_ui::UiExt as _; -use re_viewer_context::{MaybeMutRef, ViewerContext}; +use re_viewer_context::{MaybeMutRef, StoreViewContext}; use crate::response_utils::response_with_changes_of_inner; pub(crate) fn edit_marker_shape_ui( - _ctx: &ViewerContext<'_>, + _ctx: &StoreViewContext<'_>, ui: &mut egui::Ui, marker: &mut MaybeMutRef<'_, MarkerShape>, ) -> egui::Response { diff --git a/crates/viewer/re_component_ui/src/pinhole.rs b/crates/viewer/re_component_ui/src/pinhole.rs index d2e57aa8b612..0ecbddc8f9ee 100644 --- a/crates/viewer/re_component_ui/src/pinhole.rs +++ b/crates/viewer/re_component_ui/src/pinhole.rs @@ -1,8 +1,8 @@ use re_sdk_types::components::PinholeProjection; -use re_viewer_context::{MaybeMutRef, UiLayout, ViewerContext}; +use re_viewer_context::{MaybeMutRef, StoreViewContext, UiLayout}; pub fn singleline_view_pinhole( - ctx: &ViewerContext<'_>, + ctx: &StoreViewContext<'_>, ui: &mut egui::Ui, value: &mut MaybeMutRef<'_, PinholeProjection>, ) -> egui::Response { @@ -30,7 +30,7 @@ pub fn singleline_view_pinhole( } pub fn multiline_view_pinhole( - _ctx: &ViewerContext<'_>, + _ctx: &StoreViewContext<'_>, ui: &mut egui::Ui, value: &mut MaybeMutRef<'_, PinholeProjection>, ) -> egui::Response { diff --git a/crates/viewer/re_component_ui/src/plane3d.rs b/crates/viewer/re_component_ui/src/plane3d.rs index bb7af17d0d7d..6b2a2147a60f 100644 --- a/crates/viewer/re_component_ui/src/plane3d.rs +++ b/crates/viewer/re_component_ui/src/plane3d.rs @@ -75,7 +75,7 @@ impl TryFrom for glam::Vec3 { } pub fn edit_or_view_plane3d( - _ctx: &re_viewer_context::ViewerContext<'_>, + _ctx: &re_viewer_context::StoreViewContext<'_>, ui: &mut egui::Ui, value: &mut MaybeMutRef<'_, components::Plane3D>, ) -> egui::Response { @@ -130,7 +130,7 @@ pub fn edit_or_view_plane3d( } pub fn multiline_edit_or_view_plane3d( - _ctx: &re_viewer_context::ViewerContext<'_>, + _ctx: &re_viewer_context::StoreViewContext<'_>, ui: &mut egui::Ui, value: &mut MaybeMutRef<'_, components::Plane3D>, ) -> egui::Response { diff --git a/crates/viewer/re_component_ui/src/radius.rs b/crates/viewer/re_component_ui/src/radius.rs index fc4078de1922..1e52df5ce6b7 100644 --- a/crates/viewer/re_component_ui/src/radius.rs +++ b/crates/viewer/re_component_ui/src/radius.rs @@ -1,12 +1,12 @@ use egui::NumExt as _; use re_format::format_f32; use re_sdk_types::components::Radius; -use re_viewer_context::{MaybeMutRef, ViewerContext}; +use re_viewer_context::{MaybeMutRef, StoreViewContext}; use crate::response_utils::response_with_changes_of_inner; pub fn edit_radius_ui( - _ctx: &ViewerContext<'_>, + _ctx: &StoreViewContext<'_>, ui: &mut egui::Ui, value: &mut MaybeMutRef<'_, Radius>, ) -> egui::Response { diff --git a/crates/viewer/re_component_ui/src/resolution.rs b/crates/viewer/re_component_ui/src/resolution.rs index 3eae89e8e672..95f04563d460 100644 --- a/crates/viewer/re_component_ui/src/resolution.rs +++ b/crates/viewer/re_component_ui/src/resolution.rs @@ -1,9 +1,9 @@ use re_sdk_types::components::Resolution; use re_ui::syntax_highlighting::SyntaxHighlightedBuilder; -use re_viewer_context::{MaybeMutRef, UiLayout, ViewerContext}; +use re_viewer_context::{MaybeMutRef, StoreViewContext, UiLayout}; pub fn edit_or_view_resolution( - _ctx: &ViewerContext<'_>, + _ctx: &StoreViewContext<'_>, ui: &mut egui::Ui, value: &mut MaybeMutRef<'_, Resolution>, ) -> egui::Response { diff --git a/crates/viewer/re_component_ui/src/text_log_columns.rs b/crates/viewer/re_component_ui/src/text_log_columns.rs index 7dd3af114f5d..c1403ffa64a2 100644 --- a/crates/viewer/re_component_ui/src/text_log_columns.rs +++ b/crates/viewer/re_component_ui/src/text_log_columns.rs @@ -1,10 +1,10 @@ use re_sdk_types::blueprint::components::TextLogColumn; -use re_viewer_context::{MaybeMutRef, ViewerContext}; +use re_viewer_context::{MaybeMutRef, StoreViewContext}; use crate::visible_dnd::visible_dnd; pub fn edit_or_view_columns_singleline( - _ctx: &ViewerContext<'_>, + _ctx: &StoreViewContext<'_>, ui: &mut egui::Ui, columns: &mut MaybeMutRef<'_, Vec>, ) -> egui::Response { @@ -28,7 +28,7 @@ pub fn edit_or_view_columns_singleline( } pub fn edit_or_view_columns_multiline( - _ctx: &ViewerContext<'_>, + _ctx: &StoreViewContext<'_>, ui: &mut egui::Ui, columns: &mut MaybeMutRef<'_, Vec>, ) -> egui::Response { diff --git a/crates/viewer/re_component_ui/src/time_range.rs b/crates/viewer/re_component_ui/src/time_range.rs index 1841b0fb5277..1dea8687d35c 100644 --- a/crates/viewer/re_component_ui/src/time_range.rs +++ b/crates/viewer/re_component_ui/src/time_range.rs @@ -7,10 +7,10 @@ use re_ui::{ RelativeTimeRange, TimeDragValue, UiExt as _, relative_time_range_boundary_label_text, relative_time_range_label_text, }; -use re_viewer_context::{MaybeMutRef, TimeControlCommand}; +use re_viewer_context::{MaybeMutRef, StoreViewContext, TimeControlCommand}; pub fn time_range_multiline_edit_or_view_ui( - ctx: &re_viewer_context::ViewerContext<'_>, + ctx: &StoreViewContext<'_>, ui: &mut egui::Ui, value: &mut MaybeMutRef<'_, TimeRange>, ) -> egui::Response { @@ -18,9 +18,7 @@ pub fn time_range_multiline_edit_or_view_ui( return ui.weak("No active timeline"); }; - let time_drag_value = if let Some(range) = ctx - .recording() - .time_range_for(ctx.time_ctrl.timeline_name()) + let time_drag_value = if let Some(range) = ctx.db.time_range_for(ctx.time_ctrl.timeline_name()) { TimeDragValue::from_abs_time_range(range) } else { @@ -54,7 +52,7 @@ pub fn time_range_multiline_edit_or_view_ui( current_time, time_type, value, - ctx.app_options().timestamp_format, + ctx.app_options.timestamp_format, ); let mut response_z = ui @@ -79,7 +77,7 @@ pub fn time_range_multiline_edit_or_view_ui( value, resolved_range: AbsoluteTimeRange::new(current_start, current_end), time_type, - timestamp_format: ctx.app_options().timestamp_format, + timestamp_format: ctx.app_options.timestamp_format, current_time, } .ui(ui); @@ -94,14 +92,16 @@ pub fn time_range_multiline_edit_or_view_ui( if ui.rect_contains_pointer(response.rect) { let absolute_range = AbsoluteTimeRange::from_relative_time_range(value, current_time); - ctx.send_time_commands([TimeControlCommand::HighlightRange(absolute_range)]); + ctx.send_time_commands_to_active_recording([TimeControlCommand::HighlightRange( + absolute_range, + )]); } response } pub fn time_range_singleline_view_ui( - ctx: &re_viewer_context::ViewerContext<'_>, + ctx: &StoreViewContext<'_>, ui: &mut egui::Ui, value: &mut MaybeMutRef<'_, TimeRange>, ) -> egui::Response { @@ -109,9 +109,7 @@ pub fn time_range_singleline_view_ui( return ui.weak("No active timeline"); }; - let time_drag_value = if let Some(range) = ctx - .recording() - .time_range_for(ctx.time_ctrl.timeline_name()) + let time_drag_value = if let Some(range) = ctx.db.time_range_for(ctx.time_ctrl.timeline_name()) { TimeDragValue::from_abs_time_range(range) } else { @@ -129,7 +127,7 @@ pub fn time_range_singleline_view_ui( current_time, time_type, value, - ctx.app_options().timestamp_format.with_short(true), + ctx.app_options.timestamp_format.with_short(true), ); let mut res = ui.label(text); @@ -140,14 +138,16 @@ pub fn time_range_singleline_view_ui( if res.hovered() { let absolute_range = AbsoluteTimeRange::from_relative_time_range(value, current_time); - ctx.send_time_commands([TimeControlCommand::HighlightRange(absolute_range)]); + ctx.send_time_commands_to_active_recording([TimeControlCommand::HighlightRange( + absolute_range, + )]); } res } fn view_visible_history_boundary_ui( - ctx: &re_viewer_context::ViewerContext<'_>, + ctx: &StoreViewContext<'_>, ui: &mut egui::Ui, visible_history_boundary: &TimeRangeBoundary, time_type: TimeType, @@ -166,11 +166,11 @@ fn view_visible_history_boundary_ui( TimeType::Sequence => TimeType::Sequence, TimeType::DurationNs | TimeType::TimestampNs => TimeType::DurationNs, } - .format(*time_int, ctx.app_options().timestamp_format), + .format(*time_int, ctx.app_options.timestamp_format), ); } TimeRangeBoundary::Absolute(time_int) => { - ui.label(time_type.format(*time_int, ctx.app_options().timestamp_format)); + ui.label(time_type.format(*time_int, ctx.app_options.timestamp_format)); } TimeRangeBoundary::Infinite => {} } diff --git a/crates/viewer/re_component_ui/src/timeline_columns.rs b/crates/viewer/re_component_ui/src/timeline_columns.rs index 6fcec9b087f3..fc82cc0d8319 100644 --- a/crates/viewer/re_component_ui/src/timeline_columns.rs +++ b/crates/viewer/re_component_ui/src/timeline_columns.rs @@ -1,12 +1,12 @@ use re_data_ui::item_ui::timeline_button; use re_log_types::TimelineName; use re_sdk_types::blueprint::components::TimelineColumn; -use re_viewer_context::{MaybeMutRef, ViewerContext}; +use re_viewer_context::{MaybeMutRef, StoreViewContext}; use crate::visible_dnd::visible_dnd; pub fn edit_or_view_columns_singleline( - ctx: &ViewerContext<'_>, + ctx: &StoreViewContext<'_>, ui: &mut egui::Ui, columns: &mut MaybeMutRef<'_, Vec>, ) -> egui::Response { @@ -21,7 +21,7 @@ pub fn edit_or_view_columns_singleline( } pub fn edit_or_view_columns_multiline( - ctx: &ViewerContext<'_>, + ctx: &StoreViewContext<'_>, ui: &mut egui::Ui, columns: &mut MaybeMutRef<'_, Vec>, ) -> egui::Response { @@ -32,27 +32,29 @@ pub fn edit_or_view_columns_multiline( .map(|col| timeline_button(ctx, ui, &TimelineName::new(&col.timeline))) .reduce(|a, b| a.union(b)) .unwrap_or_else(|| ui.weak("Empty")), + MaybeMutRef::MutRef(columns) => { - // Add new timelines to the end of the UI, if there is any edit - // these will be written to the component. - let extra_columns = ctx - .recording() - .timelines() - .values() - .filter(|timeline| { - columns - .iter() - .all(|col| col.timeline.as_str() != timeline.name().as_str()) - }) - .map(|timeline| { - TimelineColumn(re_sdk_types::blueprint::datatypes::TimelineColumn { - visible: false.into(), - timeline: timeline.name().as_str().into(), + if let Some(recording) = ctx.active_recording() { + // Add new timelines to the end of the UI, if there is any edit + // these will be written to the component. + let extra_columns = recording + .timelines() + .values() + .filter(|timeline| { + columns + .iter() + .all(|col| col.timeline.as_str() != timeline.name().as_str()) + }) + .map(|timeline| { + TimelineColumn(re_sdk_types::blueprint::datatypes::TimelineColumn { + visible: false.into(), + timeline: timeline.name().as_str().into(), + }) }) - }) - .collect::>(); + .collect::>(); - columns.extend(extra_columns); + columns.extend(extra_columns); + } visible_dnd( ui, diff --git a/crates/viewer/re_component_ui/src/transform_frame_id.rs b/crates/viewer/re_component_ui/src/transform_frame_id.rs index 5d3ecf6eaa31..ddb402a3399a 100644 --- a/crates/viewer/re_component_ui/src/transform_frame_id.rs +++ b/crates/viewer/re_component_ui/src/transform_frame_id.rs @@ -1,14 +1,14 @@ use re_sdk_types::components::TransformFrameId; use re_ui::syntax_highlighting::SyntaxHighlightedBuilder; use re_ui::text_edit::autocomplete_text_edit; -use re_viewer_context::{MaybeMutRef, UiLayout, ViewerContext}; +use re_viewer_context::{MaybeMutRef, StoreViewContext, UiLayout}; /// Shows a potentially editable `frame_id`. /// If the `frame_id` is being edited, a list of matching frame names is shown as suggestions. /// /// Note: implicit, entity-path-derived frame IDs are not suggested. pub fn edit_or_view_transform_frame_id( - ctx: &ViewerContext<'_>, + ctx: &StoreViewContext<'_>, ui: &mut egui::Ui, frame_id: &mut MaybeMutRef<'_, TransformFrameId>, ) -> egui::Response { @@ -18,11 +18,11 @@ pub fn edit_or_view_transform_frame_id( SyntaxHighlightedBuilder::new().with_string_value(frame_id.as_str()), ), MaybeMutRef::MutRef(frame_id) => { - let suggestions = { - let caches = ctx.store_context.caches; + let suggestions = if let Some(store_ctx) = ctx.active_store_context { + let caches = store_ctx.caches; let frame_id_registry = caches.entry(|c: &mut re_viewer_context::TransformDatabaseStoreCache| { - c.frame_id_registry(ctx.recording()) + c.frame_id_registry(store_ctx.recording) }); frame_id_registry @@ -30,6 +30,8 @@ pub fn edit_or_view_transform_frame_id( .filter(|(_, id)| !id.is_entity_path_derived()) .map(|(_, id)| id.to_string()) .collect::>() + } else { + Vec::new() }; let mut tmp_string = frame_id.as_str().to_owned(); diff --git a/crates/viewer/re_component_ui/src/transforms.rs b/crates/viewer/re_component_ui/src/transforms.rs index 0b8b1f9fcd8b..91fd07c2bd0d 100644 --- a/crates/viewer/re_component_ui/src/transforms.rs +++ b/crates/viewer/re_component_ui/src/transforms.rs @@ -4,7 +4,7 @@ use re_ui::UiExt as _; use re_viewer_context::MaybeMutRef; pub fn singleline_view_transform_mat3x3( - _ctx: &re_viewer_context::ViewerContext<'_>, + _ctx: &re_viewer_context::StoreViewContext<'_>, ui: &mut egui::Ui, value: &mut MaybeMutRef<'_, TransformMat3x3>, ) -> egui::Response { @@ -16,7 +16,7 @@ pub fn singleline_view_transform_mat3x3( } pub fn multiline_view_transform_mat3x3( - _ctx: &re_viewer_context::ViewerContext<'_>, + _ctx: &re_viewer_context::StoreViewContext<'_>, ui: &mut egui::Ui, value: &mut MaybeMutRef<'_, TransformMat3x3>, ) -> egui::Response { diff --git a/crates/viewer/re_component_ui/src/variant_uis/redap_entry_kind.rs b/crates/viewer/re_component_ui/src/variant_uis/redap_entry_kind.rs index 8eefb869b5c7..d6c18a4861fd 100644 --- a/crates/viewer/re_component_ui/src/variant_uis/redap_entry_kind.rs +++ b/crates/viewer/re_component_ui/src/variant_uis/redap_entry_kind.rs @@ -2,11 +2,11 @@ use std::error::Error; use re_protos::cloud::v1alpha1::EntryKind; use re_types_core::{ComponentIdentifier, RowId}; -use re_viewer_context::ViewerContext; +use re_viewer_context::StoreViewContext; /// Parse an `Int32Array` as an `EntryKind` and display it. pub fn redap_entry_kind( - _ctx: &ViewerContext<'_>, + _ctx: &StoreViewContext<'_>, ui: &mut egui::Ui, _component: ComponentIdentifier, _row_id: Option, diff --git a/crates/viewer/re_component_ui/src/variant_uis/redap_thumbnail.rs b/crates/viewer/re_component_ui/src/variant_uis/redap_thumbnail.rs index a2bfc7f149d3..f94bd2758365 100644 --- a/crates/viewer/re_component_ui/src/variant_uis/redap_thumbnail.rs +++ b/crates/viewer/re_component_ui/src/variant_uis/redap_thumbnail.rs @@ -1,15 +1,13 @@ use std::error::Error; -use re_log_types::TimelineName; use re_sdk_types::components::MediaType; use re_types_core::{ComponentIdentifier, Loggable as _, RowId}; use re_ui::UiLayout; -use re_viewer_context::ViewerContext; -use re_viewer_context::external::re_chunk_store::LatestAtQuery; +use re_viewer_context::StoreViewContext; /// Display a thumbnail that takes all the available space. pub fn redap_thumbnail( - ctx: &ViewerContext<'_>, + ctx: &StoreViewContext<'_>, ui: &mut egui::Ui, component: ComponentIdentifier, row_id: Option, @@ -25,7 +23,6 @@ pub fn redap_thumbnail( let media_type = MediaType::guess_from_data(slice); let image = ctx - .store_context .caches .entry(|c: &mut re_viewer_context::ImageDecodeCache| { c.entry_encoded_color(row_id, component, slice, media_type.as_ref()) @@ -33,9 +30,9 @@ pub fn redap_thumbnail( re_data_ui::image_preview_ui( ctx, + None, // Can't look up annotations for segmentation images ui, UiLayout::List, - &LatestAtQuery::latest(TimelineName::new("unknown")), &re_log_types::EntityPath::from("redap_thumbnail"), &image, None, diff --git a/crates/viewer/re_component_ui/src/variant_uis/redap_uri_button.rs b/crates/viewer/re_component_ui/src/variant_uis/redap_uri_button.rs index 77e0fa5cc705..7e53c64e2ec9 100644 --- a/crates/viewer/re_component_ui/src/variant_uis/redap_uri_button.rs +++ b/crates/viewer/re_component_ui/src/variant_uis/redap_uri_button.rs @@ -6,13 +6,13 @@ use re_types_core::{ComponentIdentifier, RowId}; use re_ui::UiExt as _; use re_uri::RedapUri; use re_viewer_context::open_url::ViewerOpenUrl; -use re_viewer_context::{SystemCommand, SystemCommandSender as _, ViewerContext}; +use re_viewer_context::{StoreViewContext, SystemCommand, SystemCommandSender as _}; /// Display an URL as an `Open` button (instead of spelling the full URL). /// /// Requires a String mono-component which is valid [`RedapUri`]. pub fn redap_uri_button( - ctx: &ViewerContext<'_>, + ctx: &StoreViewContext<'_>, ui: &mut egui::Ui, _component: ComponentIdentifier, _row_id: Option, @@ -84,7 +84,7 @@ pub fn redap_uri_button( .clicked() { if let Ok(url) = ViewerOpenUrl::from(uri_clone).sharable_url(None) { - ctx.command_sender() + ctx.command_sender .send_system(SystemCommand::CopyViewerUrl(url)); } else { re_log::error!("Failed to create a sharable url for recording"); @@ -101,10 +101,9 @@ pub fn redap_uri_button( .on_hover_text("This recording is already loaded. Click to switch to it."); if response.clicked() { // Show it: - ctx.command_sender() - .send_system(SystemCommand::set_selection( - re_viewer_context::Item::StoreId(loaded_recording_info.store_id.clone()), - )); + ctx.command_sender.send_system(SystemCommand::set_selection( + re_viewer_context::Item::StoreId(loaded_recording_info.store_id.clone()), + )); } } else if is_loading { ui.loading_indicator("Loading redap recording"); diff --git a/crates/viewer/re_component_ui/src/video_timestamp.rs b/crates/viewer/re_component_ui/src/video_timestamp.rs index fd1a6233c89d..c1f531a3f5dd 100644 --- a/crates/viewer/re_component_ui/src/video_timestamp.rs +++ b/crates/viewer/re_component_ui/src/video_timestamp.rs @@ -1,9 +1,9 @@ use re_format::time::{format_relative_timestamp_secs, parse_relative_timestamp_secs}; use re_sdk_types::components::VideoTimestamp; -use re_viewer_context::{MaybeMutRef, ViewerContext}; +use re_viewer_context::{MaybeMutRef, StoreViewContext}; pub fn edit_or_view_timestamp( - _ctx: &ViewerContext<'_>, + _ctx: &StoreViewContext<'_>, ui: &mut egui::Ui, timestamp: &mut MaybeMutRef<'_, VideoTimestamp>, ) -> egui::Response { diff --git a/crates/viewer/re_component_ui/src/view_coordinates.rs b/crates/viewer/re_component_ui/src/view_coordinates.rs index 1ef6ff66cae6..2e7671aa84a9 100644 --- a/crates/viewer/re_component_ui/src/view_coordinates.rs +++ b/crates/viewer/re_component_ui/src/view_coordinates.rs @@ -1,9 +1,9 @@ use re_sdk_types::components::ViewCoordinates; use re_ui::syntax_highlighting::SyntaxHighlightedBuilder; -use re_viewer_context::{MaybeMutRef, UiLayout, ViewerContext}; +use re_viewer_context::{MaybeMutRef, StoreViewContext, UiLayout}; pub fn edit_or_view_view_coordinates( - _ctx: &ViewerContext<'_>, + _ctx: &StoreViewContext<'_>, ui: &mut egui::Ui, value: &mut MaybeMutRef<'_, ViewCoordinates>, ) -> egui::Response { diff --git a/crates/viewer/re_component_ui/src/visual_bounds2d.rs b/crates/viewer/re_component_ui/src/visual_bounds2d.rs index 270f01979e13..83a25acf6e9d 100644 --- a/crates/viewer/re_component_ui/src/visual_bounds2d.rs +++ b/crates/viewer/re_component_ui/src/visual_bounds2d.rs @@ -2,10 +2,10 @@ use egui::NumExt as _; use re_sdk_types::blueprint::components::VisualBounds2D; use re_sdk_types::datatypes::Range2D; use re_ui::UiExt as _; -use re_viewer_context::{MaybeMutRef, ViewerContext}; +use re_viewer_context::{MaybeMutRef, StoreViewContext}; pub fn multiline_edit_visual_bounds2d( - _ctx: &ViewerContext<'_>, + _ctx: &StoreViewContext<'_>, ui: &mut egui::Ui, value: &mut MaybeMutRef<'_, VisualBounds2D>, ) -> egui::Response { @@ -80,7 +80,7 @@ fn range_mut_ui(ui: &mut egui::Ui, [start, end]: &mut [f64; 2]) -> egui::Respons } pub fn singleline_edit_visual_bounds2d( - _ctx: &ViewerContext<'_>, + _ctx: &StoreViewContext<'_>, ui: &mut egui::Ui, value: &mut MaybeMutRef<'_, VisualBounds2D>, ) -> egui::Response { diff --git a/crates/viewer/re_component_ui/src/zoom_level.rs b/crates/viewer/re_component_ui/src/zoom_level.rs index 602519d642f1..2bf0a2406858 100644 --- a/crates/viewer/re_component_ui/src/zoom_level.rs +++ b/crates/viewer/re_component_ui/src/zoom_level.rs @@ -9,7 +9,7 @@ const MAX_ZOOM_LEVEL: f64 = 19.0; /// Editor for a [`re_sdk_types::blueprint::components::ZoomLevel`]. pub fn edit_zoom_level( - _ctx: &re_viewer_context::ViewerContext<'_>, + _ctx: &re_viewer_context::StoreViewContext<'_>, ui: &mut egui::Ui, value: &mut MaybeMutRef<'_, ZoomLevel>, ) -> egui::Response { diff --git a/crates/viewer/re_component_ui/tests/test_all_components_ui.rs b/crates/viewer/re_component_ui/tests/test_all_components_ui.rs index c60e57a2b525..26740655e5e9 100644 --- a/crates/viewer/re_component_ui/tests/test_all_components_ui.rs +++ b/crates/viewer/re_component_ui/tests/test_all_components_ui.rs @@ -11,7 +11,7 @@ use egui_kittest::{OsThreshold, SnapshotError, SnapshotOptions}; use itertools::Itertools as _; use nohash_hasher::IntSet; use re_component_ui::create_component_ui_registry; -use re_log_types::{EntityPath, TimelineName}; +use re_log_types::EntityPath; use re_sdk_types::ComponentDescriptor; use re_sdk_types::blueprint::components::{ComponentColumnSelector, QueryExpression}; use re_sdk_types::components::{self, GraphEdge, GraphNode, ImageFormat, Text}; @@ -20,7 +20,6 @@ use re_test_context::TestContext; use re_types_core::reflection::Reflection; use re_types_core::{Component, ComponentBatch, ComponentType}; use re_ui::{UiExt as _, list_item}; -use re_viewer_context::external::re_chunk_store::LatestAtQuery; use re_viewer_context::external::re_chunk_store::external::re_chunk; use re_viewer_context::{UiLayout, ViewerContext}; @@ -264,13 +263,9 @@ fn test_single_component_ui_as_list_item( ui.list_item_flat_noninteractive( list_item::PropertyContent::new("ComponentName").value_fn(|ui, _| { ctx.component_ui_registry().component_ui_raw( - ctx, + &ctx.active_recording_store_view_context(), ui, UiLayout::List, - // Note: recording and queries are only used for tooltips, - // which we are not testing here. - &LatestAtQuery::latest(TimelineName::log_time()), - ctx.recording(), &EntityPath::root(), // As of writing, `ComponentDescriptor` the descriptor part is only used for // caching and actual lookup of uis is only done via `ComponentType`. diff --git a/crates/viewer/re_data_ui/src/annotation_context_ui.rs b/crates/viewer/re_data_ui/src/annotation_context_ui.rs index fce544c41b3d..455ca46d4578 100644 --- a/crates/viewer/re_data_ui/src/annotation_context_ui.rs +++ b/crates/viewer/re_data_ui/src/annotation_context_ui.rs @@ -8,23 +8,21 @@ use re_sdk_types::datatypes::{ use re_sdk_types::{Component as _, ComponentDescriptor, RowId}; use re_ui::UiExt as _; use re_ui::syntax_highlighting::SyntaxHighlightedBuilder; -use re_viewer_context::{UiLayout, ViewerContext, auto_color_egui}; +use re_viewer_context::{StoreViewContext, UiLayout, auto_color_egui}; use super::DataUi; impl crate::EntityDataUi for components::ClassId { fn entity_data_ui( &self, - _ctx: &ViewerContext<'_>, + ctx: &StoreViewContext<'_>, ui: &mut egui::Ui, ui_layout: UiLayout, entity_path: &EntityPath, _component_descriptor: &ComponentDescriptor, _row_id: Option, - query: &re_chunk_store::LatestAtQuery, - db: &re_entity_db::EntityDb, ) { - let annotations = crate::annotations(db, query, entity_path); + let annotations = crate::annotations(ctx, entity_path); let class = annotations .resolved_class_description(Some(*self)) .class_description; @@ -62,16 +60,14 @@ impl crate::EntityDataUi for components::ClassId { impl crate::EntityDataUi for components::KeypointId { fn entity_data_ui( &self, - _ctx: &ViewerContext<'_>, + ctx: &StoreViewContext<'_>, ui: &mut egui::Ui, ui_layout: UiLayout, entity_path: &EntityPath, _component_descriptor: &ComponentDescriptor, _row_id: Option, - query: &re_chunk_store::LatestAtQuery, - db: &re_entity_db::EntityDb, ) { - if let Some(info) = annotation_info(entity_path, query, db, self.0) { + if let Some(info) = annotation_info(ctx, entity_path, self.0) { ui.horizontal(|ui| { // Color first, to keep subsequent rows of the same things aligned small_color_ui(ui, &info); @@ -93,9 +89,8 @@ impl crate::EntityDataUi for components::KeypointId { } fn annotation_info( + ctx: &StoreViewContext<'_>, entity_path: &re_log_types::EntityPath, - query: &re_chunk_store::LatestAtQuery, - db: &re_entity_db::EntityDb, keypoint_id: KeypointId, ) -> Option { // TODO(#3168): this needs to use the index of the keypoint to look up the correct @@ -104,7 +99,7 @@ fn annotation_info( // TODO(grtlr): If there's several class ids we have no idea which one to use. // This code uses the first one that shows up. // We should search instead for a class id that is likely a sibling of the keypoint id. - let storage_engine = db.storage_engine(); + let storage_engine = ctx.db.storage_engine(); let store = storage_engine.store(); let mut possible_class_id_components = store .all_components_for_entity(entity_path)? @@ -115,26 +110,19 @@ fn annotation_info( }); let picked_class_id_component = possible_class_id_components.next()?; - let (_, class_id) = db.latest_at_component_quiet::( + let (_, class_id) = ctx.db.latest_at_component_quiet::( entity_path, - query, + &ctx.query(), picked_class_id_component, )?; - let annotations = crate::annotations(db, query, entity_path); + let annotations = crate::annotations(ctx, entity_path); let class = annotations.resolved_class_description(Some(class_id)); class.keypoint_map?.get(&keypoint_id).cloned() } impl DataUi for AnnotationContext { - fn data_ui( - &self, - _ctx: &ViewerContext<'_>, - ui: &mut egui::Ui, - ui_layout: UiLayout, - _query: &re_chunk_store::LatestAtQuery, - _db: &re_entity_db::EntityDb, - ) { + fn data_ui(&self, _ctx: &StoreViewContext<'_>, ui: &mut egui::Ui, ui_layout: UiLayout) { match ui_layout { UiLayout::List | UiLayout::Tooltip => { let text = if self.0.len() == 1 { diff --git a/crates/viewer/re_data_ui/src/app_id_ui.rs b/crates/viewer/re_data_ui/src/app_id_ui.rs index a0a6862eb495..a2ab5ce69892 100644 --- a/crates/viewer/re_data_ui/src/app_id_ui.rs +++ b/crates/viewer/re_data_ui/src/app_id_ui.rs @@ -3,26 +3,22 @@ use re_entity_db::EntityDb; use re_log_types::ApplicationId; use re_sdk_types::archetypes::RecordingInfo; use re_sdk_types::components::Timestamp; -use re_viewer_context::{UiLayout, ViewerContext}; +use re_viewer_context::{AppContext, UiLayout}; use crate::item_ui::entity_db_button_ui; -impl crate::DataUi for ApplicationId { - fn data_ui( - &self, - ctx: &ViewerContext<'_>, - ui: &mut egui::Ui, - ui_layout: UiLayout, - _query: &re_chunk_store::LatestAtQuery, - _db: &re_entity_db::EntityDb, - ) { +impl crate::AppUi for ApplicationId { + fn app_ui(&self, ctx: &AppContext<'_>, ui: &mut egui::Ui, ui_layout: UiLayout) { egui::Grid::new("application_id") .num_columns(2) .show(ui, |ui| { ui.label("Application ID"); let mut label = self.to_string(); - if self == ctx.store_context.application_id() { + if ctx + .active_store_context + .is_some_and(|sc| self == sc.application_id()) + { label.push_str(" (active)"); } UiLayout::List.label(ui, label); @@ -63,7 +59,7 @@ impl crate::DataUi for ApplicationId { ui.add_space(8.0); ui.strong("Loaded recordings for this app"); for entity_db in recordings { - entity_db_button_ui(ctx, ui, entity_db, ui_layout, true); + entity_db_button_ui(ctx, entity_db, ui, ui_layout, true); } }); } diff --git a/crates/viewer/re_data_ui/src/blob_ui.rs b/crates/viewer/re_data_ui/src/blob_ui.rs index ed48e6ca1398..9a95cb29727b 100644 --- a/crates/viewer/re_data_ui/src/blob_ui.rs +++ b/crates/viewer/re_data_ui/src/blob_ui.rs @@ -7,7 +7,7 @@ use re_sdk_types::{ComponentDescriptor, ComponentIdentifier, RowId, archetypes, use re_types_core::Component as _; use re_ui::list_item::{self, ListItemContentButtonsExt as _, PropertyContent}; use re_ui::{UiExt as _, icons}; -use re_viewer_context::{StoredBlobCacheKey, UiLayout, ViewerContext}; +use re_viewer_context::{AppContext, StoreViewContext, StoredBlobCacheKey, UiLayout}; use crate::image_ui::ImageUi; use crate::video_ui::VideoUi; @@ -16,14 +16,12 @@ use crate::{EntityDataUi, find_and_deserialize_archetype_mono_component}; impl EntityDataUi for Blob { fn entity_data_ui( &self, - ctx: &ViewerContext<'_>, + ctx: &StoreViewContext<'_>, ui: &mut egui::Ui, ui_layout: UiLayout, entity_path: &EntityPath, component_descriptor: &ComponentDescriptor, row_id: Option, - query: &re_chunk_store::LatestAtQuery, - _db: &re_entity_db::EntityDb, ) { let compact_size_string = re_format::format_bytes(self.len() as _); @@ -48,7 +46,7 @@ impl EntityDataUi for Blob { if ui_layout.is_single_line() { ui.horizontal(|ui| { ui.set_truncate_style(); - blob_ui.data_ui(ctx, ui, ui_layout, query, entity_path); + blob_ui.data_ui(ctx, ui, ui_layout, entity_path); ui.label(compact_size_string); @@ -81,7 +79,7 @@ impl EntityDataUi for Blob { ) .on_hover_text("Failed to detect media type (Mime) from magic header bytes"); } - blob_ui.data_ui(ctx, ui, ui_layout, query, entity_path); + blob_ui.data_ui(ctx, ui, ui_layout, entity_path); }); } } @@ -143,7 +141,7 @@ pub struct BlobUi { impl BlobUi { pub fn from_components( - ctx: &ViewerContext<'_>, + ctx: &StoreViewContext<'_>, entity_path: &re_log_types::EntityPath, blob_descr: &ComponentDescriptor, blob_chunk: &UnitChunkShared, @@ -193,7 +191,7 @@ impl BlobUi { } pub fn new( - ctx: &re_viewer_context::ViewerContext<'_>, + ctx: &StoreViewContext<'_>, entity_path: &re_log_types::EntityPath, blob_component_descriptor: &ComponentDescriptor, blob_row_id: Option, @@ -236,7 +234,7 @@ impl BlobUi { pub fn inline_download_button<'a>( &'a self, - ctx: &'a ViewerContext<'_>, + ctx: &'a AppContext<'_>, entity_path: &'a EntityPath, mut property_content: list_item::PropertyContent<'a>, ) -> list_item::PropertyContent<'a> { @@ -256,7 +254,7 @@ impl BlobUi { file_name.push_str(file_extension); } - ctx.command_sender().save_file_dialog( + ctx.command_sender.save_file_dialog( re_capabilities::MainThreadToken::i_promise_i_am_on_the_main_thread(), &file_name, "Save blob".to_owned(), @@ -267,10 +265,9 @@ impl BlobUi { pub fn data_ui( &self, - ctx: &ViewerContext<'_>, + ctx: &StoreViewContext<'_>, ui: &mut egui::Ui, ui_layout: UiLayout, - query: &re_chunk_store::LatestAtQuery, entity_path: &EntityPath, ) { if let Some(row_id) = self.row_id @@ -284,11 +281,11 @@ impl BlobUi { } if let Some(image) = &self.image { - image.data_ui(ctx, ui, ui_layout, query, entity_path); + image.data_ui(ctx, ui, ui_layout, entity_path); } if let Some(video) = &self.video { - video.data_ui(ctx, ui, ui_layout, query); + video.data_ui(ctx, ui, ui_layout); } } } diff --git a/crates/viewer/re_data_ui/src/component_path_ui.rs b/crates/viewer/re_data_ui/src/component_path_ui.rs index 67cf2b33e468..6094b2d1580a 100644 --- a/crates/viewer/re_data_ui/src/component_path_ui.rs +++ b/crates/viewer/re_data_ui/src/component_path_ui.rs @@ -1,28 +1,22 @@ use re_log_types::{ComponentPath, Instance}; use re_ui::UiExt as _; -use re_viewer_context::{UiLayout, ViewerContext}; +use re_viewer_context::{StoreViewContext, UiLayout}; use crate::latest_all_instance_ui::LatestAllInstanceResult; use super::DataUi; impl DataUi for ComponentPath { - fn data_ui( - &self, - ctx: &ViewerContext<'_>, - ui: &mut egui::Ui, - ui_layout: UiLayout, - query: &re_chunk_store::LatestAtQuery, - db: &re_entity_db::EntityDb, - ) { + fn data_ui(&self, ctx: &StoreViewContext<'_>, ui: &mut egui::Ui, ui_layout: UiLayout) { let Self { entity_path, component, } = self.clone(); - let engine = db.storage_engine(); + let engine = ctx.db.storage_engine(); + let query = ctx.query(); - let results = engine.cache().latest_all(query, &entity_path, [component]); + let results = engine.cache().latest_all(&query, &entity_path, [component]); if let Some(hits) = results.components.get(&component) { LatestAllInstanceResult { @@ -31,15 +25,15 @@ impl DataUi for ComponentPath { instance: Instance::ALL, hits, } - .data_ui(ctx, ui, ui_layout, query, db); - } else if db.tree().subtree(&entity_path).is_some() { + .data_ui(ctx, ui, ui_layout); + } else if ctx.db.tree().subtree(&entity_path).is_some() { let any_missing_chunks = !results.missing_virtual.is_empty(); // TODO(RR-3670): figure out how to handle missing chunks - if any_missing_chunks && db.can_fetch_chunks_from_redap() { + if any_missing_chunks && ctx.db.can_fetch_chunks_from_redap() { ui.loading_indicator("Fetching chunks from redap"); } else if engine.store().entity_has_component_on_timeline( - &query.timeline(), + &ctx.timeline_name(), &entity_path, component, ) { diff --git a/crates/viewer/re_data_ui/src/component_type_ui.rs b/crates/viewer/re_data_ui/src/component_type_ui.rs index 881d2f9d3934..f2cf6771bab9 100644 --- a/crates/viewer/re_data_ui/src/component_type_ui.rs +++ b/crates/viewer/re_data_ui/src/component_type_ui.rs @@ -1,18 +1,11 @@ use re_types_core::ComponentType; use re_ui::UiExt as _; -use re_viewer_context::{UiLayout, ViewerContext}; +use re_viewer_context::{StoreViewContext, UiLayout}; use super::DataUi; impl DataUi for ComponentType { - fn data_ui( - &self, - ctx: &ViewerContext<'_>, - ui: &mut egui::Ui, - ui_layout: UiLayout, - _query: &re_chunk_store::LatestAtQuery, - _db: &re_entity_db::EntityDb, - ) { + fn data_ui(&self, ctx: &StoreViewContext<'_>, ui: &mut egui::Ui, ui_layout: UiLayout) { if ui_layout.is_single_line() { ui.label(self.full_name()); } else { @@ -27,7 +20,8 @@ impl DataUi for ComponentType { // Only show the first line of the docs: if let Some(markdown) = ctx - .reflection() + .app_ctx + .reflection .components .get(self) .map(|info| info.docstring_md) diff --git a/crates/viewer/re_data_ui/src/component_ui_registry.rs b/crates/viewer/re_data_ui/src/component_ui_registry.rs index d9cdf7ac2aa2..4f3f108a1818 100644 --- a/crates/viewer/re_data_ui/src/component_ui_registry.rs +++ b/crates/viewer/re_data_ui/src/component_ui_registry.rs @@ -26,38 +26,30 @@ pub fn add_to_registry( registry.add_legacy_display_ui( C::name(), Box::new( - |ctx, - ui, - ui_layout, - query, - db, - entity_path, - component_descriptor, - row_id, - component_raw| match C::from_arrow(component_raw) { - Ok(components) => match components.len() { - 1 => { - components[0].entity_data_ui( - ctx, + |ctx, ui, ui_layout, entity_path, component_descriptor, row_id, component_raw| { + match C::from_arrow(component_raw) { + Ok(components) => match components.len() { + 1 => { + components[0].entity_data_ui( + ctx, + ui, + ui_layout, + entity_path, + component_descriptor, + row_id, + ); + } + _ => arrow_ui( ui, ui_layout, - entity_path, - component_descriptor, - row_id, - query, - db, - ); + ctx.app_options.timestamp_format, + component_raw, + ), + }, + Err(err) => { + ui.error_with_details_on_hover("(failed to deserialize)") + .on_hover_text(err.to_string()); } - _ => arrow_ui( - ui, - ui_layout, - ctx.app_options().timestamp_format, - component_raw, - ), - }, - Err(err) => { - ui.error_with_details_on_hover("(failed to deserialize)") - .on_hover_text(err.to_string()); } }, ), diff --git a/crates/viewer/re_data_ui/src/data_source_ui.rs b/crates/viewer/re_data_ui/src/data_source_ui.rs index a993fb8c5d1a..f146453eff64 100644 --- a/crates/viewer/re_data_ui/src/data_source_ui.rs +++ b/crates/viewer/re_data_ui/src/data_source_ui.rs @@ -1,19 +1,12 @@ use re_log_types::StoreKind; use re_sdk_types::archetypes::RecordingInfo; use re_sdk_types::components::Timestamp; -use re_viewer_context::{UiLayout, ViewerContext}; +use re_viewer_context::{AppContext, UiLayout}; use crate::item_ui::entity_db_button_ui; -impl crate::DataUi for re_log_channel::LogSource { - fn data_ui( - &self, - ctx: &ViewerContext<'_>, - ui: &mut egui::Ui, - ui_layout: UiLayout, - _query: &re_chunk_store::LatestAtQuery, - _db: &re_entity_db::EntityDb, - ) { +impl crate::AppUi for re_log_channel::LogSource { + fn app_ui(&self, ctx: &AppContext<'_>, ui: &mut egui::Ui, ui_layout: UiLayout) { ui.label(self.to_string()); if ui_layout.is_single_line() { @@ -62,16 +55,16 @@ impl crate::DataUi for re_log_channel::LogSource { if !recordings.is_empty() { ui.add_space(8.0); ui.strong("Recordings from this data source"); - for entity_db in recordings { - entity_db_button_ui(ctx, ui, entity_db, ui_layout, true); + for db in recordings { + entity_db_button_ui(ctx, db, ui, ui_layout, true); } } if !blueprints.is_empty() { ui.add_space(8.0); ui.strong("Blueprints from this data source"); - for entity_db in blueprints { - entity_db_button_ui(ctx, ui, entity_db, ui_layout, true); + for db in blueprints { + entity_db_button_ui(ctx, db, ui, ui_layout, true); } } }); diff --git a/crates/viewer/re_data_ui/src/entity_db_ui.rs b/crates/viewer/re_data_ui/src/entity_db_ui.rs index 30ae27bdd17f..24a75e121032 100644 --- a/crates/viewer/re_data_ui/src/entity_db_ui.rs +++ b/crates/viewer/re_data_ui/src/entity_db_ui.rs @@ -11,19 +11,12 @@ use re_format::{format_bytes, format_uint}; use re_log_channel::LogSource; use re_log_types::{EntityPath, StoreKind}; use re_ui::UiExt as _; -use re_viewer_context::{UiLayout, ViewerContext}; +use re_viewer_context::{AppContext, UiLayout}; use crate::item_ui::{app_id_button_ui, data_source_button_ui}; -impl crate::DataUi for EntityDb { - fn data_ui( - &self, - ctx: &ViewerContext<'_>, - ui: &mut egui::Ui, - ui_layout: UiLayout, - _query: &re_chunk_store::LatestAtQuery, - _db: &re_entity_db::EntityDb, - ) { +impl crate::AppUi for EntityDb { + fn app_ui(&self, ctx: &AppContext<'_>, ui: &mut egui::Ui, ui_layout: UiLayout) { re_tracing::profile_function!(); if ui_layout.is_single_line() { @@ -41,7 +34,7 @@ impl crate::DataUi for EntityDb { } egui::Grid::new("entity_db").num_columns(2).show(ui, |ui| { - grid_content_ui(self, ctx, ui, ui_layout); + grid_content_ui(ctx, self, ui, ui_layout); }); let hub = ctx.store_hub(); @@ -50,50 +43,56 @@ impl crate::DataUi for EntityDb { StoreKind::Recording => {} StoreKind::Blueprint => { - let active_app_id = ctx.store_context.application_id(); - let is_active_app_id = self.application_id() == active_app_id; - - if is_active_app_id { - let is_default = - hub.default_blueprint_id_for_app(active_app_id) == Some(self.store_id()); - let is_active = - hub.active_blueprint_id_for_app(active_app_id) == Some(self.store_id()); - - match (is_default, is_active) { - (false, false) => {} - (true, false) => { - ui.add_space(8.0); - ui.label("This is the default blueprint for the current application."); - - if let Some(active_blueprint) = - hub.active_blueprint_for_app(active_app_id) - && active_blueprint.cloned_from() == Some(self.store_id()) - { - // The active blueprint is a clone of the selected blueprint. - if self.latest_row_id() == active_blueprint.latest_row_id() { - ui.label("The active blueprint is a clone of this blueprint."); - } else { - ui.label("The active blueprint is a modified clone of this blueprint."); + if let Some(active_app_id) = ctx.active_store_context.map(|sc| sc.application_id()) + { + let is_active_app_id = self.application_id() == active_app_id; + + if is_active_app_id { + let is_default = hub.default_blueprint_id_for_app(active_app_id) + == Some(self.store_id()); + let is_active = + hub.active_blueprint_id_for_app(active_app_id) == Some(self.store_id()); + + match (is_default, is_active) { + (false, false) => {} + (true, false) => { + ui.add_space(8.0); + ui.label( + "This is the default blueprint for the current application.", + ); + + if let Some(active_blueprint) = + hub.active_blueprint_for_app(active_app_id) + && active_blueprint.cloned_from() == Some(self.store_id()) + { + // The active blueprint is a clone of the selected blueprint. + if self.latest_row_id() == active_blueprint.latest_row_id() { + ui.label( + "The active blueprint is a clone of this blueprint.", + ); + } else { + ui.label("The active blueprint is a modified clone of this blueprint."); + } } } + (false, true) => { + ui.add_space(8.0); + ui.label(format!("This is the active blueprint for the current application, '{active_app_id}'")); + } + (true, true) => { + ui.add_space(8.0); + ui.label(format!("This is both the active and default blueprint for the current application, '{active_app_id}'")); + } } - (false, true) => { - ui.add_space(8.0); - ui.label(format!("This is the active blueprint for the current application, '{active_app_id}'")); - } - (true, true) => { - ui.add_space(8.0); - ui.label(format!("This is both the active and default blueprint for the current application, '{active_app_id}'")); - } + } else { + ui.add_space(8.0); + ui.label("This blueprint is not for the active application"); } - } else { - ui.add_space(8.0); - ui.label("This blueprint is not for the active application"); } } } - if ctx.app_options().show_metrics + if ctx.app_options.show_metrics && self.can_fetch_chunks_from_redap() && ui_layout.is_selection_panel() { @@ -103,7 +102,7 @@ impl crate::DataUi for EntityDb { }); } - if cfg!(debug_assertions) && !ctx.app_ctx.is_test { + if cfg!(debug_assertions) && !ctx.is_test { ui.collapsing_header("Debug info", true, |ui| { debug_ui(ui, self); }); @@ -111,7 +110,7 @@ impl crate::DataUi for EntityDb { } } -fn grid_content_ui(db: &EntityDb, ctx: &ViewerContext<'_>, ui: &mut egui::Ui, ui_layout: UiLayout) { +fn grid_content_ui(ctx: &AppContext<'_>, db: &EntityDb, ui: &mut egui::Ui, ui_layout: UiLayout) { { ui.grid_left_hand_label(&format!("{} ID", db.store_id().kind())); ui.label(db.store_id().recording_id().to_string()); @@ -165,7 +164,7 @@ fn grid_content_ui(db: &EntityDb, ctx: &ViewerContext<'_>, ui: &mut egui::Ui, ui ui.end_row(); } - let show_last_modified_time = !ctx.app_ctx.is_test; + let show_last_modified_time = !ctx.is_test; // Hide in tests because it is non-deterministic (it's based on `RowId`). if show_last_modified_time && let Some(latest_row_id) = db.latest_row_id() @@ -173,7 +172,7 @@ fn grid_content_ui(db: &EntityDb, ctx: &ViewerContext<'_>, ui: &mut egui::Ui, ui { let time = re_log_types::Timestamp::from_nanos_since_epoch(nanos_since_epoch); ui.grid_left_hand_label("Modified"); - ui.label(time.format(ctx.app_options().timestamp_format)); + ui.label(time.format(ctx.app_options.timestamp_format)); ui.end_row(); } @@ -221,7 +220,7 @@ fn grid_content_ui(db: &EntityDb, ctx: &ViewerContext<'_>, ui: &mut egui::Ui, ui if db.rrd_manifest_index().has_manifest() { ui.grid_left_hand_label("Downloaded"); - let memory_limit = ctx.app_ctx.memory_limit; + let memory_limit = ctx.memory_limit; let max_downloaded_bytes = if db.rrd_manifest_index().is_fully_loaded() { full_size_bytes } else { diff --git a/crates/viewer/re_data_ui/src/entity_path_ui.rs b/crates/viewer/re_data_ui/src/entity_path_ui.rs index 81aa63bf1181..3bbd844258bd 100644 --- a/crates/viewer/re_data_ui/src/entity_path_ui.rs +++ b/crates/viewer/re_data_ui/src/entity_path_ui.rs @@ -1,17 +1,10 @@ use re_entity_db::InstancePath; -use re_viewer_context::{UiLayout, ViewerContext}; +use re_viewer_context::{StoreViewContext, UiLayout}; use super::DataUi; impl DataUi for re_entity_db::EntityPath { - fn data_ui( - &self, - ctx: &ViewerContext<'_>, - ui: &mut egui::Ui, - ui_layout: UiLayout, - query: &re_chunk_store::LatestAtQuery, - db: &re_entity_db::EntityDb, - ) { - InstancePath::entity_all(self.clone()).data_ui(ctx, ui, ui_layout, query, db); + fn data_ui(&self, ctx: &StoreViewContext<'_>, ui: &mut egui::Ui, ui_layout: UiLayout) { + InstancePath::entity_all(self.clone()).data_ui(ctx, ui, ui_layout); } } diff --git a/crates/viewer/re_data_ui/src/extra_data_ui.rs b/crates/viewer/re_data_ui/src/extra_data_ui.rs index 26f7def27bc6..b63d2b75d780 100644 --- a/crates/viewer/re_data_ui/src/extra_data_ui.rs +++ b/crates/viewer/re_data_ui/src/extra_data_ui.rs @@ -1,7 +1,7 @@ use re_chunk_store::UnitChunkShared; use re_types_core::ComponentDescriptor; use re_ui::{UiLayout, list_item}; -use re_viewer_context::ViewerContext; +use re_viewer_context::{AppContext, StoreViewContext}; use crate::{ blob_ui::BlobUi, image_ui::ImageUi, transform_frames_ui::TransformFramesUi, video_ui::VideoUi, @@ -16,8 +16,7 @@ pub enum ExtraDataUi { impl ExtraDataUi { pub fn from_components( - ctx: &ViewerContext<'_>, - query: &re_chunk_store::LatestAtQuery, + ctx: &StoreViewContext<'_>, entity_path: &re_log_types::EntityPath, descr: &ComponentDescriptor, chunk: &UnitChunkShared, @@ -28,16 +27,16 @@ impl ExtraDataUi { .or_else(|| { ImageUi::from_components(ctx, descr, chunk, entity_components).map(Self::Image) }) - .or_else(|| VideoUi::from_components(ctx, query, entity_path, descr).map(Self::Video)) + .or_else(|| VideoUi::from_components(ctx, entity_path, descr).map(Self::Video)) .or_else(|| { - TransformFramesUi::from_components(ctx, query, descr, chunk, entity_components) + TransformFramesUi::from_components(ctx, descr, chunk, entity_components) .map(Self::TransformHierarchy) }) } pub fn add_inline_buttons<'a>( &'a self, - ctx: &'a ViewerContext<'_>, + ctx: &'a AppContext<'_>, main_thread_token: re_capabilities::MainThreadToken, entity_path: &'a re_log_types::EntityPath, mut property_content: list_item::PropertyContent<'a>, @@ -61,24 +60,23 @@ impl ExtraDataUi { pub fn data_ui( self, - ctx: &ViewerContext<'_>, + ctx: &StoreViewContext<'_>, ui: &mut egui::Ui, layout: UiLayout, - query: &re_chunk_store::LatestAtQuery, entity_path: &re_log_types::EntityPath, ) { match self { Self::Video(video) => { - video.data_ui(ctx, ui, layout, query); + video.data_ui(ctx, ui, layout); } Self::Image(image) => { - image.data_ui(ctx, ui, layout, query, entity_path); + image.data_ui(ctx, ui, layout, entity_path); } Self::Blob(blob) => { - blob.data_ui(ctx, ui, layout, query, entity_path); + blob.data_ui(ctx, ui, layout, entity_path); } Self::TransformHierarchy(transform_hierarchy) => { - transform_hierarchy.data_ui(ctx, ui, layout); + transform_hierarchy.data_ui(ctx.app_ctx, ctx.db, ui, layout); } } } diff --git a/crates/viewer/re_data_ui/src/image_ui.rs b/crates/viewer/re_data_ui/src/image_ui.rs index 4bdb15fac97f..e41658803508 100644 --- a/crates/viewer/re_data_ui/src/image_ui.rs +++ b/crates/viewer/re_data_ui/src/image_ui.rs @@ -10,34 +10,35 @@ use re_types_core::{Component as _, ComponentDescriptor, RowId}; use re_ui::list_item::ListItemContentButtonsExt as _; use re_ui::{UiExt as _, icons, list_item}; use re_viewer_context::gpu_bridge::{self, image_data_range_heuristic, image_to_gpu}; -use re_viewer_context::{ColormapWithRange, ImageInfo, ImageStatsCache, UiLayout, ViewerContext}; +use re_viewer_context::{ + AppContext, ColormapWithRange, ImageInfo, ImageStatsCache, StoreViewContext, UiLayout, +}; use crate::find_and_deserialize_archetype_mono_component; /// Show the given image with an appropriate size. /// -/// For segmentation images, the annotation context is looked up. +/// For segmentation images, the annotation context is looked up in `store_ctx`. pub fn image_preview_ui( - ctx: &ViewerContext<'_>, + app_ctx: &StoreViewContext<'_>, + store_ctx: Option<&StoreViewContext<'_>>, ui: &mut egui::Ui, ui_layout: UiLayout, - query: &re_chunk_store::LatestAtQuery, entity_path: &re_log_types::EntityPath, image: &ImageInfo, colormap_with_range: Option<&ColormapWithRange>, ) -> Option<()> { - let image_stats = ctx - .store_context + let image_stats = app_ctx .caches .entry(|c: &mut ImageStatsCache| c.entry(image)); - let annotations = crate::annotations(ctx.recording(), query, entity_path); + let annotations = store_ctx.map(|store_ctx| crate::annotations(store_ctx, entity_path)); let debug_name = entity_path.to_string(); let texture = image_to_gpu( - ctx.render_ctx(), + app_ctx.render_ctx, &debug_name, image, &image_stats, - &annotations, + annotations.as_deref(), colormap_with_range, ) .ok()?; @@ -46,7 +47,7 @@ pub fn image_preview_ui( let preview_size = texture_preview_size(ui, ui_layout, [w, h]); texture_preview_ui( - ctx.render_ctx(), + app_ctx.render_ctx, ui, ui_layout, &debug_name, @@ -258,11 +259,8 @@ pub struct ImageUi { } impl ImageUi { - pub fn new(ctx: &ViewerContext<'_>, image: ImageInfo) -> Self { - let image_stats = ctx - .store_context - .caches - .entry(|c: &mut ImageStatsCache| c.entry(&image)); + pub fn new(ctx: &StoreViewContext<'_>, image: ImageInfo) -> Self { + let image_stats = ctx.caches.entry(|c: &mut ImageStatsCache| c.entry(&image)); let data_range = image_data_range_heuristic(&image_stats, &image.format); Self { image, @@ -272,14 +270,13 @@ impl ImageUi { } pub fn from_blob( - ctx: &ViewerContext<'_>, + ctx: &StoreViewContext<'_>, blob_row_id: RowId, blob_component_descriptor: &ComponentDescriptor, blob: &re_sdk_types::datatypes::Blob, media_type: Option<&MediaType>, ) -> Option { - ctx.store_context - .caches + ctx.caches .entry(|c: &mut re_viewer_context::ImageDecodeCache| { c.entry_encoded_color( blob_row_id, @@ -293,7 +290,7 @@ impl ImageUi { } pub fn from_components( - ctx: &ViewerContext<'_>, + ctx: &StoreViewContext<'_>, image_buffer_descr: &ComponentDescriptor, image_buffer_chunk: &UnitChunkShared, entity_components: &[(ComponentDescriptor, UnitChunkShared)], @@ -324,10 +321,7 @@ impl ImageUi { image_format.0, kind, ); - let image_stats = ctx - .store_context - .caches - .entry(|c: &mut ImageStatsCache| c.entry(&image)); + let image_stats = ctx.caches.entry(|c: &mut ImageStatsCache| c.entry(&image)); let colormap = find_and_deserialize_archetype_mono_component::( entity_components, @@ -366,7 +360,7 @@ impl ImageUi { pub fn inline_copy_button<'a>( &'a self, - ctx: &'a ViewerContext<'_>, + ctx: &'a AppContext<'_>, property_content: list_item::PropertyContent<'a>, ) -> list_item::PropertyContent<'a> { property_content.with_action_button(&icons::COPY, "Copy image", move || { @@ -375,7 +369,7 @@ impl ImageUi { [rgba.width() as _, rgba.height() as _], bytemuck::cast_slice(rgba.as_raw()), ); - ctx.egui_ctx().copy_image(egui_image); + ctx.egui_ctx.copy_image(egui_image); re_log::info!("Copied image to clipboard"); } else { re_log::error!("Invalid image"); @@ -385,7 +379,7 @@ impl ImageUi { pub fn inline_download_button<'a>( &'a self, - ctx: &'a ViewerContext<'_>, + ctx: &'a AppContext<'_>, main_thread_token: MainThreadToken, entity_path: &'a re_log_types::EntityPath, property_content: list_item::PropertyContent<'a>, @@ -400,7 +394,7 @@ impl ImageUi { .map_or("image", |name| name.unescaped_str()) .to_owned() ); - ctx.command_sender().save_file_dialog( + ctx.command_sender.save_file_dialog( main_thread_token, &file_name, "Save image".to_owned(), @@ -416,10 +410,9 @@ impl ImageUi { pub fn data_ui( &self, - ctx: &ViewerContext<'_>, + ctx: &StoreViewContext<'_>, ui: &mut egui::Ui, ui_layout: UiLayout, - query: &re_chunk_store::LatestAtQuery, entity_path: &re_log_types::EntityPath, ) { let Self { @@ -430,9 +423,9 @@ impl ImageUi { image_preview_ui( ctx, + Some(ctx), ui, ui_layout, - query, entity_path, image, colormap_with_range.as_ref(), diff --git a/crates/viewer/re_data_ui/src/instance_path_ui.rs b/crates/viewer/re_data_ui/src/instance_path_ui.rs index 5ef9651674c1..96a4fe8aa9e3 100644 --- a/crates/viewer/re_data_ui/src/instance_path_ui.rs +++ b/crates/viewer/re_data_ui/src/instance_path_ui.rs @@ -10,7 +10,7 @@ use re_sdk_types::reflection::ComponentDescriptorExt as _; use re_sdk_types::{ArchetypeName, Component as _, ComponentDescriptor, components}; use re_ui::list_item::{ListItemContentButtonsExt as _, PropertyContent}; use re_ui::{UiExt as _, design_tokens_of_visuals, list_item}; -use re_viewer_context::{HoverHighlight, Item, UiLayout, ViewerContext}; +use re_viewer_context::{HoverHighlight, Item, StoreViewContext, UiLayout}; use super::DataUi; use crate::{ @@ -22,14 +22,7 @@ use crate::{ const MAX_COMPONENTS_IN_TOOLTIP: usize = 3; impl DataUi for InstancePath { - fn data_ui( - &self, - ctx: &ViewerContext<'_>, - ui: &mut egui::Ui, - ui_layout: UiLayout, - query: &re_chunk_store::LatestAtQuery, - db: &re_entity_db::EntityDb, - ) { + fn data_ui(&self, ctx: &StoreViewContext<'_>, ui: &mut egui::Ui, ui_layout: UiLayout) { re_tracing::profile_function!(); let Self { @@ -37,10 +30,11 @@ impl DataUi for InstancePath { instance, } = self; - let components = db + let components = ctx + .db .storage_engine() .store() - .all_components_on_timeline(&query.timeline(), entity_path); + .all_components_on_timeline(&ctx.timeline_name(), entity_path); let Some(unordered_components) = components else { // This is fine - e.g. we're looking at `/world` and the user has only logged to `/world/car`. @@ -48,14 +42,14 @@ impl DataUi for InstancePath { ui, format!( "{self} has no own components on timeline {:?}, but its children do", - query.timeline() + ctx.timeline_name() ), ); return; }; let component_descriptors: Vec = { - let storage_engine = db.storage_engine(); + let storage_engine = ctx.db.storage_engine(); let store = storage_engine.store(); unordered_components .iter() @@ -65,12 +59,12 @@ impl DataUi for InstancePath { }; let components_by_archetype = crate::sorted_component_list_by_archetype_for_ui( - ctx.reflection(), + ctx.app_ctx.reflection, component_descriptors.iter().cloned(), ); - let query_results = db.storage_engine().cache().latest_all( - query, + let query_results = ctx.db.storage_engine().cache().latest_all( + &ctx.query(), entity_path, unordered_components.iter().copied(), ); @@ -82,8 +76,6 @@ impl DataUi for InstancePath { ui, ui_layout, self, - query, - db, &components_by_archetype, &query_results, ); @@ -104,34 +96,26 @@ impl DataUi for InstancePath { .collect_vec(); for (descr, shared) in &all_components { - if let Some(data) = ExtraDataUi::from_components( - ctx, - query, - entity_path, - descr, - shared, - &all_components, - ) { - data.data_ui(ctx, ui, ui_layout, query, entity_path); + if let Some(data) = + ExtraDataUi::from_components(ctx, entity_path, descr, shared, &all_components) + { + data.data_ui(ctx, ui, ui_layout, entity_path); } } } - if any_missing_chunks && db.can_fetch_chunks_from_redap() { + if any_missing_chunks && ctx.db.can_fetch_chunks_from_redap() { // TODO(RR-3670): figure out how to handle missing chunks ui.loading_indicator("Fetching chunks from redap"); } } } -#[expect(clippy::too_many_arguments)] fn instance_path_ui( - ctx: &ViewerContext<'_>, + ctx: &StoreViewContext<'_>, ui: &mut egui::Ui, ui_layout: UiLayout, instance_path: &InstancePath, - query: &re_chunk_store::LatestAtQuery, - db: &re_entity_db::EntityDb, components_by_archetype: &ArchetypeComponentMap, query_results: &LatestAllResults, ) { @@ -158,8 +142,6 @@ fn instance_path_ui( ui, ui_layout, instance_path, - query, - db, components_by_archetype, query_results, ); @@ -170,8 +152,6 @@ fn instance_path_ui( ui, ui_layout, instance_path, - query, - db, components_by_archetype, query_results, ) @@ -206,8 +186,6 @@ fn instance_path_ui( ui, ui_layout, instance_path, - query, - db, components_by_archetype, query_results, ); @@ -217,14 +195,11 @@ fn instance_path_ui( /// Show the value of a single instance (e.g. a point in a point cloud), /// focusing only on the components that are different between different points. -#[expect(clippy::too_many_arguments)] fn try_summary_ui_for_tooltip( - ctx: &ViewerContext<'_>, + ctx: &StoreViewContext<'_>, ui: &mut egui::Ui, ui_layout: UiLayout, instance_path: &InstancePath, - query: &re_chunk_store::LatestAtQuery, - db: &re_entity_db::EntityDb, components_by_archetype: &ArchetypeComponentMap, query_results: &LatestAllResults, ) -> Result<(), ()> { @@ -274,8 +249,6 @@ fn try_summary_ui_for_tooltip( ui, ui_layout, instance_path, - query, - db, &instanced_components_by_archetype, query_results, ); @@ -293,14 +266,11 @@ fn try_summary_ui_for_tooltip( Ok(()) } -#[expect(clippy::too_many_arguments)] fn component_list_ui( - ctx: &ViewerContext<'_>, + ctx: &StoreViewContext<'_>, ui: &mut egui::Ui, ui_layout: UiLayout, instance_path: &InstancePath, - query: &re_chunk_store::LatestAtQuery, - db: &re_entity_db::EntityDb, components_by_archetype: &ArchetypeComponentMap, query_results: &LatestAllResults, ) { @@ -347,8 +317,6 @@ fn component_list_ui( ui, ui_layout, instance_path, - query, - db, archetype_components, component_descr, hits, @@ -365,7 +333,7 @@ fn component_list_ui( // Maybe there _were_ data, but it has been GCed. // Maybe there _will be_ data, once we have loaded it. let any_missing_chunks = !query_results.missing_virtual.is_empty(); - if any_missing_chunks && db.can_fetch_chunks_from_redap() { + if any_missing_chunks && ctx.db.can_fetch_chunks_from_redap() { ui.loading_indicator("Fetching chunks from redap"); } else { ui.weak("-"); // TODO(RR-3670): figure out how to handle missing chunks @@ -378,12 +346,10 @@ fn component_list_ui( #[expect(clippy::too_many_arguments)] fn component_ui( - ctx: &ViewerContext<'_>, + ctx: &StoreViewContext<'_>, ui: &mut egui::Ui, ui_layout: UiLayout, instance_path: &InstancePath, - query: &re_chunk_store::LatestAtQuery, - db: &re_entity_db::EntityDb, archetype_components: &[ComponentDescriptor], component_descr: &ComponentDescriptor, hits: &LatestAllComponentResults, @@ -403,8 +369,11 @@ fn component_ui( let mut list_item = ui.list_item().interactive(interactive); if interactive { - let is_hovered = - ctx.selection_state().highlight_for_ui_element(&item) == HoverHighlight::Hovered; + let is_hovered = ctx + .app_ctx + .selection_state() + .highlight_for_ui_element(&item) + == HoverHighlight::Hovered; list_item = list_item.force_hovered(is_hovered); } @@ -425,7 +394,6 @@ fn component_ui( ExtraDataUi::from_components( ctx, - query, entity_path, component_descr, &unit, @@ -450,23 +418,28 @@ fn component_ui( instance: *instance, hits, } - .data_ui(ctx, ui, UiLayout::List, query, db); + .data_ui(ctx, ui, UiLayout::List); }); if let Some(extra_data_ui) = &extra_data_ui && ui_layout == UiLayout::SelectionPanel { content = extra_data_ui - .add_inline_buttons(ctx, MainThreadToken::from_egui_ui(ui), entity_path, content) + .add_inline_buttons( + ctx.app_ctx, + MainThreadToken::from_egui_ui(ui), + entity_path, + content, + ) .with_always_show_buttons(true); } let response = list_item.show_flat(ui, content).on_hover_ui(|ui| { if let Some(component_type) = component_descr.component_type { - component_type.data_ui_recording(ctx, ui, UiLayout::Tooltip); + component_type.data_ui(ctx, ui, UiLayout::Tooltip); } - if let Some(column) = db.storage_engine().store().resolve_component_selector( + if let Some(column) = ctx.db.storage_engine().store().resolve_component_selector( &re_sorbet::ComponentColumnSelector::from_descriptor( entity_path.clone(), component_descr, @@ -482,7 +455,8 @@ fn component_ui( }); if interactive { - ctx.handle_select_hover_drag_interactions(&response, item, false); + ctx.app_ctx + .handle_select_hover_drag_interactions(&response, item, false); } } diff --git a/crates/viewer/re_data_ui/src/item_ui.rs b/crates/viewer/re_data_ui/src/item_ui.rs index 41510fc20ab2..b1ffbf47ede8 100644 --- a/crates/viewer/re_data_ui/src/item_ui.rs +++ b/crates/viewer/re_data_ui/src/item_ui.rs @@ -12,10 +12,12 @@ use re_ui::list_item::ListItemContentButtonsExt as _; use re_ui::{SyntaxHighlighting as _, UiExt as _, icons, list_item}; use re_viewer_context::open_url::ViewerOpenUrl; use re_viewer_context::{ - DataResultInteractionAddress, HoverHighlight, Item, Route, SystemCommand, - SystemCommandSender as _, TimeControlCommand, UiLayout, ViewId, ViewerContext, + AppContext, DataResultInteractionAddress, HoverHighlight, Item, Route, StoreViewContext, + SystemCommand, SystemCommandSender as _, TimeControlCommand, UiLayout, ViewId, ViewerContext, }; +use crate::AppUi as _; + use super::DataUi as _; // TODO(andreas): This is where we want to go, but we need to figure out how get the [`re_viewer_context::ViewClass`] from the `ViewId`. @@ -43,17 +45,13 @@ use super::DataUi as _; /// Show an entity path and make it selectable. pub fn entity_path_button( - ctx: &ViewerContext<'_>, - query: &re_chunk_store::LatestAtQuery, - db: &re_entity_db::EntityDb, + ctx: &StoreViewContext<'_>, ui: &mut egui::Ui, view_id: Option, entity_path: &EntityPath, ) -> egui::Response { instance_path_button_to( ctx, - query, - db, ui, view_id, &InstancePath::entity_all(entity_path.clone()), @@ -63,9 +61,7 @@ pub fn entity_path_button( /// Show the different parts of an entity path and make them selectable. pub fn entity_path_parts_buttons( - ctx: &ViewerContext<'_>, - query: &re_chunk_store::LatestAtQuery, - db: &re_entity_db::EntityDb, + ctx: &StoreViewContext<'_>, ui: &mut egui::Ui, view_id: Option, entity_path: &EntityPath, @@ -85,7 +81,7 @@ pub fn entity_path_parts_buttons( if !with_individual_icons { // Show one single icon up-front instead: let instance_path = InstancePath::entity_all(entity_path.clone()); - ui.add(instance_path_icon(&query.timeline(), db, &instance_path).as_image()); + ui.add(instance_path_icon(ctx, &instance_path).as_image()); } if entity_path.is_root() { @@ -98,8 +94,6 @@ pub fn entity_path_parts_buttons( ui.strong("/"); instance_path_button_to_ex( ctx, - query, - db, ui, view_id, &InstancePath::entity_all(accumulated.clone()), @@ -112,37 +106,9 @@ pub fn entity_path_parts_buttons( .response } -/// Show an entity path that is part of the blueprint and make it selectable. -/// -/// Like [`entity_path_button_to`] but with the apriori knowledge that this exists in the blueprint. -pub fn blueprint_entity_path_button_to( - ctx: &ViewerContext<'_>, - ui: &mut egui::Ui, - entity_path: &EntityPath, - text: impl Into, -) -> egui::Response { - // If we're targeting an entity in the blueprint store, - // it doesn't make much sense to specify the view id since view ids are - // embedded in entity paths of the blueprint store. - // I.e. if there is a view relationship that we would care about, we would know that from the path! - let view_id = None; - - entity_path_button_to( - ctx, - ctx.blueprint_query, - ctx.blueprint_db(), - ui, - view_id, - entity_path, - text, - ) -} - /// Show an entity path and make it selectable. pub fn entity_path_button_to( - ctx: &ViewerContext<'_>, - query: &re_chunk_store::LatestAtQuery, - db: &re_entity_db::EntityDb, + ctx: &StoreViewContext<'_>, ui: &mut egui::Ui, view_id: Option, entity_path: &EntityPath, @@ -150,8 +116,6 @@ pub fn entity_path_button_to( ) -> egui::Response { instance_path_button_to( ctx, - query, - db, ui, view_id, &InstancePath::entity_all(entity_path.clone()), @@ -161,17 +125,13 @@ pub fn entity_path_button_to( /// Show an instance id and make it selectable. pub fn instance_path_button( - ctx: &ViewerContext<'_>, - query: &re_chunk_store::LatestAtQuery, - db: &re_entity_db::EntityDb, + ctx: &StoreViewContext<'_>, ui: &mut egui::Ui, view_id: Option, instance_path: &InstancePath, ) -> egui::Response { instance_path_button_to( ctx, - query, - db, ui, view_id, instance_path, @@ -184,16 +144,18 @@ pub fn instance_path_button( /// The choice of icon is based on whether the instance is "empty" as in hasn't any logged component /// _on the current timeline_. pub fn instance_path_icon( - timeline: &TimelineName, - db: &re_entity_db::EntityDb, + ctx: &StoreViewContext<'_>, instance_path: &InstancePath, ) -> &'static icons::Icon { if instance_path.is_all() { + let timeline = ctx.timeline_name(); + // It is an entity path - if db + if ctx + .db .storage_engine() .store() - .entity_has_physical_data_on_timeline(timeline, &instance_path.entity_path) + .entity_has_physical_data_on_timeline(&timeline, &instance_path.entity_path) { if instance_path.entity_path.is_reserved() { &icons::ENTITY_RESERVED @@ -211,55 +173,28 @@ pub fn instance_path_icon( } } -/// The current time query, based on the current time control and an `entity_path` -/// -/// If the user is inspecting the blueprint, and the `entity_path` is on the blueprint -/// timeline, then use the blueprint. Otherwise, use the recording. -// TODO(jleibs): Ideally this wouldn't be necessary and we could make the assessment -// directly from the entity_path. -pub fn guess_query_and_db_for_selected_entity<'a>( - ctx: &'a ViewerContext<'_>, - entity_path: &EntityPath, -) -> (re_chunk_store::LatestAtQuery, &'a re_entity_db::EntityDb) { - if ctx.app_options().inspect_blueprint_timeline - && ctx.store_context.blueprint.is_logged_entity(entity_path) - { - ( - ctx.blueprint_time_ctrl.current_query(), - ctx.store_context.blueprint, - ) - } else { - (ctx.time_ctrl.current_query(), ctx.recording()) - } -} - pub fn guess_instance_path_icon( ctx: &ViewerContext<'_>, instance_path: &InstancePath, ) -> &'static icons::Icon { - let (query, db) = guess_query_and_db_for_selected_entity(ctx, &instance_path.entity_path); - instance_path_icon(&query.timeline(), db, instance_path) + let ctx = ctx.guess_store_view_context_for_entity(&instance_path.entity_path); + instance_path_icon(&ctx, instance_path) } /// Show an instance id and make it selectable. pub fn instance_path_button_to( - ctx: &ViewerContext<'_>, - query: &re_chunk_store::LatestAtQuery, - db: &re_entity_db::EntityDb, + ctx: &StoreViewContext<'_>, ui: &mut egui::Ui, view_id: Option, instance_path: &InstancePath, text: impl Into, ) -> egui::Response { - instance_path_button_to_ex(ctx, query, db, ui, view_id, instance_path, text, true) + instance_path_button_to_ex(ctx, ui, view_id, instance_path, text, true) } /// Show an instance id and make it selectable. -#[expect(clippy::too_many_arguments)] fn instance_path_button_to_ex( - ctx: &ViewerContext<'_>, - query: &re_chunk_store::LatestAtQuery, - db: &re_entity_db::EntityDb, + ctx: &StoreViewContext<'_>, ui: &mut egui::Ui, view_id: Option, instance_path: &InstancePath, @@ -278,7 +213,7 @@ fn instance_path_button_to_ex( let response = if with_icon { ui.selectable_label_with_icon( - instance_path_icon(&query.timeline(), db, instance_path), + instance_path_icon(ctx, instance_path), text, ctx.is_selected_or_loading(&item), re_ui::LabelStyle::Normal, @@ -289,7 +224,7 @@ fn instance_path_button_to_ex( let response = response.on_hover_ui(|ui| { let include_subtree = false; - instance_hover_card_ui(ui, ctx, query, db, instance_path, include_subtree); + instance_hover_card_ui(ui, ctx, instance_path, include_subtree); }); cursor_interact_with_selectable(ctx, response, item) @@ -297,9 +232,7 @@ fn instance_path_button_to_ex( /// Show the different parts of an instance path and make them selectable. pub fn instance_path_parts_buttons( - ctx: &ViewerContext<'_>, - query: &re_chunk_store::LatestAtQuery, - db: &re_entity_db::EntityDb, + ctx: &StoreViewContext<'_>, ui: &mut egui::Ui, view_id: Option, instance_path: &InstancePath, @@ -310,7 +243,7 @@ pub fn instance_path_parts_buttons( ui.spacing_mut().item_spacing.x = 2.0; // Show one single icon up-front instead: - ui.add(instance_path_icon(&query.timeline(), db, instance_path).as_image()); + ui.add(instance_path_icon(ctx, instance_path).as_image()); let mut accumulated = Vec::new(); for part in instance_path.entity_path.iter() { @@ -319,8 +252,6 @@ pub fn instance_path_parts_buttons( ui.strong("/"); instance_path_button_to_ex( ctx, - query, - db, ui, view_id, &InstancePath::entity_all(accumulated.clone()), @@ -333,8 +264,6 @@ pub fn instance_path_parts_buttons( ui.weak("["); instance_path_button_to_ex( ctx, - query, - db, ui, view_id, instance_path, @@ -462,9 +391,7 @@ fn entity_tree_stats_ui( } pub fn data_blueprint_button_to( - ctx: &ViewerContext<'_>, - query: &re_chunk_store::LatestAtQuery, - db: &re_entity_db::EntityDb, + ctx: &StoreViewContext<'_>, ui: &mut egui::Ui, text: impl Into, view_id: ViewId, @@ -478,7 +405,7 @@ pub fn data_blueprint_button_to( .selectable_label(ctx.is_selected_or_loading(&item), text) .on_hover_ui(|ui| { let include_subtree = false; - entity_hover_card_ui(ui, ctx, query, db, entity_path, include_subtree); + entity_hover_card_ui(ui, ctx, entity_path, include_subtree); }); cursor_interact_with_selectable(ctx, response, item) } @@ -508,7 +435,7 @@ pub fn time_button( } pub fn timeline_button( - ctx: &ViewerContext<'_>, + ctx: &AppContext<'_>, ui: &mut egui::Ui, timeline: &TimelineName, ) -> egui::Response { @@ -516,18 +443,20 @@ pub fn timeline_button( } pub fn timeline_button_to( - ctx: &ViewerContext<'_>, + ctx: &AppContext<'_>, ui: &mut egui::Ui, text: impl Into, timeline_name: &TimelineName, ) -> egui::Response { - let is_selected = ctx.time_ctrl.timeline_name() == timeline_name; + let is_selected = ctx + .active_time_ctrl + .is_some_and(|time_ctr| time_ctr.timeline_name() == timeline_name); let response = ui .selectable_label(is_selected, text) .on_hover_text("Click to switch to this timeline"); if response.clicked() { - ctx.send_time_commands([ + ctx.send_time_commands_to_active_recording([ TimeControlCommand::SetActiveTimeline(*timeline_name), TimeControlCommand::Pause, ]); @@ -537,7 +466,7 @@ pub fn timeline_button_to( // TODO(andreas): Move elsewhere, this is not directly part of the item_ui. pub fn cursor_interact_with_selectable( - ctx: &ViewerContext<'_>, + ctx: &AppContext<'_>, response: egui::Response, item: Item, ) -> egui::Response { @@ -562,13 +491,11 @@ pub fn cursor_interact_with_selectable( /// If `include_subtree=true`, stats for the entire entity subtree will be shown. pub fn instance_hover_card_ui( ui: &mut egui::Ui, - ctx: &ViewerContext<'_>, - query: &re_chunk_store::LatestAtQuery, - db: &re_entity_db::EntityDb, + ctx: &StoreViewContext<'_>, instance_path: &InstancePath, include_subtree: bool, ) { - if !db.is_known_entity(&instance_path.entity_path) { + if !ctx.db.is_known_entity(&instance_path.entity_path) { ui.label("Unknown entity."); return; } @@ -587,14 +514,14 @@ pub fn instance_hover_card_ui( // Then we can move the size view into `data_ui`. if instance_path.instance.is_all() { - if let Some(subtree) = db.tree().subtree(&instance_path.entity_path) { - entity_tree_stats_ui(ui, &query.timeline(), db, subtree, include_subtree); + if let Some(subtree) = ctx.db.tree().subtree(&instance_path.entity_path) { + entity_tree_stats_ui(ui, &ctx.timeline_name(), ctx.db, subtree, include_subtree); } } else { // TODO(emilk): per-component stats } - instance_path.data_ui(ctx, ui, UiLayout::Tooltip, query, db); + instance_path.data_ui(ctx, ui, UiLayout::Tooltip); } /// Displays the "hover card" (i.e. big tooltip) for an entity. @@ -602,18 +529,16 @@ pub fn instance_hover_card_ui( /// If `include_subtree=true`, stats for the entire entity subtree will be shown. pub fn entity_hover_card_ui( ui: &mut egui::Ui, - ctx: &ViewerContext<'_>, - query: &re_chunk_store::LatestAtQuery, - db: &re_entity_db::EntityDb, + ctx: &StoreViewContext<'_>, entity_path: &EntityPath, include_subtree: bool, ) { let instance_path = InstancePath::entity_all(entity_path.clone()); - instance_hover_card_ui(ui, ctx, query, db, &instance_path, include_subtree); + instance_hover_card_ui(ui, ctx, &instance_path, include_subtree); } pub fn app_id_button_ui( - ctx: &ViewerContext<'_>, + ctx: &AppContext<'_>, ui: &mut egui::Ui, app_id: &ApplicationId, ) -> egui::Response { @@ -627,14 +552,14 @@ pub fn app_id_button_ui( ); let response = response.on_hover_ui(|ui| { - app_id.data_ui_recording(ctx, ui, re_viewer_context::UiLayout::Tooltip); + app_id.app_ui(ctx, ui, re_viewer_context::UiLayout::Tooltip); }); cursor_interact_with_selectable(ctx, response, item) } pub fn data_source_button_ui( - ctx: &ViewerContext<'_>, + ctx: &AppContext<'_>, ui: &mut egui::Ui, data_source: &re_log_channel::LogSource, ) -> egui::Response { @@ -648,7 +573,7 @@ pub fn data_source_button_ui( ); let response = response.on_hover_ui(|ui| { - data_source.data_ui_recording(ctx, ui, re_viewer_context::UiLayout::Tooltip); + data_source.app_ui(ctx, ui, re_viewer_context::UiLayout::Tooltip); }); cursor_interact_with_selectable(ctx, response, item) @@ -657,13 +582,13 @@ pub fn data_source_button_ui( /// This uses [`list_item::ListItem::show_hierarchical`], meaning it comes with built-in /// indentation. pub fn store_id_button_ui( - ctx: &ViewerContext<'_>, + ctx: &AppContext<'_>, ui: &mut egui::Ui, store_id: &re_log_types::StoreId, ui_layout: UiLayout, ) { if let Some(entity_db) = ctx.store_bundle().get(store_id) { - entity_db_button_ui(ctx, ui, entity_db, ui_layout, true); + entity_db_button_ui(ctx, entity_db, ui, ui_layout, true); } else { ui_layout.label(ui, "").on_hover_ui(|ui| { ui.label(format!("{store_id:?}")); @@ -678,9 +603,9 @@ pub fn store_id_button_ui( /// This uses [`list_item::ListItem::show_hierarchical`], meaning it comes with built-in /// indentation. pub fn entity_db_button_ui( - ctx: &ViewerContext<'_>, - ui: &mut egui::Ui, + ctx: &AppContext<'_>, entity_db: &re_entity_db::EntityDb, + ui: &mut egui::Ui, ui_layout: UiLayout, include_app_id: bool, ) -> egui::Response { @@ -710,7 +635,7 @@ pub fn entity_db_button_ui( .recording_info_property::(RecordingInfo::descriptor_start_time().component) .map(|started| { re_log_types::Timestamp::from(started.0) - .to_jiff_zoned(ctx.app_options().timestamp_format) + .to_jiff_zoned(ctx.app_options.timestamp_format) .strftime("%H:%M:%S") .to_string() }) @@ -744,7 +669,7 @@ pub fn entity_db_button_ui( } }); if resp.clicked() { - ctx.command_sender() + ctx.command_sender .send_system(SystemCommand::CloseRecordingOrTable( store_id.clone().into(), )); @@ -754,7 +679,10 @@ pub fn entity_db_button_ui( let mut list_item = ui .list_item() - .active(ctx.store_context.is_active(&store_id)) + .active( + ctx.active_store_context + .is_some_and(|sc| sc.is_active(&store_id)), + ) .selected(ctx.is_selected_or_loading(&item)); if ctx.hovered().contains_item(&item) { @@ -765,13 +693,7 @@ pub fn entity_db_button_ui( list_item .show_hierarchical(ui, item_content) .on_hover_ui(|ui| { - entity_db.data_ui( - ctx, - ui, - re_viewer_context::UiLayout::Tooltip, - &ctx.current_query(), - entity_db, - ); + entity_db.app_ui(ctx, ui, re_viewer_context::UiLayout::Tooltip); }) }) .inner; @@ -795,7 +717,7 @@ pub fn entity_db_button_ui( .clicked() && let Ok(url) = url { - ctx.command_sender() + ctx.command_sender .send_system(SystemCommand::CopyViewerUrl(url)); } @@ -814,7 +736,7 @@ pub fn entity_db_button_ui( // TODO(jleibs): We should still have an `Activate this Blueprint` button in the selection panel // for the blueprint. if store_id.is_recording() { - ctx.command_sender() + ctx.command_sender .send_system(SystemCommand::SetRoute(new_entry.route())); } } diff --git a/crates/viewer/re_data_ui/src/latest_all_instance_ui.rs b/crates/viewer/re_data_ui/src/latest_all_instance_ui.rs index ed435af15ae0..ecbd815c2b7a 100644 --- a/crates/viewer/re_data_ui/src/latest_all_instance_ui.rs +++ b/crates/viewer/re_data_ui/src/latest_all_instance_ui.rs @@ -3,7 +3,7 @@ use re_log_types::{EntityPath, Instance}; use re_query::LatestAllComponentResults; use re_sdk_types::ComponentIdentifier; use re_ui::UiExt as _; -use re_viewer_context::{UiLayout, ViewerContext}; +use re_viewer_context::{StoreViewContext, UiLayout}; use super::{DataUi, LatestAtInstanceResult}; @@ -28,14 +28,7 @@ pub struct LatestAllInstanceResult<'a> { } impl DataUi for LatestAllInstanceResult<'_> { - fn data_ui( - &self, - ctx: &ViewerContext<'_>, - ui: &mut egui::Ui, - ui_layout: UiLayout, - query: &re_chunk_store::LatestAtQuery, - db: &re_entity_db::EntityDb, - ) { + fn data_ui(&self, ctx: &StoreViewContext<'_>, ui: &mut egui::Ui, ui_layout: UiLayout) { let Self { entity_path, component, @@ -47,12 +40,13 @@ impl DataUi for LatestAllInstanceResult<'_> { ui.sanity_check(); - let engine = db.storage_engine(); + let engine = ctx.db.storage_engine(); let time = hits.time(); - let timeline = query.timeline(); - let time_type = db.timeline_type(&query.timeline()); - let formatted_time = time_type.format(time, ctx.app_options().timestamp_format); + let timeline = ctx.timeline(); + let timeline_name = timeline.name(); + let time_type = timeline.typ(); + let formatted_time = time_type.format(time, ctx.app_ctx.app_options.timestamp_format); if !ui_layout.is_single_line() { // Display time and other diagnostic information as a preamble: @@ -99,7 +93,7 @@ impl DataUi for LatestAllInstanceResult<'_> { ui.horizontal(|ui| { ui.add(re_ui::icons::COMPONENT_TEMPORAL.as_image()); ui.label(format!( - "Logged {} at {timeline}={formatted_time}", + "Logged {} at {timeline_name}={formatted_time}", format_plural_s(hits.num_rows(), "time") )); }); @@ -116,7 +110,7 @@ impl DataUi for LatestAllInstanceResult<'_> { instance, unit: &unit, } - .data_ui(ctx, ui, ui_layout, query, db); + .data_ui(ctx, ui, ui_layout); } else { // Many hits on the same time point (e.g. transforms): @@ -128,7 +122,7 @@ impl DataUi for LatestAllInstanceResult<'_> { )); } else { ui.label(format!( - "Logged {} at {timeline}={formatted_time}", + "Logged {} at {timeline_name}={formatted_time}", format_plural_s(hits.num_rows(), "time") )); } @@ -144,7 +138,7 @@ impl DataUi for LatestAllInstanceResult<'_> { instance, unit: &unit, } - .data_ui(ctx, ui, ui_layout, query, db); + .data_ui(ctx, ui, ui_layout); }); } } diff --git a/crates/viewer/re_data_ui/src/latest_at_instance_ui.rs b/crates/viewer/re_data_ui/src/latest_at_instance_ui.rs index 4a28dae14f47..e5f2dc621e15 100644 --- a/crates/viewer/re_data_ui/src/latest_at_instance_ui.rs +++ b/crates/viewer/re_data_ui/src/latest_at_instance_ui.rs @@ -5,7 +5,7 @@ use re_format::format_plural_s; use re_log_types::{EntityPath, Instance, TimePoint}; use re_sdk_types::ComponentIdentifier; use re_ui::{SyntaxHighlighting as _, UiExt as _}; -use re_viewer_context::{UiLayout, ViewerContext}; +use re_viewer_context::{StoreViewContext, UiLayout}; use crate::item_ui; @@ -27,14 +27,7 @@ pub struct LatestAtInstanceResult<'a> { } impl DataUi for LatestAtInstanceResult<'_> { - fn data_ui( - &self, - ctx: &ViewerContext<'_>, - ui: &mut egui::Ui, - ui_layout: UiLayout, - query: &re_chunk_store::LatestAtQuery, - db: &re_entity_db::EntityDb, - ) { + fn data_ui(&self, ctx: &StoreViewContext<'_>, ui: &mut egui::Ui, ui_layout: UiLayout) { let Self { entity_path, component, @@ -48,7 +41,7 @@ impl DataUi for LatestAtInstanceResult<'_> { let tokens = ui.tokens(); - let engine = db.storage_engine(); + let engine = ctx.db.storage_engine(); let Some(component_descriptor) = engine .store() @@ -98,11 +91,11 @@ impl DataUi for LatestAtInstanceResult<'_> { if num_instances == 1 && entity_path.starts_with(&EntityPath::properties()) && let Some(array) = unit.component_batch_raw(component) - && ctx.component_ui_registry().try_show_edit_ui( + && ctx.app_ctx.component_ui_registry.try_show_edit_ui( ctx, ui, re_viewer_context::EditTarget { - store_id: db.store_id().clone(), + store_id: ctx.db.store_id().clone(), timepoint: TimePoint::STATIC, entity_path: entity_path.clone(), }, @@ -114,12 +107,10 @@ impl DataUi for LatestAtInstanceResult<'_> { return; } - ctx.component_ui_registry().component_ui( + ctx.app_ctx.component_ui_registry.component_ui( ctx, ui, ui_layout, - query, - db, &entity_path, &component_descriptor, unit, @@ -160,8 +151,6 @@ impl DataUi for LatestAtInstanceResult<'_> { InstancePath::instance(entity_path.clone(), instance); item_ui::instance_path_button_to( ctx, - query, - db, ui, None, &instance_path, @@ -170,12 +159,10 @@ impl DataUi for LatestAtInstanceResult<'_> { } }); row.col(|ui| { - ctx.component_ui_registry().component_ui( + ctx.app_ctx.component_ui_registry.component_ui( ctx, ui, UiLayout::List, - query, - db, &entity_path, &component_descriptor, unit, diff --git a/crates/viewer/re_data_ui/src/lib.rs b/crates/viewer/re_data_ui/src/lib.rs index bc5b2d6f3288..0160e9c73a9c 100644 --- a/crates/viewer/re_data_ui/src/lib.rs +++ b/crates/viewer/re_data_ui/src/lib.rs @@ -7,8 +7,7 @@ use re_log_types::EntityPath; use re_sdk_types::reflection::ComponentDescriptorExt as _; use re_sdk_types::{ComponentDescriptor, RowId}; -use re_ui::UiExt as _; -use re_viewer_context::{UiLayout, ViewerContext}; +use re_viewer_context::{AppContext, StoreViewContext, UiLayout}; mod annotation_context_ui; mod app_id_ui; @@ -81,23 +80,26 @@ pub fn sorted_component_list_by_archetype_for_ui( map } +/// Types implementing [`AppUi`] can display themselves in an [`egui::Ui`] using only an [`AppContext`]. +pub trait AppUi { + /// If you need to lookup something in the chunk store, use the given query to do so. + fn app_ui(&self, ctx: &AppContext<'_>, ui: &mut egui::Ui, ui_layout: UiLayout); +} + /// Types implementing [`DataUi`] can display themselves in an [`egui::Ui`]. +/// +/// Use this for things that live inside of a recording. pub trait DataUi { - /// If you need to lookup something in the chunk store, use the given query to do so. - fn data_ui( - &self, - ctx: &ViewerContext<'_>, - ui: &mut egui::Ui, - ui_layout: UiLayout, - query: &re_chunk_store::LatestAtQuery, - db: &re_entity_db::EntityDb, - ); + fn data_ui(&self, ctx: &StoreViewContext<'_>, ui: &mut egui::Ui, ui_layout: UiLayout); +} - /// Called [`Self::data_ui`] using the default query and recording. - fn data_ui_recording(&self, ctx: &ViewerContext<'_>, ui: &mut egui::Ui, ui_layout: UiLayout) { - ui.sanity_check(); - self.data_ui(ctx, ui, ui_layout, &ctx.current_query(), ctx.recording()); - ui.sanity_check(); +// Blanket implementation of DataUi for everything that already implements AppUi: +impl DataUi for T +where + T: AppUi, +{ + fn data_ui(&self, ctx: &StoreViewContext<'_>, ui: &mut egui::Ui, ui_layout: UiLayout) { + self.app_ui(ctx.app_ctx, ui, ui_layout); } } @@ -106,17 +108,14 @@ pub trait DataUi { /// This is given the context of the entity it is part of so it can do queries. pub trait EntityDataUi { /// If you need to lookup something in the chunk store, use the given query to do so. - #[expect(clippy::too_many_arguments)] fn entity_data_ui( &self, - ctx: &ViewerContext<'_>, + ctx: &StoreViewContext<'_>, ui: &mut egui::Ui, ui_layout: UiLayout, entity_path: &EntityPath, component_descriptor: &ComponentDescriptor, row_id: Option, - query: &re_chunk_store::LatestAtQuery, - db: &re_entity_db::EntityDb, ); } @@ -126,19 +125,17 @@ where { fn entity_data_ui( &self, - ctx: &ViewerContext<'_>, + ctx: &StoreViewContext<'_>, ui: &mut egui::Ui, ui_layout: UiLayout, entity_path: &EntityPath, _component_descriptor: &ComponentDescriptor, _row_id: Option, - query: &re_chunk_store::LatestAtQuery, - db: &re_entity_db::EntityDb, ) { // This ensures that UI state is maintained per entity. For example, the collapsed state for // `AnnotationContext` component is not saved by all instances of the component. ui.push_id(entity_path.hash(), |ui| { - self.data_ui(ctx, ui, ui_layout, query, db); + self.data_ui(ctx, ui, ui_layout); }); } } @@ -146,13 +143,12 @@ where // --------------------------------------------------------------------------- pub fn annotations( - db: &re_entity_db::EntityDb, - query: &re_chunk_store::LatestAtQuery, + ctx: &StoreViewContext<'_>, entity_path: &re_entity_db::EntityPath, ) -> std::sync::Arc { re_tracing::profile_function!(); let mut annotation_map = re_viewer_context::AnnotationMap::default(); - annotation_map.load(db, query); + annotation_map.load(ctx.db, &ctx.query()); annotation_map.find(entity_path) } diff --git a/crates/viewer/re_data_ui/src/store_id_ui.rs b/crates/viewer/re_data_ui/src/store_id_ui.rs index 56fcd4690694..2950ce9545ef 100644 --- a/crates/viewer/re_data_ui/src/store_id_ui.rs +++ b/crates/viewer/re_data_ui/src/store_id_ui.rs @@ -1,16 +1,9 @@ -use re_viewer_context::{UiLayout, ViewerContext}; +use re_viewer_context::{AppContext, UiLayout}; -impl crate::DataUi for re_log_types::StoreId { - fn data_ui( - &self, - ctx: &ViewerContext<'_>, - ui: &mut egui::Ui, - ui_layout: UiLayout, - query: &re_chunk_store::LatestAtQuery, - db: &re_entity_db::EntityDb, - ) { +impl crate::AppUi for re_log_types::StoreId { + fn app_ui(&self, ctx: &AppContext<'_>, ui: &mut egui::Ui, ui_layout: UiLayout) { if let Some(entity_db) = ctx.store_bundle().get(self) { - entity_db.data_ui(ctx, ui, ui_layout, query, db); + entity_db.app_ui(ctx, ui, ui_layout); } else { ui_layout.label(ui, "").on_hover_ui(|ui| { ui.label(format!("{self:?}")); diff --git a/crates/viewer/re_data_ui/src/tensor_ui.rs b/crates/viewer/re_data_ui/src/tensor_ui.rs index 255bb34133f6..64dbb460f193 100644 --- a/crates/viewer/re_data_ui/src/tensor_ui.rs +++ b/crates/viewer/re_data_ui/src/tensor_ui.rs @@ -4,7 +4,7 @@ use re_log_types::hash::Hash64; use re_sdk_types::datatypes::TensorData; use re_sdk_types::{ComponentDescriptor, RowId}; use re_ui::UiExt as _; -use re_viewer_context::{TensorStats, TensorStatsCache, UiLayout, ViewerContext}; +use re_viewer_context::{StoreViewContext, TensorStats, TensorStatsCache, UiLayout}; use super::EntityDataUi; @@ -35,14 +35,12 @@ fn format_tensor_shape_single_line(tensor: &TensorData) -> String { impl EntityDataUi for re_sdk_types::components::TensorData { fn entity_data_ui( &self, - ctx: &ViewerContext<'_>, + ctx: &StoreViewContext<'_>, ui: &mut egui::Ui, ui_layout: UiLayout, _entity_path: &EntityPath, _component_descriptor: &ComponentDescriptor, row_id: Option, - _query: &re_chunk_store::LatestAtQuery, - _db: &re_entity_db::EntityDb, ) { re_tracing::profile_function!(); // RowId is enough for cache keying the tensor stats right now since you can't have more than one per row. @@ -52,7 +50,7 @@ impl EntityDataUi for re_sdk_types::components::TensorData { } pub fn tensor_ui( - ctx: &ViewerContext<'_>, + ctx: &StoreViewContext<'_>, ui: &mut egui::Ui, ui_layout: UiLayout, tensor_cache_key: Hash64, @@ -61,7 +59,6 @@ pub fn tensor_ui( // See if we can convert the tensor to a GPU texture. // Even if not, we will show info about the tensor. let tensor_stats = ctx - .store_context .caches .entry(|c: &mut TensorStatsCache| c.entry(tensor_cache_key, tensor)); diff --git a/crates/viewer/re_data_ui/src/transform_frames_ui.rs b/crates/viewer/re_data_ui/src/transform_frames_ui.rs index d4f2cf00c287..d20e72d4a404 100644 --- a/crates/viewer/re_data_ui/src/transform_frames_ui.rs +++ b/crates/viewer/re_data_ui/src/transform_frames_ui.rs @@ -4,8 +4,8 @@ use re_sdk_types::components::{self, TransformFrameId}; use re_sdk_types::{ComponentDescriptor, TransformFrameIdHash, archetypes}; use re_ui::{HasDesignTokens as _, UiExt as _, UiLayout, icons}; use re_viewer_context::{ - DataResultInteractionAddress, Item, SystemCommand, SystemCommandSender as _, - TransformDatabaseStoreCache, ViewerContext, + AppContext, DataResultInteractionAddress, Item, StoreViewContext, SystemCommand, + SystemCommandSender as _, TransformDatabaseStoreCache, }; /// The max amount of ancestors we show before putting a '…'. @@ -34,8 +34,7 @@ pub struct TransformFramesUi { impl TransformFramesUi { pub fn from_components( - ctx: &ViewerContext<'_>, - query: &re_chunk_store::LatestAtQuery, + ctx: &StoreViewContext<'_>, transform_frame_descr: &ComponentDescriptor, transform_frame_chunk: &UnitChunkShared, entity_components: &[(ComponentDescriptor, UnitChunkShared)], @@ -75,11 +74,11 @@ impl TransformFramesUi { let mut frame_id_hash = TransformFrameIdHash::new(&frame_id); - let caches = ctx.store_context.caches; - let frame_ids = caches - .entry(|c: &mut TransformDatabaseStoreCache| c.frame_id_registry(ctx.recording())); - let transforms = caches.entry(|c: &mut TransformDatabaseStoreCache| { - c.transforms_for_timeline(ctx.recording(), *ctx.time_ctrl.timeline_name()) + let (frame_ids, transforms) = ctx.caches.entry(|c: &mut TransformDatabaseStoreCache| { + ( + c.frame_id_registry(ctx.db), + c.transforms_for_timeline(ctx.db, ctx.timeline_name()), + ) }); let mut frames = Vec::new(); @@ -105,11 +104,11 @@ impl TransformFramesUi { frames.push(TransformFrameInfo { frame_id: frame_id.clone(), - source_entity: Some(frame.associated_entity_path(query.at()).clone()), + source_entity: Some(frame.associated_entity_path(ctx.query().at()).clone()), }); let Some(transform) = - frame.latest_at_transform(ctx.recording(), &missing_chunk_reporter, query) + frame.latest_at_transform(ctx.db, &missing_chunk_reporter, &ctx.query()) else { break false; }; @@ -130,7 +129,13 @@ impl TransformFramesUi { }) } - pub fn data_ui(&self, ctx: &ViewerContext<'_>, ui: &mut egui::Ui, layout: UiLayout) { + pub fn data_ui( + &self, + ctx: &AppContext<'_>, + db: &re_entity_db::EntityDb, + ui: &mut egui::Ui, + layout: UiLayout, + ) { match layout { UiLayout::Tooltip => {} // Don't show in tooltips. UiLayout::List | UiLayout::SelectionPanel => { @@ -145,7 +150,7 @@ impl TransformFramesUi { .max_height(350.0) .stick_to_bottom(true) .show(ui, |ui| { - self.show_transforms(ctx, layout, ui); + self.show_transforms(ctx, db, layout, ui); }) }); }); @@ -153,7 +158,13 @@ impl TransformFramesUi { } } - fn show_transforms(&self, ctx: &ViewerContext<'_>, layout: UiLayout, ui: &mut egui::Ui) { + fn show_transforms( + &self, + ctx: &AppContext<'_>, + db: &re_entity_db::EntityDb, + layout: UiLayout, + ui: &mut egui::Ui, + ) { let Self { missing_chunk_reporter, frames, @@ -161,7 +172,7 @@ impl TransformFramesUi { } = self; if missing_chunk_reporter.any_missing() { - if ctx.recording().can_fetch_chunks_from_redap() { + if db.can_fetch_chunks_from_redap() { ui.loading_indicator("Fetching chunks from redap"); } else { // TODO(RR-3670): figure out how to handle missing chunks @@ -204,7 +215,7 @@ impl TransformFramesUi { } fn transform_ui( - ctx: &ViewerContext<'_>, + ctx: &AppContext<'_>, ui: &mut egui::Ui, transform: &TransformFrameInfo, is_current: bool, @@ -239,7 +250,7 @@ fn transform_ui( Item::from(source_entity.clone()) }; - ctx.command_sender() + ctx.command_sender .send_system(SystemCommand::set_selection(item)); } } diff --git a/crates/viewer/re_data_ui/src/video_ui.rs b/crates/viewer/re_data_ui/src/video_ui.rs index b1fda9c76890..494e35586fe5 100644 --- a/crates/viewer/re_data_ui/src/video_ui.rs +++ b/crates/viewer/re_data_ui/src/video_ui.rs @@ -13,8 +13,8 @@ use re_ui::UiExt as _; use re_ui::list_item::{self, PropertyContent}; use re_video::{FrameInfo, VideoDataDescription}; use re_viewer_context::{ - SharablePlayableVideoStream, UiLayout, VideoStreamCache, VideoStreamProcessingError, - ViewerContext, video_stream_time_from_query, + SharablePlayableVideoStream, StoreViewContext, UiLayout, VideoStreamCache, + VideoStreamProcessingError, video_stream_time_from_query, }; use crate::image_ui::texture_preview_size; @@ -310,7 +310,7 @@ fn timestamp_ui( } fn decoded_frame_ui<'a>( - ctx: &re_viewer_context::ViewerContext<'_>, + render_ctx: &re_renderer::RenderContext, ui: &mut egui::Ui, ui_layout: UiLayout, video: &re_renderer::video::Video, @@ -320,12 +320,7 @@ fn decoded_frame_ui<'a>( let player_stream_id = re_video::player::VideoPlayerStreamId(ui.id().with("video_player").value()); - match video.frame_at( - ctx.render_ctx(), - player_stream_id, - video_time, - get_video_buffer, - ) { + match video.frame_at(render_ctx, player_stream_id, video_time, get_video_buffer) { Ok(VideoFrameTexture { texture, decoder_delay_state, @@ -372,7 +367,7 @@ fn decoded_frame_ui<'a>( let response = if let Some(texture) = texture { crate::image_ui::texture_preview_ui( - ctx.render_ctx(), + render_ctx, ui, ui_layout, "video_preview", @@ -603,7 +598,7 @@ pub enum VideoUi { impl VideoUi { pub fn from_blob( - ctx: &ViewerContext<'_>, + ctx: &StoreViewContext<'_>, entity_path: &re_log_types::EntityPath, blob_row_id: RowId, blob_component_descriptor: &ComponentDescriptor, @@ -611,20 +606,19 @@ impl VideoUi { media_type: Option<&MediaType>, video_timestamp: Option, ) -> Option { - let result = - ctx.store_context - .caches - .entry(|c: &mut re_viewer_context::VideoAssetCache| { - let debug_name = entity_path.to_string(); - c.entry( - debug_name, - blob_row_id, - blob_component_descriptor.component, - blob, - media_type, - ctx.app_options().video_decoder_settings(), - ) - }); + let result = ctx + .caches + .entry(|c: &mut re_viewer_context::VideoAssetCache| { + let debug_name = entity_path.to_string(); + c.entry( + debug_name, + blob_row_id, + blob_component_descriptor.component, + blob, + media_type, + ctx.app_options.video_decoder_settings(), + ) + }); let certain_this_is_a_video = blob_component_descriptor.archetype == Some(archetypes::AssetVideo::name()); @@ -644,8 +638,7 @@ impl VideoUi { } pub fn from_components( - ctx: &ViewerContext<'_>, - query: &re_chunk_store::LatestAtQuery, + ctx: &StoreViewContext<'_>, entity_path: &re_log_types::EntityPath, descr: &ComponentDescriptor, ) -> Option { @@ -653,30 +646,24 @@ impl VideoUi { return None; } - let video_stream_result = ctx.store_context.caches.entry(|c: &mut VideoStreamCache| { + let video_stream_result = ctx.caches.entry(|c: &mut VideoStreamCache| { c.entry( - ctx.recording(), + ctx.db, entity_path, - query.timeline(), - ctx.app_options().video_decoder_settings(), + ctx.timeline_name(), + ctx.app_ctx.app_options.video_decoder_settings(), ) }); Some(Self::Stream(video_stream_result)) } - pub fn data_ui( - &self, - ctx: &ViewerContext<'_>, - ui: &mut egui::Ui, - ui_layout: UiLayout, - query: &re_chunk_store::LatestAtQuery, - ) { + pub fn data_ui(&self, ctx: &StoreViewContext<'_>, ui: &mut egui::Ui, ui_layout: UiLayout) { match self { Self::Stream(video_stream_result) => { video_stream_result_ui(ui, ui_layout, video_stream_result); - let storage_engine = ctx.store_context.recording.storage_engine(); + let storage_engine = ctx.db.storage_engine(); let get_chunk_array = |id| { let chunk = storage_engine .store() @@ -693,12 +680,19 @@ impl VideoUi { if let Ok(video) = video_stream_result { let video = video.read(); - let time = video_stream_time_from_query(query); - decoded_frame_ui(ctx, ui, ui_layout, &video.video_renderer, time, &|id| { - let buffer = get_chunk_array(re_sdk_types::ChunkId::from_tuid(id)); + let time = video_stream_time_from_query(&ctx.query()); + decoded_frame_ui( + ctx.render_ctx(), + ui, + ui_layout, + &video.video_renderer, + time, + &|id| { + let buffer = get_chunk_array(re_sdk_types::ChunkId::from_tuid(id)); - buffer.map(|b| b.as_slice()).unwrap_or(&[]) - }); + buffer.map(|b| b.as_slice()).unwrap_or(&[]) + }, + ); } } Self::Asset(video_result, timestamp, blob) => { @@ -721,12 +715,14 @@ impl VideoUi { } }); let video_time = re_viewer_context::video_timestamp_component_to_video_time( - ctx, + Some(ctx.time_ctrl), video_timestamp, video.data_descr().timescale, ); - decoded_frame_ui(ctx, ui, ui_layout, video, video_time, &|_| blob); + decoded_frame_ui(ctx.render_ctx(), ui, ui_layout, video, video_time, &|_| { + blob + }); } } } diff --git a/crates/viewer/re_dataframe_ui/Cargo.toml b/crates/viewer/re_dataframe_ui/Cargo.toml index 87ae5422ee1d..9f9e110cdb92 100644 --- a/crates/viewer/re_dataframe_ui/Cargo.toml +++ b/crates/viewer/re_dataframe_ui/Cargo.toml @@ -20,7 +20,6 @@ all-features = true [dependencies] re_arrow_util.workspace = true -re_chunk_store.workspace = true re_component_ui.workspace = true re_dataframe.workspace = true re_format.workspace = true diff --git a/crates/viewer/re_dataframe_ui/src/datafusion_table_widget.rs b/crates/viewer/re_dataframe_ui/src/datafusion_table_widget.rs index 5d33ee480517..2b5e4fcce60d 100644 --- a/crates/viewer/re_dataframe_ui/src/datafusion_table_widget.rs +++ b/crates/viewer/re_dataframe_ui/src/datafusion_table_widget.rs @@ -9,13 +9,13 @@ use egui_table::{CellInfo, HeaderCellInfo}; use itertools::Itertools as _; use re_format::{format_plural_s, format_uint}; use re_log::error; -use re_log_types::{EntryId, TimelineName, Timestamp}; +use re_log_types::{EntryId, Timestamp}; use re_sorbet::{ColumnDescriptorRef, SorbetSchema}; use re_ui::egui_ext::response_ext::ResponseExt as _; use re_ui::menu::menu_style; use re_ui::{UiExt as _, icons}; use re_viewer_context::{ - AsyncRuntimeHandle, SystemCommand, SystemCommandSender as _, ViewerContext, + AsyncRuntimeHandle, StoreViewContext, SystemCommand, SystemCommandSender as _, }; use crate::StreamingCacheTableProvider; @@ -230,7 +230,7 @@ impl<'a> DataFusionTableWidget<'a> { /// Display the table. pub fn show( self, - viewer_ctx: &ViewerContext<'_>, + viewer_ctx: &StoreViewContext<'_>, runtime: &AsyncRuntimeHandle, ui: &mut egui::Ui, ) -> TableStatus { @@ -352,7 +352,7 @@ impl<'a> DataFusionTableWidget<'a> { //TODO(ab): make the argument list less crazy #[expect(clippy::too_many_arguments)] fn table_ui( - viewer_ctx: &ViewerContext<'_>, + viewer_ctx: &StoreViewContext<'_>, runtime: &AsyncRuntimeHandle, ui: &mut egui::Ui, session_ctx: Arc, @@ -503,7 +503,7 @@ impl<'a> DataFusionTableWidget<'a> { fn bottom_bar_ui( ui: &mut Ui, - ctx: &ViewerContext<'_>, + ctx: &StoreViewContext<'_>, session_id: Id, total_rows: u64, visible_columns: usize, @@ -574,7 +574,7 @@ fn id_from_session_context_and_table( fn title_ui( ui: &mut egui::Ui, - ctx: &ViewerContext<'_>, + ctx: &StoreViewContext<'_>, table_config: Option<&mut TableConfig>, title: &str, url: Option<&str>, @@ -621,7 +621,7 @@ enum BottomBarAction { struct DataFusionTableDelegate<'a> { session_id: Id, - ctx: &'a ViewerContext<'a>, + ctx: &'a StoreViewContext<'a>, table_style: re_ui::TableStyle, query_result: &'a DataFusionQueryResult, migrated_fields: &'a Vec, @@ -810,17 +810,7 @@ impl egui_table::TableDelegate for DataFusionTableDelegate<'_> { { let column = &display_record_batch.columns()[col_index]; - // TODO(#9029): it is _very_ unfortunate that we must provide a fake timeline, but - // avoiding doing so needs significant refactoring work. - column.data_ui( - self.ctx, - ui, - &re_viewer_context::external::re_chunk_store::LatestAtQuery::latest( - TimelineName::new("unknown"), - ), - batch_index, - None, - ); + column.data_ui(self.ctx, ui, batch_index, None); } } diff --git a/crates/viewer/re_dataframe_ui/src/display_record_batch.rs b/crates/viewer/re_dataframe_ui/src/display_record_batch.rs index cc80494c9e1e..7ad4c39c73fb 100644 --- a/crates/viewer/re_dataframe_ui/src/display_record_batch.rs +++ b/crates/viewer/re_dataframe_ui/src/display_record_batch.rs @@ -10,7 +10,6 @@ use arrow::array::{ use arrow::buffer::{NullBuffer as ArrowNullBuffer, ScalarBuffer as ArrowScalarBuffer}; use arrow::datatypes::DataType as ArrowDataType; use re_arrow_util::ArrowArrayDowncastRef as _; -use re_chunk_store::LatestAtQuery; use re_component_ui::REDAP_THUMBNAIL_VARIANT; use re_dataframe::external::re_chunk::{TimeColumn, TimeColumnError}; use re_log_types::hash::Hash64; @@ -20,7 +19,7 @@ use re_sdk_types::components::{Blob, MediaType}; use re_sorbet::ColumnDescriptorRef; use re_types_core::{Component as _, DeserializationError, Loggable as _, RowId}; use re_ui::UiExt as _; -use re_viewer_context::{UiLayout, VariantName, ViewerContext}; +use re_viewer_context::{StoreViewContext, UiLayout, VariantName}; use crate::table_blueprint::ColumnBlueprint; @@ -211,9 +210,8 @@ impl DisplayComponentColumn { fn data_ui( &self, - ctx: &ViewerContext<'_>, + ctx: &StoreViewContext<'_>, ui: &mut egui::Ui, - latest_at_query: &LatestAtQuery, row_index: usize, instance_index: Option, ) { @@ -286,8 +284,6 @@ impl DisplayComponentColumn { ctx, ui, UiLayout::List, - latest_at_query, - ctx.recording(), &self.entity_path, &self.component_descr, row_id, @@ -371,9 +367,8 @@ impl DisplayColumn { /// [`Self::instance_count`]), nothing is displayed. pub fn data_ui( &self, - ctx: &ViewerContext<'_>, + ctx: &StoreViewContext<'_>, ui: &mut egui::Ui, - latest_at_query: &LatestAtQuery, row_index: usize, instance_index: Option, ) { @@ -426,7 +421,7 @@ impl DisplayColumn { } Self::Component(component_column) => { - component_column.data_ui(ctx, ui, latest_at_query, row_index, instance_index); + component_column.data_ui(ctx, ui, row_index, instance_index); } } } diff --git a/crates/viewer/re_dataframe_ui/tests/ascending_descending.rs b/crates/viewer/re_dataframe_ui/tests/ascending_descending.rs index 463d0662552d..29c89b9323d7 100644 --- a/crates/viewer/re_dataframe_ui/tests/ascending_descending.rs +++ b/crates/viewer/re_dataframe_ui/tests/ascending_descending.rs @@ -22,10 +22,10 @@ async fn test_no_sort() { let mut harness = test_context .setup_kittest_for_rendering_ui([600.0, 400.0]) .build_ui(|ui| { - test_context.run(&ui.ctx().clone(), |viewer_ctx| { + test_context.run_recording(&ui.ctx().clone(), |ctx| { let status = DataFusionTableWidget::new(Arc::clone(&session_context), table_ref) .title("No sort") - .show(viewer_ctx, &runtime_handle, ui); + .show(ctx, &runtime_handle, ui); table_status.lock().replace(status); }); @@ -45,14 +45,14 @@ async fn test_ascending() { let mut harness = test_context .setup_kittest_for_rendering_ui([600.0, 400.0]) .build_ui(|ui| { - test_context.run(&ui.ctx().clone(), |viewer_ctx| { + test_context.run_recording(&ui.ctx().clone(), |ctx| { let status = DataFusionTableWidget::new(Arc::clone(&session_context), table_ref) .title("Ascending") .initial_blueprint(TableBlueprint { sort_by: Some(SortBy::ascending("col")), ..Default::default() }) - .show(viewer_ctx, &runtime_handle, ui); + .show(ctx, &runtime_handle, ui); table_status.lock().replace(status); }); @@ -72,14 +72,14 @@ async fn test_descending() { let mut harness = test_context .setup_kittest_for_rendering_ui([600.0, 400.0]) .build_ui(|ui| { - test_context.run(&ui.ctx().clone(), |viewer_ctx| { + test_context.run_recording(&ui.ctx().clone(), |ctx| { let status = DataFusionTableWidget::new(Arc::clone(&session_context), table_ref) .title("Descending") .initial_blueprint(TableBlueprint { sort_by: Some(SortBy::descending("col")), ..Default::default() }) - .show(viewer_ctx, &runtime_handle, ui); + .show(ctx, &runtime_handle, ui); table_status.lock().replace(status); }); @@ -99,10 +99,10 @@ async fn test_column_menu_button() { let mut harness = test_context .setup_kittest_for_rendering_ui([600.0, 400.0]) .build_ui(|ui| { - test_context.run(&ui.ctx().clone(), |viewer_ctx| { + test_context.run_recording(&ui.ctx().clone(), |ctx| { let status = DataFusionTableWidget::new(Arc::clone(&session_context), table_ref) .title("Column menu button") - .show(viewer_ctx, &runtime_handle, ui); + .show(ctx, &runtime_handle, ui); table_status.lock().replace(status); }); diff --git a/crates/viewer/re_recording_panel/src/recording_panel_ui.rs b/crates/viewer/re_recording_panel/src/recording_panel_ui.rs index 4c349acdf487..73ec824527e6 100644 --- a/crates/viewer/re_recording_panel/src/recording_panel_ui.rs +++ b/crates/viewer/re_recording_panel/src/recording_panel_ui.rs @@ -443,9 +443,9 @@ fn dataset_entry_ui( SegmentData::Loaded { entity_db } => { let include_app_id = false; // we already show it in the parent item let response = entity_db_button_ui( - ctx, - ui, + &ctx.app_ctx, entity_db, + ui, UiLayout::SelectionPanel, include_app_id, ); @@ -616,9 +616,9 @@ fn app_id_section_ui(ctx: &ViewerContext<'_>, ui: &mut egui::Ui, local_app_id: & for recording_data in loaded_recordings { let include_app_id = false; // we already show it in the parent item let response = entity_db_button_ui( - ctx, - ui, + &ctx.app_ctx, recording_data.entity_db, + ui, UiLayout::SelectionPanel, include_app_id, ); @@ -634,7 +634,11 @@ fn app_id_section_ui(ctx: &ViewerContext<'_>, ui: &mut egui::Ui, local_app_id: & }; item_response = item_response.on_hover_ui(|ui| { - app_id.data_ui_recording(ctx, ui, UiLayout::Tooltip); + app_id.data_ui( + &ctx.active_recording_store_view_context(), + ui, + UiLayout::Tooltip, + ); }); ctx.handle_select_hover_drag_interactions(&item_response, item.clone(), false); diff --git a/crates/viewer/re_redap_browser/src/servers.rs b/crates/viewer/re_redap_browser/src/servers.rs index 28a729c3b151..3fec75ca37d0 100644 --- a/crates/viewer/re_redap_browser/src/servers.rs +++ b/crates/viewer/re_redap_browser/src/servers.rs @@ -15,7 +15,7 @@ use re_sorbet::ColumnDescriptorRef; use re_ui::alert::Alert; use re_ui::{UiExt as _, icons}; use re_viewer_context::{ - AppContext, AsyncRuntimeHandle, EditRedapServerModalCommand, ViewerContext, + AppContext, AsyncRuntimeHandle, EditRedapServerModalCommand, StoreViewContext, }; use crate::context::Context; @@ -132,7 +132,7 @@ impl Server { /// Central panel UI for when a server is selected. fn server_ui( &self, - viewer_ctx: &ViewerContext<'_>, + viewer_ctx: &StoreViewContext<'_>, ctx: &Context<'_>, ui: &mut egui::Ui, has_active_login_flow: bool, @@ -192,7 +192,7 @@ impl Server { fn dataset_entry_ui( &self, - viewer_ctx: &ViewerContext<'_>, + viewer_ctx: &StoreViewContext<'_>, ui: &mut egui::Ui, dataset: &Dataset, ) { @@ -252,7 +252,7 @@ impl Server { .show(viewer_ctx, &self.runtime, ui); } - fn table_entry_ui(&self, viewer_ctx: &ViewerContext<'_>, ui: &mut egui::Ui, table: &Table) { + fn table_entry_ui(&self, viewer_ctx: &StoreViewContext<'_>, ui: &mut egui::Ui, table: &Table) { re_dataframe_ui::DataFusionTableWidget::new(self.tables_session_ctx.clone(), table.name()) .title(table.name()) .url(re_uri::EntryUri::new(table.origin.clone(), table.id()).to_string()) @@ -261,7 +261,7 @@ impl Server { } fn error_ui( - viewer_ctx: &ViewerContext<'_>, + viewer_ctx: &StoreViewContext<'_>, ctx: &Context<'_>, ui: &mut egui::Ui, origin: &re_uri::Origin, @@ -732,7 +732,7 @@ impl RedapServers { pub fn server_central_panel_ui( &self, - viewer_ctx: &ViewerContext<'_>, + viewer_ctx: &StoreViewContext<'_>, ui: &mut egui::Ui, origin: &re_uri::Origin, ) { @@ -756,7 +756,7 @@ impl RedapServers { pub fn entry_ui( &self, - viewer_ctx: &ViewerContext<'_>, + viewer_ctx: &StoreViewContext<'_>, ui: &mut egui::Ui, active_entry: EntryId, ) { diff --git a/crates/viewer/re_selection_panel/src/defaults_ui.rs b/crates/viewer/re_selection_panel/src/defaults_ui.rs index 4ba6fa84e25a..2b7834a73d6c 100644 --- a/crates/viewer/re_selection_panel/src/defaults_ui.rs +++ b/crates/viewer/re_selection_panel/src/defaults_ui.rs @@ -11,8 +11,8 @@ use re_types_core::reflection::ComponentDescriptorExt as _; use re_ui::list_item::{LabelContent, ListItemContentButtonsExt as _}; use re_ui::{OnResponseExt as _, SyntaxHighlighting as _, UiExt as _}; use re_viewer_context::{ - ComponentUiTypes, QueryContext, SystemCommand, SystemCommandSender as _, UiLayout, ViewContext, - VisualizerCollection, blueprint_timeline, + ComponentUiTypes, QueryContext, StoreViewContext, SystemCommand, SystemCommandSender as _, + UiLayout, ViewContext, VisualizerCollection, blueprint_timeline, }; use re_viewport_blueprint::ViewBlueprint; @@ -38,12 +38,11 @@ pub fn view_components_defaults_section_ui( ui: &mut egui::Ui, view: &ViewBlueprint, ) { - let db = ctx.viewer_ctx.blueprint_db(); - let query = ctx.blueprint_query(); + let blueprint_store_view_ctx = ctx.viewer_ctx.blueprint_store_view_ctx(); // TODO(andreas): Components in `active_defaults` should be sorted by field order within each archetype. // Right now, they're just sorted by descriptor, which is not the same. - let active_defaults = active_defaults(ctx, view, db, query); + let active_defaults = active_defaults(&blueprint_store_view_ctx, view); let visualizers = ctx.new_visualizer_collection(); let visualized_components_by_archetype = visualized_components_by_archetype(&visualizers, ctx.viewer_ctx.app_options()); @@ -68,7 +67,7 @@ pub fn view_components_defaults_section_ui( ctx, ui, &view.defaults_path, - query, + &blueprint_store_view_ctx.query(), components_to_show_in_add_menu.unwrap_or_default(), ); }) @@ -85,7 +84,7 @@ override is specified.\n Click on the `+` button to add a new default value."; let body = |ui: &mut egui::Ui| { - active_default_ui(ctx, ui, &active_defaults, view, query, db); + active_default_ui(ctx, &blueprint_store_view_ctx, ui, &active_defaults, view); }; ui.section_collapsing_header("Component defaults") .with_button(add_button) @@ -95,18 +94,17 @@ Click on the `+` button to add a new default value."; fn active_default_ui( ctx: &ViewContext<'_>, + blueprint_store_view_ctx: &StoreViewContext<'_>, ui: &mut egui::Ui, active_defaults: &BTreeMap, view: &ViewBlueprint, - query: &LatestAtQuery, - db: &re_entity_db::EntityDb, ) { let query_context = QueryContext { view_ctx: ctx, target_entity_path: &view.defaults_path, instruction_id: None, archetype_name: None, - query: query.clone(), + query: blueprint_store_view_ctx.query().clone(), }; re_ui::list_item::list_item_scope(ui, "defaults", |ui| { @@ -119,7 +117,8 @@ fn active_default_ui( let mut previous_archetype_name = None; for (component, default_value) in active_defaults { - let Some(component_descr) = db + let Some(component_descr) = blueprint_store_view_ctx + .db .storage_engine() .store() .entity_component_descriptor(&view.defaults_path, *component) @@ -139,10 +138,11 @@ fn active_default_ui( let value_fn = |ui: &mut egui::Ui| { let allow_multiline = false; + let store_view_ctx = ctx.viewer_ctx.blueprint_store_view_ctx(); ctx.viewer_ctx.component_ui_registry().edit_ui_raw( &query_context, + &store_view_ctx, ui, - db, view.defaults_path.clone(), &component_descr, None, // No cache key. @@ -165,8 +165,8 @@ fn active_default_ui( if let Some(component_type) = component_descr.component_type { response.on_hover_ui(|ui| { - component_type.data_ui_recording( - ctx.viewer_ctx, + component_type.data_ui( + &ctx.viewer_ctx.active_recording_store_view_context(), ui, re_viewer_context::UiLayout::Tooltip, ); @@ -217,24 +217,22 @@ fn visualized_components_by_archetype( } fn active_defaults( - ctx: &ViewContext<'_>, + blueprint_ctx: &StoreViewContext<'_>, view: &ViewBlueprint, - db: &re_entity_db::EntityDb, - query: &LatestAtQuery, ) -> BTreeMap { // Cleared components should act as unset, so we filter out everything that's empty, // even if they are listed in `all_components`. - ctx.blueprint_db() - .storage_engine() + let engine = blueprint_ctx.db.storage_engine(); + + engine .store() .all_components_on_timeline(&blueprint_timeline(), &view.defaults_path) .unwrap_or_default() .into_iter() .filter_map(|component| { - let data = db - .storage_engine() + let data = engine .cache() - .latest_at(query, &view.defaults_path, [component]) + .latest_at(&blueprint_ctx.query(), &view.defaults_path, [component]) .component_batch_raw(component)?; (!data.is_empty()).then_some((component, data)) }) @@ -328,8 +326,8 @@ fn add_popup_ui( if ui .button(descriptor.archetype_field_name()) .on_hover_ui(|ui| { - entry.component_type.data_ui_recording( - ctx.viewer_ctx, + entry.component_type.data_ui( + &ctx.viewer_ctx.active_recording_store_view_context(), ui, UiLayout::Tooltip, ); diff --git a/crates/viewer/re_selection_panel/src/item_heading_no_breadcrumbs.rs b/crates/viewer/re_selection_panel/src/item_heading_no_breadcrumbs.rs index 587e655ac246..88f9950e70dc 100644 --- a/crates/viewer/re_selection_panel/src/item_heading_no_breadcrumbs.rs +++ b/crates/viewer/re_selection_panel/src/item_heading_no_breadcrumbs.rs @@ -27,7 +27,7 @@ pub fn item_title_list_item( item_heading_no_breadcrumbs(ctx, viewport, ui, item); }), ); - cursor_interact_with_selectable(ctx, response, item.clone()); + cursor_interact_with_selectable(&ctx.app_ctx, response, item.clone()); } /// Fully descriptive heading for an item, without any breadcrumbs. diff --git a/crates/viewer/re_selection_panel/src/item_heading_with_breadcrumbs.rs b/crates/viewer/re_selection_panel/src/item_heading_with_breadcrumbs.rs index a227360d364a..0c87d4343d3f 100644 --- a/crates/viewer/re_selection_panel/src/item_heading_with_breadcrumbs.rs +++ b/crates/viewer/re_selection_panel/src/item_heading_with_breadcrumbs.rs @@ -229,7 +229,7 @@ fn last_part_of_item_heading( if let Some(tooltip) = tooltip { response = response.on_hover_text(tooltip); } - cursor_interact_with_selectable(ctx, response, item.clone()); + cursor_interact_with_selectable(&ctx.app_ctx, response, item.clone()); } /// The breadcrumbs of containers and views in the viewport. @@ -257,7 +257,7 @@ fn viewport_breadcrumbs( if let Some(tooltip) = tooltip { response = response.on_hover_text(tooltip); } - cursor_interact_with_selectable(ctx, response, item); + cursor_interact_with_selectable(&ctx.app_ctx, response, item); separator_icon_ui(ui); } @@ -325,7 +325,7 @@ fn entity_path_breadcrumbs( } else { Item::from(full_entity_path) }; - cursor_interact_with_selectable(ctx, response, item); + cursor_interact_with_selectable(&ctx.app_ctx, response, item); separator_icon_ui(ui); } diff --git a/crates/viewer/re_selection_panel/src/item_title.rs b/crates/viewer/re_selection_panel/src/item_title.rs index b7b661e37983..b56ac1c06ad4 100644 --- a/crates/viewer/re_selection_panel/src/item_title.rs +++ b/crates/viewer/re_selection_panel/src/item_title.rs @@ -1,6 +1,6 @@ use egui::WidgetText; use re_chunk::EntityPath; -use re_data_ui::item_ui::{guess_instance_path_icon, guess_query_and_db_for_selected_entity}; +use re_data_ui::item_ui::guess_instance_path_icon; use re_entity_db::InstancePath; use re_log_types::{ComponentPath, TableId}; use re_sdk_types::archetypes::RecordingInfo; @@ -20,8 +20,9 @@ pub fn is_component_static(ctx: &ViewerContext<'_>, component_path: &ComponentPa entity_path, component, } = component_path; - let (_query, db) = guess_query_and_db_for_selected_entity(ctx, entity_path); - db.storage_engine() + ctx.guess_store_view_context_for_entity(entity_path) + .db + .storage_engine() .store() .entity_has_static_component(entity_path, *component) } diff --git a/crates/viewer/re_selection_panel/src/selection_panel.rs b/crates/viewer/re_selection_panel/src/selection_panel.rs index e1c1e05da92c..4e80197f78f4 100644 --- a/crates/viewer/re_selection_panel/src/selection_panel.rs +++ b/crates/viewer/re_selection_panel/src/selection_panel.rs @@ -2,9 +2,7 @@ use egui::{NumExt as _, TextBuffer, WidgetInfo, WidgetType}; use egui_tiles::ContainerKind; use re_context_menu::{SelectionUpdateBehavior, context_menu_ui_for_item}; use re_data_ui::DataUi; -use re_data_ui::item_ui::{ - self, cursor_interact_with_selectable, guess_query_and_db_for_selected_entity, -}; +use re_data_ui::item_ui::{self, cursor_interact_with_selectable}; use re_entity_db::{EntityPath, InstancePath}; use re_log_types::{ComponentPath, EntityPathFilter, EntityPathSubs, ResolvedEntityPathFilter}; use re_sdk_types::blueprint::components::VisualizerInstructionId; @@ -17,9 +15,9 @@ use re_ui::{ }; use re_viewer_context::{ BlueprintContext as _, ContainerId, Contents, DataQueryResult, DataResult, - DataResultInteractionAddress, HoverHighlight, Item, RecommendedVisualizers, SystemCommand, - SystemCommandSender as _, TimeControlCommand, UiLayout, ViewContext, ViewId, ViewStates, - ViewSystemIdentifier, ViewerContext, VisualizerInstruction, VisualizerViewReport, + DataResultInteractionAddress, HoverHighlight, Item, RecommendedVisualizers, StoreViewContext, + SystemCommand, SystemCommandSender as _, TimeControlCommand, UiLayout, ViewContext, ViewId, + ViewStates, ViewSystemIdentifier, ViewerContext, VisualizerInstruction, VisualizerViewReport, contents_name_style, icon_for_container_kind, }; use re_viewport_blueprint::ViewportBlueprint; @@ -35,6 +33,7 @@ use crate::visible_time_range_ui::{ use crate::visualizer_ui::visualizer_ui; // --- + fn default_selection_panel_width(screen_width: f32) -> f32 { (0.45 * screen_width).min(300.0).round() } @@ -188,8 +187,8 @@ impl SelectionPanel { component, } = component_path; - let (query, db) = guess_query_and_db_for_selected_entity(ctx, entity_path); - let engine = db.storage_engine(); + let store_view_ctx = ctx.guess_store_view_context_for_entity(entity_path); + let engine = store_view_ctx.db.storage_engine(); let component_descriptor = engine .store() .entity_component_descriptor(entity_path, *component) @@ -198,10 +197,9 @@ impl SelectionPanel { let is_static = engine .store() .entity_has_static_component(entity_path, component_descriptor.component); - ui.list_item_flat_noninteractive(PropertyContent::new("Parent entity").value_fn( |ui, _| { - item_ui::entity_path_parts_buttons(ctx, &query, db, ui, None, entity_path); + item_ui::entity_path_parts_buttons(&store_view_ctx, ui, None, entity_path); }, )); @@ -269,15 +267,13 @@ impl SelectionPanel { } Item::InstancePath(instance_path) => { - let (query, db) = - guess_query_and_db_for_selected_entity(ctx, &instance_path.entity_path); + let store_view_ctx = + ctx.guess_store_view_context_for_entity(&instance_path.entity_path); ui.list_item_flat_noninteractive(PropertyContent::new("Entity path").value_fn( |ui, _| { item_ui::entity_path_parts_buttons( - ctx, - &query, - db, + &store_view_ctx, ui, None, &instance_path.entity_path, @@ -311,15 +307,12 @@ impl SelectionPanel { instance_path, visualizer: _, }) => { + let store_view_ctx = + ctx.guess_store_view_context_for_entity(&instance_path.entity_path); ui.list_item_flat_noninteractive(PropertyContent::new("Stream entity").value_fn( |ui, _| { - let (query, db) = - guess_query_and_db_for_selected_entity(ctx, &instance_path.entity_path); - item_ui::entity_path_parts_buttons( - ctx, - &query, - db, + &store_view_ctx, ui, None, &instance_path.entity_path, @@ -332,7 +325,7 @@ impl SelectionPanel { |ui, _| { let response = ui.button(instance_path.instance.to_string()); cursor_interact_with_selectable( - ctx, + &ctx.app_ctx, response, Item::from(instance_path.clone()), ); @@ -366,24 +359,24 @@ impl SelectionPanel { _ => {} } - let (query, db) = if let Some(entity_path) = item.entity_path() { - guess_query_and_db_for_selected_entity(ctx, entity_path) + let store_view_ctx = if let Some(entity_path) = item.entity_path() { + ctx.guess_store_view_context_for_entity(entity_path) } else { - (ctx.current_query(), ctx.recording()) + ctx.active_recording_store_view_context() }; if let Some(data_ui_item) = data_section_ui(item) { ui.section_collapsing_header("Data").show(ui, |ui| { // TODO(#6075): Because `list_item_scope` changes it. Temporary until everything is `ListItem`. ui.spacing_mut().item_spacing.y = ui.global_style().spacing.item_spacing.y; - data_ui_item.data_ui(ctx, ui, ui_layout, &query, db); + data_ui_item.data_ui(&store_view_ctx, ui, ui_layout); }); } match item { Item::StoreId(_) => { ui.section_collapsing_header("Properties").show(ui, |ui| { - show_recording_properties(ctx, db, &query, ui, ui_layout); + show_recording_properties(&store_view_ctx, ui, ui_layout); }); } @@ -834,15 +827,14 @@ To learn more about coordinate frames, see the [Spaces & Transforms](https://rer } fn show_recording_properties( - ctx: &ViewerContext<'_>, - db: &re_entity_db::EntityDb, - query: &re_chunk::LatestAtQuery, + store_view_ctx: &StoreViewContext<'_>, ui: &mut egui::Ui, ui_layout: UiLayout, ) { re_tracing::profile_function!(); - let mut property_entities = db + let mut property_entities = store_view_ctx + .db .sorted_entity_paths() .filter_map(|entity_path| entity_path.strip_prefix(&EntityPath::properties())) .collect::>(); @@ -870,7 +862,7 @@ fn show_recording_properties( ui.label(entity_path.syntax_highlighted(ui.style())); }); } - entity_path.data_ui(ctx, ui, ui_layout, query, db); + entity_path.data_ui(store_view_ctx, ui, ui_layout); } }); } @@ -1104,7 +1096,7 @@ fn view_button( contents_name_style(&view_name), ) .on_hover_text(format!("{} view", class.display_name())); - item_ui::cursor_interact_with_selectable(ctx, response, item) + item_ui::cursor_interact_with_selectable(&ctx.app_ctx, response, item) } /// Display a list of all the views an entity appears in. @@ -1117,7 +1109,7 @@ fn list_existing_data_blueprints( let views_with_path = viewport.views_containing_entity_path(ctx, instance_path.entity_path.hash()); - let (query, db) = guess_query_and_db_for_selected_entity(ctx, &instance_path.entity_path); + let store_view_ctx = ctx.guess_store_view_context_for_entity(&instance_path.entity_path); if views_with_path.is_empty() { ui.weak("(Not shown in any view)"); @@ -1140,9 +1132,7 @@ fn list_existing_data_blueprints( let include_subtree = false; item_ui::instance_hover_card_ui( ui, - ctx, - &query, - db, + &store_view_ctx, instance_path, include_subtree, ); diff --git a/crates/viewer/re_selection_panel/src/view_entity_picker.rs b/crates/viewer/re_selection_panel/src/view_entity_picker.rs index dd03534039b9..e3e255c2602b 100644 --- a/crates/viewer/re_selection_panel/src/view_entity_picker.rs +++ b/crates/viewer/re_selection_panel/src/view_entity_picker.rs @@ -187,7 +187,6 @@ fn add_entities_line_ui( ) { re_tracing::profile_function!(); - let query = ctx.current_query(); let entity_path = &entity_data.entity_path; let name = &entity_data.label; @@ -216,10 +215,9 @@ fn add_entities_line_ui( widget_text = widget_text.strong(); } + let store_view_ctx = ctx.active_recording_store_view_context(); let response = item_ui::instance_path_button_to( - ctx, - &query, - ctx.recording(), + &store_view_ctx, ui, Some(view.id), &InstancePath::entity_all(entity_path.clone()), diff --git a/crates/viewer/re_selection_panel/src/visualizer_ui.rs b/crates/viewer/re_selection_panel/src/visualizer_ui.rs index 2816055317a1..d3d31ac71f3b 100644 --- a/crates/viewer/re_selection_panel/src/visualizer_ui.rs +++ b/crates/viewer/re_selection_panel/src/visualizer_ui.rs @@ -315,7 +315,7 @@ fn visualizer_components( let multiline = false; if let TryShowEditUiResult::Shown { edited_value } = ctx.viewer_ctx.component_ui_registry().try_show_edit_ui( - ctx.viewer_ctx, + &ctx.viewer_ctx.blueprint_store_view_ctx(), ui, re_viewer_context::EditTarget { store_id: ctx.viewer_ctx.store_context.blueprint.store_id().clone(), @@ -341,12 +341,11 @@ fn visualizer_components( } } else { // Display the value without edit ui. + let store_view_ctx = ctx.viewer_ctx.active_recording_store_view_context(); ctx.viewer_ctx.component_ui_registry().component_ui_raw( - ctx.viewer_ctx, + &store_view_ctx, ui, UiLayout::List, - &store_query, - ctx.recording(), &data_result.entity_path, target_component_descr, current_value_row_id, @@ -424,7 +423,11 @@ fn visualizer_components( if let Some(component_type) = target_component_descr.component_type { response.on_hover_ui(|ui| { // TODO(andreas): Add data ui for component descr? - component_type.data_ui_recording(ctx.viewer_ctx, ui, UiLayout::Tooltip); + component_type.data_ui( + &ctx.viewer_ctx.active_recording_store_view_context(), + ui, + UiLayout::Tooltip, + ); }); } @@ -813,12 +816,11 @@ fn source_component_item_ui( ui.label(format!("{} values", re_format::format_uint(num_values))); } else { let viewer_ctx = mapping_ctx.viewer_ctx(); + let store_view_ctx = viewer_ctx.active_recording_store_view_context(); viewer_ctx.component_ui_registry().component_ui_raw( - viewer_ctx, + &store_view_ctx, ui, UiLayout::List, - &mapping_ctx.query_ctx.query, - mapping_ctx.view_ctx().recording(), &mapping_ctx.data_result.entity_path, mapping_ctx.target_component_descr, None, // row id doesn't matter since we're only showing a single value here. diff --git a/crates/viewer/re_test_context/src/lib.rs b/crates/viewer/re_test_context/src/lib.rs index fa1c4ea3ced7..f116d1d37cfb 100644 --- a/crates/viewer/re_test_context/src/lib.rs +++ b/crates/viewer/re_test_context/src/lib.rs @@ -22,9 +22,9 @@ use re_ui::Help; use re_viewer_context::{ AppContext, AppOptions, ApplicationSelectionState, BlueprintContext, CommandReceiver, CommandSender, ComponentUiRegistry, DataQueryResult, FallbackProviderRegistry, Item, - ItemCollection, NeedsRepaint, Route, StoreHub, SystemCommand, SystemCommandSender as _, - TimeControl, TimeControlCommand, ViewClass, ViewClassRegistry, ViewId, ViewStates, - ViewerContext, blueprint_timeline, command_channel, + ItemCollection, NeedsRepaint, Route, StoreHub, StoreViewContext, SystemCommand, + SystemCommandSender as _, TimeControl, TimeControlCommand, ViewClass, ViewClassRegistry, + ViewId, ViewStates, ViewerContext, blueprint_timeline, command_channel, }; pub mod external { @@ -584,6 +584,20 @@ impl TestContext { .expect("registering a class should succeed"); } + /// Run the provided closure with a [`StoreViewContext`] produced by the [`Self`]. + /// + /// IMPORTANT: call [`Self::handle_system_commands`] after calling this function if your test + /// relies on system commands. + pub fn run_recording( + &self, + egui_ctx: &egui::Context, + func: impl FnOnce(&StoreViewContext<'_>), + ) { + self.run(egui_ctx, |viewer_ctx| { + func(&viewer_ctx.active_recording_store_view_context()); + }); + } + /// Run the provided closure with a [`ViewerContext`] produced by the [`Self`]. /// /// IMPORTANT: call [`Self::handle_system_commands`] after calling this function if your test @@ -655,6 +669,8 @@ impl TestContext { connection_registry: &self.connection_registry, storage_context: &storage_context, + active_store_context: Some(&store_context), // TODO(RR-3033): should sometimes be `None` + component_ui_registry: &self.component_ui_registry, route: &Route::LocalRecording { @@ -664,6 +680,8 @@ impl TestContext { selection_state: &selection_state, focused_item: &focused_item, drag_and_drop_manager: &drag_and_drop_manager, + active_time_ctrl: Some(&self.time_ctrl.read()), + connected_receivers: &Default::default(), auth_context: None, }, component_fallback_registry: &self.component_fallback_registry, diff --git a/crates/viewer/re_time_panel/Cargo.toml b/crates/viewer/re_time_panel/Cargo.toml index e02dff8a0d4b..6ac8c565797e 100644 --- a/crates/viewer/re_time_panel/Cargo.toml +++ b/crates/viewer/re_time_panel/Cargo.toml @@ -23,16 +23,17 @@ default = [] testing = ["smallvec/serde"] [dependencies] +re_chunk.workspace = true re_chunk_store.workspace = true re_context_menu.workspace = true re_data_ui.workspace = true re_entity_db.workspace = true re_format.workspace = true re_int_histogram.workspace = true -re_log.workspace = true re_log_types.workspace = true -re_tracing.workspace = true +re_log.workspace = true re_sdk_types.workspace = true +re_tracing.workspace = true re_ui.workspace = true re_viewer_context.workspace = true re_viewport_blueprint.workspace = true diff --git a/crates/viewer/re_time_panel/benches/bench_density_graph.rs b/crates/viewer/re_time_panel/benches/bench_density_graph.rs index 0eed48cd8603..038cdf5813e0 100644 --- a/crates/viewer/re_time_panel/benches/bench_density_graph.rs +++ b/crates/viewer/re_time_panel/benches/bench_density_graph.rs @@ -52,12 +52,12 @@ fn run(b: &mut Bencher<'_, WallTime>, config: DensityGraphBuilderConfig, entry: b.iter(|| { black_box(build_density_graph( + &db, + timeline.name(), ui, &time_ranges_ui, row_rect, - &db, &item, - timeline.name(), config, )); }); diff --git a/crates/viewer/re_time_panel/src/data_density_graph.rs b/crates/viewer/re_time_panel/src/data_density_graph.rs index 6eff0ae7d80b..270fb1bf7f55 100644 --- a/crates/viewer/re_time_panel/src/data_density_graph.rs +++ b/crates/viewer/re_time_panel/src/data_density_graph.rs @@ -7,11 +7,13 @@ use std::sync::Arc; use egui::epaint::Vertex; use egui::{Color32, NumExt as _, Rangef, Rect, Shape, lerp, pos2, remap}; +use re_chunk::TimelineName; use re_chunk_store::{ChunkTrackingMode, RangeQuery}; +use re_entity_db::EntityDb; use re_log::debug_assert; -use re_log_types::{AbsoluteTimeRange, ComponentPath, TimeInt, TimeReal, TimelineName}; +use re_log_types::{AbsoluteTimeRange, ComponentPath, TimeInt, TimeReal}; use re_ui::UiExt as _; -use re_viewer_context::{Item, TimeControl, UiLayout, ViewerContext}; +use re_viewer_context::{AppContext, Item, StoreViewContext, UiLayout}; use super::time_ranges_ui::TimeRangesUi; use crate::recursive_chunks_per_timeline_subscriber::PathRecursiveChunksPerTimelineStoreSubscriber; @@ -424,14 +426,15 @@ fn smooth(buckets: &[Bucket]) -> Vec { /// `paint_fully_loaded_ranges` indicates if fully loaded ranges from the rrd /// manifest should be filled in. pub fn paint_loaded_indicator_bar( + ctx: &StoreViewContext<'_>, ui: &egui::Ui, time_ranges_ui: &TimeRangesUi, - db: &re_entity_db::EntityDb, - time_ctrl: &TimeControl, y: f32, full_x_range: Rangef, paint_fully_loaded_ranges: bool, ) { + let StoreViewContext { db, time_ctrl, .. } = ctx; + let Some(timeline) = time_ctrl.timeline() else { return; }; @@ -516,12 +519,9 @@ pub fn paint_loaded_indicator_bar( } /// Returns the hovered time, if any. -#[expect(clippy::too_many_arguments)] pub fn data_density_graph_ui( data_density_graph_painter: &mut DataDensityGraphPainter, - ctx: &ViewerContext<'_>, - time_ctrl: &TimeControl, - db: &re_entity_db::EntityDb, + ctx: &StoreViewContext<'_>, time_area_painter: &egui::Painter, ui: &egui::Ui, time_ranges_ui: &TimeRangesUi, @@ -530,21 +530,21 @@ pub fn data_density_graph_ui( ) -> Option { re_tracing::profile_function!(); - let num_missing_chunk_ids_before = db.storage_engine().store().num_missing_chunk_ids(); + let num_missing_chunk_ids_before = ctx.db.storage_engine().store().num_missing_chunk_ids(); let mut data = build_density_graph( + ctx.db, + &ctx.timeline_name(), ui, time_ranges_ui, row_rect, - db, item, - time_ctrl.timeline()?.name(), DensityGraphBuilderConfig::default(), ); re_log::debug_assert_eq!( num_missing_chunk_ids_before, - db.storage_engine().store().num_missing_chunk_ids(), + ctx.db.storage_engine().store().num_missing_chunk_ids(), "The density graph should not request new chunks. (This assert assumes single-threaded access to the store)." ); @@ -568,12 +568,12 @@ pub fn data_density_graph_ui( } pub fn build_density_graph<'a>( + db: &EntityDb, + timeline: &TimelineName, ui: &'a egui::Ui, time_ranges_ui: &'a TimeRangesUi, row_rect: Rect, - db: &re_entity_db::EntityDb, item: &TimePanelItem, - timeline: &TimelineName, config: DensityGraphBuilderConfig, ) -> DensityGraphBuilder<'a> { re_tracing::profile_function!(); @@ -764,28 +764,31 @@ impl Default for DensityGraphBuilderConfig { } pub fn show_row_ids_tooltip( - ctx: &ViewerContext<'_>, + ctx: &StoreViewContext<'_>, ui: &mut egui::Ui, - time_ctrl: &TimeControl, - db: &re_entity_db::EntityDb, item: &TimePanelItem, at_time: TimeInt, ) { use re_data_ui::DataUi as _; let ui_layout = UiLayout::Tooltip; - let query = re_chunk_store::LatestAtQuery::new(*time_ctrl.timeline_name(), at_time); let TimePanelItem { entity_path, component, } = item; + let mut time_ctrl = ctx.time_ctrl.clone(); + time_ctrl.set_time_ad_hoc(at_time.into()); + let ctx = StoreViewContext { + time_ctrl: &time_ctrl, + ..ctx.clone() + }; + if let Some(component) = *component { - ComponentPath::new(entity_path.clone(), component).data_ui(ctx, ui, ui_layout, &query, db); + ComponentPath::new(entity_path.clone(), component).data_ui(&ctx, ui, ui_layout); } else { - re_entity_db::InstancePath::entity_all(entity_path.clone()) - .data_ui(ctx, ui, ui_layout, &query, db); + re_entity_db::InstancePath::entity_all(entity_path.clone()).data_ui(&ctx, ui, ui_layout); } } @@ -899,7 +902,7 @@ impl<'a> DensityGraphBuilder<'a> { } } -fn graph_color(ctx: &ViewerContext<'_>, item: &Item, ui: &egui::Ui) -> Color32 { +fn graph_color(ctx: &AppContext<'_>, item: &Item, ui: &egui::Ui) -> Color32 { let is_selected = ctx.selection().contains_item(item); if is_selected { diff --git a/crates/viewer/re_time_panel/src/streams_tree_data.rs b/crates/viewer/re_time_panel/src/streams_tree_data.rs index c2b0b8d94ae0..0b5d34e6a2bb 100644 --- a/crates/viewer/re_time_panel/src/streams_tree_data.rs +++ b/crates/viewer/re_time_panel/src/streams_tree_data.rs @@ -7,7 +7,7 @@ use re_entity_db::{EntityTree, InstancePath}; use re_log_types::{ComponentPath, EntityPath}; use re_sdk_types::ComponentDescriptor; use re_ui::filter_widget::{FilterMatcher, PathRanges}; -use re_viewer_context::{CollapseScope, Item, ViewerContext, VisitorControlFlow}; +use re_viewer_context::{AppContext, CollapseScope, Item, ViewerContext, VisitorControlFlow}; use smallvec::SmallVec; use crate::time_panel::TimePanelSource; @@ -69,7 +69,7 @@ impl StreamsTreeData { /// components are visited. pub fn visit( &self, - viewer_context: &ViewerContext<'_>, + ctx: &AppContext<'_>, entity_db: &re_entity_db::EntityDb, mut visitor: impl FnMut(EntityOrComponentData<'_>) -> VisitorControlFlow, ) -> ControlFlow { @@ -77,7 +77,7 @@ impl StreamsTreeData { let store = engine.store(); for child in &self.children { - child.visit(viewer_context, store, &mut visitor)?; + child.visit(ctx, store, &mut visitor)?; } ControlFlow::Continue(()) @@ -213,18 +213,16 @@ impl EntityData { /// Visit this entity, included its components in the provided store. pub fn visit( &self, - viewer_context: &ViewerContext<'_>, + ctx: &AppContext<'_>, store: &ChunkStore, visitor: &mut impl FnMut(EntityOrComponentData<'_>) -> VisitorControlFlow, ) -> ControlFlow { if visitor(EntityOrComponentData::Entity(self)).visit_children()? { for child in &self.children { - child.visit(viewer_context, store, visitor)?; + child.visit(ctx, store, visitor)?; } - for (_, component_descriptors) in - components_for_entity(viewer_context, store, &self.entity_path) - { + for (_, component_descriptors) in components_for_entity(ctx, store, &self.entity_path) { for component_descriptor in component_descriptors { // these cannot have children let _ = visitor(EntityOrComponentData::Component { @@ -252,13 +250,13 @@ impl EntityData { /// Lists the components to be displayed for the given entity pub fn components_for_entity( - viewer_context: &ViewerContext<'_>, + ctx: &AppContext<'_>, store: &ChunkStore, entity_path: &EntityPath, ) -> ArchetypeComponentMap { if let Some(components) = store.all_components_for_entity(entity_path) { sorted_component_list_by_archetype_for_ui( - viewer_context.reflection(), + ctx.reflection, components .iter() .filter_map(|component| store.entity_component_descriptor(entity_path, *component)), diff --git a/crates/viewer/re_time_panel/src/time_panel.rs b/crates/viewer/re_time_panel/src/time_panel.rs index 1ca62639c508..a6d27357de51 100644 --- a/crates/viewer/re_time_panel/src/time_panel.rs +++ b/crates/viewer/re_time_panel/src/time_panel.rs @@ -8,8 +8,7 @@ use egui::{ }; use re_context_menu::{SelectionUpdateBehavior, context_menu_ui_for_item_with_context}; use re_data_ui::DataUi as _; -use re_data_ui::item_ui::guess_instance_path_icon; -use re_entity_db::{EntityDb, InstancePath}; +use re_entity_db::InstancePath; use re_log_types::{ AbsoluteTimeRange, ApplicationId, ComponentPath, EntityPath, TimeInt, TimeReal, }; @@ -22,9 +21,9 @@ use re_ui::{ }; use re_viewer_context::open_url::ViewerOpenUrl; use re_viewer_context::{ - CollapseScope, HoverHighlight, Item, ItemCollection, ItemContext, SystemCommand, - SystemCommandSender as _, TimeControl, TimeControlCommand, TimeView, UiLayout, ViewerContext, - VisitorControlFlow, + AppContext, AppOptions, CollapseScope, HoverHighlight, Item, ItemCollection, ItemContext, + StoreViewContext, SystemCommand, SystemCommandSender as _, TimeControl, TimeControlCommand, + TimeView, UiLayout, ViewerContext, VisitorControlFlow, }; use re_viewport_blueprint::ViewportBlueprint; @@ -191,13 +190,11 @@ impl TimePanel { self.filter_state.activate(query); } - #[expect(clippy::too_many_arguments)] pub fn show_panel( &mut self, - ctx: &ViewerContext<'_>, + viewer_ctx: &ViewerContext<'_>, + store_ctx: &StoreViewContext<'_>, viewport_blueprint: &ViewportBlueprint, - entity_db: &re_entity_db::EntityDb, - time_ctrl: &TimeControl, ui: &mut egui::Ui, state: PanelState, mut panel_frame: egui::Frame, @@ -209,9 +206,9 @@ impl TimePanel { let tokens = ui.tokens(); // Invalidate the filter widget if the store id has changed. - if self.filter_state_app_id.as_ref() != Some(ctx.store_context.application_id()) { + if self.filter_state_app_id.as_ref() != Some(viewer_ctx.store_context.application_id()) { self.filter_state = Default::default(); - self.filter_state_app_id = Some(ctx.store_context.application_id().clone()); + self.filter_state_app_id = Some(viewer_ctx.store_context.application_id().clone()); } self.data_density_graph_painter.begin_frame(ui.ctx()); @@ -262,15 +259,14 @@ impl TimePanel { ui.horizontal(|ui| { ui.spacing_mut().interact_size = Vec2::splat(tokens.top_bar_height()); ui.visuals_mut().button_frame = true; - self.collapsed_ui(entity_db, ctx, time_ctrl, ui, &mut time_commands); + self.collapsed_ui(store_ctx, viewer_ctx, ui, &mut time_commands); }); } else { // Expanded: self.show_expanded_with_header( - ctx, - time_ctrl, + viewer_ctx, + store_ctx, viewport_blueprint, - entity_db, ui, &mut time_commands, ); @@ -279,9 +275,10 @@ impl TimePanel { ); if !time_commands.is_empty() { - ctx.command_sender() + viewer_ctx + .command_sender() .send_system(SystemCommand::TimeControlCommands { - store_id: entity_db.store_id().clone(), + store_id: store_ctx.db.store_id().clone(), time_commands, }); } @@ -289,10 +286,9 @@ impl TimePanel { pub fn show_expanded_with_header( &mut self, - ctx: &ViewerContext<'_>, - time_ctrl: &TimeControl, + viewer_ctx: &ViewerContext<'_>, + store_ctx: &StoreViewContext<'_>, viewport_blueprint: &ViewportBlueprint, - entity_db: &EntityDb, ui: &mut Ui, time_commands: &mut Vec, ) { @@ -310,7 +306,7 @@ impl TimePanel { ui.horizontal(|ui| { ui.spacing_mut().interact_size = Vec2::splat(tokens.top_bar_height()); ui.visuals_mut().button_frame = true; - self.top_row_ui(ctx, time_ctrl, entity_db, ui, time_commands); + self.top_row_ui(store_ctx, ui, time_commands); }); }) .response @@ -330,10 +326,9 @@ impl TimePanel { streams_frame.inner_margin.left = margin.left; streams_frame.show(ui, |ui| { self.expanded_ui( - ctx, - time_ctrl, + store_ctx, + viewer_ctx, viewport_blueprint, - entity_db, ui, time_commands, top_row_rect.bottom(), @@ -344,14 +339,16 @@ impl TimePanel { fn collapsed_ui( &mut self, - entity_db: &re_entity_db::EntityDb, - ctx: &ViewerContext<'_>, - time_ctrl: &TimeControl, + store_ctx: &StoreViewContext<'_>, + viewer_ctx: &ViewerContext<'_>, ui: &mut egui::Ui, time_commands: &mut Vec, ) { re_tracing::profile_function!(); + let time_ctrl = store_ctx.time_ctrl; + let entity_db = store_ctx.db; + let loading_text = if entity_db.is_currently_downloading_manifest() { Some("Downloading meta-data") } else if time_ctrl.is_pending() { @@ -396,13 +393,7 @@ impl TimePanel { ui, time_commands, ); - self.collapsed_time_marker_and_time( - ui, - ctx, - time_ctrl, - entity_db, - time_commands, - ); + self.collapsed_time_marker_and_time(viewer_ctx, store_ctx, ui, time_commands); }); }); } else { @@ -427,24 +418,22 @@ impl TimePanel { self.time_control_ui.fps_ui(time_ctrl, ui, time_commands); } - self.collapsed_time_marker_and_time(ui, ctx, time_ctrl, entity_db, time_commands); + self.collapsed_time_marker_and_time(viewer_ctx, store_ctx, ui, time_commands); } } - #[expect(clippy::too_many_arguments)] fn expanded_ui( &mut self, - ctx: &ViewerContext<'_>, - time_ctrl: &TimeControl, + store_ctx: &StoreViewContext<'_>, + viewer_ctx: &ViewerContext<'_>, viewport_blueprint: &ViewportBlueprint, - entity_db: &re_entity_db::EntityDb, ui: &mut egui::Ui, time_commands: &mut Vec, top_row_y: f32, ) { re_tracing::profile_function!(); - if entity_db.is_currently_downloading_manifest() { + if store_ctx.db.is_currently_downloading_manifest() { ui.loading_screen_ui("Downloading meta-data", |ui| { let text = "Downloading meta-data"; ui.label(egui::RichText::from(text).heading().strong()); @@ -453,12 +442,12 @@ impl TimePanel { return; } - if time_ctrl.is_pending() { + if store_ctx.time_ctrl.is_pending() { ui.loading_screen_ui("Waiting for timeline", |ui| { - let text = format!("Waiting for timeline: {}", time_ctrl.timeline_name()); + let text = format!("Waiting for timeline: {}", store_ctx.timeline_name()); ui.label(egui::RichText::from(text).heading().strong()); - let timeline_count = entity_db.timelines().len(); + let timeline_count = store_ctx.db.timelines().len(); match timeline_count { 0 => {} @@ -513,10 +502,9 @@ impl TimePanel { let draw_loaded_ranges = true; data_density_graph::paint_loaded_indicator_bar( + store_ctx, ui, &self.time_ranges_ui, - entity_db, - time_ctrl, top_row_y, time_fg_x_range, draw_loaded_ranges, @@ -524,13 +512,12 @@ impl TimePanel { let side_margin = 26.0; // chosen so that the scroll bar looks approximately centered in the default gap self.time_ranges_ui = initialize_time_ranges_ui( - time_ctrl, - entity_db, + store_ctx, Rangef::new( time_fg_x_range.min + side_margin, time_fg_x_range.max - side_margin, ), - time_ctrl.time_view(), + store_ctx.time_ctrl.time_view(), ); let full_y_range = Rangef::new(ui.max_rect().top(), ui.max_rect().bottom()); @@ -579,7 +566,7 @@ impl TimePanel { let time_bg_area_painter = ui.painter().with_clip_rect(time_bg_area_rect); let time_area_painter = ui.painter().with_clip_rect(time_fg_area_rect); - if let Some(highlighted_range) = time_ctrl.highlighted_range { + if let Some(highlighted_range) = store_ctx.time_ctrl.highlighted_range { paint_range_highlight( highlighted_range, &self.time_ranges_ui, @@ -594,14 +581,14 @@ impl TimePanel { ui.visuals().widgets.noninteractive.bg_stroke, ); - if let Some(time_type) = time_ctrl.time_type() { + if let Some(time_type) = store_ctx.time_ctrl.time_type() { paint_ticks::paint_time_ranges_and_ticks( &self.time_ranges_ui, ui, &time_area_painter, timeline_rect.y_range(), time_type, - ctx.app_options().timestamp_format, + store_ctx.app_options().timestamp_format, ); } paint_time_ranges_gaps( @@ -611,8 +598,8 @@ impl TimePanel { full_y_range, ); time_selection_ui::loop_selection_ui( - ctx, - time_ctrl, + viewer_ctx, + store_ctx.time_ctrl, &self.time_ranges_ui, ui, &time_bg_area_painter, @@ -637,10 +624,9 @@ impl TimePanel { ui.full_span_scope(0.0..=time_x_left, |ui| { list_item::list_item_scope(ui, "streams_tree", |ui| { self.tree_ui( - ctx, - time_ctrl, + store_ctx, + viewer_ctx, viewport_blueprint, - entity_db, &time_area_response, &lower_time_area_painter, ui, @@ -672,9 +658,9 @@ impl TimePanel { // Put time-marker on top and last, so that you can always drag it self.time_marker_ui( + viewer_ctx, + store_ctx, ui, - ctx, - time_ctrl, Some(time_area_response), &time_area_painter, &timeline_rect, @@ -683,7 +669,7 @@ impl TimePanel { ); self.time_ranges_ui - .snap_time_control(time_ctrl, time_commands); + .snap_time_control(store_ctx.time_ctrl, time_commands); // remember where to show the time for next frame: self.prev_col_width = self.next_col_right - ui.min_rect().left(); @@ -693,10 +679,9 @@ impl TimePanel { #[expect(clippy::too_many_arguments)] fn tree_ui( &mut self, - ctx: &ViewerContext<'_>, - time_ctrl: &TimeControl, + store_ctx: &StoreViewContext<'_>, + viewer_ctx: &ViewerContext<'_>, viewport_blueprint: &ViewportBlueprint, - entity_db: &re_entity_db::EntityDb, time_area_response: &egui::Response, time_area_painter: &egui::Painter, ui: &mut egui::Ui, @@ -721,18 +706,17 @@ impl TimePanel { let streams_tree_data = crate::streams_tree_data::StreamsTreeData::from_source_and_filter( - ctx, + viewer_ctx, self.source, &filter_matcher, ); for child in &streams_tree_data.children { self.show_entity( - ctx, - time_ctrl, + store_ctx, + viewer_ctx, viewport_blueprint, &streams_tree_data, - entity_db, time_area_response, time_area_painter, child, @@ -747,11 +731,10 @@ impl TimePanel { #[expect(clippy::too_many_arguments)] fn show_entity( &mut self, - ctx: &ViewerContext<'_>, - time_ctrl: &TimeControl, + store_ctx: &StoreViewContext<'_>, + viewer_ctx: &ViewerContext<'_>, viewport_blueprint: &ViewportBlueprint, streams_tree_data: &StreamsTreeData, - entity_db: &re_entity_db::EntityDb, time_area_response: &egui::Response, time_area_painter: &egui::Painter, entity_data: &EntityData, @@ -763,8 +746,8 @@ impl TimePanel { let entity_path = &entity_data.entity_path; let item = TimePanelItem::entity_path(entity_path.clone()); - let is_selected = ctx.selection().contains_item(&item.to_item()); - let is_item_hovered = ctx + let is_selected = store_ctx.selection().contains_item(&item.to_item()); + let is_item_hovered = store_ctx .selection_state() .highlight_for_ui_element(&item.to_item()) == HoverHighlight::Hovered; @@ -772,7 +755,7 @@ impl TimePanel { let collapse_scope = self.collapse_scope(); // Expand if one of the children is focused - let focused_entity_path = ctx + let focused_entity_path = store_ctx .focused_item() .as_ref() .and_then(|item| item.entity_path()); @@ -804,23 +787,22 @@ impl TimePanel { id, entity_data.default_open, list_item::LabelContent::new(format_matching_text( - ctx.egui_ctx(), + store_ctx.egui_ctx, &entity_data.label, entity_data.highlight_sections.iter().cloned(), None, )) - .with_icon(guess_instance_path_icon( - ctx, + .with_icon(re_data_ui::item_ui::instance_path_icon( + store_ctx, &InstancePath::from(entity_path.clone()), )) .truncate(false), |ui| { self.show_entity_contents( - ctx, - time_ctrl, + store_ctx, + viewer_ctx, viewport_blueprint, streams_tree_data, - entity_db, time_area_response, time_area_painter, entity_data, @@ -832,14 +814,7 @@ impl TimePanel { let response = response.on_hover_ui(|ui| { let include_subtree = true; - re_data_ui::item_ui::entity_hover_card_ui( - ui, - ctx, - &time_ctrl.current_query(), - entity_db, - entity_path, - include_subtree, - ); + re_data_ui::item_ui::entity_hover_card_ui(ui, store_ctx, entity_path, include_subtree); }); if Some(entity_path) == focused_entity_path { @@ -852,10 +827,10 @@ impl TimePanel { } self.handle_interactions_for_item( - ctx, + store_ctx, + viewer_ctx, viewport_blueprint, streams_tree_data, - entity_db, item.to_item(), &response, true, @@ -877,9 +852,9 @@ impl TimePanel { let is_visible = ui.is_rect_visible(full_width_rect); if is_visible { - let tree_has_data_in_current_timeline = entity_db.subtree_has_data_on_timeline( - &entity_db.storage_engine(), - time_ctrl.timeline_name(), + let tree_has_data_in_current_timeline = store_ctx.db.subtree_has_data_on_timeline( + &store_ctx.db.storage_engine(), + &store_ctx.timeline_name(), entity_path, ); if tree_has_data_in_current_timeline { @@ -888,14 +863,18 @@ impl TimePanel { response_rect.y_range(), ); - highlight_timeline_row(ui, ctx, time_area_painter, &item.to_item(), &row_rect); + highlight_timeline_row( + ui, + store_ctx.app_ctx, + time_area_painter, + &item.to_item(), + &row_rect, + ); // show the density graph only if that item is closed if is_closed { self.data_density_graph_ui( - ctx, - time_ctrl, - entity_db, + store_ctx, time_area_painter, ui, row_rect, @@ -911,11 +890,10 @@ impl TimePanel { #[expect(clippy::too_many_arguments)] fn show_entity_contents( &mut self, - ctx: &ViewerContext<'_>, - time_ctrl: &TimeControl, + store_ctx: &StoreViewContext<'_>, + viewer_ctx: &ViewerContext<'_>, viewport_blueprint: &ViewportBlueprint, streams_tree_data: &StreamsTreeData, - entity_db: &re_entity_db::EntityDb, time_area_response: &egui::Response, time_area_painter: &egui::Painter, entity_data: &EntityData, @@ -926,11 +904,10 @@ impl TimePanel { for child in &entity_data.children { self.show_entity( - ctx, - time_ctrl, + store_ctx, + viewer_ctx, viewport_blueprint, streams_tree_data, - entity_db, time_area_response, time_area_painter, child, @@ -940,10 +917,10 @@ impl TimePanel { } let entity_path = &entity_data.entity_path; - let engine = entity_db.storage_engine(); + let engine = store_ctx.db.storage_engine(); let store = engine.store(); - let components_by_archetype = components_for_entity(ctx, store, entity_path); + let components_by_archetype = components_for_entity(store_ctx.app_ctx, store, entity_path); let num_archetypes = components_by_archetype.len(); for (archetype, components) in components_by_archetype { if archetype.is_none() && num_archetypes == 1 { @@ -962,14 +939,15 @@ impl TimePanel { entity_path: entity_path.clone(), component: Some(component), }; - let timeline = time_ctrl.timeline_name(); + let timeline = store_ctx.timeline_name(); let response = ui .list_item() .render_offscreen(false) - .selected(ctx.selection().contains_item(&item.to_item())) + .selected(store_ctx.selection().contains_item(&item.to_item())) .force_hovered( - ctx.selection_state() + store_ctx + .selection_state() .highlight_for_ui_element(&item.to_item()) == HoverHighlight::Hovered, ) @@ -985,10 +963,10 @@ impl TimePanel { ); self.handle_interactions_for_item( - ctx, + store_ctx, + viewer_ctx, viewport_blueprint, streams_tree_data, - entity_db, item.to_item(), &response, false, @@ -1001,7 +979,7 @@ impl TimePanel { store.num_physical_static_events_for_component(entity_path, component); let num_temporal_messages = store .num_physical_temporal_events_for_component_on_timeline( - time_ctrl.timeline_name(), + &store_ctx.timeline_name(), entity_path, component, ); @@ -1048,12 +1026,7 @@ impl TimePanel { // Conversely, temporal components change over time, and so showing a specific instance here // can be confusing. if is_static { - let query = re_chunk_store::LatestAtQuery::new( - *time_ctrl.timeline_name(), - TimeInt::MAX, - ); - let ui_layout = UiLayout::Tooltip; - component_path.data_ui(ctx, ui, ui_layout, &query, entity_db); + component_path.data_ui(store_ctx, ui, UiLayout::Tooltip); } }); } @@ -1072,7 +1045,7 @@ impl TimePanel { if is_visible { let component_has_data_in_current_timeline = store .entity_has_component_on_timeline( - time_ctrl.timeline_name(), + &store_ctx.timeline_name(), entity_path, component, ); @@ -1086,21 +1059,14 @@ impl TimePanel { highlight_timeline_row( ui, - ctx, + store_ctx.app_ctx, time_area_painter, &item.to_item(), &row_rect, ); - let db = match self.source { - TimePanelSource::Recording => ctx.recording(), - TimePanelSource::Blueprint => ctx.store_context.blueprint, - }; - self.data_density_graph_ui( - ctx, - time_ctrl, - db, + store_ctx, time_area_painter, ui, row_rect, @@ -1116,16 +1082,16 @@ impl TimePanel { #[expect(clippy::too_many_arguments)] fn handle_interactions_for_item( &mut self, - ctx: &ViewerContext<'_>, + store_ctx: &StoreViewContext<'_>, + viewer_ctx: &ViewerContext<'_>, viewport_blueprint: &ViewportBlueprint, streams_tree_data: &StreamsTreeData, - entity_db: &re_entity_db::EntityDb, item: Item, response: &egui::Response, is_draggable: bool, ) { context_menu_ui_for_item_with_context( - ctx, + viewer_ctx, viewport_blueprint, &item, // expand/collapse context menu actions need this information @@ -1136,10 +1102,16 @@ impl TimePanel { response, SelectionUpdateBehavior::UseSelection, ); - ctx.handle_select_hover_drag_interactions(response, item.clone(), is_draggable); - ctx.handle_select_focus_sync(response, item.clone()); + viewer_ctx.handle_select_hover_drag_interactions(response, item.clone(), is_draggable); + viewer_ctx.handle_select_focus_sync(response, item.clone()); - self.handle_range_selection(ctx, streams_tree_data, entity_db, item.clone(), response); + self.handle_range_selection( + viewer_ctx, + store_ctx, + streams_tree_data, + item.clone(), + response, + ); if Some(item) == self.scroll_to_me_item { response.scroll_to_me(None); @@ -1148,12 +1120,9 @@ impl TimePanel { } /// Paint a data density graph, supporting tooltips. - #[expect(clippy::too_many_arguments)] fn data_density_graph_ui( &mut self, - ctx: &ViewerContext<'_>, - time_ctrl: &TimeControl, - db: &re_entity_db::EntityDb, + ctx: &StoreViewContext<'_>, time_area_painter: &egui::Painter, ui: &egui::Ui, row_rect: Rect, @@ -1163,8 +1132,6 @@ impl TimePanel { let hovered_time = data_density_graph::data_density_graph_ui( &mut self.data_density_graph_painter, ctx, - time_ctrl, - db, time_area_painter, ui, &self.time_ranges_ui, @@ -1178,7 +1145,7 @@ impl TimePanel { ctx.selection_state().set_hovered(item.to_item()); if ui.input(|i| i.pointer.primary_clicked()) { - ctx.command_sender() + ctx.command_sender .send_system(SystemCommand::SetSelection(item.to_item().into())); time_commands.push(TimeControlCommand::SetTime(hovered_time.into())); @@ -1197,14 +1164,7 @@ impl TimePanel { ) .gap(12.0) .show(|ui| { - data_density_graph::show_row_ids_tooltip( - ctx, - ui, - time_ctrl, - db, - item, - hovered_time, - ); + data_density_graph::show_row_ids_tooltip(ctx, ui, item, hovered_time); }); } } @@ -1216,8 +1176,8 @@ impl TimePanel { fn handle_range_selection( &mut self, ctx: &ViewerContext<'_>, + store_ctx: &StoreViewContext<'_>, streams_tree_data: &StreamsTreeData, - entity_db: &re_entity_db::EntityDb, item: Item, response: &Response, ) { @@ -1232,8 +1192,8 @@ impl TimePanel { if let Some(anchor_item) = &self.range_selection_anchor_item { let items_in_range = Self::items_in_range( ctx, + store_ctx, streams_tree_data, - entity_db, self.collapse_scope(), anchor_item, &item, @@ -1278,9 +1238,9 @@ impl TimePanel { /// existing last-clicked item (if any). It takes into account the collapsed state, so only /// actually visible items may be selected. fn items_in_range( - ctx: &ViewerContext<'_>, + viewer_ctx: &ViewerContext<'_>, + store_ctx: &StoreViewContext<'_>, streams_tree_data: &StreamsTreeData, - entity_db: &re_entity_db::EntityDb, collapse_scope: CollapseScope, anchor_item: &Item, shift_clicked_item: &Item, @@ -1289,33 +1249,35 @@ impl TimePanel { let mut found_last_clicked_items = false; let mut found_shift_clicked_items = false; - let _ignored = streams_tree_data.visit(ctx, entity_db, |entity_or_component| { - let item = entity_or_component.item(); + let _ignored = + streams_tree_data.visit(store_ctx.app_ctx, store_ctx.db, |entity_or_component| { + let item = entity_or_component.item(); - if &item == anchor_item { - found_last_clicked_items = true; - } + if &item == anchor_item { + found_last_clicked_items = true; + } - if &item == shift_clicked_item { - found_shift_clicked_items = true; - } + if &item == shift_clicked_item { + found_shift_clicked_items = true; + } - if found_last_clicked_items || found_shift_clicked_items { - items_in_range.push(item); - } + if found_last_clicked_items || found_shift_clicked_items { + items_in_range.push(item); + } - if found_last_clicked_items && found_shift_clicked_items { - return VisitorControlFlow::Break(()); - } + if found_last_clicked_items && found_shift_clicked_items { + return VisitorControlFlow::Break(()); + } - let is_expanded = entity_or_component.is_open(ctx.egui_ctx(), collapse_scope); + let is_expanded = + entity_or_component.is_open(viewer_ctx.egui_ctx(), collapse_scope); - if is_expanded { - VisitorControlFlow::Continue - } else { - VisitorControlFlow::SkipBranch - } - }); + if is_expanded { + VisitorControlFlow::Continue + } else { + VisitorControlFlow::SkipBranch + } + }); if !found_last_clicked_items { vec![] @@ -1326,12 +1288,12 @@ impl TimePanel { fn top_row_ui( &mut self, - ctx: &ViewerContext<'_>, - time_ctrl: &TimeControl, - entity_db: &re_entity_db::EntityDb, + store_ctx: &StoreViewContext<'_>, ui: &mut egui::Ui, time_commands: &mut Vec, ) { + let StoreViewContext { time_ctrl, db, .. } = store_ctx; + ui.spacing_mut().item_spacing.x = 18.0; // from figma if ui.max_rect().width() < 600.0 { @@ -1347,12 +1309,12 @@ impl TimePanel { ui.horizontal(|ui| { self.time_control_ui.timeline_selector_ui( time_ctrl, - entity_db.timeline_histograms(), + db.timeline_histograms(), ui, time_commands, ); - self.current_time_ui(ctx, time_ctrl, ui, time_commands); + self.current_time_ui(store_ctx.app_options, time_ctrl, ui, time_commands); ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { help_button(ui); @@ -1361,7 +1323,7 @@ impl TimePanel { }); } else { // One row: - let timeline_histograms = entity_db.timeline_histograms(); + let timeline_histograms = db.timeline_histograms(); self.time_control_ui .play_pause_ui(time_ctrl, ui, time_commands); @@ -1374,17 +1336,17 @@ impl TimePanel { self.time_control_ui .playback_speed_ui(time_ctrl, ui, time_commands); self.time_control_ui.fps_ui(time_ctrl, ui, time_commands); - self.current_time_ui(ctx, time_ctrl, ui, time_commands); + self.current_time_ui(store_ctx.app_options, time_ctrl, ui, time_commands); ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { help_button(ui); - let freshness = entity_db + let freshness = db .rrd_manifest_index() .chunk_requests() .bandwidth_data_freshness(ui.time()); - if ctx.app_options().show_metrics && freshness > 0.0 { - let mut rate = entity_db + if store_ctx.app_options.show_metrics && freshness > 0.0 { + let mut rate = db .rrd_manifest_index() .chunk_requests() .bandwidth() @@ -1428,12 +1390,14 @@ impl TimePanel { fn collapsed_time_marker_and_time( &mut self, + viewer_ctx: &ViewerContext<'_>, + store_ctx: &StoreViewContext<'_>, ui: &mut egui::Ui, - ctx: &ViewerContext<'_>, - time_ctrl: &TimeControl, - entity_db: &re_entity_db::EntityDb, time_commands: &mut Vec, ) { + let time_ctrl = store_ctx.time_ctrl; + let entity_db = store_ctx.db; + let Some(time_range) = entity_db.time_range_for(time_ctrl.timeline_name()) else { // We have no data on this timeline return; @@ -1453,10 +1417,9 @@ impl TimePanel { let draw_loaded_ranges = false; data_density_graph::paint_loaded_indicator_bar( + store_ctx, ui, &self.time_ranges_ui, - entity_db, - time_ctrl, time_range_rect.min.y, time_range_rect.x_range(), draw_loaded_ranges, @@ -1465,12 +1428,8 @@ impl TimePanel { if time_range_rect.width() > 50.0 { ui.allocate_rect(time_range_rect, egui::Sense::hover()); - self.time_ranges_ui = initialize_time_ranges_ui( - time_ctrl, - entity_db, - time_range_rect.x_range(), - None, - ); + self.time_ranges_ui = + initialize_time_ranges_ui(store_ctx, time_range_rect.x_range(), None); self.time_ranges_ui .snap_time_control(time_ctrl, time_commands); @@ -1502,9 +1461,7 @@ impl TimePanel { data_density_graph::data_density_graph_ui( &mut self.data_density_graph_painter, - ctx, - time_ctrl, - entity_db, + store_ctx, ui.painter(), ui, &self.time_ranges_ui, @@ -1513,9 +1470,9 @@ impl TimePanel { ); self.time_marker_ui( + viewer_ctx, + store_ctx, ui, - ctx, - time_ctrl, None, &painter, &time_range_rect, @@ -1525,12 +1482,12 @@ impl TimePanel { } } - self.current_time_ui(ctx, time_ctrl, ui, time_commands); + self.current_time_ui(store_ctx.app_options, time_ctrl, ui, time_commands); } fn current_time_ui( &mut self, - ctx: &ViewerContext<'_>, + app_options: &AppOptions, time_ctrl: &TimeControl, ui: &mut egui::Ui, time_commands: &mut Vec, @@ -1562,11 +1519,7 @@ impl TimePanel { num_subsecond_decimals(1.0 / self.time_ranges_ui.points_per_time); let mut time_str = self.time_edit_string.clone().unwrap_or_else(|| { - time_type.format_opt( - time_int, - ctx.app_options().timestamp_format, - subsecond_decimals, - ) + time_type.format_opt(time_int, app_options.timestamp_format, subsecond_decimals) }); ui.style_mut().spacing.text_edit_width = 200.0; @@ -1577,7 +1530,7 @@ impl TimePanel { } if response.lost_focus() { if let Some(time_int) = - time_type.parse_time(&time_str, ctx.app_options().timestamp_format) + time_type.parse_time(&time_str, app_options.timestamp_format) { time_commands.push(TimeControlCommand::SetTime(time_int.into())); } else { @@ -1621,7 +1574,7 @@ fn archetype_label_ui( /// Draw the hovered/selected highlight background for a timeline row. fn highlight_timeline_row( ui: &Ui, - ctx: &ViewerContext<'_>, + ctx: &AppContext<'_>, painter: &Painter, item: &Item, row_rect: &Rect, @@ -1702,8 +1655,7 @@ fn help_button(ui: &mut egui::Ui) { // ---------------------------------------------------------------------------- fn initialize_time_ranges_ui( - time_ctrl: &TimeControl, - entity_db: &re_entity_db::EntityDb, + store_ctx: &StoreViewContext<'_>, x_range: Rangef, mut time_view: Option, ) -> TimeRangesUi { @@ -1711,9 +1663,9 @@ fn initialize_time_ranges_ui( let mut time_range = Vec::new(); - let timeline = time_ctrl.timeline_name(); - if let Some(times) = entity_db.time_histogram(timeline) - && let Some(time_type) = time_ctrl.time_type() + let timeline = store_ctx.timeline_name(); + if let Some(times) = store_ctx.db.time_histogram(&timeline) + && let Some(time_type) = store_ctx.time_ctrl.time_type() { // NOTE: `times` can be empty if a GC wiped everything. if !times.is_empty() { @@ -1972,7 +1924,7 @@ fn timeline_properties_context_menu( time_ctrl: &TimeControl, hovered_time: TimeReal, ) { - let mut url = ViewerOpenUrl::from_context(ctx); + let mut url = ViewerOpenUrl::from_context(&ctx.app_ctx); let has_fragment = url.as_mut().is_ok_and(|url| { if let Some(fragment) = url.fragment_mut() { fragment.time_selection = None; @@ -2030,15 +1982,17 @@ impl TimePanel { #[expect(clippy::too_many_arguments)] fn time_marker_ui( &self, + viewer_ctx: &ViewerContext<'_>, + store_ctx: &StoreViewContext<'_>, ui: &egui::Ui, - ctx: &ViewerContext<'_>, - time_ctrl: &TimeControl, time_area_response: Option, time_area_painter: &egui::Painter, timeline_rect: &Rect, interact_rect: &Rect, time_commands: &mut Vec, ) { + let time_ctrl = store_ctx.time_ctrl; + // timeline_rect: top part with the second ticks and time marker // We only check for drags in the streams rect, because @@ -2093,7 +2047,7 @@ impl TimePanel { let popup_is_open = egui::Popup::context_menu(&response) .width(300.0) .show(|ui| { - timeline_properties_context_menu(ui, ctx, time_ctrl, preview_time); + timeline_properties_context_menu(ui, viewer_ctx, time_ctrl, preview_time); }) .is_some(); if popup_is_open { diff --git a/crates/viewer/re_time_panel/src/time_selection_ui.rs b/crates/viewer/re_time_panel/src/time_selection_ui.rs index 7957f238b88b..1b29f78c70d7 100644 --- a/crates/viewer/re_time_panel/src/time_selection_ui.rs +++ b/crates/viewer/re_time_panel/src/time_selection_ui.rs @@ -341,7 +341,7 @@ fn selection_context_menu( .send_ui(UICommand::SaveRecordingSelection); } - let mut url = ViewerOpenUrl::from_context(ctx); + let mut url = ViewerOpenUrl::from_context(&ctx.app_ctx); let has_time_range = url.as_mut().is_ok_and(|url| url.fragment_mut().is_some()); let copy_command = url.and_then(|url| url.copy_url_command()); if ui diff --git a/crates/viewer/re_time_panel/tests/time_panel_filter_tests.rs b/crates/viewer/re_time_panel/tests/time_panel_filter_tests.rs index 4ff58a1ca57a..5693dfbf84c1 100644 --- a/crates/viewer/re_time_panel/tests/time_panel_filter_tests.rs +++ b/crates/viewer/re_time_panel/tests/time_panel_filter_tests.rs @@ -132,11 +132,11 @@ fn run_time_panel_and_save_snapshot( let mut time_commands = Vec::new(); + let store_ctx = viewer_ctx.active_recording_store_view_context(); time_panel.show_expanded_with_header( viewer_ctx, - viewer_ctx.time_ctrl, + &store_ctx, &blueprint, - viewer_ctx.recording(), ui, &mut time_commands, ); diff --git a/crates/viewer/re_time_panel/tests/time_panel_tests.rs b/crates/viewer/re_time_panel/tests/time_panel_tests.rs index 3963a698a455..8cfc97ecc9a1 100644 --- a/crates/viewer/re_time_panel/tests/time_panel_tests.rs +++ b/crates/viewer/re_time_panel/tests/time_panel_tests.rs @@ -468,11 +468,11 @@ fn run_time_panel_and_save_snapshot( let mut time_commands = Vec::new(); + let store_ctx = viewer_ctx.active_recording_store_view_context(); time_panel.show_expanded_with_header( viewer_ctx, - viewer_ctx.time_ctrl, + &store_ctx, &blueprint, - viewer_ctx.recording(), ui, &mut time_commands, ); diff --git a/crates/viewer/re_view/src/view_property_ui.rs b/crates/viewer/re_view/src/view_property_ui.rs index 81c01a6654ab..b6a273a9cb45 100644 --- a/crates/viewer/re_view/src/view_property_ui.rs +++ b/crates/viewer/re_view/src/view_property_ui.rs @@ -135,7 +135,7 @@ fn view_property_ui_impl( /// Use [`view_property_ui`] whenever possible to show the ui for all components of a view property archetype. /// This function is only useful if you want to show custom ui for some of the components. pub fn view_property_component_ui( - ctx: &QueryContext<'_>, + query_ctx: &QueryContext<'_>, ui: &mut egui::Ui, property: &ViewProperty, display_name: &str, @@ -147,17 +147,17 @@ pub fn view_property_component_ui( let component_array = property.component_raw(component); let row_id = property.component_row_id(component); - let viewer_ctx = ctx.viewer_ctx(); + let viewer_ctx = query_ctx.viewer_ctx(); let ui_types = viewer_ctx .component_ui_registry() .registered_ui_types(field.component_type); let singleline_ui: &dyn Fn(&mut egui::Ui) = &|ui| { viewer_ctx.component_ui_registry().singleline_edit_ui( - ctx, + query_ctx, + &viewer_ctx.blueprint_store_view_ctx(), ui, - viewer_ctx.blueprint_db(), - ctx.target_entity_path.clone(), + query_ctx.target_entity_path.clone(), &component_descr, row_id, component_array.as_deref(), @@ -166,10 +166,10 @@ pub fn view_property_component_ui( let multiline_ui: &dyn Fn(&mut egui::Ui) = &|ui| { viewer_ctx.component_ui_registry().multiline_edit_ui( - ctx, + query_ctx, + &viewer_ctx.blueprint_store_view_ctx(), ui, - viewer_ctx.blueprint_db(), - ctx.target_entity_path.clone(), + query_ctx.target_entity_path.clone(), &component_descr, row_id, component_array.as_deref(), @@ -184,7 +184,7 @@ pub fn view_property_component_ui( }; view_property_component_ui_custom( - ctx, + query_ctx, ui, property, display_name, diff --git a/crates/viewer/re_view_dataframe/src/dataframe_ui.rs b/crates/viewer/re_view_dataframe/src/dataframe_ui.rs index 2c3f09e3c9b8..b2022a05dcf2 100644 --- a/crates/viewer/re_view_dataframe/src/dataframe_ui.rs +++ b/crates/viewer/re_view_dataframe/src/dataframe_ui.rs @@ -5,7 +5,7 @@ use anyhow::Context as _; use arrow::array::ArrayRef; use egui::{NumExt as _, RichText}; use itertools::Itertools as _; -use re_chunk_store::{ColumnDescriptor, LatestAtQuery}; +use re_chunk_store::ColumnDescriptor; use re_dataframe::QueryHandle; use re_dataframe::external::re_query::StorageEngineArcReadGuard; use re_dataframe_ui::re_table_utils::{apply_table_style_fixes, cell_ui, header_ui}; @@ -14,7 +14,7 @@ use re_log_types::{EntityPath, TimeInt, TimelineName}; use re_sdk_types::ComponentDescriptor; use re_sdk_types::reflection::ComponentDescriptorExt as _; use re_ui::UiExt as _; -use re_viewer_context::{TimeControlCommand, ViewId, ViewerContext}; +use re_viewer_context::{StoreViewContext, TimeControlCommand, ViewId}; use crate::expanded_rows::{ExpandedRows, ExpandedRowsCache}; @@ -34,7 +34,7 @@ pub(crate) enum HideColumnAction { /// Display a dataframe table for the provided query. pub(crate) fn dataframe_ui( - ctx: &ViewerContext<'_>, + ctx: &StoreViewContext<'_>, ui: &mut egui::Ui, query_handle: &re_dataframe::QueryHandle, expanded_rows_cache: &mut ExpandedRowsCache, @@ -210,7 +210,7 @@ impl RowsDisplayData { /// [`egui_table::TableDelegate`] implementation for displaying a [`QueryHandle`] in a table. struct DataframeTableDelegate<'a> { - ctx: &'a ViewerContext<'a>, + ctx: &'a StoreViewContext<'a>, table_style: re_ui::TableStyle, query_handle: &'a QueryHandle, selected_columns: &'a [ColumnDescriptor], @@ -385,7 +385,7 @@ impl egui_table::TableDelegate for DataframeTableDelegate<'_> { ColumnDescriptor::RowId(_) => {} ColumnDescriptor::Time(descr) => { if response.clicked() { - self.ctx.send_time_commands([ + self.ctx.send_time_commands_to_active_recording([ TimeControlCommand::SetActiveTimeline(*descr.timeline().name()), ]); } @@ -474,7 +474,10 @@ impl egui_table::TableDelegate for DataframeTableDelegate<'_> { .query() .filtered_index .unwrap_or_else(|| TimelineName::new("")); - let latest_at_query = LatestAtQuery::new(filtered_index, timestamp); + + let mut time_ctrl_at_time = self.ctx.time_ctrl.clone(); + time_ctrl_at_time.set_time_cursor_ad_hoc(filtered_index, timestamp.into()); + let ctx_at_time = self.ctx.with_time_ctrl(&time_ctrl_at_time); ui.set_truncate_style(); @@ -506,13 +509,7 @@ impl egui_table::TableDelegate for DataframeTableDelegate<'_> { // This is called when data actually needs to be drawn (as opposed to summaries like // "N instances" or "N more…"). let data_content = |ui: &mut egui::Ui| { - column.data_ui( - self.ctx, - ui, - &latest_at_query, - batch_row_idx, - instance_index, - ); + column.data_ui(&ctx_at_time, ui, batch_row_idx, instance_index); }; // Draw the cell content with some margin. diff --git a/crates/viewer/re_view_dataframe/src/view_class.rs b/crates/viewer/re_view_dataframe/src/view_class.rs index 918c5b0b8d32..eff7fab23d00 100644 --- a/crates/viewer/re_view_dataframe/src/view_class.rs +++ b/crates/viewer/re_view_dataframe/src/view_class.rs @@ -202,7 +202,7 @@ Configure in the selection panel: state.latest_time = timelines_match.then(|| ctx.time_ctrl.time_i64()).flatten(); let hide_column_actions = dataframe_ui( - ctx, + &ctx.active_recording_store_view_context(), ui, &query_handle, &mut state.expanded_rows_cache, diff --git a/crates/viewer/re_view_graph/src/ui/draw.rs b/crates/viewer/re_view_graph/src/ui/draw.rs index fc030b585d1c..c1db028e0fc3 100644 --- a/crates/viewer/re_view_graph/src/ui/draw.rs +++ b/crates/viewer/re_view_graph/src/ui/draw.rs @@ -12,7 +12,7 @@ use re_sdk_types::ArrowString; use re_ui::list_item; use re_viewer_context::{ DataResultInteractionAddress, HoverHighlight, InteractionHighlight, Item, SelectionHighlight, - SystemCommand, SystemCommandSender as _, UiLayout, ViewHighlights, ViewQuery, ViewerContext, + StoreViewContext, SystemCommand, SystemCommandSender as _, UiLayout, ViewHighlights, ViewQuery, }; use crate::graph::{Graph, Node}; @@ -324,7 +324,7 @@ pub fn draw_entity_rect( /// Draws the graph using the layout. pub fn draw_graph( ui: &mut Ui, - ctx: &ViewerContext<'_>, + ctx: &StoreViewContext<'_>, graph: &Graph, layout: &Layout, query: &ViewQuery<'_>, @@ -362,16 +362,9 @@ pub fn draw_graph( response = response.on_hover_ui(|ui| { list_item::list_item_scope(ui, "graph_node_hover", |ui| { - item_ui::instance_path_button( - ctx, - &query.latest_at_query(), - ctx.recording(), - ui, - Some(query.view_id), - &instance_path, - ); - - instance_path.data_ui_recording(ctx, ui, UiLayout::Tooltip); + item_ui::instance_path_button(ctx, ui, Some(query.view_id), &instance_path); + + instance_path.data_ui(ctx, ui, UiLayout::Tooltip); }); }); diff --git a/crates/viewer/re_view_graph/src/ui/selection.rs b/crates/viewer/re_view_graph/src/ui/selection.rs index d6e22539dbbb..4b0f81eb2aaa 100644 --- a/crates/viewer/re_view_graph/src/ui/selection.rs +++ b/crates/viewer/re_view_graph/src/ui/selection.rs @@ -52,8 +52,8 @@ pub fn view_property_force_ui( let singleline_ui: &dyn Fn(&mut egui::Ui) = &|ui| { ctx.viewer_ctx.component_ui_registry().singleline_edit_ui( &query_ctx, + &ctx.viewer_ctx.blueprint_store_view_ctx(), ui, - ctx.blueprint_db(), query_ctx.target_entity_path.clone(), &component_descr, row_id, diff --git a/crates/viewer/re_view_graph/src/view.rs b/crates/viewer/re_view_graph/src/view.rs index 4e8823082c1e..839535bb6e6c 100644 --- a/crates/viewer/re_view_graph/src/view.rs +++ b/crates/viewer/re_view_graph/src/view.rs @@ -247,7 +247,7 @@ impl ViewClass for GraphView { for graph in &graphs { draw_graph( ui, - ctx, + &ctx.active_recording_store_view_context(), graph, layout, query, diff --git a/crates/viewer/re_view_map/src/map_view.rs b/crates/viewer/re_view_map/src/map_view.rs index 8d4954cf1369..b8658f60ee9e 100644 --- a/crates/viewer/re_view_map/src/map_view.rs +++ b/crates/viewer/re_view_map/src/map_view.rs @@ -3,14 +3,14 @@ use re_data_ui::{DataUi as _, item_ui}; use re_entity_db::InstancePathHash; use re_log_types::EntityPath; use re_renderer::view_builder::ViewBuilderError; -use re_renderer::{RenderContext, ViewBuilder, ViewPickingConfiguration}; +use re_renderer::{ViewBuilder, ViewPickingConfiguration}; use re_sdk_types::blueprint::archetypes::{MapBackground, MapZoom}; use re_sdk_types::blueprint::components::{MapProvider, ZoomLevel}; use re_sdk_types::{View as _, ViewClassIdentifier}; use re_ui::{Help, IconText, icons, list_item}; use re_view::AnnotationSceneContext; use re_viewer_context::{ - DataResultInteractionAddress, IdentifiedViewSystem as _, Item, SystemCommand, + DataResultInteractionAddress, IdentifiedViewSystem as _, Item, StoreViewContext, SystemCommand, SystemCommandSender as _, SystemExecutionOutput, UiLayout, ViewClass, ViewClassExt as _, ViewClassLayoutPriority, ViewClassRegistryError, ViewHighlights, ViewId, ViewQuery, ViewSpawnHeuristics, ViewState, ViewStateExt as _, ViewSystemExecutionError, @@ -306,8 +306,7 @@ impl ViewClass for MapView { // let picking_config = handle_picking_and_ui_interactions( - ctx, - ctx.render_ctx(), + &ctx.active_recording_store_view_context(), ui.ctx(), query, state, @@ -398,8 +397,7 @@ fn create_view_builder( /// Handle picking and related ui interactions. fn handle_picking_and_ui_interactions( - ctx: &ViewerContext<'_>, - render_ctx: &RenderContext, + ctx: &StoreViewContext<'_>, egui_ctx: &egui::Context, query: &ViewQuery<'_>, state: &mut MapViewState, @@ -415,7 +413,7 @@ fn handle_picking_and_ui_interactions( pointer_in_pixel *= pixels_per_point; let picking_result = picking_gpu( - render_ctx, + ctx.render_ctx(), picking_readback_identifier, glam::vec2(pointer_in_pixel.x, pointer_in_pixel.y), &mut state.last_gpu_picking_result, @@ -457,28 +455,21 @@ fn handle_picking_and_ui_interactions( /// Handle all UI interactions based on the currently picked instance (if any). fn handle_ui_interactions( - ctx: &ViewerContext<'_>, + ctx: &StoreViewContext<'_>, query: &ViewQuery<'_>, mut map_response: Response, picked_instance: Option, ) { - if let Some(instance_path) = picked_instance.and_then(|hash| hash.resolve(ctx.recording())) { + if let Some(instance_path) = picked_instance.and_then(|hash| hash.resolve(ctx.db)) { // TODO(andreas): GPU picking doesn't tell us which visualizer produced the result. // We need to add the ability to look up the visualizer id when using GPU-based picking. let visualizer = None; map_response = map_response.on_hover_ui_at_pointer(|ui| { list_item::list_item_scope(ui, "map_hover", |ui| { - item_ui::instance_path_button( - ctx, - &query.latest_at_query(), - ctx.recording(), - ui, - Some(query.view_id), - &instance_path, - ); - - instance_path.data_ui_recording(ctx, ui, UiLayout::Tooltip); + item_ui::instance_path_button(ctx, ui, Some(query.view_id), &instance_path); + + instance_path.data_ui(ctx, ui, UiLayout::Tooltip); }); }); diff --git a/crates/viewer/re_view_spatial/src/picking_ui.rs b/crates/viewer/re_view_spatial/src/picking_ui.rs index 282dc21f7315..ecbb7a9a812f 100644 --- a/crates/viewer/re_view_spatial/src/picking_ui.rs +++ b/crates/viewer/re_view_spatial/src/picking_ui.rs @@ -126,7 +126,7 @@ pub fn picking( ui.set_max_width(320.0); ui.vertical(|ui| { textured_rect_hover_ui( - ctx, + &ctx.active_recording_store_view_context(), ui, &instance_path, query, @@ -144,14 +144,16 @@ pub fn picking( list_item_scope(ui, "spatial_hover", |ui| { hit_ui(ui, hit); item_ui::instance_path_button( - ctx, - &query.latest_at_query(), - ctx.recording(), + &ctx.active_recording_store_view_context(), ui, Some(query.view_id), &instance_path, ); - instance_path.data_ui_recording(ctx, ui, UiLayout::Tooltip); + instance_path.data_ui( + &ctx.active_recording_store_view_context(), + ui, + UiLayout::Tooltip, + ); }); }) }; diff --git a/crates/viewer/re_view_spatial/src/picking_ui_pixel.rs b/crates/viewer/re_view_spatial/src/picking_ui_pixel.rs index a3f4568a1c5a..e20249985bf8 100644 --- a/crates/viewer/re_view_spatial/src/picking_ui_pixel.rs +++ b/crates/viewer/re_view_spatial/src/picking_ui_pixel.rs @@ -7,7 +7,7 @@ use re_sdk_types::image::ImageKind; use re_sdk_types::tensor_data::TensorElement; use re_ui::UiExt as _; use re_view::AnnotationSceneContext; -use re_viewer_context::{Annotations, ImageInfo, ViewQuery, ViewerContext, gpu_bridge}; +use re_viewer_context::{Annotations, ImageInfo, StoreViewContext, ViewQuery, gpu_bridge}; use crate::PickableRectSourceData; use crate::view_kind::SpatialViewKind; @@ -20,7 +20,7 @@ pub struct PickedPixelInfo { #[expect(clippy::too_many_arguments)] pub fn textured_rect_hover_ui( - ctx: &ViewerContext<'_>, + ctx: &StoreViewContext<'_>, ui: &mut egui::Ui, instance_path: &re_entity_db::InstancePath, query: &ViewQuery<'_>, @@ -47,14 +47,7 @@ pub fn textured_rect_hover_ui( let depth_meter = depth_meter.map(|d| *d.0); - item_ui::instance_path_button( - ctx, - &query.latest_at_query(), - ctx.recording(), - ui, - Some(query.view_id), - instance_path, - ); + item_ui::instance_path_button(ctx, ui, Some(query.view_id), instance_path); ui.add_space(8.0); diff --git a/crates/viewer/re_view_spatial/src/visualizers/utilities/textured_rect.rs b/crates/viewer/re_view_spatial/src/visualizers/utilities/textured_rect.rs index b5b564ae916f..c98267289dca 100644 --- a/crates/viewer/re_view_spatial/src/visualizers/utilities/textured_rect.rs +++ b/crates/viewer/re_view_spatial/src/visualizers/utilities/textured_rect.rs @@ -28,7 +28,7 @@ pub fn textured_rect_from_image( &debug_name, image, &image_stats, - &ent_context.annotations, + Some(&ent_context.annotations), colormap, ) .map(|colormapped_texture| { diff --git a/crates/viewer/re_view_spatial/src/visualizers/video/video_frame_reference.rs b/crates/viewer/re_view_spatial/src/visualizers/video/video_frame_reference.rs index 4188af48e387..1beb735a6955 100644 --- a/crates/viewer/re_view_spatial/src/visualizers/video/video_frame_reference.rs +++ b/crates/viewer/re_view_spatial/src/visualizers/video/video_frame_reference.rs @@ -185,7 +185,7 @@ impl VideoFrameReferenceVisualizer { } let video_time = re_viewer_context::video_timestamp_component_to_video_time( - ctx.viewer_ctx(), + Some(ctx.viewer_ctx().time_ctrl), *video_timestamp, video.data_descr().timescale, ); diff --git a/crates/viewer/re_view_text_log/src/view_class.rs b/crates/viewer/re_view_text_log/src/view_class.rs index 26ef6dd9b7f6..d9cba9a79d62 100644 --- a/crates/viewer/re_view_text_log/src/view_class.rs +++ b/crates/viewer/re_view_text_log/src/view_class.rs @@ -372,7 +372,7 @@ fn table_ui( } header.col(|ui| { - timeline_button(ctx, ui, &TimelineName::new(&col.timeline)); + timeline_button(&ctx.app_ctx, ui, &TimelineName::new(&col.timeline)); }); } for col in columns { @@ -389,8 +389,6 @@ fn table_ui( body_clip_rect = Some(body.max_rect()); - let query = ctx.current_query(); - let row_heights = entries .iter() .map(|te| calc_row_height(tokens, table_style, te)); @@ -438,9 +436,7 @@ fn table_ui( row.col(|ui| match col.kind { bp_datatypes::TextLogColumnKind::EntityPath => { item_ui::entity_path_button( - ctx, - &query, - ctx.recording(), + &ctx.active_recording_store_view_context(), ui, None, &entry.entity_path, diff --git a/crates/viewer/re_viewer/src/app.rs b/crates/viewer/re_viewer/src/app.rs index e24ce266c205..911cfce566bf 100644 --- a/crates/viewer/re_viewer/src/app.rs +++ b/crates/viewer/re_viewer/src/app.rs @@ -27,9 +27,9 @@ use re_ui::{ContextExt as _, UICommand, UICommandSender as _, UiExt as _, notifi use re_viewer_context::open_url::{OpenUrlOptions, ViewerOpenUrl, combine_with_base_url}; use re_viewer_context::store_hub::{BlueprintPersistence, StoreHub, StoreHubStats}; use re_viewer_context::{ - AppOptions, AsyncRuntimeHandle, AuthContext, CommandReceiver, CommandSender, - ComponentUiRegistry, EditRedapServerModalCommand, FallbackProviderRegistry, Item, - MoveDirection, MoveSpeed, NeedsRepaint, RecordingOrTable, Route, StorageContext, StoreContext, + ActiveStoreContext, AppOptions, AsyncRuntimeHandle, AuthContext, CommandReceiver, + CommandSender, ComponentUiRegistry, EditRedapServerModalCommand, FallbackProviderRegistry, + Item, MoveDirection, MoveSpeed, NeedsRepaint, RecordingOrTable, Route, StorageContext, SystemCommand, SystemCommandSender as _, TableStore, TimeControlCommand, ViewClass, ViewClassRegistry, ViewClassRegistryError, command_channel, sanitize_file_name, }; @@ -706,7 +706,7 @@ impl App { egui_ctx: &egui::Context, app_blueprint: &AppBlueprint<'_>, storage_context: &StorageContext<'_>, - store_context: Option<&StoreContext<'_>>, + store_context: Option<&ActiveStoreContext<'_>>, route: &Route, ) { while let Some(cmd) = self.command_receiver.recv_ui() { @@ -1581,7 +1581,7 @@ impl App { egui_ctx: &egui::Context, app_blueprint: &AppBlueprint<'_>, storage_context: &StorageContext<'_>, - store_context: Option<&StoreContext<'_>>, + store_context: Option<&ActiveStoreContext<'_>>, route: &Route, cmd: UICommand, ) { @@ -2254,7 +2254,7 @@ impl App { fn copy_entity_hierarchy_to_clipboard( &mut self, egui_ctx: &egui::Context, - store_context: Option<&StoreContext<'_>>, + store_context: Option<&ActiveStoreContext<'_>>, ) { let Some(entity_db) = store_context.as_ref().map(|ctx| ctx.recording) else { re_log::warn!("Could not copy entity hierarchy: No active recording"); @@ -2350,7 +2350,7 @@ impl App { frame: &eframe::Frame, app_blueprint: &AppBlueprint<'_>, gpu_resource_stats: &WgpuResourcePoolStatistics, - store_context: &StoreContext<'_>, + store_context: &ActiveStoreContext<'_>, storage_context: &StorageContext<'_>, mem_usage_tree: Option, store_stats: Option<&StoreHubStats>, @@ -2632,8 +2632,8 @@ impl App { .replace(Route::LocalRecording { recording_id }); } else { // TODO(RR-3713): show a blueprint for it anyway - re_log::warn_once!( - "Can't show {app_id} at this time - we have no recording for it" + re_log::debug_warn_once!( + "Can't switch to app '{app_id}' at this time - we have no recording for it" ); } } @@ -4076,7 +4076,7 @@ async fn async_open_rrd_dialog() -> Vec { fn save_active_recording( app: &mut App, - store_context: Option<&StoreContext<'_>>, + store_context: Option<&ActiveStoreContext<'_>>, loop_selection: Option<(TimelineName, re_log_types::AbsoluteTimeRangeF)>, ) -> anyhow::Result<()> { let Some(entity_db) = store_context.as_ref().map(|view| view.recording) else { @@ -4121,7 +4121,10 @@ fn save_recording( ) } -fn save_blueprint(app: &mut App, store_context: Option<&StoreContext<'_>>) -> anyhow::Result<()> { +fn save_blueprint( + app: &mut App, + store_context: Option<&ActiveStoreContext<'_>>, +) -> anyhow::Result<()> { let Some(store_context) = store_context else { anyhow::bail!("No blueprint to save"); }; diff --git a/crates/viewer/re_viewer/src/app_state.rs b/crates/viewer/re_viewer/src/app_state.rs index 2bcabc49a608..e142ae125f35 100644 --- a/crates/viewer/re_viewer/src/app_state.rs +++ b/crates/viewer/re_viewer/src/app_state.rs @@ -15,12 +15,12 @@ use re_sdk_types::blueprint::components::{PanelState, PlayState}; use re_ui::{ContextExt as _, UiExt as _}; use re_viewer_context::open_url::{self, ViewerOpenUrl}; use re_viewer_context::{ - AppContext, AppOptions, ApplicationSelectionState, AsyncRuntimeHandle, AuthContext, - BlueprintContext, BlueprintUndoState, CommandSender, ComponentUiRegistry, DragAndDropManager, - FallbackProviderRegistry, Item, PerVisualizerTypeInViewClass, Route, SelectionChange, - StorageContext, StoreContext, StoreHub, SystemCommand, SystemCommandSender as _, TableStore, - TimeControl, TimeControlCommand, ViewClassRegistry, ViewStates, ViewerContext, - blueprint_timeline, + ActiveStoreContext, AppContext, AppOptions, ApplicationSelectionState, AsyncRuntimeHandle, + AuthContext, BlueprintContext, BlueprintUndoState, CommandSender, ComponentUiRegistry, + DragAndDropManager, FallbackProviderRegistry, Item, PerVisualizerTypeInViewClass, Route, + SelectionChange, StorageContext, StoreHub, StoreViewContext, SystemCommand, + SystemCommandSender as _, TableStore, TimeControl, TimeControlCommand, ViewClassRegistry, + ViewStates, ViewerContext, blueprint_timeline, }; use re_viewport::ViewportUi; use re_viewport_blueprint::ViewportBlueprint; @@ -183,7 +183,7 @@ impl AppState { /// Currently selected section of time, if any. pub fn loop_selection( &self, - store_context: Option<&StoreContext<'_>>, + store_context: Option<&ActiveStoreContext<'_>>, ) -> Option<(TimelineName, AbsoluteTimeRangeF)> { let rec_id = store_context.as_ref()?.recording.store_id(); let time_ctrl = self.time_controls.get(rec_id)?; @@ -204,7 +204,7 @@ impl AppState { render_ctx: &re_renderer::RenderContext, // TODO(RR-3033): the viewer should be robust to not having a `StoreContext`. // We should only have one if we are currently looking at a blueprint. - store_context: &StoreContext<'_>, + store_context: &ActiveStoreContext<'_>, storage_context: &StorageContext<'_>, reflection: &re_types_core::reflection::Reflection, component_ui_registry: &ComponentUiRegistry, @@ -419,11 +419,15 @@ impl AppState { connection_registry, storage_context, + active_store_context: Some(store_context), + component_ui_registry, route, selection_state, focused_item, drag_and_drop_manager: &drag_and_drop_manager, + active_time_ctrl: Some(time_ctrl), + connected_receivers: rx_log, auth_context: auth_state.as_ref(), }, component_fallback_registry, @@ -486,9 +490,8 @@ impl AppState { blueprint_time_panel.show_panel( &ctx, + &ctx.blueprint_store_view_ctx(), &viewport_ui.blueprint, - blueprint_db, - blueprint_time_control, ui, PanelState::Expanded, // Give the blueprint time panel a distinct color from the normal time panel: @@ -517,9 +520,8 @@ impl AppState { if route.has_time_panel() { time_panel.show_panel( &ctx, + &ctx.active_recording_store_view_context(), &viewport_ui.blueprint, - ctx.recording(), - ctx.time_ctrl, ui, app_blueprint.time_panel_state(), ui.tokens().bottom_panel_frame(), @@ -640,7 +642,13 @@ impl AppState { match route { Route::LocalTable(table_id) => { if let Some(store) = ctx.table_stores().get(table_id) { - table_ui(&ctx, runtime, ui, table_id, store); + table_ui( + &ctx.active_recording_store_view_context(), + runtime, + ui, + table_id, + store, + ); } else { re_log::error_once!( "Could not find batch store for table id {}", @@ -663,7 +671,11 @@ impl AppState { } Route::RedapEntry(entry) => { - redap_servers.entry_ui(&ctx, ui, entry.entry_id); + redap_servers.entry_ui( + &ctx.active_recording_store_view_context(), // TODO(RR-1127): this makes no sense + ui, + entry.entry_id, + ); } Route::RedapServer(origin) => { @@ -698,7 +710,11 @@ impl AppState { &login_state, ); } else { - redap_servers.server_central_panel_ui(&ctx, ui, origin); + redap_servers.server_central_panel_ui( + &ctx.active_recording_store_view_context(), // TODO(RR-3033): server_central_panel_ui should not know about any recording/blueprint + ui, + origin, + ); } } @@ -844,7 +860,7 @@ impl AppState { } fn table_ui( - ctx: &ViewerContext<'_>, + ctx: &StoreViewContext<'_>, runtime: &AsyncRuntimeHandle, ui: &mut Ui, table_id: &TableId, diff --git a/crates/viewer/re_viewer/src/ui/rerun_menu.rs b/crates/viewer/re_viewer/src/ui/rerun_menu.rs index b2c4bd3f4fb4..8768f8c7294c 100644 --- a/crates/viewer/re_viewer/src/ui/rerun_menu.rs +++ b/crates/viewer/re_viewer/src/ui/rerun_menu.rs @@ -6,7 +6,7 @@ use egui::containers::menu::{MenuButton, MenuConfig}; use egui::{Button, NumExt as _, ScrollArea}; use re_ui::menu::menu_style; use re_ui::{UICommand, UICommandSender as _, UiExt as _}; -use re_viewer_context::StoreContext; +use re_viewer_context::ActiveStoreContext; use crate::App; @@ -16,7 +16,7 @@ impl App { pub fn rerun_menu_button_ui( &mut self, render_state: Option<&egui_wgpu::RenderState>, - _store_context: Option<&StoreContext<'_>>, + _store_context: Option<&ActiveStoreContext<'_>>, ui: &mut egui::Ui, ) { let desired_icon_height = if ui.max_rect().height() <= 24.0 { @@ -77,7 +77,7 @@ impl App { &mut self, ui: &mut egui::Ui, render_state: Option<&egui_wgpu::RenderState>, - _store_context: Option<&StoreContext<'_>>, + _store_context: Option<&ActiveStoreContext<'_>>, ) { ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend); // no wrapping: make as wide as needed @@ -233,7 +233,7 @@ impl App { } } - fn save_buttons_ui(&self, ui: &mut egui::Ui, store_ctx: Option<&StoreContext<'_>>) { + fn save_buttons_ui(&self, ui: &mut egui::Ui, store_ctx: Option<&ActiveStoreContext<'_>>) { use re_ui::UICommandSender as _; let file_save_in_progress = self.background_tasks.is_file_save_in_progress(); diff --git a/crates/viewer/re_viewer/src/ui/top_panel.rs b/crates/viewer/re_viewer/src/ui/top_panel.rs index 427cc9c7e4c6..8c762f234e27 100644 --- a/crates/viewer/re_viewer/src/ui/top_panel.rs +++ b/crates/viewer/re_viewer/src/ui/top_panel.rs @@ -4,7 +4,7 @@ use re_format::format_uint; use re_renderer::WgpuResourcePoolStatistics; use re_sorbet::TimestampLocation; use re_ui::{ContextExt as _, UICommand, UiExt as _, icons}; -use re_viewer_context::{StoreContext, StoreHub, SystemCommand, SystemCommandSender as _}; +use re_viewer_context::{ActiveStoreContext, StoreHub, SystemCommand, SystemCommandSender as _}; use crate::App; use crate::app_blueprint::AppBlueprint; @@ -14,7 +14,7 @@ pub fn top_panel( frame: &eframe::Frame, app: &mut App, app_blueprint: &AppBlueprint<'_>, - store_context: Option<&StoreContext<'_>>, + store_context: Option<&ActiveStoreContext<'_>>, store_hub: &StoreHub, gpu_resource_stats: &WgpuResourcePoolStatistics, ui: &mut egui::Ui, @@ -82,7 +82,7 @@ fn top_bar_ui( frame: &eframe::Frame, app: &mut App, app_blueprint: &AppBlueprint<'_>, - store_context: Option<&StoreContext<'_>>, + store_context: Option<&ActiveStoreContext<'_>>, store_hub: &StoreHub, ui: &mut egui::Ui, gpu_resource_stats: &WgpuResourcePoolStatistics, diff --git a/crates/viewer/re_viewer/tests/app_kittest.rs b/crates/viewer/re_viewer/tests/app_kittest.rs index 2dea5dfb9221..33fcef32eb79 100644 --- a/crates/viewer/re_viewer/tests/app_kittest.rs +++ b/crates/viewer/re_viewer/tests/app_kittest.rs @@ -7,7 +7,7 @@ use egui_kittest::kittest::Queryable as _; use re_sdk_types::components::Colormap; use re_test_context::TestContext; use re_viewer::viewer_test_utils::{self, HarnessOptions}; -use re_viewer_context::{MaybeMutRef, ViewerContext}; +use re_viewer_context::MaybeMutRef; /// Navigates from welcome to settings screen and snapshots it. #[tokio::test] @@ -69,7 +69,7 @@ fn colormap_selector_ui() { .build_ui(|ui| { re_ui::apply_style_and_install_loaders(ui.ctx()); - test_context.run(&ui.ctx().clone(), |ctx: &ViewerContext<'_>| { + test_context.run_recording(&ui.ctx().clone(), |ctx| { ui.horizontal(|ui| { ui.label("Colormap:"); diff --git a/crates/viewer/re_viewer_context/src/store_context.rs b/crates/viewer/re_viewer_context/src/active_store_context.rs similarity index 93% rename from crates/viewer/re_viewer_context/src/store_context.rs rename to crates/viewer/re_viewer_context/src/active_store_context.rs index ad1f41f90272..cfd3e7563686 100644 --- a/crates/viewer/re_viewer_context/src/store_context.rs +++ b/crates/viewer/re_viewer_context/src/active_store_context.rs @@ -4,7 +4,7 @@ use re_log_types::{ApplicationId, StoreId}; use crate::Caches; /// The current Blueprint and Recording being displayed by the viewer -pub struct StoreContext<'a> { +pub struct ActiveStoreContext<'a> { /// The current active blueprint. pub blueprint: &'a EntityDb, @@ -23,7 +23,7 @@ pub struct StoreContext<'a> { pub should_enable_heuristics: bool, } -impl StoreContext<'_> { +impl ActiveStoreContext<'_> { pub fn is_active(&self, store_id: &StoreId) -> bool { self.recording.store_id() == store_id || self.blueprint.store_id() == store_id } diff --git a/crates/viewer/re_viewer_context/src/app_context.rs b/crates/viewer/re_viewer_context/src/app_context.rs index 0883f7fd96ba..79e6cc21f4a8 100644 --- a/crates/viewer/re_viewer_context/src/app_context.rs +++ b/crates/viewer/re_viewer_context/src/app_context.rs @@ -1,6 +1,18 @@ +use ahash::HashMap; +use arrow::array::ArrayRef; +use re_chunk::RowId; +use re_chunk_store::external::re_chunk::Chunk; +use re_entity_db::EntityDb; +use re_log_types::{EntityPath, StoreId, TimePoint}; +use re_sdk_types::ComponentDescriptor; +use re_ui::ContextExt as _; + +use crate::drag_and_drop::DragAndDropPayload; +use crate::time_control::TimeControlCommand; use crate::{ - AppOptions, ApplicationSelectionState, CommandSender, ComponentUiRegistry, DragAndDropManager, - ItemCollection, Route, StorageContext, + ActiveStoreContext, AppOptions, ApplicationSelectionState, CommandSender, ComponentUiRegistry, + DragAndDropManager, Item, ItemCollection, Route, StorageContext, StoreHub, SystemCommand, + SystemCommandSender as _, TableStores, TimeControl, }; /// Application context that is shared across all parts of the viewer. @@ -37,6 +49,11 @@ pub struct AppContext<'a> { /// All loaded recordings, blueprints, tables, etc. pub storage_context: &'a StorageContext<'a>, + /// The currently active store and blueprint, if any. + /// + /// This is `None` if the current [`Route`] is not pointing to a recording. + pub active_store_context: Option<&'a ActiveStoreContext<'a>>, + /// How to display components. pub component_ui_registry: &'a ComponentUiRegistry, @@ -55,6 +72,12 @@ pub struct AppContext<'a> { /// Helper object to manage drag-and-drop operations. pub drag_and_drop_manager: &'a DragAndDropManager, + /// The time control for the active recording, if any. + pub active_time_ctrl: Option<&'a TimeControl>, + + /// Where we are getting our data from. + pub connected_receivers: &'a re_log_channel::LogReceiverSet, + /// Are we logged in to rerun cloud? pub auth_context: Option<&'a AuthContext>, } @@ -113,4 +136,227 @@ impl AppContext<'_> { _ => None, } } + + pub fn tokens(&self) -> &'static re_ui::DesignTokens { + self.egui_ctx.tokens() + } + + /// How to configure the renderer. + #[inline] + pub fn render_mode(&self) -> re_renderer::RenderMode { + if self.is_test { + re_renderer::RenderMode::Deterministic + } else { + re_renderer::RenderMode::Beautiful + } + } + + /// The [`StoreHub`]. + pub fn store_hub(&self) -> &StoreHub { + self.storage_context.hub + } + + /// All loaded recordings, blueprints, etc. + pub fn store_bundle(&self) -> &re_entity_db::StoreBundle { + self.storage_context.bundle + } + + /// All loaded tables. + pub fn table_stores(&self) -> &TableStores { + self.storage_context.tables + } + + /// Item that got focused on the last frame if any. + pub fn focused_item(&self) -> &Option { + self.focused_item + } + + /// Helper object to manage drag-and-drop operations. + pub fn drag_and_drop_manager(&self) -> &DragAndDropManager { + self.drag_and_drop_manager + } + + /// The active recording, if any. + pub fn active_recording(&self) -> Option<&EntityDb> { + self.active_store_context.map(|ctx| ctx.recording) + } + + /// The active blueprint, if any. + pub fn active_blueprint(&self) -> Option<&EntityDb> { + self.active_store_context.map(|ctx| ctx.blueprint) + } + + /// The time control for the active recording, if any. + pub fn active_time_ctrl(&self) -> Option<&TimeControl> { + self.active_time_ctrl + } + + /// Helper function to send [`TimeControlCommand`]s for the active recording. + pub fn send_time_commands_to_active_recording( + &self, + commands: impl IntoIterator, + ) { + let commands: Vec<_> = commands.into_iter().collect(); + if !commands.is_empty() { + if let Some(store_ctx) = self.active_store_context { + self.command_sender + .send_system(SystemCommand::TimeControlCommands { + store_id: store_ctx.recording.store_id().clone(), + time_commands: commands, + }); + } else { + re_log::debug_warn_once!("Time command ignored - no active recording"); + } + } + } + + /// Consistently handle the selection, hover, drag start interactions for a given set of items. + pub fn handle_select_hover_drag_interactions( + &self, + response: &egui::Response, + interacted_items: impl Into, + draggable: bool, + ) { + let mut interacted_items = interacted_items.into(); + + if let Some(store_ctx) = self.active_store_context + && let Some(time_ctrl) = self.active_time_ctrl + { + interacted_items = interacted_items + .into_mono_instance_path_items(store_ctx.recording, &time_ctrl.current_query()); + } + let selection_state = self.selection_state(); + + if response.hovered() { + selection_state.set_hovered(interacted_items.clone()); + } + + let single_selected = self.selection().single_item() == interacted_items.single_item(); + + if single_selected && self.selection_state().selection_changed().is_some() { + response.scroll_to_me(None); + } + + if draggable && response.drag_started() { + let mut selected_items = selection_state.selected_items().clone(); + let is_already_selected = interacted_items + .iter() + .all(|(item, _)| selected_items.contains_item(item)); + + let is_cmd_held = response.ctx.input(|i| i.modifiers.command); + + let dragged_items = if !is_already_selected && is_cmd_held { + selected_items.extend(interacted_items); + self.command_sender + .send_system(SystemCommand::set_selection(selected_items.clone())); + selected_items + } else if !is_already_selected { + interacted_items + } else { + selected_items + }; + + let items_may_be_dragged = self + .drag_and_drop_manager + .are_items_draggable(&dragged_items); + + let payload = if items_may_be_dragged { + DragAndDropPayload::from_items(&dragged_items) + } else { + DragAndDropPayload::Invalid + }; + + egui::DragAndDrop::set_payload(&response.ctx, payload); + } else if response.clicked() { + if response.double_clicked() + && let Some(item) = interacted_items.first_item() + { + let item = if let Item::DataResult(data_result) = item { + interacted_items = Item::DataResult(data_result.as_entity_all()).into(); + interacted_items + .first_item() + .expect("That item was just added") + } else { + item + }; + + self.command_sender + .send_system(SystemCommand::SetFocus(item.clone())); + } + + let modifiers = response.ctx.input(|i| i.modifiers); + + if !modifiers.shift { + if modifiers.command { + let mut toggle_items_set: HashMap<_, _> = interacted_items + .iter() + .map(|(item, ctx)| (item.clone(), ctx.clone())) + .collect(); + + let mut new_selection = selection_state.selected_items().clone(); + + new_selection.retain(|item, ctx| { + if let Some(new_ctx) = toggle_items_set.get(item) { + if new_ctx == ctx || new_ctx.is_none() { + toggle_items_set.remove(item); + false + } else { + true + } + } else { + true + } + }); + + for (item, ctx) in new_selection.iter_mut() { + if let Some(new_ctx) = toggle_items_set.get(item) { + *ctx = new_ctx.clone(); + toggle_items_set.remove(item); + } + } + + new_selection.extend( + interacted_items + .into_iter() + .filter(|(item, _)| toggle_items_set.contains_key(item)), + ); + + self.command_sender + .send_system(SystemCommand::set_selection(new_selection)); + } else { + self.command_sender + .send_system(SystemCommand::set_selection(interacted_items)); + } + } + } + } + + /// Reverts to the default route. + pub fn revert_to_default_route(&self) { + self.command_sender.send_system(SystemCommand::ResetRoute); + } + + /// Append an array to the given store. + pub fn append_array_to_store( + &self, + store_id: StoreId, + timepoint: TimePoint, + entity_path: EntityPath, + component_descr: ComponentDescriptor, + array: ArrayRef, + ) { + let chunk = match Chunk::builder(entity_path) + .with_row(RowId::new(), timepoint, [(component_descr, array)]) + .build() + { + Ok(chunk) => chunk, + Err(err) => { + re_log::error_once!("Failed to create Chunk: {err}"); + return; + } + }; + + self.command_sender + .send_system(SystemCommand::AppendToStore(store_id, vec![chunk])); + } } diff --git a/crates/viewer/re_viewer_context/src/blueprint_helpers.rs b/crates/viewer/re_viewer_context/src/blueprint_helpers.rs index 27e289bf31bf..095258564710 100644 --- a/crates/viewer/re_viewer_context/src/blueprint_helpers.rs +++ b/crates/viewer/re_viewer_context/src/blueprint_helpers.rs @@ -5,7 +5,9 @@ use re_entity_db::EntityDb; use re_log_types::{EntityPath, StoreId, TimeInt, TimePoint, Timeline}; use re_sdk_types::{AsComponents, ComponentBatch, ComponentDescriptor, SerializedComponentBatch}; -use crate::{CommandSender, StoreContext, SystemCommand, SystemCommandSender as _, ViewerContext}; +use crate::{ + ActiveStoreContext, CommandSender, SystemCommand, SystemCommandSender as _, ViewerContext, +}; #[inline] pub fn blueprint_timeline() -> TimelineName { @@ -25,7 +27,7 @@ pub fn blueprint_timepoint_for_writes(blueprint: &re_entity_db::EntityDb) -> Tim TimePoint::from([(timeline, TimeInt::new_temporal(max_time))]) } -impl StoreContext<'_> { +impl ActiveStoreContext<'_> { /// The timepoint to use when writing an update to the blueprint. #[inline] pub fn blueprint_timepoint_for_writes(&self) -> TimePoint { @@ -181,6 +183,32 @@ pub trait BlueprintContext { .send_system(SystemCommand::AppendToStore(store_id, vec![chunk])); } + fn save_static_blueprint_array( + &self, + entity_path: EntityPath, + component_descr: ComponentDescriptor, + array: ArrayRef, + ) { + let blueprint = self.current_blueprint(); + + let chunk = match Chunk::builder(entity_path) + .with_row(RowId::new(), TimePoint::STATIC, [(component_descr, array)]) + .build() + { + Ok(chunk) => chunk, + Err(err) => { + re_log::error_once!("Failed to create Chunk: {err}"); + return; + } + }; + + self.command_sender() + .send_system(SystemCommand::AppendToStore( + blueprint.store_id().clone(), + vec![chunk], + )); + } + /// Queries a raw component from the currently active blueprint. fn raw_latest_at_in_current_blueprint( &self, diff --git a/crates/viewer/re_viewer_context/src/component_ui_registry.rs b/crates/viewer/re_viewer_context/src/component_ui_registry.rs index 6f43bfbe3948..d3c62a2454cd 100644 --- a/crates/viewer/re_viewer_context/src/component_ui_registry.rs +++ b/crates/viewer/re_viewer_context/src/component_ui_registry.rs @@ -2,15 +2,13 @@ use std::collections::BTreeMap; use ahash::HashMap; use re_chunk::{ComponentIdentifier, RowId, TimePoint, UnitChunkShared}; -use re_chunk_store::LatestAtQuery; -use re_entity_db::{EntityDb, EntityPath}; +use re_entity_db::EntityPath; use re_log::{ResultExt as _, debug_assert_eq}; use re_log_types::{Instance, StoreId}; use re_sdk_types::{ComponentDescriptor, ComponentType}; use re_ui::{UiExt as _, UiLayout}; -use crate::blueprint_helpers::BlueprintContext as _; -use crate::{MaybeMutRef, QueryContext, ViewerContext}; +use crate::{MaybeMutRef, QueryContext, StoreViewContext}; /// Describes where an edit should be written to if any pub struct EditTarget { @@ -52,11 +50,9 @@ impl ComponentUiTypes { type LegacyDisplayComponentUiCallback = Box< dyn Fn( - &ViewerContext<'_>, + &StoreViewContext<'_>, &mut egui::Ui, UiLayout, - &LatestAtQuery, - &EntityDb, &EntityPath, &ComponentDescriptor, Option, @@ -104,7 +100,7 @@ impl From for ComponentUiIdentifier { /// If no edit was made, should return `None`. pub type UntypedComponentEditOrViewCallback = Box< dyn Fn( - &ViewerContext<'_>, + &StoreViewContext<'_>, &mut egui::Ui, &ComponentDescriptor, Option, @@ -198,7 +194,11 @@ impl ComponentUiRegistry { /// * Make sure that changes are propagated via [`egui::Response::mark_changed`] if necessary. pub fn add_singleline_edit_or_view( &mut self, - callback: impl Fn(&ViewerContext<'_>, &mut egui::Ui, &mut MaybeMutRef<'_, C>) -> egui::Response + callback: impl Fn( + &StoreViewContext<'_>, + &mut egui::Ui, + &mut MaybeMutRef<'_, C>, + ) -> egui::Response + Send + Sync + 'static, @@ -226,7 +226,11 @@ impl ComponentUiRegistry { /// * Make sure that changes are propagated via [`egui::Response::mark_changed`] if necessary. pub fn add_multiline_edit_or_view( &mut self, - callback: impl Fn(&ViewerContext<'_>, &mut egui::Ui, &mut MaybeMutRef<'_, C>) -> egui::Response + callback: impl Fn( + &StoreViewContext<'_>, + &mut egui::Ui, + &mut MaybeMutRef<'_, C>, + ) -> egui::Response + Send + Sync + 'static, @@ -254,7 +258,7 @@ impl ComponentUiRegistry { pub fn add_singleline_array_edit_or_view( &mut self, callback: impl Fn( - &ViewerContext<'_>, + &StoreViewContext<'_>, &mut egui::Ui, &mut MaybeMutRef<'_, Vec>, ) -> egui::Response @@ -283,7 +287,7 @@ impl ComponentUiRegistry { pub fn add_multiline_array_edit_or_view( &mut self, callback: impl Fn( - &ViewerContext<'_>, + &StoreViewContext<'_>, &mut egui::Ui, &mut MaybeMutRef<'_, Vec>, ) -> egui::Response @@ -298,7 +302,11 @@ impl ComponentUiRegistry { fn add_editor_ui( &mut self, multiline: bool, - callback: impl Fn(&ViewerContext<'_>, &mut egui::Ui, &mut MaybeMutRef<'_, C>) -> egui::Response + callback: impl Fn( + &StoreViewContext<'_>, + &mut egui::Ui, + &mut MaybeMutRef<'_, C>, + ) -> egui::Response + Send + Sync + 'static, @@ -340,7 +348,7 @@ impl ComponentUiRegistry { &mut self, multiline: bool, callback: impl Fn( - &ViewerContext<'_>, + &StoreViewContext<'_>, &mut egui::Ui, &mut MaybeMutRef<'_, Vec>, ) -> egui::Response @@ -400,7 +408,7 @@ impl ComponentUiRegistry { &mut self, variant_name: impl Into, callback: impl Fn( - &ViewerContext<'_>, + &StoreViewContext<'_>, &mut egui::Ui, ComponentIdentifier, Option, @@ -493,11 +501,9 @@ impl ComponentUiRegistry { #[expect(clippy::too_many_arguments)] pub fn component_ui( &self, - ctx: &ViewerContext<'_>, + ctx: &StoreViewContext<'_>, ui: &mut egui::Ui, ui_layout: UiLayout, - query: &LatestAtQuery, - db: &EntityDb, entity_path: &EntityPath, component_descr: &ComponentDescriptor, unit: &UnitChunkShared, @@ -541,8 +547,6 @@ impl ComponentUiRegistry { ctx, ui, ui_layout, - query, - db, entity_path, component_descr, unit.row_id(), @@ -596,11 +600,9 @@ impl ComponentUiRegistry { #[expect(clippy::too_many_arguments)] pub fn component_ui_raw( &self, - ctx: &ViewerContext<'_>, + ctx: &StoreViewContext<'_>, ui: &mut egui::Ui, ui_layout: UiLayout, - query: &LatestAtQuery, - db: &EntityDb, entity_path: &EntityPath, component_descr: &ComponentDescriptor, row_id: Option, @@ -617,8 +619,6 @@ impl ComponentUiRegistry { ctx, ui, ui_layout, - query, - db, entity_path, component_descr, row_id, @@ -663,7 +663,7 @@ impl ComponentUiRegistry { #[expect(clippy::too_many_arguments)] pub fn variant_ui_raw( &self, - ctx: &ViewerContext<'_>, + ctx: &StoreViewContext<'_>, ui: &mut egui::Ui, ui_layout: UiLayout, variant_name: VariantName, @@ -721,9 +721,9 @@ impl ComponentUiRegistry { #[expect(clippy::too_many_arguments)] pub fn multiline_edit_ui( &self, - ctx: &QueryContext<'_>, + query_ctx: &QueryContext<'_>, + store_view_ctx: &StoreViewContext<'_>, ui: &mut egui::Ui, - origin_db: &EntityDb, blueprint_write_path: EntityPath, component_descr: &ComponentDescriptor, row_id: Option, @@ -731,9 +731,9 @@ impl ComponentUiRegistry { ) { let multiline = true; self.edit_ui( - ctx, + query_ctx, + store_view_ctx, ui, - origin_db, blueprint_write_path, component_descr, row_id, @@ -750,9 +750,9 @@ impl ComponentUiRegistry { #[expect(clippy::too_many_arguments)] pub fn singleline_edit_ui( &self, - ctx: &QueryContext<'_>, + query_ctx: &QueryContext<'_>, + store_view_ctx: &StoreViewContext<'_>, ui: &mut egui::Ui, - origin_db: &EntityDb, blueprint_write_path: EntityPath, component_descr: &ComponentDescriptor, row_id: Option, @@ -760,9 +760,9 @@ impl ComponentUiRegistry { ) { let multiline = false; self.edit_ui( - ctx, + query_ctx, + store_view_ctx, ui, - origin_db, blueprint_write_path, component_descr, row_id, @@ -774,9 +774,9 @@ impl ComponentUiRegistry { #[expect(clippy::too_many_arguments)] fn edit_ui( &self, - ctx: &QueryContext<'_>, + query_ctx: &QueryContext<'_>, + store_view_ctx: &StoreViewContext<'_>, ui: &mut egui::Ui, - origin_db: &EntityDb, blueprint_write_path: EntityPath, component_descr: &ComponentDescriptor, row_id: Option, @@ -787,9 +787,9 @@ impl ComponentUiRegistry { let run_with = |array| { self.edit_ui_raw( - ctx, + query_ctx, + store_view_ctx, ui, - origin_db, blueprint_write_path, component_descr, row_id, @@ -802,10 +802,10 @@ impl ComponentUiRegistry { if let Some(component_array) = component_array.filter(|array| !array.is_empty()) { run_with(component_array); } else { - let fallback = ctx + let fallback = query_ctx .viewer_ctx() .component_fallback_registry - .fallback_for(component_descr, ctx); + .fallback_for(component_descr, query_ctx); run_with(fallback.as_ref()); } } @@ -814,9 +814,9 @@ impl ComponentUiRegistry { #[expect(clippy::too_many_arguments)] pub fn edit_ui_raw( &self, - ctx: &QueryContext<'_>, + query_ctx: &QueryContext<'_>, + store_view_ctx: &StoreViewContext<'_>, ui: &mut egui::Ui, - origin_db: &EntityDb, blueprint_write_path: EntityPath, component_descr: &ComponentDescriptor, row_id: Option, @@ -824,11 +824,11 @@ impl ComponentUiRegistry { allow_multiline: bool, ) { if self.try_show_edit_ui( - ctx.viewer_ctx(), + store_view_ctx, ui, EditTarget { - store_id: ctx.store_ctx().blueprint.store_id().clone(), - timepoint: ctx.store_ctx().blueprint_timepoint_for_writes(), + store_id: query_ctx.store_ctx().blueprint.store_id().clone(), + timepoint: query_ctx.store_ctx().blueprint_timepoint_for_writes(), entity_path: blueprint_write_path, }, component_raw, @@ -838,12 +838,10 @@ impl ComponentUiRegistry { { // Even if we can't edit the component, it's still helpful to show what the value is. self.component_ui_raw( - ctx.viewer_ctx(), + store_view_ctx, ui, UiLayout::List, - &ctx.query, - origin_db, - ctx.target_entity_path, + query_ctx.target_entity_path, component_descr, row_id, component_raw, @@ -857,7 +855,7 @@ impl ComponentUiRegistry { /// Note that single values may have different editors registered than arrays of values. pub fn try_show_edit_ui( &self, - ctx: &ViewerContext<'_>, + ctx: &StoreViewContext<'_>, ui: &mut egui::Ui, target: EditTarget, raw_current_value: &dyn arrow::array::Array, @@ -894,7 +892,7 @@ impl ComponentUiRegistry { timepoint, entity_path, } = target; - ctx.append_array_to_store( + ctx.app_ctx.append_array_to_store( store_id, timepoint, entity_path, diff --git a/crates/viewer/re_viewer_context/src/gpu_bridge/colormap.rs b/crates/viewer/re_viewer_context/src/gpu_bridge/colormap.rs index cca32da59237..9320c0c35ca6 100644 --- a/crates/viewer/re_viewer_context/src/gpu_bridge/colormap.rs +++ b/crates/viewer/re_viewer_context/src/gpu_bridge/colormap.rs @@ -98,7 +98,7 @@ fn colormap_variant_ui( } fn colormap_category_ui( - ctx: &crate::ViewerContext<'_>, + ctx: &crate::StoreViewContext<'_>, ui: &mut egui::Ui, category: ColormapCategory, selected: &mut re_sdk_types::components::Colormap, @@ -130,7 +130,7 @@ fn colormap_category_ui( } pub fn colormap_edit_or_view_ui( - ctx: &crate::ViewerContext<'_>, + ctx: &crate::StoreViewContext<'_>, ui: &mut egui::Ui, map: &mut MaybeMutRef<'_, re_sdk_types::components::Colormap>, ) -> egui::Response { diff --git a/crates/viewer/re_viewer_context/src/gpu_bridge/image_to_gpu.rs b/crates/viewer/re_viewer_context/src/gpu_bridge/image_to_gpu.rs index 6c9f54a3ac4b..d998f9806912 100644 --- a/crates/viewer/re_viewer_context/src/gpu_bridge/image_to_gpu.rs +++ b/crates/viewer/re_viewer_context/src/gpu_bridge/image_to_gpu.rs @@ -47,7 +47,7 @@ pub fn image_to_gpu( debug_name: &str, image: &ImageInfo, image_stats: &ImageStats, - annotations: &Annotations, + annotations: Option<&Annotations>, colormap: Option<&ColormapWithRange>, ) -> anyhow::Result { re_tracing::profile_function!(); @@ -66,14 +66,20 @@ pub fn image_to_gpu( image_stats, colormap, ), - ImageKind::Segmentation => segmentation_image_to_gpu( - render_ctx, - debug_name, - texture_key, - image, - image_stats, - annotations, - ), + ImageKind::Segmentation => { + if let Some(annotations) = annotations { + segmentation_image_to_gpu( + render_ctx, + debug_name, + texture_key, + image, + image_stats, + annotations, + ) + } else { + anyhow::bail!("Missing annotations for segmentation image") + } + } } } diff --git a/crates/viewer/re_viewer_context/src/lib.rs b/crates/viewer/re_viewer_context/src/lib.rs index 7e1a00b9c8c4..badf11d8ddf0 100644 --- a/crates/viewer/re_viewer_context/src/lib.rs +++ b/crates/viewer/re_viewer_context/src/lib.rs @@ -4,6 +4,7 @@ #![warn(clippy::iter_over_hash_type)] // TODO(#6198): enable everywhere +mod active_store_context; mod annotations; mod app_context; mod app_options; @@ -30,8 +31,8 @@ mod recording_or_table; mod route; mod selection_state; mod storage_context; -mod store_context; pub mod store_hub; +mod store_view_context; mod tables; mod tensor; mod time_control; @@ -47,6 +48,7 @@ mod visitor_flow_control; pub use re_ui::UiLayout; +pub use self::active_store_context::ActiveStoreContext; pub use self::annotations::{ AnnotationContextStoreSubscriber, AnnotationMap, Annotations, ResolvedAnnotationInfo, ResolvedAnnotationInfos, @@ -99,8 +101,8 @@ pub use self::selection_state::{ SelectionHighlight, }; pub use self::storage_context::StorageContext; -pub use self::store_context::StoreContext; pub use self::store_hub::StoreHub; +pub use self::store_view_context::StoreViewContext; pub use self::tables::{TableStore, TableStores}; pub use self::tensor::{ImageStats, TensorStats}; pub use self::time_control::{ diff --git a/crates/viewer/re_viewer_context/src/open_url.rs b/crates/viewer/re_viewer_context/src/open_url.rs index 55817436e2df..929fa7e0b365 100644 --- a/crates/viewer/re_viewer_context/src/open_url.rs +++ b/crates/viewer/re_viewer_context/src/open_url.rs @@ -7,8 +7,8 @@ use re_uri::external::url::{self, Url}; use vec1::{Vec1, vec1}; use crate::{ - CommandSender, Item, ItemCollection, Route, StoreHub, SystemCommand, SystemCommandSender as _, - ViewerContext, + AppContext, CommandSender, Item, ItemCollection, Route, StoreHub, SystemCommand, + SystemCommandSender as _, }; /// A URL that points to a selection (typically an entity) within the currently active recording. @@ -244,11 +244,11 @@ pub struct OpenUrlOptions { } impl ViewerOpenUrl { - pub fn from_context(ctx: &ViewerContext<'_>) -> anyhow::Result { + pub fn from_context(ctx: &AppContext<'_>) -> anyhow::Result { Self::from_context_expanded( ctx.store_hub(), - ctx.route(), - Some(ctx.time_ctrl), + ctx.route, + ctx.active_time_ctrl(), ctx.selection(), ) } diff --git a/crates/viewer/re_viewer_context/src/query_context.rs b/crates/viewer/re_viewer_context/src/query_context.rs index 0c1cd82279d1..6aaa9c0d442e 100644 --- a/crates/viewer/re_viewer_context/src/query_context.rs +++ b/crates/viewer/re_viewer_context/src/query_context.rs @@ -8,7 +8,8 @@ use slotmap::SlotMap; use smallvec::SmallVec; use crate::{ - DataResult, StoreContext, ViewContext, ViewId, ViewState, ViewerContext, blueprint_timeline, + ActiveStoreContext, AppContext, DataResult, ViewContext, ViewId, ViewState, ViewerContext, + blueprint_timeline, }; slotmap::new_key_type! { @@ -17,6 +18,8 @@ slotmap::new_key_type! { } /// Context for a latest-at query in a specific view. +/// +/// Never use [`QueryContext`] where [`ViewContext`] would suffice. // TODO(andreas) this is centered around latest-at queries. Does it have to be? Makes sense for UI, but that means it won't scale much into Visualizer queriers. // This is currently used only for fallback providers, but the expectation is that we're using this more widely as the primary context object // in all places where we query a specific entity in a specific view. @@ -42,13 +45,18 @@ pub struct QueryContext<'a> { } impl QueryContext<'_> { + #[inline] + pub fn app_ctx(&self) -> &AppContext<'_> { + &self.view_ctx.viewer_ctx.app_ctx + } + #[inline] pub fn viewer_ctx(&self) -> &ViewerContext<'_> { self.view_ctx.viewer_ctx } #[inline] - pub fn store_ctx(&self) -> &StoreContext<'_> { + pub fn store_ctx(&self) -> &ActiveStoreContext<'_> { self.view_ctx.viewer_ctx.store_context } diff --git a/crates/viewer/re_viewer_context/src/store_hub.rs b/crates/viewer/re_viewer_context/src/store_hub.rs index bf9964abb82c..7d8b259e987f 100644 --- a/crates/viewer/re_viewer_context/src/store_hub.rs +++ b/crates/viewer/re_viewer_context/src/store_hub.rs @@ -18,8 +18,8 @@ use re_sdk_types::archetypes; use re_sdk_types::components::Timestamp; use crate::{ - BlueprintUndoState, Caches, RecordingOrTable, Route, StorageContext, StoreContext, TableStore, - TableStores, + ActiveStoreContext, BlueprintUndoState, Caches, RecordingOrTable, Route, StorageContext, + TableStore, TableStores, }; /// Interface for accessing all blueprints and recordings. @@ -211,11 +211,11 @@ impl StoreHub { &mut self.store_bundle } - /// Get a read-only [`StorageContext`] and [`StoreContext`] from the [`StoreHub`]. + /// Get a read-only [`StorageContext`] and [`ActiveStoreContext`] from the [`StoreHub`]. /// /// All of the returned references to blueprints and recordings will have a /// matching [`ApplicationId`]. - pub fn read_context(&mut self, route: &Route) -> (StorageContext<'_>, StoreContext<'_>) { + pub fn read_context(&mut self, route: &Route) -> (StorageContext<'_>, ActiveStoreContext<'_>) { static EMPTY_RECORDING: LazyLock = LazyLock::new(|| EntityDb::new(re_log_types::StoreId::empty_recording())); static EMPTY_BLUEPRINT: LazyLock = LazyLock::new(|| { @@ -295,7 +295,7 @@ impl StoreHub { &EMPTY_CACHES }); - Some(StoreContext { + Some(ActiveStoreContext { blueprint: active_blueprint, default_blueprint, recording: recording.unwrap_or(&EMPTY_RECORDING), @@ -306,7 +306,7 @@ impl StoreHub { // TODO(RR-3033): the viewer should be robust to not having a `StoreContext`. // We should only have one if we are currently looking at a blueprint. - let store_context = store_context.unwrap_or(StoreContext { + let store_context = store_context.unwrap_or(ActiveStoreContext { blueprint: &EMPTY_BLUEPRINT, default_blueprint: None, recording: &EMPTY_RECORDING, diff --git a/crates/viewer/re_viewer_context/src/store_view_context.rs b/crates/viewer/re_viewer_context/src/store_view_context.rs new file mode 100644 index 000000000000..55fde541dd79 --- /dev/null +++ b/crates/viewer/re_viewer_context/src/store_view_context.rs @@ -0,0 +1,75 @@ +use re_chunk::{Timeline, TimelineName}; +use re_entity_db::EntityDb; + +use crate::{AppContext, AppOptions, Caches, TimeControl}; + +/// Context for viewing a specific store, +/// (either a recording, or a blueprint). +/// +/// Never use [`StoreViewContext`] where [`AppContext`] would suffice. +#[derive(Clone)] +pub struct StoreViewContext<'a> { + pub app_ctx: &'a AppContext<'a>, + + /// The store we are viewing + pub db: &'a EntityDb, + + /// Where the time cursor is at etc + pub time_ctrl: &'a TimeControl, + + /// Needed to display images, videos, etc + pub caches: &'a Caches, +} + +impl<'a> std::ops::Deref for StoreViewContext<'a> { + type Target = AppContext<'a>; + + #[inline] + fn deref(&self) -> &Self::Target { + self.app_ctx + } +} + +impl<'a> StoreViewContext<'a> { + /// Move time cursor + #[must_use] + pub fn with_time_ctrl(&self, time_ctrl: &'a TimeControl) -> Self { + Self { + time_ctrl, + ..self.clone() + } + } + + /// The current time cursor + pub fn query(&self) -> re_chunk_store::LatestAtQuery { + self.time_ctrl.current_query() + } + + /// The currently selected timeline for this store. + pub fn timeline_name(&self) -> TimelineName { + self.query().timeline() + } + + /// The currently selected timeline for this store. + pub fn timeline(&self) -> Timeline { + let name = self.timeline_name(); + let typ = self.db.timeline_type(&name); + Timeline::new(name, typ) + } + + pub fn render_ctx(&self) -> &re_renderer::RenderContext { + self.app_ctx.render_ctx + } + + pub fn app_options(&self) -> &AppOptions { + self.app_ctx.app_options + } + + pub fn component_ui_registry(&self) -> &crate::ComponentUiRegistry { + self.app_ctx.component_ui_registry + } + + pub fn command_sender(&self) -> &crate::CommandSender { + self.app_ctx.command_sender + } +} diff --git a/crates/viewer/re_viewer_context/src/time_control.rs b/crates/viewer/re_viewer_context/src/time_control.rs index ab4bedbe4283..f8838e3d5e7a 100644 --- a/crates/viewer/re_viewer_context/src/time_control.rs +++ b/crates/viewer/re_viewer_context/src/time_control.rs @@ -558,11 +558,17 @@ impl TimeControl { /// Sets the current time. /// - /// If `blueprint_ctx` is some, this will also update the time stored in - /// the blueprint if `time_int` has changed. - fn update_time(&mut self, time: TimeReal) { + /// This will NOT update the blueprint! + pub fn set_time_ad_hoc(&mut self, time: TimeReal) { + self.set_time_cursor_ad_hoc(*self.timeline_name(), time); + } + + /// Sets the current time. + /// + /// This will NOT update the blueprint! + pub fn set_time_cursor_ad_hoc(&mut self, timeline: TimelineName, time: TimeReal) { self.states - .entry(*self.timeline_name()) + .entry(timeline) .or_insert_with(|| TimeState::new(time)) .time = time; } @@ -633,7 +639,7 @@ impl TimeControl { if self.loop_mode == LoopMode::Off && full_range.max() <= state.time { // We've reached the end of the data - self.update_time(full_range.max().into()); + self.set_time_ad_hoc(full_range.max().into()); if more_data_is_streaming_in { // then let's wait for it without pausing! @@ -666,7 +672,7 @@ impl TimeControl { new_time = loop_range.min; // loop! } - self.update_time(new_time); + self.set_time_ad_hoc(new_time); NeedsRepaint::Yes } @@ -674,7 +680,7 @@ impl TimeControl { } PlayState::Following => { // Set the time to the max: - self.update_time(full_range.max().into()); + self.set_time_ad_hoc(full_range.max().into()); NeedsRepaint::No // no need for request_repaint - we already repaint when new data arrives } diff --git a/crates/viewer/re_viewer_context/src/utils/video.rs b/crates/viewer/re_viewer_context/src/utils/video.rs index 1957dafa05bd..c49c3bb17b7c 100644 --- a/crates/viewer/re_viewer_context/src/utils/video.rs +++ b/crates/viewer/re_viewer_context/src/utils/video.rs @@ -1,8 +1,8 @@ -use crate::ViewerContext; +use crate::TimeControl; /// Convert a video timestamp from component to a video time. pub fn video_timestamp_component_to_video_time( - ctx: &ViewerContext<'_>, + time_ctrl: Option<&TimeControl>, video_timestamp: re_sdk_types::components::VideoTimestamp, timescale: Option, ) -> re_video::Time { @@ -10,7 +10,8 @@ pub fn video_timestamp_component_to_video_time( re_video::Time::from_nanos(video_timestamp.as_nanos(), timescale) } else { // If there's no timescale, assume that timestamps are frames and use our currently set fps. - let fps = ctx.time_ctrl.fps().unwrap_or(1.0); + const DEFAULT_FPS: f32 = 30.0; + let fps = time_ctrl.and_then(|tc| tc.fps()).unwrap_or(DEFAULT_FPS); re_video::Time((video_timestamp.0.0 as f64 * fps as f64) as i64) } } diff --git a/crates/viewer/re_viewer_context/src/view/view_context.rs b/crates/viewer/re_viewer_context/src/view/view_context.rs index 4e07fb152759..4f1bc06f432a 100644 --- a/crates/viewer/re_viewer_context/src/view/view_context.rs +++ b/crates/viewer/re_viewer_context/src/view/view_context.rs @@ -5,18 +5,20 @@ use re_sdk_types::blueprint::components::VisualizerInstructionId; use re_sdk_types::{AsComponents, ComponentBatch, ComponentDescriptor, ViewClassIdentifier}; use super::VisualizerCollection; -use crate::blueprint_helpers::BlueprintContext as _; use crate::{DataQueryResult, DataResult, QueryContext, ViewId}; +use crate::{ViewerContext, blueprint_helpers::BlueprintContext as _}; /// The context associated with a view. /// -/// This combines our [`crate::ViewerContext`] with [`crate::ViewState`] +/// This combines our [`ViewerContext`] with [`crate::ViewState`] /// and other view-specific information. This is used as the interface for /// execution of view systems and selection panel UI elements that happen /// within the context of a view to simplify plumbing of the necessary /// information to resolve a query with possible overrides and fallback values. +/// +/// Never use [`ViewContext`] where [`ViewerContext`] would suffice. pub struct ViewContext<'a> { - pub viewer_ctx: &'a crate::ViewerContext<'a>, + pub viewer_ctx: &'a ViewerContext<'a>, pub view_id: ViewId, pub view_class_identifier: ViewClassIdentifier, // TODO(RR-3076): Eventually we want to get rid of the _general_ concept of `space_origin`. diff --git a/crates/viewer/re_viewer_context/src/viewer_context.rs b/crates/viewer/re_viewer_context/src/viewer_context.rs index 3a66beed805a..41be70048b5e 100644 --- a/crates/viewer/re_viewer_context/src/viewer_context.rs +++ b/crates/viewer/re_viewer_context/src/viewer_context.rs @@ -1,25 +1,26 @@ use ahash::HashMap; +use re_chunk::EntityPath; use re_chunk_store::LatestAtQuery; use re_entity_db::entity_db::EntityDb; use re_log_types::{EntryId, TableId}; use re_query::StorageEngineReadGuard; use re_sdk_types::ViewClassIdentifier; -use re_ui::ContextExt as _; use re_ui::list_item::ListItem; use crate::command_sender::{SelectionSource, SetSelection}; use crate::component_fallbacks::FallbackProviderRegistry; -use crate::drag_and_drop::DragAndDropPayload; use crate::query_context::DataQueryResult; use crate::time_control::TimeControlCommand; use crate::{ - AppContext, AppOptions, ApplicationSelectionState, CommandSender, ComponentUiRegistry, - DragAndDropManager, IndicatedEntities, Item, ItemCollection, PerVisualizerType, - PerVisualizerTypeInViewClass, StoreContext, StoreHub, SystemCommand, SystemCommandSender as _, - TimeControl, ViewClassRegistry, ViewId, VisualizableEntities, + ActiveStoreContext, AppContext, AppOptions, ApplicationSelectionState, CommandSender, + ComponentUiRegistry, DragAndDropManager, IndicatedEntities, Item, ItemCollection, + PerVisualizerType, PerVisualizerTypeInViewClass, StoreHub, StoreViewContext, SystemCommand, + SystemCommandSender as _, TimeControl, ViewClassRegistry, ViewId, VisualizableEntities, }; -/// Common things needed to view a specific recording with a specific blueprint. +/// The most powerful context, when you need to know the active blueprint and views. +/// +/// Never use [`ViewerContext`] where [`StoreViewContext`] would suffice. pub struct ViewerContext<'a> { /// App context shared across all parts of the viewer. pub app_ctx: AppContext<'a>, @@ -56,7 +57,7 @@ pub struct ViewerContext<'a> { pub connected_receivers: &'a re_log_channel::LogReceiverSet, /// The active recording and blueprint. - pub store_context: &'a StoreContext<'a>, + pub store_context: &'a ActiveStoreContext<'a>, } // Forwarding of `AppContext` methods to `ViewerContext`. Leaving this as a @@ -68,7 +69,7 @@ impl ViewerContext<'_> { } pub fn tokens(&self) -> &'static re_ui::DesignTokens { - self.egui_ctx().tokens() + self.app_ctx.tokens() } /// Runtime info about components and archetypes. @@ -96,14 +97,10 @@ impl ViewerContext<'_> { self.app_ctx.render_ctx } - /// How to configure the renderer + /// How to configure the renderer. #[inline] pub fn render_mode(&self) -> re_renderer::RenderMode { - if self.app_ctx.is_test { - re_renderer::RenderMode::Deterministic - } else { - re_renderer::RenderMode::Beautiful - } + self.app_ctx.render_mode() } /// Interface for sending commands back to the app @@ -118,21 +115,58 @@ impl ViewerContext<'_> { /// The [`StoreHub`]. pub fn store_hub(&self) -> &StoreHub { - self.app_ctx.storage_context.hub + self.app_ctx.store_hub() } /// All loaded recordings, blueprints, etc. pub fn store_bundle(&self) -> &re_entity_db::StoreBundle { - self.app_ctx.storage_context.bundle + self.app_ctx.store_bundle() } /// All loaded tables. pub fn table_stores(&self) -> &crate::TableStores { - self.app_ctx.storage_context.tables + self.app_ctx.table_stores() } } -impl ViewerContext<'_> { +impl<'a> ViewerContext<'a> { + /// Create a [`crate::StoreViewContext`] for the active recording. + pub fn active_recording_store_view_context(&'a self) -> StoreViewContext<'a> { + StoreViewContext { + app_ctx: &self.app_ctx, + db: self.store_context.recording, + time_ctrl: self.time_ctrl, + caches: self.store_context.caches, + } + } + + /// Create a [`crate::StoreViewContext`] for the active blueprint. + pub fn blueprint_store_view_ctx(&self) -> StoreViewContext<'_> { + StoreViewContext { + app_ctx: &self.app_ctx, + db: self.store_context.blueprint, + time_ctrl: self.blueprint_time_ctrl, + caches: self.store_context.caches, // TODO(RR-3033): what cache to use here? + } + } + + /// If the user is inspecting the blueprint, and the `entity_path` is on the blueprint + /// timeline, then use the blueprint. Otherwise, use the recording. + // TODO(jleibs): Ideally this wouldn't be necessary and we could make the assessment + // directly from the entity_path. + pub fn guess_store_view_context_for_entity( + &self, + entity_path: &EntityPath, + ) -> StoreViewContext<'_> { + if self.app_options().inspect_blueprint_timeline + && self.store_context.blueprint.is_logged_entity(entity_path) + { + self.blueprint_store_view_ctx() + } else { + self.active_recording_store_view_context() + } + } + /// The active recording. #[inline] pub fn recording(&self) -> &EntityDb { @@ -194,14 +228,15 @@ impl ViewerContext<'_> { /// Item that got focused on the last frame if any. pub fn focused_item(&self) -> &Option { - self.app_ctx.focused_item + self.app_ctx.focused_item() } /// Helper object to manage drag-and-drop operations. pub fn drag_and_drop_manager(&self) -> &DragAndDropManager { - self.app_ctx.drag_and_drop_manager + self.app_ctx.drag_and_drop_manager() } + /// The current time cursor pub fn current_query(&self) -> re_chunk_store::LatestAtQuery { self.time_ctrl.current_query() } @@ -222,155 +257,15 @@ impl ViewerContext<'_> { /// Consistently handle the selection, hover, drag start interactions for a given set of items. /// - /// The `draggable` parameter controls whether a drag can be initiated from this item. When a UI - /// element represents an [`crate::Item`], one must make the call whether this element should be - /// meaningfully draggable by the users. This is ultimately a subjective decision, but some here - /// are some guidelines: - /// - Is there a meaningful destination for the dragged payload? For example, dragging stuff out - /// of a modal dialog is by definition meaningless. - /// - Even if a drag destination exists, would that be obvious to the user? - /// - Is it expected for that kind of UI element to be draggable? For example, buttons aren't - /// typically draggable. - /// - /// Drag vs. selection semantics: - /// - /// - When dragging an unselected item, that item only is dragged, and the selection is - /// unchanged… - /// - …unless cmd/ctrl is held, in which case the item is added to the selection and the entire - /// selection is dragged. - /// - When dragging a selected item, the entire selection is dragged as well. - /// - /// You might also want to call [`Self::handle_select_focus_sync`] to keep keyboard focus in - /// sync with selection. + /// See [`AppContext::handle_select_hover_drag_interactions`] for details. pub fn handle_select_hover_drag_interactions( &self, response: &egui::Response, interacted_items: impl Into, draggable: bool, ) { - let mut interacted_items = interacted_items - .into() - .into_mono_instance_path_items(self.recording(), &self.current_query()); - let selection_state = self.selection_state(); - - if response.hovered() { - selection_state.set_hovered(interacted_items.clone()); - } - - let single_selected = self.selection().single_item() == interacted_items.single_item(); - - // If we were just selected, scroll into view - if single_selected && self.selection_state().selection_changed().is_some() { - response.scroll_to_me(None); - } - - if draggable && response.drag_started() { - let mut selected_items = selection_state.selected_items().clone(); - let is_already_selected = interacted_items - .iter() - .all(|(item, _)| selected_items.contains_item(item)); - - let is_cmd_held = response.ctx.input(|i| i.modifiers.command); - - // see semantics description in the docstring - let dragged_items = if !is_already_selected && is_cmd_held { - selected_items.extend(interacted_items); - self.command_sender() - .send_system(SystemCommand::set_selection(selected_items.clone())); - selected_items - } else if !is_already_selected { - interacted_items - } else { - selected_items - }; - - let items_may_be_dragged = self - .app_ctx - .drag_and_drop_manager - .are_items_draggable(&dragged_items); - - let payload = if items_may_be_dragged { - DragAndDropPayload::from_items(&dragged_items) - } else { - DragAndDropPayload::Invalid - }; - - egui::DragAndDrop::set_payload(&response.ctx, payload); - } else if response.clicked() { - if response.double_clicked() - && let Some(item) = interacted_items.first_item() - { - // Double click always selects the whole instance and nothing else. - let item = if let Item::DataResult(data_result) = item { - interacted_items = Item::DataResult(data_result.as_entity_all()).into(); - interacted_items - .first_item() - .expect("That item was just added") - } else { - item - }; - - self.app_ctx - .command_sender - .send_system(crate::SystemCommand::SetFocus(item.clone())); - } - - let modifiers = response.ctx.input(|i| i.modifiers); - - // Shift-clicking means extending the selection. This generally requires local context, - // so we don't handle it here. - if !modifiers.shift { - if modifiers.command { - // Sends a command to select `ìnteracted_items` unless already selected in which case they get unselected. - // If however an object is already selected but now gets passed a *different* item context, it stays selected after all - // but with an updated context! - - let mut toggle_items_set: HashMap<_, _> = interacted_items - .iter() - .map(|(item, ctx)| (item.clone(), ctx.clone())) - .collect(); - - let mut new_selection = selection_state.selected_items().clone(); - - // If an item was already selected with the exact same context remove it. - // If an item was already selected and loses its context, remove it. - new_selection.retain(|item, ctx| { - if let Some(new_ctx) = toggle_items_set.get(item) { - if new_ctx == ctx || new_ctx.is_none() { - toggle_items_set.remove(item); - false - } else { - true - } - } else { - true - } - }); - - // Update context for items that are remaining in the toggle_item_set: - for (item, ctx) in new_selection.iter_mut() { - if let Some(new_ctx) = toggle_items_set.get(item) { - *ctx = new_ctx.clone(); - toggle_items_set.remove(item); - } - } - - // Make sure we preserve the order - old items kept in same order, new items added to the end. - // Add the new items, unless they were toggling out existing items: - new_selection.extend( - interacted_items - .into_iter() - .filter(|(item, _)| toggle_items_set.contains_key(item)), - ); - - self.command_sender() - .send_system(SystemCommand::set_selection(new_selection)); - } else { - self.command_sender() - .send_system(SystemCommand::set_selection(interacted_items)); - } - } - } + self.app_ctx + .handle_select_hover_drag_interactions(response, interacted_items, draggable); } /// Helper to synchronize item selection with egui focus. @@ -446,9 +341,9 @@ impl ViewerContext<'_> { self.recording().application_id() != StoreHub::welcome_screen_app_id() } - /// Reverts to the default route + /// Reverts to the default route. pub fn revert_to_default_route(&self) { - self.command_sender().send_system(SystemCommand::ResetRoute); + self.app_ctx.revert_to_default_route(); } /// Iterates over all entities that are visualizeable for a given view class. diff --git a/crates/viewer/re_viewport_blueprint/benches/data_query.rs b/crates/viewer/re_viewport_blueprint/benches/data_query.rs index c5f7010e0e08..a6dd87fc71b1 100644 --- a/crates/viewer/re_viewport_blueprint/benches/data_query.rs +++ b/crates/viewer/re_viewport_blueprint/benches/data_query.rs @@ -12,7 +12,7 @@ use re_sdk_types::archetypes::Points2D; use re_sdk_types::components::Position2D; use re_types_core::ViewClassIdentifier; use re_viewer_context::{ - Caches, PerVisualizerType, PerVisualizerTypeInViewClass, QueryRange, StoreContext, + ActiveStoreContext, Caches, PerVisualizerType, PerVisualizerTypeInViewClass, QueryRange, ViewClassRegistry, VisualizableEntities, VisualizableReason, blueprint_timeline, }; use re_viewport_blueprint::ViewContents; @@ -74,7 +74,7 @@ fn query_tree_many_entities(c: &mut Criterion) { "bench_app", )); - let ctx = StoreContext { + let ctx = ActiveStoreContext { blueprint: &blueprint, default_blueprint: None, recording: &recording, diff --git a/crates/viewer/re_viewport_blueprint/src/view.rs b/crates/viewer/re_viewport_blueprint/src/view.rs index 24c415441f4d..2d64e551256a 100644 --- a/crates/viewer/re_viewport_blueprint/src/view.rs +++ b/crates/viewer/re_viewport_blueprint/src/view.rs @@ -9,9 +9,9 @@ use re_sdk_types::blueprint::components::{self as blueprint_components, ViewOrig use re_sdk_types::components::{Name, Visible}; use re_types_core::Archetype as _; use re_viewer_context::{ - BlueprintContext as _, ContentsName, QueryRange, RecommendedView, StoreContext, SystemCommand, - SystemCommandSender as _, ViewClass, ViewClassRegistry, ViewContext, ViewId, ViewState, - ViewStates, ViewerContext, + ActiveStoreContext, BlueprintContext as _, ContentsName, QueryRange, RecommendedView, + SystemCommand, SystemCommandSender as _, ViewClass, ViewClassRegistry, ViewContext, ViewId, + ViewState, ViewStates, ViewerContext, }; use crate::{ViewContents, ViewProperty}; @@ -237,7 +237,7 @@ impl ViewBlueprint { /// Creates a new [`ViewBlueprint`] with the same contents, but a different [`ViewId`] /// /// Also duplicates all the queries in the view. - pub fn duplicate(&self, store_context: &StoreContext<'_>, query: &LatestAtQuery) -> Self { + pub fn duplicate(&self, store_context: &ActiveStoreContext<'_>, query: &LatestAtQuery) -> Self { let mut pending_writes = Vec::new(); let blueprint = store_context.blueprint; let blueprint_engine = blueprint.storage_engine(); diff --git a/crates/viewer/re_viewport_blueprint/src/view_contents.rs b/crates/viewer/re_viewport_blueprint/src/view_contents.rs index 6f5af3f2f235..f8e767f326a1 100644 --- a/crates/viewer/re_viewport_blueprint/src/view_contents.rs +++ b/crates/viewer/re_viewport_blueprint/src/view_contents.rs @@ -260,7 +260,7 @@ impl ViewContents { #[expect(clippy::too_many_arguments)] pub fn build_data_result_tree( &self, - ctx: &re_viewer_context::StoreContext<'_>, + ctx: &re_viewer_context::ActiveStoreContext<'_>, active_timeline: Option<&Timeline>, view_class_registry: &re_viewer_context::ViewClassRegistry, blueprint_query: &LatestAtQuery, @@ -729,7 +729,7 @@ mod tests { use re_log_types::example_components::{MyPoint, MyPoints}; use re_log_types::{StoreId, TimePoint, Timeline}; use re_viewer_context::{ - Caches, StoreContext, ViewClassRegistry, VisualizableReason, blueprint_timeline, + ActiveStoreContext, Caches, ViewClassRegistry, VisualizableReason, blueprint_timeline, }; use super::*; @@ -788,7 +788,7 @@ mod tests { ) }); - let ctx = StoreContext { + let ctx = ActiveStoreContext { blueprint: &blueprint, default_blueprint: None, recording: &recording, diff --git a/examples/rust/custom_view/src/points3d_color_view.rs b/examples/rust/custom_view/src/points3d_color_view.rs index c710c5b94e78..63c558136857 100644 --- a/examples/rust/custom_view/src/points3d_color_view.rs +++ b/examples/rust/custom_view/src/points3d_color_view.rs @@ -293,21 +293,9 @@ fn color_space_ui( // Update the global selection state if the user interacts with a point and show hover ui for the entire keypoint. let instance = InstancePath::instance(ent_path.clone(), *instance); let interact = interact.on_hover_ui_at_pointer(|ui| { - item_ui::instance_path_button( - ctx, - &ctx.current_query(), - ctx.recording(), - ui, - Some(query.view_id), - &instance, - ); - instance.data_ui( - ctx, - ui, - UiLayout::Tooltip, - &ctx.current_query(), - ctx.recording(), - ); + let store_view_ctx = ctx.active_recording_store_view_context(); + item_ui::instance_path_button(&store_view_ctx, ui, Some(query.view_id), &instance); + instance.data_ui(&store_view_ctx, ui, UiLayout::Tooltip); hovering_any_point = true; }); From 7ff0710edbb1a3282c19b588d5330f0a714e50de Mon Sep 17 00:00:00 2001 From: Andreas Reich Date: Tue, 3 Mar 2026 11:43:24 +0100 Subject: [PATCH 017/513] Adjust range_zip & clamped_zip codegen ### Related * Split out of https://github.com/rerun-io/reality/pull/969 ### What I need _even_ more optional components on `range_zip` in an upcoming PR. Also `clamped_zip` is _almost_ unused now, so I trimmed it a bit. In fact I intend to get rid of `clamped_zip`, maybe already as part of a refactor around fallback handling, but I'll have to revisit that. --------- Source-Ref: 2a32684cf5abefcbe878828d7327638d933e0895 Co-authored-by: Claude Opus 4.6 --- crates/store/re_query/src/bin/clamped_zip.rs | 25 +- crates/store/re_query/src/bin/range_zip.rs | 12 +- .../re_query/src/clamped_zip/generated.rs | 2034 +---------------- .../store/re_query/src/range_zip/generated.rs | 851 ++++++- scripts/ci/check_large_files.py | 2 +- 5 files changed, 883 insertions(+), 2041 deletions(-) diff --git a/crates/store/re_query/src/bin/clamped_zip.rs b/crates/store/re_query/src/bin/clamped_zip.rs index 7b110f4b49d1..dd385c3903c7 100644 --- a/crates/store/re_query/src/bin/clamped_zip.rs +++ b/crates/store/re_query/src/bin/clamped_zip.rs @@ -257,16 +257,15 @@ fn generate_impl(params: &Params) -> String { .collect_vec() .join(", "); - let next = - params - .to_required_names() - .into_iter() - .map(|r| format!("let {r}_next = self.{r}.next()?;")) - .chain(params.to_optional_names().into_iter().map(|o| { - format!("let {o}_next = self.{o}.next().or(self.{o}_latest_value.take());") - })) - .collect_vec() - .join("\n"); + let next = params + .to_required_names() + .into_iter() + .map(|r| format!("let {r}_next = self.{r}.next()?;")) + .chain(params.to_optional_names().into_iter().map(|o| { + format!("let {o}_next = self.{o}.next().or_else(|| self.{o}_latest_value.take());") + })) + .collect_vec() + .join("\n"); let update_latest = params .to_optional_names() @@ -314,8 +313,8 @@ fn generate_impl(params: &Params) -> String { } fn main() { - let num_required = 1..3; - let num_optional = 1..10; + let num_required = 1..=2; + let num_optional = 1..=5; let output = num_required .flat_map(|num_required| { @@ -340,7 +339,7 @@ fn main() { println!( " - // This file was generated using `cargo r -p re_query --all-features --bin clamped_zip`. + // This file was generated using `cargo r -p re_query --all-features --bin clamped_zip > crates/store/re_query/src/clamped_zip/generated.rs && cargo fmt`. // DO NOT EDIT. // --- diff --git a/crates/store/re_query/src/bin/range_zip.rs b/crates/store/re_query/src/bin/range_zip.rs index 964f94d708e2..71718717e6a3 100644 --- a/crates/store/re_query/src/bin/range_zip.rs +++ b/crates/store/re_query/src/bin/range_zip.rs @@ -320,14 +320,14 @@ fn generate_struct(params: &Params) -> String { /// while let Some((_, data)) = o0.next_if(|(index, _)| index <= &max_index) { /// o0_data = Some(data); /// } -/// let o0_data = o0_data.or(o0_data_latest.take()); +/// let o0_data = o0_data.or_else(|| o0_data_latest.take()); /// o0_data_latest.clone_from(&o0_data); /// /// let mut o1_data = None; /// while let Some((_, data)) = o1.next_if(|(index, _)| index <= &max_index) { /// o1_data = Some(data); /// } -/// let o1_data = o1_data.or(o1_data_latest.take()); +/// let o1_data = o1_data.or_else(|| o1_data_latest.take()); /// o1_data_latest.clone_from(&o1_data); /// /// Some((max_index, r0_data, r1_data, o0_data, o1_data)) @@ -419,7 +419,7 @@ fn generate_impl(params: &Params) -> String { while let Some((_, data)) = {o}.next_if(|(index, _)| index <= &max_index) {{ {o}_data = Some(data); }} - let {o}_data = {o}_data.or({o}_data_latest.take()); + let {o}_data = {o}_data.or_else(|| {o}_data_latest.take()); {o}_data_latest.clone_from(&{o}_data); " ) @@ -456,8 +456,8 @@ fn generate_impl(params: &Params) -> String { } fn main() { - let num_required = 1..3; - let num_optional = 1..10; + let num_required = 1..=2; + let num_optional = 1..=10; let output = num_required .flat_map(|num_required| { @@ -482,7 +482,7 @@ fn main() { println!( " - // This file was generated using `cargo r -p re_query --all-features --bin range_zip`. + // This file was generated using `cargo r -p re_query --all-features --bin range_zip > crates/store/re_query/src/range_zip/generated.rs && cargo fmt`. // DO NOT EDIT. // --- diff --git a/crates/store/re_query/src/clamped_zip/generated.rs b/crates/store/re_query/src/clamped_zip/generated.rs index b1aa5e54fc56..9b2fa42a4d9b 100644 --- a/crates/store/re_query/src/clamped_zip/generated.rs +++ b/crates/store/re_query/src/clamped_zip/generated.rs @@ -1,4 +1,4 @@ -// This file was generated using `cargo r -p re_query --all-features --bin clamped_zip`. +// This file was generated using `cargo r -p re_query --all-features --bin clamped_zip > crates/store/re_query/src/clamped_zip/generated.rs && cargo fmt`. // DO NOT EDIT. // --- @@ -577,1581 +577,102 @@ where } } -/// Returns a new [`ClampedZip1x6`] iterator. -/// -/// The number of elements in a clamped zip iterator corresponds to the number of elements in the -/// shortest of its required iterators (`r0`). -/// -/// Optional iterators (`o0`, `o1`, `o2`, `o3`, `o4`, `o5`) will repeat their latest values if they happen to be too short -/// to be zipped with the shortest of the required iterators. -/// -/// If an optional iterator is not only too short but actually empty, its associated default function -/// (`o0_default_fn`, `o1_default_fn`, `o2_default_fn`, `o3_default_fn`, `o4_default_fn`, `o5_default_fn`) will be executed and the resulting value repeated as necessary. -pub fn clamped_zip_1x6( - r0: R0, - o0: O0, - o0_default_fn: D0, - o1: O1, - o1_default_fn: D1, - o2: O2, - o2_default_fn: D2, - o3: O3, - o3_default_fn: D3, - o4: O4, - o4_default_fn: D4, - o5: O5, - o5_default_fn: D5, -) -> ClampedZip1x6< - R0::IntoIter, - O0::IntoIter, - O1::IntoIter, - O2::IntoIter, - O3::IntoIter, - O4::IntoIter, - O5::IntoIter, - D0, - D1, - D2, - D3, - D4, - D5, -> -where - R0: IntoIterator, - O0: IntoIterator, - O0::Item: Clone, - O1: IntoIterator, - O1::Item: Clone, - O2: IntoIterator, - O2::Item: Clone, - O3: IntoIterator, - O3::Item: Clone, - O4: IntoIterator, - O4::Item: Clone, - O5: IntoIterator, - O5::Item: Clone, - D0: Fn() -> O0::Item, - D1: Fn() -> O1::Item, - D2: Fn() -> O2::Item, - D3: Fn() -> O3::Item, - D4: Fn() -> O4::Item, - D5: Fn() -> O5::Item, -{ - ClampedZip1x6 { - r0: r0.into_iter(), - o0: o0.into_iter(), - o1: o1.into_iter(), - o2: o2.into_iter(), - o3: o3.into_iter(), - o4: o4.into_iter(), - o5: o5.into_iter(), - o0_default_fn, - o1_default_fn, - o2_default_fn, - o3_default_fn, - o4_default_fn, - o5_default_fn, - o0_latest_value: None, - o1_latest_value: None, - o2_latest_value: None, - o3_latest_value: None, - o4_latest_value: None, - o5_latest_value: None, - } -} - -/// Implements a clamped zip iterator combinator with 2 required iterators and 2 optional -/// iterators. -/// -/// See [`clamped_zip_1x6`] for more information. -pub struct ClampedZip1x6 -where - R0: Iterator, - O0: Iterator, - O0::Item: Clone, - O1: Iterator, - O1::Item: Clone, - O2: Iterator, - O2::Item: Clone, - O3: Iterator, - O3::Item: Clone, - O4: Iterator, - O4::Item: Clone, - O5: Iterator, - O5::Item: Clone, - D0: Fn() -> O0::Item, - D1: Fn() -> O1::Item, - D2: Fn() -> O2::Item, - D3: Fn() -> O3::Item, - D4: Fn() -> O4::Item, - D5: Fn() -> O5::Item, -{ - r0: R0, - o0: O0, - o1: O1, - o2: O2, - o3: O3, - o4: O4, - o5: O5, - o0_default_fn: D0, - o1_default_fn: D1, - o2_default_fn: D2, - o3_default_fn: D3, - o4_default_fn: D4, - o5_default_fn: D5, - - o0_latest_value: Option, - o1_latest_value: Option, - o2_latest_value: Option, - o3_latest_value: Option, - o4_latest_value: Option, - o5_latest_value: Option, -} - -impl Iterator - for ClampedZip1x6 -where - R0: Iterator, - O0: Iterator, - O0::Item: Clone, - O1: Iterator, - O1::Item: Clone, - O2: Iterator, - O2::Item: Clone, - O3: Iterator, - O3::Item: Clone, - O4: Iterator, - O4::Item: Clone, - O5: Iterator, - O5::Item: Clone, - D0: Fn() -> O0::Item, - D1: Fn() -> O1::Item, - D2: Fn() -> O2::Item, - D3: Fn() -> O3::Item, - D4: Fn() -> O4::Item, - D5: Fn() -> O5::Item, -{ - type Item = ( - R0::Item, - O0::Item, - O1::Item, - O2::Item, - O3::Item, - O4::Item, - O5::Item, - ); - - #[inline] - fn next(&mut self) -> Option { - let r0_next = self.r0.next()?; - let o0_next = self.o0.next().or_else(|| self.o0_latest_value.take()); - let o1_next = self.o1.next().or_else(|| self.o1_latest_value.take()); - let o2_next = self.o2.next().or_else(|| self.o2_latest_value.take()); - let o3_next = self.o3.next().or_else(|| self.o3_latest_value.take()); - let o4_next = self.o4.next().or_else(|| self.o4_latest_value.take()); - let o5_next = self.o5.next().or_else(|| self.o5_latest_value.take()); - - self.o0_latest_value.clone_from(&o0_next); - self.o1_latest_value.clone_from(&o1_next); - self.o2_latest_value.clone_from(&o2_next); - self.o3_latest_value.clone_from(&o3_next); - self.o4_latest_value.clone_from(&o4_next); - self.o5_latest_value.clone_from(&o5_next); - - Some(( - r0_next, - o0_next.unwrap_or_else(|| (self.o0_default_fn)()), - o1_next.unwrap_or_else(|| (self.o1_default_fn)()), - o2_next.unwrap_or_else(|| (self.o2_default_fn)()), - o3_next.unwrap_or_else(|| (self.o3_default_fn)()), - o4_next.unwrap_or_else(|| (self.o4_default_fn)()), - o5_next.unwrap_or_else(|| (self.o5_default_fn)()), - )) - } -} - -/// Returns a new [`ClampedZip1x7`] iterator. -/// -/// The number of elements in a clamped zip iterator corresponds to the number of elements in the -/// shortest of its required iterators (`r0`). -/// -/// Optional iterators (`o0`, `o1`, `o2`, `o3`, `o4`, `o5`, `o6`) will repeat their latest values if they happen to be too short -/// to be zipped with the shortest of the required iterators. -/// -/// If an optional iterator is not only too short but actually empty, its associated default function -/// (`o0_default_fn`, `o1_default_fn`, `o2_default_fn`, `o3_default_fn`, `o4_default_fn`, `o5_default_fn`, `o6_default_fn`) will be executed and the resulting value repeated as necessary. -pub fn clamped_zip_1x7( - r0: R0, - o0: O0, - o0_default_fn: D0, - o1: O1, - o1_default_fn: D1, - o2: O2, - o2_default_fn: D2, - o3: O3, - o3_default_fn: D3, - o4: O4, - o4_default_fn: D4, - o5: O5, - o5_default_fn: D5, - o6: O6, - o6_default_fn: D6, -) -> ClampedZip1x7< - R0::IntoIter, - O0::IntoIter, - O1::IntoIter, - O2::IntoIter, - O3::IntoIter, - O4::IntoIter, - O5::IntoIter, - O6::IntoIter, - D0, - D1, - D2, - D3, - D4, - D5, - D6, -> -where - R0: IntoIterator, - O0: IntoIterator, - O0::Item: Clone, - O1: IntoIterator, - O1::Item: Clone, - O2: IntoIterator, - O2::Item: Clone, - O3: IntoIterator, - O3::Item: Clone, - O4: IntoIterator, - O4::Item: Clone, - O5: IntoIterator, - O5::Item: Clone, - O6: IntoIterator, - O6::Item: Clone, - D0: Fn() -> O0::Item, - D1: Fn() -> O1::Item, - D2: Fn() -> O2::Item, - D3: Fn() -> O3::Item, - D4: Fn() -> O4::Item, - D5: Fn() -> O5::Item, - D6: Fn() -> O6::Item, -{ - ClampedZip1x7 { - r0: r0.into_iter(), - o0: o0.into_iter(), - o1: o1.into_iter(), - o2: o2.into_iter(), - o3: o3.into_iter(), - o4: o4.into_iter(), - o5: o5.into_iter(), - o6: o6.into_iter(), - o0_default_fn, - o1_default_fn, - o2_default_fn, - o3_default_fn, - o4_default_fn, - o5_default_fn, - o6_default_fn, - o0_latest_value: None, - o1_latest_value: None, - o2_latest_value: None, - o3_latest_value: None, - o4_latest_value: None, - o5_latest_value: None, - o6_latest_value: None, - } -} - -/// Implements a clamped zip iterator combinator with 2 required iterators and 2 optional -/// iterators. -/// -/// See [`clamped_zip_1x7`] for more information. -pub struct ClampedZip1x7 -where - R0: Iterator, - O0: Iterator, - O0::Item: Clone, - O1: Iterator, - O1::Item: Clone, - O2: Iterator, - O2::Item: Clone, - O3: Iterator, - O3::Item: Clone, - O4: Iterator, - O4::Item: Clone, - O5: Iterator, - O5::Item: Clone, - O6: Iterator, - O6::Item: Clone, - D0: Fn() -> O0::Item, - D1: Fn() -> O1::Item, - D2: Fn() -> O2::Item, - D3: Fn() -> O3::Item, - D4: Fn() -> O4::Item, - D5: Fn() -> O5::Item, - D6: Fn() -> O6::Item, -{ - r0: R0, - o0: O0, - o1: O1, - o2: O2, - o3: O3, - o4: O4, - o5: O5, - o6: O6, - o0_default_fn: D0, - o1_default_fn: D1, - o2_default_fn: D2, - o3_default_fn: D3, - o4_default_fn: D4, - o5_default_fn: D5, - o6_default_fn: D6, - - o0_latest_value: Option, - o1_latest_value: Option, - o2_latest_value: Option, - o3_latest_value: Option, - o4_latest_value: Option, - o5_latest_value: Option, - o6_latest_value: Option, -} - -impl Iterator - for ClampedZip1x7 -where - R0: Iterator, - O0: Iterator, - O0::Item: Clone, - O1: Iterator, - O1::Item: Clone, - O2: Iterator, - O2::Item: Clone, - O3: Iterator, - O3::Item: Clone, - O4: Iterator, - O4::Item: Clone, - O5: Iterator, - O5::Item: Clone, - O6: Iterator, - O6::Item: Clone, - D0: Fn() -> O0::Item, - D1: Fn() -> O1::Item, - D2: Fn() -> O2::Item, - D3: Fn() -> O3::Item, - D4: Fn() -> O4::Item, - D5: Fn() -> O5::Item, - D6: Fn() -> O6::Item, -{ - type Item = ( - R0::Item, - O0::Item, - O1::Item, - O2::Item, - O3::Item, - O4::Item, - O5::Item, - O6::Item, - ); - - #[inline] - fn next(&mut self) -> Option { - let r0_next = self.r0.next()?; - let o0_next = self.o0.next().or_else(|| self.o0_latest_value.take()); - let o1_next = self.o1.next().or_else(|| self.o1_latest_value.take()); - let o2_next = self.o2.next().or_else(|| self.o2_latest_value.take()); - let o3_next = self.o3.next().or_else(|| self.o3_latest_value.take()); - let o4_next = self.o4.next().or_else(|| self.o4_latest_value.take()); - let o5_next = self.o5.next().or_else(|| self.o5_latest_value.take()); - let o6_next = self.o6.next().or_else(|| self.o6_latest_value.take()); - - self.o0_latest_value.clone_from(&o0_next); - self.o1_latest_value.clone_from(&o1_next); - self.o2_latest_value.clone_from(&o2_next); - self.o3_latest_value.clone_from(&o3_next); - self.o4_latest_value.clone_from(&o4_next); - self.o5_latest_value.clone_from(&o5_next); - self.o6_latest_value.clone_from(&o6_next); - - Some(( - r0_next, - o0_next.unwrap_or_else(|| (self.o0_default_fn)()), - o1_next.unwrap_or_else(|| (self.o1_default_fn)()), - o2_next.unwrap_or_else(|| (self.o2_default_fn)()), - o3_next.unwrap_or_else(|| (self.o3_default_fn)()), - o4_next.unwrap_or_else(|| (self.o4_default_fn)()), - o5_next.unwrap_or_else(|| (self.o5_default_fn)()), - o6_next.unwrap_or_else(|| (self.o6_default_fn)()), - )) - } -} - -/// Returns a new [`ClampedZip1x8`] iterator. -/// -/// The number of elements in a clamped zip iterator corresponds to the number of elements in the -/// shortest of its required iterators (`r0`). -/// -/// Optional iterators (`o0`, `o1`, `o2`, `o3`, `o4`, `o5`, `o6`, `o7`) will repeat their latest values if they happen to be too short -/// to be zipped with the shortest of the required iterators. -/// -/// If an optional iterator is not only too short but actually empty, its associated default function -/// (`o0_default_fn`, `o1_default_fn`, `o2_default_fn`, `o3_default_fn`, `o4_default_fn`, `o5_default_fn`, `o6_default_fn`, `o7_default_fn`) will be executed and the resulting value repeated as necessary. -pub fn clamped_zip_1x8( - r0: R0, - o0: O0, - o0_default_fn: D0, - o1: O1, - o1_default_fn: D1, - o2: O2, - o2_default_fn: D2, - o3: O3, - o3_default_fn: D3, - o4: O4, - o4_default_fn: D4, - o5: O5, - o5_default_fn: D5, - o6: O6, - o6_default_fn: D6, - o7: O7, - o7_default_fn: D7, -) -> ClampedZip1x8< - R0::IntoIter, - O0::IntoIter, - O1::IntoIter, - O2::IntoIter, - O3::IntoIter, - O4::IntoIter, - O5::IntoIter, - O6::IntoIter, - O7::IntoIter, - D0, - D1, - D2, - D3, - D4, - D5, - D6, - D7, -> -where - R0: IntoIterator, - O0: IntoIterator, - O0::Item: Clone, - O1: IntoIterator, - O1::Item: Clone, - O2: IntoIterator, - O2::Item: Clone, - O3: IntoIterator, - O3::Item: Clone, - O4: IntoIterator, - O4::Item: Clone, - O5: IntoIterator, - O5::Item: Clone, - O6: IntoIterator, - O6::Item: Clone, - O7: IntoIterator, - O7::Item: Clone, - D0: Fn() -> O0::Item, - D1: Fn() -> O1::Item, - D2: Fn() -> O2::Item, - D3: Fn() -> O3::Item, - D4: Fn() -> O4::Item, - D5: Fn() -> O5::Item, - D6: Fn() -> O6::Item, - D7: Fn() -> O7::Item, -{ - ClampedZip1x8 { - r0: r0.into_iter(), - o0: o0.into_iter(), - o1: o1.into_iter(), - o2: o2.into_iter(), - o3: o3.into_iter(), - o4: o4.into_iter(), - o5: o5.into_iter(), - o6: o6.into_iter(), - o7: o7.into_iter(), - o0_default_fn, - o1_default_fn, - o2_default_fn, - o3_default_fn, - o4_default_fn, - o5_default_fn, - o6_default_fn, - o7_default_fn, - o0_latest_value: None, - o1_latest_value: None, - o2_latest_value: None, - o3_latest_value: None, - o4_latest_value: None, - o5_latest_value: None, - o6_latest_value: None, - o7_latest_value: None, - } -} - -/// Implements a clamped zip iterator combinator with 2 required iterators and 2 optional -/// iterators. -/// -/// See [`clamped_zip_1x8`] for more information. -pub struct ClampedZip1x8 -where - R0: Iterator, - O0: Iterator, - O0::Item: Clone, - O1: Iterator, - O1::Item: Clone, - O2: Iterator, - O2::Item: Clone, - O3: Iterator, - O3::Item: Clone, - O4: Iterator, - O4::Item: Clone, - O5: Iterator, - O5::Item: Clone, - O6: Iterator, - O6::Item: Clone, - O7: Iterator, - O7::Item: Clone, - D0: Fn() -> O0::Item, - D1: Fn() -> O1::Item, - D2: Fn() -> O2::Item, - D3: Fn() -> O3::Item, - D4: Fn() -> O4::Item, - D5: Fn() -> O5::Item, - D6: Fn() -> O6::Item, - D7: Fn() -> O7::Item, -{ - r0: R0, - o0: O0, - o1: O1, - o2: O2, - o3: O3, - o4: O4, - o5: O5, - o6: O6, - o7: O7, - o0_default_fn: D0, - o1_default_fn: D1, - o2_default_fn: D2, - o3_default_fn: D3, - o4_default_fn: D4, - o5_default_fn: D5, - o6_default_fn: D6, - o7_default_fn: D7, - - o0_latest_value: Option, - o1_latest_value: Option, - o2_latest_value: Option, - o3_latest_value: Option, - o4_latest_value: Option, - o5_latest_value: Option, - o6_latest_value: Option, - o7_latest_value: Option, -} - -impl Iterator - for ClampedZip1x8 -where - R0: Iterator, - O0: Iterator, - O0::Item: Clone, - O1: Iterator, - O1::Item: Clone, - O2: Iterator, - O2::Item: Clone, - O3: Iterator, - O3::Item: Clone, - O4: Iterator, - O4::Item: Clone, - O5: Iterator, - O5::Item: Clone, - O6: Iterator, - O6::Item: Clone, - O7: Iterator, - O7::Item: Clone, - D0: Fn() -> O0::Item, - D1: Fn() -> O1::Item, - D2: Fn() -> O2::Item, - D3: Fn() -> O3::Item, - D4: Fn() -> O4::Item, - D5: Fn() -> O5::Item, - D6: Fn() -> O6::Item, - D7: Fn() -> O7::Item, -{ - type Item = ( - R0::Item, - O0::Item, - O1::Item, - O2::Item, - O3::Item, - O4::Item, - O5::Item, - O6::Item, - O7::Item, - ); - - #[inline] - fn next(&mut self) -> Option { - let r0_next = self.r0.next()?; - let o0_next = self.o0.next().or_else(|| self.o0_latest_value.take()); - let o1_next = self.o1.next().or_else(|| self.o1_latest_value.take()); - let o2_next = self.o2.next().or_else(|| self.o2_latest_value.take()); - let o3_next = self.o3.next().or_else(|| self.o3_latest_value.take()); - let o4_next = self.o4.next().or_else(|| self.o4_latest_value.take()); - let o5_next = self.o5.next().or_else(|| self.o5_latest_value.take()); - let o6_next = self.o6.next().or_else(|| self.o6_latest_value.take()); - let o7_next = self.o7.next().or_else(|| self.o7_latest_value.take()); - - self.o0_latest_value.clone_from(&o0_next); - self.o1_latest_value.clone_from(&o1_next); - self.o2_latest_value.clone_from(&o2_next); - self.o3_latest_value.clone_from(&o3_next); - self.o4_latest_value.clone_from(&o4_next); - self.o5_latest_value.clone_from(&o5_next); - self.o6_latest_value.clone_from(&o6_next); - self.o7_latest_value.clone_from(&o7_next); - - Some(( - r0_next, - o0_next.unwrap_or_else(|| (self.o0_default_fn)()), - o1_next.unwrap_or_else(|| (self.o1_default_fn)()), - o2_next.unwrap_or_else(|| (self.o2_default_fn)()), - o3_next.unwrap_or_else(|| (self.o3_default_fn)()), - o4_next.unwrap_or_else(|| (self.o4_default_fn)()), - o5_next.unwrap_or_else(|| (self.o5_default_fn)()), - o6_next.unwrap_or_else(|| (self.o6_default_fn)()), - o7_next.unwrap_or_else(|| (self.o7_default_fn)()), - )) - } -} - -/// Returns a new [`ClampedZip1x9`] iterator. -/// -/// The number of elements in a clamped zip iterator corresponds to the number of elements in the -/// shortest of its required iterators (`r0`). -/// -/// Optional iterators (`o0`, `o1`, `o2`, `o3`, `o4`, `o5`, `o6`, `o7`, `o8`) will repeat their latest values if they happen to be too short -/// to be zipped with the shortest of the required iterators. -/// -/// If an optional iterator is not only too short but actually empty, its associated default function -/// (`o0_default_fn`, `o1_default_fn`, `o2_default_fn`, `o3_default_fn`, `o4_default_fn`, `o5_default_fn`, `o6_default_fn`, `o7_default_fn`, `o8_default_fn`) will be executed and the resulting value repeated as necessary. -pub fn clamped_zip_1x9( - r0: R0, - o0: O0, - o0_default_fn: D0, - o1: O1, - o1_default_fn: D1, - o2: O2, - o2_default_fn: D2, - o3: O3, - o3_default_fn: D3, - o4: O4, - o4_default_fn: D4, - o5: O5, - o5_default_fn: D5, - o6: O6, - o6_default_fn: D6, - o7: O7, - o7_default_fn: D7, - o8: O8, - o8_default_fn: D8, -) -> ClampedZip1x9< - R0::IntoIter, - O0::IntoIter, - O1::IntoIter, - O2::IntoIter, - O3::IntoIter, - O4::IntoIter, - O5::IntoIter, - O6::IntoIter, - O7::IntoIter, - O8::IntoIter, - D0, - D1, - D2, - D3, - D4, - D5, - D6, - D7, - D8, -> -where - R0: IntoIterator, - O0: IntoIterator, - O0::Item: Clone, - O1: IntoIterator, - O1::Item: Clone, - O2: IntoIterator, - O2::Item: Clone, - O3: IntoIterator, - O3::Item: Clone, - O4: IntoIterator, - O4::Item: Clone, - O5: IntoIterator, - O5::Item: Clone, - O6: IntoIterator, - O6::Item: Clone, - O7: IntoIterator, - O7::Item: Clone, - O8: IntoIterator, - O8::Item: Clone, - D0: Fn() -> O0::Item, - D1: Fn() -> O1::Item, - D2: Fn() -> O2::Item, - D3: Fn() -> O3::Item, - D4: Fn() -> O4::Item, - D5: Fn() -> O5::Item, - D6: Fn() -> O6::Item, - D7: Fn() -> O7::Item, - D8: Fn() -> O8::Item, -{ - ClampedZip1x9 { - r0: r0.into_iter(), - o0: o0.into_iter(), - o1: o1.into_iter(), - o2: o2.into_iter(), - o3: o3.into_iter(), - o4: o4.into_iter(), - o5: o5.into_iter(), - o6: o6.into_iter(), - o7: o7.into_iter(), - o8: o8.into_iter(), - o0_default_fn, - o1_default_fn, - o2_default_fn, - o3_default_fn, - o4_default_fn, - o5_default_fn, - o6_default_fn, - o7_default_fn, - o8_default_fn, - o0_latest_value: None, - o1_latest_value: None, - o2_latest_value: None, - o3_latest_value: None, - o4_latest_value: None, - o5_latest_value: None, - o6_latest_value: None, - o7_latest_value: None, - o8_latest_value: None, - } -} - -/// Implements a clamped zip iterator combinator with 2 required iterators and 2 optional -/// iterators. -/// -/// See [`clamped_zip_1x9`] for more information. -pub struct ClampedZip1x9 -where - R0: Iterator, - O0: Iterator, - O0::Item: Clone, - O1: Iterator, - O1::Item: Clone, - O2: Iterator, - O2::Item: Clone, - O3: Iterator, - O3::Item: Clone, - O4: Iterator, - O4::Item: Clone, - O5: Iterator, - O5::Item: Clone, - O6: Iterator, - O6::Item: Clone, - O7: Iterator, - O7::Item: Clone, - O8: Iterator, - O8::Item: Clone, - D0: Fn() -> O0::Item, - D1: Fn() -> O1::Item, - D2: Fn() -> O2::Item, - D3: Fn() -> O3::Item, - D4: Fn() -> O4::Item, - D5: Fn() -> O5::Item, - D6: Fn() -> O6::Item, - D7: Fn() -> O7::Item, - D8: Fn() -> O8::Item, -{ - r0: R0, - o0: O0, - o1: O1, - o2: O2, - o3: O3, - o4: O4, - o5: O5, - o6: O6, - o7: O7, - o8: O8, - o0_default_fn: D0, - o1_default_fn: D1, - o2_default_fn: D2, - o3_default_fn: D3, - o4_default_fn: D4, - o5_default_fn: D5, - o6_default_fn: D6, - o7_default_fn: D7, - o8_default_fn: D8, - - o0_latest_value: Option, - o1_latest_value: Option, - o2_latest_value: Option, - o3_latest_value: Option, - o4_latest_value: Option, - o5_latest_value: Option, - o6_latest_value: Option, - o7_latest_value: Option, - o8_latest_value: Option, -} - -impl Iterator - for ClampedZip1x9 -where - R0: Iterator, - O0: Iterator, - O0::Item: Clone, - O1: Iterator, - O1::Item: Clone, - O2: Iterator, - O2::Item: Clone, - O3: Iterator, - O3::Item: Clone, - O4: Iterator, - O4::Item: Clone, - O5: Iterator, - O5::Item: Clone, - O6: Iterator, - O6::Item: Clone, - O7: Iterator, - O7::Item: Clone, - O8: Iterator, - O8::Item: Clone, - D0: Fn() -> O0::Item, - D1: Fn() -> O1::Item, - D2: Fn() -> O2::Item, - D3: Fn() -> O3::Item, - D4: Fn() -> O4::Item, - D5: Fn() -> O5::Item, - D6: Fn() -> O6::Item, - D7: Fn() -> O7::Item, - D8: Fn() -> O8::Item, -{ - type Item = ( - R0::Item, - O0::Item, - O1::Item, - O2::Item, - O3::Item, - O4::Item, - O5::Item, - O6::Item, - O7::Item, - O8::Item, - ); - - #[inline] - fn next(&mut self) -> Option { - let r0_next = self.r0.next()?; - let o0_next = self.o0.next().or_else(|| self.o0_latest_value.take()); - let o1_next = self.o1.next().or_else(|| self.o1_latest_value.take()); - let o2_next = self.o2.next().or_else(|| self.o2_latest_value.take()); - let o3_next = self.o3.next().or_else(|| self.o3_latest_value.take()); - let o4_next = self.o4.next().or_else(|| self.o4_latest_value.take()); - let o5_next = self.o5.next().or_else(|| self.o5_latest_value.take()); - let o6_next = self.o6.next().or_else(|| self.o6_latest_value.take()); - let o7_next = self.o7.next().or_else(|| self.o7_latest_value.take()); - let o8_next = self.o8.next().or_else(|| self.o8_latest_value.take()); - - self.o0_latest_value.clone_from(&o0_next); - self.o1_latest_value.clone_from(&o1_next); - self.o2_latest_value.clone_from(&o2_next); - self.o3_latest_value.clone_from(&o3_next); - self.o4_latest_value.clone_from(&o4_next); - self.o5_latest_value.clone_from(&o5_next); - self.o6_latest_value.clone_from(&o6_next); - self.o7_latest_value.clone_from(&o7_next); - self.o8_latest_value.clone_from(&o8_next); - - Some(( - r0_next, - o0_next.unwrap_or_else(|| (self.o0_default_fn)()), - o1_next.unwrap_or_else(|| (self.o1_default_fn)()), - o2_next.unwrap_or_else(|| (self.o2_default_fn)()), - o3_next.unwrap_or_else(|| (self.o3_default_fn)()), - o4_next.unwrap_or_else(|| (self.o4_default_fn)()), - o5_next.unwrap_or_else(|| (self.o5_default_fn)()), - o6_next.unwrap_or_else(|| (self.o6_default_fn)()), - o7_next.unwrap_or_else(|| (self.o7_default_fn)()), - o8_next.unwrap_or_else(|| (self.o8_default_fn)()), - )) - } -} - -/// Returns a new [`ClampedZip2x1`] iterator. -/// -/// The number of elements in a clamped zip iterator corresponds to the number of elements in the -/// shortest of its required iterators (`r0`, `r1`). -/// -/// Optional iterators (`o0`) will repeat their latest values if they happen to be too short -/// to be zipped with the shortest of the required iterators. -/// -/// If an optional iterator is not only too short but actually empty, its associated default function -/// (`o0_default_fn`) will be executed and the resulting value repeated as necessary. -pub fn clamped_zip_2x1( - r0: R0, - r1: R1, - o0: O0, - o0_default_fn: D0, -) -> ClampedZip2x1 -where - R0: IntoIterator, - R1: IntoIterator, - O0: IntoIterator, - O0::Item: Clone, - D0: Fn() -> O0::Item, -{ - ClampedZip2x1 { - r0: r0.into_iter(), - r1: r1.into_iter(), - o0: o0.into_iter(), - o0_default_fn, - o0_latest_value: None, - } -} - -/// Implements a clamped zip iterator combinator with 2 required iterators and 2 optional -/// iterators. -/// -/// See [`clamped_zip_2x1`] for more information. -pub struct ClampedZip2x1 -where - R0: Iterator, - R1: Iterator, - O0: Iterator, - O0::Item: Clone, - D0: Fn() -> O0::Item, -{ - r0: R0, - r1: R1, - o0: O0, - o0_default_fn: D0, - - o0_latest_value: Option, -} - -impl Iterator for ClampedZip2x1 -where - R0: Iterator, - R1: Iterator, - O0: Iterator, - O0::Item: Clone, - D0: Fn() -> O0::Item, -{ - type Item = (R0::Item, R1::Item, O0::Item); - - #[inline] - fn next(&mut self) -> Option { - let r0_next = self.r0.next()?; - let r1_next = self.r1.next()?; - let o0_next = self.o0.next().or_else(|| self.o0_latest_value.take()); - - self.o0_latest_value.clone_from(&o0_next); - - Some(( - r0_next, - r1_next, - o0_next.unwrap_or_else(|| (self.o0_default_fn)()), - )) - } -} - -/// Returns a new [`ClampedZip2x2`] iterator. -/// -/// The number of elements in a clamped zip iterator corresponds to the number of elements in the -/// shortest of its required iterators (`r0`, `r1`). -/// -/// Optional iterators (`o0`, `o1`) will repeat their latest values if they happen to be too short -/// to be zipped with the shortest of the required iterators. -/// -/// If an optional iterator is not only too short but actually empty, its associated default function -/// (`o0_default_fn`, `o1_default_fn`) will be executed and the resulting value repeated as necessary. -pub fn clamped_zip_2x2( - r0: R0, - r1: R1, - o0: O0, - o0_default_fn: D0, - o1: O1, - o1_default_fn: D1, -) -> ClampedZip2x2 -where - R0: IntoIterator, - R1: IntoIterator, - O0: IntoIterator, - O0::Item: Clone, - O1: IntoIterator, - O1::Item: Clone, - D0: Fn() -> O0::Item, - D1: Fn() -> O1::Item, -{ - ClampedZip2x2 { - r0: r0.into_iter(), - r1: r1.into_iter(), - o0: o0.into_iter(), - o1: o1.into_iter(), - o0_default_fn, - o1_default_fn, - o0_latest_value: None, - o1_latest_value: None, - } -} - -/// Implements a clamped zip iterator combinator with 2 required iterators and 2 optional -/// iterators. -/// -/// See [`clamped_zip_2x2`] for more information. -pub struct ClampedZip2x2 -where - R0: Iterator, - R1: Iterator, - O0: Iterator, - O0::Item: Clone, - O1: Iterator, - O1::Item: Clone, - D0: Fn() -> O0::Item, - D1: Fn() -> O1::Item, -{ - r0: R0, - r1: R1, - o0: O0, - o1: O1, - o0_default_fn: D0, - o1_default_fn: D1, - - o0_latest_value: Option, - o1_latest_value: Option, -} - -impl Iterator for ClampedZip2x2 -where - R0: Iterator, - R1: Iterator, - O0: Iterator, - O0::Item: Clone, - O1: Iterator, - O1::Item: Clone, - D0: Fn() -> O0::Item, - D1: Fn() -> O1::Item, -{ - type Item = (R0::Item, R1::Item, O0::Item, O1::Item); - - #[inline] - fn next(&mut self) -> Option { - let r0_next = self.r0.next()?; - let r1_next = self.r1.next()?; - let o0_next = self.o0.next().or_else(|| self.o0_latest_value.take()); - let o1_next = self.o1.next().or_else(|| self.o1_latest_value.take()); - - self.o0_latest_value.clone_from(&o0_next); - self.o1_latest_value.clone_from(&o1_next); - - Some(( - r0_next, - r1_next, - o0_next.unwrap_or_else(|| (self.o0_default_fn)()), - o1_next.unwrap_or_else(|| (self.o1_default_fn)()), - )) - } -} - -/// Returns a new [`ClampedZip2x3`] iterator. -/// -/// The number of elements in a clamped zip iterator corresponds to the number of elements in the -/// shortest of its required iterators (`r0`, `r1`). -/// -/// Optional iterators (`o0`, `o1`, `o2`) will repeat their latest values if they happen to be too short -/// to be zipped with the shortest of the required iterators. -/// -/// If an optional iterator is not only too short but actually empty, its associated default function -/// (`o0_default_fn`, `o1_default_fn`, `o2_default_fn`) will be executed and the resulting value repeated as necessary. -pub fn clamped_zip_2x3( - r0: R0, - r1: R1, - o0: O0, - o0_default_fn: D0, - o1: O1, - o1_default_fn: D1, - o2: O2, - o2_default_fn: D2, -) -> ClampedZip2x3 -where - R0: IntoIterator, - R1: IntoIterator, - O0: IntoIterator, - O0::Item: Clone, - O1: IntoIterator, - O1::Item: Clone, - O2: IntoIterator, - O2::Item: Clone, - D0: Fn() -> O0::Item, - D1: Fn() -> O1::Item, - D2: Fn() -> O2::Item, -{ - ClampedZip2x3 { - r0: r0.into_iter(), - r1: r1.into_iter(), - o0: o0.into_iter(), - o1: o1.into_iter(), - o2: o2.into_iter(), - o0_default_fn, - o1_default_fn, - o2_default_fn, - o0_latest_value: None, - o1_latest_value: None, - o2_latest_value: None, - } -} - -/// Implements a clamped zip iterator combinator with 2 required iterators and 2 optional -/// iterators. -/// -/// See [`clamped_zip_2x3`] for more information. -pub struct ClampedZip2x3 -where - R0: Iterator, - R1: Iterator, - O0: Iterator, - O0::Item: Clone, - O1: Iterator, - O1::Item: Clone, - O2: Iterator, - O2::Item: Clone, - D0: Fn() -> O0::Item, - D1: Fn() -> O1::Item, - D2: Fn() -> O2::Item, -{ - r0: R0, - r1: R1, - o0: O0, - o1: O1, - o2: O2, - o0_default_fn: D0, - o1_default_fn: D1, - o2_default_fn: D2, - - o0_latest_value: Option, - o1_latest_value: Option, - o2_latest_value: Option, -} - -impl Iterator for ClampedZip2x3 -where - R0: Iterator, - R1: Iterator, - O0: Iterator, - O0::Item: Clone, - O1: Iterator, - O1::Item: Clone, - O2: Iterator, - O2::Item: Clone, - D0: Fn() -> O0::Item, - D1: Fn() -> O1::Item, - D2: Fn() -> O2::Item, -{ - type Item = (R0::Item, R1::Item, O0::Item, O1::Item, O2::Item); - - #[inline] - fn next(&mut self) -> Option { - let r0_next = self.r0.next()?; - let r1_next = self.r1.next()?; - let o0_next = self.o0.next().or_else(|| self.o0_latest_value.take()); - let o1_next = self.o1.next().or_else(|| self.o1_latest_value.take()); - let o2_next = self.o2.next().or_else(|| self.o2_latest_value.take()); - - self.o0_latest_value.clone_from(&o0_next); - self.o1_latest_value.clone_from(&o1_next); - self.o2_latest_value.clone_from(&o2_next); - - Some(( - r0_next, - r1_next, - o0_next.unwrap_or_else(|| (self.o0_default_fn)()), - o1_next.unwrap_or_else(|| (self.o1_default_fn)()), - o2_next.unwrap_or_else(|| (self.o2_default_fn)()), - )) - } -} - -/// Returns a new [`ClampedZip2x4`] iterator. -/// -/// The number of elements in a clamped zip iterator corresponds to the number of elements in the -/// shortest of its required iterators (`r0`, `r1`). -/// -/// Optional iterators (`o0`, `o1`, `o2`, `o3`) will repeat their latest values if they happen to be too short -/// to be zipped with the shortest of the required iterators. -/// -/// If an optional iterator is not only too short but actually empty, its associated default function -/// (`o0_default_fn`, `o1_default_fn`, `o2_default_fn`, `o3_default_fn`) will be executed and the resulting value repeated as necessary. -pub fn clamped_zip_2x4( - r0: R0, - r1: R1, - o0: O0, - o0_default_fn: D0, - o1: O1, - o1_default_fn: D1, - o2: O2, - o2_default_fn: D2, - o3: O3, - o3_default_fn: D3, -) -> ClampedZip2x4< - R0::IntoIter, - R1::IntoIter, - O0::IntoIter, - O1::IntoIter, - O2::IntoIter, - O3::IntoIter, - D0, - D1, - D2, - D3, -> -where - R0: IntoIterator, - R1: IntoIterator, - O0: IntoIterator, - O0::Item: Clone, - O1: IntoIterator, - O1::Item: Clone, - O2: IntoIterator, - O2::Item: Clone, - O3: IntoIterator, - O3::Item: Clone, - D0: Fn() -> O0::Item, - D1: Fn() -> O1::Item, - D2: Fn() -> O2::Item, - D3: Fn() -> O3::Item, -{ - ClampedZip2x4 { - r0: r0.into_iter(), - r1: r1.into_iter(), - o0: o0.into_iter(), - o1: o1.into_iter(), - o2: o2.into_iter(), - o3: o3.into_iter(), - o0_default_fn, - o1_default_fn, - o2_default_fn, - o3_default_fn, - o0_latest_value: None, - o1_latest_value: None, - o2_latest_value: None, - o3_latest_value: None, - } -} - -/// Implements a clamped zip iterator combinator with 2 required iterators and 2 optional -/// iterators. -/// -/// See [`clamped_zip_2x4`] for more information. -pub struct ClampedZip2x4 -where - R0: Iterator, - R1: Iterator, - O0: Iterator, - O0::Item: Clone, - O1: Iterator, - O1::Item: Clone, - O2: Iterator, - O2::Item: Clone, - O3: Iterator, - O3::Item: Clone, - D0: Fn() -> O0::Item, - D1: Fn() -> O1::Item, - D2: Fn() -> O2::Item, - D3: Fn() -> O3::Item, -{ - r0: R0, - r1: R1, - o0: O0, - o1: O1, - o2: O2, - o3: O3, - o0_default_fn: D0, - o1_default_fn: D1, - o2_default_fn: D2, - o3_default_fn: D3, - - o0_latest_value: Option, - o1_latest_value: Option, - o2_latest_value: Option, - o3_latest_value: Option, -} - -impl Iterator - for ClampedZip2x4 -where - R0: Iterator, - R1: Iterator, - O0: Iterator, - O0::Item: Clone, - O1: Iterator, - O1::Item: Clone, - O2: Iterator, - O2::Item: Clone, - O3: Iterator, - O3::Item: Clone, - D0: Fn() -> O0::Item, - D1: Fn() -> O1::Item, - D2: Fn() -> O2::Item, - D3: Fn() -> O3::Item, -{ - type Item = (R0::Item, R1::Item, O0::Item, O1::Item, O2::Item, O3::Item); - - #[inline] - fn next(&mut self) -> Option { - let r0_next = self.r0.next()?; - let r1_next = self.r1.next()?; - let o0_next = self.o0.next().or_else(|| self.o0_latest_value.take()); - let o1_next = self.o1.next().or_else(|| self.o1_latest_value.take()); - let o2_next = self.o2.next().or_else(|| self.o2_latest_value.take()); - let o3_next = self.o3.next().or_else(|| self.o3_latest_value.take()); - - self.o0_latest_value.clone_from(&o0_next); - self.o1_latest_value.clone_from(&o1_next); - self.o2_latest_value.clone_from(&o2_next); - self.o3_latest_value.clone_from(&o3_next); - - Some(( - r0_next, - r1_next, - o0_next.unwrap_or_else(|| (self.o0_default_fn)()), - o1_next.unwrap_or_else(|| (self.o1_default_fn)()), - o2_next.unwrap_or_else(|| (self.o2_default_fn)()), - o3_next.unwrap_or_else(|| (self.o3_default_fn)()), - )) - } -} - -/// Returns a new [`ClampedZip2x5`] iterator. +/// Returns a new [`ClampedZip2x1`] iterator. /// /// The number of elements in a clamped zip iterator corresponds to the number of elements in the /// shortest of its required iterators (`r0`, `r1`). /// -/// Optional iterators (`o0`, `o1`, `o2`, `o3`, `o4`) will repeat their latest values if they happen to be too short +/// Optional iterators (`o0`) will repeat their latest values if they happen to be too short /// to be zipped with the shortest of the required iterators. /// /// If an optional iterator is not only too short but actually empty, its associated default function -/// (`o0_default_fn`, `o1_default_fn`, `o2_default_fn`, `o3_default_fn`, `o4_default_fn`) will be executed and the resulting value repeated as necessary. -pub fn clamped_zip_2x5( +/// (`o0_default_fn`) will be executed and the resulting value repeated as necessary. +pub fn clamped_zip_2x1( r0: R0, r1: R1, o0: O0, o0_default_fn: D0, - o1: O1, - o1_default_fn: D1, - o2: O2, - o2_default_fn: D2, - o3: O3, - o3_default_fn: D3, - o4: O4, - o4_default_fn: D4, -) -> ClampedZip2x5< - R0::IntoIter, - R1::IntoIter, - O0::IntoIter, - O1::IntoIter, - O2::IntoIter, - O3::IntoIter, - O4::IntoIter, - D0, - D1, - D2, - D3, - D4, -> +) -> ClampedZip2x1 where R0: IntoIterator, R1: IntoIterator, O0: IntoIterator, O0::Item: Clone, - O1: IntoIterator, - O1::Item: Clone, - O2: IntoIterator, - O2::Item: Clone, - O3: IntoIterator, - O3::Item: Clone, - O4: IntoIterator, - O4::Item: Clone, D0: Fn() -> O0::Item, - D1: Fn() -> O1::Item, - D2: Fn() -> O2::Item, - D3: Fn() -> O3::Item, - D4: Fn() -> O4::Item, { - ClampedZip2x5 { + ClampedZip2x1 { r0: r0.into_iter(), r1: r1.into_iter(), o0: o0.into_iter(), - o1: o1.into_iter(), - o2: o2.into_iter(), - o3: o3.into_iter(), - o4: o4.into_iter(), o0_default_fn, - o1_default_fn, - o2_default_fn, - o3_default_fn, - o4_default_fn, o0_latest_value: None, - o1_latest_value: None, - o2_latest_value: None, - o3_latest_value: None, - o4_latest_value: None, } } /// Implements a clamped zip iterator combinator with 2 required iterators and 2 optional /// iterators. /// -/// See [`clamped_zip_2x5`] for more information. -pub struct ClampedZip2x5 +/// See [`clamped_zip_2x1`] for more information. +pub struct ClampedZip2x1 where R0: Iterator, R1: Iterator, O0: Iterator, O0::Item: Clone, - O1: Iterator, - O1::Item: Clone, - O2: Iterator, - O2::Item: Clone, - O3: Iterator, - O3::Item: Clone, - O4: Iterator, - O4::Item: Clone, D0: Fn() -> O0::Item, - D1: Fn() -> O1::Item, - D2: Fn() -> O2::Item, - D3: Fn() -> O3::Item, - D4: Fn() -> O4::Item, { r0: R0, r1: R1, o0: O0, - o1: O1, - o2: O2, - o3: O3, - o4: O4, o0_default_fn: D0, - o1_default_fn: D1, - o2_default_fn: D2, - o3_default_fn: D3, - o4_default_fn: D4, o0_latest_value: Option, - o1_latest_value: Option, - o2_latest_value: Option, - o3_latest_value: Option, - o4_latest_value: Option, } -impl Iterator - for ClampedZip2x5 +impl Iterator for ClampedZip2x1 where R0: Iterator, - R1: Iterator, - O0: Iterator, - O0::Item: Clone, - O1: Iterator, - O1::Item: Clone, - O2: Iterator, - O2::Item: Clone, - O3: Iterator, - O3::Item: Clone, - O4: Iterator, - O4::Item: Clone, + R1: Iterator, + O0: Iterator, + O0::Item: Clone, D0: Fn() -> O0::Item, - D1: Fn() -> O1::Item, - D2: Fn() -> O2::Item, - D3: Fn() -> O3::Item, - D4: Fn() -> O4::Item, { - type Item = ( - R0::Item, - R1::Item, - O0::Item, - O1::Item, - O2::Item, - O3::Item, - O4::Item, - ); + type Item = (R0::Item, R1::Item, O0::Item); #[inline] fn next(&mut self) -> Option { let r0_next = self.r0.next()?; let r1_next = self.r1.next()?; let o0_next = self.o0.next().or_else(|| self.o0_latest_value.take()); - let o1_next = self.o1.next().or_else(|| self.o1_latest_value.take()); - let o2_next = self.o2.next().or_else(|| self.o2_latest_value.take()); - let o3_next = self.o3.next().or_else(|| self.o3_latest_value.take()); - let o4_next = self.o4.next().or_else(|| self.o4_latest_value.take()); self.o0_latest_value.clone_from(&o0_next); - self.o1_latest_value.clone_from(&o1_next); - self.o2_latest_value.clone_from(&o2_next); - self.o3_latest_value.clone_from(&o3_next); - self.o4_latest_value.clone_from(&o4_next); Some(( r0_next, r1_next, o0_next.unwrap_or_else(|| (self.o0_default_fn)()), - o1_next.unwrap_or_else(|| (self.o1_default_fn)()), - o2_next.unwrap_or_else(|| (self.o2_default_fn)()), - o3_next.unwrap_or_else(|| (self.o3_default_fn)()), - o4_next.unwrap_or_else(|| (self.o4_default_fn)()), )) } } -/// Returns a new [`ClampedZip2x6`] iterator. +/// Returns a new [`ClampedZip2x2`] iterator. /// /// The number of elements in a clamped zip iterator corresponds to the number of elements in the /// shortest of its required iterators (`r0`, `r1`). /// -/// Optional iterators (`o0`, `o1`, `o2`, `o3`, `o4`, `o5`) will repeat their latest values if they happen to be too short +/// Optional iterators (`o0`, `o1`) will repeat their latest values if they happen to be too short /// to be zipped with the shortest of the required iterators. /// /// If an optional iterator is not only too short but actually empty, its associated default function -/// (`o0_default_fn`, `o1_default_fn`, `o2_default_fn`, `o3_default_fn`, `o4_default_fn`, `o5_default_fn`) will be executed and the resulting value repeated as necessary. -pub fn clamped_zip_2x6( +/// (`o0_default_fn`, `o1_default_fn`) will be executed and the resulting value repeated as necessary. +pub fn clamped_zip_2x2( r0: R0, r1: R1, o0: O0, o0_default_fn: D0, o1: O1, o1_default_fn: D1, - o2: O2, - o2_default_fn: D2, - o3: O3, - o3_default_fn: D3, - o4: O4, - o4_default_fn: D4, - o5: O5, - o5_default_fn: D5, -) -> ClampedZip2x6< - R0::IntoIter, - R1::IntoIter, - O0::IntoIter, - O1::IntoIter, - O2::IntoIter, - O3::IntoIter, - O4::IntoIter, - O5::IntoIter, - D0, - D1, - D2, - D3, - D4, - D5, -> +) -> ClampedZip2x2 where R0: IntoIterator, R1: IntoIterator, @@ -2159,50 +680,26 @@ where O0::Item: Clone, O1: IntoIterator, O1::Item: Clone, - O2: IntoIterator, - O2::Item: Clone, - O3: IntoIterator, - O3::Item: Clone, - O4: IntoIterator, - O4::Item: Clone, - O5: IntoIterator, - O5::Item: Clone, D0: Fn() -> O0::Item, D1: Fn() -> O1::Item, - D2: Fn() -> O2::Item, - D3: Fn() -> O3::Item, - D4: Fn() -> O4::Item, - D5: Fn() -> O5::Item, { - ClampedZip2x6 { + ClampedZip2x2 { r0: r0.into_iter(), r1: r1.into_iter(), o0: o0.into_iter(), o1: o1.into_iter(), - o2: o2.into_iter(), - o3: o3.into_iter(), - o4: o4.into_iter(), - o5: o5.into_iter(), o0_default_fn, o1_default_fn, - o2_default_fn, - o3_default_fn, - o4_default_fn, - o5_default_fn, o0_latest_value: None, o1_latest_value: None, - o2_latest_value: None, - o3_latest_value: None, - o4_latest_value: None, - o5_latest_value: None, } } /// Implements a clamped zip iterator combinator with 2 required iterators and 2 optional /// iterators. /// -/// See [`clamped_zip_2x6`] for more information. -pub struct ClampedZip2x6 +/// See [`clamped_zip_2x2`] for more information. +pub struct ClampedZip2x2 where R0: Iterator, R1: Iterator, @@ -2210,46 +707,21 @@ where O0::Item: Clone, O1: Iterator, O1::Item: Clone, - O2: Iterator, - O2::Item: Clone, - O3: Iterator, - O3::Item: Clone, - O4: Iterator, - O4::Item: Clone, - O5: Iterator, - O5::Item: Clone, D0: Fn() -> O0::Item, D1: Fn() -> O1::Item, - D2: Fn() -> O2::Item, - D3: Fn() -> O3::Item, - D4: Fn() -> O4::Item, - D5: Fn() -> O5::Item, { r0: R0, r1: R1, o0: O0, o1: O1, - o2: O2, - o3: O3, - o4: O4, - o5: O5, o0_default_fn: D0, o1_default_fn: D1, - o2_default_fn: D2, - o3_default_fn: D3, - o4_default_fn: D4, - o5_default_fn: D5, o0_latest_value: Option, o1_latest_value: Option, - o2_latest_value: Option, - o3_latest_value: Option, - o4_latest_value: Option, - o5_latest_value: Option, } -impl Iterator - for ClampedZip2x6 +impl Iterator for ClampedZip2x2 where R0: Iterator, R1: Iterator, @@ -2257,31 +729,10 @@ where O0::Item: Clone, O1: Iterator, O1::Item: Clone, - O2: Iterator, - O2::Item: Clone, - O3: Iterator, - O3::Item: Clone, - O4: Iterator, - O4::Item: Clone, - O5: Iterator, - O5::Item: Clone, D0: Fn() -> O0::Item, D1: Fn() -> O1::Item, - D2: Fn() -> O2::Item, - D3: Fn() -> O3::Item, - D4: Fn() -> O4::Item, - D5: Fn() -> O5::Item, { - type Item = ( - R0::Item, - R1::Item, - O0::Item, - O1::Item, - O2::Item, - O3::Item, - O4::Item, - O5::Item, - ); + type Item = (R0::Item, R1::Item, O0::Item, O1::Item); #[inline] fn next(&mut self) -> Option { @@ -2289,42 +740,30 @@ where let r1_next = self.r1.next()?; let o0_next = self.o0.next().or_else(|| self.o0_latest_value.take()); let o1_next = self.o1.next().or_else(|| self.o1_latest_value.take()); - let o2_next = self.o2.next().or_else(|| self.o2_latest_value.take()); - let o3_next = self.o3.next().or_else(|| self.o3_latest_value.take()); - let o4_next = self.o4.next().or_else(|| self.o4_latest_value.take()); - let o5_next = self.o5.next().or_else(|| self.o5_latest_value.take()); self.o0_latest_value.clone_from(&o0_next); self.o1_latest_value.clone_from(&o1_next); - self.o2_latest_value.clone_from(&o2_next); - self.o3_latest_value.clone_from(&o3_next); - self.o4_latest_value.clone_from(&o4_next); - self.o5_latest_value.clone_from(&o5_next); Some(( r0_next, r1_next, o0_next.unwrap_or_else(|| (self.o0_default_fn)()), o1_next.unwrap_or_else(|| (self.o1_default_fn)()), - o2_next.unwrap_or_else(|| (self.o2_default_fn)()), - o3_next.unwrap_or_else(|| (self.o3_default_fn)()), - o4_next.unwrap_or_else(|| (self.o4_default_fn)()), - o5_next.unwrap_or_else(|| (self.o5_default_fn)()), )) } } -/// Returns a new [`ClampedZip2x7`] iterator. +/// Returns a new [`ClampedZip2x3`] iterator. /// /// The number of elements in a clamped zip iterator corresponds to the number of elements in the /// shortest of its required iterators (`r0`, `r1`). /// -/// Optional iterators (`o0`, `o1`, `o2`, `o3`, `o4`, `o5`, `o6`) will repeat their latest values if they happen to be too short +/// Optional iterators (`o0`, `o1`, `o2`) will repeat their latest values if they happen to be too short /// to be zipped with the shortest of the required iterators. /// /// If an optional iterator is not only too short but actually empty, its associated default function -/// (`o0_default_fn`, `o1_default_fn`, `o2_default_fn`, `o3_default_fn`, `o4_default_fn`, `o5_default_fn`, `o6_default_fn`) will be executed and the resulting value repeated as necessary. -pub fn clamped_zip_2x7( +/// (`o0_default_fn`, `o1_default_fn`, `o2_default_fn`) will be executed and the resulting value repeated as necessary. +pub fn clamped_zip_2x3( r0: R0, r1: R1, o0: O0, @@ -2333,32 +772,7 @@ pub fn clamped_zip_2x7 ClampedZip2x7< - R0::IntoIter, - R1::IntoIter, - O0::IntoIter, - O1::IntoIter, - O2::IntoIter, - O3::IntoIter, - O4::IntoIter, - O5::IntoIter, - O6::IntoIter, - D0, - D1, - D2, - D3, - D4, - D5, - D6, -> +) -> ClampedZip2x3 where R0: IntoIterator, R1: IntoIterator, @@ -2368,54 +782,30 @@ where O1::Item: Clone, O2: IntoIterator, O2::Item: Clone, - O3: IntoIterator, - O3::Item: Clone, - O4: IntoIterator, - O4::Item: Clone, - O5: IntoIterator, - O5::Item: Clone, - O6: IntoIterator, - O6::Item: Clone, D0: Fn() -> O0::Item, D1: Fn() -> O1::Item, D2: Fn() -> O2::Item, - D3: Fn() -> O3::Item, - D4: Fn() -> O4::Item, - D5: Fn() -> O5::Item, - D6: Fn() -> O6::Item, { - ClampedZip2x7 { + ClampedZip2x3 { r0: r0.into_iter(), r1: r1.into_iter(), o0: o0.into_iter(), o1: o1.into_iter(), o2: o2.into_iter(), - o3: o3.into_iter(), - o4: o4.into_iter(), - o5: o5.into_iter(), - o6: o6.into_iter(), o0_default_fn, o1_default_fn, o2_default_fn, - o3_default_fn, - o4_default_fn, - o5_default_fn, - o6_default_fn, o0_latest_value: None, o1_latest_value: None, o2_latest_value: None, - o3_latest_value: None, - o4_latest_value: None, - o5_latest_value: None, - o6_latest_value: None, } } /// Implements a clamped zip iterator combinator with 2 required iterators and 2 optional /// iterators. /// -/// See [`clamped_zip_2x7`] for more information. -pub struct ClampedZip2x7 +/// See [`clamped_zip_2x3`] for more information. +pub struct ClampedZip2x3 where R0: Iterator, R1: Iterator, @@ -2425,50 +815,25 @@ where O1::Item: Clone, O2: Iterator, O2::Item: Clone, - O3: Iterator, - O3::Item: Clone, - O4: Iterator, - O4::Item: Clone, - O5: Iterator, - O5::Item: Clone, - O6: Iterator, - O6::Item: Clone, D0: Fn() -> O0::Item, D1: Fn() -> O1::Item, D2: Fn() -> O2::Item, - D3: Fn() -> O3::Item, - D4: Fn() -> O4::Item, - D5: Fn() -> O5::Item, - D6: Fn() -> O6::Item, { r0: R0, r1: R1, o0: O0, o1: O1, o2: O2, - o3: O3, - o4: O4, - o5: O5, - o6: O6, o0_default_fn: D0, o1_default_fn: D1, o2_default_fn: D2, - o3_default_fn: D3, - o4_default_fn: D4, - o5_default_fn: D5, - o6_default_fn: D6, o0_latest_value: Option, o1_latest_value: Option, o2_latest_value: Option, - o3_latest_value: Option, - o4_latest_value: Option, - o5_latest_value: Option, - o6_latest_value: Option, } -impl Iterator - for ClampedZip2x7 +impl Iterator for ClampedZip2x3 where R0: Iterator, R1: Iterator, @@ -2478,33 +843,11 @@ where O1::Item: Clone, O2: Iterator, O2::Item: Clone, - O3: Iterator, - O3::Item: Clone, - O4: Iterator, - O4::Item: Clone, - O5: Iterator, - O5::Item: Clone, - O6: Iterator, - O6::Item: Clone, D0: Fn() -> O0::Item, D1: Fn() -> O1::Item, D2: Fn() -> O2::Item, - D3: Fn() -> O3::Item, - D4: Fn() -> O4::Item, - D5: Fn() -> O5::Item, - D6: Fn() -> O6::Item, { - type Item = ( - R0::Item, - R1::Item, - O0::Item, - O1::Item, - O2::Item, - O3::Item, - O4::Item, - O5::Item, - O6::Item, - ); + type Item = (R0::Item, R1::Item, O0::Item, O1::Item, O2::Item); #[inline] fn next(&mut self) -> Option { @@ -2513,18 +856,10 @@ where let o0_next = self.o0.next().or_else(|| self.o0_latest_value.take()); let o1_next = self.o1.next().or_else(|| self.o1_latest_value.take()); let o2_next = self.o2.next().or_else(|| self.o2_latest_value.take()); - let o3_next = self.o3.next().or_else(|| self.o3_latest_value.take()); - let o4_next = self.o4.next().or_else(|| self.o4_latest_value.take()); - let o5_next = self.o5.next().or_else(|| self.o5_latest_value.take()); - let o6_next = self.o6.next().or_else(|| self.o6_latest_value.take()); self.o0_latest_value.clone_from(&o0_next); self.o1_latest_value.clone_from(&o1_next); self.o2_latest_value.clone_from(&o2_next); - self.o3_latest_value.clone_from(&o3_next); - self.o4_latest_value.clone_from(&o4_next); - self.o5_latest_value.clone_from(&o5_next); - self.o6_latest_value.clone_from(&o6_next); Some(( r0_next, @@ -2532,25 +867,21 @@ where o0_next.unwrap_or_else(|| (self.o0_default_fn)()), o1_next.unwrap_or_else(|| (self.o1_default_fn)()), o2_next.unwrap_or_else(|| (self.o2_default_fn)()), - o3_next.unwrap_or_else(|| (self.o3_default_fn)()), - o4_next.unwrap_or_else(|| (self.o4_default_fn)()), - o5_next.unwrap_or_else(|| (self.o5_default_fn)()), - o6_next.unwrap_or_else(|| (self.o6_default_fn)()), )) } } -/// Returns a new [`ClampedZip2x8`] iterator. +/// Returns a new [`ClampedZip2x4`] iterator. /// /// The number of elements in a clamped zip iterator corresponds to the number of elements in the /// shortest of its required iterators (`r0`, `r1`). /// -/// Optional iterators (`o0`, `o1`, `o2`, `o3`, `o4`, `o5`, `o6`, `o7`) will repeat their latest values if they happen to be too short +/// Optional iterators (`o0`, `o1`, `o2`, `o3`) will repeat their latest values if they happen to be too short /// to be zipped with the shortest of the required iterators. /// /// If an optional iterator is not only too short but actually empty, its associated default function -/// (`o0_default_fn`, `o1_default_fn`, `o2_default_fn`, `o3_default_fn`, `o4_default_fn`, `o5_default_fn`, `o6_default_fn`, `o7_default_fn`) will be executed and the resulting value repeated as necessary. -pub fn clamped_zip_2x8( +/// (`o0_default_fn`, `o1_default_fn`, `o2_default_fn`, `o3_default_fn`) will be executed and the resulting value repeated as necessary. +pub fn clamped_zip_2x4( r0: R0, r1: R1, o0: O0, @@ -2561,33 +892,17 @@ pub fn clamped_zip_2x8 ClampedZip2x8< +) -> ClampedZip2x4< R0::IntoIter, R1::IntoIter, O0::IntoIter, O1::IntoIter, O2::IntoIter, O3::IntoIter, - O4::IntoIter, - O5::IntoIter, - O6::IntoIter, - O7::IntoIter, D0, D1, D2, D3, - D4, - D5, - D6, - D7, > where R0: IntoIterator, @@ -2600,58 +915,34 @@ where O2::Item: Clone, O3: IntoIterator, O3::Item: Clone, - O4: IntoIterator, - O4::Item: Clone, - O5: IntoIterator, - O5::Item: Clone, - O6: IntoIterator, - O6::Item: Clone, - O7: IntoIterator, - O7::Item: Clone, D0: Fn() -> O0::Item, D1: Fn() -> O1::Item, D2: Fn() -> O2::Item, D3: Fn() -> O3::Item, - D4: Fn() -> O4::Item, - D5: Fn() -> O5::Item, - D6: Fn() -> O6::Item, - D7: Fn() -> O7::Item, { - ClampedZip2x8 { + ClampedZip2x4 { r0: r0.into_iter(), r1: r1.into_iter(), o0: o0.into_iter(), o1: o1.into_iter(), o2: o2.into_iter(), o3: o3.into_iter(), - o4: o4.into_iter(), - o5: o5.into_iter(), - o6: o6.into_iter(), - o7: o7.into_iter(), o0_default_fn, o1_default_fn, o2_default_fn, o3_default_fn, - o4_default_fn, - o5_default_fn, - o6_default_fn, - o7_default_fn, o0_latest_value: None, o1_latest_value: None, o2_latest_value: None, o3_latest_value: None, - o4_latest_value: None, - o5_latest_value: None, - o6_latest_value: None, - o7_latest_value: None, } } /// Implements a clamped zip iterator combinator with 2 required iterators and 2 optional /// iterators. /// -/// See [`clamped_zip_2x8`] for more information. -pub struct ClampedZip2x8 +/// See [`clamped_zip_2x4`] for more information. +pub struct ClampedZip2x4 where R0: Iterator, R1: Iterator, @@ -2663,22 +954,10 @@ where O2::Item: Clone, O3: Iterator, O3::Item: Clone, - O4: Iterator, - O4::Item: Clone, - O5: Iterator, - O5::Item: Clone, - O6: Iterator, - O6::Item: Clone, - O7: Iterator, - O7::Item: Clone, D0: Fn() -> O0::Item, D1: Fn() -> O1::Item, D2: Fn() -> O2::Item, D3: Fn() -> O3::Item, - D4: Fn() -> O4::Item, - D5: Fn() -> O5::Item, - D6: Fn() -> O6::Item, - D7: Fn() -> O7::Item, { r0: R0, r1: R1, @@ -2686,31 +965,19 @@ where o1: O1, o2: O2, o3: O3, - o4: O4, - o5: O5, - o6: O6, - o7: O7, o0_default_fn: D0, o1_default_fn: D1, o2_default_fn: D2, o3_default_fn: D3, - o4_default_fn: D4, - o5_default_fn: D5, - o6_default_fn: D6, - o7_default_fn: D7, o0_latest_value: Option, o1_latest_value: Option, o2_latest_value: Option, o3_latest_value: Option, - o4_latest_value: Option, - o5_latest_value: Option, - o6_latest_value: Option, - o7_latest_value: Option, } -impl Iterator - for ClampedZip2x8 +impl Iterator + for ClampedZip2x4 where R0: Iterator, R1: Iterator, @@ -2722,35 +989,12 @@ where O2::Item: Clone, O3: Iterator, O3::Item: Clone, - O4: Iterator, - O4::Item: Clone, - O5: Iterator, - O5::Item: Clone, - O6: Iterator, - O6::Item: Clone, - O7: Iterator, - O7::Item: Clone, D0: Fn() -> O0::Item, D1: Fn() -> O1::Item, D2: Fn() -> O2::Item, D3: Fn() -> O3::Item, - D4: Fn() -> O4::Item, - D5: Fn() -> O5::Item, - D6: Fn() -> O6::Item, - D7: Fn() -> O7::Item, { - type Item = ( - R0::Item, - R1::Item, - O0::Item, - O1::Item, - O2::Item, - O3::Item, - O4::Item, - O5::Item, - O6::Item, - O7::Item, - ); + type Item = (R0::Item, R1::Item, O0::Item, O1::Item, O2::Item, O3::Item); #[inline] fn next(&mut self) -> Option { @@ -2760,19 +1004,11 @@ where let o1_next = self.o1.next().or_else(|| self.o1_latest_value.take()); let o2_next = self.o2.next().or_else(|| self.o2_latest_value.take()); let o3_next = self.o3.next().or_else(|| self.o3_latest_value.take()); - let o4_next = self.o4.next().or_else(|| self.o4_latest_value.take()); - let o5_next = self.o5.next().or_else(|| self.o5_latest_value.take()); - let o6_next = self.o6.next().or_else(|| self.o6_latest_value.take()); - let o7_next = self.o7.next().or_else(|| self.o7_latest_value.take()); self.o0_latest_value.clone_from(&o0_next); self.o1_latest_value.clone_from(&o1_next); self.o2_latest_value.clone_from(&o2_next); self.o3_latest_value.clone_from(&o3_next); - self.o4_latest_value.clone_from(&o4_next); - self.o5_latest_value.clone_from(&o5_next); - self.o6_latest_value.clone_from(&o6_next); - self.o7_latest_value.clone_from(&o7_next); Some(( r0_next, @@ -2781,46 +1017,21 @@ where o1_next.unwrap_or_else(|| (self.o1_default_fn)()), o2_next.unwrap_or_else(|| (self.o2_default_fn)()), o3_next.unwrap_or_else(|| (self.o3_default_fn)()), - o4_next.unwrap_or_else(|| (self.o4_default_fn)()), - o5_next.unwrap_or_else(|| (self.o5_default_fn)()), - o6_next.unwrap_or_else(|| (self.o6_default_fn)()), - o7_next.unwrap_or_else(|| (self.o7_default_fn)()), )) } } -/// Returns a new [`ClampedZip2x9`] iterator. +/// Returns a new [`ClampedZip2x5`] iterator. /// /// The number of elements in a clamped zip iterator corresponds to the number of elements in the /// shortest of its required iterators (`r0`, `r1`). /// -/// Optional iterators (`o0`, `o1`, `o2`, `o3`, `o4`, `o5`, `o6`, `o7`, `o8`) will repeat their latest values if they happen to be too short +/// Optional iterators (`o0`, `o1`, `o2`, `o3`, `o4`) will repeat their latest values if they happen to be too short /// to be zipped with the shortest of the required iterators. /// /// If an optional iterator is not only too short but actually empty, its associated default function -/// (`o0_default_fn`, `o1_default_fn`, `o2_default_fn`, `o3_default_fn`, `o4_default_fn`, `o5_default_fn`, `o6_default_fn`, `o7_default_fn`, `o8_default_fn`) will be executed and the resulting value repeated as necessary. -pub fn clamped_zip_2x9< - R0, - R1, - O0, - O1, - O2, - O3, - O4, - O5, - O6, - O7, - O8, - D0, - D1, - D2, - D3, - D4, - D5, - D6, - D7, - D8, ->( +/// (`o0_default_fn`, `o1_default_fn`, `o2_default_fn`, `o3_default_fn`, `o4_default_fn`) will be executed and the resulting value repeated as necessary. +pub fn clamped_zip_2x5( r0: R0, r1: R1, o0: O0, @@ -2833,15 +1044,7 @@ pub fn clamped_zip_2x9< o3_default_fn: D3, o4: O4, o4_default_fn: D4, - o5: O5, - o5_default_fn: D5, - o6: O6, - o6_default_fn: D6, - o7: O7, - o7_default_fn: D7, - o8: O8, - o8_default_fn: D8, -) -> ClampedZip2x9< +) -> ClampedZip2x5< R0::IntoIter, R1::IntoIter, O0::IntoIter, @@ -2849,19 +1052,11 @@ pub fn clamped_zip_2x9< O2::IntoIter, O3::IntoIter, O4::IntoIter, - O5::IntoIter, - O6::IntoIter, - O7::IntoIter, - O8::IntoIter, D0, D1, D2, D3, D4, - D5, - D6, - D7, - D8, > where R0: IntoIterator, @@ -2876,25 +1071,13 @@ where O3::Item: Clone, O4: IntoIterator, O4::Item: Clone, - O5: IntoIterator, - O5::Item: Clone, - O6: IntoIterator, - O6::Item: Clone, - O7: IntoIterator, - O7::Item: Clone, - O8: IntoIterator, - O8::Item: Clone, D0: Fn() -> O0::Item, D1: Fn() -> O1::Item, D2: Fn() -> O2::Item, D3: Fn() -> O3::Item, D4: Fn() -> O4::Item, - D5: Fn() -> O5::Item, - D6: Fn() -> O6::Item, - D7: Fn() -> O7::Item, - D8: Fn() -> O8::Item, { - ClampedZip2x9 { + ClampedZip2x5 { r0: r0.into_iter(), r1: r1.into_iter(), o0: o0.into_iter(), @@ -2902,57 +1085,25 @@ where o2: o2.into_iter(), o3: o3.into_iter(), o4: o4.into_iter(), - o5: o5.into_iter(), - o6: o6.into_iter(), - o7: o7.into_iter(), - o8: o8.into_iter(), o0_default_fn, o1_default_fn, o2_default_fn, o3_default_fn, o4_default_fn, - o5_default_fn, - o6_default_fn, - o7_default_fn, - o8_default_fn, o0_latest_value: None, o1_latest_value: None, o2_latest_value: None, o3_latest_value: None, o4_latest_value: None, - o5_latest_value: None, - o6_latest_value: None, - o7_latest_value: None, - o8_latest_value: None, } } /// Implements a clamped zip iterator combinator with 2 required iterators and 2 optional /// iterators. /// -/// See [`clamped_zip_2x9`] for more information. -pub struct ClampedZip2x9< - R0, - R1, - O0, - O1, - O2, - O3, - O4, - O5, - O6, - O7, - O8, - D0, - D1, - D2, - D3, - D4, - D5, - D6, - D7, - D8, -> where +/// See [`clamped_zip_2x5`] for more information. +pub struct ClampedZip2x5 +where R0: Iterator, R1: Iterator, O0: Iterator, @@ -2965,23 +1116,11 @@ pub struct ClampedZip2x9< O3::Item: Clone, O4: Iterator, O4::Item: Clone, - O5: Iterator, - O5::Item: Clone, - O6: Iterator, - O6::Item: Clone, - O7: Iterator, - O7::Item: Clone, - O8: Iterator, - O8::Item: Clone, D0: Fn() -> O0::Item, D1: Fn() -> O1::Item, D2: Fn() -> O2::Item, D3: Fn() -> O3::Item, D4: Fn() -> O4::Item, - D5: Fn() -> O5::Item, - D6: Fn() -> O6::Item, - D7: Fn() -> O7::Item, - D8: Fn() -> O8::Item, { r0: R0, r1: R1, @@ -2990,54 +1129,21 @@ pub struct ClampedZip2x9< o2: O2, o3: O3, o4: O4, - o5: O5, - o6: O6, - o7: O7, - o8: O8, o0_default_fn: D0, o1_default_fn: D1, o2_default_fn: D2, o3_default_fn: D3, o4_default_fn: D4, - o5_default_fn: D5, - o6_default_fn: D6, - o7_default_fn: D7, - o8_default_fn: D8, o0_latest_value: Option, o1_latest_value: Option, o2_latest_value: Option, o3_latest_value: Option, o4_latest_value: Option, - o5_latest_value: Option, - o6_latest_value: Option, - o7_latest_value: Option, - o8_latest_value: Option, } -impl Iterator - for ClampedZip2x9< - R0, - R1, - O0, - O1, - O2, - O3, - O4, - O5, - O6, - O7, - O8, - D0, - D1, - D2, - D3, - D4, - D5, - D6, - D7, - D8, - > +impl Iterator + for ClampedZip2x5 where R0: Iterator, R1: Iterator, @@ -3051,23 +1157,11 @@ where O3::Item: Clone, O4: Iterator, O4::Item: Clone, - O5: Iterator, - O5::Item: Clone, - O6: Iterator, - O6::Item: Clone, - O7: Iterator, - O7::Item: Clone, - O8: Iterator, - O8::Item: Clone, D0: Fn() -> O0::Item, D1: Fn() -> O1::Item, D2: Fn() -> O2::Item, D3: Fn() -> O3::Item, D4: Fn() -> O4::Item, - D5: Fn() -> O5::Item, - D6: Fn() -> O6::Item, - D7: Fn() -> O7::Item, - D8: Fn() -> O8::Item, { type Item = ( R0::Item, @@ -3077,10 +1171,6 @@ where O2::Item, O3::Item, O4::Item, - O5::Item, - O6::Item, - O7::Item, - O8::Item, ); #[inline] @@ -3092,20 +1182,12 @@ where let o2_next = self.o2.next().or_else(|| self.o2_latest_value.take()); let o3_next = self.o3.next().or_else(|| self.o3_latest_value.take()); let o4_next = self.o4.next().or_else(|| self.o4_latest_value.take()); - let o5_next = self.o5.next().or_else(|| self.o5_latest_value.take()); - let o6_next = self.o6.next().or_else(|| self.o6_latest_value.take()); - let o7_next = self.o7.next().or_else(|| self.o7_latest_value.take()); - let o8_next = self.o8.next().or_else(|| self.o8_latest_value.take()); self.o0_latest_value.clone_from(&o0_next); self.o1_latest_value.clone_from(&o1_next); self.o2_latest_value.clone_from(&o2_next); self.o3_latest_value.clone_from(&o3_next); self.o4_latest_value.clone_from(&o4_next); - self.o5_latest_value.clone_from(&o5_next); - self.o6_latest_value.clone_from(&o6_next); - self.o7_latest_value.clone_from(&o7_next); - self.o8_latest_value.clone_from(&o8_next); Some(( r0_next, @@ -3115,10 +1197,6 @@ where o2_next.unwrap_or_else(|| (self.o2_default_fn)()), o3_next.unwrap_or_else(|| (self.o3_default_fn)()), o4_next.unwrap_or_else(|| (self.o4_default_fn)()), - o5_next.unwrap_or_else(|| (self.o5_default_fn)()), - o6_next.unwrap_or_else(|| (self.o6_default_fn)()), - o7_next.unwrap_or_else(|| (self.o7_default_fn)()), - o8_next.unwrap_or_else(|| (self.o8_default_fn)()), )) } } diff --git a/crates/store/re_query/src/range_zip/generated.rs b/crates/store/re_query/src/range_zip/generated.rs index 178d9ddbd0b5..98770c038be0 100644 --- a/crates/store/re_query/src/range_zip/generated.rs +++ b/crates/store/re_query/src/range_zip/generated.rs @@ -1,4 +1,4 @@ -// This file was generated using `cargo r -p re_query --all-features --bin range_zip`. +// This file was generated using `cargo r -p re_query --all-features --bin range_zip > crates/store/re_query/src/range_zip/generated.rs && cargo fmt`. // DO NOT EDIT. // --- @@ -1628,6 +1628,379 @@ where } } +/// Returns a new [`RangeZip1x10`] iterator. +/// +/// The number of elements in a range zip iterator corresponds to the number of elements in the +/// shortest of its required iterators (`r0`). +/// +/// Each call to `next` is guaranteed to yield the next value for each required iterator, +/// as well as the most recent index amongst all of them. +/// +/// Optional iterators accumulate their state and yield their most recent value (if any), +/// each time the required iterators fire. +pub fn range_zip_1x10< + Idx, + IR0, + R0, + IO0, + O0, + IO1, + O1, + IO2, + O2, + IO3, + O3, + IO4, + O4, + IO5, + O5, + IO6, + O6, + IO7, + O7, + IO8, + O8, + IO9, + O9, +>( + r0: IR0, + o0: IO0, + o1: IO1, + o2: IO2, + o3: IO3, + o4: IO4, + o5: IO5, + o6: IO6, + o7: IO7, + o8: IO8, + o9: IO9, +) -> RangeZip1x10< + Idx, + IR0::IntoIter, + R0, + IO0::IntoIter, + O0, + IO1::IntoIter, + O1, + IO2::IntoIter, + O2, + IO3::IntoIter, + O3, + IO4::IntoIter, + O4, + IO5::IntoIter, + O5, + IO6::IntoIter, + O6, + IO7::IntoIter, + O7, + IO8::IntoIter, + O8, + IO9::IntoIter, + O9, +> +where + Idx: std::cmp::Ord, + IR0: IntoIterator, + IO0: IntoIterator, + IO1: IntoIterator, + IO2: IntoIterator, + IO3: IntoIterator, + IO4: IntoIterator, + IO5: IntoIterator, + IO6: IntoIterator, + IO7: IntoIterator, + IO8: IntoIterator, + IO9: IntoIterator, +{ + RangeZip1x10 { + r0: r0.into_iter(), + o0: o0.into_iter().peekable(), + o1: o1.into_iter().peekable(), + o2: o2.into_iter().peekable(), + o3: o3.into_iter().peekable(), + o4: o4.into_iter().peekable(), + o5: o5.into_iter().peekable(), + o6: o6.into_iter().peekable(), + o7: o7.into_iter().peekable(), + o8: o8.into_iter().peekable(), + o9: o9.into_iter().peekable(), + + o0_data_latest: None, + o1_data_latest: None, + o2_data_latest: None, + o3_data_latest: None, + o4_data_latest: None, + o5_data_latest: None, + o6_data_latest: None, + o7_data_latest: None, + o8_data_latest: None, + o9_data_latest: None, + } +} + +/// Implements a range zip iterator combinator with 2 required iterators and 2 optional +/// iterators. +/// +/// See [`range_zip_1x10`] for more information. +pub struct RangeZip1x10< + Idx, + IR0, + R0, + IO0, + O0, + IO1, + O1, + IO2, + O2, + IO3, + O3, + IO4, + O4, + IO5, + O5, + IO6, + O6, + IO7, + O7, + IO8, + O8, + IO9, + O9, +> where + Idx: std::cmp::Ord, + IR0: Iterator, + IO0: Iterator, + IO1: Iterator, + IO2: Iterator, + IO3: Iterator, + IO4: Iterator, + IO5: Iterator, + IO6: Iterator, + IO7: Iterator, + IO8: Iterator, + IO9: Iterator, +{ + r0: IR0, + o0: Peekable, + o1: Peekable, + o2: Peekable, + o3: Peekable, + o4: Peekable, + o5: Peekable, + o6: Peekable, + o7: Peekable, + o8: Peekable, + o9: Peekable, + + o0_data_latest: Option, + o1_data_latest: Option, + o2_data_latest: Option, + o3_data_latest: Option, + o4_data_latest: Option, + o5_data_latest: Option, + o6_data_latest: Option, + o7_data_latest: Option, + o8_data_latest: Option, + o9_data_latest: Option, +} + +impl< + Idx, + IR0, + R0, + IO0, + O0, + IO1, + O1, + IO2, + O2, + IO3, + O3, + IO4, + O4, + IO5, + O5, + IO6, + O6, + IO7, + O7, + IO8, + O8, + IO9, + O9, +> Iterator + for RangeZip1x10< + Idx, + IR0, + R0, + IO0, + O0, + IO1, + O1, + IO2, + O2, + IO3, + O3, + IO4, + O4, + IO5, + O5, + IO6, + O6, + IO7, + O7, + IO8, + O8, + IO9, + O9, + > +where + Idx: std::cmp::Ord, + IR0: Iterator, + IO0: Iterator, + IO1: Iterator, + IO2: Iterator, + IO3: Iterator, + IO4: Iterator, + IO5: Iterator, + IO6: Iterator, + IO7: Iterator, + IO8: Iterator, + IO9: Iterator, + O0: Clone, + O1: Clone, + O2: Clone, + O3: Clone, + O4: Clone, + O5: Clone, + O6: Clone, + O7: Clone, + O8: Clone, + O9: Clone, +{ + type Item = ( + Idx, + R0, + Option, + Option, + Option, + Option, + Option, + Option, + Option, + Option, + Option, + Option, + ); + + #[inline] + fn next(&mut self) -> Option { + let Self { + r0, + o0, + o1, + o2, + o3, + o4, + o5, + o6, + o7, + o8, + o9, + o0_data_latest, + o1_data_latest, + o2_data_latest, + o3_data_latest, + o4_data_latest, + o5_data_latest, + o6_data_latest, + o7_data_latest, + o8_data_latest, + o9_data_latest, + } = self; + + let (r0_index, r0_data) = r0.next()?; + + let max_index = [r0_index].into_iter().max()?; + + let mut o0_data = None; + while let Some((_, data)) = o0.next_if(|(index, _)| index <= &max_index) { + o0_data = Some(data); + } + let o0_data = o0_data.or_else(|| o0_data_latest.take()); + o0_data_latest.clone_from(&o0_data); + + let mut o1_data = None; + while let Some((_, data)) = o1.next_if(|(index, _)| index <= &max_index) { + o1_data = Some(data); + } + let o1_data = o1_data.or_else(|| o1_data_latest.take()); + o1_data_latest.clone_from(&o1_data); + + let mut o2_data = None; + while let Some((_, data)) = o2.next_if(|(index, _)| index <= &max_index) { + o2_data = Some(data); + } + let o2_data = o2_data.or_else(|| o2_data_latest.take()); + o2_data_latest.clone_from(&o2_data); + + let mut o3_data = None; + while let Some((_, data)) = o3.next_if(|(index, _)| index <= &max_index) { + o3_data = Some(data); + } + let o3_data = o3_data.or_else(|| o3_data_latest.take()); + o3_data_latest.clone_from(&o3_data); + + let mut o4_data = None; + while let Some((_, data)) = o4.next_if(|(index, _)| index <= &max_index) { + o4_data = Some(data); + } + let o4_data = o4_data.or_else(|| o4_data_latest.take()); + o4_data_latest.clone_from(&o4_data); + + let mut o5_data = None; + while let Some((_, data)) = o5.next_if(|(index, _)| index <= &max_index) { + o5_data = Some(data); + } + let o5_data = o5_data.or_else(|| o5_data_latest.take()); + o5_data_latest.clone_from(&o5_data); + + let mut o6_data = None; + while let Some((_, data)) = o6.next_if(|(index, _)| index <= &max_index) { + o6_data = Some(data); + } + let o6_data = o6_data.or_else(|| o6_data_latest.take()); + o6_data_latest.clone_from(&o6_data); + + let mut o7_data = None; + while let Some((_, data)) = o7.next_if(|(index, _)| index <= &max_index) { + o7_data = Some(data); + } + let o7_data = o7_data.or_else(|| o7_data_latest.take()); + o7_data_latest.clone_from(&o7_data); + + let mut o8_data = None; + while let Some((_, data)) = o8.next_if(|(index, _)| index <= &max_index) { + o8_data = Some(data); + } + let o8_data = o8_data.or_else(|| o8_data_latest.take()); + o8_data_latest.clone_from(&o8_data); + + let mut o9_data = None; + while let Some((_, data)) = o9.next_if(|(index, _)| index <= &max_index) { + o9_data = Some(data); + } + let o9_data = o9_data.or_else(|| o9_data_latest.take()); + o9_data_latest.clone_from(&o9_data); + + Some(( + max_index, r0_data, o0_data, o1_data, o2_data, o3_data, o4_data, o5_data, o6_data, + o7_data, o8_data, o9_data, + )) + } +} + /// Returns a new [`RangeZip2x1`] iterator. /// /// The number of elements in a range zip iterator corresponds to the number of elements in the @@ -2144,7 +2517,194 @@ where IO3: IntoIterator, IO4: IntoIterator, { - RangeZip2x5 { + RangeZip2x5 { + r0: r0.into_iter(), + r1: r1.into_iter(), + o0: o0.into_iter().peekable(), + o1: o1.into_iter().peekable(), + o2: o2.into_iter().peekable(), + o3: o3.into_iter().peekable(), + o4: o4.into_iter().peekable(), + + o0_data_latest: None, + o1_data_latest: None, + o2_data_latest: None, + o3_data_latest: None, + o4_data_latest: None, + } +} + +/// Implements a range zip iterator combinator with 2 required iterators and 2 optional +/// iterators. +/// +/// See [`range_zip_2x5`] for more information. +pub struct RangeZip2x5 +where + Idx: std::cmp::Ord, + IR0: Iterator, + IR1: Iterator, + IO0: Iterator, + IO1: Iterator, + IO2: Iterator, + IO3: Iterator, + IO4: Iterator, +{ + r0: IR0, + r1: IR1, + o0: Peekable, + o1: Peekable, + o2: Peekable, + o3: Peekable, + o4: Peekable, + + o0_data_latest: Option, + o1_data_latest: Option, + o2_data_latest: Option, + o3_data_latest: Option, + o4_data_latest: Option, +} + +impl Iterator + for RangeZip2x5 +where + Idx: std::cmp::Ord, + IR0: Iterator, + IR1: Iterator, + IO0: Iterator, + IO1: Iterator, + IO2: Iterator, + IO3: Iterator, + IO4: Iterator, + O0: Clone, + O1: Clone, + O2: Clone, + O3: Clone, + O4: Clone, +{ + type Item = ( + Idx, + R0, + R1, + Option, + Option, + Option, + Option, + Option, + ); + + #[inline] + fn next(&mut self) -> Option { + let Self { + r0, + r1, + o0, + o1, + o2, + o3, + o4, + o0_data_latest, + o1_data_latest, + o2_data_latest, + o3_data_latest, + o4_data_latest, + } = self; + + let (r0_index, r0_data) = r0.next()?; + let (r1_index, r1_data) = r1.next()?; + + let max_index = [r0_index, r1_index].into_iter().max()?; + + let mut o0_data = None; + while let Some((_, data)) = o0.next_if(|(index, _)| index <= &max_index) { + o0_data = Some(data); + } + let o0_data = o0_data.or_else(|| o0_data_latest.take()); + o0_data_latest.clone_from(&o0_data); + + let mut o1_data = None; + while let Some((_, data)) = o1.next_if(|(index, _)| index <= &max_index) { + o1_data = Some(data); + } + let o1_data = o1_data.or_else(|| o1_data_latest.take()); + o1_data_latest.clone_from(&o1_data); + + let mut o2_data = None; + while let Some((_, data)) = o2.next_if(|(index, _)| index <= &max_index) { + o2_data = Some(data); + } + let o2_data = o2_data.or_else(|| o2_data_latest.take()); + o2_data_latest.clone_from(&o2_data); + + let mut o3_data = None; + while let Some((_, data)) = o3.next_if(|(index, _)| index <= &max_index) { + o3_data = Some(data); + } + let o3_data = o3_data.or_else(|| o3_data_latest.take()); + o3_data_latest.clone_from(&o3_data); + + let mut o4_data = None; + while let Some((_, data)) = o4.next_if(|(index, _)| index <= &max_index) { + o4_data = Some(data); + } + let o4_data = o4_data.or_else(|| o4_data_latest.take()); + o4_data_latest.clone_from(&o4_data); + + Some(( + max_index, r0_data, r1_data, o0_data, o1_data, o2_data, o3_data, o4_data, + )) + } +} + +/// Returns a new [`RangeZip2x6`] iterator. +/// +/// The number of elements in a range zip iterator corresponds to the number of elements in the +/// shortest of its required iterators (`r0`, `r1`). +/// +/// Each call to `next` is guaranteed to yield the next value for each required iterator, +/// as well as the most recent index amongst all of them. +/// +/// Optional iterators accumulate their state and yield their most recent value (if any), +/// each time the required iterators fire. +pub fn range_zip_2x6( + r0: IR0, + r1: IR1, + o0: IO0, + o1: IO1, + o2: IO2, + o3: IO3, + o4: IO4, + o5: IO5, +) -> RangeZip2x6< + Idx, + IR0::IntoIter, + R0, + IR1::IntoIter, + R1, + IO0::IntoIter, + O0, + IO1::IntoIter, + O1, + IO2::IntoIter, + O2, + IO3::IntoIter, + O3, + IO4::IntoIter, + O4, + IO5::IntoIter, + O5, +> +where + Idx: std::cmp::Ord, + IR0: IntoIterator, + IR1: IntoIterator, + IO0: IntoIterator, + IO1: IntoIterator, + IO2: IntoIterator, + IO3: IntoIterator, + IO4: IntoIterator, + IO5: IntoIterator, +{ + RangeZip2x6 { r0: r0.into_iter(), r1: r1.into_iter(), o0: o0.into_iter().peekable(), @@ -2152,20 +2712,22 @@ where o2: o2.into_iter().peekable(), o3: o3.into_iter().peekable(), o4: o4.into_iter().peekable(), + o5: o5.into_iter().peekable(), o0_data_latest: None, o1_data_latest: None, o2_data_latest: None, o3_data_latest: None, o4_data_latest: None, + o5_data_latest: None, } } /// Implements a range zip iterator combinator with 2 required iterators and 2 optional /// iterators. /// -/// See [`range_zip_2x5`] for more information. -pub struct RangeZip2x5 +/// See [`range_zip_2x6`] for more information. +pub struct RangeZip2x6 where Idx: std::cmp::Ord, IR0: Iterator, @@ -2175,6 +2737,7 @@ where IO2: Iterator, IO3: Iterator, IO4: Iterator, + IO5: Iterator, { r0: IR0, r1: IR1, @@ -2183,16 +2746,18 @@ where o2: Peekable, o3: Peekable, o4: Peekable, + o5: Peekable, o0_data_latest: Option, o1_data_latest: Option, o2_data_latest: Option, o3_data_latest: Option, o4_data_latest: Option, + o5_data_latest: Option, } -impl Iterator - for RangeZip2x5 +impl Iterator + for RangeZip2x6 where Idx: std::cmp::Ord, IR0: Iterator, @@ -2202,11 +2767,13 @@ where IO2: Iterator, IO3: Iterator, IO4: Iterator, + IO5: Iterator, O0: Clone, O1: Clone, O2: Clone, O3: Clone, O4: Clone, + O5: Clone, { type Item = ( Idx, @@ -2217,6 +2784,7 @@ where Option, Option, Option, + Option, ); #[inline] @@ -2229,11 +2797,13 @@ where o2, o3, o4, + o5, o0_data_latest, o1_data_latest, o2_data_latest, o3_data_latest, o4_data_latest, + o5_data_latest, } = self; let (r0_index, r0_data) = r0.next()?; @@ -2276,13 +2846,20 @@ where let o4_data = o4_data.or_else(|| o4_data_latest.take()); o4_data_latest.clone_from(&o4_data); + let mut o5_data = None; + while let Some((_, data)) = o5.next_if(|(index, _)| index <= &max_index) { + o5_data = Some(data); + } + let o5_data = o5_data.or_else(|| o5_data_latest.take()); + o5_data_latest.clone_from(&o5_data); + Some(( - max_index, r0_data, r1_data, o0_data, o1_data, o2_data, o3_data, o4_data, + max_index, r0_data, r1_data, o0_data, o1_data, o2_data, o3_data, o4_data, o5_data, )) } } -/// Returns a new [`RangeZip2x6`] iterator. +/// Returns a new [`RangeZip2x7`] iterator. /// /// The number of elements in a range zip iterator corresponds to the number of elements in the /// shortest of its required iterators (`r0`, `r1`). @@ -2292,7 +2869,27 @@ where /// /// Optional iterators accumulate their state and yield their most recent value (if any), /// each time the required iterators fire. -pub fn range_zip_2x6( +pub fn range_zip_2x7< + Idx, + IR0, + R0, + IR1, + R1, + IO0, + O0, + IO1, + O1, + IO2, + O2, + IO3, + O3, + IO4, + O4, + IO5, + O5, + IO6, + O6, +>( r0: IR0, r1: IR1, o0: IO0, @@ -2301,7 +2898,8 @@ pub fn range_zip_2x6 RangeZip2x6< + o6: IO6, +) -> RangeZip2x7< Idx, IR0::IntoIter, R0, @@ -2319,6 +2917,8 @@ pub fn range_zip_2x6 where Idx: std::cmp::Ord, @@ -2330,8 +2930,9 @@ where IO3: IntoIterator, IO4: IntoIterator, IO5: IntoIterator, + IO6: IntoIterator, { - RangeZip2x6 { + RangeZip2x7 { r0: r0.into_iter(), r1: r1.into_iter(), o0: o0.into_iter().peekable(), @@ -2340,6 +2941,7 @@ where o3: o3.into_iter().peekable(), o4: o4.into_iter().peekable(), o5: o5.into_iter().peekable(), + o6: o6.into_iter().peekable(), o0_data_latest: None, o1_data_latest: None, @@ -2347,15 +2949,35 @@ where o3_data_latest: None, o4_data_latest: None, o5_data_latest: None, + o6_data_latest: None, } } /// Implements a range zip iterator combinator with 2 required iterators and 2 optional /// iterators. /// -/// See [`range_zip_2x6`] for more information. -pub struct RangeZip2x6 -where +/// See [`range_zip_2x7`] for more information. +pub struct RangeZip2x7< + Idx, + IR0, + R0, + IR1, + R1, + IO0, + O0, + IO1, + O1, + IO2, + O2, + IO3, + O3, + IO4, + O4, + IO5, + O5, + IO6, + O6, +> where Idx: std::cmp::Ord, IR0: Iterator, IR1: Iterator, @@ -2365,6 +2987,7 @@ where IO3: Iterator, IO4: Iterator, IO5: Iterator, + IO6: Iterator, { r0: IR0, r1: IR1, @@ -2374,6 +2997,7 @@ where o3: Peekable, o4: Peekable, o5: Peekable, + o6: Peekable, o0_data_latest: Option, o1_data_latest: Option, @@ -2381,10 +3005,31 @@ where o3_data_latest: Option, o4_data_latest: Option, o5_data_latest: Option, + o6_data_latest: Option, } -impl Iterator - for RangeZip2x6 +impl Iterator + for RangeZip2x7< + Idx, + IR0, + R0, + IR1, + R1, + IO0, + O0, + IO1, + O1, + IO2, + O2, + IO3, + O3, + IO4, + O4, + IO5, + O5, + IO6, + O6, + > where Idx: std::cmp::Ord, IR0: Iterator, @@ -2395,12 +3040,14 @@ where IO3: Iterator, IO4: Iterator, IO5: Iterator, + IO6: Iterator, O0: Clone, O1: Clone, O2: Clone, O3: Clone, O4: Clone, O5: Clone, + O6: Clone, { type Item = ( Idx, @@ -2412,6 +3059,7 @@ where Option, Option, Option, + Option, ); #[inline] @@ -2425,12 +3073,14 @@ where o3, o4, o5, + o6, o0_data_latest, o1_data_latest, o2_data_latest, o3_data_latest, o4_data_latest, o5_data_latest, + o6_data_latest, } = self; let (r0_index, r0_data) = r0.next()?; @@ -2480,13 +3130,21 @@ where let o5_data = o5_data.or_else(|| o5_data_latest.take()); o5_data_latest.clone_from(&o5_data); + let mut o6_data = None; + while let Some((_, data)) = o6.next_if(|(index, _)| index <= &max_index) { + o6_data = Some(data); + } + let o6_data = o6_data.or_else(|| o6_data_latest.take()); + o6_data_latest.clone_from(&o6_data); + Some(( max_index, r0_data, r1_data, o0_data, o1_data, o2_data, o3_data, o4_data, o5_data, + o6_data, )) } } -/// Returns a new [`RangeZip2x7`] iterator. +/// Returns a new [`RangeZip2x8`] iterator. /// /// The number of elements in a range zip iterator corresponds to the number of elements in the /// shortest of its required iterators (`r0`, `r1`). @@ -2496,7 +3154,7 @@ where /// /// Optional iterators accumulate their state and yield their most recent value (if any), /// each time the required iterators fire. -pub fn range_zip_2x7< +pub fn range_zip_2x8< Idx, IR0, R0, @@ -2516,6 +3174,8 @@ pub fn range_zip_2x7< O5, IO6, O6, + IO7, + O7, >( r0: IR0, r1: IR1, @@ -2526,7 +3186,8 @@ pub fn range_zip_2x7< o4: IO4, o5: IO5, o6: IO6, -) -> RangeZip2x7< + o7: IO7, +) -> RangeZip2x8< Idx, IR0::IntoIter, R0, @@ -2546,6 +3207,8 @@ pub fn range_zip_2x7< O5, IO6::IntoIter, O6, + IO7::IntoIter, + O7, > where Idx: std::cmp::Ord, @@ -2558,8 +3221,9 @@ where IO4: IntoIterator, IO5: IntoIterator, IO6: IntoIterator, + IO7: IntoIterator, { - RangeZip2x7 { + RangeZip2x8 { r0: r0.into_iter(), r1: r1.into_iter(), o0: o0.into_iter().peekable(), @@ -2569,6 +3233,7 @@ where o4: o4.into_iter().peekable(), o5: o5.into_iter().peekable(), o6: o6.into_iter().peekable(), + o7: o7.into_iter().peekable(), o0_data_latest: None, o1_data_latest: None, @@ -2577,14 +3242,15 @@ where o4_data_latest: None, o5_data_latest: None, o6_data_latest: None, + o7_data_latest: None, } } /// Implements a range zip iterator combinator with 2 required iterators and 2 optional /// iterators. /// -/// See [`range_zip_2x7`] for more information. -pub struct RangeZip2x7< +/// See [`range_zip_2x8`] for more information. +pub struct RangeZip2x8< Idx, IR0, R0, @@ -2604,6 +3270,8 @@ pub struct RangeZip2x7< O5, IO6, O6, + IO7, + O7, > where Idx: std::cmp::Ord, IR0: Iterator, @@ -2615,6 +3283,7 @@ pub struct RangeZip2x7< IO4: Iterator, IO5: Iterator, IO6: Iterator, + IO7: Iterator, { r0: IR0, r1: IR1, @@ -2625,6 +3294,7 @@ pub struct RangeZip2x7< o4: Peekable, o5: Peekable, o6: Peekable, + o7: Peekable, o0_data_latest: Option, o1_data_latest: Option, @@ -2633,10 +3303,12 @@ pub struct RangeZip2x7< o4_data_latest: Option, o5_data_latest: Option, o6_data_latest: Option, + o7_data_latest: Option, } -impl Iterator - for RangeZip2x7< +impl + Iterator + for RangeZip2x8< Idx, IR0, R0, @@ -2656,6 +3328,8 @@ impl where Idx: std::cmp::Ord, @@ -2668,6 +3342,7 @@ where IO4: Iterator, IO5: Iterator, IO6: Iterator, + IO7: Iterator, O0: Clone, O1: Clone, O2: Clone, @@ -2675,6 +3350,7 @@ where O4: Clone, O5: Clone, O6: Clone, + O7: Clone, { type Item = ( Idx, @@ -2687,6 +3363,7 @@ where Option, Option, Option, + Option, ); #[inline] @@ -2701,6 +3378,7 @@ where o4, o5, o6, + o7, o0_data_latest, o1_data_latest, o2_data_latest, @@ -2708,6 +3386,7 @@ where o4_data_latest, o5_data_latest, o6_data_latest, + o7_data_latest, } = self; let (r0_index, r0_data) = r0.next()?; @@ -2764,14 +3443,21 @@ where let o6_data = o6_data.or_else(|| o6_data_latest.take()); o6_data_latest.clone_from(&o6_data); + let mut o7_data = None; + while let Some((_, data)) = o7.next_if(|(index, _)| index <= &max_index) { + o7_data = Some(data); + } + let o7_data = o7_data.or_else(|| o7_data_latest.take()); + o7_data_latest.clone_from(&o7_data); + Some(( max_index, r0_data, r1_data, o0_data, o1_data, o2_data, o3_data, o4_data, o5_data, - o6_data, + o6_data, o7_data, )) } } -/// Returns a new [`RangeZip2x8`] iterator. +/// Returns a new [`RangeZip2x9`] iterator. /// /// The number of elements in a range zip iterator corresponds to the number of elements in the /// shortest of its required iterators (`r0`, `r1`). @@ -2781,7 +3467,7 @@ where /// /// Optional iterators accumulate their state and yield their most recent value (if any), /// each time the required iterators fire. -pub fn range_zip_2x8< +pub fn range_zip_2x9< Idx, IR0, R0, @@ -2803,6 +3489,8 @@ pub fn range_zip_2x8< O6, IO7, O7, + IO8, + O8, >( r0: IR0, r1: IR1, @@ -2814,7 +3502,8 @@ pub fn range_zip_2x8< o5: IO5, o6: IO6, o7: IO7, -) -> RangeZip2x8< + o8: IO8, +) -> RangeZip2x9< Idx, IR0::IntoIter, R0, @@ -2836,6 +3525,8 @@ pub fn range_zip_2x8< O6, IO7::IntoIter, O7, + IO8::IntoIter, + O8, > where Idx: std::cmp::Ord, @@ -2849,8 +3540,9 @@ where IO5: IntoIterator, IO6: IntoIterator, IO7: IntoIterator, + IO8: IntoIterator, { - RangeZip2x8 { + RangeZip2x9 { r0: r0.into_iter(), r1: r1.into_iter(), o0: o0.into_iter().peekable(), @@ -2861,6 +3553,7 @@ where o5: o5.into_iter().peekable(), o6: o6.into_iter().peekable(), o7: o7.into_iter().peekable(), + o8: o8.into_iter().peekable(), o0_data_latest: None, o1_data_latest: None, @@ -2870,14 +3563,15 @@ where o5_data_latest: None, o6_data_latest: None, o7_data_latest: None, + o8_data_latest: None, } } /// Implements a range zip iterator combinator with 2 required iterators and 2 optional /// iterators. /// -/// See [`range_zip_2x8`] for more information. -pub struct RangeZip2x8< +/// See [`range_zip_2x9`] for more information. +pub struct RangeZip2x9< Idx, IR0, R0, @@ -2899,6 +3593,8 @@ pub struct RangeZip2x8< O6, IO7, O7, + IO8, + O8, > where Idx: std::cmp::Ord, IR0: Iterator, @@ -2911,6 +3607,7 @@ pub struct RangeZip2x8< IO5: Iterator, IO6: Iterator, IO7: Iterator, + IO8: Iterator, { r0: IR0, r1: IR1, @@ -2922,6 +3619,7 @@ pub struct RangeZip2x8< o5: Peekable, o6: Peekable, o7: Peekable, + o8: Peekable, o0_data_latest: Option, o1_data_latest: Option, @@ -2931,11 +3629,35 @@ pub struct RangeZip2x8< o5_data_latest: Option, o6_data_latest: Option, o7_data_latest: Option, + o8_data_latest: Option, } -impl - Iterator - for RangeZip2x8< +impl< + Idx, + IR0, + R0, + IR1, + R1, + IO0, + O0, + IO1, + O1, + IO2, + O2, + IO3, + O3, + IO4, + O4, + IO5, + O5, + IO6, + O6, + IO7, + O7, + IO8, + O8, +> Iterator + for RangeZip2x9< Idx, IR0, R0, @@ -2957,6 +3679,8 @@ impl where Idx: std::cmp::Ord, @@ -2970,6 +3694,7 @@ where IO5: Iterator, IO6: Iterator, IO7: Iterator, + IO8: Iterator, O0: Clone, O1: Clone, O2: Clone, @@ -2978,6 +3703,7 @@ where O5: Clone, O6: Clone, O7: Clone, + O8: Clone, { type Item = ( Idx, @@ -2991,6 +3717,7 @@ where Option, Option, Option, + Option, ); #[inline] @@ -3006,6 +3733,7 @@ where o5, o6, o7, + o8, o0_data_latest, o1_data_latest, o2_data_latest, @@ -3014,6 +3742,7 @@ where o5_data_latest, o6_data_latest, o7_data_latest, + o8_data_latest, } = self; let (r0_index, r0_data) = r0.next()?; @@ -3077,14 +3806,21 @@ where let o7_data = o7_data.or_else(|| o7_data_latest.take()); o7_data_latest.clone_from(&o7_data); + let mut o8_data = None; + while let Some((_, data)) = o8.next_if(|(index, _)| index <= &max_index) { + o8_data = Some(data); + } + let o8_data = o8_data.or_else(|| o8_data_latest.take()); + o8_data_latest.clone_from(&o8_data); + Some(( max_index, r0_data, r1_data, o0_data, o1_data, o2_data, o3_data, o4_data, o5_data, - o6_data, o7_data, + o6_data, o7_data, o8_data, )) } } -/// Returns a new [`RangeZip2x9`] iterator. +/// Returns a new [`RangeZip2x10`] iterator. /// /// The number of elements in a range zip iterator corresponds to the number of elements in the /// shortest of its required iterators (`r0`, `r1`). @@ -3094,7 +3830,7 @@ where /// /// Optional iterators accumulate their state and yield their most recent value (if any), /// each time the required iterators fire. -pub fn range_zip_2x9< +pub fn range_zip_2x10< Idx, IR0, R0, @@ -3118,6 +3854,8 @@ pub fn range_zip_2x9< O7, IO8, O8, + IO9, + O9, >( r0: IR0, r1: IR1, @@ -3130,7 +3868,8 @@ pub fn range_zip_2x9< o6: IO6, o7: IO7, o8: IO8, -) -> RangeZip2x9< + o9: IO9, +) -> RangeZip2x10< Idx, IR0::IntoIter, R0, @@ -3154,6 +3893,8 @@ pub fn range_zip_2x9< O7, IO8::IntoIter, O8, + IO9::IntoIter, + O9, > where Idx: std::cmp::Ord, @@ -3168,8 +3909,9 @@ where IO6: IntoIterator, IO7: IntoIterator, IO8: IntoIterator, + IO9: IntoIterator, { - RangeZip2x9 { + RangeZip2x10 { r0: r0.into_iter(), r1: r1.into_iter(), o0: o0.into_iter().peekable(), @@ -3181,6 +3923,7 @@ where o6: o6.into_iter().peekable(), o7: o7.into_iter().peekable(), o8: o8.into_iter().peekable(), + o9: o9.into_iter().peekable(), o0_data_latest: None, o1_data_latest: None, @@ -3191,14 +3934,15 @@ where o6_data_latest: None, o7_data_latest: None, o8_data_latest: None, + o9_data_latest: None, } } /// Implements a range zip iterator combinator with 2 required iterators and 2 optional /// iterators. /// -/// See [`range_zip_2x9`] for more information. -pub struct RangeZip2x9< +/// See [`range_zip_2x10`] for more information. +pub struct RangeZip2x10< Idx, IR0, R0, @@ -3222,6 +3966,8 @@ pub struct RangeZip2x9< O7, IO8, O8, + IO9, + O9, > where Idx: std::cmp::Ord, IR0: Iterator, @@ -3235,6 +3981,7 @@ pub struct RangeZip2x9< IO6: Iterator, IO7: Iterator, IO8: Iterator, + IO9: Iterator, { r0: IR0, r1: IR1, @@ -3247,6 +3994,7 @@ pub struct RangeZip2x9< o6: Peekable, o7: Peekable, o8: Peekable, + o9: Peekable, o0_data_latest: Option, o1_data_latest: Option, @@ -3257,6 +4005,7 @@ pub struct RangeZip2x9< o6_data_latest: Option, o7_data_latest: Option, o8_data_latest: Option, + o9_data_latest: Option, } impl< @@ -3283,8 +4032,10 @@ impl< O7, IO8, O8, + IO9, + O9, > Iterator - for RangeZip2x9< + for RangeZip2x10< Idx, IR0, R0, @@ -3308,6 +4059,8 @@ impl< O7, IO8, O8, + IO9, + O9, > where Idx: std::cmp::Ord, @@ -3322,6 +4075,7 @@ where IO6: Iterator, IO7: Iterator, IO8: Iterator, + IO9: Iterator, O0: Clone, O1: Clone, O2: Clone, @@ -3331,6 +4085,7 @@ where O6: Clone, O7: Clone, O8: Clone, + O9: Clone, { type Item = ( Idx, @@ -3345,6 +4100,7 @@ where Option, Option, Option, + Option, ); #[inline] @@ -3361,6 +4117,7 @@ where o6, o7, o8, + o9, o0_data_latest, o1_data_latest, o2_data_latest, @@ -3370,6 +4127,7 @@ where o6_data_latest, o7_data_latest, o8_data_latest, + o9_data_latest, } = self; let (r0_index, r0_data) = r0.next()?; @@ -3440,9 +4198,16 @@ where let o8_data = o8_data.or_else(|| o8_data_latest.take()); o8_data_latest.clone_from(&o8_data); + let mut o9_data = None; + while let Some((_, data)) = o9.next_if(|(index, _)| index <= &max_index) { + o9_data = Some(data); + } + let o9_data = o9_data.or_else(|| o9_data_latest.take()); + o9_data_latest.clone_from(&o9_data); + Some(( max_index, r0_data, r1_data, o0_data, o1_data, o2_data, o3_data, o4_data, o5_data, - o6_data, o7_data, o8_data, + o6_data, o7_data, o8_data, o9_data, )) } } diff --git a/scripts/ci/check_large_files.py b/scripts/ci/check_large_files.py index 360073da4cb4..3dfa4c3a5031 100755 --- a/scripts/ci/check_large_files.py +++ b/scripts/ci/check_large_files.py @@ -15,7 +15,7 @@ "crates/store/re_dataframe/src/query.rs", "crates/store/re_protos/proto/schema_snapshot.yaml", "crates/store/re_protos/src/v1alpha1/rerun.cloud.v1alpha1.rs", - "crates/store/re_tf/src/transform_resolution_cache.rs", # TODO(andreas): Should move tests out to standalone files. + "crates/store/re_query/src/range_zip/generated.rs", "crates/store/re_sdk_types/src/datatypes/tensor_buffer.rs", "crates/store/re_sdk_types/src/reflection/mod.rs", "crates/top/re_sdk/src/recording_stream.rs", From d12bcb42e897fdf7d0cbaba45a9446fac7adac63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Proch=C3=A1zka?= Date: Tue, 3 Mar 2026 12:10:51 +0100 Subject: [PATCH 018/513] Post 0.30 release things Source-Ref: a585af7d795633a2aff46d304590f5a3521e1216 Co-authored-by: Andreas Reich Co-authored-by: Wumpf <1220815+Wumpf@users.noreply.github.com> Co-authored-by: Andreas Reich --- CHANGELOG.md | 33 +- Cargo.lock | 687 ++++++++++-------- Cargo.toml | 182 ++--- .../rerun/components/interpolation_mode.fbs | 4 +- crates/utils/re_mutex/README.md | 4 +- crates/viewer/re_ui/tests/help_ui_test.rs | 6 +- .../viewer/re_viewer/src/ui/memory_panel.rs | 2 +- .../content/getting-started/data-in/python.md | 4 +- .../howto/visualization/component-mappings.md | 6 +- .../howto/visualization/plot-any-scalar.md | 2 +- .../types/components/interpolation_mode.md | 6 +- examples/rust/animated_urdf/Cargo.toml | 2 +- examples/rust/blueprint/Cargo.toml | 2 +- examples/rust/blueprint_stocks/Cargo.toml | 2 +- examples/rust/clock/Cargo.toml | 2 +- examples/rust/custom_callback/Cargo.toml | 2 +- examples/rust/custom_data_loader/Cargo.toml | 2 +- .../rust/custom_store_subscriber/Cargo.toml | 2 +- examples/rust/custom_view/Cargo.toml | 2 +- examples/rust/custom_visualizer/Cargo.toml | 2 +- examples/rust/dataframe_query/Cargo.toml | 2 +- examples/rust/dna/Cargo.toml | 2 +- examples/rust/extend_viewer_ui/Cargo.toml | 2 +- examples/rust/external_data_loader/Cargo.toml | 2 +- examples/rust/graph_lattice/Cargo.toml | 2 +- examples/rust/incremental_logging/Cargo.toml | 2 +- examples/rust/lenses/Cargo.toml | 2 +- examples/rust/log_file/Cargo.toml | 2 +- examples/rust/minimal/Cargo.toml | 2 +- examples/rust/minimal_options/Cargo.toml | 2 +- examples/rust/minimal_serve/Cargo.toml | 2 +- examples/rust/objectron/Cargo.toml | 2 +- examples/rust/raw_mesh/Cargo.toml | 2 +- examples/rust/shared_recording/Cargo.toml | 2 +- examples/rust/spawn_viewer/Cargo.toml | 2 +- examples/rust/stdio/Cargo.toml | 2 +- examples/rust/template/Cargo.toml | 2 +- examples/rust/viewer_callbacks/Cargo.toml | 2 +- rerun_cpp/src/rerun/c/sdk_info.h | 4 +- rerun_js/web-viewer-react/package.json | 4 +- rerun_js/web-viewer/package.json | 2 +- rerun_notebook/pyproject.toml | 2 +- rerun_py/pyproject.toml | 2 +- rerun_py/rerun_sdk/rerun/__init__.py | 4 +- uv.lock | 2 +- 45 files changed, 540 insertions(+), 470 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 14f339e30acf..24648c73057e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,13 @@ # Rerun changelog -## 0.30.0 - (UNRELEASED) - plot any scalar & on-demand streaming +## 0.31.0 - (UNRELEASED) + + + +## [0.30.0](https://github.com/rerun-io/rerun/compare/0.29.2...0.30.0) - 2026-02-25 - plot any scalar & on-demand streaming + +🧳 Migration guide: https://rerun.io/docs/reference/migration/migration-0-30 ### ✨ Overview & highlights @@ -45,9 +51,9 @@ To quickly navigate to the desired visualizer, each time series view now shows a For more details please refer to our documentation: -- [Customize views](https://rerun.io/docs/concepts/visualization/customize-views?speculative-link) -- [Plot any scalar](https://rerun.io/docs/howto/visualization/plot-any-scalar?speculative-link) -- [Component mappings outside of plotting](https://rerun.io/docs/howto/visualizations/component-mappings?speculative-link), shown on the example of a colored point cloud +- [Customize views](https://rerun.io/docs/concepts/visualization/customize-views) +- [Plot any scalar](https://rerun.io/docs/howto/visualization/plot-any-scalar) +- [Component mappings outside of plotting](https://rerun.io/docs/howto/visualization/component-mappings), shown on the example of a colored point cloud And finally, thanks to a contribution from [@vfilter](https://github.com/vfilter), the series lines visualizer now also supports different interpolation modes to render staircase (or step) functions: @@ -131,7 +137,8 @@ For more details, see the [custom visualizer example](https://github.com/rerun-i - **CLI**: `.rrd` files are no longer tailed by default - **SDK**: `SeriesVisible` component type has been removed -🧳 Migration guide: https://rerun.io/docs/reference/migration/migration-0-30?speculative-link +🧳 Migration guide: https://rerun.io/docs/reference/migration/migration-0-30 + ### 🔎 Details @@ -169,9 +176,11 @@ For more details, see the [custom visualizer example](https://github.com/rerun-i - Basic Rust & Python blueprint API for component mappings [c6d7409](https://github.com/rerun-io/rerun/commit/c6d7409bcd94402e219b4d31c682fef52eb3b340) - Move URDF joint transform computation to Rust [4e10aea](https://github.com/rerun-io/rerun/commit/4e10aeac7fd4213f20337081ea4c738bdf69c1f6) - Allow for custom visualizer with custom shader that integrates into existing view + example [3bf7120](https://github.com/rerun-io/rerun/commit/3bf71204daed7b1d218f6fb145c20d87d0215e36) -- Rust `BlueprintActivation` default now matches python behavior [3f85747](https://github.com/rerun-io/rerun/commit/3f8574733c537038d6aee67c58e2b4967f1d21ea) +- Improve feature-gating in Rust SDK (removes datafusion & co from the SDK deps) [#12659](https://github.com/rerun-io/rerun/pull/12659) (thanks [@paulzhng](https://github.com/paulzhng)!) #### 🪳 Bug fixes +- Fix first-person camera having zero speed in zero-sized scenes [#12535](https://github.com/rerun-io/rerun/pull/12535) (thanks [@Shivam-Bhardwaj](https://github.com/Shivam-Bhardwaj)!) +- Rust `BlueprintActivation` default now matches python behavior [3f85747](https://github.com/rerun-io/rerun/commit/3f8574733c537038d6aee67c58e2b4967f1d21ea) - Fix heuristic for `target_frame` in 3D views for scenes with pinholes & named frames [3c678cc](https://github.com/rerun-io/rerun/commit/3c678cccb0e0e64bbaa2f02c6d6b0ed270bd319d) - Fix `sensor_msgs::PointCloud2` MCAP parser for small pointclouds [6491b95](https://github.com/rerun-io/rerun/commit/6491b955fb3d4d31f14ef0a6ac0d41398c3d3cf4) - Bug fix: allow copying selected text [4094a91](https://github.com/rerun-io/rerun/commit/4094a918637ad60efb27677701e0ae4d86e1b1a6) @@ -184,10 +193,12 @@ For more details, see the [custom visualizer example](https://github.com/rerun-i - Recover from failing to load RrdManifest [ad214ca](https://github.com/rerun-io/rerun/commit/ad214ca5d40112fcc3e460acb21283647801b679) - Fix transparent annotation classes not leading to transparent segmentation image [55578c3](https://github.com/rerun-io/rerun/commit/55578c35e314d284dfb82b0089ff26e26b2191e4) - Make the "copy" button work for components in the time panel tree [8f788a9](https://github.com/rerun-io/rerun/commit/8f788a9d92cd37bdff6e84dcf344d743ab3bb433) +- Fix deadlock when loading LeRobot datasets [#12652](https://github.com/rerun-io/rerun/pull/12652) - Fix NV12/YUYV ROS2 images being incorrectly loaded as depth [04beb77](https://github.com/rerun-io/rerun/commit/04beb777f4a49e6f720b09207fb5873ae22dacc0) - Don't include redo-buffer when saving blueprints [85cc4f9](https://github.com/rerun-io/rerun/commit/85cc4f96e00fb5b9b4a68e6249917832c9c7abc3) - Address issue where `.dae` with multiple triangle groups is not rendered [83e96bf](https://github.com/rerun-io/rerun/commit/83e96bf585dc7e9f708af6952d506e8013959078) - Don't reset video player on keyframe boundary for AV1 [3bcd9b8](https://github.com/rerun-io/rerun/commit/3bcd9b87d02b1af80e3bfb7c4ad42d6483f0d274) +- Fix mono8/mono16 image channel classification [#12660](https://github.com/rerun-io/rerun/pull/12660) - Set AR for wasm development build on macOS [7945601](https://github.com/rerun-io/rerun/commit/7945601bb1b162fc09e79640419a0216c9d14e8e) - Fix interactions going through popups to the timepanel [1efc8c1](https://github.com/rerun-io/rerun/commit/1efc8c131d4f13c3238972c7bcf7957e2685833e) - Fix arrow key stepping on sequence timeline [1c6a71c](https://github.com/rerun-io/rerun/commit/1c6a71c77be2619a3c7abb6eb5c22a6fbbdbf2be) @@ -197,6 +208,7 @@ For more details, see the [custom visualizer example](https://github.com/rerun-i - Extract `Selectors` from (nested) `StructArray` fields [8319663](https://github.com/rerun-io/rerun/commit/8319663a38d538adb20b2785c107d3394a969dce) - Video player fetching missing chunks and less memory usage with big gops [2596f4e](https://github.com/rerun-io/rerun/commit/2596f4ed342ef5fa5363354c82699d53783f7792) - Stop offering to visualize static scalar data as time series (would previously emitting a visualization error) [cc8c82c](https://github.com/rerun-io/rerun/commit/cc8c82c53e5bf9c7aad62f279f24de3537e268de) +- Source selector for visualizer components [#12548](https://github.com/rerun-io/rerun/pull/12548) - Implement `Selector` when resolving component mappings [81879fd](https://github.com/rerun-io/rerun/commit/81879fd3f0cdbe3938865b4fa8ad2ba7b383ff24) - New (simpler) heuristic for spawning time series views [53fc1fe](https://github.com/rerun-io/rerun/commit/53fc1fe6e938b08d0a4e5dc9a4ac32a920d10d50) - Show at most 20 time series plots per entity, hide legend for more than 20 plots [b103ed5](https://github.com/rerun-io/rerun/commit/b103ed5dcc16a1384ac63bf75e5596b3b31d0d06) @@ -220,7 +232,9 @@ For more details, see the [custom visualizer example](https://github.com/rerun-i - Visualizers can be added right from a view's selection [2e0f4cf](https://github.com/rerun-io/rerun/commit/2e0f4cf7154cfcaac206e8274bd682741218e691) - Make columns reorderable by entity in the dataframe view [e1e439e](https://github.com/rerun-io/rerun/commit/e1e439e0ceb4604334d4e33f00e68a90613e99e2) - Allow loading arbitrary data loader files (mcaps, pngs, ...) via http urls [a02a22b](https://github.com/rerun-io/rerun/commit/a02a22b90a2c91b3c9b979c76b8ec83bd5b67477) +- Drop cached videos once all referencing episodes are loaded [#12653](https://github.com/rerun-io/rerun/pull/12653) - Add auto-scroll feature and time indicator to the dataframe view [514f1f3](https://github.com/rerun-io/rerun/commit/514f1f32de4e0e87ecd45ce624145f9625b185e5) +- Add `InterpolationMode` component for step function rendering [#12657](https://github.com/rerun-io/rerun/pull/12657) (thanks [@vfilter](https://github.com/vfilter)!) - Allow loading extensionless http urls via magic bytes detection [daf7a35](https://github.com/rerun-io/rerun/commit/daf7a35ebd5b25a3b53c7fda9d4b6c77dee0e705) - Fixes performance issue of too many time series plots [a74b382](https://github.com/rerun-io/rerun/commit/a74b382b9ce0b413f14d96d4a2f264e1f4b2abe8) - Support `(U)Int16` in time series plots [6bb58e4](https://github.com/rerun-io/rerun/commit/6bb58e489ba76c1744599e222180f9a358720933) @@ -242,6 +256,8 @@ For more details, see the [custom visualizer example](https://github.com/rerun-i - Make handling of out of order video chunks much faster [f050460](https://github.com/rerun-io/rerun/commit/f050460c8b37c83beb0a0c33cf504d79e2aeb206) #### 🧑‍🏫 Examples +- Rerun to LeRobot export example [#12541](https://github.com/rerun-io/rerun/pull/12541) +- Add ROS TF example [#12603](https://github.com/rerun-io/rerun/pull/12603) - Add webpage example [e2fd7f6](https://github.com/rerun-io/rerun/commit/e2fd7f65fa0b264f68a61935aba33ef54ff20ab4) #### 📚 Docs @@ -279,10 +295,7 @@ For more details, see the [custom visualizer example](https://github.com/rerun-i - Add `rerun auth logout` [7b3ae54](https://github.com/rerun-io/rerun/commit/7b3ae543dc0de39cf382aefc435f420de99ef223) - `rerun rrd split` [9bde24f](https://github.com/rerun-io/rerun/commit/9bde24fb5860f507bec155da377f40c1b8b0359e) - Add `--follow` option to explicitly follow files and URLs [c34a84b](https://github.com/rerun-io/rerun/commit/c34a84b5382620033182bb41719d1e1de74f10c6) -- base changelog update [250bb52](https://github.com/rerun-io/rerun/commit/250bb5248b2178d02e656381af274e646ac6d239) -- remove speculative links [c1ddfd6](https://github.com/rerun-io/rerun/commit/c1ddfd6f676ca91b32f79c9e100aa2c8972fe502) -- Bump versions to 0.30.0-rc.1 [b7fa785](https://github.com/rerun-io/rerun/commit/b7fa78566ae4ca71775e3e521aa28f2465c72f8f) -- fill in breaking changes + migration guide link [1555e63](https://github.com/rerun-io/rerun/commit/1555e63aea63308f4bae6ae10dc6d620bd28b016) + ## [0.29.2](https://github.com/rerun-io/rerun/compare/0.29.1...0.29.2) - 2026-02-13 - Bug fixes and documentation update diff --git a/Cargo.lock b/Cargo.lock index 99edf223aed0..eff1f49bdeb4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -254,7 +254,7 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "animated_urdf" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "anyhow", "clap", @@ -382,7 +382,7 @@ dependencies = [ "argh_shared", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -414,9 +414,9 @@ checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" [[package]] name = "arrow" -version = "57.2.0" +version = "57.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a2b10dcb159faf30d3f81f6d56c1211a5bea2ca424eabe477648a44b993320e" +checksum = "e4754a624e5ae42081f464514be454b39711daae0458906dacde5f4c632f33a8" dependencies = [ "arrow-arith", "arrow-array", @@ -436,9 +436,9 @@ dependencies = [ [[package]] name = "arrow-arith" -version = "57.2.0" +version = "57.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "288015089e7931843c80ed4032c5274f02b37bcb720c4a42096d50b390e70372" +checksum = "f7b3141e0ec5145a22d8694ea8b6d6f69305971c4fa1c1a13ef0195aef2d678b" dependencies = [ "arrow-array", "arrow-buffer", @@ -450,9 +450,9 @@ dependencies = [ [[package]] name = "arrow-array" -version = "57.2.0" +version = "57.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65ca404ea6191e06bf30956394173337fa9c35f445bd447fe6c21ab944e1a23c" +checksum = "4c8955af33b25f3b175ee10af580577280b4bd01f7e823d94c7cdef7cf8c9aef" dependencies = [ "ahash", "arrow-buffer", @@ -461,7 +461,7 @@ dependencies = [ "chrono", "chrono-tz", "half", - "hashbrown 0.16.0", + "hashbrown 0.16.1", "num-complex", "num-integer", "num-traits", @@ -469,9 +469,9 @@ dependencies = [ [[package]] name = "arrow-buffer" -version = "57.2.0" +version = "57.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36356383099be0151dacc4245309895f16ba7917d79bdb71a7148659c9206c56" +checksum = "c697ddca96183182f35b3a18e50b9110b11e916d7b7799cbfd4d34662f2c56c2" dependencies = [ "bytes", "half", @@ -481,9 +481,9 @@ dependencies = [ [[package]] name = "arrow-cast" -version = "57.2.0" +version = "57.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8e372ed52bd4ee88cc1e6c3859aa7ecea204158ac640b10e187936e7e87074" +checksum = "646bbb821e86fd57189c10b4fcdaa941deaf4181924917b0daa92735baa6ada5" dependencies = [ "arrow-array", "arrow-buffer", @@ -503,9 +503,9 @@ dependencies = [ [[package]] name = "arrow-csv" -version = "57.2.0" +version = "57.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e4100b729fe656f2e4fb32bc5884f14acf9118d4ad532b7b33c1132e4dce896" +checksum = "8da746f4180004e3ce7b83c977daf6394d768332349d3d913998b10a120b790a" dependencies = [ "arrow-array", "arrow-cast", @@ -518,9 +518,9 @@ dependencies = [ [[package]] name = "arrow-data" -version = "57.2.0" +version = "57.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf87f4ff5fc13290aa47e499a8b669a82c5977c6a1fedce22c7f542c1fd5a597" +checksum = "1fdd994a9d28e6365aa78e15da3f3950c0fdcea6b963a12fa1c391afb637b304" dependencies = [ "arrow-buffer", "arrow-schema", @@ -531,9 +531,9 @@ dependencies = [ [[package]] name = "arrow-ipc" -version = "57.2.0" +version = "57.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb3ca63edd2073fcb42ba112f8ae165df1de935627ead6e203d07c99445f2081" +checksum = "abf7df950701ab528bf7c0cf7eeadc0445d03ef5d6ffc151eaae6b38a58feff1" dependencies = [ "arrow-array", "arrow-buffer", @@ -547,9 +547,9 @@ dependencies = [ [[package]] name = "arrow-json" -version = "57.2.0" +version = "57.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a36b2332559d3310ebe3e173f75b29989b4412df4029a26a30cc3f7da0869297" +checksum = "0ff8357658bedc49792b13e2e862b80df908171275f8e6e075c460da5ee4bf86" dependencies = [ "arrow-array", "arrow-buffer", @@ -558,7 +558,7 @@ dependencies = [ "arrow-schema", "chrono", "half", - "indexmap 2.12.0", + "indexmap 2.13.0", "itoa", "lexical-core", "memchr", @@ -571,9 +571,9 @@ dependencies = [ [[package]] name = "arrow-ord" -version = "57.2.0" +version = "57.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c4e0530272ca755d6814218dffd04425c5b7854b87fa741d5ff848bf50aa39" +checksum = "f7d8f1870e03d4cbed632959498bcc84083b5a24bded52905ae1695bd29da45b" dependencies = [ "arrow-array", "arrow-buffer", @@ -584,9 +584,9 @@ dependencies = [ [[package]] name = "arrow-pyarrow" -version = "57.2.0" +version = "57.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f45c7989cb70214b2f362eaa10266d15e1a433692f2ea1514018be3aace679f4" +checksum = "d18c442b4c266aaf3d7f7dd40fd7ae058cef7f113b00ff0cd8256e1e218ec544" dependencies = [ "arrow-array", "arrow-data", @@ -596,9 +596,9 @@ dependencies = [ [[package]] name = "arrow-row" -version = "57.2.0" +version = "57.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b07f52788744cc71c4628567ad834cadbaeb9f09026ff1d7a4120f69edf7abd3" +checksum = "18228633bad92bff92a95746bbeb16e5fc318e8382b75619dec26db79e4de4c0" dependencies = [ "arrow-array", "arrow-buffer", @@ -609,9 +609,9 @@ dependencies = [ [[package]] name = "arrow-schema" -version = "57.2.0" +version = "57.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bb63203e8e0e54b288d0d8043ca8fa1013820822a27692ef1b78a977d879f2c" +checksum = "8c872d36b7bf2a6a6a2b40de9156265f0242910791db366a2c17476ba8330d68" dependencies = [ "bitflags 2.9.4", "serde_core", @@ -620,9 +620,9 @@ dependencies = [ [[package]] name = "arrow-select" -version = "57.2.0" +version = "57.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c96d8a1c180b44ecf2e66c9a2f2bbcb8b1b6f14e165ce46ac8bde211a363411b" +checksum = "68bf3e3efbd1278f770d67e5dc410257300b161b93baedb3aae836144edcaf4b" dependencies = [ "ahash", "arrow-array", @@ -634,9 +634,9 @@ dependencies = [ [[package]] name = "arrow-string" -version = "57.2.0" +version = "57.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8ad6a81add9d3ea30bf8374ee8329992c7fd246ffd8b7e2f48a3cea5aa0cc9a" +checksum = "85e968097061b3c0e9fe3079cf2e703e487890700546b5b0647f60fca1b5a8d8" dependencies = [ "arrow-array", "arrow-buffer", @@ -803,7 +803,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -843,7 +843,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -860,7 +860,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -904,7 +904,7 @@ checksum = "49c98dba06b920588de7d63f6acc23f1e6a9fade5fd6198e564506334fb5a4f5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -1253,7 +1253,7 @@ dependencies = [ [[package]] name = "blueprint" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "clap", "rerun", @@ -1261,7 +1261,7 @@ dependencies = [ [[package]] name = "blueprint_stocks" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "anyhow", "chrono", @@ -1294,7 +1294,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -1358,7 +1358,7 @@ checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -1710,7 +1710,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -1736,7 +1736,7 @@ dependencies = [ [[package]] name = "clock" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "anyhow", "clap", @@ -1751,7 +1751,7 @@ checksum = "fe6d2e5af09e8c8ad56c969f2157a3d4238cebc7c55f0a517728c38f7b200f81" dependencies = [ "serde", "termcolor", - "unicode-width 0.1.14", + "unicode-width 0.2.2", ] [[package]] @@ -1813,7 +1813,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f76990911f2267d837d9d0ad060aa63aaad170af40904b29461734c339030d4d" dependencies = [ "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -2199,7 +2199,7 @@ checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f" [[package]] name = "custom_callback" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "bincode", "mimalloc", @@ -2211,7 +2211,7 @@ dependencies = [ [[package]] name = "custom_data_loader" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "crossbeam", "re_build_tools", @@ -2221,7 +2221,7 @@ dependencies = [ [[package]] name = "custom_store_subscriber" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "re_build_tools", "rerun", @@ -2229,7 +2229,7 @@ dependencies = [ [[package]] name = "custom_view" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "mimalloc", "rerun", @@ -2237,7 +2237,7 @@ dependencies = [ [[package]] name = "custom_visualizer" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "bytemuck", "mimalloc", @@ -2277,7 +2277,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -2288,7 +2288,7 @@ checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" dependencies = [ "darling_core", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -2313,7 +2313,7 @@ checksum = "5c297a1c74b71ae29df00c3e22dd9534821d60eb9af5a0192823fa2acea70c2a" [[package]] name = "dataframe_query" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "itertools 0.14.0", "rerun", @@ -2431,7 +2431,7 @@ dependencies = [ "chrono", "half", "hashbrown 0.14.5", - "indexmap 2.12.0", + "indexmap 2.13.0", "libc", "log", "object_store", @@ -2622,7 +2622,7 @@ dependencies = [ "datafusion-functions-aggregate-common", "datafusion-functions-window-common", "datafusion-physical-expr-common", - "indexmap 2.12.0", + "indexmap 2.13.0", "itertools 0.14.0", "paste", "serde_json", @@ -2637,7 +2637,7 @@ checksum = "5ce2fb1b8c15c9ac45b0863c30b268c69dc9ee7a1ee13ecf5d067738338173dc" dependencies = [ "arrow", "datafusion-common", - "indexmap 2.12.0", + "indexmap 2.13.0", "itertools 0.14.0", "paste", ] @@ -2804,7 +2804,7 @@ checksum = "1063ad4c9e094b3f798acee16d9a47bd7372d9699be2de21b05c3bd3f34ab848" dependencies = [ "datafusion-doc", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -2819,7 +2819,7 @@ dependencies = [ "datafusion-expr", "datafusion-expr-common", "datafusion-physical-expr", - "indexmap 2.12.0", + "indexmap 2.13.0", "itertools 0.14.0", "log", "regex", @@ -2841,7 +2841,7 @@ dependencies = [ "datafusion-physical-expr-common", "half", "hashbrown 0.14.5", - "indexmap 2.12.0", + "indexmap 2.13.0", "itertools 0.14.0", "parking_lot", "paste", @@ -2918,7 +2918,7 @@ dependencies = [ "futures", "half", "hashbrown 0.14.5", - "indexmap 2.12.0", + "indexmap 2.13.0", "itertools 0.14.0", "log", "parking_lot", @@ -3006,7 +3006,7 @@ dependencies = [ "chrono", "datafusion-common", "datafusion-expr", - "indexmap 2.12.0", + "indexmap 2.13.0", "log", "regex", "sqlparser", @@ -3050,7 +3050,7 @@ checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -3061,9 +3061,9 @@ checksum = "edf215dbb8cb1409cca7645aaed35f9e39fb0a21855bba1ac48bc0334903bf66" [[package]] name = "dify" -version = "0.7.4" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11217d469eafa3b809ad84651eb9797ccbb440b4a916d5d85cb1b994e89787f6" +checksum = "90ce0fb972943b4e88cd03b8f92953df0c71bb05e0bde8e5b684895d808013cc" dependencies = [ "anyhow", "colored", @@ -3139,7 +3139,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -3153,7 +3153,7 @@ dependencies = [ [[package]] name = "dna" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "itertools 0.14.0", "rand 0.9.2", @@ -3206,7 +3206,7 @@ dependencies = [ [[package]] name = "ecolor" version = "0.33.3" -source = "git+https://github.com/emilk/egui.git?branch=main#c89a4d1b38460cd58e25db5761355b837e57b719" +source = "git+https://github.com/emilk/egui.git?branch=main#9276778181619be4065715ab03d36ca0b318667d" dependencies = [ "bytemuck", "color-hex", @@ -3223,7 +3223,7 @@ checksum = "18aade80d5e09429040243ce1143ddc08a92d7a22820ac512610410a4dd5214f" [[package]] name = "eframe" version = "0.33.3" -source = "git+https://github.com/emilk/egui.git?branch=main#c89a4d1b38460cd58e25db5761355b837e57b719" +source = "git+https://github.com/emilk/egui.git?branch=main#9276778181619be4065715ab03d36ca0b318667d" dependencies = [ "ahash", "bytemuck", @@ -3261,7 +3261,7 @@ dependencies = [ [[package]] name = "egui" version = "0.33.3" -source = "git+https://github.com/emilk/egui.git?branch=main#c89a4d1b38460cd58e25db5761355b837e57b719" +source = "git+https://github.com/emilk/egui.git?branch=main#9276778181619be4065715ab03d36ca0b318667d" dependencies = [ "accesskit", "ahash", @@ -3281,7 +3281,7 @@ dependencies = [ [[package]] name = "egui-wgpu" version = "0.33.3" -source = "git+https://github.com/emilk/egui.git?branch=main#c89a4d1b38460cd58e25db5761355b837e57b719" +source = "git+https://github.com/emilk/egui.git?branch=main#9276778181619be4065715ab03d36ca0b318667d" dependencies = [ "ahash", "bytemuck", @@ -3300,7 +3300,7 @@ dependencies = [ [[package]] name = "egui-winit" version = "0.33.3" -source = "git+https://github.com/emilk/egui.git?branch=main#c89a4d1b38460cd58e25db5761355b837e57b719" +source = "git+https://github.com/emilk/egui.git?branch=main#9276778181619be4065715ab03d36ca0b318667d" dependencies = [ "accesskit_winit", "arboard", @@ -3368,7 +3368,7 @@ dependencies = [ [[package]] name = "egui_extras" version = "0.33.3" -source = "git+https://github.com/emilk/egui.git?branch=main#c89a4d1b38460cd58e25db5761355b837e57b719" +source = "git+https://github.com/emilk/egui.git?branch=main#9276778181619be4065715ab03d36ca0b318667d" dependencies = [ "ahash", "egui", @@ -3385,7 +3385,7 @@ dependencies = [ [[package]] name = "egui_glow" version = "0.33.3" -source = "git+https://github.com/emilk/egui.git?branch=main#c89a4d1b38460cd58e25db5761355b837e57b719" +source = "git+https://github.com/emilk/egui.git?branch=main#9276778181619be4065715ab03d36ca0b318667d" dependencies = [ "bytemuck", "egui", @@ -3401,7 +3401,7 @@ dependencies = [ [[package]] name = "egui_kittest" version = "0.33.3" -source = "git+https://github.com/emilk/egui.git?branch=main#c89a4d1b38460cd58e25db5761355b837e57b719" +source = "git+https://github.com/emilk/egui.git?branch=main#9276778181619be4065715ab03d36ca0b318667d" dependencies = [ "dify", "eframe", @@ -3413,7 +3413,7 @@ dependencies = [ "pollster", "serde", "tempfile", - "toml 0.8.23", + "toml 1.0.3+spec-1.1.0", "wgpu", ] @@ -3480,7 +3480,7 @@ checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "emath" version = "0.33.3" -source = "git+https://github.com/emilk/egui.git?branch=main#c89a4d1b38460cd58e25db5761355b837e57b719" +source = "git+https://github.com/emilk/egui.git?branch=main#9276778181619be4065715ab03d36ca0b318667d" dependencies = [ "bytemuck", "serde", @@ -3525,7 +3525,7 @@ checksum = "f282cfdfe92516eb26c2af8589c274c7c17681f5ecc03c18255fe741c6aa64eb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -3546,7 +3546,7 @@ checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -3557,7 +3557,7 @@ checksum = "2f9ed6b3789237c8a0c1c505af1c7eb2c560df6186f01b098c3a1064ea532f38" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -3578,7 +3578,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -3606,7 +3606,7 @@ dependencies = [ [[package]] name = "epaint" version = "0.33.3" -source = "git+https://github.com/emilk/egui.git?branch=main#c89a4d1b38460cd58e25db5761355b837e57b719" +source = "git+https://github.com/emilk/egui.git?branch=main#9276778181619be4065715ab03d36ca0b318667d" dependencies = [ "ahash", "bytemuck", @@ -3627,7 +3627,7 @@ dependencies = [ [[package]] name = "epaint_default_fonts" version = "0.33.3" -source = "git+https://github.com/emilk/egui.git?branch=main#c89a4d1b38460cd58e25db5761355b837e57b719" +source = "git+https://github.com/emilk/egui.git?branch=main#9276778181619be4065715ab03d36ca0b318667d" [[package]] name = "equivalent" @@ -3700,7 +3700,7 @@ dependencies = [ [[package]] name = "extend_viewer_ui" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "mimalloc", "rerun", @@ -3850,9 +3850,9 @@ checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" [[package]] name = "font-types" -version = "0.10.1" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39a654f404bbcbd48ea58c617c2993ee91d1cb63727a37bf2323a4edeed1b8c5" +checksum = "b1e4d2d0cf79d38430cc9dc9aadec84774bff2e1ba30ae2bf6c16cfce9385a23" dependencies = [ "bytemuck", ] @@ -3875,7 +3875,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -4012,7 +4012,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -4071,7 +4071,7 @@ dependencies = [ "libc", "log", "rustversion", - "windows 0.58.0", + "windows 0.61.3", ] [[package]] @@ -4189,9 +4189,9 @@ dependencies = [ [[package]] name = "geographiclib-rs" -version = "0.2.5" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f611040a2bb37eaa29a78a128d1e92a378a03e0b6e66ae27398d42b1ba9a7841" +checksum = "c5a7f08910fd98737a6eda7568e7c5e645093e073328eeef49758cfe8b0489c7" dependencies = [ "libm", ] @@ -4345,7 +4345,7 @@ dependencies = [ "inflections", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -4479,7 +4479,7 @@ dependencies = [ [[package]] name = "graph_lattice" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "anyhow", "clap", @@ -4499,7 +4499,7 @@ dependencies = [ "futures-core", "futures-sink", "http", - "indexmap 2.12.0", + "indexmap 2.13.0", "slab", "tokio", "tokio-util", @@ -4571,9 +4571,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.16.0" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" dependencies = [ "allocator-api2", "equivalent", @@ -4905,7 +4905,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core 0.58.0", + "windows-core 0.62.2", ] [[package]] @@ -5062,7 +5062,7 @@ checksum = "edcd27d72f2f071c64249075f42e205ff93c9a4c5f6c6da53e79ed9f9832c285" [[package]] name = "incremental_logging" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "anyhow", "clap", @@ -5088,12 +5088,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.12.0" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" dependencies = [ "equivalent", - "hashbrown 0.16.0", + "hashbrown 0.16.1", "serde", "serde_core", ] @@ -5294,7 +5294,7 @@ checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -5453,9 +5453,9 @@ dependencies = [ [[package]] name = "kurbo" -version = "0.12.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce9729cc38c18d86123ab736fd2e7151763ba226ac2490ec092d1dd148825e32" +checksum = "7564e90fe3c0d5771e1f0bc95322b21baaeaa0d9213fa6a0b61c99f8b17b3bfb" dependencies = [ "arrayvec", "euclid", @@ -5944,7 +5944,7 @@ checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" [[package]] name = "lenses" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "anyhow", "arrow", @@ -6016,9 +6016,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.180" +version = "0.2.182" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" +checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" [[package]] name = "libloading" @@ -6124,9 +6124,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.28" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "log-once" @@ -6139,7 +6139,7 @@ dependencies = [ [[package]] name = "log_benchmark" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "anyhow", "clap", @@ -6152,7 +6152,7 @@ dependencies = [ [[package]] name = "log_file" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "anyhow", "clap", @@ -6181,7 +6181,7 @@ dependencies = [ "quote", "regex-syntax", "rustc_version", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -6221,7 +6221,7 @@ version = "0.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1dc47f592c06f33f8e3aea9591776ec7c9f9e4124778ff8a3c3b87159f7e593" dependencies = [ - "hashbrown 0.16.0", + "hashbrown 0.16.1", ] [[package]] @@ -6429,7 +6429,7 @@ checksum = "49e7bc1560b95a3c4a25d03de42fe76ca718ab92d1a22a55b9b4cf67b3ae635c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -6480,7 +6480,7 @@ dependencies = [ [[package]] name = "minimal" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "rerun", ] @@ -6493,7 +6493,7 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "minimal_options" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "anyhow", "clap", @@ -6503,7 +6503,7 @@ dependencies = [ [[package]] name = "minimal_serve" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "rerun", ] @@ -6603,9 +6603,9 @@ dependencies = [ "cfg_aliases", "codespan-reporting", "half", - "hashbrown 0.16.0", + "hashbrown 0.16.1", "hexf-parse", - "indexmap 2.12.0", + "indexmap 2.13.0", "libm", "log", "num-traits", @@ -6796,7 +6796,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -6859,7 +6859,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -7269,7 +7269,7 @@ dependencies = [ [[package]] name = "objectron" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "anyhow", "clap", @@ -7511,9 +7511,9 @@ dependencies = [ [[package]] name = "parquet" -version = "57.2.0" +version = "57.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f6a2926a30477c0b95fea6c28c3072712b139337a242c2cc64817bdc20a8854" +checksum = "6ee96b29972a257b855ff2341b37e61af5f12d6af1158b6dcdb5b31ea07bb3cb" dependencies = [ "ahash", "arrow-array", @@ -7530,7 +7530,7 @@ dependencies = [ "flate2", "futures", "half", - "hashbrown 0.16.0", + "hashbrown 0.16.1", "lz4_flex 0.12.0", "num-bigint", "num-integer", @@ -7599,13 +7599,13 @@ checksum = "132dca9b868d927b35b5dd728167b2dee150eb1ad686008fc71ccb298b776fca" [[package]] name = "peniko" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3c76095c9a636173600478e0373218c7b955335048c2bcd12dc6a79657649d8" +checksum = "9a2b6aadb221872732e87d465213e9be5af2849b0e8cc5300a8ba98fffa2e00a" dependencies = [ "bytemuck", "color", - "kurbo 0.12.0", + "kurbo 0.13.0", "linebender_resource_handle", "smallvec", ] @@ -7652,7 +7652,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -7672,7 +7672,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ "fixedbitset 0.4.2", - "indexmap 2.12.0", + "indexmap 2.13.0", ] [[package]] @@ -7682,7 +7682,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" dependencies = [ "fixedbitset 0.5.7", - "indexmap 2.12.0", + "indexmap 2.13.0", ] [[package]] @@ -7693,7 +7693,7 @@ checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455" dependencies = [ "fixedbitset 0.5.7", "hashbrown 0.15.5", - "indexmap 2.12.0", + "indexmap 2.13.0", "serde", ] @@ -7736,7 +7736,7 @@ dependencies = [ "phf_shared 0.11.3", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", "unicase", ] @@ -7782,7 +7782,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -7816,7 +7816,7 @@ checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "plot_dashboard_stress" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "anyhow", "clap", @@ -7861,7 +7861,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe55bbee2b70d1c1e58d8340eda9a80c5ce11fb9b1bc10b5fc1575c490d38fa9" dependencies = [ "byteorder", - "indexmap 2.12.0", + "indexmap 2.13.0", "peg", ] @@ -7963,7 +7963,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -8001,7 +8001,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52717f9a02b6965224f95ca2a81e2e0c5c43baacd28ca057577988930b6c3d5b" dependencies = [ "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -8024,7 +8024,7 @@ checksum = "9adf1691c04c0a5ff46ff8f262b58beb07b0dbb61f96f9f54f6cbd82106ed87f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -8053,7 +8053,7 @@ dependencies = [ "prost", "prost-types", "regex", - "syn 2.0.114", + "syn 2.0.117", "tempfile", ] @@ -8067,7 +8067,7 @@ dependencies = [ "itertools 0.14.0", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -8187,7 +8187,7 @@ dependencies = [ "proc-macro2", "pyo3-macros-backend", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -8200,7 +8200,7 @@ dependencies = [ "proc-macro2", "pyo3-build-config", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -8428,7 +8428,7 @@ checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" [[package]] name = "raw_mesh" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "anyhow", "bytes", @@ -8465,7 +8465,7 @@ dependencies = [ [[package]] name = "re_analytics" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "crossbeam", "directories", @@ -8486,7 +8486,7 @@ dependencies = [ [[package]] name = "re_arrow_combinators" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "arrow", "insta", @@ -8498,7 +8498,7 @@ dependencies = [ [[package]] name = "re_arrow_ui" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "arrow", "egui", @@ -8515,7 +8515,7 @@ dependencies = [ [[package]] name = "re_arrow_util" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "anyhow", "arrow", @@ -8533,7 +8533,7 @@ dependencies = [ [[package]] name = "re_auth" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "async-trait", "base64 0.22.1", @@ -8567,7 +8567,7 @@ dependencies = [ [[package]] name = "re_backoff" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "getrandom 0.3.3", "js-sys", @@ -8579,7 +8579,7 @@ dependencies = [ [[package]] name = "re_blueprint_tree" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "egui", "egui_kittest", @@ -8606,7 +8606,7 @@ dependencies = [ [[package]] name = "re_build_info" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "re_byte_size", "serde", @@ -8614,7 +8614,7 @@ dependencies = [ [[package]] name = "re_build_tools" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "anyhow", "cargo_metadata 0.23.0", @@ -8628,7 +8628,7 @@ dependencies = [ [[package]] name = "re_byte_size" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "arrow", "glam", @@ -8641,7 +8641,7 @@ dependencies = [ [[package]] name = "re_capabilities" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "document-features", "egui", @@ -8651,14 +8651,14 @@ dependencies = [ [[package]] name = "re_case" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "convert_case", ] [[package]] name = "re_chunk" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "ahash", "anyhow", @@ -8690,7 +8690,7 @@ dependencies = [ [[package]] name = "re_chunk_store" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "ahash", "anyhow", @@ -8724,7 +8724,7 @@ dependencies = [ [[package]] name = "re_chunk_store_ui" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "arrow", "egui", @@ -8743,7 +8743,7 @@ dependencies = [ [[package]] name = "re_component_fallbacks" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "re_log_types", "re_sdk_types", @@ -8752,7 +8752,7 @@ dependencies = [ [[package]] name = "re_component_ui" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "arrow", "egui", @@ -8779,7 +8779,7 @@ dependencies = [ [[package]] name = "re_context_menu" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "egui", "egui_tiles", @@ -8800,7 +8800,7 @@ dependencies = [ [[package]] name = "re_crash_handler" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "backtrace", "econtext", @@ -8813,7 +8813,7 @@ dependencies = [ [[package]] name = "re_data_loader" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "ahash", "anyhow", @@ -8821,7 +8821,7 @@ dependencies = [ "cfg-if", "crossbeam", "image", - "indexmap 2.12.0", + "indexmap 2.13.0", "insta", "itertools 0.14.0", "mcap", @@ -8856,7 +8856,7 @@ dependencies = [ [[package]] name = "re_data_source" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "anyhow", "ehttp", @@ -8880,7 +8880,7 @@ dependencies = [ [[package]] name = "re_data_ui" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "ahash", "anyhow", @@ -8916,7 +8916,7 @@ dependencies = [ [[package]] name = "re_dataframe" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "anyhow", "arrow", @@ -8942,7 +8942,7 @@ dependencies = [ [[package]] name = "re_dataframe_ui" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "ahash", "arrow", @@ -8985,7 +8985,7 @@ dependencies = [ [[package]] name = "re_datafusion" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "ahash", "arrow", @@ -9016,7 +9016,7 @@ dependencies = [ [[package]] name = "re_dev_tools" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "anyhow", "argh", @@ -9044,14 +9044,14 @@ dependencies = [ [[package]] name = "re_entity_db" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "ahash", "anyhow", "arrow", "document-features", "emath", - "indexmap 2.12.0", + "indexmap 2.13.0", "itertools 0.14.0", "nohash-hasher", "poll-promise", @@ -9082,14 +9082,14 @@ dependencies = [ [[package]] name = "re_error" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "anyhow", ] [[package]] name = "re_format" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "half", "itertools 0.14.0", @@ -9099,7 +9099,7 @@ dependencies = [ [[package]] name = "re_grpc_client" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "async-stream", "crossbeam", @@ -9124,7 +9124,7 @@ dependencies = [ [[package]] name = "re_grpc_server" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "anyhow", "async-stream", @@ -9155,7 +9155,7 @@ dependencies = [ [[package]] name = "re_int_histogram" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "criterion", "insta", @@ -9168,7 +9168,7 @@ dependencies = [ [[package]] name = "re_integration_test" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "egui", "egui_kittest", @@ -9199,7 +9199,7 @@ dependencies = [ [[package]] name = "re_lenses" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "arrow", "itertools 0.14.0", @@ -9216,7 +9216,7 @@ dependencies = [ [[package]] name = "re_log" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "crossbeam", "env_filter", @@ -9231,7 +9231,7 @@ dependencies = [ [[package]] name = "re_log_channel" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "camino", "crossbeam", @@ -9248,7 +9248,7 @@ dependencies = [ [[package]] name = "re_log_encoding" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "arrow", "bytes", @@ -9288,7 +9288,7 @@ dependencies = [ [[package]] name = "re_log_types" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "ahash", "arrow", @@ -9326,7 +9326,7 @@ dependencies = [ [[package]] name = "re_mcap" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "ahash", "anyhow", @@ -9352,7 +9352,7 @@ dependencies = [ [[package]] name = "re_memory" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "ahash", "backtrace", @@ -9374,7 +9374,7 @@ dependencies = [ [[package]] name = "re_memory_view" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "eframe", "egui", @@ -9399,7 +9399,7 @@ dependencies = [ [[package]] name = "re_mutex" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "cfg-if", "parking_lot", @@ -9408,7 +9408,7 @@ dependencies = [ [[package]] name = "re_perf_telemetry" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "ahash", "anyhow", @@ -9439,7 +9439,7 @@ dependencies = [ [[package]] name = "re_protos" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "arrow", "http", @@ -9469,7 +9469,7 @@ dependencies = [ [[package]] name = "re_protos_builder" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "camino", "re_log", @@ -9478,7 +9478,7 @@ dependencies = [ [[package]] name = "re_query" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "ahash", "anyhow", @@ -9511,7 +9511,7 @@ dependencies = [ [[package]] name = "re_quota_channel" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "crossbeam", "parking_lot", @@ -9546,7 +9546,7 @@ dependencies = [ [[package]] name = "re_recording_panel" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "ahash", "egui", @@ -9569,7 +9569,7 @@ dependencies = [ [[package]] name = "re_redap_browser" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "ahash", "cfg-if", @@ -9604,7 +9604,7 @@ dependencies = [ [[package]] name = "re_redap_client" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "ahash", "arrow", @@ -9639,7 +9639,7 @@ dependencies = [ [[package]] name = "re_redap_tests" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "anyhow", "arrow", @@ -9670,7 +9670,7 @@ dependencies = [ [[package]] name = "re_renderer" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "ahash", "anyhow", @@ -9725,7 +9725,7 @@ dependencies = [ [[package]] name = "re_renderer_examples" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "ahash", "anyhow", @@ -9748,7 +9748,7 @@ dependencies = [ [[package]] name = "re_ros_msg" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "anyhow", "serde", @@ -9757,7 +9757,7 @@ dependencies = [ [[package]] name = "re_rvl" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "byteorder", "thiserror 2.0.17", @@ -9765,7 +9765,7 @@ dependencies = [ [[package]] name = "re_sdk" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "ahash", "arrow", @@ -9809,7 +9809,7 @@ dependencies = [ [[package]] name = "re_sdk_types" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "array-init", "arrow", @@ -9821,7 +9821,7 @@ dependencies = [ "glam", "half", "image", - "indexmap 2.12.0", + "indexmap 2.13.0", "infer", "itertools 0.14.0", "macaw", @@ -9851,7 +9851,7 @@ dependencies = [ [[package]] name = "re_selection_panel" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "arrow", "egui", @@ -9885,7 +9885,7 @@ dependencies = [ [[package]] name = "re_server" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "ahash", "anyhow", @@ -9942,7 +9942,7 @@ dependencies = [ [[package]] name = "re_sorbet" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "arrow", "itertools 0.14.0", @@ -9962,14 +9962,14 @@ dependencies = [ [[package]] name = "re_span" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "num-traits", ] [[package]] name = "re_string_interner" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "ahash", "nohash-hasher", @@ -9981,7 +9981,7 @@ dependencies = [ [[package]] name = "re_test_context" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "ahash", "anyhow", @@ -10014,7 +10014,7 @@ dependencies = [ [[package]] name = "re_test_viewport" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "ahash", "egui", @@ -10031,7 +10031,7 @@ dependencies = [ [[package]] name = "re_tf" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "ahash", "arrow", @@ -10057,7 +10057,7 @@ dependencies = [ [[package]] name = "re_time_panel" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "anyhow", "criterion", @@ -10089,7 +10089,7 @@ dependencies = [ [[package]] name = "re_tracing" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "puffin", "puffin_http", @@ -10100,7 +10100,7 @@ dependencies = [ [[package]] name = "re_tuid" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "bytemuck", "criterion", @@ -10115,14 +10115,14 @@ dependencies = [ [[package]] name = "re_types" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "re_sdk_types", ] [[package]] name = "re_types_builder" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "anyhow", "camino", @@ -10144,7 +10144,7 @@ dependencies = [ "re_tracing", "regex-lite", "serde", - "syn 2.0.114", + "syn 2.0.117", "tempfile", "toml 0.9.8", "unindent", @@ -10153,7 +10153,7 @@ dependencies = [ [[package]] name = "re_types_core" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "anyhow", "arrow", @@ -10179,7 +10179,7 @@ dependencies = [ [[package]] name = "re_ui" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "ahash", "anyhow", @@ -10220,7 +10220,7 @@ dependencies = [ [[package]] name = "re_uri" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "re_log", "re_log_types", @@ -10233,7 +10233,7 @@ dependencies = [ [[package]] name = "re_video" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "ahash", "bit-vec", @@ -10270,7 +10270,7 @@ dependencies = [ [[package]] name = "re_view" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "ahash", "egui", @@ -10296,7 +10296,7 @@ dependencies = [ [[package]] name = "re_view_bar_chart" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "arrow", "egui", @@ -10316,7 +10316,7 @@ dependencies = [ [[package]] name = "re_view_dataframe" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "anyhow", "arrow", @@ -10345,7 +10345,7 @@ dependencies = [ [[package]] name = "re_view_graph" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "ahash", "egui", @@ -10372,7 +10372,7 @@ dependencies = [ [[package]] name = "re_view_map" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "bytemuck", "egui", @@ -10397,7 +10397,7 @@ dependencies = [ [[package]] name = "re_view_spatial" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "ahash", "anyhow", @@ -10449,7 +10449,7 @@ dependencies = [ [[package]] name = "re_view_tensor" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "anyhow", "bytemuck", @@ -10475,7 +10475,7 @@ dependencies = [ [[package]] name = "re_view_text_document" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "egui", "egui_commonmark", @@ -10493,7 +10493,7 @@ dependencies = [ [[package]] name = "re_view_text_log" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "egui", "egui_extras", @@ -10515,7 +10515,7 @@ dependencies = [ [[package]] name = "re_view_time_series" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "arrayvec", "egui", @@ -10544,7 +10544,7 @@ dependencies = [ [[package]] name = "re_viewer" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "ahash", "anyhow", @@ -10645,7 +10645,7 @@ dependencies = [ [[package]] name = "re_viewer_context" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "ahash", "anyhow", @@ -10665,7 +10665,7 @@ dependencies = [ "half", "home", "image", - "indexmap 2.12.0", + "indexmap 2.13.0", "itertools 0.14.0", "linked-hash-map", "macaw", @@ -10721,7 +10721,7 @@ dependencies = [ [[package]] name = "re_viewport" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "ahash", "egui", @@ -10745,7 +10745,7 @@ dependencies = [ [[package]] name = "re_viewport_blueprint" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "ahash", "arrow", @@ -10775,7 +10775,7 @@ dependencies = [ [[package]] name = "re_web_viewer_server" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "document-features", "re_analytics", @@ -10788,9 +10788,9 @@ dependencies = [ [[package]] name = "read-fonts" -version = "0.35.0" +version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6717cf23b488adf64b9d711329542ba34de147df262370221940dfabc2c91358" +checksum = "7b634fabf032fab15307ffd272149b622260f55974d9fad689292a5d33df02e5" dependencies = [ "bytemuck", "font-types", @@ -10842,7 +10842,7 @@ checksum = "1165225c21bff1f3bbce98f5a1f889949bc902d3575308cc7b0de30b4f6d27c7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -10859,9 +10859,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.12.2" +version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" dependencies = [ "aho-corasick", "memchr", @@ -10871,9 +10871,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" dependencies = [ "aho-corasick", "memchr", @@ -10978,7 +10978,7 @@ dependencies = [ [[package]] name = "rerun" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "ahash", "anyhow", @@ -10989,7 +10989,7 @@ dependencies = [ "crossbeam", "document-features", "env_filter", - "indexmap 2.12.0", + "indexmap 2.13.0", "indicatif", "itertools 0.14.0", "jiff", @@ -11039,7 +11039,7 @@ dependencies = [ [[package]] name = "rerun-cli" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "document-features", "mimalloc", @@ -11054,7 +11054,7 @@ dependencies = [ [[package]] name = "rerun-loader-rust-file" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "anyhow", "argh", @@ -11063,7 +11063,7 @@ dependencies = [ [[package]] name = "rerun_c" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "ahash", "arrow", @@ -11080,7 +11080,7 @@ dependencies = [ [[package]] name = "rerun_py" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "arrow", "bytes", @@ -11285,13 +11285,13 @@ dependencies = [ "regex", "relative-path", "rustc_version", - "syn 2.0.114", + "syn 2.0.117", "unicode-ident", ] [[package]] name = "run_wasm" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "cargo-run-wasm", "pico-args", @@ -11644,7 +11644,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -11679,7 +11679,7 @@ checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -11693,9 +11693,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e24345aa0fe688594e73770a5f6d1b216508b4f93484c0026d521acd30134392" +checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" dependencies = [ "serde_core", ] @@ -11756,7 +11756,7 @@ dependencies = [ [[package]] name = "shared_recording" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "rerun", ] @@ -11849,9 +11849,9 @@ dependencies = [ [[package]] name = "skrifa" -version = "0.37.0" +version = "0.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c31071dedf532758ecf3fed987cdb4bd9509f900e026ab684b4ecb81ea49841" +checksum = "7fbdfe3d2475fbd7ddd1f3e5cf8288a30eb3e5f95832829570cd88115a7434ac" dependencies = [ "bytemuck", "read-fonts", @@ -11945,7 +11945,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -11956,7 +11956,7 @@ checksum = "1b6b67fb9a61334225b5b790716f609cd58395f895b3fe8b328786812a40bc3b" [[package]] name = "snippets" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "crossbeam", "itertools 0.14.0", @@ -12002,7 +12002,7 @@ dependencies = [ [[package]] name = "spawn_viewer" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "rerun", ] @@ -12034,7 +12034,7 @@ checksum = "da5fc6819faabb412da764b99d3b713bb55083c11e7e0c00144d386cd6a1939c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -12074,7 +12074,7 @@ checksum = "8207e78455ffdf55661170876f88daf85356e4edd54e0a3dbc79586ca1e50cbe" [[package]] name = "stdio" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "rerun", ] @@ -12129,7 +12129,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -12167,9 +12167,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.114" +version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", @@ -12193,7 +12193,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -12389,7 +12389,7 @@ dependencies = [ [[package]] name = "template" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "rerun", ] @@ -12405,7 +12405,7 @@ dependencies = [ [[package]] name = "test_data_density_graph" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "anyhow", "rand 0.9.2", @@ -12415,7 +12415,7 @@ dependencies = [ [[package]] name = "test_image_memory" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "mimalloc", "re_format", @@ -12425,7 +12425,7 @@ dependencies = [ [[package]] name = "test_out_of_order_transforms" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "anyhow", "clap", @@ -12436,7 +12436,7 @@ dependencies = [ [[package]] name = "test_ui_wakeup" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "anyhow", "clap", @@ -12470,7 +12470,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -12481,7 +12481,7 @@ checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -12676,7 +12676,7 @@ checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -12720,7 +12720,7 @@ version = "0.8.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" dependencies = [ - "indexmap 2.12.0", + "indexmap 2.13.0", "serde", "serde_spanned 0.6.9", "toml_datetime 0.6.11", @@ -12733,15 +12733,28 @@ version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8" dependencies = [ - "indexmap 2.12.0", + "indexmap 2.13.0", "serde_core", - "serde_spanned 1.0.3", + "serde_spanned 1.0.4", "toml_datetime 0.7.3", "toml_parser", "toml_writer", "winnow", ] +[[package]] +name = "toml" +version = "1.0.3+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7614eaf19ad818347db24addfa201729cf2a9b6fdfd9eb0ab870fcacc606c0c" +dependencies = [ + "serde_core", + "serde_spanned 1.0.4", + "toml_datetime 1.0.0+spec-1.1.0", + "toml_parser", + "winnow", +] + [[package]] name = "toml_datetime" version = "0.6.11" @@ -12760,13 +12773,22 @@ dependencies = [ "serde_core", ] +[[package]] +name = "toml_datetime" +version = "1.0.0+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32c2555c699578a4f59f0cc68e5116c8d7cabbd45e1409b989d4be085b53f13e" +dependencies = [ + "serde_core", +] + [[package]] name = "toml_edit" version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ - "indexmap 2.12.0", + "indexmap 2.13.0", "serde", "serde_spanned 0.6.9", "toml_datetime 0.6.11", @@ -12780,7 +12802,7 @@ version = "0.23.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3effe7c0e86fdff4f69cdd2ccc1b96f933e24811c5441d44904e8683e27184b" dependencies = [ - "indexmap 2.12.0", + "indexmap 2.13.0", "toml_datetime 0.7.3", "toml_parser", "winnow", @@ -12788,9 +12810,9 @@ dependencies = [ [[package]] name = "toml_parser" -version = "1.0.4" +version = "1.0.9+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" +checksum = "702d4415e08923e7e1ef96cd5727c0dfed80b4d2fa25db9647fe5eb6f7c5a4c4" dependencies = [ "winnow", ] @@ -12848,7 +12870,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -12873,7 +12895,7 @@ dependencies = [ "prost-build", "prost-types", "quote", - "syn 2.0.114", + "syn 2.0.117", "tempfile", "tonic-build", ] @@ -12929,7 +12951,7 @@ checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" dependencies = [ "futures-core", "futures-util", - "indexmap 2.12.0", + "indexmap 2.13.0", "pin-project-lite", "slab", "sync_wrapper", @@ -12991,7 +13013,7 @@ checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -13094,7 +13116,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c5f7c95348f20c1c913d72157b3c6dee6ea3e30b3d19502c5a7f6d3f160dacbf" dependencies = [ "cc", - "windows-targets 0.48.5", + "windows-targets 0.52.6", ] [[package]] @@ -13345,12 +13367,13 @@ dependencies = [ [[package]] name = "vello_common" -version = "0.0.4" +version = "0.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a235ba928b3109ad9e7696270edb09445a52ae1c7c08e6d31a19b1cdd6cbc24a" +checksum = "1bd1a4c633ce09e7d713df1a6e036644a125e15e0c169cfb5180ddf5836ca04b" dependencies = [ "bytemuck", "fearless_simd", + "hashbrown 0.16.1", "log", "peniko", "skrifa", @@ -13359,11 +13382,12 @@ dependencies = [ [[package]] name = "vello_cpu" -version = "0.0.4" +version = "0.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0bd1fcf9c1814f17a491e07113623d44e3ec1125a9f3401f5e047d6d326da21" +checksum = "0162bfe48aabf6a9fdcd401b628c7d9f260c2cbabb343c70a65feba6f7849edc" dependencies = [ "bytemuck", + "hashbrown 0.16.1", "vello_common", ] @@ -13375,7 +13399,7 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "viewer_callbacks" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "mimalloc", "rerun", @@ -13439,7 +13463,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -13497,7 +13521,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", "wasm-bindgen-shared", ] @@ -13565,7 +13589,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -13657,7 +13681,7 @@ dependencies = [ "ahash", "bitflags 2.9.4", "hashbrown 0.14.5", - "indexmap 2.12.0", + "indexmap 2.13.0", "semver", "serde", ] @@ -13842,7 +13866,7 @@ dependencies = [ "cfg-if", "cfg_aliases", "document-features", - "hashbrown 0.16.0", + "hashbrown 0.16.1", "js-sys", "log", "naga", @@ -13873,8 +13897,8 @@ dependencies = [ "bytemuck", "cfg_aliases", "document-features", - "hashbrown 0.16.0", - "indexmap 2.12.0", + "hashbrown 0.16.1", + "indexmap 2.13.0", "log", "naga", "once_cell", @@ -13950,7 +13974,7 @@ dependencies = [ "gpu-alloc", "gpu-allocator", "gpu-descriptor", - "hashbrown 0.16.0", + "hashbrown 0.16.1", "js-sys", "khronos-egl", "libc", @@ -14100,6 +14124,19 @@ dependencies = [ "windows-strings 0.4.2", ] +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement 0.60.2", + "windows-interface 0.59.3", + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", +] + [[package]] name = "windows-future" version = "0.2.1" @@ -14119,7 +14156,7 @@ checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -14130,7 +14167,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -14141,7 +14178,7 @@ checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -14152,7 +14189,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -14195,6 +14232,15 @@ dependencies = [ "windows-link 0.1.3", ] +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link 0.2.1", +] + [[package]] name = "windows-strings" version = "0.1.0" @@ -14214,6 +14260,15 @@ dependencies = [ "windows-link 0.1.3", ] +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link 0.2.1", +] + [[package]] name = "windows-sys" version = "0.45.0" @@ -14727,7 +14782,7 @@ checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", "synstructure", ] @@ -14782,7 +14837,7 @@ checksum = "dc6821851fa840b708b4cbbaf6241868cabc85a2dc22f426361b0292bfc0b836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", "zbus-lockstep", "zbus_xml", "zvariant", @@ -14797,7 +14852,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", "zbus_names", "zvariant", "zvariant_utils", @@ -14855,7 +14910,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -14866,7 +14921,7 @@ checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -14886,7 +14941,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", "synstructure", ] @@ -14926,7 +14981,7 @@ checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -14952,7 +15007,7 @@ dependencies = [ "crossbeam-utils", "displaydoc", "flate2", - "indexmap 2.12.0", + "indexmap 2.13.0", "memchr", "thiserror 2.0.17", "zopfli", @@ -15042,7 +15097,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", "zvariant_utils", ] @@ -15055,6 +15110,6 @@ dependencies = [ "proc-macro2", "quote", "serde", - "syn 2.0.114", + "syn 2.0.117", "winnow", ] diff --git a/Cargo.toml b/Cargo.toml index dfe90c0f3529..7991e82a64df 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,7 +31,7 @@ include = [ license = "MIT OR Apache-2.0" repository = "https://github.com/rerun-io/rerun" rust-version = "1.92" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" [workspace.metadata.cargo-shear] ignored = [ @@ -57,104 +57,104 @@ ignored = [ # re_log_types 0.3.0-alpha.0, NOT 0.3.0-alpha.4 even though it is newer and semver-compatible. # crates/build: -re_build_info = { path = "crates/build/re_build_info", version = "=0.30.0-alpha.1", default-features = false } -re_build_tools = { path = "crates/build/re_build_tools", version = "=0.30.0-alpha.1", default-features = false } -re_dev_tools = { path = "crates/build/re_dev_tools", version = "=0.30.0-alpha.1", default-features = false } -re_protos_builder = { path = "crates/build/re_protos_builder", version = "=0.30.0-alpha.1", default-features = false } -re_types_builder = { path = "crates/build/re_types_builder", version = "=0.30.0-alpha.1", default-features = false } +re_build_info = { path = "crates/build/re_build_info", version = "=0.31.0-alpha.1", default-features = false } +re_build_tools = { path = "crates/build/re_build_tools", version = "=0.31.0-alpha.1", default-features = false } +re_dev_tools = { path = "crates/build/re_dev_tools", version = "=0.31.0-alpha.1", default-features = false } +re_protos_builder = { path = "crates/build/re_protos_builder", version = "=0.31.0-alpha.1", default-features = false } +re_types_builder = { path = "crates/build/re_types_builder", version = "=0.31.0-alpha.1", default-features = false } # crates/store: -re_arrow_combinators = { path = "crates/store/re_arrow_combinators", version = "=0.30.0-alpha.1", default-features = false } -re_chunk = { path = "crates/store/re_chunk", version = "=0.30.0-alpha.1", default-features = false } -re_chunk_store = { path = "crates/store/re_chunk_store", version = "=0.30.0-alpha.1", default-features = false } -re_data_loader = { path = "crates/store/re_data_loader", version = "=0.30.0-alpha.1", default-features = false } -re_data_source = { path = "crates/store/re_data_source", version = "=0.30.0-alpha.1", default-features = false } -re_dataframe = { path = "crates/store/re_dataframe", version = "=0.30.0-alpha.1", default-features = false } -re_datafusion = { path = "crates/store/re_datafusion", version = "=0.30.0-alpha.1", default-features = false } -re_entity_db = { path = "crates/store/re_entity_db", version = "=0.30.0-alpha.1", default-features = false } -re_grpc_client = { path = "crates/store/re_grpc_client", version = "=0.30.0-alpha.1", default-features = false } -re_grpc_server = { path = "crates/store/re_grpc_server", version = "=0.30.0-alpha.1", default-features = false } -re_lenses = { path = "crates/store/re_lenses", version = "=0.30.0-alpha.1", default-features = false } -re_log_channel = { path = "crates/store/re_log_channel", version = "=0.30.0-alpha.1", default-features = false } -re_log_encoding = { path = "crates/store/re_log_encoding", version = "=0.30.0-alpha.1", default-features = false } -re_log_types = { path = "crates/store/re_log_types", version = "=0.30.0-alpha.1", default-features = false } -re_mcap = { path = "crates/store/re_mcap", version = "=0.30.0-alpha.1", default-features = false } -re_protos = { path = "crates/store/re_protos", version = "=0.30.0-alpha.1", default-features = false } -re_query = { path = "crates/store/re_query", version = "=0.30.0-alpha.1", default-features = false } -re_redap_client = { path = "crates/store/re_redap_client", version = "=0.30.0-alpha.1", default-features = false } -re_redap_tests = { path = "crates/store/re_redap_tests", version = "=0.30.0-alpha.1", default-features = false } -re_sdk_types = { path = "crates/store/re_sdk_types", version = "=0.30.0-alpha.1", default-features = false } -re_server = { path = "crates/store/re_server", version = "=0.30.0-alpha.1", default-features = false } -re_sorbet = { path = "crates/store/re_sorbet", version = "=0.30.0-alpha.1", default-features = false } -re_tf = { path = "crates/store/re_tf", version = "=0.30.0-alpha.1", default-features = false } -re_types_core = { path = "crates/store/re_types_core", version = "=0.30.0-alpha.1", default-features = false } -re_uri = { path = "crates/store/re_uri", version = "=0.30.0-alpha.1", default-features = false } +re_arrow_combinators = { path = "crates/store/re_arrow_combinators", version = "=0.31.0-alpha.1", default-features = false } +re_chunk = { path = "crates/store/re_chunk", version = "=0.31.0-alpha.1", default-features = false } +re_chunk_store = { path = "crates/store/re_chunk_store", version = "=0.31.0-alpha.1", default-features = false } +re_data_loader = { path = "crates/store/re_data_loader", version = "=0.31.0-alpha.1", default-features = false } +re_data_source = { path = "crates/store/re_data_source", version = "=0.31.0-alpha.1", default-features = false } +re_dataframe = { path = "crates/store/re_dataframe", version = "=0.31.0-alpha.1", default-features = false } +re_datafusion = { path = "crates/store/re_datafusion", version = "=0.31.0-alpha.1", default-features = false } +re_entity_db = { path = "crates/store/re_entity_db", version = "=0.31.0-alpha.1", default-features = false } +re_grpc_client = { path = "crates/store/re_grpc_client", version = "=0.31.0-alpha.1", default-features = false } +re_grpc_server = { path = "crates/store/re_grpc_server", version = "=0.31.0-alpha.1", default-features = false } +re_lenses = { path = "crates/store/re_lenses", version = "=0.31.0-alpha.1", default-features = false } +re_log_channel = { path = "crates/store/re_log_channel", version = "=0.31.0-alpha.1", default-features = false } +re_log_encoding = { path = "crates/store/re_log_encoding", version = "=0.31.0-alpha.1", default-features = false } +re_log_types = { path = "crates/store/re_log_types", version = "=0.31.0-alpha.1", default-features = false } +re_mcap = { path = "crates/store/re_mcap", version = "=0.31.0-alpha.1", default-features = false } +re_protos = { path = "crates/store/re_protos", version = "=0.31.0-alpha.1", default-features = false } +re_query = { path = "crates/store/re_query", version = "=0.31.0-alpha.1", default-features = false } +re_redap_client = { path = "crates/store/re_redap_client", version = "=0.31.0-alpha.1", default-features = false } +re_redap_tests = { path = "crates/store/re_redap_tests", version = "=0.31.0-alpha.1", default-features = false } +re_sdk_types = { path = "crates/store/re_sdk_types", version = "=0.31.0-alpha.1", default-features = false } +re_server = { path = "crates/store/re_server", version = "=0.31.0-alpha.1", default-features = false } +re_sorbet = { path = "crates/store/re_sorbet", version = "=0.31.0-alpha.1", default-features = false } +re_tf = { path = "crates/store/re_tf", version = "=0.31.0-alpha.1", default-features = false } +re_types_core = { path = "crates/store/re_types_core", version = "=0.31.0-alpha.1", default-features = false } +re_uri = { path = "crates/store/re_uri", version = "=0.31.0-alpha.1", default-features = false } # crates/top: -re_sdk = { path = "crates/top/re_sdk", version = "=0.30.0-alpha.1", default-features = false } -rerun = { path = "crates/top/rerun", version = "=0.30.0-alpha.1", default-features = false } -rerun_c = { path = "crates/top/rerun_c", version = "=0.30.0-alpha.1", default-features = false } -rerun-cli = { path = "crates/top/rerun-cli", version = "=0.30.0-alpha.1", default-features = false } +re_sdk = { path = "crates/top/re_sdk", version = "=0.31.0-alpha.1", default-features = false } +rerun = { path = "crates/top/rerun", version = "=0.31.0-alpha.1", default-features = false } +rerun_c = { path = "crates/top/rerun_c", version = "=0.31.0-alpha.1", default-features = false } +rerun-cli = { path = "crates/top/rerun-cli", version = "=0.31.0-alpha.1", default-features = false } # crates/utils: -re_analytics = { path = "crates/utils/re_analytics", version = "=0.30.0-alpha.1", default-features = false } -re_arrow_util = { path = "crates/utils/re_arrow_util", version = "=0.30.0-alpha.1", default-features = false } -re_auth = { path = "crates/utils/re_auth", version = "=0.30.0-alpha.1", default-features = false } -re_backoff = { path = "crates/utils/re_backoff", version = "=0.30.0-alpha.1", default-features = false } -re_byte_size = { path = "crates/utils/re_byte_size", version = "=0.30.0-alpha.1", default-features = false } -re_capabilities = { path = "crates/utils/re_capabilities", version = "=0.30.0-alpha.1", default-features = false } -re_case = { path = "crates/utils/re_case", version = "=0.30.0-alpha.1", default-features = false } -re_crash_handler = { path = "crates/utils/re_crash_handler", version = "=0.30.0-alpha.1", default-features = false } -re_error = { path = "crates/utils/re_error", version = "=0.30.0-alpha.1", default-features = false } -re_format = { path = "crates/utils/re_format", version = "=0.30.0-alpha.1", default-features = false } -re_int_histogram = { path = "crates/utils/re_int_histogram", version = "=0.30.0-alpha.1", default-features = false } -re_log = { path = "crates/utils/re_log", version = "=0.30.0-alpha.1", default-features = false } -re_memory = { path = "crates/utils/re_memory", version = "=0.30.0-alpha.1", default-features = false } -re_mutex = { path = "crates/utils/re_mutex", version = "=0.30.0-alpha.1", default-features = false } -re_perf_telemetry = { path = "crates/utils/re_perf_telemetry", version = "=0.30.0-alpha.1", default-features = false } -re_quota_channel = { path = "crates/utils/re_quota_channel", version = "=0.30.0-alpha.1", default-features = false } -re_ros_msg = { path = "crates/utils/re_ros_msg", version = "=0.30.0-alpha.1", default-features = false } -re_rvl = { path = "crates/utils/re_rvl", version = "=0.30.0-alpha.1", default-features = false } -re_span = { path = "crates/utils/re_span", version = "=0.30.0-alpha.1", default-features = false } -re_string_interner = { path = "crates/utils/re_string_interner", version = "=0.30.0-alpha.1", default-features = false } -re_tracing = { path = "crates/utils/re_tracing", version = "=0.30.0-alpha.1", default-features = false } -re_tuid = { path = "crates/utils/re_tuid", version = "=0.30.0-alpha.1", default-features = false } -re_video = { path = "crates/utils/re_video", version = "=0.30.0-alpha.1", default-features = false } +re_analytics = { path = "crates/utils/re_analytics", version = "=0.31.0-alpha.1", default-features = false } +re_arrow_util = { path = "crates/utils/re_arrow_util", version = "=0.31.0-alpha.1", default-features = false } +re_auth = { path = "crates/utils/re_auth", version = "=0.31.0-alpha.1", default-features = false } +re_backoff = { path = "crates/utils/re_backoff", version = "=0.31.0-alpha.1", default-features = false } +re_byte_size = { path = "crates/utils/re_byte_size", version = "=0.31.0-alpha.1", default-features = false } +re_capabilities = { path = "crates/utils/re_capabilities", version = "=0.31.0-alpha.1", default-features = false } +re_case = { path = "crates/utils/re_case", version = "=0.31.0-alpha.1", default-features = false } +re_crash_handler = { path = "crates/utils/re_crash_handler", version = "=0.31.0-alpha.1", default-features = false } +re_error = { path = "crates/utils/re_error", version = "=0.31.0-alpha.1", default-features = false } +re_format = { path = "crates/utils/re_format", version = "=0.31.0-alpha.1", default-features = false } +re_int_histogram = { path = "crates/utils/re_int_histogram", version = "=0.31.0-alpha.1", default-features = false } +re_log = { path = "crates/utils/re_log", version = "=0.31.0-alpha.1", default-features = false } +re_memory = { path = "crates/utils/re_memory", version = "=0.31.0-alpha.1", default-features = false } +re_mutex = { path = "crates/utils/re_mutex", version = "=0.31.0-alpha.1", default-features = false } +re_perf_telemetry = { path = "crates/utils/re_perf_telemetry", version = "=0.31.0-alpha.1", default-features = false } +re_quota_channel = { path = "crates/utils/re_quota_channel", version = "=0.31.0-alpha.1", default-features = false } +re_ros_msg = { path = "crates/utils/re_ros_msg", version = "=0.31.0-alpha.1", default-features = false } +re_rvl = { path = "crates/utils/re_rvl", version = "=0.31.0-alpha.1", default-features = false } +re_span = { path = "crates/utils/re_span", version = "=0.31.0-alpha.1", default-features = false } +re_string_interner = { path = "crates/utils/re_string_interner", version = "=0.31.0-alpha.1", default-features = false } +re_tracing = { path = "crates/utils/re_tracing", version = "=0.31.0-alpha.1", default-features = false } +re_tuid = { path = "crates/utils/re_tuid", version = "=0.31.0-alpha.1", default-features = false } +re_video = { path = "crates/utils/re_video", version = "=0.31.0-alpha.1", default-features = false } # crates/viewer: -re_arrow_ui = { path = "crates/viewer/re_arrow_ui", version = "=0.30.0-alpha.1", default-features = false } -re_blueprint_tree = { path = "crates/viewer/re_blueprint_tree", version = "=0.30.0-alpha.1", default-features = false } -re_chunk_store_ui = { path = "crates/viewer/re_chunk_store_ui", version = "=0.30.0-alpha.1", default-features = false } -re_component_fallbacks = { path = "crates/viewer/re_component_fallbacks", version = "=0.30.0-alpha.1", default-features = false } -re_component_ui = { path = "crates/viewer/re_component_ui", version = "=0.30.0-alpha.1", default-features = false } -re_context_menu = { path = "crates/viewer/re_context_menu", version = "=0.30.0-alpha.1", default-features = false } -re_data_ui = { path = "crates/viewer/re_data_ui", version = "=0.30.0-alpha.1", default-features = false } -re_dataframe_ui = { path = "crates/viewer/re_dataframe_ui", version = "=0.30.0-alpha.1", default-features = false } -re_memory_view = { path = "crates/viewer/re_memory_view", version = "=0.30.0-alpha.1", default-features = false } -re_recording_panel = { path = "crates/viewer/re_recording_panel", version = "=0.30.0-alpha.1", default-features = false } -re_redap_browser = { path = "crates/viewer/re_redap_browser", version = "=0.30.0-alpha.1", default-features = false } -re_renderer = { path = "crates/viewer/re_renderer", version = "=0.30.0-alpha.1", default-features = false } -re_renderer_examples = { path = "crates/viewer/re_renderer_examples", version = "=0.30.0-alpha.1", default-features = false } -re_selection_panel = { path = "crates/viewer/re_selection_panel", version = "=0.30.0-alpha.1", default-features = false } -re_test_context = { path = "crates/viewer/re_test_context", version = "=0.30.0-alpha.1", default-features = false } -re_test_viewport = { path = "crates/viewer/re_test_viewport", version = "=0.30.0-alpha.1", default-features = false } -re_time_panel = { path = "crates/viewer/re_time_panel", version = "=0.30.0-alpha.1", default-features = false } -re_ui = { path = "crates/viewer/re_ui", version = "=0.30.0-alpha.1", default-features = false } -re_view = { path = "crates/viewer/re_view", version = "=0.30.0-alpha.1", default-features = false } -re_view_bar_chart = { path = "crates/viewer/re_view_bar_chart", version = "=0.30.0-alpha.1", default-features = false } -re_view_dataframe = { path = "crates/viewer/re_view_dataframe", version = "=0.30.0-alpha.1", default-features = false } -re_view_graph = { path = "crates/viewer/re_view_graph", version = "=0.30.0-alpha.1", default-features = false } -re_view_map = { path = "crates/viewer/re_view_map", version = "=0.30.0-alpha.1", default-features = false } -re_view_spatial = { path = "crates/viewer/re_view_spatial", version = "=0.30.0-alpha.1", default-features = false } -re_view_tensor = { path = "crates/viewer/re_view_tensor", version = "=0.30.0-alpha.1", default-features = false } -re_view_text_document = { path = "crates/viewer/re_view_text_document", version = "=0.30.0-alpha.1", default-features = false } -re_view_text_log = { path = "crates/viewer/re_view_text_log", version = "=0.30.0-alpha.1", default-features = false } -re_view_time_series = { path = "crates/viewer/re_view_time_series", version = "=0.30.0-alpha.1", default-features = false } -re_viewer = { path = "crates/viewer/re_viewer", version = "=0.30.0-alpha.1", default-features = false } -re_viewer_context = { path = "crates/viewer/re_viewer_context", version = "=0.30.0-alpha.1", default-features = false } -re_viewport = { path = "crates/viewer/re_viewport", version = "=0.30.0-alpha.1", default-features = false } -re_viewport_blueprint = { path = "crates/viewer/re_viewport_blueprint", version = "=0.30.0-alpha.1", default-features = false } -re_web_viewer_server = { path = "crates/viewer/re_web_viewer_server", version = "=0.30.0-alpha.1", default-features = false } +re_arrow_ui = { path = "crates/viewer/re_arrow_ui", version = "=0.31.0-alpha.1", default-features = false } +re_blueprint_tree = { path = "crates/viewer/re_blueprint_tree", version = "=0.31.0-alpha.1", default-features = false } +re_chunk_store_ui = { path = "crates/viewer/re_chunk_store_ui", version = "=0.31.0-alpha.1", default-features = false } +re_component_fallbacks = { path = "crates/viewer/re_component_fallbacks", version = "=0.31.0-alpha.1", default-features = false } +re_component_ui = { path = "crates/viewer/re_component_ui", version = "=0.31.0-alpha.1", default-features = false } +re_context_menu = { path = "crates/viewer/re_context_menu", version = "=0.31.0-alpha.1", default-features = false } +re_data_ui = { path = "crates/viewer/re_data_ui", version = "=0.31.0-alpha.1", default-features = false } +re_dataframe_ui = { path = "crates/viewer/re_dataframe_ui", version = "=0.31.0-alpha.1", default-features = false } +re_memory_view = { path = "crates/viewer/re_memory_view", version = "=0.31.0-alpha.1", default-features = false } +re_recording_panel = { path = "crates/viewer/re_recording_panel", version = "=0.31.0-alpha.1", default-features = false } +re_redap_browser = { path = "crates/viewer/re_redap_browser", version = "=0.31.0-alpha.1", default-features = false } +re_renderer = { path = "crates/viewer/re_renderer", version = "=0.31.0-alpha.1", default-features = false } +re_renderer_examples = { path = "crates/viewer/re_renderer_examples", version = "=0.31.0-alpha.1", default-features = false } +re_selection_panel = { path = "crates/viewer/re_selection_panel", version = "=0.31.0-alpha.1", default-features = false } +re_test_context = { path = "crates/viewer/re_test_context", version = "=0.31.0-alpha.1", default-features = false } +re_test_viewport = { path = "crates/viewer/re_test_viewport", version = "=0.31.0-alpha.1", default-features = false } +re_time_panel = { path = "crates/viewer/re_time_panel", version = "=0.31.0-alpha.1", default-features = false } +re_ui = { path = "crates/viewer/re_ui", version = "=0.31.0-alpha.1", default-features = false } +re_view = { path = "crates/viewer/re_view", version = "=0.31.0-alpha.1", default-features = false } +re_view_bar_chart = { path = "crates/viewer/re_view_bar_chart", version = "=0.31.0-alpha.1", default-features = false } +re_view_dataframe = { path = "crates/viewer/re_view_dataframe", version = "=0.31.0-alpha.1", default-features = false } +re_view_graph = { path = "crates/viewer/re_view_graph", version = "=0.31.0-alpha.1", default-features = false } +re_view_map = { path = "crates/viewer/re_view_map", version = "=0.31.0-alpha.1", default-features = false } +re_view_spatial = { path = "crates/viewer/re_view_spatial", version = "=0.31.0-alpha.1", default-features = false } +re_view_tensor = { path = "crates/viewer/re_view_tensor", version = "=0.31.0-alpha.1", default-features = false } +re_view_text_document = { path = "crates/viewer/re_view_text_document", version = "=0.31.0-alpha.1", default-features = false } +re_view_text_log = { path = "crates/viewer/re_view_text_log", version = "=0.31.0-alpha.1", default-features = false } +re_view_time_series = { path = "crates/viewer/re_view_time_series", version = "=0.31.0-alpha.1", default-features = false } +re_viewer = { path = "crates/viewer/re_viewer", version = "=0.31.0-alpha.1", default-features = false } +re_viewer_context = { path = "crates/viewer/re_viewer_context", version = "=0.31.0-alpha.1", default-features = false } +re_viewport = { path = "crates/viewer/re_viewport", version = "=0.31.0-alpha.1", default-features = false } +re_viewport_blueprint = { path = "crates/viewer/re_viewport_blueprint", version = "=0.31.0-alpha.1", default-features = false } +re_web_viewer_server = { path = "crates/viewer/re_web_viewer_server", version = "=0.31.0-alpha.1", default-features = false } # Rerun crates in other repos: re_mp4 = "0.4.0" diff --git a/crates/store/re_sdk_types/definitions/rerun/components/interpolation_mode.fbs b/crates/store/re_sdk_types/definitions/rerun/components/interpolation_mode.fbs index 65421d22955e..6f6522cbb23b 100644 --- a/crates/store/re_sdk_types/definitions/rerun/components/interpolation_mode.fbs +++ b/crates/store/re_sdk_types/definitions/rerun/components/interpolation_mode.fbs @@ -1,9 +1,7 @@ namespace rerun.components; /// Specifies how values between data points are interpolated in time series. -enum InterpolationMode: ubyte ( - "attr.docs.unreleased" -) { +enum InterpolationMode: ubyte { /// Invalid value. Won't show up in generated types. Invalid = 0, diff --git a/crates/utils/re_mutex/README.md b/crates/utils/re_mutex/README.md index cd87559e78f7..6e326948e066 100644 --- a/crates/utils/re_mutex/README.md +++ b/crates/utils/re_mutex/README.md @@ -2,8 +2,8 @@ Part of the [`rerun`](https://github.com/rerun-io/rerun) family of crates. -[![Latest version](https://img.shields.io/crates/v/re_mutex.svg)](https://crates.io/crates/re_mutex?speculative-link) -[![Documentation](https://docs.rs/re_mutex/badge.svg)](https://docs.rs/re_mutex?speculative-link) +[![Latest version](https://img.shields.io/crates/v/re_mutex.svg)](https://crates.io/crates/re_mutex) +[![Documentation](https://docs.rs/re_mutex/badge.svg)](https://docs.rs/re_mutex) ![MIT](https://img.shields.io/badge/license-MIT-blue.svg) ![Apache](https://img.shields.io/badge/license-Apache-blue.svg) diff --git a/crates/viewer/re_ui/tests/help_ui_test.rs b/crates/viewer/re_ui/tests/help_ui_test.rs index 04e2ee60da94..1ecf20ebe4b6 100644 --- a/crates/viewer/re_ui/tests/help_ui_test.rs +++ b/crates/viewer/re_ui/tests/help_ui_test.rs @@ -58,6 +58,10 @@ fn test_help() { harness.try_run_realtime().ok(); - snapshot_results.add(harness.try_snapshot(format!("help_{os:?}"))); + // TODO(#12450): Keeps failing randomly on swiftshader. + #[cfg(not(target_os = "macos"))] + { + snapshot_results.add(harness.try_snapshot(format!("help_{os:?}"))); + } } } diff --git a/crates/viewer/re_viewer/src/ui/memory_panel.rs b/crates/viewer/re_viewer/src/ui/memory_panel.rs index 27c04817c815..293c3baafa9b 100644 --- a/crates/viewer/re_viewer/src/ui/memory_panel.rs +++ b/crates/viewer/re_viewer/src/ui/memory_panel.rs @@ -548,7 +548,7 @@ pub fn memory_tree_ui( ui.label("Memory flamegraph visualizing the memory usage tree."); ui.hyperlink_to( "Learn more", - "https://docs.rs/re_byte_size/latest/re_byte_size/trait.MemUsageTreeCapture.html?speculative-link", + "https://docs.rs/re_byte_size/latest/re_byte_size/trait.MemUsageTreeCapture.html", ); #[expect(dead_code)] diff --git a/docs/content/getting-started/data-in/python.md b/docs/content/getting-started/data-in/python.md index 284a408ee40e..b773ffde86cc 100644 --- a/docs/content/getting-started/data-in/python.md +++ b/docs/content/getting-started/data-in/python.md @@ -121,7 +121,7 @@ archetypes altogether. For more information on how the Rerun data model works, refer to our section on [Entities and Components](../../concepts/logging-and-ingestion/entity-component.md). -Our [Python SDK](https://ref.rerun.io/docs/python) integrates with the rest of the Python ecosystem: the points and colors returned by [`build_color_spiral`](https://ref.rerun.io/docs/python/stable/common/demo_utilities/#rerun.utilities.build_color_spiral?speculative-link) in this example are vanilla `numpy` arrays. +Our [Python SDK](https://ref.rerun.io/docs/python) integrates with the rest of the Python ecosystem: the points and colors returned by [`build_color_spiral`](https://ref.rerun.io/docs/python/stable/common/utilities/#rerun.utilities.build_color_spiral) in this example are vanilla `numpy` arrays. Rerun takes care of mapping those arrays to actual Rerun components depending on the context (e.g. we're calling [`rr.Points3D`](https://ref.rerun.io/docs/python/stable/common/archetypes/#rerun.archetypes.Points3D) in this case). ### Entities & hierarchies @@ -165,7 +165,7 @@ rr.log( ) ``` -Once again, although we are getting fancier and fancier with our [`numpy` incantations](https://ref.rerun.io/docs/python/stable/common/demo_utilities/#rerun.utilities.util.bounce_lerp?speculative-link), +Once again, although we are getting fancier and fancier with our [`numpy` incantations](https://ref.rerun.io/docs/python/stable/common/utilities/#rerun.utilities.util.bounce_lerp), there is nothing new here: it's all about building out `numpy` arrays and feeding them to the Rerun API. diff --git a/docs/content/howto/visualization/component-mappings.md b/docs/content/howto/visualization/component-mappings.md index d8296dca8f38..4c05792bbd63 100644 --- a/docs/content/howto/visualization/component-mappings.md +++ b/docs/content/howto/visualization/component-mappings.md @@ -6,7 +6,7 @@ order: 250 By default, each visualizer reads its input components from the - for example, the `Points3D` visualizer reads colors from `Points3D:colors`. **Component mappings** let you override this, redirecting any visualizer input to a different component on the same entity. This makes it possible to store multiple variants of the same data and switch between them per view. -To learn more about how visualizers are set up in general, also have a look at the [concept page on customizing Views](https://landing-4rzjg7apg-rerun.vercel.app/docs/concepts/visualization/customize-views#speculative-link). +To learn more about how visualizers are set up in general, also have a look at the [concept page on customizing Views](https://rerun.io/docs/concepts/visualization/customize-views). This guide uses a point cloud with two color sets as a running example, but the same technique works for any component! @@ -14,8 +14,8 @@ This guide uses a point cloud with two color sets as a running example, but the You can find the full example here: -* 🐍 [Python](https://github.com/rerun-io/rerun/blob/latest/docs/snippets/all/howto/dual_color_point_cloud.py?speculative-link) -* 🦀 [Rust](https://github.com/rerun-io/rerun/blob/latest/docs/snippets/all/howto/dual_color_point_cloud.rs?speculative-link) +* 🐍 [Python](https://github.com/rerun-io/rerun/blob/latest/docs/snippets/all/howto/dual_color_point_cloud.py) +* 🦀 [Rust](https://github.com/rerun-io/rerun/blob/latest/docs/snippets/all/howto/dual_color_point_cloud.rs) diff --git a/docs/content/howto/visualization/plot-any-scalar.md b/docs/content/howto/visualization/plot-any-scalar.md index 033e4638037d..4ff101dad985 100644 --- a/docs/content/howto/visualization/plot-any-scalar.md +++ b/docs/content/howto/visualization/plot-any-scalar.md @@ -19,7 +19,7 @@ The supported data types are: - `Boolean` - Any of the above nested inside of [Arrow structs](https://arrow.apache.org/docs/format/Intro.html#struct). -For background on how visualizers resolve component values, see [Customize views](../../concepts/visualization/customize-views.md?speculative-link). +For background on how visualizers resolve component values, see [Customize views](../../concepts/visualization/customize-views.md). ## Logging custom data diff --git a/docs/content/reference/types/components/interpolation_mode.md b/docs/content/reference/types/components/interpolation_mode.md index b61744dcdb48..587760f973ae 100644 --- a/docs/content/reference/types/components/interpolation_mode.md +++ b/docs/content/reference/types/components/interpolation_mode.md @@ -31,9 +31,9 @@ uint8 ``` ## API reference links - * 🌊 [C++ API docs for `InterpolationMode`](https://ref.rerun.io/docs/cpp/stable/namespacererun_1_1components.html?speculative-link) - * 🐍 [Python API docs for `InterpolationMode`](https://ref.rerun.io/docs/python/stable/common/components?speculative-link#rerun.components.InterpolationMode) - * 🦀 [Rust API docs for `InterpolationMode`](https://docs.rs/rerun/latest/rerun/components/enum.InterpolationMode.html?speculative-link) + * 🌊 [C++ API docs for `InterpolationMode`](https://ref.rerun.io/docs/cpp/stable/namespacererun_1_1components.html) + * 🐍 [Python API docs for `InterpolationMode`](https://ref.rerun.io/docs/python/stable/common/components#rerun.components.InterpolationMode) + * 🦀 [Rust API docs for `InterpolationMode`](https://docs.rs/rerun/latest/rerun/components/enum.InterpolationMode.html) ## Used by diff --git a/examples/rust/animated_urdf/Cargo.toml b/examples/rust/animated_urdf/Cargo.toml index d5c36f919933..ac764dbd1748 100644 --- a/examples/rust/animated_urdf/Cargo.toml +++ b/examples/rust/animated_urdf/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "animated_urdf" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" edition = "2024" rust-version = "1.92" license = "MIT OR Apache-2.0" diff --git a/examples/rust/blueprint/Cargo.toml b/examples/rust/blueprint/Cargo.toml index 958528ca4daa..a5a3278fcc79 100644 --- a/examples/rust/blueprint/Cargo.toml +++ b/examples/rust/blueprint/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "blueprint" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" edition = "2024" rust-version = "1.92" license = "MIT OR Apache-2.0" diff --git a/examples/rust/blueprint_stocks/Cargo.toml b/examples/rust/blueprint_stocks/Cargo.toml index ba32f25506f4..b1b2b62ecfe9 100644 --- a/examples/rust/blueprint_stocks/Cargo.toml +++ b/examples/rust/blueprint_stocks/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "blueprint_stocks" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" edition = "2024" rust-version = "1.92" license = "MIT OR Apache-2.0" diff --git a/examples/rust/clock/Cargo.toml b/examples/rust/clock/Cargo.toml index da796d639e17..1fc639afe870 100644 --- a/examples/rust/clock/Cargo.toml +++ b/examples/rust/clock/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "clock" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" edition = "2024" rust-version = "1.92" license = "MIT OR Apache-2.0" diff --git a/examples/rust/custom_callback/Cargo.toml b/examples/rust/custom_callback/Cargo.toml index 8ec1ec691120..77f3e9a5d9d4 100644 --- a/examples/rust/custom_callback/Cargo.toml +++ b/examples/rust/custom_callback/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "custom_callback" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" edition = "2024" rust-version = "1.92" license = "MIT OR Apache-2.0" diff --git a/examples/rust/custom_data_loader/Cargo.toml b/examples/rust/custom_data_loader/Cargo.toml index dd6b3444d9c6..7f8221f0dc5b 100644 --- a/examples/rust/custom_data_loader/Cargo.toml +++ b/examples/rust/custom_data_loader/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "custom_data_loader" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" edition = "2024" rust-version = "1.92" license = "MIT OR Apache-2.0" diff --git a/examples/rust/custom_store_subscriber/Cargo.toml b/examples/rust/custom_store_subscriber/Cargo.toml index d59bfa356600..16a5b35896f2 100644 --- a/examples/rust/custom_store_subscriber/Cargo.toml +++ b/examples/rust/custom_store_subscriber/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "custom_store_subscriber" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" edition = "2024" rust-version = "1.92" license = "MIT OR Apache-2.0" diff --git a/examples/rust/custom_view/Cargo.toml b/examples/rust/custom_view/Cargo.toml index bee9cf47067f..c1944c39b95b 100644 --- a/examples/rust/custom_view/Cargo.toml +++ b/examples/rust/custom_view/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "custom_view" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" edition = "2024" rust-version = "1.92" license = "MIT OR Apache-2.0" diff --git a/examples/rust/custom_visualizer/Cargo.toml b/examples/rust/custom_visualizer/Cargo.toml index bd13f43548cd..cd951df05088 100644 --- a/examples/rust/custom_visualizer/Cargo.toml +++ b/examples/rust/custom_visualizer/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "custom_visualizer" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" edition = "2024" rust-version = "1.92" license = "MIT OR Apache-2.0" diff --git a/examples/rust/dataframe_query/Cargo.toml b/examples/rust/dataframe_query/Cargo.toml index ae942192a93b..51210574ba6c 100644 --- a/examples/rust/dataframe_query/Cargo.toml +++ b/examples/rust/dataframe_query/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dataframe_query" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" edition = "2024" rust-version = "1.92" license = "MIT OR Apache-2.0" diff --git a/examples/rust/dna/Cargo.toml b/examples/rust/dna/Cargo.toml index a1a735b68b17..8fb424612a82 100644 --- a/examples/rust/dna/Cargo.toml +++ b/examples/rust/dna/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dna" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" edition = "2024" rust-version = "1.92" license = "MIT OR Apache-2.0" diff --git a/examples/rust/extend_viewer_ui/Cargo.toml b/examples/rust/extend_viewer_ui/Cargo.toml index fc738d2e28c2..9ca3a0156440 100644 --- a/examples/rust/extend_viewer_ui/Cargo.toml +++ b/examples/rust/extend_viewer_ui/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "extend_viewer_ui" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" edition = "2024" rust-version = "1.92" license = "MIT OR Apache-2.0" diff --git a/examples/rust/external_data_loader/Cargo.toml b/examples/rust/external_data_loader/Cargo.toml index 19cc087d1d1b..f564d7c39760 100644 --- a/examples/rust/external_data_loader/Cargo.toml +++ b/examples/rust/external_data_loader/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rerun-loader-rust-file" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" edition = "2024" rust-version = "1.92" license = "MIT OR Apache-2.0" diff --git a/examples/rust/graph_lattice/Cargo.toml b/examples/rust/graph_lattice/Cargo.toml index 71d1b5be267a..fcfd1333114a 100644 --- a/examples/rust/graph_lattice/Cargo.toml +++ b/examples/rust/graph_lattice/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "graph_lattice" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" edition = "2024" rust-version = "1.92" license = "MIT OR Apache-2.0" diff --git a/examples/rust/incremental_logging/Cargo.toml b/examples/rust/incremental_logging/Cargo.toml index 900b1f15aa7a..051360ac91c9 100644 --- a/examples/rust/incremental_logging/Cargo.toml +++ b/examples/rust/incremental_logging/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "incremental_logging" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" edition = "2024" rust-version = "1.92" license = "MIT OR Apache-2.0" diff --git a/examples/rust/lenses/Cargo.toml b/examples/rust/lenses/Cargo.toml index 2e87f274f5f9..88b84461f2ee 100644 --- a/examples/rust/lenses/Cargo.toml +++ b/examples/rust/lenses/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lenses" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" edition = "2024" rust-version = "1.92" license = "MIT OR Apache-2.0" diff --git a/examples/rust/log_file/Cargo.toml b/examples/rust/log_file/Cargo.toml index 457492a22f82..22895defde0a 100644 --- a/examples/rust/log_file/Cargo.toml +++ b/examples/rust/log_file/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "log_file" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" edition = "2024" rust-version = "1.92" license = "MIT OR Apache-2.0" diff --git a/examples/rust/minimal/Cargo.toml b/examples/rust/minimal/Cargo.toml index e44fa4a09902..bc5097a140b2 100644 --- a/examples/rust/minimal/Cargo.toml +++ b/examples/rust/minimal/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "minimal" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" edition = "2024" rust-version = "1.92" license = "MIT OR Apache-2.0" diff --git a/examples/rust/minimal_options/Cargo.toml b/examples/rust/minimal_options/Cargo.toml index 67426aad0e3c..5d6d2f7f0ff6 100644 --- a/examples/rust/minimal_options/Cargo.toml +++ b/examples/rust/minimal_options/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "minimal_options" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" edition = "2024" rust-version = "1.92" license = "MIT OR Apache-2.0" diff --git a/examples/rust/minimal_serve/Cargo.toml b/examples/rust/minimal_serve/Cargo.toml index 5b39c3704a99..4f33f2c794f2 100644 --- a/examples/rust/minimal_serve/Cargo.toml +++ b/examples/rust/minimal_serve/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "minimal_serve" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" edition = "2024" rust-version = "1.92" license = "MIT OR Apache-2.0" diff --git a/examples/rust/objectron/Cargo.toml b/examples/rust/objectron/Cargo.toml index 5aa162997653..e90612f74d7b 100644 --- a/examples/rust/objectron/Cargo.toml +++ b/examples/rust/objectron/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "objectron" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" edition = "2024" rust-version = "1.92" license = "MIT OR Apache-2.0" diff --git a/examples/rust/raw_mesh/Cargo.toml b/examples/rust/raw_mesh/Cargo.toml index 179eaeed1ab5..1d519a8b8412 100644 --- a/examples/rust/raw_mesh/Cargo.toml +++ b/examples/rust/raw_mesh/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "raw_mesh" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" edition = "2024" rust-version = "1.92" license = "MIT OR Apache-2.0" diff --git a/examples/rust/shared_recording/Cargo.toml b/examples/rust/shared_recording/Cargo.toml index b3a498ecc570..945aeaa1e05e 100644 --- a/examples/rust/shared_recording/Cargo.toml +++ b/examples/rust/shared_recording/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "shared_recording" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" edition = "2024" rust-version = "1.92" license = "MIT OR Apache-2.0" diff --git a/examples/rust/spawn_viewer/Cargo.toml b/examples/rust/spawn_viewer/Cargo.toml index afe82fbecfa9..479b32987b5f 100644 --- a/examples/rust/spawn_viewer/Cargo.toml +++ b/examples/rust/spawn_viewer/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "spawn_viewer" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" edition = "2024" rust-version = "1.92" license = "MIT OR Apache-2.0" diff --git a/examples/rust/stdio/Cargo.toml b/examples/rust/stdio/Cargo.toml index 949f8773d332..433e0d01813a 100644 --- a/examples/rust/stdio/Cargo.toml +++ b/examples/rust/stdio/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "stdio" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" edition = "2024" rust-version = "1.92" license = "MIT OR Apache-2.0" diff --git a/examples/rust/template/Cargo.toml b/examples/rust/template/Cargo.toml index e054b0b6c58a..77a2b54acf5f 100644 --- a/examples/rust/template/Cargo.toml +++ b/examples/rust/template/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "template" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" edition = "2024" rust-version = "1.92" license = "MIT OR Apache-2.0" diff --git a/examples/rust/viewer_callbacks/Cargo.toml b/examples/rust/viewer_callbacks/Cargo.toml index 5d3bba09f37f..4a4b75dcc7cf 100644 --- a/examples/rust/viewer_callbacks/Cargo.toml +++ b/examples/rust/viewer_callbacks/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "viewer_callbacks" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" edition = "2024" rust-version = "1.92" license = "MIT OR Apache-2.0" diff --git a/rerun_cpp/src/rerun/c/sdk_info.h b/rerun_cpp/src/rerun/c/sdk_info.h index 9ab94770c335..f1747b258e7d 100644 --- a/rerun_cpp/src/rerun/c/sdk_info.h +++ b/rerun_cpp/src/rerun/c/sdk_info.h @@ -2,13 +2,13 @@ /// /// This should match the string returned by `rr_version_string` (C) or `rerun::version_string` (C++). /// If not, the SDK's binary and the C header are out of sync. -#define RERUN_SDK_HEADER_VERSION "0.30.0-alpha.1+dev" +#define RERUN_SDK_HEADER_VERSION "0.31.0-alpha.1+dev" /// Major version of the Rerun C SDK. #define RERUN_SDK_HEADER_VERSION_MAJOR 0 /// Minor version of the Rerun C SDK. -#define RERUN_SDK_HEADER_VERSION_MINOR 30 +#define RERUN_SDK_HEADER_VERSION_MINOR 31 /// Patch version of the Rerun C SDK. #define RERUN_SDK_HEADER_VERSION_PATCH 0 diff --git a/rerun_js/web-viewer-react/package.json b/rerun_js/web-viewer-react/package.json index d4eaecd2b313..335be116ed40 100644 --- a/rerun_js/web-viewer-react/package.json +++ b/rerun_js/web-viewer-react/package.json @@ -1,6 +1,6 @@ { "name": "@rerun-io/web-viewer-react", - "version": "0.30.0-alpha.1+dev", + "version": "0.31.0-alpha.1+dev", "description": "Embed the Rerun web viewer in your React app", "licenses": [ { @@ -43,7 +43,7 @@ "tsconfig.json" ], "dependencies": { - "@rerun-io/web-viewer": "0.30.0-alpha.1" + "@rerun-io/web-viewer": "0.31.0-alpha.1" }, "peerDependencies": { "@types/react": "^18.2.33 || ^19.0.0", diff --git a/rerun_js/web-viewer/package.json b/rerun_js/web-viewer/package.json index a3e813cacc82..e77e09a11645 100644 --- a/rerun_js/web-viewer/package.json +++ b/rerun_js/web-viewer/package.json @@ -1,6 +1,6 @@ { "name": "@rerun-io/web-viewer", - "version": "0.30.0-alpha.1+dev", + "version": "0.31.0-alpha.1+dev", "description": "Embed the Rerun web viewer in your app", "licenses": [ { diff --git a/rerun_notebook/pyproject.toml b/rerun_notebook/pyproject.toml index 71553dc095a8..6f047d047e75 100644 --- a/rerun_notebook/pyproject.toml +++ b/rerun_notebook/pyproject.toml @@ -5,7 +5,7 @@ build-backend = "hatchling.build" [project] name = "rerun-notebook" description = "Implementation helper for running rerun-sdk in notebooks" -version = "0.30.0-alpha.1+dev" +version = "0.31.0-alpha.1+dev" dependencies = [ "anywidget", "jupyter-ui-poll", diff --git a/rerun_py/pyproject.toml b/rerun_py/pyproject.toml index b15a6f502720..22780599cde5 100644 --- a/rerun_py/pyproject.toml +++ b/rerun_py/pyproject.toml @@ -47,7 +47,7 @@ tests = [ "torch>=2.5", # Needs numpy 2 support "datafusion==51.0.0", ] -notebook = ["rerun-notebook==0.30.0-alpha.1+dev"] +notebook = ["rerun-notebook==0.31.0-alpha.1+dev"] # TODO(RR-3786): when pyarrow is fixed, we should remove the pandas dependency datafusion = ["datafusion==51.0.0", "pandas>=2"] # deprecated, replaced by `dataplatform` (NOLINT) dataplatform = [ # NOLINT diff --git a/rerun_py/rerun_sdk/rerun/__init__.py b/rerun_py/rerun_sdk/rerun/__init__.py index 0b0ef334e9e1..9b88d2b22fac 100644 --- a/rerun_py/rerun_sdk/rerun/__init__.py +++ b/rerun_py/rerun_sdk/rerun/__init__.py @@ -9,8 +9,8 @@ import numpy as np -__version__ = "0.30.0-alpha.1+dev" -__version_info__ = (0, 30, 0, "alpha.1") +__version__ = "0.31.0-alpha.1+dev" +__version_info__ = (0, 31, 0, "alpha.1") if sys.version_info < (3, 10): # noqa: UP036 raise RuntimeError("Rerun SDK requires Python 3.10 or later.") diff --git a/uv.lock b/uv.lock index 7a4ce028cc51..3dd49a0c159f 100644 --- a/uv.lock +++ b/uv.lock @@ -4631,7 +4631,7 @@ requires-dist = [{ name = "sitecustomize-entrypoints" }] [[package]] name = "rerun-notebook" -version = "0.30.0a1+dev" +version = "0.31.0a1+dev" source = { editable = "rerun_notebook" } dependencies = [ { name = "anywidget" }, From 00d1166f411d58fd43c5f558e5ccc65282c1b530 Mon Sep 17 00:00:00 2001 From: Michael Grupp Date: Tue, 3 Mar 2026 12:22:38 +0100 Subject: [PATCH 019/513] Add script for easy fetching of OSS patch candidates Displays a sorted table with all info a release captain needs to cherry-pick stuff. Also: - handles sync commits - warns about unmerged `consider-patch` PRs - adds a warning column for patches that were merged before a minor / patch release and are potentially outdated Bildschirmfoto 2026-02-12 um 13 07
50 Source-Ref: c073f39cca4935ffc5f7ce186b41052023ea74e9 --- RELEASES.md | 10 + pyproject.toml | 3 + scripts/fetch_patch_candidates.py | 314 ++++++++++++++++++++++++++++++ uv.lock | 22 +++ 4 files changed, 349 insertions(+) create mode 100755 scripts/fetch_patch_candidates.py diff --git a/RELEASES.md b/RELEASES.md index 3e1e44017e93..85175f676b3c 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -91,6 +91,11 @@ Do this once you have prepared your patch-release branch and it's ready for test ### 3. If this is a patch release, cherry-pick commits for inclusion in the release into the branch In GitHub we have a `consider-patch` label that we put on PRs that we might want to include in the release. +The fastest way to get an overview of all the patch candidate PRs from both repositories and their corresponding commit hashes is to run this script: + +``` +uv run scripts/fetch_patch_candidates.py +``` When done, run [`cargo semver-checks`](https://github.com/obi1kenobi/cargo-semver-checks) to check that we haven't introduced any semver breaking changes. @@ -176,3 +181,8 @@ Make sure the `consider-patch` label on GitHub is up-to-date. For a full release Summarize your experience with the release process to our [Release Postmortems](https://www.notion.so/rerunio/Release-Postmortems-271b24554b1980589770df810d2e4ed5) Notion page. Create tickets if you think we can improve the process, put them into the `Actionable items` section. + +### 10. Clean up PR labels + +`uv run scripts/fetch_patch_candidates.py` will show a warning for `consider-patch`-labeled PRs that have been merged before a release. +Make sure to remove the label from PRs that are already part of a release. diff --git a/pyproject.toml b/pyproject.toml index 06dc3ef1b9e7..9e6a4a7d2938 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -74,6 +74,9 @@ dev = [ "datafusion>=50.0", "jupyter>=1.0", "types-protobuf", # Needed by mcap-protobuf-support + # Needed for pretty-printing tables in `fetch_patch_candidates.py` + "tabulate>=0.9.0", + "types-tabulate>=0.9.0", ] snippets = ["rerun-sdk", "av", "pandas", "mcap-protobuf-support"] examples = [ diff --git a/scripts/fetch_patch_candidates.py b/scripts/fetch_patch_candidates.py new file mode 100755 index 000000000000..700056a9d00a --- /dev/null +++ b/scripts/fetch_patch_candidates.py @@ -0,0 +1,314 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import argparse +import json +import re +import subprocess +import sys +from dataclasses import dataclass +from typing import Any + +from colorama import Fore, Style +import pandas as pd +from tabulate import tabulate +from tqdm import tqdm + +DOC = """ +Fetch potential patch release candidates from both rerun-io/rerun and rerun-io/reality. + +Looks at merged PRs labeled: +- "consider-patch" in rerun-io/rerun +- "consider-oss-patch" in rerun-io/reality + +Requirements: +- gh CLI (https://cli.github.com/) authenticated with GitHub +- 3rd-party packages are part of the UV workspace +""" + +MAX_COLUMN_WIDTH = 50 + +OWNER = "rerun-io" +RERUN_LABEL = "consider-patch" +REALITY_LABEL = "consider-oss-patch" + + +@dataclass +class Release: + tag: str + published_at: str + + +@dataclass +class PullRequest: + repo: str + number: int + title: str + url: str + merged_at: str + merge_commit_sha: str | None + author: str + + +@dataclass +class PatchCandidate: + prs: list[PullRequest] + merged: bool + rerun_sha: str | None + reality_sha: str | None + warning: str | None = None + + +def eprint(*args: object, **kwargs: Any) -> None: + print(*args, file=sys.stderr, **kwargs) + + +def strip_pr_number(title: str) -> str: + """Strip trailing PR number from title: 'some commit (#123)' -> 'some commit'.""" + return re.sub(r"\s*\(#\d+\)\s*$", "", title).strip() + + +def fetch_prs(repo: str, label: str, state: str) -> list[PullRequest]: + """Fetch PRs with the given label and state from a GitHub repo.""" + try: + result = subprocess.run( + [ + "gh", + "pr", + "list", + "--repo", + f"{OWNER}/{repo}", + "--label", + label, + "--state", + state, + "--json", + "number,title,url,mergeCommit,mergedAt,author", + "--limit", + "100", + ], + capture_output=True, + text=True, + check=True, + ) + raw: list[dict[str, Any]] = json.loads(result.stdout) + prs = [] + for pr in raw: + author_info = pr.get("author") + if author_info and author_info.get("name"): + author = author_info["name"] + elif author_info and author_info.get("login"): + author = author_info["login"] + else: + author = "failed to get author" + + prs.append( + PullRequest( + repo=repo, + number=pr["number"], + title=pr["title"], + url=pr["url"], + merged_at=pr.get("mergedAt", ""), + merge_commit_sha=(pr.get("mergeCommit") or {}).get("oid"), + author=author, + ) + ) + prs.sort(key=lambda pr: pr.merged_at) + return prs + except subprocess.CalledProcessError as e: + eprint(f"ERROR fetching PRs from {OWNER}/{repo}: {e.stderr.strip()}") + eprint("Make sure gh CLI is installed and authenticated: https://cli.github.com/") + sys.exit(1) + + +def fetch_rerun_releases() -> list[Release]: + """Fetch recent releases from rerun-io/rerun, sorted by date ascending.""" + try: + result = subprocess.run( + [ + "gh", + "release", + "list", + "--repo", + f"{OWNER}/rerun", + "--json", + "tagName,publishedAt", + "--limit", + "50", + ], + capture_output=True, + text=True, + check=True, + ) + raw = json.loads(result.stdout) + releases = [Release(tag=r["tagName"], published_at=r.get("publishedAt", "")) for r in raw] + releases.sort(key=lambda r: r.published_at) + return releases + except subprocess.CalledProcessError as e: + eprint(f"Warning: could not fetch releases: {e.stderr.strip()}") + return [] + + +def find_release_after(merged_at: str, releases: list[Release]) -> Release | None: + """Find the earliest rerun release published after the given merge date.""" + for release in releases: + if "prerelease" in release.tag: + continue + if release.published_at > merged_at: + return release + return None + + +def maybe_warn(merged_at: str, releases: list[Release]) -> str | None: + """Creates a warning if a release was published after merge, which may indicate an outdated label.""" + release = find_release_after(merged_at, releases) if merged_at else None + return f"{release.tag} released after merge! Outdated label?" if release else None + + +def find_commit_via_github(repo: str, message: str) -> str | None: + """Search for a commit by message using GitHub's commit search API.""" + escaped = message.replace('"', '\\"') + query = f'"{escaped}" repo:{OWNER}/{repo}' + try: + result = subprocess.run( + [ + "gh", + "api", + "--method", + "GET", + "search/commits", + "-f", + f"q={query}", + "--jq", + ".items[0].sha", + ], + capture_output=True, + text=True, + check=True, + ) + sha = result.stdout.strip() + return sha if sha and sha != "null" else None + except subprocess.CalledProcessError: + return None + + +def main() -> None: + parser = argparse.ArgumentParser( + description=DOC, + formatter_class=argparse.RawDescriptionHelpFormatter, + ) + parser.parse_args() + + with tqdm(total=5, desc="Fetching data", file=sys.stderr, leave=False) as pbar: + pbar.set_description("Fetching rerun releases") + releases = fetch_rerun_releases() + pbar.update(1) + + pbar.set_description(f"Fetching merged '{RERUN_LABEL}' PRs from rerun") + rerun_prs = fetch_prs("rerun", RERUN_LABEL, "merged") + pbar.update(1) + + pbar.set_description(f"Fetching merged '{REALITY_LABEL}' PRs from reality") + reality_prs = fetch_prs("reality", REALITY_LABEL, "merged") + pbar.update(1) + + pbar.set_description(f"Fetching open '{RERUN_LABEL}' PRs from rerun") + rerun_open = fetch_prs("rerun", RERUN_LABEL, "open") + pbar.update(1) + + pbar.set_description(f"Fetching open '{REALITY_LABEL}' PRs from reality") + reality_open = fetch_prs("reality", REALITY_LABEL, "open") + pbar.update(1) + + all_results: list[PatchCandidate] = [] + + # Open (unmerged) PRs — no commit resolution needed. + for pr in rerun_open + reality_open: + all_results.append(PatchCandidate(prs=[pr], merged=False, rerun_sha=None, reality_sha=None)) + + # Merged PRs — resolve commits across repos. + merged_prs = rerun_prs + reality_prs + progress = tqdm(merged_prs, desc="Resolving commits", file=sys.stderr, leave=False) + for pr in progress: + progress.set_description(pr.url) + search_msg = strip_pr_number(pr.title) + + if pr.repo == "rerun": + rerun_sha = pr.merge_commit_sha + reality_sha = find_commit_via_github("reality", search_msg) + else: + rerun_sha = find_commit_via_github("rerun", search_msg) + reality_sha = pr.merge_commit_sha + + all_results.append( + PatchCandidate( + prs=[pr], + merged=True, + rerun_sha=rerun_sha, + reality_sha=reality_sha, + warning=maybe_warn(pr.merged_at, releases), + ) + ) + + # Deduplicate entries that appear in both repos (repo sync creates matching commits). + deduped: dict[str, PatchCandidate] = {} + for c in all_results: + key = strip_pr_number(c.prs[0].title) + if key in deduped: + existing = deduped[key] + existing.rerun_sha = existing.rerun_sha or c.rerun_sha + existing.reality_sha = existing.reality_sha or c.reality_sha + existing.warning = existing.warning or c.warning + existing.prs.extend(c.prs) + else: + deduped[key] = c + all_results = list(deduped.values()) + + # Ensure that the PRs from both repos are sorted by time. + # Note: this here works because we have ISO timestamps, which sort lexicographically. + all_results.sort(key=lambda c: c.prs[0].merged_at or "") + + # Warn about unmerged PRs that still carry the patch label. + for c in all_results: + if not c.merged: + eprint( + f"{Fore.YELLOW}WARNING: {c.prs[0].url} has patch label but isn't merged yet! ({c.prs[0].author}){Style.RESET_ALL}" + ) + + merged_results = [c for c in all_results if c.merged] + if not merged_results: + eprint("No merged patch candidates found in either repository.") + return + + def short_sha(sha: str | None) -> str: + return sha[:8] if sha else "—" + + def merge_date(candidate: PatchCandidate) -> str: + return candidate.prs[0].merged_at[:10] if candidate.prs[0].merged_at else "—" + + columns: dict[str, list[str]] = { + "Merge date": [merge_date(c) for c in merged_results], + "rerun": [short_sha(c.rerun_sha) for c in merged_results], + "reality": [short_sha(c.reality_sha) for c in merged_results], + "Origin PR": ["\n".join(p.url for p in c.prs) for c in merged_results], + "Commit message": [c.prs[0].title for c in merged_results], + "Author": [c.prs[0].author for c in merged_results], + } + has_warnings = any(c.warning for c in merged_results) + if has_warnings: + columns[f"{Fore.YELLOW}WARNING{Style.RESET_ALL}"] = [c.warning or "" for c in merged_results] + + df = pd.DataFrame(columns) + print( + tabulate( + df.values.tolist(), + headers=list(df.columns), + tablefmt="rounded_grid", + stralign="left", + maxcolwidths=MAX_COLUMN_WIDTH, + ) + ) + + +if __name__ == "__main__": + main() diff --git a/uv.lock b/uv.lock index 3dd49a0c159f..811f1241ac77 100644 --- a/uv.lock +++ b/uv.lock @@ -4764,6 +4764,7 @@ dev = [ { name = "ruff" }, { name = "semver" }, { name = "syrupy" }, + { name = "tabulate" }, { name = "tomlkit" }, { name = "tqdm" }, { name = "ty" }, @@ -4771,6 +4772,7 @@ dev = [ { name = "types-protobuf" }, { name = "types-pyyaml" }, { name = "types-requests" }, + { name = "types-tabulate" }, { name = "types-tqdm" }, { name = "wheel" }, ] @@ -4878,6 +4880,7 @@ dev = [ { name = "ruff", specifier = "==0.12.10" }, { name = "semver", specifier = ">=3.0,<3.1" }, { name = "syrupy", specifier = ">=4.0" }, + { name = "tabulate", specifier = ">=0.9.0" }, { name = "tomlkit", specifier = ">=0.12" }, { name = "tqdm", specifier = ">=4.60" }, { name = "ty", specifier = "==0.0.11" }, @@ -4885,6 +4888,7 @@ dev = [ { name = "types-protobuf" }, { name = "types-pyyaml", specifier = ">=6.0" }, { name = "types-requests", specifier = "==2.32.4.20250913" }, + { name = "types-tabulate", specifier = ">=0.9.0" }, { name = "types-tqdm", specifier = "==4.67.0.20250809" }, { name = "wheel", specifier = ">=0.46" }, ] @@ -5553,6 +5557,15 @@ requires-dist = [ { name = "rerun-sdk", editable = "rerun_py" }, ] +[[package]] +name = "tabulate" +version = "0.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ec/fe/802052aecb21e3797b8f7902564ab6ea0d60ff8ca23952079064155d1ae1/tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c", size = 81090, upload-time = "2022-10-06T17:21:48.54Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/44/4a5f08c96eb108af5cb50b41f76142f0afa346dfa99d5296fe7202a11854/tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f", size = 35252, upload-time = "2022-10-06T17:21:44.262Z" }, +] + [[package]] name = "template" version = "0.1.0" @@ -5914,6 +5927,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2a/20/9a227ea57c1285986c4cf78400d0a91615d25b24e257fd9e2969606bdfae/types_requests-2.32.4.20250913-py3-none-any.whl", hash = "sha256:78c9c1fffebbe0fa487a418e0fa5252017e9c60d1a2da394077f1780f655d7e1", size = 20658, upload-time = "2025-09-13T02:40:01.115Z" }, ] +[[package]] +name = "types-tabulate" +version = "0.9.0.20241207" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3f/43/16030404a327e4ff8c692f2273854019ed36718667b2993609dc37d14dd4/types_tabulate-0.9.0.20241207.tar.gz", hash = "sha256:ac1ac174750c0a385dfd248edc6279fa328aaf4ea317915ab879a2ec47833230", size = 8195, upload-time = "2024-12-07T02:54:42.554Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5e/86/a9ebfd509cbe74471106dffed320e208c72537f9aeb0a55eaa6b1b5e4d17/types_tabulate-0.9.0.20241207-py3-none-any.whl", hash = "sha256:b8dad1343c2a8ba5861c5441370c3e35908edd234ff036d4298708a1d4cf8a85", size = 8307, upload-time = "2024-12-07T02:54:41.031Z" }, +] + [[package]] name = "types-tqdm" version = "4.67.0.20250809" From 09666960eafc6a8b6c215643d0b28f503dbc1c05 Mon Sep 17 00:00:00 2001 From: Andreas Reich Date: Tue, 3 Mar 2026 13:43:10 +0100 Subject: [PATCH 020/513] Fix rare ui id conflict in list item content Kinda strange that this didn't come up earlier, but it can happen that if there's two identical ui elements in the visualizer ui they failed to render Before: image After: image --------- Source-Ref: 598239087befa745fc1c8a18f151c536966d442a Co-authored-by: lucasmerlin --- crates/viewer/re_ui/src/list_item/property_content.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/viewer/re_ui/src/list_item/property_content.rs b/crates/viewer/re_ui/src/list_item/property_content.rs index cd5184a3b6bf..1eec843b9316 100644 --- a/crates/viewer/re_ui/src/list_item/property_content.rs +++ b/crates/viewer/re_ui/src/list_item/property_content.rs @@ -185,6 +185,7 @@ impl ListItemContent for PropertyContent<'_> { buttons, } = *self; + let content_id_salt = egui::Id::new(label.text()); let tokens = ui.tokens(); // │ │ @@ -303,6 +304,7 @@ impl ListItemContent for PropertyContent<'_> { { let mut child_ui = ui.new_child( egui::UiBuilder::new() + .id_salt(content_id_salt) .max_rect(value_rect) .layout(egui::Layout::left_to_right(egui::Align::Center)), ); From e9edd43c638d97905d94ffc828f1e25b742025ed Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Tue, 3 Mar 2026 14:15:40 +0100 Subject: [PATCH 021/513] Follow `arrow-rs` datatype formatting We've updated to arrow 57, which _finally_ implements [proper `Display` formatting for `DataType`](https://github.com/apache/arrow-rs/pull/8290). arrow-rs formats things slightly differently from how we have, but I think we should just converge on one standard here, in arrow-rs, and [in datafusion](https://github.com/apache/datafusion/pull/20605). ## How to review Only look at changes in `.rs` files by [clicking here](https://github.com/rerun-io/reality/pull/967/changes?file-filters%5B%5D=.rs). Then take a look at a few snapshots Source-Ref: 8587961f0f1089a9a0f22c7e6a1cf907af81d0e6 --- Cargo.lock | 4 +- .../src/codegen/docs/datatype_docs.rs | 72 +-- .../src/codegen/docs/website.rs | 35 +- .../tests/test_explode.rs | 238 +++++----- .../tests/test_selector.rs | 324 +++++++------- .../tests/test_string_transforms.rs | 226 +++++----- .../tests/test_transform.rs | 392 ++++++++-------- .../store/re_arrow_combinators/tests/util.rs | 146 +++--- crates/store/re_chunk/src/chunk.rs | 6 +- .../snapshots/formatting__format_chunk.snap | 36 +- .../formatting__format_chunk_redacted.snap | 38 +- crates/store/re_chunk_store/src/writes.rs | 4 +- .../formatting__format_chunk_store.snap | 36 +- ...ssed_video__foxglove_compressed_video.snap | 67 ++- ..._transforms__foxglove_frame_transform.snap | 16 +- ...transforms__foxglove_frame_transforms.snap | 16 +- ...sts__foxglove__test_log__foxglove_log.snap | 62 +-- ...est_point_cloud__foxglove_point_cloud.snap | 15 +- ...e__test_raw_image__foxglove_raw_image.snap | 18 +- ..._query__tests__async_barebones_static.snap | 26 +- ...uery__tests__async_barebones_temporal.snap | 4 +- ..._dataframe__query__tests__barebones-2.snap | 4 +- ...re_dataframe__query__tests__barebones.snap | 26 +- .../re_dataframe__query__tests__clears-2.snap | 4 +- .../re_dataframe__query__tests__clears.snap | 4 +- ...e__query__tests__filtered_index_range.snap | 4 +- ...__query__tests__filtered_index_values.snap | 30 +- ..._query__tests__filtered_is_not_null-2.snap | 24 +- ..._query__tests__filtered_is_not_null-3.snap | 4 +- ..._query__tests__filtered_is_not_null-4.snap | 4 +- ...e__query__tests__filtered_is_not_null.snap | 24 +- ...query__tests__query_static_any_values.snap | 22 +- ..._dataframe__query__tests__selection-2.snap | 2 +- ..._dataframe__query__tests__selection-3.snap | 13 +- ..._dataframe__query__tests__selection-4.snap | 9 +- ...__sparse_fill_strategy_latestatglobal.snap | 4 +- ...e__query__tests__using_index_values-2.snap | 4 +- ...ame__query__tests__using_index_values.snap | 50 +-- ...aframe__query__tests__view_contents-2.snap | 38 +- ...ataframe__query__tests__view_contents.snap | 16 +- ...y__tests__view_contents_and_selection.snap | 45 +- crates/store/re_entity_db/Cargo.toml | 1 + crates/store/re_entity_db/src/entity_db.rs | 15 +- crates/store/re_lenses/Cargo.toml | 1 - crates/store/re_lenses/src/ast.rs | 2 +- crates/store/re_lenses/src/error.rs | 4 +- ...ifests__rrd_manifest_blueprint_schema.snap | 26 +- ...ifests__rrd_manifest_recording_schema.snap | 68 +-- ...nifests__simple_manifest_batch_schema.snap | 68 +-- ...tion_tests__decode_failure_resilience.snap | 55 +-- ...d_combinations_with_presence_tracking.snap | 13 +- ...ombinations_without_presence_tracking.snap | 13 +- ...obuf__integration_tests__oneof_nested.snap | 40 +- ...f__integration_tests__oneof_top_level.snap | 44 +- ...ests__create_table__create_table_data.snap | 8 +- ...dataset_schema__simple_dataset_schema.snap | 28 +- ...__entries_table__entries_table_schema.snap | 10 +- ...ch_chunks__simple_dataset_fetch_chunk.snap | 306 ++++++------- ...__query_dataset__empty_dataset_schema.snap | 14 +- ...ataset__simple_dataset_default_schema.snap | 14 +- ..._simple_dataset_exclude_static_schema.snap | 14 +- ...imple_dataset_exclude_temporal_schema.snap | 14 +- ...__simple_dataset_single_entity_schema.snap | 14 +- ..._simple_dataset_single_segment_schema.snap | 14 +- ...ery_dataset__simple_with_layer_schema.snap | 14 +- ...ry_dataset__with_query_default_schema.snap | 14 +- ...aset__with_query_latest_at_end_schema.snap | 14 +- ...query_dataset__with_query_none_schema.snap | 14 +- ..._dataset__with_query_range_all_schema.snap | 14 +- ...filter__simple_dataset_default_schema.snap | 10 +- ...er__simple_dataset_frame_nr_eq_schema.snap | 10 +- ...lter__simple_dataset_seg_id_eq_schema.snap | 10 +- ...uration_all_valid_index_values_schema.snap | 10 +- ...equence_all_valid_index_values_schema.snap | 10 +- ...mestamp_all_valid_index_values_schema.snap | 10 +- ...gister_segment__empty_manifest_schema.snap | 20 +- ...gister_segment__empty_segments_schema.snap | 12 +- ...t_of_order_properties_manifest_schema.snap | 22 +- ...t_of_order_properties_segments_schema.snap | 14 +- ...ister_prefix_manifest_manifest_schema.snap | 20 +- ...ister_prefix_segments_segments_schema.snap | 16 +- ...props_should_be_there_segments_schema.snap | 14 +- ...ent__simple_blueprint_manifest_schema.snap | 20 +- ...ent__simple_blueprint_segments_schema.snap | 16 +- ...ister_segment__simple_manifest_schema.snap | 20 +- ...ister_segment__simple_segments_schema.snap | 16 +- ...t__simple_with_layers_manifest_schema.snap | 20 +- ...t__simple_with_layers_segments_schema.snap | 16 +- ...th_multiple_timelines_manifest_schema.snap | 22 +- ...th_multiple_timelines_segments_schema.snap | 26 +- ...imple_with_properties_manifest_schema.snap | 24 +- ...imple_with_properties_segments_schema.snap | 20 +- ...ifest__fetch_chunks_from_rrd_manifest.snap | 51 +-- ...rd_manifest_1_all_there_sorbet_schema.snap | 12 +- ...manifest_2_base_removed_sorbet_schema.snap | 4 +- ..._manifest__rrd_manifest_sorbet_schema.snap | 10 +- ...ing_1_should_be_empty_response_schema.snap | 20 +- ...oducts_1_register_all_manifest_schema.snap | 20 +- ...oducts_1_register_all_segments_schema.snap | 14 +- ...rs_BD_for_segments_13_manifest_schema.snap | 20 +- ...rs_BD_for_segments_13_response_schema.snap | 20 +- ...rs_BD_for_segments_13_segments_schema.snap | 14 +- ...s_BD_for_all_segments_manifest_schema.snap | 20 +- ...s_BD_for_all_segments_response_schema.snap | 20 +- ...s_BD_for_all_segments_segments_schema.snap | 14 +- ...ayers_for_segments_23_manifest_schema.snap | 20 +- ...ayers_for_segments_23_response_schema.snap | 20 +- ...ayers_for_segments_23_segments_schema.snap | 14 +- ...simple_1_register_all_manifest_schema.snap | 20 +- ...simple_1_register_all_segments_schema.snap | 14 +- ..._2_remove_segment_id2_manifest_schema.snap | 20 +- ..._2_remove_segment_id2_response_schema.snap | 20 +- ..._2_remove_segment_id2_segments_schema.snap | 14 +- ...ve_remaining_segments_manifest_schema.snap | 20 +- ...ve_remaining_segments_response_schema.snap | 20 +- ...ve_remaining_segments_segments_schema.snap | 14 +- ...mple_4_reregister_all_manifest_schema.snap | 20 +- ...mple_4_reregister_all_segments_schema.snap | 14 +- ..._unregister_then_query_1_added_schema.snap | 14 +- ...nregister_then_query_2_removed_schema.snap | 14 +- .../src/component_column_descriptor.rs | 3 +- crates/store/re_types_core/src/result.rs | 18 +- ..._lenses__operations__destructure_cast.snap | 60 +-- ..._lenses__operations__destructure_only.snap | 60 +-- ...nses__lenses__operations__inner_count.snap | 60 +-- ...__lenses__operations__scatter_columns.snap | 52 +-- ...s__operations__scatter_columns_static.snap | 52 +-- ...es__lenses__operations__single_static.snap | 36 +- crates/utils/re_arrow_util/src/compare.rs | 6 +- crates/utils/re_arrow_util/src/format.rs | 16 +- .../re_arrow_util/src/format_data_type.rs | 173 -------- crates/utils/re_arrow_util/src/lib.rs | 4 +- .../re_arrow_util/src/test_extensions.rs | 8 +- crates/viewer/re_arrow_ui/src/datatype_ui.rs | 59 +-- .../tests/snapshots/binary_array.png | 4 +- .../tests/snapshots/complex_map.png | 4 +- .../tests/snapshots/coordinates_struct.png | 4 +- .../tests/snapshots/deeply_nested_struct.png | 4 +- .../tests/snapshots/dictionary_array.png | 4 +- .../tests/snapshots/fixed_size_list.png | 4 +- .../tests/snapshots/large_binary_array.png | 4 +- .../tests/snapshots/metadata_struct.png | 4 +- .../tests/snapshots/mixed_lists.png | 4 +- .../tests/snapshots/optional_struct.png | 4 +- .../tests/snapshots/simple_map.png | 4 +- .../tests/snapshots/union_array.png | 4 +- .../viewer/re_chunk_store_ui/src/chunk_ui.rs | 2 +- crates/viewer/re_component_ui/Cargo.toml | 1 - .../src/variant_uis/redap_entry_kind.rs | 7 +- .../src/variant_uis/redap_uri_button.rs | 7 +- .../viewer/re_data_ui/src/instance_path_ui.rs | 2 +- .../re_dataframe_ui/src/header_tooltip.rs | 2 +- .../header_tooltip_chunk_component_column.png | 4 +- ...tip_chunk_component_column_with_extras.png | 4 +- ...der_tooltip_dataframe_component_column.png | 4 +- ...dataframe_component_column_with_extras.png | 4 +- .../snapshots/header_tooltip_raw_field.png | 4 +- ...tip_raw_field_user_and_sorbet_metadata.png | 4 +- ...d_user_and_sorbet_metadata_with_extras.png | 4 +- ...header_tooltip_raw_field_user_metadata.png | 4 +- ...ip_raw_field_user_metadata_with_extras.png | 4 +- .../header_tooltip_raw_field_with_extras.png | 4 +- .../header_tooltip_row_field_nullable.png | 4 +- ...tooltip_row_field_nullable_with_extras.png | 4 +- crates/viewer/re_view/Cargo.toml | 1 - crates/viewer/re_view/src/lib.rs | 5 +- crates/viewer/re_view/src/query.rs | 4 +- .../src/cache/video_stream_cache.rs | 8 +- .../types/components/aggregation_policy.md | 2 +- .../types/components/albedo_factor.md | 2 +- .../types/components/annotation_context.md | 38 +- .../reference/types/components/axis_length.md | 2 +- .../reference/types/components/blob.md | 2 +- .../reference/types/components/channel_id.md | 2 +- .../components/channel_message_counts.md | 8 +- .../reference/types/components/class_id.md | 2 +- .../types/components/clear_is_recursive.md | 2 +- .../reference/types/components/color.md | 2 +- .../reference/types/components/colormap.md | 2 +- .../reference/types/components/count.md | 2 +- .../reference/types/components/depth_meter.md | 2 +- .../reference/types/components/draw_order.md | 2 +- .../reference/types/components/entity_path.md | 2 +- .../reference/types/components/fill_mode.md | 2 +- .../reference/types/components/fill_ratio.md | 2 +- .../types/components/gamma_correction.md | 2 +- .../types/components/geo_line_string.md | 2 +- .../reference/types/components/graph_edge.md | 8 +- .../reference/types/components/graph_node.md | 2 +- .../reference/types/components/graph_type.md | 2 +- .../reference/types/components/half_size2d.md | 2 +- .../reference/types/components/half_size3d.md | 2 +- .../types/components/image_buffer.md | 2 +- .../types/components/image_format.md | 14 +- .../types/components/image_plane_distance.md | 2 +- .../reference/types/components/interactive.md | 2 +- .../types/components/interpolation_mode.md | 2 +- .../types/components/key_value_pairs.md | 8 +- .../reference/types/components/keypoint_id.md | 2 +- .../reference/types/components/lat_lon.md | 2 +- .../reference/types/components/length.md | 2 +- .../types/components/line_strip2d.md | 2 +- .../types/components/line_strip3d.md | 2 +- .../types/components/linear_speed.md | 2 +- .../types/components/magnification_filter.md | 2 +- .../types/components/marker_shape.md | 2 +- .../reference/types/components/marker_size.md | 2 +- .../reference/types/components/media_type.md | 2 +- .../reference/types/components/name.md | 2 +- .../reference/types/components/opacity.md | 2 +- .../types/components/pinhole_projection.md | 2 +- .../reference/types/components/plane3d.md | 2 +- .../reference/types/components/position2d.md | 2 +- .../reference/types/components/position3d.md | 2 +- .../reference/types/components/radius.md | 2 +- .../reference/types/components/range1d.md | 2 +- .../reference/types/components/resolution.md | 2 +- .../types/components/rotation_axis_angle.md | 8 +- .../types/components/rotation_quat.md | 2 +- .../reference/types/components/scalar.md | 2 +- .../reference/types/components/scale3d.md | 2 +- .../reference/types/components/schema_id.md | 2 +- .../reference/types/components/show_labels.md | 2 +- .../types/components/stroke_width.md | 2 +- .../reference/types/components/tensor_data.md | 36 +- .../tensor_dimension_index_selection.md | 8 +- .../components/tensor_height_dimension.md | 8 +- .../components/tensor_width_dimension.md | 8 +- .../reference/types/components/texcoord2d.md | 2 +- .../reference/types/components/text.md | 2 +- .../types/components/text_log_level.md | 2 +- .../reference/types/components/timestamp.md | 2 +- .../types/components/transform_frame_id.md | 2 +- .../types/components/transform_mat3x3.md | 2 +- .../types/components/transform_relation.md | 2 +- .../types/components/translation3d.md | 2 +- .../types/components/triangle_indices.md | 2 +- .../reference/types/components/value_range.md | 2 +- .../reference/types/components/vector2d.md | 2 +- .../reference/types/components/vector3d.md | 2 +- .../reference/types/components/video_codec.md | 2 +- .../types/components/video_sample.md | 2 +- .../types/components/video_timestamp.md | 2 +- .../types/components/view_coordinates.md | 2 +- .../reference/types/components/visible.md | 2 +- .../types/datatypes/absolute_time_range.md | 12 +- .../reference/types/datatypes/angle.md | 2 +- .../types/datatypes/annotation_info.md | 16 +- .../content/reference/types/datatypes/blob.md | 2 +- .../content/reference/types/datatypes/bool.md | 2 +- .../types/datatypes/channel_count_pair.md | 12 +- .../types/datatypes/channel_datatype.md | 2 +- .../types/datatypes/class_description.md | 38 +- .../datatypes/class_description_map_elem.md | 42 +- .../reference/types/datatypes/class_id.md | 2 +- .../reference/types/datatypes/color_model.md | 2 +- .../reference/types/datatypes/dvec2d.md | 2 +- .../reference/types/datatypes/entity_path.md | 2 +- .../reference/types/datatypes/float32.md | 2 +- .../reference/types/datatypes/float64.md | 2 +- .../reference/types/datatypes/image_format.md | 24 +- .../reference/types/datatypes/keypoint_id.md | 2 +- .../types/datatypes/keypoint_pair.md | 12 +- .../reference/types/datatypes/mat3x3.md | 2 +- .../reference/types/datatypes/mat4x4.md | 2 +- .../reference/types/datatypes/pixel_format.md | 2 +- .../reference/types/datatypes/plane3d.md | 2 +- .../reference/types/datatypes/quaternion.md | 2 +- .../reference/types/datatypes/range1d.md | 2 +- .../reference/types/datatypes/range2d.md | 12 +- .../reference/types/datatypes/rgba32.md | 2 +- .../types/datatypes/rotation_axis_angle.md | 12 +- .../types/datatypes/tensor_buffer.md | 50 +-- .../reference/types/datatypes/tensor_data.md | 42 +- .../tensor_dimension_index_selection.md | 12 +- .../datatypes/tensor_dimension_selection.md | 12 +- .../reference/types/datatypes/time_int.md | 2 +- .../reference/types/datatypes/time_range.md | 32 +- .../types/datatypes/time_range_boundary.md | 16 +- .../reference/types/datatypes/uint16.md | 2 +- .../reference/types/datatypes/uint32.md | 2 +- .../reference/types/datatypes/uint64.md | 2 +- .../content/reference/types/datatypes/utf8.md | 2 +- .../reference/types/datatypes/utf8pair.md | 12 +- .../content/reference/types/datatypes/uuid.md | 2 +- .../reference/types/datatypes/uvec2d.md | 2 +- .../reference/types/datatypes/uvec3d.md | 2 +- .../reference/types/datatypes/uvec4d.md | 2 +- .../reference/types/datatypes/vec2d.md | 2 +- .../reference/types/datatypes/vec3d.md | 2 +- .../reference/types/datatypes/vec4d.md | 2 +- .../types/datatypes/video_timestamp.md | 2 +- .../types/datatypes/view_coordinates.md | 2 +- .../types/datatypes/visible_time_range.md | 38 +- pixi.toml | 4 +- .../test_current/test_catalog_basics.py | 32 +- .../test_current/test_dataframe_api.py | 38 +- .../test_current/test_dataset_basics.py | 60 +-- .../test_current/test_table_api.py | 40 +- .../test_current/test_table_basics.py | 144 +++--- .../test_draft/test_dataframe_api.py | 38 +- .../test_draft/test_dataset_basics.py | 116 ++--- .../test_draft/test_dataset_views.py | 114 ++--- .../test_draft/test_index_ranges.py | 120 ++--- .../api_sandbox/test_draft/test_table_api.py | 40 +- .../test_draft/test_table_basics.py | 168 +++---- .../__snapshots__/test_dataset_views.ambr | 418 +++++++++--------- .../e2e_redap_tests/test_index_ranges.py | 140 +++--- .../e2e_redap_tests/test_registration.py | 76 ++-- 309 files changed, 3359 insertions(+), 3510 deletions(-) delete mode 100644 crates/utils/re_arrow_util/src/format_data_type.rs diff --git a/Cargo.lock b/Cargo.lock index eff1f49bdeb4..6da32cde91b1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8762,7 +8762,6 @@ dependencies = [ "egui_plot", "itertools 0.14.0", "nohash-hasher", - "re_arrow_util", "re_data_ui", "re_format", "re_log", @@ -9052,6 +9051,7 @@ dependencies = [ "document-features", "emath", "indexmap 2.13.0", + "insta", "itertools 0.14.0", "nohash-hasher", "poll-promise", @@ -9205,7 +9205,6 @@ dependencies = [ "itertools 0.14.0", "nohash-hasher", "re_arrow_combinators", - "re_arrow_util", "re_chunk", "re_log", "re_log_types", @@ -10278,7 +10277,6 @@ dependencies = [ "itertools 0.14.0", "nohash-hasher", "re_arrow_combinators", - "re_arrow_util", "re_chunk_store", "re_entity_db", "re_log", diff --git a/crates/build/re_types_builder/src/codegen/docs/datatype_docs.rs b/crates/build/re_types_builder/src/codegen/docs/datatype_docs.rs index e9584a678fb7..89c0759aa0ea 100644 --- a/crates/build/re_types_builder/src/codegen/docs/datatype_docs.rs +++ b/crates/build/re_types_builder/src/codegen/docs/datatype_docs.rs @@ -5,19 +5,19 @@ use crate::data_type::{AtomicDataType, DataType, UnionMode}; fn atomic_datatype_docs(page: &mut String, datatype: &AtomicDataType) { match datatype { - AtomicDataType::Null => page.push_str("null"), - AtomicDataType::Boolean => page.push_str("boolean"), - AtomicDataType::Int8 => page.push_str("int8"), - AtomicDataType::Int16 => page.push_str("int16"), - AtomicDataType::Int32 => page.push_str("int32"), - AtomicDataType::Int64 => page.push_str("int64"), - AtomicDataType::UInt8 => page.push_str("uint8"), - AtomicDataType::UInt16 => page.push_str("uint16"), - AtomicDataType::UInt32 => page.push_str("uint32"), - AtomicDataType::UInt64 => page.push_str("uint64"), - AtomicDataType::Float16 => page.push_str("float16"), - AtomicDataType::Float32 => page.push_str("float32"), - AtomicDataType::Float64 => page.push_str("float64"), + AtomicDataType::Null => page.push_str("Null"), + AtomicDataType::Boolean => page.push_str("Boolean"), + AtomicDataType::Int8 => page.push_str("Int8"), + AtomicDataType::Int16 => page.push_str("Int16"), + AtomicDataType::Int32 => page.push_str("Int32"), + AtomicDataType::Int64 => page.push_str("Int64"), + AtomicDataType::UInt8 => page.push_str("UInt8"), + AtomicDataType::UInt16 => page.push_str("UInt16"), + AtomicDataType::UInt32 => page.push_str("UInt32"), + AtomicDataType::UInt64 => page.push_str("UInt64"), + AtomicDataType::Float16 => page.push_str("Float16"), + AtomicDataType::Float32 => page.push_str("Float32"), + AtomicDataType::Float64 => page.push_str("Float64"), } } @@ -30,45 +30,53 @@ fn datatype_docs_impl(page: &mut String, indent: usize, datatype: &DataType) { DataType::Atomic(atomic) => { atomic_datatype_docs(page, atomic); } - DataType::Utf8 => page.push_str("utf8"), - DataType::Binary => page.push_str("binary"), + DataType::Utf8 => page.push_str("Utf8"), + DataType::Binary => page.push_str("Binary"), DataType::List(inner) => { - page.push_str("List<"); + page.push_str("List("); + if !inner.is_nullable() { + // This follows the notation set by arrow-rs. + // If we change this, we should probably change + // arrow-rs and datafusion to match. + page.push_str("non-null "); + } datatype_docs_impl(page, indent + 1, inner.data_type()); - page.push('>'); + page.push(')'); } DataType::FixedSizeList(inner, length) => { - page.push_str(&format!("FixedSizeList<{length}, ")); + page.push_str(&format!("FixedSizeList({length} x ")); + if !inner.is_nullable() { + page.push_str("non-null "); + } datatype_docs_impl(page, indent + 1, inner.data_type()); - page.push('>'); + page.push(')'); } DataType::Struct(fields) => { - page.push_str("Struct {\n"); + page.push_str("Struct(\n"); for field in fields { - page.push_indented(indent + 1, field.name(), 0); - page.push_str(": "); - if field.is_nullable() { - page.push_str("nullable "); + page.push_indented(indent + 1, format!("{:?}: ", field.name()), 0); + if !field.is_nullable() { + page.push_str("non-null "); } datatype_docs_impl(page, indent + 1, field.data_type()); page.push('\n'); } - page.push_indented(indent, "}", 0); + page.push_indented(indent, ")", 0); } DataType::Union(union_fields, union_mode) => { match union_mode { - UnionMode::Sparse => page.push_str("SparseUnion {\n"), - UnionMode::Dense => page.push_str("DenseUnion {\n"), + UnionMode::Sparse => page.push_str("Union(Sparse,\n"), + UnionMode::Dense => page.push_str("Union(Dense,\n"), } for (index, field) in union_fields.iter().enumerate() { - page.push_indented(indent + 1, format!("{index} = {:?}: ", field.name()), 0); - if field.is_nullable() { - page.push_str("nullable "); + page.push_indented(indent + 1, format!("{index}: ({:?}: ", field.name()), 0); + if !field.is_nullable() { + page.push_str("non-null "); } datatype_docs_impl(page, indent + 1, field.data_type()); - page.push('\n'); + page.push_str(")\n"); } - page.push_indented(indent, "}", 0); + page.push_indented(indent, ")", 0); } DataType::Object { datatype, .. } => { datatype_docs_impl(page, indent, datatype); diff --git a/crates/build/re_types_builder/src/codegen/docs/website.rs b/crates/build/re_types_builder/src/codegen/docs/website.rs index dd5baba052b5..ae0efc823f74 100644 --- a/crates/build/re_types_builder/src/codegen/docs/website.rs +++ b/crates/build/re_types_builder/src/codegen/docs/website.rs @@ -401,20 +401,20 @@ fn write_fields(reporter: &Reporter, objects: &Objects, o: &mut String, object: Type::Unit => unreachable!("Should be handled elsewhere"), // We use explicit, arrow-like names: - Type::UInt8 => atomic("uint8"), - Type::UInt16 => atomic("uint16"), - Type::UInt32 => atomic("uint32"), - Type::UInt64 => atomic("uint64"), - Type::Int8 => atomic("int8"), - Type::Int16 => atomic("int16"), - Type::Int32 => atomic("int32"), - Type::Int64 => atomic("int64"), - Type::Bool => atomic("boolean"), - Type::Float16 => atomic("float16"), - Type::Float32 => atomic("float32"), - Type::Float64 => atomic("float64"), - Type::Binary => atomic("binary"), - Type::String => atomic("utf8"), + Type::UInt8 => atomic("UInt8"), + Type::UInt16 => atomic("UInt16"), + Type::UInt32 => atomic("UInt32"), + Type::UInt64 => atomic("UInt64"), + Type::Int8 => atomic("Int8"), + Type::Int16 => atomic("Int16"), + Type::Int32 => atomic("Int32"), + Type::Int64 => atomic("Int64"), + Type::Bool => atomic("Boolean"), + Type::Float16 => atomic("Float16"), + Type::Float32 => atomic("Float32"), + Type::Float64 => atomic("Float64"), + Type::Binary => atomic("Binary"), + Type::String => atomic("Utf8"), Type::Array { elem_type, length } => { format!( @@ -475,8 +475,11 @@ fn write_fields(reporter: &Reporter, objects: &Objects, o: &mut String, object: if field.typ == Type::Unit { field_string.push_str("`null`"); } else { - if field.is_nullable { - field_string.push_str("nullable "); + if !field.is_nullable { + // This follows the notation set by arrow-rs. + // If we change this, we should probably change + // arrow-rs and datafusion to match. + field_string.push_str("non-null "); } field_string.push_str(&type_info(objects, &field.typ)); } diff --git a/crates/store/re_arrow_combinators/tests/test_explode.rs b/crates/store/re_arrow_combinators/tests/test_explode.rs index 851bb0565fb7..6933987b1c79 100644 --- a/crates/store/re_arrow_combinators/tests/test_explode.rs +++ b/crates/store/re_arrow_combinators/tests/test_explode.rs @@ -17,41 +17,41 @@ fn test_explode_primitives() { Some(vec![Some(6)]), ]); - insta::assert_snapshot!(format!("{}", DisplayRB(input.clone())), @" - ┌───────────────────────────────────┐ - │ col │ - │ --- │ - │ type: nullable List[nullable i32] │ - ╞═══════════════════════════════════╡ - │ [1, 2, 3] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [4, 5] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [6] │ - └───────────────────────────────────┘ + insta::assert_snapshot!(format!("{}", DisplayRB(input.clone())), @r" + ┌───────────────────┐ + │ col │ + │ --- │ + │ type: List(Int32) │ + ╞═══════════════════╡ + │ [1, 2, 3] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [4, 5] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [6] │ + └───────────────────┘ "); let explode = Explode; let result = explode.transform(&input).unwrap(); - insta::assert_snapshot!(format!("{}", DisplayRB(result)), @" - ┌───────────────────────────────────┐ - │ col │ - │ --- │ - │ type: nullable List[nullable i32] │ - ╞═══════════════════════════════════╡ - │ [1] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [2] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [3] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [4] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [5] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [6] │ - └───────────────────────────────────┘ + insta::assert_snapshot!(format!("{}", DisplayRB(result)), @r" + ┌───────────────────┐ + │ col │ + │ --- │ + │ type: List(Int32) │ + ╞═══════════════════╡ + │ [1] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [2] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [3] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [4] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [5] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [6] │ + └───────────────────┘ "); } @@ -64,41 +64,41 @@ fn test_explode_with_nulls_and_empty() { Some(vec![Some(3)]), ]); - insta::assert_snapshot!(format!("{}", DisplayRB(input.clone())), @" - ┌───────────────────────────────────┐ - │ col │ - │ --- │ - │ type: nullable List[nullable i32] │ - ╞═══════════════════════════════════╡ - │ [1, 2] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ null │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [3] │ - └───────────────────────────────────┘ + insta::assert_snapshot!(format!("{}", DisplayRB(input.clone())), @r" + ┌───────────────────┐ + │ col │ + │ --- │ + │ type: List(Int32) │ + ╞═══════════════════╡ + │ [1, 2] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ null │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [3] │ + └───────────────────┘ "); let explode = Explode; let result = explode.transform(&input).unwrap(); - insta::assert_snapshot!(format!("{}", DisplayRB(result)), @" - ┌───────────────────────────────────┐ - │ col │ - │ --- │ - │ type: nullable List[nullable i32] │ - ╞═══════════════════════════════════╡ - │ [1] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [2] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ null │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [3] │ - └───────────────────────────────────┘ + insta::assert_snapshot!(format!("{}", DisplayRB(result)), @r" + ┌───────────────────┐ + │ col │ + │ --- │ + │ type: List(Int32) │ + ╞═══════════════════╡ + │ [1] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [2] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ null │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [3] │ + └───────────────────┘ "); } @@ -118,33 +118,33 @@ fn test_explode_nested_lists() { None, ); - insta::assert_snapshot!(format!("{}", DisplayRB(input.clone())), @" - ┌──────────────────────────────────────────────────┐ - │ col │ - │ --- │ - │ type: nullable List[nullable List[nullable i32]] │ - ╞══════════════════════════════════════════════════╡ - │ [[1, 2], [3]] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [[4, 5, 6]] │ - └──────────────────────────────────────────────────┘ + insta::assert_snapshot!(format!("{}", DisplayRB(input.clone())), @r" + ┌─────────────────────────┐ + │ col │ + │ --- │ + │ type: List(List(Int32)) │ + ╞═════════════════════════╡ + │ [[1, 2], [3]] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [[4, 5, 6]] │ + └─────────────────────────┘ "); let explode = Explode; let result = explode.transform(&input).unwrap(); - insta::assert_snapshot!(format!("{}", DisplayRB(result)), @" - ┌──────────────────────────────────────────────────┐ - │ col │ - │ --- │ - │ type: nullable List[nullable List[nullable i32]] │ - ╞══════════════════════════════════════════════════╡ - │ [[1, 2]] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [[3]] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [[4, 5, 6]] │ - └──────────────────────────────────────────────────┘ + insta::assert_snapshot!(format!("{}", DisplayRB(result)), @r" + ┌─────────────────────────┐ + │ col │ + │ --- │ + │ type: List(List(Int32)) │ + ╞═════════════════════════╡ + │ [[1, 2]] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [[3]] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [[4, 5, 6]] │ + └─────────────────────────┘ "); } @@ -158,13 +158,13 @@ fn test_explode_empty_input() { let explode = Explode; let result = explode.transform(&input).unwrap(); - insta::assert_snapshot!(format!("{}", DisplayRB(result)), @" - ┌───────────────────────────────────┐ - │ col │ - │ --- │ - │ type: nullable List[nullable i32] │ - ╞═══════════════════════════════════╡ - └───────────────────────────────────┘ + insta::assert_snapshot!(format!("{}", DisplayRB(result)), @r" + ┌───────────────────┐ + │ col │ + │ --- │ + │ type: List(Int32) │ + ╞═══════════════════╡ + └───────────────────┘ "); } @@ -177,40 +177,40 @@ fn test_explode_with_skips_in_offset_buffer() { let input = ListArray::new(field, offsets, Arc::new(values), Some(validity)); - insta::assert_snapshot!(format!("{}", DisplayRB(input.clone())), @" - ┌───────────────────────────────────┐ - │ col │ - │ --- │ - │ type: nullable List[nullable i32] │ - ╞═══════════════════════════════════╡ - │ [0, 1] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ null │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [7, 8, 9] │ - └───────────────────────────────────┘ + insta::assert_snapshot!(format!("{}", DisplayRB(input.clone())), @r" + ┌───────────────────┐ + │ col │ + │ --- │ + │ type: List(Int32) │ + ╞═══════════════════╡ + │ [0, 1] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ null │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [7, 8, 9] │ + └───────────────────┘ "); let explode = Explode; let result = explode.transform(&input).unwrap(); - insta::assert_snapshot!(format!("{}", DisplayRB(result)), @" - ┌───────────────────────────────────┐ - │ col │ - │ --- │ - │ type: nullable List[nullable i32] │ - ╞═══════════════════════════════════╡ - │ [0] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [1] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ null │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [7] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [8] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [9] │ - └───────────────────────────────────┘ + insta::assert_snapshot!(format!("{}", DisplayRB(result)), @r" + ┌───────────────────┐ + │ col │ + │ --- │ + │ type: List(Int32) │ + ╞═══════════════════╡ + │ [0] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [1] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ null │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [7] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [8] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [9] │ + └───────────────────┘ "); } diff --git a/crates/store/re_arrow_combinators/tests/test_selector.rs b/crates/store/re_arrow_combinators/tests/test_selector.rs index 31170b1063d6..c85fd8ec1b8f 100644 --- a/crates/store/re_arrow_combinators/tests/test_selector.rs +++ b/crates/store/re_arrow_combinators/tests/test_selector.rs @@ -21,26 +21,26 @@ fn execute_nested_struct() -> Result<(), Error> { .execute_per_row(&array)? .unwrap(); - insta::assert_snapshot!(format!("{}", DisplayRB(result)), @" - ┌───────────────────────────────────┐ - │ col │ - │ --- │ - │ type: nullable List[nullable f64] │ - ╞═══════════════════════════════════╡ - │ [1.0] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [null] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ null │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [3.0, 5.0] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [null, 7.0] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [null, null] │ - └───────────────────────────────────┘ + insta::assert_snapshot!(format!("{}", DisplayRB(result)), @r" + ┌─────────────────────┐ + │ col │ + │ --- │ + │ type: List(Float64) │ + ╞═════════════════════╡ + │ [1.0] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [null] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ null │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [3.0, 5.0] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [null, 7.0] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [null, null] │ + └─────────────────────┘ "); Ok(()) @@ -52,25 +52,25 @@ fn execute_identity() -> Result<(), Error> { let result = ".".parse::()?.execute_per_row(&array)?.unwrap(); - insta::assert_snapshot!(format!("{}", DisplayRB(result)), @" - ┌───────────────────────────────────────────────────┐ - │ col │ - │ --- │ - │ type: nullable List[nullable Struct[1]] │ - ╞═══════════════════════════════════════════════════╡ - │ [{poses: [{x: 1.0, y: 2.0}, {x: 3.0, y: 4.0}]}] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [{poses: [{x: 5.0, y: 6.0}]}] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [{poses: []}] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ null │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [{poses: [{x: 7.0, y: null}, {x: 9.0, y: 10.0}]}] │ - └───────────────────────────────────────────────────┘ - "); + insta::assert_snapshot!(format!("{}", DisplayRB(result)), @r#" + ┌─────────────────────────────────────────────────────────────────────────────────────────┐ + │ col │ + │ --- │ + │ type: List(Struct("poses": non-null List(non-null Struct("x": Float64, "y": Float64)))) │ + ╞═════════════════════════════════════════════════════════════════════════════════════════╡ + │ [{poses: [{x: 1.0, y: 2.0}, {x: 3.0, y: 4.0}]}] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [{poses: [{x: 5.0, y: 6.0}]}] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [{poses: []}] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ null │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [{poses: [{x: 7.0, y: null}, {x: 9.0, y: 10.0}]}] │ + └─────────────────────────────────────────────────────────────────────────────────────────┘ + "#); Ok(()) } @@ -83,25 +83,25 @@ fn execute_simple_field() -> Result<(), Error> { .execute_per_row(&array)? .unwrap(); - insta::assert_snapshot!(format!("{}", DisplayRB(result)), @r" - ┌───────────────────────────────────────────────┐ - │ col │ - │ --- │ - │ type: nullable List[nullable List[Struct[2]]] │ - ╞═══════════════════════════════════════════════╡ - │ [[{x: 1.0, y: 2.0}, {x: 3.0, y: 4.0}]] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [[{x: 5.0, y: 6.0}]] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [[]] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ null │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [[{x: 7.0, y: null}, {x: 9.0, y: 10.0}]] │ - └───────────────────────────────────────────────┘ - "); + insta::assert_snapshot!(format!("{}", DisplayRB(result)), @r#" + ┌───────────────────────────────────────────────────────────────┐ + │ col │ + │ --- │ + │ type: List(List(non-null Struct("x": Float64, "y": Float64))) │ + ╞═══════════════════════════════════════════════════════════════╡ + │ [[{x: 1.0, y: 2.0}, {x: 3.0, y: 4.0}]] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [[{x: 5.0, y: 6.0}]] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [[]] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ null │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [[{x: 7.0, y: null}, {x: 9.0, y: 10.0}]] │ + └───────────────────────────────────────────────────────────────┘ + "#); Ok(()) } @@ -114,25 +114,25 @@ fn execute_index() -> Result<(), Error> { .execute_per_row(&array)? .unwrap(); - insta::assert_snapshot!(format!("{}", DisplayRB(result)), @" - ┌─────────────────────────────────────────┐ - │ col │ - │ --- │ - │ type: nullable List[nullable Struct[2]] │ - ╞═════════════════════════════════════════╡ - │ [{x: 1.0, y: 2.0}] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [{x: 5.0, y: 6.0}] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [null] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ null │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [{x: 7.0, y: null}] │ - └─────────────────────────────────────────┘ - "); + insta::assert_snapshot!(format!("{}", DisplayRB(result)), @r#" + ┌────────────────────────────────────────────────┐ + │ col │ + │ --- │ + │ type: List(Struct("x": Float64, "y": Float64)) │ + ╞════════════════════════════════════════════════╡ + │ [{x: 1.0, y: 2.0}] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [{x: 5.0, y: 6.0}] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [null] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ null │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [{x: 7.0, y: null}] │ + └────────────────────────────────────────────────┘ + "#); Ok(()) } @@ -145,24 +145,24 @@ fn execute_index_chained() -> Result<(), Error> { .execute_per_row(&array)? .unwrap(); - insta::assert_snapshot!(format!("{}", DisplayRB(result)), @" - ┌───────────────────────────────────┐ - │ col │ - │ --- │ - │ type: nullable List[nullable f64] │ - ╞═══════════════════════════════════╡ - │ [1.0] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [5.0] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [null] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ null │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [7.0] │ - └───────────────────────────────────┘ + insta::assert_snapshot!(format!("{}", DisplayRB(result)), @r" + ┌─────────────────────┐ + │ col │ + │ --- │ + │ type: List(Float64) │ + ╞═════════════════════╡ + │ [1.0] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [5.0] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [null] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ null │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [7.0] │ + └─────────────────────┘ "); Ok(()) } @@ -176,25 +176,25 @@ fn execute_index_to_extract_second_element() -> Result<(), Error> { .execute_per_row(&array)? .unwrap(); - insta::assert_snapshot!(format!("{}", DisplayRB(result)), @" - ┌─────────────────────────────────────────┐ - │ col │ - │ --- │ - │ type: nullable List[nullable Struct[2]] │ - ╞═════════════════════════════════════════╡ - │ [{x: 3.0, y: 4.0}] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [null] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [null] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ null │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [{x: 9.0, y: 10.0}] │ - └─────────────────────────────────────────┘ - "); + insta::assert_snapshot!(format!("{}", DisplayRB(result)), @r#" + ┌────────────────────────────────────────────────┐ + │ col │ + │ --- │ + │ type: List(Struct("x": Float64, "y": Float64)) │ + ╞════════════════════════════════════════════════╡ + │ [{x: 3.0, y: 4.0}] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [null] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [null] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ null │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [{x: 9.0, y: 10.0}] │ + └────────────────────────────────────────────────┘ + "#); Ok(()) } @@ -207,25 +207,25 @@ fn execute_array_each() -> Result<(), Error> { .execute_per_row(&array)? .unwrap(); - insta::assert_snapshot!(format!("{}", DisplayRB(result)), @" - ┌─────────────────────────────────────────┐ - │ col │ - │ --- │ - │ type: nullable List[nullable Struct[2]] │ - ╞═════════════════════════════════════════╡ - │ [{x: 1.0, y: 2.0}, {x: 3.0, y: 4.0}] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [{x: 5.0, y: 6.0}] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ null │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [{x: 7.0, y: null}, {x: 9.0, y: 10.0}] │ - └─────────────────────────────────────────┘ - "); + insta::assert_snapshot!(format!("{}", DisplayRB(result)), @r#" + ┌────────────────────────────────────────────────┐ + │ col │ + │ --- │ + │ type: List(Struct("x": Float64, "y": Float64)) │ + ╞════════════════════════════════════════════════╡ + │ [{x: 1.0, y: 2.0}, {x: 3.0, y: 4.0}] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [{x: 5.0, y: 6.0}] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ null │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [{x: 7.0, y: null}, {x: 9.0, y: 10.0}] │ + └────────────────────────────────────────────────┘ + "#); Ok(()) } @@ -262,25 +262,25 @@ fn execute_index_out_of_bounds() -> Result<(), Error> { .execute_per_row(&array)? .unwrap(); - insta::assert_snapshot!(format!("{}", DisplayRB(result)), @" - ┌─────────────────────────────────────────┐ - │ col │ - │ --- │ - │ type: nullable List[nullable Struct[2]] │ - ╞═════════════════════════════════════════╡ - │ [null] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [null] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [null] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ null │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [null] │ - └─────────────────────────────────────────┘ - "); + insta::assert_snapshot!(format!("{}", DisplayRB(result)), @r#" + ┌────────────────────────────────────────────────┐ + │ col │ + │ --- │ + │ type: List(Struct("x": Float64, "y": Float64)) │ + ╞════════════════════════════════════════════════╡ + │ [null] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [null] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [null] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ null │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [null] │ + └────────────────────────────────────────────────┘ + "#); Ok(()) } @@ -295,16 +295,16 @@ fn execute_index_on_fixed_size_list() -> Result<(), Error> { let list_field = Arc::new(Field::new_list_field(fixed_list.data_type().clone(), true)); let array = ListArray::new(list_field, offsets, Arc::new(fixed_list), None); - insta::assert_snapshot!(format!("{}", DisplayRB(array.clone())), @" - ┌──────────────────────────────────────────────────────────────┐ - │ col │ - │ --- │ - │ type: nullable List[nullable FixedSizeList[nullable i32; 3]] │ - ╞══════════════════════════════════════════════════════════════╡ - │ [[1, 2, 3], [4, 5, 6]] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [[7, 8, 9]] │ - └──────────────────────────────────────────────────────────────┘ + insta::assert_snapshot!(format!("{}", DisplayRB(array.clone())), @r" + ┌──────────────────────────────────────┐ + │ col │ + │ --- │ + │ type: List(FixedSizeList(3 x Int32)) │ + ╞══════════════════════════════════════╡ + │ [[1, 2, 3], [4, 5, 6]] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [[7, 8, 9]] │ + └──────────────────────────────────────┘ "); let result = ".[0][1]".parse::()?.execute_per_row(&array); diff --git a/crates/store/re_arrow_combinators/tests/test_string_transforms.rs b/crates/store/re_arrow_combinators/tests/test_string_transforms.rs index 161e38ad8fc8..8d6e8011fe07 100644 --- a/crates/store/re_arrow_combinators/tests/test_string_transforms.rs +++ b/crates/store/re_arrow_combinators/tests/test_string_transforms.rs @@ -17,117 +17,117 @@ fn test_string_transforms_from_nested_struct() { .transform(&list_array) .expect("failed to extract names"); insta::assert_snapshot!(DisplayRB(names_list.clone()), @r" - ┌────────────────────────────────────┐ - │ col │ - │ --- │ - │ type: nullable List[nullable Utf8] │ - ╞════════════════════════════════════╡ - │ [alice] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [null] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ null │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [null, dave] │ - └────────────────────────────────────┘ - "); + ┌──────────────────┐ + │ col │ + │ --- │ + │ type: List(Utf8) │ + ╞══════════════════╡ + │ [alice] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [null] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ null │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [null, dave] │ + └──────────────────┘ + "); let colors_list = MapList::new(GetField::new("data")) .then(MapList::new(GetField::new("colors"))) .transform(&list_array) .expect("failed to extract colors"); insta::assert_snapshot!(DisplayRB(colors_list.clone()), @r" - ┌────────────────────────────────────┐ - │ col │ - │ --- │ - │ type: nullable List[nullable Utf8] │ - ╞════════════════════════════════════╡ - │ [red] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [null] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ null │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [null, yellow] │ - └────────────────────────────────────┘ - "); + ┌──────────────────┐ + │ col │ + │ --- │ + │ type: List(Utf8) │ + ╞══════════════════╡ + │ [red] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [null] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ null │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [null, yellow] │ + └──────────────────┘ + "); // Test prefix on names array using MapList. let prefix_names = MapList::new(StringPrefix::new("user:")) .transform(&names_list) .expect("prefix transformation failed"); insta::assert_snapshot!(DisplayRB(prefix_names.clone()), @r" - ┌────────────────────────────────────┐ - │ col │ - │ --- │ - │ type: nullable List[nullable Utf8] │ - ╞════════════════════════════════════╡ - │ [user:alice] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [null] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ null │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [null, user:dave] │ - └────────────────────────────────────┘ - "); + ┌───────────────────┐ + │ col │ + │ --- │ + │ type: List(Utf8) │ + ╞═══════════════════╡ + │ [user:alice] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [null] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ null │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [null, user:dave] │ + └───────────────────┘ + "); // Test suffix on colors array using MapList. let suffix_colors = MapList::new(StringSuffix::new("_color")) .transform(&colors_list) .expect("suffix transformation failed"); insta::assert_snapshot!(DisplayRB(suffix_colors.clone()), @r" - ┌────────────────────────────────────┐ - │ col │ - │ --- │ - │ type: nullable List[nullable Utf8] │ - ╞════════════════════════════════════╡ - │ [red_color] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [null] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ null │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [null, yellow_color] │ - └────────────────────────────────────┘ - "); + ┌──────────────────────┐ + │ col │ + │ --- │ + │ type: List(Utf8) │ + ╞══════════════════════╡ + │ [red_color] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [null] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ null │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [null, yellow_color] │ + └──────────────────────┘ + "); // Test chaining on names array using MapList and Compose (via .then()). let chained_names = MapList::new(StringPrefix::new("<").then(StringSuffix::new(">"))) .transform(&names_list) .expect("chained transformation failed"); insta::assert_snapshot!(DisplayRB(chained_names.clone()), @r" - ┌────────────────────────────────────┐ - │ col │ - │ --- │ - │ type: nullable List[nullable Utf8] │ - ╞════════════════════════════════════╡ - │ [] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [null] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ null │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [null, ] │ - └────────────────────────────────────┘ - "); + ┌──────────────────┐ + │ col │ + │ --- │ + │ type: List(Utf8) │ + ╞══════════════════╡ + │ [] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [null] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ null │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [null, ] │ + └──────────────────┘ + "); // Verify original nested list structure is unaffected by the transformations. - insta::assert_snapshot!(DisplayRB(list_array.clone()), @r" - ┌───────────────────────────────────────────────────────┐ - │ col │ - │ --- │ - │ type: nullable List[nullable Struct[1]] │ - ╞═══════════════════════════════════════════════════════╡ - │ [{data: {names: alice, colors: red}}] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [null] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ null │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [{data: null}, {data: {names: dave, colors: yellow}}] │ - └───────────────────────────────────────────────────────┘ - "); + insta::assert_snapshot!(DisplayRB(list_array.clone()), @r#" + ┌───────────────────────────────────────────────────────────────────┐ + │ col │ + │ --- │ + │ type: List(Struct("data": Struct("names": Utf8, "colors": Utf8))) │ + ╞═══════════════════════════════════════════════════════════════════╡ + │ [{data: {names: alice, colors: red}}] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [null] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ null │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [{data: null}, {data: {names: dave, colors: yellow}}] │ + └───────────────────────────────────────────────────────────────────┘ + "#); } /// Tests that `StringPrefix` and `StringSuffix` preserve empty strings as-is when configured to do so. @@ -142,38 +142,38 @@ fn test_string_transforms_preserve_empty_strings() { .transform(&input) .unwrap(); insta::assert_snapshot!(DisplayRB(prefixed), @r" - ┌─────────────────────┐ - │ col │ - │ --- │ - │ type: nullable Utf8 │ - ╞═════════════════════╡ - │ prefix_hello │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ null │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ prefix_world │ - └─────────────────────┘ - "); + ┌──────────────┐ + │ col │ + │ --- │ + │ type: Utf8 │ + ╞══════════════╡ + │ prefix_hello │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ null │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ prefix_world │ + └──────────────┘ + "); let suffixed = StringSuffix::new("_suffix") .with_suffix_empty_string(false) .transform(&input) .unwrap(); insta::assert_snapshot!(DisplayRB(suffixed), @r" - ┌─────────────────────┐ - │ col │ - │ --- │ - │ type: nullable Utf8 │ - ╞═════════════════════╡ - │ hello_suffix │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ null │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ world_suffix │ - └─────────────────────┘ - "); + ┌──────────────┐ + │ col │ + │ --- │ + │ type: Utf8 │ + ╞══════════════╡ + │ hello_suffix │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ null │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ world_suffix │ + └──────────────┘ + "); } diff --git a/crates/store/re_arrow_combinators/tests/test_transform.rs b/crates/store/re_arrow_combinators/tests/test_transform.rs index 1fb00403bbc8..5cde79b21a5f 100644 --- a/crates/store/re_arrow_combinators/tests/test_transform.rs +++ b/crates/store/re_arrow_combinators/tests/test_transform.rs @@ -24,23 +24,23 @@ fn simple() { let result: ListArray = pipeline.transform(&array).unwrap(); insta::assert_snapshot!(format!("{}", DisplayRB(result.clone())), @r" - ┌──────────────────────────────────────────────────────────────┐ - │ col │ - │ --- │ - │ type: nullable List[nullable FixedSizeList[nullable f64; 2]] │ - ╞══════════════════════════════════════════════════════════════╡ - │ [[1.0, 2.0], [3.0, 4.0]] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [[5.0, 6.0]] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ null │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [[7.0, null], [9.0, 10.0]] │ - └──────────────────────────────────────────────────────────────┘ + ┌────────────────────────────────────────┐ + │ col │ + │ --- │ + │ type: List(FixedSizeList(2 x Float64)) │ + ╞════════════════════════════════════════╡ + │ [[1.0, 2.0], [3.0, 4.0]] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [[5.0, 6.0]] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ null │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [[7.0, null], [9.0, 10.0]] │ + └────────────────────────────────────────┘ "); } @@ -64,23 +64,23 @@ fn add_one_to_leaves() { insta::assert_snapshot!( format!("{}", DisplayRB(result.clone())) , @r" - ┌──────────────────────────────────────────────────────────────┐ - │ col │ - │ --- │ - │ type: nullable List[nullable FixedSizeList[nullable f64; 2]] │ - ╞══════════════════════════════════════════════════════════════╡ - │ [[2.0, 3.0], [4.0, 5.0]] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [[6.0, 7.0]] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ null │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [[8.0, null], [10.0, 11.0]] │ - └──────────────────────────────────────────────────────────────┘ + ┌────────────────────────────────────────┐ + │ col │ + │ --- │ + │ type: List(FixedSizeList(2 x Float64)) │ + ╞════════════════════════════════════════╡ + │ [[2.0, 3.0], [4.0, 5.0]] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [[6.0, 7.0]] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ null │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [[8.0, null], [10.0, 11.0]] │ + └────────────────────────────────────────┘ " ); } @@ -101,23 +101,23 @@ fn convert_to_f32() { let result = pipeline.transform(&array).unwrap(); insta::assert_snapshot!(DisplayRB(result.clone()), @r" - ┌──────────────────────────────────────────────────────────────┐ - │ col │ - │ --- │ - │ type: nullable List[nullable FixedSizeList[nullable f32; 2]] │ - ╞══════════════════════════════════════════════════════════════╡ - │ [[1.0, 2.0], [3.0, 4.0]] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [[5.0, 6.0]] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ null │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [[7.0, null], [9.0, 10.0]] │ - └──────────────────────────────────────────────────────────────┘ + ┌────────────────────────────────────────┐ + │ col │ + │ --- │ + │ type: List(FixedSizeList(2 x Float32)) │ + ╞════════════════════════════════════════╡ + │ [[1.0, 2.0], [3.0, 4.0]] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [[5.0, 6.0]] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ null │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [[7.0, null], [9.0, 10.0]] │ + └────────────────────────────────────────┘ "); } @@ -136,23 +136,23 @@ fn replace_nulls() { let result = pipeline.transform(&array).unwrap(); insta::assert_snapshot!(format!("{}", DisplayRB(result.clone())), @r" - ┌─────────────────────────────────────────────────────┐ - │ col │ - │ --- │ - │ type: nullable List[nullable FixedSizeList[f64; 2]] │ - ╞═════════════════════════════════════════════════════╡ - │ [[1.0, 2.0], [3.0, 4.0]] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [[5.0, 6.0]] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ null │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [[7.0, 1337.0], [9.0, 10.0]] │ - └─────────────────────────────────────────────────────┘ + ┌─────────────────────────────────────────────────┐ + │ col │ + │ --- │ + │ type: List(FixedSizeList(2 x non-null Float64)) │ + ╞═════════════════════════════════════════════════╡ + │ [[1.0, 2.0], [3.0, 4.0]] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [[5.0, 6.0]] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ null │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [[7.0, 1337.0], [9.0, 10.0]] │ + └─────────────────────────────────────────────────┘ "); } @@ -166,25 +166,25 @@ fn test_flatten_single_element() { let result = pipeline.transform(&array).unwrap(); insta::assert_snapshot!( - format!("{}", DisplayRB(result.clone())), @" - ┌─────────────────────────────────────────┐ - │ col │ - │ --- │ - │ type: nullable List[nullable Struct[2]] │ - ╞═════════════════════════════════════════╡ - │ [{x: 1.0, y: 2.0}, {x: 3.0, y: 4.0}] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [{x: 5.0, y: 6.0}] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ null │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [{x: 7.0, y: null}, {x: 9.0, y: 10.0}] │ - └─────────────────────────────────────────┘ - " + format!("{}", DisplayRB(result.clone())), @r#" + ┌────────────────────────────────────────────────┐ + │ col │ + │ --- │ + │ type: List(Struct("x": Float64, "y": Float64)) │ + ╞════════════════════════════════════════════════╡ + │ [{x: 1.0, y: 2.0}, {x: 3.0, y: 4.0}] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [{x: 5.0, y: 6.0}] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ null │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [{x: 7.0, y: null}, {x: 9.0, y: 10.0}] │ + └────────────────────────────────────────────────┘ + "# ); } @@ -251,26 +251,26 @@ fn test_flatten_multiple_elements() { .unwrap(); insta::assert_snapshot!( - format!("{}", DisplayRB(result.clone())), @" - ┌───────────────────────────────────┐ - │ col │ - │ --- │ - │ type: nullable List[nullable i32] │ - ╞═══════════════════════════════════╡ - │ [1, 2, 3, 4] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [5, null, 6, 7, 8] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [9] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ null │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [10, 11] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [32, 33, 34] │ - └───────────────────────────────────┘ + format!("{}", DisplayRB(result.clone())), @r" + ┌────────────────────┐ + │ col │ + │ --- │ + │ type: List(Int32) │ + ╞════════════════════╡ + │ [1, 2, 3, 4] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [5, null, 6, 7, 8] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [9] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ null │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [10, 11] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [32, 33, 34] │ + └────────────────────┘ " ); } @@ -337,11 +337,11 @@ fn test_row_major_to_col_major() { .unwrap(); insta::assert_snapshot!( - format!("{}", DisplayRB(result.clone())), @" + format!("{}", DisplayRB(result.clone())), @r" ┌──────────────────────────────────────────────────┐ │ col │ │ --- │ - │ type: nullable FixedSizeList[nullable i32; 12] │ + │ type: FixedSizeList(12 x Int32) │ ╞══════════════════════════════════════════════════╡ │ [1, 4, 7, 10, null, 5, 8, 11, 3, 6, null, 12] │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ @@ -365,23 +365,23 @@ fn test_map_list_nullability() { let result: ListArray = pipeline.transform(&array).unwrap(); insta::assert_snapshot!(format!("{}", DisplayRB(result.clone())), @r" - ┌──────────────────────────────────────────────────────────────┐ - │ col │ - │ --- │ - │ type: nullable List[nullable FixedSizeList[nullable f64; 2]] │ - ╞══════════════════════════════════════════════════════════════╡ - │ [[1.0, 2.0], [3.0, 4.0]] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [[5.0, 6.0]] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ null │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [[7.0, null], [9.0, 10.0]] │ - └──────────────────────────────────────────────────────────────┘ + ┌────────────────────────────────────────┐ + │ col │ + │ --- │ + │ type: List(FixedSizeList(2 x Float64)) │ + ╞════════════════════════════════════════╡ + │ [[1.0, 2.0], [3.0, 4.0]] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [[5.0, 6.0]] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ null │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [[7.0, null], [9.0, 10.0]] │ + └────────────────────────────────────────┘ "); } @@ -394,16 +394,16 @@ fn test_map_list_outer_nullability() { let result: ListArray = pipeline.transform(&array).unwrap(); - insta::assert_snapshot!(format!("{}", DisplayRB(result.clone())), @" - ┌──────────────────────────┐ - │ col │ - │ --- │ - │ type: nullable List[f32] │ - ╞══════════════════════════╡ - │ [1.0, 2.0] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [3.0, 4.0, 5.0] │ - └──────────────────────────┘ + insta::assert_snapshot!(format!("{}", DisplayRB(result.clone())), @r" + ┌──────────────────────────────┐ + │ col │ + │ --- │ + │ type: List(non-null Float32) │ + ╞══════════════════════════════╡ + │ [1.0, 2.0] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [3.0, 4.0, 5.0] │ + └──────────────────────────────┘ "); let array = fixtures::list_with_nulls(); @@ -411,15 +411,15 @@ fn test_map_list_outer_nullability() { let result: ListArray = pipeline.transform(&array).unwrap(); insta::assert_snapshot!(format!("{}", DisplayRB(result.clone())), @r" - ┌───────────────────────────────────┐ - │ col │ - │ --- │ - │ type: nullable List[nullable f32] │ - ╞═══════════════════════════════════╡ - │ [1.0, 2.0] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ null │ - └───────────────────────────────────┘ + ┌─────────────────────┐ + │ col │ + │ --- │ + │ type: List(Float32) │ + ╞═════════════════════╡ + │ [1.0, 2.0] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ null │ + └─────────────────────┘ "); } @@ -433,15 +433,15 @@ fn test_map_list_outer_nullability_identity() { let result: ListArray = pipeline.transform(&array).unwrap(); insta::assert_snapshot!(format!("{}", DisplayRB(result.clone())), @r" - ┌─────────────────────────┐ - │ col │ - │ --- │ - │ type: nullable List[u8] │ - ╞═════════════════════════╡ - │ [1, 2] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [3, 4, 5] │ - └─────────────────────────┘ + ┌────────────────────────────┐ + │ col │ + │ --- │ + │ type: List(non-null UInt8) │ + ╞════════════════════════════╡ + │ [1, 2] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [3, 4, 5] │ + └────────────────────────────┘ "); } @@ -455,50 +455,50 @@ fn test_promote_inner_nulls_nested_struct() { .unwrap(); // Before: row 1 is `[null]` and row 5 is `[null, null]` - insta::assert_snapshot!(format!("{}", DisplayRB(location.clone())), @" - ┌─────────────────────────────────────────┐ - │ col │ - │ --- │ - │ type: nullable List[nullable Struct[2]] │ - ╞═════════════════════════════════════════╡ - │ [{x: 1.0, y: 2.0}] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [null] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ null │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [{x: 3.0, y: 4.0}, {x: 5.0, y: 6.0}] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [null, {x: 7.0, y: 8.0}] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [null, null] │ - └─────────────────────────────────────────┘ - "); + insta::assert_snapshot!(format!("{}", DisplayRB(location.clone())), @r#" + ┌────────────────────────────────────────────────┐ + │ col │ + │ --- │ + │ type: List(Struct("x": Float64, "y": Float64)) │ + ╞════════════════════════════════════════════════╡ + │ [{x: 1.0, y: 2.0}] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [null] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ null │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [{x: 3.0, y: 4.0}, {x: 5.0, y: 6.0}] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [null, {x: 7.0, y: 8.0}] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [null, null] │ + └────────────────────────────────────────────────┘ + "#); // After: row 1 `[null]` and row 6 `[null, null]` are promoted to outer `null`s let result = PromoteInnerNulls.transform(&location).unwrap(); - insta::assert_snapshot!(format!("{}", DisplayRB(result)), @" - ┌─────────────────────────────────────────┐ - │ col │ - │ --- │ - │ type: nullable List[nullable Struct[2]] │ - ╞═════════════════════════════════════════╡ - │ [{x: 1.0, y: 2.0}] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ null │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ null │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [{x: 3.0, y: 4.0}, {x: 5.0, y: 6.0}] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [null, {x: 7.0, y: 8.0}] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ null │ - └─────────────────────────────────────────┘ - "); + insta::assert_snapshot!(format!("{}", DisplayRB(result)), @r#" + ┌────────────────────────────────────────────────┐ + │ col │ + │ --- │ + │ type: List(Struct("x": Float64, "y": Float64)) │ + ╞════════════════════════════════════════════════╡ + │ [{x: 1.0, y: 2.0}] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ null │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ null │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [{x: 3.0, y: 4.0}, {x: 5.0, y: 6.0}] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [null, {x: 7.0, y: 8.0}] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ null │ + └────────────────────────────────────────────────┘ + "#); } diff --git a/crates/store/re_arrow_combinators/tests/util.rs b/crates/store/re_arrow_combinators/tests/util.rs index 558853dbd500..78f0419fdb1f 100644 --- a/crates/store/re_arrow_combinators/tests/util.rs +++ b/crates/store/re_arrow_combinators/tests/util.rs @@ -45,27 +45,27 @@ pub mod fixtures { #[test] fn example_nested_struct_column() { let array = nested_struct_column(); - insta::assert_snapshot!(format!("{}", super::DisplayRB(array)), @" - ┌──────────────────────────────────────────────────────────────┐ - │ col │ - │ --- │ - │ type: nullable List[nullable Struct[1]] │ - ╞══════════════════════════════════════════════════════════════╡ - │ [{location: {x: 1.0, y: 2.0}}] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [{location: null}] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ null │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [{location: {x: 3.0, y: 4.0}}, {location: {x: 5.0, y: 6.0}}] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [null, {location: {x: 7.0, y: 8.0}}] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [{location: null}, {location: null}] │ - └──────────────────────────────────────────────────────────────┘ - "); + insta::assert_snapshot!(format!("{}", super::DisplayRB(array)), @r#" + ┌────────────────────────────────────────────────────────────────────┐ + │ col │ + │ --- │ + │ type: List(Struct("location": Struct("x": Float64, "y": Float64))) │ + ╞════════════════════════════════════════════════════════════════════╡ + │ [{location: {x: 1.0, y: 2.0}}] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [{location: null}] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ null │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [{location: {x: 3.0, y: 4.0}}, {location: {x: 5.0, y: 6.0}}] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [null, {location: {x: 7.0, y: 8.0}}] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [{location: null}, {location: null}] │ + └────────────────────────────────────────────────────────────────────┘ + "#); } pub fn nested_struct_column() -> ListArray { @@ -138,25 +138,25 @@ pub mod fixtures { #[test] fn example_nested_list_struct_column() { let array = nested_list_struct_column(); - insta::assert_snapshot!(format!("{}", super::DisplayRB(array)), @" - ┌───────────────────────────────────────────────────┐ - │ col │ - │ --- │ - │ type: nullable List[nullable Struct[1]] │ - ╞═══════════════════════════════════════════════════╡ - │ [{poses: [{x: 1.0, y: 2.0}, {x: 3.0, y: 4.0}]}] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [{poses: [{x: 5.0, y: 6.0}]}] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [{poses: []}] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ null │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [{poses: [{x: 7.0, y: null}, {x: 9.0, y: 10.0}]}] │ - └───────────────────────────────────────────────────┘ - "); + insta::assert_snapshot!(format!("{}", super::DisplayRB(array)), @r#" + ┌─────────────────────────────────────────────────────────────────────────────────────────┐ + │ col │ + │ --- │ + │ type: List(Struct("poses": non-null List(non-null Struct("x": Float64, "y": Float64)))) │ + ╞═════════════════════════════════════════════════════════════════════════════════════════╡ + │ [{poses: [{x: 1.0, y: 2.0}, {x: 3.0, y: 4.0}]}] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [{poses: [{x: 5.0, y: 6.0}]}] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [{poses: []}] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ null │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [{poses: [{x: 7.0, y: null}, {x: 9.0, y: 10.0}]}] │ + └─────────────────────────────────────────────────────────────────────────────────────────┘ + "#); } pub fn nested_list_struct_column() -> ListArray { @@ -239,21 +239,21 @@ pub mod fixtures { #[test] fn example_nested_string_struct_column() { let list_array = nested_string_struct_column(); - insta::assert_snapshot!(super::DisplayRB(list_array.clone()), @r" - ┌───────────────────────────────────────────────────────┐ - │ col │ - │ --- │ - │ type: nullable List[nullable Struct[1]] │ - ╞═══════════════════════════════════════════════════════╡ - │ [{data: {names: alice, colors: red}}] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [null] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ null │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [{data: null}, {data: {names: dave, colors: yellow}}] │ - └───────────────────────────────────────────────────────┘ - "); + insta::assert_snapshot!(super::DisplayRB(list_array.clone()), @r#" + ┌───────────────────────────────────────────────────────────────────┐ + │ col │ + │ --- │ + │ type: List(Struct("data": Struct("names": Utf8, "colors": Utf8))) │ + ╞═══════════════════════════════════════════════════════════════════╡ + │ [{data: {names: alice, colors: red}}] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [null] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ null │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [{data: null}, {data: {names: dave, colors: yellow}}] │ + └───────────────────────────────────────────────────────────────────┘ + "#); } /// Creates a nested struct column with string values from an underlying shared buffer. @@ -344,15 +344,15 @@ pub mod fixtures { fn example_list_not_nullable() { let array = list_not_nullable(); insta::assert_snapshot!(format!("{}", super::DisplayRB(array)), @r" - ┌─────────────────────────┐ - │ col │ - │ --- │ - │ type: nullable List[u8] │ - ╞═════════════════════════╡ - │ [1, 2] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [3, 4, 5] │ - └─────────────────────────┘ + ┌────────────────────────────┐ + │ col │ + │ --- │ + │ type: List(non-null UInt8) │ + ╞════════════════════════════╡ + │ [1, 2] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [3, 4, 5] │ + └────────────────────────────┘ "); } @@ -372,15 +372,15 @@ pub mod fixtures { fn example_list_with_nulls() { let array = list_with_nulls(); insta::assert_snapshot!(format!("{}", super::DisplayRB(array)), @r" - ┌──────────────────────────────────┐ - │ col │ - │ --- │ - │ type: nullable List[nullable u8] │ - ╞══════════════════════════════════╡ - │ [1, 2] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ null │ - └──────────────────────────────────┘ + ┌───────────────────┐ + │ col │ + │ --- │ + │ type: List(UInt8) │ + ╞═══════════════════╡ + │ [1, 2] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ null │ + └───────────────────┘ "); } diff --git a/crates/store/re_chunk/src/chunk.rs b/crates/store/re_chunk/src/chunk.rs index 983095ec7629..0c65667d0a52 100644 --- a/crates/store/re_chunk/src/chunk.rs +++ b/crates/store/re_chunk/src/chunk.rs @@ -9,7 +9,7 @@ use arrow::array::{ use arrow::buffer::{NullBuffer as ArrowNullBuffer, ScalarBuffer as ArrowScalarBuffer}; use itertools::{Either, Itertools as _, izip}; use nohash_hasher::IntMap; -use re_arrow_util::{ArrowArrayDowncastRef as _, DisplayDataType, widen_binary_arrays}; +use re_arrow_util::{ArrowArrayDowncastRef as _, widen_binary_arrays}; use re_byte_size::SizeBytes as _; use re_log::debug_assert; use re_log_types::{ @@ -787,7 +787,7 @@ pub enum TimeColumnError { ContainsNulls, #[error("Unsupported data type : {0}")] - UnsupportedDataType(DisplayDataType), + UnsupportedDataType(arrow::datatypes::DataType), } impl Chunk { @@ -1164,7 +1164,7 @@ impl TimeColumn { Ok((times.values().clone(), times.nulls().cloned())) } else { Err(TimeColumnError::UnsupportedDataType( - array.data_type().clone().into(), + array.data_type().clone(), )) } } diff --git a/crates/store/re_chunk/tests/snapshots/formatting__format_chunk.snap b/crates/store/re_chunk/tests/snapshots/formatting__format_chunk.snap index 97c0a8a71014..439508f85827 100644 --- a/crates/store/re_chunk/tests/snapshots/formatting__format_chunk.snap +++ b/crates/store/re_chunk/tests/snapshots/formatting__format_chunk.snap @@ -2,22 +2,22 @@ source: crates/store/re_chunk/tests/formatting.rs expression: "format!(\"{:240}\", chunk)" --- -┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ METADATA: │ -│ * entity_path: /this/that │ -│ * id: chunk_0000000000661EFDf2e3b19f7c045f15 │ +┌────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ METADATA: │ +│ * entity_path: /this/that │ +│ * id: chunk_0000000000661EFDf2e3b19f7c045f15 │ │ * version: [**REDACTED**] │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ ┌──────────────────────────────────────────────┬──────────────────────┬───────────────────────────────┬───────────────────────────────────┬────────────────────────────────────┐ │ -│ │ RowId ┆ frame_nr ┆ log_time ┆ my_index ┆ example.MyPoints:colors │ │ -│ │ --- ┆ --- ┆ --- ┆ --- ┆ --- │ │ -│ │ type: FixedSizeBinary[16] ┆ type: nullable i64 ┆ type: nullable Timestamp(ns) ┆ type: nullable List[nullable u64] ┆ type: nullable List[nullable u32] │ │ -│ │ ARROW:extension:metadata: ┆ index_name: frame_nr ┆ index_name: log_time ┆ component: my_index ┆ archetype: example.MyPoints │ │ -│ │ {"namespace":"row"} ┆ is_sorted: true ┆ is_sorted: true ┆ component_type: example.MyIndex ┆ component: example.MyPoints:colors │ │ -│ │ ARROW:extension:name: TUID ┆ kind: index ┆ kind: index ┆ kind: data ┆ component_type: example.MyColor │ │ -│ │ is_sorted: true ┆ ┆ ┆ ┆ kind: data │ │ -│ │ kind: control ┆ ┆ ┆ ┆ │ │ -│ ╞══════════════════════════════════════════════╪══════════════════════╪═══════════════════════════════╪═══════════════════════════════════╪════════════════════════════════════╡ │ -│ │ row_0000000067816A6Bb4b8c1254d40007b ┆ 1 ┆ 2025-01-10T18:43:42.123456789 ┆ [0, 1, 2] ┆ [0, 1, 2] │ │ -│ └──────────────────────────────────────────────┴──────────────────────┴───────────────────────────────┴───────────────────────────────────┴────────────────────────────────────┘ │ -└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ ┌──────────────────────────────────────────────┬──────────────────────┬───────────────────────────────┬─────────────────────────────────┬────────────────────────────────────┐ │ +│ │ RowId ┆ frame_nr ┆ log_time ┆ my_index ┆ example.MyPoints:colors │ │ +│ │ --- ┆ --- ┆ --- ┆ --- ┆ --- │ │ +│ │ type: non-null FixedSizeBinary(16) ┆ type: Int64 ┆ type: Timestamp(ns) ┆ type: List(UInt64) ┆ type: List(UInt32) │ │ +│ │ ARROW:extension:metadata: ┆ index_name: frame_nr ┆ index_name: log_time ┆ component: my_index ┆ archetype: example.MyPoints │ │ +│ │ {"namespace":"row"} ┆ is_sorted: true ┆ is_sorted: true ┆ component_type: example.MyIndex ┆ component: example.MyPoints:colors │ │ +│ │ ARROW:extension:name: TUID ┆ kind: index ┆ kind: index ┆ kind: data ┆ component_type: example.MyColor │ │ +│ │ is_sorted: true ┆ ┆ ┆ ┆ kind: data │ │ +│ │ kind: control ┆ ┆ ┆ ┆ │ │ +│ ╞══════════════════════════════════════════════╪══════════════════════╪═══════════════════════════════╪═════════════════════════════════╪════════════════════════════════════╡ │ +│ │ row_0000000067816A6Bb4b8c1254d40007b ┆ 1 ┆ 2025-01-10T18:43:42.123456789 ┆ [0, 1, 2] ┆ [0, 1, 2] │ │ +│ └──────────────────────────────────────────────┴──────────────────────┴───────────────────────────────┴─────────────────────────────────┴────────────────────────────────────┘ │ +└────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ diff --git a/crates/store/re_chunk/tests/snapshots/formatting__format_chunk_redacted.snap b/crates/store/re_chunk/tests/snapshots/formatting__format_chunk_redacted.snap index d1f307415dcf..24ac433c5def 100644 --- a/crates/store/re_chunk/tests/snapshots/formatting__format_chunk_redacted.snap +++ b/crates/store/re_chunk/tests/snapshots/formatting__format_chunk_redacted.snap @@ -2,22 +2,22 @@ source: crates/store/re_chunk/tests/formatting.rs expression: "format!(\"{:-240}\", chunk)" --- -┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ METADATA: │ -│ * entity_path: /this/that │ -│ * id: [**REDACTED**] │ -│ * version: [**REDACTED**] │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ ┌──────────────────────────────────────────────┬──────────────────────┬───────────────────────────────┬───────────────────────────────────┬────────────────────────────────────┐ │ -│ │ RowId ┆ frame_nr ┆ log_time ┆ my_index ┆ example.MyPoints:colors │ │ -│ │ --- ┆ --- ┆ --- ┆ --- ┆ --- │ │ -│ │ type: FixedSizeBinary[16] ┆ type: nullable i64 ┆ type: nullable Timestamp(ns) ┆ type: nullable List[nullable u64] ┆ type: nullable List[nullable u32] │ │ -│ │ ARROW:extension:metadata: ┆ index_name: frame_nr ┆ index_name: log_time ┆ component: my_index ┆ archetype: example.MyPoints │ │ -│ │ {"namespace":"row"} ┆ is_sorted: true ┆ is_sorted: true ┆ component_type: example.MyIndex ┆ component: example.MyPoints:colors │ │ -│ │ ARROW:extension:name: TUID ┆ kind: index ┆ kind: index ┆ kind: data ┆ component_type: example.MyColor │ │ -│ │ is_sorted: true ┆ ┆ ┆ ┆ kind: data │ │ -│ │ kind: control ┆ ┆ ┆ ┆ │ │ -│ ╞══════════════════════════════════════════════╪══════════════════════╪═══════════════════════════════╪═══════════════════════════════════╪════════════════════════════════════╡ │ -│ │ row_[**REDACTED**] ┆ 1 ┆ 2025-01-10T18:43:42.123456789 ┆ [0, 1, 2] ┆ [0, 1, 2] │ │ -│ └──────────────────────────────────────────────┴──────────────────────┴───────────────────────────────┴───────────────────────────────────┴────────────────────────────────────┘ │ -└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +┌────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ METADATA: │ +│ * entity_path: /this/that │ +│ * id: [**REDACTED**] │ +│ * version: [**REDACTED**] │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ ┌──────────────────────────────────────────────┬──────────────────────┬───────────────────────────────┬─────────────────────────────────┬────────────────────────────────────┐ │ +│ │ RowId ┆ frame_nr ┆ log_time ┆ my_index ┆ example.MyPoints:colors │ │ +│ │ --- ┆ --- ┆ --- ┆ --- ┆ --- │ │ +│ │ type: non-null FixedSizeBinary(16) ┆ type: Int64 ┆ type: Timestamp(ns) ┆ type: List(UInt64) ┆ type: List(UInt32) │ │ +│ │ ARROW:extension:metadata: ┆ index_name: frame_nr ┆ index_name: log_time ┆ component: my_index ┆ archetype: example.MyPoints │ │ +│ │ {"namespace":"row"} ┆ is_sorted: true ┆ is_sorted: true ┆ component_type: example.MyIndex ┆ component: example.MyPoints:colors │ │ +│ │ ARROW:extension:name: TUID ┆ kind: index ┆ kind: index ┆ kind: data ┆ component_type: example.MyColor │ │ +│ │ is_sorted: true ┆ ┆ ┆ ┆ kind: data │ │ +│ │ kind: control ┆ ┆ ┆ ┆ │ │ +│ ╞══════════════════════════════════════════════╪══════════════════════╪═══════════════════════════════╪═════════════════════════════════╪════════════════════════════════════╡ │ +│ │ row_[**REDACTED**] ┆ 1 ┆ 2025-01-10T18:43:42.123456789 ┆ [0, 1, 2] ┆ [0, 1, 2] │ │ +│ └──────────────────────────────────────────────┴──────────────────────┴───────────────────────────────┴─────────────────────────────────┴────────────────────────────────────┘ │ +└────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ diff --git a/crates/store/re_chunk_store/src/writes.rs b/crates/store/re_chunk_store/src/writes.rs index 1bd6835d51fd..a224dc45dc45 100644 --- a/crates/store/re_chunk_store/src/writes.rs +++ b/crates/store/re_chunk_store/src/writes.rs @@ -86,8 +86,8 @@ impl ChunkStore { "Component '{}' on entity '{}' changed type from {} to {}", descr.component, descr.entity_path, - re_arrow_util::format_data_type(&previous.2), - re_arrow_util::format_data_type(&inner_datatype) + previous.2, + inner_datatype ); } } diff --git a/crates/store/re_chunk_store/tests/snapshots/formatting__format_chunk_store.snap b/crates/store/re_chunk_store/tests/snapshots/formatting__format_chunk_store.snap index 620a2254ed89..a677cd05e06b 100644 --- a/crates/store/re_chunk_store/tests/snapshots/formatting__format_chunk_store.snap +++ b/crates/store/re_chunk_store/tests/snapshots/formatting__format_chunk_store.snap @@ -14,25 +14,25 @@ ChunkStore { physical chunks: [ chunk_0000000000661EFDf2e3b19f7c045f15 (status:loaded static:no) origin: (cannot be re-fetched) - ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ - │ METADATA: │ - │ * entity_path: /this/that │ - │ * id: chunk_0000000000661EFDf2e3b19f7c045f15 │ + ┌───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ METADATA: │ + │ * entity_path: /this/that │ + │ * id: chunk_0000000000661EFDf2e3b19f7c045f15 │ │ * version: [**REDACTED**] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ ┌─────────────────────────────────────────────┬──────────────────────┬───────────────────────────────┬───────────────────────────────────┬────────────────────────────────────┐ │ - │ │ RowId ┆ frame_nr ┆ log_time ┆ my_index ┆ example.MyPoints:colors │ │ - │ │ --- ┆ --- ┆ --- ┆ --- ┆ --- │ │ - │ │ type: FixedSizeBinary[16] ┆ type: nullable i64 ┆ type: nullable Timestamp(ns) ┆ type: nullable List[nullable u64] ┆ type: nullable List[nullable u32] │ │ - │ │ ARROW:extension:metadata: ┆ index_name: frame_nr ┆ index_name: log_time ┆ component: my_index ┆ archetype: example.MyPoints │ │ - │ │ {"namespace":"row"} ┆ is_sorted: true ┆ is_sorted: true ┆ component_type: example.MyIndex ┆ component: example.MyPoints:colors │ │ - │ │ ARROW:extension:name: TUID ┆ kind: index ┆ kind: index ┆ kind: data ┆ component_type: example.MyColor │ │ - │ │ is_sorted: true ┆ ┆ ┆ ┆ kind: data │ │ - │ │ kind: control ┆ ┆ ┆ ┆ │ │ - │ ╞═════════════════════════════════════════════╪══════════════════════╪═══════════════════════════════╪═══════════════════════════════════╪════════════════════════════════════╡ │ - │ │ row_0000000067816A6Bb4b8c1254d40007b ┆ 1 ┆ 2025-01-10T18:43:42.123456789 ┆ [0, 1, 2] ┆ [0, 1, 2] │ │ - │ └─────────────────────────────────────────────┴──────────────────────┴───────────────────────────────┴───────────────────────────────────┴────────────────────────────────────┘ │ - └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ ┌─────────────────────────────────────────────┬──────────────────────┬───────────────────────────────┬─────────────────────────────────┬────────────────────────────────────┐ │ + │ │ RowId ┆ frame_nr ┆ log_time ┆ my_index ┆ example.MyPoints:colors │ │ + │ │ --- ┆ --- ┆ --- ┆ --- ┆ --- │ │ + │ │ type: non-null FixedSizeBinary(16) ┆ type: Int64 ┆ type: Timestamp(ns) ┆ type: List(UInt64) ┆ type: List(UInt32) │ │ + │ │ ARROW:extension:metadata: ┆ index_name: frame_nr ┆ index_name: log_time ┆ component: my_index ┆ archetype: example.MyPoints │ │ + │ │ {"namespace":"row"} ┆ is_sorted: true ┆ is_sorted: true ┆ component_type: example.MyIndex ┆ component: example.MyPoints:colors │ │ + │ │ ARROW:extension:name: TUID ┆ kind: index ┆ kind: index ┆ kind: data ┆ component_type: example.MyColor │ │ + │ │ is_sorted: true ┆ ┆ ┆ ┆ kind: data │ │ + │ │ kind: control ┆ ┆ ┆ ┆ │ │ + │ ╞═════════════════════════════════════════════╪══════════════════════╪═══════════════════════════════╪═════════════════════════════════╪════════════════════════════════════╡ │ + │ │ row_0000000067816A6Bb4b8c1254d40007b ┆ 1 ┆ 2025-01-10T18:43:42.123456789 ┆ [0, 1, 2] ┆ [0, 1, 2] │ │ + │ └─────────────────────────────────────────────┴──────────────────────┴───────────────────────────────┴─────────────────────────────────┴────────────────────────────────────┘ │ + └───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ ] virtual chunks: [ ] diff --git a/crates/store/re_data_loader/src/loader_mcap/tests/foxglove/snapshots/re_data_loader__loader_mcap__tests__foxglove__test_compressed_video__foxglove_compressed_video.snap b/crates/store/re_data_loader/src/loader_mcap/tests/foxglove/snapshots/re_data_loader__loader_mcap__tests__foxglove__test_compressed_video__foxglove_compressed_video.snap index 5b593caed84d..41772122cb10 100644 --- a/crates/store/re_data_loader/src/loader_mcap/tests/foxglove/snapshots/re_data_loader__loader_mcap__tests__foxglove__test_compressed_video__foxglove_compressed_video.snap +++ b/crates/store/re_data_loader/src/loader_mcap/tests/foxglove/snapshots/re_data_loader__loader_mcap__tests__foxglove__test_compressed_video__foxglove_compressed_video.snap @@ -2,37 +2,36 @@ source: crates/store/re_data_loader/src/loader_mcap/tests/foxglove/test_compressed_video.rs expression: "format!(\"{:-240}\", chunk)" --- -┌───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ METADATA: │ -│ * entity_path: /compressed_video │ -│ * id: [**REDACTED**] │ -│ * version: [**REDACTED**] │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ ┌────────────────────────────────┬──────────────────────────────┬────────────────────────────────┬───────────────────────────────┬────────────────────────────────┬────────────────────────────────┬────────────────────────────────┐ │ -│ │ RowId ┆ message_log_time ┆ message_publish_time ┆ timestamp ┆ CoordinateFrame:frame ┆ VideoStream:codec ┆ VideoStream:sample │ │ -│ │ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- │ │ -│ │ type: FixedSizeBinary[16] ┆ type: nullable Duration(ns) ┆ type: nullable Duration(ns) ┆ type: nullable Timestamp(ns) ┆ type: nullable List[nullable ┆ type: nullable List[nullable ┆ type: nullable List[nullable │ │ -│ │ ARROW:extension:metadata: ┆ index_name: message_log_time ┆ index_name: ┆ index_name: timestamp ┆ Utf8] ┆ u32] ┆ List[u8]] │ │ -│ │ {"namespace":"row"} ┆ is_sorted: true ┆ message_publish_time ┆ is_sorted: true ┆ archetype: CoordinateFrame ┆ archetype: VideoStream ┆ archetype: VideoStream │ │ -│ │ ARROW:extension:name: TUID ┆ kind: index ┆ is_sorted: true ┆ kind: index ┆ component: ┆ component: VideoStream:codec ┆ component: VideoStream:sample │ │ -│ │ is_sorted: true ┆ ┆ kind: index ┆ ┆ CoordinateFrame:frame ┆ component_type: VideoCodec ┆ component_type: VideoSample │ │ -│ │ kind: control ┆ ┆ ┆ ┆ component_type: ┆ kind: data ┆ kind: data │ │ -│ │ ┆ ┆ ┆ ┆ TransformFrameId ┆ ┆ │ │ -│ │ ┆ ┆ ┆ ┆ kind: data ┆ ┆ │ │ -│ ╞════════════════════════════════╪══════════════════════════════╪════════════════════════════════╪═══════════════════════════════╪════════════════════════════════╪════════════════════════════════╪════════════════════════════════╡ │ -│ │ row_[**REDACTED**] ┆ P0D ┆ P0D ┆ 1970-01-01T00:00:00 ┆ [camera_frame_image_plane] ┆ [1635148593] ┆ [[0, 0, 0, 1, 103, 100, 16, │ │ -│ │ ┆ ┆ ┆ ┆ ┆ ┆ 10, 172, 184, 143, 66, 0, 0, │ │ -│ │ ┆ ┆ ┆ ┆ ┆ ┆ 3, 0, 2, 0, 0, 3, 0, 121, 8, │ │ -│ │ ┆ ┆ ┆ ┆ ┆ ┆ 0, 0, 0, 1, 1… │ │ -│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ -│ │ row_[**REDACTED**] ┆ PT0.033333333S ┆ PT0.033333333S ┆ 1970-01-01T00:00:00.033333333 ┆ [camera_frame_image_plane] ┆ [1635148593] ┆ [[0, 0, 0, 1, 103, 100, 16, │ │ -│ │ ┆ ┆ ┆ ┆ ┆ ┆ 10, 172, 184, 143, 66, 0, 0, │ │ -│ │ ┆ ┆ ┆ ┆ ┆ ┆ 3, 0, 2, 0, 0, 3, 0, 121, 8, │ │ -│ │ ┆ ┆ ┆ ┆ ┆ ┆ 0, 0, 0, 1, 1… │ │ -│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ -│ │ row_[**REDACTED**] ┆ PT0.066666666S ┆ PT0.066666666S ┆ 1970-01-01T00:00:00.066666666 ┆ [camera_frame_image_plane] ┆ [1635148593] ┆ [[0, 0, 0, 1, 103, 100, 16, │ │ -│ │ ┆ ┆ ┆ ┆ ┆ ┆ 10, 172, 184, 143, 66, 0, 0, │ │ -│ │ ┆ ┆ ┆ ┆ ┆ ┆ 3, 0, 2, 0, 0, 3, 0, 121, 8, │ │ -│ │ ┆ ┆ ┆ ┆ ┆ ┆ 0, 0, 0, 1, 1… │ │ -│ └────────────────────────────────┴──────────────────────────────┴────────────────────────────────┴───────────────────────────────┴────────────────────────────────┴────────────────────────────────┴────────────────────────────────┘ │ -└───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ METADATA: │ +│ * entity_path: /compressed_video │ +│ * id: [**REDACTED**] │ +│ * version: [**REDACTED**] │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ ┌────────────────────────────────┬──────────────────────────────┬────────────────────────────────┬───────────────────────────────┬────────────────────────────────┬──────────────────────────────┬────────────────────────────────┐ │ +│ │ RowId ┆ message_log_time ┆ message_publish_time ┆ timestamp ┆ CoordinateFrame:frame ┆ VideoStream:codec ┆ VideoStream:sample │ │ +│ │ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- │ │ +│ │ type: non-null ┆ type: Duration(ns) ┆ type: Duration(ns) ┆ type: Timestamp(ns) ┆ type: List(Utf8) ┆ type: List(UInt32) ┆ type: List(List(non-null │ │ +│ │ FixedSizeBinary(16) ┆ index_name: message_log_time ┆ index_name: ┆ index_name: timestamp ┆ archetype: CoordinateFrame ┆ archetype: VideoStream ┆ UInt8)) │ │ +│ │ ARROW:extension:metadata: ┆ is_sorted: true ┆ message_publish_time ┆ is_sorted: true ┆ component: ┆ component: VideoStream:codec ┆ archetype: VideoStream │ │ +│ │ {"namespace":"row"} ┆ kind: index ┆ is_sorted: true ┆ kind: index ┆ CoordinateFrame:frame ┆ component_type: VideoCodec ┆ component: VideoStream:sample │ │ +│ │ ARROW:extension:name: TUID ┆ ┆ kind: index ┆ ┆ component_type: ┆ kind: data ┆ component_type: VideoSample │ │ +│ │ is_sorted: true ┆ ┆ ┆ ┆ TransformFrameId ┆ ┆ kind: data │ │ +│ │ kind: control ┆ ┆ ┆ ┆ kind: data ┆ ┆ │ │ +│ ╞════════════════════════════════╪══════════════════════════════╪════════════════════════════════╪═══════════════════════════════╪════════════════════════════════╪══════════════════════════════╪════════════════════════════════╡ │ +│ │ row_[**REDACTED**] ┆ P0D ┆ P0D ┆ 1970-01-01T00:00:00 ┆ [camera_frame_image_plane] ┆ [1635148593] ┆ [[0, 0, 0, 1, 103, 100, 16, │ │ +│ │ ┆ ┆ ┆ ┆ ┆ ┆ 10, 172, 184, 143, 66, 0, 0, │ │ +│ │ ┆ ┆ ┆ ┆ ┆ ┆ 3, 0, 2, 0, 0, 3, 0, 121, 8, │ │ +│ │ ┆ ┆ ┆ ┆ ┆ ┆ 0, 0, 0, 1, 1… │ │ +│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ +│ │ row_[**REDACTED**] ┆ PT0.033333333S ┆ PT0.033333333S ┆ 1970-01-01T00:00:00.033333333 ┆ [camera_frame_image_plane] ┆ [1635148593] ┆ [[0, 0, 0, 1, 103, 100, 16, │ │ +│ │ ┆ ┆ ┆ ┆ ┆ ┆ 10, 172, 184, 143, 66, 0, 0, │ │ +│ │ ┆ ┆ ┆ ┆ ┆ ┆ 3, 0, 2, 0, 0, 3, 0, 121, 8, │ │ +│ │ ┆ ┆ ┆ ┆ ┆ ┆ 0, 0, 0, 1, 1… │ │ +│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ +│ │ row_[**REDACTED**] ┆ PT0.066666666S ┆ PT0.066666666S ┆ 1970-01-01T00:00:00.066666666 ┆ [camera_frame_image_plane] ┆ [1635148593] ┆ [[0, 0, 0, 1, 103, 100, 16, │ │ +│ │ ┆ ┆ ┆ ┆ ┆ ┆ 10, 172, 184, 143, 66, 0, 0, │ │ +│ │ ┆ ┆ ┆ ┆ ┆ ┆ 3, 0, 2, 0, 0, 3, 0, 121, 8, │ │ +│ │ ┆ ┆ ┆ ┆ ┆ ┆ 0, 0, 0, 1, 1… │ │ +│ └────────────────────────────────┴──────────────────────────────┴────────────────────────────────┴───────────────────────────────┴────────────────────────────────┴──────────────────────────────┴────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ diff --git a/crates/store/re_data_loader/src/loader_mcap/tests/foxglove/snapshots/re_data_loader__loader_mcap__tests__foxglove__test_frame_transforms__foxglove_frame_transform.snap b/crates/store/re_data_loader/src/loader_mcap/tests/foxglove/snapshots/re_data_loader__loader_mcap__tests__foxglove__test_frame_transforms__foxglove_frame_transform.snap index 1bb0009eb498..0b8e6a3c20c5 100644 --- a/crates/store/re_data_loader/src/loader_mcap/tests/foxglove/snapshots/re_data_loader__loader_mcap__tests__foxglove__test_frame_transforms__foxglove_frame_transform.snap +++ b/crates/store/re_data_loader/src/loader_mcap/tests/foxglove/snapshots/re_data_loader__loader_mcap__tests__foxglove__test_frame_transforms__foxglove_frame_transform.snap @@ -11,14 +11,14 @@ expression: "format!(\"{:-240}\", chunk)" │ ┌───────────────────────────┬───────────────────────────┬───────────────────────────┬───────────────────────────┬───────────────────────────┬───────────────────────────┬───────────────────────────┬───────────────────────────┐ │ │ │ RowId ┆ message_log_time ┆ message_publish_time ┆ timestamp ┆ Transform3D:child_frame ┆ Transform3D:parent_frame ┆ Transform3D:quaternion ┆ Transform3D:translation │ │ │ │ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- │ │ -│ │ type: FixedSizeBinary[16] ┆ type: nullable ┆ type: nullable ┆ type: nullable ┆ type: nullable ┆ type: nullable ┆ type: nullable ┆ type: nullable │ │ -│ │ ARROW:extension:metadata: ┆ Timestamp(ns) ┆ Timestamp(ns) ┆ Timestamp(ns) ┆ List[nullable Utf8] ┆ List[nullable Utf8] ┆ List[nullable ┆ List[nullable │ │ -│ │ {"namespace":"row"} ┆ index_name: ┆ index_name: ┆ index_name: timestamp ┆ archetype: Transform3D ┆ archetype: Transform3D ┆ FixedSizeList[f32; 4]] ┆ FixedSizeList[f32; 3]] │ │ -│ │ ARROW:extension:name: ┆ message_log_time ┆ message_publish_time ┆ is_sorted: true ┆ component: ┆ component: ┆ archetype: Transform3D ┆ archetype: Transform3D │ │ -│ │ TUID ┆ is_sorted: true ┆ is_sorted: true ┆ kind: index ┆ Transform3D:child_frame ┆ Transform3D:parent_frame ┆ component: ┆ component: │ │ -│ │ is_sorted: true ┆ kind: index ┆ kind: index ┆ ┆ component_type: ┆ component_type: ┆ Transform3D:quaternion ┆ Transform3D:translation │ │ -│ │ kind: control ┆ ┆ ┆ ┆ TransformFrameId ┆ TransformFrameId ┆ component_type: ┆ component_type: │ │ -│ │ ┆ ┆ ┆ ┆ kind: data ┆ kind: data ┆ RotationQuat ┆ Translation3D │ │ +│ │ type: non-null ┆ type: Timestamp(ns) ┆ type: Timestamp(ns) ┆ type: Timestamp(ns) ┆ type: List(Utf8) ┆ type: List(Utf8) ┆ type: ┆ type: │ │ +│ │ FixedSizeBinary(16) ┆ index_name: ┆ index_name: ┆ index_name: timestamp ┆ archetype: Transform3D ┆ archetype: Transform3D ┆ List(FixedSizeList(4 x ┆ List(FixedSizeList(3 x │ │ +│ │ ARROW:extension:metadata: ┆ message_log_time ┆ message_publish_time ┆ is_sorted: true ┆ component: ┆ component: ┆ non-null Float32)) ┆ non-null Float32)) │ │ +│ │ {"namespace":"row"} ┆ is_sorted: true ┆ is_sorted: true ┆ kind: index ┆ Transform3D:child_frame ┆ Transform3D:parent_frame ┆ archetype: Transform3D ┆ archetype: Transform3D │ │ +│ │ ARROW:extension:name: ┆ kind: index ┆ kind: index ┆ ┆ component_type: ┆ component_type: ┆ component: ┆ component: │ │ +│ │ TUID ┆ ┆ ┆ ┆ TransformFrameId ┆ TransformFrameId ┆ Transform3D:quaternion ┆ Transform3D:translation │ │ +│ │ is_sorted: true ┆ ┆ ┆ ┆ kind: data ┆ kind: data ┆ component_type: ┆ component_type: │ │ +│ │ kind: control ┆ ┆ ┆ ┆ ┆ ┆ RotationQuat ┆ Translation3D │ │ │ │ ┆ ┆ ┆ ┆ ┆ ┆ kind: data ┆ kind: data │ │ │ ╞═══════════════════════════╪═══════════════════════════╪═══════════════════════════╪═══════════════════════════╪═══════════════════════════╪═══════════════════════════╪═══════════════════════════╪═══════════════════════════╡ │ │ │ row_[**REDACTED**] ┆ 2026-02-10T09:27:33.72327 ┆ 2026-02-10T09:27:33.72327 ┆ 2026-02-10T09:27:33.72327 ┆ [sensor] ┆ [world] ┆ [[0.0, 0.0, 0.0, 1.0]] ┆ [[1.0, 0.0, 0.0]] │ │ diff --git a/crates/store/re_data_loader/src/loader_mcap/tests/foxglove/snapshots/re_data_loader__loader_mcap__tests__foxglove__test_frame_transforms__foxglove_frame_transforms.snap b/crates/store/re_data_loader/src/loader_mcap/tests/foxglove/snapshots/re_data_loader__loader_mcap__tests__foxglove__test_frame_transforms__foxglove_frame_transforms.snap index ea69ce2239c8..ef736422c5d1 100644 --- a/crates/store/re_data_loader/src/loader_mcap/tests/foxglove/snapshots/re_data_loader__loader_mcap__tests__foxglove__test_frame_transforms__foxglove_frame_transforms.snap +++ b/crates/store/re_data_loader/src/loader_mcap/tests/foxglove/snapshots/re_data_loader__loader_mcap__tests__foxglove__test_frame_transforms__foxglove_frame_transforms.snap @@ -11,14 +11,14 @@ expression: "format!(\"{:-240}\", chunk)" │ ┌───────────────────────────┬───────────────────────────┬───────────────────────────┬───────────────────────────┬───────────────────────────┬───────────────────────────┬───────────────────────────┬───────────────────────────┐ │ │ │ RowId ┆ message_log_time ┆ message_publish_time ┆ timestamp ┆ Transform3D:child_frame ┆ Transform3D:parent_frame ┆ Transform3D:quaternion ┆ Transform3D:translation │ │ │ │ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- │ │ -│ │ type: FixedSizeBinary[16] ┆ type: nullable ┆ type: nullable ┆ type: nullable ┆ type: nullable ┆ type: nullable ┆ type: nullable ┆ type: nullable │ │ -│ │ ARROW:extension:metadata: ┆ Timestamp(ns) ┆ Timestamp(ns) ┆ Timestamp(ns) ┆ List[nullable Utf8] ┆ List[nullable Utf8] ┆ List[nullable ┆ List[nullable │ │ -│ │ {"namespace":"row"} ┆ index_name: ┆ index_name: ┆ index_name: timestamp ┆ archetype: Transform3D ┆ archetype: Transform3D ┆ FixedSizeList[f32; 4]] ┆ FixedSizeList[f32; 3]] │ │ -│ │ ARROW:extension:name: ┆ message_log_time ┆ message_publish_time ┆ is_sorted: true ┆ component: ┆ component: ┆ archetype: Transform3D ┆ archetype: Transform3D │ │ -│ │ TUID ┆ is_sorted: true ┆ is_sorted: true ┆ kind: index ┆ Transform3D:child_frame ┆ Transform3D:parent_frame ┆ component: ┆ component: │ │ -│ │ is_sorted: true ┆ kind: index ┆ kind: index ┆ ┆ component_type: ┆ component_type: ┆ Transform3D:quaternion ┆ Transform3D:translation │ │ -│ │ kind: control ┆ ┆ ┆ ┆ TransformFrameId ┆ TransformFrameId ┆ component_type: ┆ component_type: │ │ -│ │ ┆ ┆ ┆ ┆ kind: data ┆ kind: data ┆ RotationQuat ┆ Translation3D │ │ +│ │ type: non-null ┆ type: Timestamp(ns) ┆ type: Timestamp(ns) ┆ type: Timestamp(ns) ┆ type: List(Utf8) ┆ type: List(Utf8) ┆ type: ┆ type: │ │ +│ │ FixedSizeBinary(16) ┆ index_name: ┆ index_name: ┆ index_name: timestamp ┆ archetype: Transform3D ┆ archetype: Transform3D ┆ List(FixedSizeList(4 x ┆ List(FixedSizeList(3 x │ │ +│ │ ARROW:extension:metadata: ┆ message_log_time ┆ message_publish_time ┆ is_sorted: true ┆ component: ┆ component: ┆ non-null Float32)) ┆ non-null Float32)) │ │ +│ │ {"namespace":"row"} ┆ is_sorted: true ┆ is_sorted: true ┆ kind: index ┆ Transform3D:child_frame ┆ Transform3D:parent_frame ┆ archetype: Transform3D ┆ archetype: Transform3D │ │ +│ │ ARROW:extension:name: ┆ kind: index ┆ kind: index ┆ ┆ component_type: ┆ component_type: ┆ component: ┆ component: │ │ +│ │ TUID ┆ ┆ ┆ ┆ TransformFrameId ┆ TransformFrameId ┆ Transform3D:quaternion ┆ Transform3D:translation │ │ +│ │ is_sorted: true ┆ ┆ ┆ ┆ kind: data ┆ kind: data ┆ component_type: ┆ component_type: │ │ +│ │ kind: control ┆ ┆ ┆ ┆ ┆ ┆ RotationQuat ┆ Translation3D │ │ │ │ ┆ ┆ ┆ ┆ ┆ ┆ kind: data ┆ kind: data │ │ │ ╞═══════════════════════════╪═══════════════════════════╪═══════════════════════════╪═══════════════════════════╪═══════════════════════════╪═══════════════════════════╪═══════════════════════════╪═══════════════════════════╡ │ │ │ row_[**REDACTED**] ┆ 2026-02-10T09:27:33.72327 ┆ 2026-02-10T09:27:33.72327 ┆ 2026-02-10T09:27:33.72327 ┆ [sensor] ┆ [world] ┆ [[0.0, 0.0, 0.0, 1.0]] ┆ [[1.0, 0.0, 0.0]] │ │ diff --git a/crates/store/re_data_loader/src/loader_mcap/tests/foxglove/snapshots/re_data_loader__loader_mcap__tests__foxglove__test_log__foxglove_log.snap b/crates/store/re_data_loader/src/loader_mcap/tests/foxglove/snapshots/re_data_loader__loader_mcap__tests__foxglove__test_log__foxglove_log.snap index 683594a0d169..210c089e36a0 100644 --- a/crates/store/re_data_loader/src/loader_mcap/tests/foxglove/snapshots/re_data_loader__loader_mcap__tests__foxglove__test_log__foxglove_log.snap +++ b/crates/store/re_data_loader/src/loader_mcap/tests/foxglove/snapshots/re_data_loader__loader_mcap__tests__foxglove__test_log__foxglove_log.snap @@ -2,34 +2,34 @@ source: crates/store/re_data_loader/src/loader_mcap/tests/foxglove/test_log.rs expression: "format!(\"{:-240}\", chunk)" --- -┌───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ METADATA: │ -│ * entity_path: /text_log │ -│ * id: [**REDACTED**] │ -│ * version: [**REDACTED**] │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ ┌─────────────────────────────────────┬───────────────────────────────┬──────────────────────────────────┬───────────────────────────────┬────────────────────────────────────┬─────────────────────────────────────┐ │ -│ │ RowId ┆ message_log_time ┆ message_publish_time ┆ timestamp ┆ TextLog:level ┆ TextLog:text │ │ -│ │ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- │ │ -│ │ type: FixedSizeBinary[16] ┆ type: nullable Timestamp(ns) ┆ type: nullable Timestamp(ns) ┆ type: nullable Timestamp(ns) ┆ type: nullable List[nullable Utf8] ┆ type: nullable List[nullable Utf8] │ │ -│ │ ARROW:extension:metadata: ┆ index_name: message_log_time ┆ index_name: message_publish_time ┆ index_name: timestamp ┆ archetype: TextLog ┆ archetype: TextLog │ │ -│ │ {"namespace":"row"} ┆ is_sorted: true ┆ is_sorted: true ┆ is_sorted: true ┆ component: TextLog:level ┆ component: TextLog:text │ │ -│ │ ARROW:extension:name: TUID ┆ kind: index ┆ kind: index ┆ kind: index ┆ component_type: TextLogLevel ┆ component_type: Text │ │ -│ │ is_sorted: true ┆ ┆ ┆ ┆ kind: data ┆ kind: data │ │ -│ │ kind: control ┆ ┆ ┆ ┆ ┆ │ │ -│ ╞═════════════════════════════════════╪═══════════════════════════════╪══════════════════════════════════╪═══════════════════════════════╪════════════════════════════════════╪═════════════════════════════════════╡ │ -│ │ row_[**REDACTED**] ┆ 2026-02-05T09:40:29.675813120 ┆ 2026-02-05T09:40:29.675813120 ┆ 2026-02-05T09:40:29.675813120 ┆ [null] ┆ [This message has log level │ │ -│ │ ┆ ┆ ┆ ┆ ┆ UNKNOWN] │ │ -│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ -│ │ row_[**REDACTED**] ┆ 2026-02-05T09:40:30.175813120 ┆ 2026-02-05T09:40:30.175813120 ┆ 2026-02-05T09:40:30.175813120 ┆ [DEBUG] ┆ [This message has log level DEBUG] │ │ -│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ -│ │ row_[**REDACTED**] ┆ 2026-02-05T09:40:30.675813120 ┆ 2026-02-05T09:40:30.675813120 ┆ 2026-02-05T09:40:30.675813120 ┆ [INFO] ┆ [This message has log level INFO] │ │ -│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ -│ │ row_[**REDACTED**] ┆ 2026-02-05T09:40:31.175813120 ┆ 2026-02-05T09:40:31.175813120 ┆ 2026-02-05T09:40:31.175813120 ┆ [WARN] ┆ [This message has log level │ │ -│ │ ┆ ┆ ┆ ┆ ┆ WARNING] │ │ -│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ -│ │ row_[**REDACTED**] ┆ 2026-02-05T09:40:31.675813120 ┆ 2026-02-05T09:40:31.675813120 ┆ 2026-02-05T09:40:31.675813120 ┆ [ERROR] ┆ [This message has log level ERROR] │ │ -│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ -│ │ row_[**REDACTED**] ┆ 2026-02-05T09:40:32.175813120 ┆ 2026-02-05T09:40:32.175813120 ┆ 2026-02-05T09:40:32.175813120 ┆ [CRITICAL] ┆ [This message has log level FATAL] │ │ -│ └─────────────────────────────────────┴───────────────────────────────┴──────────────────────────────────┴───────────────────────────────┴────────────────────────────────────┴─────────────────────────────────────┘ │ -└───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ METADATA: │ +│ * entity_path: /text_log │ +│ * id: [**REDACTED**] │ +│ * version: [**REDACTED**] │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ ┌─────────────────────────────────────┬───────────────────────────────┬──────────────────────────────────┬───────────────────────────────┬──────────────────────────────┬─────────────────────────────────────┐ │ +│ │ RowId ┆ message_log_time ┆ message_publish_time ┆ timestamp ┆ TextLog:level ┆ TextLog:text │ │ +│ │ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- │ │ +│ │ type: non-null FixedSizeBinary(16) ┆ type: Timestamp(ns) ┆ type: Timestamp(ns) ┆ type: Timestamp(ns) ┆ type: List(Utf8) ┆ type: List(Utf8) │ │ +│ │ ARROW:extension:metadata: ┆ index_name: message_log_time ┆ index_name: message_publish_time ┆ index_name: timestamp ┆ archetype: TextLog ┆ archetype: TextLog │ │ +│ │ {"namespace":"row"} ┆ is_sorted: true ┆ is_sorted: true ┆ is_sorted: true ┆ component: TextLog:level ┆ component: TextLog:text │ │ +│ │ ARROW:extension:name: TUID ┆ kind: index ┆ kind: index ┆ kind: index ┆ component_type: TextLogLevel ┆ component_type: Text │ │ +│ │ is_sorted: true ┆ ┆ ┆ ┆ kind: data ┆ kind: data │ │ +│ │ kind: control ┆ ┆ ┆ ┆ ┆ │ │ +│ ╞═════════════════════════════════════╪═══════════════════════════════╪══════════════════════════════════╪═══════════════════════════════╪══════════════════════════════╪═════════════════════════════════════╡ │ +│ │ row_[**REDACTED**] ┆ 2026-02-05T09:40:29.675813120 ┆ 2026-02-05T09:40:29.675813120 ┆ 2026-02-05T09:40:29.675813120 ┆ [null] ┆ [This message has log level │ │ +│ │ ┆ ┆ ┆ ┆ ┆ UNKNOWN] │ │ +│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ +│ │ row_[**REDACTED**] ┆ 2026-02-05T09:40:30.175813120 ┆ 2026-02-05T09:40:30.175813120 ┆ 2026-02-05T09:40:30.175813120 ┆ [DEBUG] ┆ [This message has log level DEBUG] │ │ +│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ +│ │ row_[**REDACTED**] ┆ 2026-02-05T09:40:30.675813120 ┆ 2026-02-05T09:40:30.675813120 ┆ 2026-02-05T09:40:30.675813120 ┆ [INFO] ┆ [This message has log level INFO] │ │ +│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ +│ │ row_[**REDACTED**] ┆ 2026-02-05T09:40:31.175813120 ┆ 2026-02-05T09:40:31.175813120 ┆ 2026-02-05T09:40:31.175813120 ┆ [WARN] ┆ [This message has log level │ │ +│ │ ┆ ┆ ┆ ┆ ┆ WARNING] │ │ +│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ +│ │ row_[**REDACTED**] ┆ 2026-02-05T09:40:31.675813120 ┆ 2026-02-05T09:40:31.675813120 ┆ 2026-02-05T09:40:31.675813120 ┆ [ERROR] ┆ [This message has log level ERROR] │ │ +│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ +│ │ row_[**REDACTED**] ┆ 2026-02-05T09:40:32.175813120 ┆ 2026-02-05T09:40:32.175813120 ┆ 2026-02-05T09:40:32.175813120 ┆ [CRITICAL] ┆ [This message has log level FATAL] │ │ +│ └─────────────────────────────────────┴───────────────────────────────┴──────────────────────────────────┴───────────────────────────────┴──────────────────────────────┴─────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ diff --git a/crates/store/re_data_loader/src/loader_mcap/tests/foxglove/snapshots/re_data_loader__loader_mcap__tests__foxglove__test_point_cloud__foxglove_point_cloud.snap b/crates/store/re_data_loader/src/loader_mcap/tests/foxglove/snapshots/re_data_loader__loader_mcap__tests__foxglove__test_point_cloud__foxglove_point_cloud.snap index 789a7fbbfcca..7174ccb401f7 100644 --- a/crates/store/re_data_loader/src/loader_mcap/tests/foxglove/snapshots/re_data_loader__loader_mcap__tests__foxglove__test_point_cloud__foxglove_point_cloud.snap +++ b/crates/store/re_data_loader/src/loader_mcap/tests/foxglove/snapshots/re_data_loader__loader_mcap__tests__foxglove__test_point_cloud__foxglove_point_cloud.snap @@ -11,14 +11,13 @@ expression: "format!(\"{:-240}\", chunk)" │ ┌────────────────────────────────┬───────────────────────────────┬────────────────────────────────┬───────────────────────────────┬────────────────────────────────┬────────────────────────────────┬────────────────────────────────┐ │ │ │ RowId ┆ message_log_time ┆ message_publish_time ┆ timestamp ┆ CoordinateFrame:frame ┆ Points3D:colors ┆ Points3D:positions │ │ │ │ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- │ │ -│ │ type: FixedSizeBinary[16] ┆ type: nullable Timestamp(ns) ┆ type: nullable Timestamp(ns) ┆ type: nullable Timestamp(ns) ┆ type: nullable List[nullable ┆ type: nullable List[nullable ┆ type: nullable List[nullable │ │ -│ │ ARROW:extension:metadata: ┆ index_name: message_log_time ┆ index_name: ┆ index_name: timestamp ┆ Utf8] ┆ u32] ┆ FixedSizeList[f32; 3]] │ │ -│ │ {"namespace":"row"} ┆ is_sorted: true ┆ message_publish_time ┆ is_sorted: true ┆ archetype: CoordinateFrame ┆ archetype: Points3D ┆ archetype: Points3D │ │ -│ │ ARROW:extension:name: TUID ┆ kind: index ┆ is_sorted: true ┆ kind: index ┆ component: ┆ component: Points3D:colors ┆ component: Points3D:positions │ │ -│ │ is_sorted: true ┆ ┆ kind: index ┆ ┆ CoordinateFrame:frame ┆ component_type: Color ┆ component_type: Position3D │ │ -│ │ kind: control ┆ ┆ ┆ ┆ component_type: ┆ kind: data ┆ kind: data │ │ -│ │ ┆ ┆ ┆ ┆ TransformFrameId ┆ ┆ │ │ -│ │ ┆ ┆ ┆ ┆ kind: data ┆ ┆ │ │ +│ │ type: non-null ┆ type: Timestamp(ns) ┆ type: Timestamp(ns) ┆ type: Timestamp(ns) ┆ type: List(Utf8) ┆ type: List(UInt32) ┆ type: List(FixedSizeList(3 x │ │ +│ │ FixedSizeBinary(16) ┆ index_name: message_log_time ┆ index_name: ┆ index_name: timestamp ┆ archetype: CoordinateFrame ┆ archetype: Points3D ┆ non-null Float32)) │ │ +│ │ ARROW:extension:metadata: ┆ is_sorted: true ┆ message_publish_time ┆ is_sorted: true ┆ component: ┆ component: Points3D:colors ┆ archetype: Points3D │ │ +│ │ {"namespace":"row"} ┆ kind: index ┆ is_sorted: true ┆ kind: index ┆ CoordinateFrame:frame ┆ component_type: Color ┆ component: Points3D:positions │ │ +│ │ ARROW:extension:name: TUID ┆ ┆ kind: index ┆ ┆ component_type: ┆ kind: data ┆ component_type: Position3D │ │ +│ │ is_sorted: true ┆ ┆ ┆ ┆ TransformFrameId ┆ ┆ kind: data │ │ +│ │ kind: control ┆ ┆ ┆ ┆ kind: data ┆ ┆ │ │ │ ╞════════════════════════════════╪═══════════════════════════════╪════════════════════════════════╪═══════════════════════════════╪════════════════════════════════╪════════════════════════════════╪════════════════════════════════╡ │ │ │ row_[**REDACTED**] ┆ 2026-02-17T14:13:33.852164096 ┆ 2026-02-17T14:13:33.852164096 ┆ 2026-02-17T14:13:33.852164096 ┆ [world] ┆ [4278190335, 16711935, 65535, ┆ [[0.0, 0.0, 0.0], [1.0, 0.0, │ │ │ │ ┆ ┆ ┆ ┆ ┆ 4294902015, 4278255615, ┆ 0.0], [1.0, 1.0, 0.0], [0.0, │ │ diff --git a/crates/store/re_data_loader/src/loader_mcap/tests/foxglove/snapshots/re_data_loader__loader_mcap__tests__foxglove__test_raw_image__foxglove_raw_image.snap b/crates/store/re_data_loader/src/loader_mcap/tests/foxglove/snapshots/re_data_loader__loader_mcap__tests__foxglove__test_raw_image__foxglove_raw_image.snap index 05c22eeb6f5d..0241fc361fa4 100644 --- a/crates/store/re_data_loader/src/loader_mcap/tests/foxglove/snapshots/re_data_loader__loader_mcap__tests__foxglove__test_raw_image__foxglove_raw_image.snap +++ b/crates/store/re_data_loader/src/loader_mcap/tests/foxglove/snapshots/re_data_loader__loader_mcap__tests__foxglove__test_raw_image__foxglove_raw_image.snap @@ -11,14 +11,16 @@ expression: "format!(\"{:-240}\", chunk)" │ ┌────────────────────────────────┬───────────────────────────────┬────────────────────────────────┬───────────────────────────────┬────────────────────────────────┬────────────────────────────────┬────────────────────────────────┐ │ │ │ RowId ┆ message_log_time ┆ message_publish_time ┆ timestamp ┆ CoordinateFrame:frame ┆ Image:buffer ┆ Image:format │ │ │ │ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- │ │ -│ │ type: FixedSizeBinary[16] ┆ type: nullable Timestamp(ns) ┆ type: nullable Timestamp(ns) ┆ type: nullable Timestamp(ns) ┆ type: nullable List[nullable ┆ type: nullable List[nullable ┆ type: nullable List[nullable │ │ -│ │ ARROW:extension:metadata: ┆ index_name: message_log_time ┆ index_name: ┆ index_name: timestamp ┆ Utf8] ┆ List[u8]] ┆ Struct[5]] │ │ -│ │ {"namespace":"row"} ┆ is_sorted: true ┆ message_publish_time ┆ is_sorted: true ┆ archetype: CoordinateFrame ┆ archetype: Image ┆ archetype: Image │ │ -│ │ ARROW:extension:name: TUID ┆ kind: index ┆ is_sorted: true ┆ kind: index ┆ component: ┆ component: Image:buffer ┆ component: Image:format │ │ -│ │ is_sorted: true ┆ ┆ kind: index ┆ ┆ CoordinateFrame:frame ┆ component_type: ImageBuffer ┆ component_type: ImageFormat │ │ -│ │ kind: control ┆ ┆ ┆ ┆ component_type: ┆ kind: data ┆ kind: data │ │ -│ │ ┆ ┆ ┆ ┆ TransformFrameId ┆ ┆ │ │ -│ │ ┆ ┆ ┆ ┆ kind: data ┆ ┆ │ │ +│ │ type: non-null ┆ type: Timestamp(ns) ┆ type: Timestamp(ns) ┆ type: Timestamp(ns) ┆ type: List(Utf8) ┆ type: List(List(non-null ┆ type: List(Struct("width": │ │ +│ │ FixedSizeBinary(16) ┆ index_name: message_log_time ┆ index_name: ┆ index_name: timestamp ┆ archetype: CoordinateFrame ┆ UInt8)) ┆ non-null UInt32, "height": │ │ +│ │ ARROW:extension:metadata: ┆ is_sorted: true ┆ message_publish_time ┆ is_sorted: true ┆ component: ┆ archetype: Image ┆ non-null UInt32, │ │ +│ │ {"namespace":"row"} ┆ kind: index ┆ is_sorted: true ┆ kind: index ┆ CoordinateFrame:frame ┆ component: Image:buffer ┆ "pixel_format": UInt8, │ │ +│ │ ARROW:extension:name: TUID ┆ ┆ kind: index ┆ ┆ component_type: ┆ component_type: ImageBuffer ┆ "color_model": UInt8, │ │ +│ │ is_sorted: true ┆ ┆ ┆ ┆ TransformFrameId ┆ kind: data ┆ "channel_datatype": UInt8)) │ │ +│ │ kind: control ┆ ┆ ┆ ┆ kind: data ┆ ┆ archetype: Image │ │ +│ │ ┆ ┆ ┆ ┆ ┆ ┆ component: Image:format │ │ +│ │ ┆ ┆ ┆ ┆ ┆ ┆ component_type: ImageFormat │ │ +│ │ ┆ ┆ ┆ ┆ ┆ ┆ kind: data │ │ │ ╞════════════════════════════════╪═══════════════════════════════╪════════════════════════════════╪═══════════════════════════════╪════════════════════════════════╪════════════════════════════════╪════════════════════════════════╡ │ │ │ row_[**REDACTED**] ┆ 2026-02-11T11:30:54.931614976 ┆ 2026-02-11T11:30:54.931614976 ┆ 2026-02-11T11:30:54.931614976 ┆ [camera_frame_image_plane] ┆ [[255, 0, 0, 255, 127, 0, 255, ┆ [{width: 10, height: 10, │ │ │ │ ┆ ┆ ┆ ┆ ┆ 255, 0, 0, 255, 0, 0, 0, 255, ┆ pixel_format: null, │ │ diff --git a/crates/store/re_dataframe/src/snapshots/re_dataframe__query__tests__async_barebones_static.snap b/crates/store/re_dataframe/src/snapshots/re_dataframe__query__tests__async_barebones_static.snap index 7ca7e4d4ce28..1177449bd8b9 100644 --- a/crates/store/re_dataframe/src/snapshots/re_dataframe__query__tests__async_barebones_static.snap +++ b/crates/store/re_dataframe/src/snapshots/re_dataframe__query__tests__async_barebones_static.snap @@ -2,16 +2,16 @@ source: crates/store/re_dataframe/src/query.rs expression: DisplayRB(dataframe) --- -┌──────────────────────┬──────────────────────────────┬────────────────────────────────────┬────────────────────────────────────┬──────────────────────────────────────┐ -│ frame_nr ┆ log_time ┆ /this/that:example.MyPoints:colors ┆ /this/that:example.MyPoints:labels ┆ /this/that:example.MyPoints:points │ -│ --- ┆ --- ┆ --- ┆ --- ┆ --- │ -│ type: nullable i64 ┆ type: nullable Timestamp(ns) ┆ type: nullable List[nullable u32] ┆ type: nullable List[nullable Utf8] ┆ type: nullable List[nullable │ -│ index_name: frame_nr ┆ index_name: log_time ┆ archetype: example.MyPoints ┆ archetype: example.MyPoints ┆ Struct[2]] │ -│ kind: index ┆ kind: index ┆ component: example.MyPoints:colors ┆ component: example.MyPoints:labels ┆ archetype: example.MyPoints │ -│ ┆ ┆ component_type: example.MyColor ┆ component_type: example.MyLabel ┆ component: example.MyPoints:points │ -│ ┆ ┆ entity_path: /this/that ┆ entity_path: /this/that ┆ component_type: example.MyPoint │ -│ ┆ ┆ kind: data ┆ is_static: true ┆ entity_path: /this/that │ -│ ┆ ┆ ┆ kind: data ┆ kind: data │ -╞══════════════════════╪══════════════════════════════╪════════════════════════════════════╪════════════════════════════════════╪══════════════════════════════════════╡ -│ null ┆ null ┆ null ┆ [c] ┆ null │ -└──────────────────────┴──────────────────────────────┴────────────────────────────────────┴────────────────────────────────────┴──────────────────────────────────────┘ +┌──────────────────────┬──────────────────────┬────────────────────────────────────┬────────────────────────────────────┬──────────────────────────────────────┐ +│ frame_nr ┆ log_time ┆ /this/that:example.MyPoints:colors ┆ /this/that:example.MyPoints:labels ┆ /this/that:example.MyPoints:points │ +│ --- ┆ --- ┆ --- ┆ --- ┆ --- │ +│ type: Int64 ┆ type: Timestamp(ns) ┆ type: List(UInt32) ┆ type: List(Utf8) ┆ type: List(Struct("x": non-null │ +│ index_name: frame_nr ┆ index_name: log_time ┆ archetype: example.MyPoints ┆ archetype: example.MyPoints ┆ Float32, "y": non-null Float32)) │ +│ kind: index ┆ kind: index ┆ component: example.MyPoints:colors ┆ component: example.MyPoints:labels ┆ archetype: example.MyPoints │ +│ ┆ ┆ component_type: example.MyColor ┆ component_type: example.MyLabel ┆ component: example.MyPoints:points │ +│ ┆ ┆ entity_path: /this/that ┆ entity_path: /this/that ┆ component_type: example.MyPoint │ +│ ┆ ┆ kind: data ┆ is_static: true ┆ entity_path: /this/that │ +│ ┆ ┆ ┆ kind: data ┆ kind: data │ +╞══════════════════════╪══════════════════════╪════════════════════════════════════╪════════════════════════════════════╪══════════════════════════════════════╡ +│ null ┆ null ┆ null ┆ [c] ┆ null │ +└──────────────────────┴──────────────────────┴────────────────────────────────────┴────────────────────────────────────┴──────────────────────────────────────┘ diff --git a/crates/store/re_dataframe/src/snapshots/re_dataframe__query__tests__async_barebones_temporal.snap b/crates/store/re_dataframe/src/snapshots/re_dataframe__query__tests__async_barebones_temporal.snap index 9f5faffb202a..8837ef8e9620 100644 --- a/crates/store/re_dataframe/src/snapshots/re_dataframe__query__tests__async_barebones_temporal.snap +++ b/crates/store/re_dataframe/src/snapshots/re_dataframe__query__tests__async_barebones_temporal.snap @@ -5,8 +5,8 @@ expression: DisplayRB(dataframe) ┌──────────────────────┬───────────────────────────────┬────────────────────────────────────┬────────────────────────────────────┬──────────────────────────────────────┐ │ frame_nr ┆ log_time ┆ /this/that:example.MyPoints:colors ┆ /this/that:example.MyPoints:labels ┆ /this/that:example.MyPoints:points │ │ --- ┆ --- ┆ --- ┆ --- ┆ --- │ -│ type: nullable i64 ┆ type: nullable Timestamp(ns) ┆ type: nullable List[nullable u32] ┆ type: nullable List[nullable Utf8] ┆ type: nullable List[nullable │ -│ index_name: frame_nr ┆ index_name: log_time ┆ archetype: example.MyPoints ┆ archetype: example.MyPoints ┆ Struct[2]] │ +│ type: Int64 ┆ type: Timestamp(ns) ┆ type: List(UInt32) ┆ type: List(Utf8) ┆ type: List(Struct("x": non-null │ +│ index_name: frame_nr ┆ index_name: log_time ┆ archetype: example.MyPoints ┆ archetype: example.MyPoints ┆ Float32, "y": non-null Float32)) │ │ kind: index ┆ kind: index ┆ component: example.MyPoints:colors ┆ component: example.MyPoints:labels ┆ archetype: example.MyPoints │ │ ┆ ┆ component_type: example.MyColor ┆ component_type: example.MyLabel ┆ component: example.MyPoints:points │ │ ┆ ┆ entity_path: /this/that ┆ entity_path: /this/that ┆ component_type: example.MyPoint │ diff --git a/crates/store/re_dataframe/src/snapshots/re_dataframe__query__tests__barebones-2.snap b/crates/store/re_dataframe/src/snapshots/re_dataframe__query__tests__barebones-2.snap index 9f5faffb202a..8837ef8e9620 100644 --- a/crates/store/re_dataframe/src/snapshots/re_dataframe__query__tests__barebones-2.snap +++ b/crates/store/re_dataframe/src/snapshots/re_dataframe__query__tests__barebones-2.snap @@ -5,8 +5,8 @@ expression: DisplayRB(dataframe) ┌──────────────────────┬───────────────────────────────┬────────────────────────────────────┬────────────────────────────────────┬──────────────────────────────────────┐ │ frame_nr ┆ log_time ┆ /this/that:example.MyPoints:colors ┆ /this/that:example.MyPoints:labels ┆ /this/that:example.MyPoints:points │ │ --- ┆ --- ┆ --- ┆ --- ┆ --- │ -│ type: nullable i64 ┆ type: nullable Timestamp(ns) ┆ type: nullable List[nullable u32] ┆ type: nullable List[nullable Utf8] ┆ type: nullable List[nullable │ -│ index_name: frame_nr ┆ index_name: log_time ┆ archetype: example.MyPoints ┆ archetype: example.MyPoints ┆ Struct[2]] │ +│ type: Int64 ┆ type: Timestamp(ns) ┆ type: List(UInt32) ┆ type: List(Utf8) ┆ type: List(Struct("x": non-null │ +│ index_name: frame_nr ┆ index_name: log_time ┆ archetype: example.MyPoints ┆ archetype: example.MyPoints ┆ Float32, "y": non-null Float32)) │ │ kind: index ┆ kind: index ┆ component: example.MyPoints:colors ┆ component: example.MyPoints:labels ┆ archetype: example.MyPoints │ │ ┆ ┆ component_type: example.MyColor ┆ component_type: example.MyLabel ┆ component: example.MyPoints:points │ │ ┆ ┆ entity_path: /this/that ┆ entity_path: /this/that ┆ component_type: example.MyPoint │ diff --git a/crates/store/re_dataframe/src/snapshots/re_dataframe__query__tests__barebones.snap b/crates/store/re_dataframe/src/snapshots/re_dataframe__query__tests__barebones.snap index 7ca7e4d4ce28..1177449bd8b9 100644 --- a/crates/store/re_dataframe/src/snapshots/re_dataframe__query__tests__barebones.snap +++ b/crates/store/re_dataframe/src/snapshots/re_dataframe__query__tests__barebones.snap @@ -2,16 +2,16 @@ source: crates/store/re_dataframe/src/query.rs expression: DisplayRB(dataframe) --- -┌──────────────────────┬──────────────────────────────┬────────────────────────────────────┬────────────────────────────────────┬──────────────────────────────────────┐ -│ frame_nr ┆ log_time ┆ /this/that:example.MyPoints:colors ┆ /this/that:example.MyPoints:labels ┆ /this/that:example.MyPoints:points │ -│ --- ┆ --- ┆ --- ┆ --- ┆ --- │ -│ type: nullable i64 ┆ type: nullable Timestamp(ns) ┆ type: nullable List[nullable u32] ┆ type: nullable List[nullable Utf8] ┆ type: nullable List[nullable │ -│ index_name: frame_nr ┆ index_name: log_time ┆ archetype: example.MyPoints ┆ archetype: example.MyPoints ┆ Struct[2]] │ -│ kind: index ┆ kind: index ┆ component: example.MyPoints:colors ┆ component: example.MyPoints:labels ┆ archetype: example.MyPoints │ -│ ┆ ┆ component_type: example.MyColor ┆ component_type: example.MyLabel ┆ component: example.MyPoints:points │ -│ ┆ ┆ entity_path: /this/that ┆ entity_path: /this/that ┆ component_type: example.MyPoint │ -│ ┆ ┆ kind: data ┆ is_static: true ┆ entity_path: /this/that │ -│ ┆ ┆ ┆ kind: data ┆ kind: data │ -╞══════════════════════╪══════════════════════════════╪════════════════════════════════════╪════════════════════════════════════╪══════════════════════════════════════╡ -│ null ┆ null ┆ null ┆ [c] ┆ null │ -└──────────────────────┴──────────────────────────────┴────────────────────────────────────┴────────────────────────────────────┴──────────────────────────────────────┘ +┌──────────────────────┬──────────────────────┬────────────────────────────────────┬────────────────────────────────────┬──────────────────────────────────────┐ +│ frame_nr ┆ log_time ┆ /this/that:example.MyPoints:colors ┆ /this/that:example.MyPoints:labels ┆ /this/that:example.MyPoints:points │ +│ --- ┆ --- ┆ --- ┆ --- ┆ --- │ +│ type: Int64 ┆ type: Timestamp(ns) ┆ type: List(UInt32) ┆ type: List(Utf8) ┆ type: List(Struct("x": non-null │ +│ index_name: frame_nr ┆ index_name: log_time ┆ archetype: example.MyPoints ┆ archetype: example.MyPoints ┆ Float32, "y": non-null Float32)) │ +│ kind: index ┆ kind: index ┆ component: example.MyPoints:colors ┆ component: example.MyPoints:labels ┆ archetype: example.MyPoints │ +│ ┆ ┆ component_type: example.MyColor ┆ component_type: example.MyLabel ┆ component: example.MyPoints:points │ +│ ┆ ┆ entity_path: /this/that ┆ entity_path: /this/that ┆ component_type: example.MyPoint │ +│ ┆ ┆ kind: data ┆ is_static: true ┆ entity_path: /this/that │ +│ ┆ ┆ ┆ kind: data ┆ kind: data │ +╞══════════════════════╪══════════════════════╪════════════════════════════════════╪════════════════════════════════════╪══════════════════════════════════════╡ +│ null ┆ null ┆ null ┆ [c] ┆ null │ +└──────────────────────┴──────────────────────┴────────────────────────────────────┴────────────────────────────────────┴──────────────────────────────────────┘ diff --git a/crates/store/re_dataframe/src/snapshots/re_dataframe__query__tests__clears-2.snap b/crates/store/re_dataframe/src/snapshots/re_dataframe__query__tests__clears-2.snap index d72703f6beb6..52b1fe5900fe 100644 --- a/crates/store/re_dataframe/src/snapshots/re_dataframe__query__tests__clears-2.snap +++ b/crates/store/re_dataframe/src/snapshots/re_dataframe__query__tests__clears-2.snap @@ -5,8 +5,8 @@ expression: DisplayRB(dataframe) ┌──────────────────────┬───────────────────────────────┬────────────────────────────────────┬────────────────────────────────────┬──────────────────────────────────────┐ │ frame_nr ┆ log_time ┆ /this/that:example.MyPoints:colors ┆ /this/that:example.MyPoints:labels ┆ /this/that:example.MyPoints:points │ │ --- ┆ --- ┆ --- ┆ --- ┆ --- │ -│ type: nullable i64 ┆ type: nullable Timestamp(ns) ┆ type: nullable List[nullable u32] ┆ type: nullable List[nullable Utf8] ┆ type: nullable List[nullable │ -│ index_name: frame_nr ┆ index_name: log_time ┆ archetype: example.MyPoints ┆ archetype: example.MyPoints ┆ Struct[2]] │ +│ type: Int64 ┆ type: Timestamp(ns) ┆ type: List(UInt32) ┆ type: List(Utf8) ┆ type: List(Struct("x": non-null │ +│ index_name: frame_nr ┆ index_name: log_time ┆ archetype: example.MyPoints ┆ archetype: example.MyPoints ┆ Float32, "y": non-null Float32)) │ │ kind: index ┆ kind: index ┆ component: example.MyPoints:colors ┆ component: example.MyPoints:labels ┆ archetype: example.MyPoints │ │ ┆ ┆ component_type: example.MyColor ┆ component_type: example.MyLabel ┆ component: example.MyPoints:points │ │ ┆ ┆ entity_path: /this/that ┆ entity_path: /this/that ┆ component_type: example.MyPoint │ diff --git a/crates/store/re_dataframe/src/snapshots/re_dataframe__query__tests__clears.snap b/crates/store/re_dataframe/src/snapshots/re_dataframe__query__tests__clears.snap index d72703f6beb6..52b1fe5900fe 100644 --- a/crates/store/re_dataframe/src/snapshots/re_dataframe__query__tests__clears.snap +++ b/crates/store/re_dataframe/src/snapshots/re_dataframe__query__tests__clears.snap @@ -5,8 +5,8 @@ expression: DisplayRB(dataframe) ┌──────────────────────┬───────────────────────────────┬────────────────────────────────────┬────────────────────────────────────┬──────────────────────────────────────┐ │ frame_nr ┆ log_time ┆ /this/that:example.MyPoints:colors ┆ /this/that:example.MyPoints:labels ┆ /this/that:example.MyPoints:points │ │ --- ┆ --- ┆ --- ┆ --- ┆ --- │ -│ type: nullable i64 ┆ type: nullable Timestamp(ns) ┆ type: nullable List[nullable u32] ┆ type: nullable List[nullable Utf8] ┆ type: nullable List[nullable │ -│ index_name: frame_nr ┆ index_name: log_time ┆ archetype: example.MyPoints ┆ archetype: example.MyPoints ┆ Struct[2]] │ +│ type: Int64 ┆ type: Timestamp(ns) ┆ type: List(UInt32) ┆ type: List(Utf8) ┆ type: List(Struct("x": non-null │ +│ index_name: frame_nr ┆ index_name: log_time ┆ archetype: example.MyPoints ┆ archetype: example.MyPoints ┆ Float32, "y": non-null Float32)) │ │ kind: index ┆ kind: index ┆ component: example.MyPoints:colors ┆ component: example.MyPoints:labels ┆ archetype: example.MyPoints │ │ ┆ ┆ component_type: example.MyColor ┆ component_type: example.MyLabel ┆ component: example.MyPoints:points │ │ ┆ ┆ entity_path: /this/that ┆ entity_path: /this/that ┆ component_type: example.MyPoint │ diff --git a/crates/store/re_dataframe/src/snapshots/re_dataframe__query__tests__filtered_index_range.snap b/crates/store/re_dataframe/src/snapshots/re_dataframe__query__tests__filtered_index_range.snap index cfcab0c87da2..d6b6017fd900 100644 --- a/crates/store/re_dataframe/src/snapshots/re_dataframe__query__tests__filtered_index_range.snap +++ b/crates/store/re_dataframe/src/snapshots/re_dataframe__query__tests__filtered_index_range.snap @@ -5,8 +5,8 @@ expression: DisplayRB(dataframe) ┌──────────────────────┬───────────────────────────────┬────────────────────────────────────┬────────────────────────────────────┬──────────────────────────────────────┐ │ frame_nr ┆ log_time ┆ /this/that:example.MyPoints:colors ┆ /this/that:example.MyPoints:labels ┆ /this/that:example.MyPoints:points │ │ --- ┆ --- ┆ --- ┆ --- ┆ --- │ -│ type: nullable i64 ┆ type: nullable Timestamp(ns) ┆ type: nullable List[nullable u32] ┆ type: nullable List[nullable Utf8] ┆ type: nullable List[nullable │ -│ index_name: frame_nr ┆ index_name: log_time ┆ archetype: example.MyPoints ┆ archetype: example.MyPoints ┆ Struct[2]] │ +│ type: Int64 ┆ type: Timestamp(ns) ┆ type: List(UInt32) ┆ type: List(Utf8) ┆ type: List(Struct("x": non-null │ +│ index_name: frame_nr ┆ index_name: log_time ┆ archetype: example.MyPoints ┆ archetype: example.MyPoints ┆ Float32, "y": non-null Float32)) │ │ kind: index ┆ kind: index ┆ component: example.MyPoints:colors ┆ component: example.MyPoints:labels ┆ archetype: example.MyPoints │ │ ┆ ┆ component_type: example.MyColor ┆ component_type: example.MyLabel ┆ component: example.MyPoints:points │ │ ┆ ┆ entity_path: /this/that ┆ entity_path: /this/that ┆ component_type: example.MyPoint │ diff --git a/crates/store/re_dataframe/src/snapshots/re_dataframe__query__tests__filtered_index_values.snap b/crates/store/re_dataframe/src/snapshots/re_dataframe__query__tests__filtered_index_values.snap index dde2c4fbf8d4..24ddf544e065 100644 --- a/crates/store/re_dataframe/src/snapshots/re_dataframe__query__tests__filtered_index_values.snap +++ b/crates/store/re_dataframe/src/snapshots/re_dataframe__query__tests__filtered_index_values.snap @@ -2,18 +2,18 @@ source: crates/store/re_dataframe/src/query.rs expression: DisplayRB(dataframe) --- -┌──────────────────────┬──────────────────────────────┬────────────────────────────────────┬────────────────────────────────────┬──────────────────────────────────────┐ -│ frame_nr ┆ log_time ┆ /this/that:example.MyPoints:colors ┆ /this/that:example.MyPoints:labels ┆ /this/that:example.MyPoints:points │ -│ --- ┆ --- ┆ --- ┆ --- ┆ --- │ -│ type: nullable i64 ┆ type: nullable Timestamp(ns) ┆ type: nullable List[nullable u32] ┆ type: nullable List[nullable Utf8] ┆ type: nullable List[nullable │ -│ index_name: frame_nr ┆ index_name: log_time ┆ archetype: example.MyPoints ┆ archetype: example.MyPoints ┆ Struct[2]] │ -│ kind: index ┆ kind: index ┆ component: example.MyPoints:colors ┆ component: example.MyPoints:labels ┆ archetype: example.MyPoints │ -│ ┆ ┆ component_type: example.MyColor ┆ component_type: example.MyLabel ┆ component: example.MyPoints:points │ -│ ┆ ┆ entity_path: /this/that ┆ entity_path: /this/that ┆ component_type: example.MyPoint │ -│ ┆ ┆ kind: data ┆ is_static: true ┆ entity_path: /this/that │ -│ ┆ ┆ ┆ kind: data ┆ kind: data │ -╞══════════════════════╪══════════════════════════════╪════════════════════════════════════╪════════════════════════════════════╪══════════════════════════════════════╡ -│ 30 ┆ null ┆ [2] ┆ [c] ┆ [{x: 2.0, y: 2.0}] │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ 60 ┆ null ┆ null ┆ [c] ┆ [{x: 5.0, y: 5.0}] │ -└──────────────────────┴──────────────────────────────┴────────────────────────────────────┴────────────────────────────────────┴──────────────────────────────────────┘ +┌──────────────────────┬──────────────────────┬────────────────────────────────────┬────────────────────────────────────┬──────────────────────────────────────┐ +│ frame_nr ┆ log_time ┆ /this/that:example.MyPoints:colors ┆ /this/that:example.MyPoints:labels ┆ /this/that:example.MyPoints:points │ +│ --- ┆ --- ┆ --- ┆ --- ┆ --- │ +│ type: Int64 ┆ type: Timestamp(ns) ┆ type: List(UInt32) ┆ type: List(Utf8) ┆ type: List(Struct("x": non-null │ +│ index_name: frame_nr ┆ index_name: log_time ┆ archetype: example.MyPoints ┆ archetype: example.MyPoints ┆ Float32, "y": non-null Float32)) │ +│ kind: index ┆ kind: index ┆ component: example.MyPoints:colors ┆ component: example.MyPoints:labels ┆ archetype: example.MyPoints │ +│ ┆ ┆ component_type: example.MyColor ┆ component_type: example.MyLabel ┆ component: example.MyPoints:points │ +│ ┆ ┆ entity_path: /this/that ┆ entity_path: /this/that ┆ component_type: example.MyPoint │ +│ ┆ ┆ kind: data ┆ is_static: true ┆ entity_path: /this/that │ +│ ┆ ┆ ┆ kind: data ┆ kind: data │ +╞══════════════════════╪══════════════════════╪════════════════════════════════════╪════════════════════════════════════╪══════════════════════════════════════╡ +│ 30 ┆ null ┆ [2] ┆ [c] ┆ [{x: 2.0, y: 2.0}] │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ 60 ┆ null ┆ null ┆ [c] ┆ [{x: 5.0, y: 5.0}] │ +└──────────────────────┴──────────────────────┴────────────────────────────────────┴────────────────────────────────────┴──────────────────────────────────────┘ diff --git a/crates/store/re_dataframe/src/snapshots/re_dataframe__query__tests__filtered_is_not_null-2.snap b/crates/store/re_dataframe/src/snapshots/re_dataframe__query__tests__filtered_is_not_null-2.snap index 836cd0232c01..3fb8b0226754 100644 --- a/crates/store/re_dataframe/src/snapshots/re_dataframe__query__tests__filtered_is_not_null-2.snap +++ b/crates/store/re_dataframe/src/snapshots/re_dataframe__query__tests__filtered_is_not_null-2.snap @@ -2,15 +2,15 @@ source: crates/store/re_dataframe/src/query.rs expression: DisplayRB(dataframe) --- -┌──────────────────────┬──────────────────────────────┬────────────────────────────────────┬────────────────────────────────────┬──────────────────────────────────────┐ -│ frame_nr ┆ log_time ┆ /this/that:example.MyPoints:colors ┆ /this/that:example.MyPoints:labels ┆ /this/that:example.MyPoints:points │ -│ --- ┆ --- ┆ --- ┆ --- ┆ --- │ -│ type: nullable i64 ┆ type: nullable Timestamp(ns) ┆ type: nullable List[nullable u32] ┆ type: nullable List[nullable Utf8] ┆ type: nullable List[nullable │ -│ index_name: frame_nr ┆ index_name: log_time ┆ archetype: example.MyPoints ┆ archetype: example.MyPoints ┆ Struct[2]] │ -│ kind: index ┆ kind: index ┆ component: example.MyPoints:colors ┆ component: example.MyPoints:labels ┆ archetype: example.MyPoints │ -│ ┆ ┆ component_type: example.MyColor ┆ component_type: example.MyLabel ┆ component: example.MyPoints:points │ -│ ┆ ┆ entity_path: /this/that ┆ entity_path: /this/that ┆ component_type: example.MyPoint │ -│ ┆ ┆ kind: data ┆ is_static: true ┆ entity_path: /this/that │ -│ ┆ ┆ ┆ kind: data ┆ kind: data │ -╞══════════════════════╪══════════════════════════════╪════════════════════════════════════╪════════════════════════════════════╪══════════════════════════════════════╡ -└──────────────────────┴──────────────────────────────┴────────────────────────────────────┴────────────────────────────────────┴──────────────────────────────────────┘ +┌──────────────────────┬──────────────────────┬────────────────────────────────────┬────────────────────────────────────┬──────────────────────────────────────┐ +│ frame_nr ┆ log_time ┆ /this/that:example.MyPoints:colors ┆ /this/that:example.MyPoints:labels ┆ /this/that:example.MyPoints:points │ +│ --- ┆ --- ┆ --- ┆ --- ┆ --- │ +│ type: Int64 ┆ type: Timestamp(ns) ┆ type: List(UInt32) ┆ type: List(Utf8) ┆ type: List(Struct("x": non-null │ +│ index_name: frame_nr ┆ index_name: log_time ┆ archetype: example.MyPoints ┆ archetype: example.MyPoints ┆ Float32, "y": non-null Float32)) │ +│ kind: index ┆ kind: index ┆ component: example.MyPoints:colors ┆ component: example.MyPoints:labels ┆ archetype: example.MyPoints │ +│ ┆ ┆ component_type: example.MyColor ┆ component_type: example.MyLabel ┆ component: example.MyPoints:points │ +│ ┆ ┆ entity_path: /this/that ┆ entity_path: /this/that ┆ component_type: example.MyPoint │ +│ ┆ ┆ kind: data ┆ is_static: true ┆ entity_path: /this/that │ +│ ┆ ┆ ┆ kind: data ┆ kind: data │ +╞══════════════════════╪══════════════════════╪════════════════════════════════════╪════════════════════════════════════╪══════════════════════════════════════╡ +└──────────────────────┴──────────────────────┴────────────────────────────────────┴────────────────────────────────────┴──────────────────────────────────────┘ diff --git a/crates/store/re_dataframe/src/snapshots/re_dataframe__query__tests__filtered_is_not_null-3.snap b/crates/store/re_dataframe/src/snapshots/re_dataframe__query__tests__filtered_is_not_null-3.snap index 9f5faffb202a..8837ef8e9620 100644 --- a/crates/store/re_dataframe/src/snapshots/re_dataframe__query__tests__filtered_is_not_null-3.snap +++ b/crates/store/re_dataframe/src/snapshots/re_dataframe__query__tests__filtered_is_not_null-3.snap @@ -5,8 +5,8 @@ expression: DisplayRB(dataframe) ┌──────────────────────┬───────────────────────────────┬────────────────────────────────────┬────────────────────────────────────┬──────────────────────────────────────┐ │ frame_nr ┆ log_time ┆ /this/that:example.MyPoints:colors ┆ /this/that:example.MyPoints:labels ┆ /this/that:example.MyPoints:points │ │ --- ┆ --- ┆ --- ┆ --- ┆ --- │ -│ type: nullable i64 ┆ type: nullable Timestamp(ns) ┆ type: nullable List[nullable u32] ┆ type: nullable List[nullable Utf8] ┆ type: nullable List[nullable │ -│ index_name: frame_nr ┆ index_name: log_time ┆ archetype: example.MyPoints ┆ archetype: example.MyPoints ┆ Struct[2]] │ +│ type: Int64 ┆ type: Timestamp(ns) ┆ type: List(UInt32) ┆ type: List(Utf8) ┆ type: List(Struct("x": non-null │ +│ index_name: frame_nr ┆ index_name: log_time ┆ archetype: example.MyPoints ┆ archetype: example.MyPoints ┆ Float32, "y": non-null Float32)) │ │ kind: index ┆ kind: index ┆ component: example.MyPoints:colors ┆ component: example.MyPoints:labels ┆ archetype: example.MyPoints │ │ ┆ ┆ component_type: example.MyColor ┆ component_type: example.MyLabel ┆ component: example.MyPoints:points │ │ ┆ ┆ entity_path: /this/that ┆ entity_path: /this/that ┆ component_type: example.MyPoint │ diff --git a/crates/store/re_dataframe/src/snapshots/re_dataframe__query__tests__filtered_is_not_null-4.snap b/crates/store/re_dataframe/src/snapshots/re_dataframe__query__tests__filtered_is_not_null-4.snap index 49e869655df8..d2c05453a207 100644 --- a/crates/store/re_dataframe/src/snapshots/re_dataframe__query__tests__filtered_is_not_null-4.snap +++ b/crates/store/re_dataframe/src/snapshots/re_dataframe__query__tests__filtered_is_not_null-4.snap @@ -5,8 +5,8 @@ expression: DisplayRB(dataframe) ┌──────────────────────┬───────────────────────────────┬────────────────────────────────────┬────────────────────────────────────┬──────────────────────────────────────┐ │ frame_nr ┆ log_time ┆ /this/that:example.MyPoints:colors ┆ /this/that:example.MyPoints:labels ┆ /this/that:example.MyPoints:points │ │ --- ┆ --- ┆ --- ┆ --- ┆ --- │ -│ type: nullable i64 ┆ type: nullable Timestamp(ns) ┆ type: nullable List[nullable u32] ┆ type: nullable List[nullable Utf8] ┆ type: nullable List[nullable │ -│ index_name: frame_nr ┆ index_name: log_time ┆ archetype: example.MyPoints ┆ archetype: example.MyPoints ┆ Struct[2]] │ +│ type: Int64 ┆ type: Timestamp(ns) ┆ type: List(UInt32) ┆ type: List(Utf8) ┆ type: List(Struct("x": non-null │ +│ index_name: frame_nr ┆ index_name: log_time ┆ archetype: example.MyPoints ┆ archetype: example.MyPoints ┆ Float32, "y": non-null Float32)) │ │ kind: index ┆ kind: index ┆ component: example.MyPoints:colors ┆ component: example.MyPoints:labels ┆ archetype: example.MyPoints │ │ ┆ ┆ component_type: example.MyColor ┆ component_type: example.MyLabel ┆ component: example.MyPoints:points │ │ ┆ ┆ entity_path: /this/that ┆ entity_path: /this/that ┆ component_type: example.MyPoint │ diff --git a/crates/store/re_dataframe/src/snapshots/re_dataframe__query__tests__filtered_is_not_null.snap b/crates/store/re_dataframe/src/snapshots/re_dataframe__query__tests__filtered_is_not_null.snap index 836cd0232c01..3fb8b0226754 100644 --- a/crates/store/re_dataframe/src/snapshots/re_dataframe__query__tests__filtered_is_not_null.snap +++ b/crates/store/re_dataframe/src/snapshots/re_dataframe__query__tests__filtered_is_not_null.snap @@ -2,15 +2,15 @@ source: crates/store/re_dataframe/src/query.rs expression: DisplayRB(dataframe) --- -┌──────────────────────┬──────────────────────────────┬────────────────────────────────────┬────────────────────────────────────┬──────────────────────────────────────┐ -│ frame_nr ┆ log_time ┆ /this/that:example.MyPoints:colors ┆ /this/that:example.MyPoints:labels ┆ /this/that:example.MyPoints:points │ -│ --- ┆ --- ┆ --- ┆ --- ┆ --- │ -│ type: nullable i64 ┆ type: nullable Timestamp(ns) ┆ type: nullable List[nullable u32] ┆ type: nullable List[nullable Utf8] ┆ type: nullable List[nullable │ -│ index_name: frame_nr ┆ index_name: log_time ┆ archetype: example.MyPoints ┆ archetype: example.MyPoints ┆ Struct[2]] │ -│ kind: index ┆ kind: index ┆ component: example.MyPoints:colors ┆ component: example.MyPoints:labels ┆ archetype: example.MyPoints │ -│ ┆ ┆ component_type: example.MyColor ┆ component_type: example.MyLabel ┆ component: example.MyPoints:points │ -│ ┆ ┆ entity_path: /this/that ┆ entity_path: /this/that ┆ component_type: example.MyPoint │ -│ ┆ ┆ kind: data ┆ is_static: true ┆ entity_path: /this/that │ -│ ┆ ┆ ┆ kind: data ┆ kind: data │ -╞══════════════════════╪══════════════════════════════╪════════════════════════════════════╪════════════════════════════════════╪══════════════════════════════════════╡ -└──────────────────────┴──────────────────────────────┴────────────────────────────────────┴────────────────────────────────────┴──────────────────────────────────────┘ +┌──────────────────────┬──────────────────────┬────────────────────────────────────┬────────────────────────────────────┬──────────────────────────────────────┐ +│ frame_nr ┆ log_time ┆ /this/that:example.MyPoints:colors ┆ /this/that:example.MyPoints:labels ┆ /this/that:example.MyPoints:points │ +│ --- ┆ --- ┆ --- ┆ --- ┆ --- │ +│ type: Int64 ┆ type: Timestamp(ns) ┆ type: List(UInt32) ┆ type: List(Utf8) ┆ type: List(Struct("x": non-null │ +│ index_name: frame_nr ┆ index_name: log_time ┆ archetype: example.MyPoints ┆ archetype: example.MyPoints ┆ Float32, "y": non-null Float32)) │ +│ kind: index ┆ kind: index ┆ component: example.MyPoints:colors ┆ component: example.MyPoints:labels ┆ archetype: example.MyPoints │ +│ ┆ ┆ component_type: example.MyColor ┆ component_type: example.MyLabel ┆ component: example.MyPoints:points │ +│ ┆ ┆ entity_path: /this/that ┆ entity_path: /this/that ┆ component_type: example.MyPoint │ +│ ┆ ┆ kind: data ┆ is_static: true ┆ entity_path: /this/that │ +│ ┆ ┆ ┆ kind: data ┆ kind: data │ +╞══════════════════════╪══════════════════════╪════════════════════════════════════╪════════════════════════════════════╪══════════════════════════════════════╡ +└──────────────────────┴──────────────────────┴────────────────────────────────────┴────────────────────────────────────┴──────────────────────────────────────┘ diff --git a/crates/store/re_dataframe/src/snapshots/re_dataframe__query__tests__query_static_any_values.snap b/crates/store/re_dataframe/src/snapshots/re_dataframe__query__tests__query_static_any_values.snap index c8076eb62738..c1cb5f04b356 100644 --- a/crates/store/re_dataframe/src/snapshots/re_dataframe__query__tests__query_static_any_values.snap +++ b/crates/store/re_dataframe/src/snapshots/re_dataframe__query__tests__query_static_any_values.snap @@ -2,14 +2,14 @@ source: crates/store/re_dataframe/src/query.rs expression: DisplayRB(dataframe) --- -┌───────────────────────────────────┬────────────────────────────────────┬────────────────────────────────────┐ -│ /test:baz ┆ /test:foo ┆ /test:yak │ -│ --- ┆ --- ┆ --- │ -│ type: nullable List[nullable u32] ┆ type: nullable List[nullable Utf8] ┆ type: nullable List[nullable Utf8] │ -│ component: baz ┆ component: foo ┆ component: yak │ -│ entity_path: /test ┆ entity_path: /test ┆ entity_path: /test │ -│ is_static: true ┆ is_static: true ┆ is_static: true │ -│ kind: data ┆ kind: data ┆ kind: data │ -╞═══════════════════════════════════╪════════════════════════════════════╪════════════════════════════════════╡ -│ [42] ┆ [bar] ┆ [yuk] │ -└───────────────────────────────────┴────────────────────────────────────┴────────────────────────────────────┘ +┌────────────────────┬────────────────────┬────────────────────┐ +│ /test:baz ┆ /test:foo ┆ /test:yak │ +│ --- ┆ --- ┆ --- │ +│ type: List(UInt32) ┆ type: List(Utf8) ┆ type: List(Utf8) │ +│ component: baz ┆ component: foo ┆ component: yak │ +│ entity_path: /test ┆ entity_path: /test ┆ entity_path: /test │ +│ is_static: true ┆ is_static: true ┆ is_static: true │ +│ kind: data ┆ kind: data ┆ kind: data │ +╞════════════════════╪════════════════════╪════════════════════╡ +│ [42] ┆ [bar] ┆ [yuk] │ +└────────────────────┴────────────────────┴────────────────────┘ diff --git a/crates/store/re_dataframe/src/snapshots/re_dataframe__query__tests__selection-2.snap b/crates/store/re_dataframe/src/snapshots/re_dataframe__query__tests__selection-2.snap index 65e75ad2b108..0c868b0e924b 100644 --- a/crates/store/re_dataframe/src/snapshots/re_dataframe__query__tests__selection-2.snap +++ b/crates/store/re_dataframe/src/snapshots/re_dataframe__query__tests__selection-2.snap @@ -5,7 +5,7 @@ expression: DisplayRB(dataframe) ┌──────────────────────┬──────────────────────┬────────────────────────────────────────┐ │ frame_nr ┆ frame_nr ┆ ATimeColumnThatDoesntExist │ │ --- ┆ --- ┆ --- │ -│ type: nullable i64 ┆ type: nullable i64 ┆ type: nullable null │ +│ type: Int64 ┆ type: Int64 ┆ type: Null │ │ index_name: frame_nr ┆ index_name: frame_nr ┆ index_name: ATimeColumnThatDoesntExist │ │ kind: index ┆ kind: index ┆ is_sorted: true │ │ ┆ ┆ kind: index │ diff --git a/crates/store/re_dataframe/src/snapshots/re_dataframe__query__tests__selection-3.snap b/crates/store/re_dataframe/src/snapshots/re_dataframe__query__tests__selection-3.snap index 8746a67a19ca..43c058d3bccc 100644 --- a/crates/store/re_dataframe/src/snapshots/re_dataframe__query__tests__selection-3.snap +++ b/crates/store/re_dataframe/src/snapshots/re_dataframe__query__tests__selection-3.snap @@ -6,12 +6,13 @@ expression: DisplayRB(dataframe) │ /this/that:example.MyPoints: ┆ /this/that:example.MyPoints: ┆ /non_existing_entity:example ┆ /this/that:MyPoints:AFieldTh ┆ /this/that:AFieldThatDoesntE ┆ /this/that:AArchetypeNameTha │ │ points ┆ points ┆ .MyPoints:points ┆ atDoesntExist ┆ xist ┆ tDoesNotExist:positions │ │ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- │ -│ type: nullable List[nullable ┆ type: nullable List[nullable ┆ type: nullable null ┆ type: nullable null ┆ type: nullable null ┆ type: nullable null │ -│ Struct[2]] ┆ Struct[2]] ┆ component: ┆ component: MyPoints:AFieldTh ┆ component: ┆ component: AArchetypeNameTha │ -│ archetype: example.MyPoints ┆ archetype: example.MyPoints ┆ example.MyPoints:points ┆ atDoesntExist ┆ AFieldThatDoesntExist ┆ tDoesNotExist:positions │ -│ component: ┆ component: ┆ entity_path: ┆ entity_path: /this/that ┆ entity_path: /this/that ┆ entity_path: /this/that │ -│ example.MyPoints:points ┆ example.MyPoints:points ┆ /non_existing_entity ┆ kind: data ┆ kind: data ┆ kind: data │ -│ component_type: ┆ component_type: ┆ kind: data ┆ ┆ ┆ │ +│ type: List(Struct("x": ┆ type: List(Struct("x": ┆ type: Null ┆ type: Null ┆ type: Null ┆ type: Null │ +│ non-null Float32, "y": ┆ non-null Float32, "y": ┆ component: ┆ component: MyPoints:AFieldTh ┆ component: ┆ component: AArchetypeNameTha │ +│ non-null Float32)) ┆ non-null Float32)) ┆ example.MyPoints:points ┆ atDoesntExist ┆ AFieldThatDoesntExist ┆ tDoesNotExist:positions │ +│ archetype: example.MyPoints ┆ archetype: example.MyPoints ┆ entity_path: ┆ entity_path: /this/that ┆ entity_path: /this/that ┆ entity_path: /this/that │ +│ component: ┆ component: ┆ /non_existing_entity ┆ kind: data ┆ kind: data ┆ kind: data │ +│ example.MyPoints:points ┆ example.MyPoints:points ┆ kind: data ┆ ┆ ┆ │ +│ component_type: ┆ component_type: ┆ ┆ ┆ ┆ │ │ example.MyPoint ┆ example.MyPoint ┆ ┆ ┆ ┆ │ │ entity_path: /this/that ┆ entity_path: /this/that ┆ ┆ ┆ ┆ │ │ kind: data ┆ kind: data ┆ ┆ ┆ ┆ │ diff --git a/crates/store/re_dataframe/src/snapshots/re_dataframe__query__tests__selection-4.snap b/crates/store/re_dataframe/src/snapshots/re_dataframe__query__tests__selection-4.snap index 698a29b1ae1c..2b9d6d1810ac 100644 --- a/crates/store/re_dataframe/src/snapshots/re_dataframe__query__tests__selection-4.snap +++ b/crates/store/re_dataframe/src/snapshots/re_dataframe__query__tests__selection-4.snap @@ -5,11 +5,10 @@ expression: DisplayRB(dataframe) ┌────────────────┬────────────────┬────────────────┬────────────────┬────────────────┬────────────────┬────────────────┬────────────────┬────────────────┬────────────────┬────────────────┐ │ frame_nr ┆ frame_nr ┆ frame_nr ┆ frame_nr ┆ frame_nr ┆ frame_nr ┆ frame_nr ┆ frame_nr ┆ frame_nr ┆ frame_nr ┆ /this/that:exa │ │ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ mple.MyPoints: │ -│ type: nullable ┆ type: nullable ┆ type: nullable ┆ type: nullable ┆ type: nullable ┆ type: nullable ┆ type: nullable ┆ type: nullable ┆ type: nullable ┆ type: nullable ┆ labels │ -│ i64 ┆ i64 ┆ i64 ┆ i64 ┆ i64 ┆ i64 ┆ i64 ┆ i64 ┆ i64 ┆ i64 ┆ --- │ -│ index_name: ┆ index_name: ┆ index_name: ┆ index_name: ┆ index_name: ┆ index_name: ┆ index_name: ┆ index_name: ┆ index_name: ┆ index_name: ┆ type: nullable │ -│ frame_nr ┆ frame_nr ┆ frame_nr ┆ frame_nr ┆ frame_nr ┆ frame_nr ┆ frame_nr ┆ frame_nr ┆ frame_nr ┆ frame_nr ┆ List[nullable │ -│ kind: index ┆ kind: index ┆ kind: index ┆ kind: index ┆ kind: index ┆ kind: index ┆ kind: index ┆ kind: index ┆ kind: index ┆ kind: index ┆ Utf8] │ +│ type: Int64 ┆ type: Int64 ┆ type: Int64 ┆ type: Int64 ┆ type: Int64 ┆ type: Int64 ┆ type: Int64 ┆ type: Int64 ┆ type: Int64 ┆ type: Int64 ┆ labels │ +│ index_name: ┆ index_name: ┆ index_name: ┆ index_name: ┆ index_name: ┆ index_name: ┆ index_name: ┆ index_name: ┆ index_name: ┆ index_name: ┆ --- │ +│ frame_nr ┆ frame_nr ┆ frame_nr ┆ frame_nr ┆ frame_nr ┆ frame_nr ┆ frame_nr ┆ frame_nr ┆ frame_nr ┆ frame_nr ┆ type: │ +│ kind: index ┆ kind: index ┆ kind: index ┆ kind: index ┆ kind: index ┆ kind: index ┆ kind: index ┆ kind: index ┆ kind: index ┆ kind: index ┆ List(Utf8) │ │ ┆ ┆ ┆ ┆ ┆ ┆ ┆ ┆ ┆ ┆ archetype: exa │ │ ┆ ┆ ┆ ┆ ┆ ┆ ┆ ┆ ┆ ┆ mple.MyPoints │ │ ┆ ┆ ┆ ┆ ┆ ┆ ┆ ┆ ┆ ┆ component: exa │ diff --git a/crates/store/re_dataframe/src/snapshots/re_dataframe__query__tests__sparse_fill_strategy_latestatglobal.snap b/crates/store/re_dataframe/src/snapshots/re_dataframe__query__tests__sparse_fill_strategy_latestatglobal.snap index 6df39c78880d..a5dbb874af53 100644 --- a/crates/store/re_dataframe/src/snapshots/re_dataframe__query__tests__sparse_fill_strategy_latestatglobal.snap +++ b/crates/store/re_dataframe/src/snapshots/re_dataframe__query__tests__sparse_fill_strategy_latestatglobal.snap @@ -5,8 +5,8 @@ expression: DisplayRB(dataframe) ┌──────────────────────┬───────────────────────────────┬────────────────────────────────────┬────────────────────────────────────┬──────────────────────────────────────┐ │ frame_nr ┆ log_time ┆ /this/that:example.MyPoints:colors ┆ /this/that:example.MyPoints:labels ┆ /this/that:example.MyPoints:points │ │ --- ┆ --- ┆ --- ┆ --- ┆ --- │ -│ type: nullable i64 ┆ type: nullable Timestamp(ns) ┆ type: nullable List[nullable u32] ┆ type: nullable List[nullable Utf8] ┆ type: nullable List[nullable │ -│ index_name: frame_nr ┆ index_name: log_time ┆ archetype: example.MyPoints ┆ archetype: example.MyPoints ┆ Struct[2]] │ +│ type: Int64 ┆ type: Timestamp(ns) ┆ type: List(UInt32) ┆ type: List(Utf8) ┆ type: List(Struct("x": non-null │ +│ index_name: frame_nr ┆ index_name: log_time ┆ archetype: example.MyPoints ┆ archetype: example.MyPoints ┆ Float32, "y": non-null Float32)) │ │ kind: index ┆ kind: index ┆ component: example.MyPoints:colors ┆ component: example.MyPoints:labels ┆ archetype: example.MyPoints │ │ ┆ ┆ component_type: example.MyColor ┆ component_type: example.MyLabel ┆ component: example.MyPoints:points │ │ ┆ ┆ entity_path: /this/that ┆ entity_path: /this/that ┆ component_type: example.MyPoint │ diff --git a/crates/store/re_dataframe/src/snapshots/re_dataframe__query__tests__using_index_values-2.snap b/crates/store/re_dataframe/src/snapshots/re_dataframe__query__tests__using_index_values-2.snap index bb8a14c2567a..d20814a0f3a4 100644 --- a/crates/store/re_dataframe/src/snapshots/re_dataframe__query__tests__using_index_values-2.snap +++ b/crates/store/re_dataframe/src/snapshots/re_dataframe__query__tests__using_index_values-2.snap @@ -5,8 +5,8 @@ expression: DisplayRB(dataframe) ┌──────────────────────┬───────────────────────────────┬────────────────────────────────────┬────────────────────────────────────┬──────────────────────────────────────┐ │ frame_nr ┆ log_time ┆ /this/that:example.MyPoints:colors ┆ /this/that:example.MyPoints:labels ┆ /this/that:example.MyPoints:points │ │ --- ┆ --- ┆ --- ┆ --- ┆ --- │ -│ type: nullable i64 ┆ type: nullable Timestamp(ns) ┆ type: nullable List[nullable u32] ┆ type: nullable List[nullable Utf8] ┆ type: nullable List[nullable │ -│ index_name: frame_nr ┆ index_name: log_time ┆ archetype: example.MyPoints ┆ archetype: example.MyPoints ┆ Struct[2]] │ +│ type: Int64 ┆ type: Timestamp(ns) ┆ type: List(UInt32) ┆ type: List(Utf8) ┆ type: List(Struct("x": non-null │ +│ index_name: frame_nr ┆ index_name: log_time ┆ archetype: example.MyPoints ┆ archetype: example.MyPoints ┆ Float32, "y": non-null Float32)) │ │ kind: index ┆ kind: index ┆ component: example.MyPoints:colors ┆ component: example.MyPoints:labels ┆ archetype: example.MyPoints │ │ ┆ ┆ component_type: example.MyColor ┆ component_type: example.MyLabel ┆ component: example.MyPoints:points │ │ ┆ ┆ entity_path: /this/that ┆ entity_path: /this/that ┆ component_type: example.MyPoint │ diff --git a/crates/store/re_dataframe/src/snapshots/re_dataframe__query__tests__using_index_values.snap b/crates/store/re_dataframe/src/snapshots/re_dataframe__query__tests__using_index_values.snap index 782ec20c6dd4..bfb6f70bcd87 100644 --- a/crates/store/re_dataframe/src/snapshots/re_dataframe__query__tests__using_index_values.snap +++ b/crates/store/re_dataframe/src/snapshots/re_dataframe__query__tests__using_index_values.snap @@ -2,28 +2,28 @@ source: crates/store/re_dataframe/src/query.rs expression: DisplayRB(dataframe) --- -┌──────────────────────┬──────────────────────────────┬────────────────────────────────────┬────────────────────────────────────┬──────────────────────────────────────┐ -│ frame_nr ┆ log_time ┆ /this/that:example.MyPoints:colors ┆ /this/that:example.MyPoints:labels ┆ /this/that:example.MyPoints:points │ -│ --- ┆ --- ┆ --- ┆ --- ┆ --- │ -│ type: nullable i64 ┆ type: nullable Timestamp(ns) ┆ type: nullable List[nullable u32] ┆ type: nullable List[nullable Utf8] ┆ type: nullable List[nullable │ -│ index_name: frame_nr ┆ index_name: log_time ┆ archetype: example.MyPoints ┆ archetype: example.MyPoints ┆ Struct[2]] │ -│ kind: index ┆ kind: index ┆ component: example.MyPoints:colors ┆ component: example.MyPoints:labels ┆ archetype: example.MyPoints │ -│ ┆ ┆ component_type: example.MyColor ┆ component_type: example.MyLabel ┆ component: example.MyPoints:points │ -│ ┆ ┆ entity_path: /this/that ┆ entity_path: /this/that ┆ component_type: example.MyPoint │ -│ ┆ ┆ kind: data ┆ is_static: true ┆ entity_path: /this/that │ -│ ┆ ┆ ┆ kind: data ┆ kind: data │ -╞══════════════════════╪══════════════════════════════╪════════════════════════════════════╪════════════════════════════════════╪══════════════════════════════════════╡ -│ 0 ┆ null ┆ null ┆ [c] ┆ null │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ 15 ┆ null ┆ null ┆ [c] ┆ null │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ 30 ┆ null ┆ [2] ┆ [c] ┆ [{x: 2.0, y: 2.0}] │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ 45 ┆ null ┆ null ┆ [c] ┆ null │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ 60 ┆ null ┆ null ┆ [c] ┆ [{x: 5.0, y: 5.0}] │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ 75 ┆ null ┆ null ┆ [c] ┆ null │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ 90 ┆ null ┆ null ┆ [c] ┆ null │ -└──────────────────────┴──────────────────────────────┴────────────────────────────────────┴────────────────────────────────────┴──────────────────────────────────────┘ +┌──────────────────────┬──────────────────────┬────────────────────────────────────┬────────────────────────────────────┬──────────────────────────────────────┐ +│ frame_nr ┆ log_time ┆ /this/that:example.MyPoints:colors ┆ /this/that:example.MyPoints:labels ┆ /this/that:example.MyPoints:points │ +│ --- ┆ --- ┆ --- ┆ --- ┆ --- │ +│ type: Int64 ┆ type: Timestamp(ns) ┆ type: List(UInt32) ┆ type: List(Utf8) ┆ type: List(Struct("x": non-null │ +│ index_name: frame_nr ┆ index_name: log_time ┆ archetype: example.MyPoints ┆ archetype: example.MyPoints ┆ Float32, "y": non-null Float32)) │ +│ kind: index ┆ kind: index ┆ component: example.MyPoints:colors ┆ component: example.MyPoints:labels ┆ archetype: example.MyPoints │ +│ ┆ ┆ component_type: example.MyColor ┆ component_type: example.MyLabel ┆ component: example.MyPoints:points │ +│ ┆ ┆ entity_path: /this/that ┆ entity_path: /this/that ┆ component_type: example.MyPoint │ +│ ┆ ┆ kind: data ┆ is_static: true ┆ entity_path: /this/that │ +│ ┆ ┆ ┆ kind: data ┆ kind: data │ +╞══════════════════════╪══════════════════════╪════════════════════════════════════╪════════════════════════════════════╪══════════════════════════════════════╡ +│ 0 ┆ null ┆ null ┆ [c] ┆ null │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ 15 ┆ null ┆ null ┆ [c] ┆ null │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ 30 ┆ null ┆ [2] ┆ [c] ┆ [{x: 2.0, y: 2.0}] │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ 45 ┆ null ┆ null ┆ [c] ┆ null │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ 60 ┆ null ┆ null ┆ [c] ┆ [{x: 5.0, y: 5.0}] │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ 75 ┆ null ┆ null ┆ [c] ┆ null │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ 90 ┆ null ┆ null ┆ [c] ┆ null │ +└──────────────────────┴──────────────────────┴────────────────────────────────────┴────────────────────────────────────┴──────────────────────────────────────┘ diff --git a/crates/store/re_dataframe/src/snapshots/re_dataframe__query__tests__view_contents-2.snap b/crates/store/re_dataframe/src/snapshots/re_dataframe__query__tests__view_contents-2.snap index 26c55ccc23c8..d2c4cbecc3c7 100644 --- a/crates/store/re_dataframe/src/snapshots/re_dataframe__query__tests__view_contents-2.snap +++ b/crates/store/re_dataframe/src/snapshots/re_dataframe__query__tests__view_contents-2.snap @@ -2,22 +2,22 @@ source: crates/store/re_dataframe/src/query.rs expression: DisplayRB(dataframe) --- -┌──────────────────────┬──────────────────────────────┬────────────────────────────────────┬────────────────────────────────────┐ -│ frame_nr ┆ log_time ┆ /this/that:example.MyPoints:colors ┆ /this/that:example.MyPoints:labels │ -│ --- ┆ --- ┆ --- ┆ --- │ -│ type: nullable i64 ┆ type: nullable Timestamp(ns) ┆ type: nullable List[nullable u32] ┆ type: nullable List[nullable Utf8] │ -│ index_name: frame_nr ┆ index_name: log_time ┆ archetype: example.MyPoints ┆ archetype: example.MyPoints │ -│ kind: index ┆ kind: index ┆ component: example.MyPoints:colors ┆ component: example.MyPoints:labels │ -│ ┆ ┆ component_type: example.MyColor ┆ component_type: example.MyLabel │ -│ ┆ ┆ entity_path: /this/that ┆ entity_path: /this/that │ -│ ┆ ┆ kind: data ┆ is_static: true │ -│ ┆ ┆ ┆ kind: data │ -╞══════════════════════╪══════════════════════════════╪════════════════════════════════════╪════════════════════════════════════╡ -│ 30 ┆ null ┆ [2] ┆ [c] │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ 40 ┆ null ┆ [3] ┆ [c] │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ 50 ┆ null ┆ [4] ┆ [c] │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ 70 ┆ null ┆ [6] ┆ [c] │ -└──────────────────────┴──────────────────────────────┴────────────────────────────────────┴────────────────────────────────────┘ +┌──────────────────────┬──────────────────────┬────────────────────────────────────┬────────────────────────────────────┐ +│ frame_nr ┆ log_time ┆ /this/that:example.MyPoints:colors ┆ /this/that:example.MyPoints:labels │ +│ --- ┆ --- ┆ --- ┆ --- │ +│ type: Int64 ┆ type: Timestamp(ns) ┆ type: List(UInt32) ┆ type: List(Utf8) │ +│ index_name: frame_nr ┆ index_name: log_time ┆ archetype: example.MyPoints ┆ archetype: example.MyPoints │ +│ kind: index ┆ kind: index ┆ component: example.MyPoints:colors ┆ component: example.MyPoints:labels │ +│ ┆ ┆ component_type: example.MyColor ┆ component_type: example.MyLabel │ +│ ┆ ┆ entity_path: /this/that ┆ entity_path: /this/that │ +│ ┆ ┆ kind: data ┆ is_static: true │ +│ ┆ ┆ ┆ kind: data │ +╞══════════════════════╪══════════════════════╪════════════════════════════════════╪════════════════════════════════════╡ +│ 30 ┆ null ┆ [2] ┆ [c] │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ 40 ┆ null ┆ [3] ┆ [c] │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ 50 ┆ null ┆ [4] ┆ [c] │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ 70 ┆ null ┆ [6] ┆ [c] │ +└──────────────────────┴──────────────────────┴────────────────────────────────────┴────────────────────────────────────┘ diff --git a/crates/store/re_dataframe/src/snapshots/re_dataframe__query__tests__view_contents.snap b/crates/store/re_dataframe/src/snapshots/re_dataframe__query__tests__view_contents.snap index e5d49dcd761f..9ce694ec6eb0 100644 --- a/crates/store/re_dataframe/src/snapshots/re_dataframe__query__tests__view_contents.snap +++ b/crates/store/re_dataframe/src/snapshots/re_dataframe__query__tests__view_contents.snap @@ -2,11 +2,11 @@ source: crates/store/re_dataframe/src/query.rs expression: DisplayRB(dataframe) --- -┌──────────────────────┬──────────────────────────────┐ -│ frame_nr ┆ log_time │ -│ --- ┆ --- │ -│ type: nullable i64 ┆ type: nullable Timestamp(ns) │ -│ index_name: frame_nr ┆ index_name: log_time │ -│ kind: index ┆ kind: index │ -╞══════════════════════╪══════════════════════════════╡ -└──────────────────────┴──────────────────────────────┘ +┌──────────────────────┬──────────────────────┐ +│ frame_nr ┆ log_time │ +│ --- ┆ --- │ +│ type: Int64 ┆ type: Timestamp(ns) │ +│ index_name: frame_nr ┆ index_name: log_time │ +│ kind: index ┆ kind: index │ +╞══════════════════════╪══════════════════════╡ +└──────────────────────┴──────────────────────┘ diff --git a/crates/store/re_dataframe/src/snapshots/re_dataframe__query__tests__view_contents_and_selection.snap b/crates/store/re_dataframe/src/snapshots/re_dataframe__query__tests__view_contents_and_selection.snap index a91287905aa2..43b2d023d095 100644 --- a/crates/store/re_dataframe/src/snapshots/re_dataframe__query__tests__view_contents_and_selection.snap +++ b/crates/store/re_dataframe/src/snapshots/re_dataframe__query__tests__view_contents_and_selection.snap @@ -2,26 +2,25 @@ source: crates/store/re_dataframe/src/query.rs expression: DisplayRB(dataframe) --- -┌──────────────────────┬──────────────────────────────┬──────────────────────┬──────────────────────────────┬──────────────────────────────┬──────────────────────────────┐ -│ frame_nr ┆ log_time ┆ log_tick ┆ /this/that:example.MyPoints: ┆ /this/that:example.MyPoints: ┆ /this/that:example.MyPoints: │ -│ --- ┆ --- ┆ --- ┆ points ┆ colors ┆ labels │ -│ type: nullable i64 ┆ type: nullable Timestamp(ns) ┆ type: nullable null ┆ --- ┆ --- ┆ --- │ -│ index_name: frame_nr ┆ index_name: log_time ┆ index_name: log_tick ┆ type: nullable null ┆ type: nullable List[nullable ┆ type: nullable List[nullable │ -│ kind: index ┆ kind: index ┆ is_sorted: true ┆ component: ┆ u32] ┆ Utf8] │ -│ ┆ ┆ kind: index ┆ example.MyPoints:points ┆ archetype: example.MyPoints ┆ archetype: example.MyPoints │ -│ ┆ ┆ ┆ entity_path: /this/that ┆ component: ┆ component: │ -│ ┆ ┆ ┆ kind: data ┆ example.MyPoints:colors ┆ example.MyPoints:labels │ -│ ┆ ┆ ┆ ┆ component_type: ┆ component_type: │ -│ ┆ ┆ ┆ ┆ example.MyColor ┆ example.MyLabel │ -│ ┆ ┆ ┆ ┆ entity_path: /this/that ┆ entity_path: /this/that │ -│ ┆ ┆ ┆ ┆ kind: data ┆ is_static: true │ -│ ┆ ┆ ┆ ┆ ┆ kind: data │ -╞══════════════════════╪══════════════════════════════╪══════════════════════╪══════════════════════════════╪══════════════════════════════╪══════════════════════════════╡ -│ 30 ┆ null ┆ null ┆ null ┆ [2] ┆ [c] │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ 40 ┆ null ┆ null ┆ null ┆ [3] ┆ [c] │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ 50 ┆ null ┆ null ┆ null ┆ [4] ┆ [c] │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ 70 ┆ null ┆ null ┆ null ┆ [6] ┆ [c] │ -└──────────────────────┴──────────────────────────────┴──────────────────────┴──────────────────────────────┴──────────────────────────────┴──────────────────────────────┘ +┌──────────────────────┬──────────────────────┬──────────────────────┬──────────────────────────────┬──────────────────────────────┬──────────────────────────────┐ +│ frame_nr ┆ log_time ┆ log_tick ┆ /this/that:example.MyPoints: ┆ /this/that:example.MyPoints: ┆ /this/that:example.MyPoints: │ +│ --- ┆ --- ┆ --- ┆ points ┆ colors ┆ labels │ +│ type: Int64 ┆ type: Timestamp(ns) ┆ type: Null ┆ --- ┆ --- ┆ --- │ +│ index_name: frame_nr ┆ index_name: log_time ┆ index_name: log_tick ┆ type: Null ┆ type: List(UInt32) ┆ type: List(Utf8) │ +│ kind: index ┆ kind: index ┆ is_sorted: true ┆ component: ┆ archetype: example.MyPoints ┆ archetype: example.MyPoints │ +│ ┆ ┆ kind: index ┆ example.MyPoints:points ┆ component: ┆ component: │ +│ ┆ ┆ ┆ entity_path: /this/that ┆ example.MyPoints:colors ┆ example.MyPoints:labels │ +│ ┆ ┆ ┆ kind: data ┆ component_type: ┆ component_type: │ +│ ┆ ┆ ┆ ┆ example.MyColor ┆ example.MyLabel │ +│ ┆ ┆ ┆ ┆ entity_path: /this/that ┆ entity_path: /this/that │ +│ ┆ ┆ ┆ ┆ kind: data ┆ is_static: true │ +│ ┆ ┆ ┆ ┆ ┆ kind: data │ +╞══════════════════════╪══════════════════════╪══════════════════════╪══════════════════════════════╪══════════════════════════════╪══════════════════════════════╡ +│ 30 ┆ null ┆ null ┆ null ┆ [2] ┆ [c] │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ 40 ┆ null ┆ null ┆ null ┆ [3] ┆ [c] │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ 50 ┆ null ┆ null ┆ null ┆ [4] ┆ [c] │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ 70 ┆ null ┆ null ┆ null ┆ [6] ┆ [c] │ +└──────────────────────┴──────────────────────┴──────────────────────┴──────────────────────────────┴──────────────────────────────┴──────────────────────────────┘ diff --git a/crates/store/re_entity_db/Cargo.toml b/crates/store/re_entity_db/Cargo.toml index caa5d246ba05..624d331e6870 100644 --- a/crates/store/re_entity_db/Cargo.toml +++ b/crates/store/re_entity_db/Cargo.toml @@ -66,6 +66,7 @@ web-time.workspace = true re_log_encoding = { workspace = true, features = ["decoder", "encoder"] } anyhow.workspace = true +insta.workspace = true similar-asserts.workspace = true [lib] diff --git a/crates/store/re_entity_db/src/entity_db.rs b/crates/store/re_entity_db/src/entity_db.rs index e6b44e39da8e..a47216208e84 100644 --- a/crates/store/re_entity_db/src/entity_db.rs +++ b/crates/store/re_entity_db/src/entity_db.rs @@ -253,10 +253,7 @@ impl EntityDb { { let name = component_type .map_or_else(|| component.to_string(), |ct| ct.short_name().to_owned()); - text.push_str(&format!( - "{component_indent}{name}: {}\n", - re_arrow_util::format_data_type(&datatype) - )); + text.push_str(&format!("{component_indent}{name}: {datatype}\n")); } else { // Fallback to component identifier text.push_str(&format!("{component_indent}{component}\n")); @@ -1377,10 +1374,12 @@ mod tests { db.add_chunk(&Arc::new(chunk))?; } - assert_eq!( - db.format_with_components(), - "/parent\n /parent/child1\n /parent/child1/grandchild\n example.MyPoint: Struct[2]\n" - ); + insta::assert_snapshot!(db.format_with_components(), @r###" + /parent + /parent/child1 + /parent/child1/grandchild + example.MyPoint: Struct("x": non-null Float32, "y": non-null Float32) + "###); Ok(()) } diff --git a/crates/store/re_lenses/Cargo.toml b/crates/store/re_lenses/Cargo.toml index cc2be209f3fc..c3e492ba2ca2 100644 --- a/crates/store/re_lenses/Cargo.toml +++ b/crates/store/re_lenses/Cargo.toml @@ -19,7 +19,6 @@ all-features = true [dependencies] re_arrow_combinators.workspace = true -re_arrow_util.workspace = true re_chunk.workspace = true re_log.workspace = true re_log_types.workspace = true diff --git a/crates/store/re_lenses/src/ast.rs b/crates/store/re_lenses/src/ast.rs index ed919c18cbdc..52c80f11959c 100644 --- a/crates/store/re_lenses/src/ast.rs +++ b/crates/store/re_lenses/src/ast.rs @@ -468,7 +468,7 @@ fn try_convert_time_column( } else { Err(LensError::InvalidTimeColumn { timeline_name, - actual_type: list_array.values().data_type().clone().into(), + actual_type: list_array.values().data_type().clone(), }) } } diff --git a/crates/store/re_lenses/src/error.rs b/crates/store/re_lenses/src/error.rs index 2998cd0d61f9..f60a106385db 100644 --- a/crates/store/re_lenses/src/error.rs +++ b/crates/store/re_lenses/src/error.rs @@ -1,4 +1,4 @@ -use re_arrow_util::DisplayDataType; +use arrow::datatypes::DataType; use re_chunk::{ComponentIdentifier, EntityPath, TimelineName}; use re_log_types::EntityPathFilter; @@ -55,7 +55,7 @@ pub enum LensError { )] InvalidTimeColumn { timeline_name: TimelineName, - actual_type: DisplayDataType, + actual_type: DataType, }, #[error("Failed to scatter existing timeline '{timeline_name}' across output rows")] diff --git a/crates/store/re_log_encoding/tests/snapshots/footers_and_manifests__rrd_manifest_blueprint_schema.snap b/crates/store/re_log_encoding/tests/snapshots/footers_and_manifests__rrd_manifest_blueprint_schema.snap index 9f4efc6799e1..15d182c51389 100644 --- a/crates/store/re_log_encoding/tests/snapshots/footers_and_manifests__rrd_manifest_blueprint_schema.snap +++ b/crates/store/re_log_encoding/tests/snapshots/footers_and_manifests__rrd_manifest_blueprint_schema.snap @@ -2,40 +2,40 @@ source: crates/store/re_log_encoding/tests/footers_and_manifests.rs expression: rrd_manifest_blueprint_sequential.data.format_schema_snapshot() --- -TimePanelBlueprint:fps:has_static_data: bool [ +TimePanelBlueprint:fps:has_static_data: non-null Boolean [ rerun:archetype: "rerun.blueprint.archetypes.TimePanelBlueprint" rerun:component: "TimePanelBlueprint:fps" rerun:component_type: "rerun.blueprint.components.Fps" rerun:index: "rerun:static" ] -chunk_byte_offset: u64 -chunk_byte_size: u64 -chunk_byte_size_uncompressed: u64 -chunk_entity_path: Utf8 -chunk_id: FixedSizeBinary[16] -chunk_is_static: bool -chunk_num_rows: u64 -frame_nr:TimePanelBlueprint:fps:end: nullable i64 [ +chunk_byte_offset: non-null UInt64 +chunk_byte_size: non-null UInt64 +chunk_byte_size_uncompressed: non-null UInt64 +chunk_entity_path: non-null Utf8 +chunk_id: non-null FixedSizeBinary(16) +chunk_is_static: non-null Boolean +chunk_num_rows: non-null UInt64 +frame_nr:TimePanelBlueprint:fps:end: Int64 [ rerun:archetype: "rerun.blueprint.archetypes.TimePanelBlueprint" rerun:component: "TimePanelBlueprint:fps" rerun:component_type: "rerun.blueprint.components.Fps" rerun:index: "frame_nr" ] -frame_nr:TimePanelBlueprint:fps:num_rows: nullable u64 [ +frame_nr:TimePanelBlueprint:fps:num_rows: UInt64 [ rerun:archetype: "rerun.blueprint.archetypes.TimePanelBlueprint" rerun:component: "TimePanelBlueprint:fps" rerun:component_type: "rerun.blueprint.components.Fps" rerun:index: "frame_nr" ] -frame_nr:TimePanelBlueprint:fps:start: nullable i64 [ +frame_nr:TimePanelBlueprint:fps:start: Int64 [ rerun:archetype: "rerun.blueprint.archetypes.TimePanelBlueprint" rerun:component: "TimePanelBlueprint:fps" rerun:component_type: "rerun.blueprint.components.Fps" rerun:index: "frame_nr" ] -frame_nr:end: nullable i64 [ +frame_nr:end: Int64 [ rerun:index: "frame_nr" ] -frame_nr:start: nullable i64 [ +frame_nr:start: Int64 [ rerun:index: "frame_nr" ] diff --git a/crates/store/re_log_encoding/tests/snapshots/footers_and_manifests__rrd_manifest_recording_schema.snap b/crates/store/re_log_encoding/tests/snapshots/footers_and_manifests__rrd_manifest_recording_schema.snap index d2fbf335fd0f..d1c6326d5c06 100644 --- a/crates/store/re_log_encoding/tests/snapshots/footers_and_manifests__rrd_manifest_recording_schema.snap +++ b/crates/store/re_log_encoding/tests/snapshots/footers_and_manifests__rrd_manifest_recording_schema.snap @@ -2,154 +2,154 @@ source: crates/store/re_log_encoding/tests/footers_and_manifests.rs expression: rrd_manifest_recording_sequential.data.format_schema_snapshot() --- -chunk_byte_offset: u64 -chunk_byte_size: u64 -chunk_byte_size_uncompressed: u64 -chunk_entity_path: Utf8 -chunk_id: FixedSizeBinary[16] -chunk_is_static: bool -chunk_num_rows: u64 -elapsed_time:end: nullable Duration(ns) [ +chunk_byte_offset: non-null UInt64 +chunk_byte_size: non-null UInt64 +chunk_byte_size_uncompressed: non-null UInt64 +chunk_entity_path: non-null Utf8 +chunk_id: non-null FixedSizeBinary(16) +chunk_is_static: non-null Boolean +chunk_num_rows: non-null UInt64 +elapsed_time:end: Duration(ns) [ rerun:index: "elapsed time" ] -elapsed_time:example_MyPoints:colors:end: nullable Duration(ns) [ +elapsed_time:example_MyPoints:colors:end: Duration(ns) [ rerun:archetype: "example.MyPoints" rerun:component: "example.MyPoints:colors" rerun:component_type: "example.MyColor" rerun:index: "elapsed time" ] -elapsed_time:example_MyPoints:colors:num_rows: nullable u64 [ +elapsed_time:example_MyPoints:colors:num_rows: UInt64 [ rerun:archetype: "example.MyPoints" rerun:component: "example.MyPoints:colors" rerun:component_type: "example.MyColor" rerun:index: "elapsed time" ] -elapsed_time:example_MyPoints:colors:start: nullable Duration(ns) [ +elapsed_time:example_MyPoints:colors:start: Duration(ns) [ rerun:archetype: "example.MyPoints" rerun:component: "example.MyPoints:colors" rerun:component_type: "example.MyColor" rerun:index: "elapsed time" ] -elapsed_time:example_MyPoints:points:end: nullable Duration(ns) [ +elapsed_time:example_MyPoints:points:end: Duration(ns) [ rerun:archetype: "example.MyPoints" rerun:component: "example.MyPoints:points" rerun:component_type: "example.MyPoint" rerun:index: "elapsed time" ] -elapsed_time:example_MyPoints:points:num_rows: nullable u64 [ +elapsed_time:example_MyPoints:points:num_rows: UInt64 [ rerun:archetype: "example.MyPoints" rerun:component: "example.MyPoints:points" rerun:component_type: "example.MyPoint" rerun:index: "elapsed time" ] -elapsed_time:example_MyPoints:points:start: nullable Duration(ns) [ +elapsed_time:example_MyPoints:points:start: Duration(ns) [ rerun:archetype: "example.MyPoints" rerun:component: "example.MyPoints:points" rerun:component_type: "example.MyPoint" rerun:index: "elapsed time" ] -elapsed_time:start: nullable Duration(ns) [ +elapsed_time:start: Duration(ns) [ rerun:index: "elapsed time" ] -example_MyPoints:colors:has_static_data: bool [ +example_MyPoints:colors:has_static_data: non-null Boolean [ rerun:archetype: "example.MyPoints" rerun:component: "example.MyPoints:colors" rerun:component_type: "example.MyColor" rerun:index: "rerun:static" ] -example_MyPoints:labels:has_static_data: bool [ +example_MyPoints:labels:has_static_data: non-null Boolean [ rerun:archetype: "example.MyPoints" rerun:component: "example.MyPoints:labels" rerun:component_type: "example.MyLabel" rerun:index: "rerun:static" ] -example_MyPoints:points:has_static_data: bool [ +example_MyPoints:points:has_static_data: non-null Boolean [ rerun:archetype: "example.MyPoints" rerun:component: "example.MyPoints:points" rerun:component_type: "example.MyPoint" rerun:index: "rerun:static" ] -frame_nr:end: nullable i64 [ +frame_nr:end: Int64 [ rerun:index: "frame_nr" ] -frame_nr:example_MyPoints:colors:end: nullable i64 [ +frame_nr:example_MyPoints:colors:end: Int64 [ rerun:archetype: "example.MyPoints" rerun:component: "example.MyPoints:colors" rerun:component_type: "example.MyColor" rerun:index: "frame_nr" ] -frame_nr:example_MyPoints:colors:num_rows: nullable u64 [ +frame_nr:example_MyPoints:colors:num_rows: UInt64 [ rerun:archetype: "example.MyPoints" rerun:component: "example.MyPoints:colors" rerun:component_type: "example.MyColor" rerun:index: "frame_nr" ] -frame_nr:example_MyPoints:colors:start: nullable i64 [ +frame_nr:example_MyPoints:colors:start: Int64 [ rerun:archetype: "example.MyPoints" rerun:component: "example.MyPoints:colors" rerun:component_type: "example.MyColor" rerun:index: "frame_nr" ] -frame_nr:example_MyPoints:points:end: nullable i64 [ +frame_nr:example_MyPoints:points:end: Int64 [ rerun:archetype: "example.MyPoints" rerun:component: "example.MyPoints:points" rerun:component_type: "example.MyPoint" rerun:index: "frame_nr" ] -frame_nr:example_MyPoints:points:num_rows: nullable u64 [ +frame_nr:example_MyPoints:points:num_rows: UInt64 [ rerun:archetype: "example.MyPoints" rerun:component: "example.MyPoints:points" rerun:component_type: "example.MyPoint" rerun:index: "frame_nr" ] -frame_nr:example_MyPoints:points:start: nullable i64 [ +frame_nr:example_MyPoints:points:start: Int64 [ rerun:archetype: "example.MyPoints" rerun:component: "example.MyPoints:points" rerun:component_type: "example.MyPoint" rerun:index: "frame_nr" ] -frame_nr:start: nullable i64 [ +frame_nr:start: Int64 [ rerun:index: "frame_nr" ] -log_time:end: nullable Timestamp(ns) [ +log_time:end: Timestamp(ns) [ rerun:index: "log_time" ] -log_time:example_MyPoints:colors:end: nullable Timestamp(ns) [ +log_time:example_MyPoints:colors:end: Timestamp(ns) [ rerun:archetype: "example.MyPoints" rerun:component: "example.MyPoints:colors" rerun:component_type: "example.MyColor" rerun:index: "log_time" ] -log_time:example_MyPoints:colors:num_rows: nullable u64 [ +log_time:example_MyPoints:colors:num_rows: UInt64 [ rerun:archetype: "example.MyPoints" rerun:component: "example.MyPoints:colors" rerun:component_type: "example.MyColor" rerun:index: "log_time" ] -log_time:example_MyPoints:colors:start: nullable Timestamp(ns) [ +log_time:example_MyPoints:colors:start: Timestamp(ns) [ rerun:archetype: "example.MyPoints" rerun:component: "example.MyPoints:colors" rerun:component_type: "example.MyColor" rerun:index: "log_time" ] -log_time:example_MyPoints:points:end: nullable Timestamp(ns) [ +log_time:example_MyPoints:points:end: Timestamp(ns) [ rerun:archetype: "example.MyPoints" rerun:component: "example.MyPoints:points" rerun:component_type: "example.MyPoint" rerun:index: "log_time" ] -log_time:example_MyPoints:points:num_rows: nullable u64 [ +log_time:example_MyPoints:points:num_rows: UInt64 [ rerun:archetype: "example.MyPoints" rerun:component: "example.MyPoints:points" rerun:component_type: "example.MyPoint" rerun:index: "log_time" ] -log_time:example_MyPoints:points:start: nullable Timestamp(ns) [ +log_time:example_MyPoints:points:start: Timestamp(ns) [ rerun:archetype: "example.MyPoints" rerun:component: "example.MyPoints:points" rerun:component_type: "example.MyPoint" rerun:index: "log_time" ] -log_time:start: nullable Timestamp(ns) [ +log_time:start: Timestamp(ns) [ rerun:index: "log_time" ] diff --git a/crates/store/re_log_encoding/tests/snapshots/footers_and_manifests__simple_manifest_batch_schema.snap b/crates/store/re_log_encoding/tests/snapshots/footers_and_manifests__simple_manifest_batch_schema.snap index 747b93d1227a..edb89f8e796f 100644 --- a/crates/store/re_log_encoding/tests/snapshots/footers_and_manifests__simple_manifest_batch_schema.snap +++ b/crates/store/re_log_encoding/tests/snapshots/footers_and_manifests__simple_manifest_batch_schema.snap @@ -2,154 +2,154 @@ source: crates/store/re_log_encoding/tests/footers_and_manifests.rs expression: rrd_manifest_batch.format_schema_snapshot() --- -chunk_byte_offset: u64 -chunk_byte_size: u64 -chunk_byte_size_uncompressed: u64 -chunk_entity_path: Utf8 -chunk_id: FixedSizeBinary[16] -chunk_is_static: bool -chunk_num_rows: u64 -elapsed_time:end: nullable Duration(ns) [ +chunk_byte_offset: non-null UInt64 +chunk_byte_size: non-null UInt64 +chunk_byte_size_uncompressed: non-null UInt64 +chunk_entity_path: non-null Utf8 +chunk_id: non-null FixedSizeBinary(16) +chunk_is_static: non-null Boolean +chunk_num_rows: non-null UInt64 +elapsed_time:end: Duration(ns) [ rerun:index: "elapsed time" ] -elapsed_time:example_MyPoints:colors:end: nullable Duration(ns) [ +elapsed_time:example_MyPoints:colors:end: Duration(ns) [ rerun:archetype: "example.MyPoints" rerun:component: "example.MyPoints:colors" rerun:component_type: "example.MyColor" rerun:index: "elapsed time" ] -elapsed_time:example_MyPoints:colors:num_rows: nullable u64 [ +elapsed_time:example_MyPoints:colors:num_rows: UInt64 [ rerun:archetype: "example.MyPoints" rerun:component: "example.MyPoints:colors" rerun:component_type: "example.MyColor" rerun:index: "elapsed time" ] -elapsed_time:example_MyPoints:colors:start: nullable Duration(ns) [ +elapsed_time:example_MyPoints:colors:start: Duration(ns) [ rerun:archetype: "example.MyPoints" rerun:component: "example.MyPoints:colors" rerun:component_type: "example.MyColor" rerun:index: "elapsed time" ] -elapsed_time:example_MyPoints:points:end: nullable Duration(ns) [ +elapsed_time:example_MyPoints:points:end: Duration(ns) [ rerun:archetype: "example.MyPoints" rerun:component: "example.MyPoints:points" rerun:component_type: "example.MyPoint" rerun:index: "elapsed time" ] -elapsed_time:example_MyPoints:points:num_rows: nullable u64 [ +elapsed_time:example_MyPoints:points:num_rows: UInt64 [ rerun:archetype: "example.MyPoints" rerun:component: "example.MyPoints:points" rerun:component_type: "example.MyPoint" rerun:index: "elapsed time" ] -elapsed_time:example_MyPoints:points:start: nullable Duration(ns) [ +elapsed_time:example_MyPoints:points:start: Duration(ns) [ rerun:archetype: "example.MyPoints" rerun:component: "example.MyPoints:points" rerun:component_type: "example.MyPoint" rerun:index: "elapsed time" ] -elapsed_time:start: nullable Duration(ns) [ +elapsed_time:start: Duration(ns) [ rerun:index: "elapsed time" ] -example_MyPoints:colors:has_static_data: bool [ +example_MyPoints:colors:has_static_data: non-null Boolean [ rerun:archetype: "example.MyPoints" rerun:component: "example.MyPoints:colors" rerun:component_type: "example.MyColor" rerun:index: "rerun:static" ] -example_MyPoints:labels:has_static_data: bool [ +example_MyPoints:labels:has_static_data: non-null Boolean [ rerun:archetype: "example.MyPoints" rerun:component: "example.MyPoints:labels" rerun:component_type: "example.MyLabel" rerun:index: "rerun:static" ] -example_MyPoints:points:has_static_data: bool [ +example_MyPoints:points:has_static_data: non-null Boolean [ rerun:archetype: "example.MyPoints" rerun:component: "example.MyPoints:points" rerun:component_type: "example.MyPoint" rerun:index: "rerun:static" ] -frame_nr:end: nullable i64 [ +frame_nr:end: Int64 [ rerun:index: "frame_nr" ] -frame_nr:example_MyPoints:colors:end: nullable i64 [ +frame_nr:example_MyPoints:colors:end: Int64 [ rerun:archetype: "example.MyPoints" rerun:component: "example.MyPoints:colors" rerun:component_type: "example.MyColor" rerun:index: "frame_nr" ] -frame_nr:example_MyPoints:colors:num_rows: nullable u64 [ +frame_nr:example_MyPoints:colors:num_rows: UInt64 [ rerun:archetype: "example.MyPoints" rerun:component: "example.MyPoints:colors" rerun:component_type: "example.MyColor" rerun:index: "frame_nr" ] -frame_nr:example_MyPoints:colors:start: nullable i64 [ +frame_nr:example_MyPoints:colors:start: Int64 [ rerun:archetype: "example.MyPoints" rerun:component: "example.MyPoints:colors" rerun:component_type: "example.MyColor" rerun:index: "frame_nr" ] -frame_nr:example_MyPoints:points:end: nullable i64 [ +frame_nr:example_MyPoints:points:end: Int64 [ rerun:archetype: "example.MyPoints" rerun:component: "example.MyPoints:points" rerun:component_type: "example.MyPoint" rerun:index: "frame_nr" ] -frame_nr:example_MyPoints:points:num_rows: nullable u64 [ +frame_nr:example_MyPoints:points:num_rows: UInt64 [ rerun:archetype: "example.MyPoints" rerun:component: "example.MyPoints:points" rerun:component_type: "example.MyPoint" rerun:index: "frame_nr" ] -frame_nr:example_MyPoints:points:start: nullable i64 [ +frame_nr:example_MyPoints:points:start: Int64 [ rerun:archetype: "example.MyPoints" rerun:component: "example.MyPoints:points" rerun:component_type: "example.MyPoint" rerun:index: "frame_nr" ] -frame_nr:start: nullable i64 [ +frame_nr:start: Int64 [ rerun:index: "frame_nr" ] -log_time:end: nullable Timestamp(ns) [ +log_time:end: Timestamp(ns) [ rerun:index: "log_time" ] -log_time:example_MyPoints:colors:end: nullable Timestamp(ns) [ +log_time:example_MyPoints:colors:end: Timestamp(ns) [ rerun:archetype: "example.MyPoints" rerun:component: "example.MyPoints:colors" rerun:component_type: "example.MyColor" rerun:index: "log_time" ] -log_time:example_MyPoints:colors:num_rows: nullable u64 [ +log_time:example_MyPoints:colors:num_rows: UInt64 [ rerun:archetype: "example.MyPoints" rerun:component: "example.MyPoints:colors" rerun:component_type: "example.MyColor" rerun:index: "log_time" ] -log_time:example_MyPoints:colors:start: nullable Timestamp(ns) [ +log_time:example_MyPoints:colors:start: Timestamp(ns) [ rerun:archetype: "example.MyPoints" rerun:component: "example.MyPoints:colors" rerun:component_type: "example.MyColor" rerun:index: "log_time" ] -log_time:example_MyPoints:points:end: nullable Timestamp(ns) [ +log_time:example_MyPoints:points:end: Timestamp(ns) [ rerun:archetype: "example.MyPoints" rerun:component: "example.MyPoints:points" rerun:component_type: "example.MyPoint" rerun:index: "log_time" ] -log_time:example_MyPoints:points:num_rows: nullable u64 [ +log_time:example_MyPoints:points:num_rows: UInt64 [ rerun:archetype: "example.MyPoints" rerun:component: "example.MyPoints:points" rerun:component_type: "example.MyPoint" rerun:index: "log_time" ] -log_time:example_MyPoints:points:start: nullable Timestamp(ns) [ +log_time:example_MyPoints:points:start: Timestamp(ns) [ rerun:archetype: "example.MyPoints" rerun:component: "example.MyPoints:points" rerun:component_type: "example.MyPoint" rerun:index: "log_time" ] -log_time:start: nullable Timestamp(ns) [ +log_time:start: Timestamp(ns) [ rerun:index: "log_time" ] diff --git a/crates/store/re_mcap/src/layers/snapshots/re_mcap__layers__protobuf__integration_tests__decode_failure_resilience.snap b/crates/store/re_mcap/src/layers/snapshots/re_mcap__layers__protobuf__integration_tests__decode_failure_resilience.snap index 8ce10acdcc35..0cf7ebb2f75b 100644 --- a/crates/store/re_mcap/src/layers/snapshots/re_mcap__layers__protobuf__integration_tests__decode_failure_resilience.snap +++ b/crates/store/re_mcap/src/layers/snapshots/re_mcap__layers__protobuf__integration_tests__decode_failure_resilience.snap @@ -2,29 +2,32 @@ source: crates/store/re_mcap/src/layers/protobuf.rs expression: "format!(\"{:-240}\", &chunks[0])" --- -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ METADATA: │ -│ * entity_path: /test_topic │ -│ * id: [**REDACTED**] │ -│ * version: [**REDACTED**] │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ ┌───────────────────────────────────────────────┬──────────────────────────────┬──────────────────────────────────┬───────────────────────────────────────────────────────┐ │ -│ │ RowId ┆ message_log_time ┆ message_publish_time ┆ com.example.Person:message │ │ -│ │ --- ┆ --- ┆ --- ┆ --- │ │ -│ │ type: FixedSizeBinary[16] ┆ type: nullable Duration(ns) ┆ type: nullable Duration(ns) ┆ type: nullable List[nullable Struct[4]] │ │ -│ │ ARROW:extension:metadata: {"namespace":"row"} ┆ index_name: message_log_time ┆ index_name: message_publish_time ┆ archetype: com.example.Person │ │ -│ │ ARROW:extension:name: TUID ┆ is_sorted: true ┆ is_sorted: true ┆ component: com.example.Person:message │ │ -│ │ is_sorted: true ┆ kind: index ┆ kind: index ┆ kind: data │ │ -│ │ kind: control ┆ ┆ ┆ │ │ -│ ╞═══════════════════════════════════════════════╪══════════════════════════════╪══════════════════════════════════╪═══════════════════════════════════════════════════════╡ │ -│ │ row_[**REDACTED**] ┆ PT0.000000101S ┆ PT0.000000101S ┆ [{name: Person1, id: 1, status: null, address: null}] │ │ -│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ -│ │ row_[**REDACTED**] ┆ PT0.000000103S ┆ PT0.000000103S ┆ [{name: Person3, id: 3, status: null, address: null}] │ │ -│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ -│ │ row_[**REDACTED**] ┆ PT0.000000105S ┆ PT0.000000105S ┆ [{name: Person5, id: 5, status: null, address: null}] │ │ -│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ -│ │ row_[**REDACTED**] ┆ PT0.000000107S ┆ PT0.000000107S ┆ [{name: Person7, id: 7, status: null, address: null}] │ │ -│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ -│ │ row_[**REDACTED**] ┆ PT0.000000109S ┆ PT0.000000109S ┆ [{name: Person9, id: 9, status: null, address: null}] │ │ -│ └───────────────────────────────────────────────┴──────────────────────────────┴──────────────────────────────────┴───────────────────────────────────────────────────────┘ │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +┌────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ METADATA: │ +│ * entity_path: /test_topic │ +│ * id: [**REDACTED**] │ +│ * version: [**REDACTED**] │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ ┌───────────────────────────────────────────────┬──────────────────────────────┬──────────────────────────────────┬──────────────────────────────────────────────────────────┐ │ +│ │ RowId ┆ message_log_time ┆ message_publish_time ┆ com.example.Person:message │ │ +│ │ --- ┆ --- ┆ --- ┆ --- │ │ +│ │ type: non-null FixedSizeBinary(16) ┆ type: Duration(ns) ┆ type: Duration(ns) ┆ type: List(Struct("name": Utf8, "id": Int32, "status": │ │ +│ │ ARROW:extension:metadata: {"namespace":"row"} ┆ index_name: message_log_time ┆ index_name: message_publish_time ┆ Struct("name": Utf8, "value": Int32), metadata: │ │ +│ │ ARROW:extension:name: TUID ┆ is_sorted: true ┆ is_sorted: true ┆ {"ARROW:extension:name": │ │ +│ │ is_sorted: true ┆ kind: index ┆ kind: index ┆ "rerun.datatypes.ProtobufEnum"}, "address": │ │ +│ │ kind: control ┆ ┆ ┆ Struct("street": Utf8, "city": Utf8))) │ │ +│ │ ┆ ┆ ┆ archetype: com.example.Person │ │ +│ │ ┆ ┆ ┆ component: com.example.Person:message │ │ +│ │ ┆ ┆ ┆ kind: data │ │ +│ ╞═══════════════════════════════════════════════╪══════════════════════════════╪══════════════════════════════════╪══════════════════════════════════════════════════════════╡ │ +│ │ row_[**REDACTED**] ┆ PT0.000000101S ┆ PT0.000000101S ┆ [{name: Person1, id: 1, status: null, address: null}] │ │ +│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ +│ │ row_[**REDACTED**] ┆ PT0.000000103S ┆ PT0.000000103S ┆ [{name: Person3, id: 3, status: null, address: null}] │ │ +│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ +│ │ row_[**REDACTED**] ┆ PT0.000000105S ┆ PT0.000000105S ┆ [{name: Person5, id: 5, status: null, address: null}] │ │ +│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ +│ │ row_[**REDACTED**] ┆ PT0.000000107S ┆ PT0.000000107S ┆ [{name: Person7, id: 7, status: null, address: null}] │ │ +│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ +│ │ row_[**REDACTED**] ┆ PT0.000000109S ┆ PT0.000000109S ┆ [{name: Person9, id: 9, status: null, address: null}] │ │ +│ └───────────────────────────────────────────────┴──────────────────────────────┴──────────────────────────────────┴──────────────────────────────────────────────────────────┘ │ +└────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ diff --git a/crates/store/re_mcap/src/layers/snapshots/re_mcap__layers__protobuf__integration_tests__field_combinations_with_presence_tracking.snap b/crates/store/re_mcap/src/layers/snapshots/re_mcap__layers__protobuf__integration_tests__field_combinations_with_presence_tracking.snap index a324769e3cec..216dd1981928 100644 --- a/crates/store/re_mcap/src/layers/snapshots/re_mcap__layers__protobuf__integration_tests__field_combinations_with_presence_tracking.snap +++ b/crates/store/re_mcap/src/layers/snapshots/re_mcap__layers__protobuf__integration_tests__field_combinations_with_presence_tracking.snap @@ -11,11 +11,14 @@ expression: "format!(\"{:-240}\", &chunks[0])" │ ┌───────────────────────────────────────────────┬──────────────────────────────┬──────────────────────────────────┬──────────────────────────────────────────────────────────┐ │ │ │ RowId ┆ message_log_time ┆ message_publish_time ┆ com.example.Person:message │ │ │ │ --- ┆ --- ┆ --- ┆ --- │ │ -│ │ type: FixedSizeBinary[16] ┆ type: nullable Duration(ns) ┆ type: nullable Duration(ns) ┆ type: nullable List[nullable Struct[4]] │ │ -│ │ ARROW:extension:metadata: {"namespace":"row"} ┆ index_name: message_log_time ┆ index_name: message_publish_time ┆ archetype: com.example.Person │ │ -│ │ ARROW:extension:name: TUID ┆ is_sorted: true ┆ is_sorted: true ┆ component: com.example.Person:message │ │ -│ │ is_sorted: true ┆ kind: index ┆ kind: index ┆ kind: data │ │ -│ │ kind: control ┆ ┆ ┆ │ │ +│ │ type: non-null FixedSizeBinary(16) ┆ type: Duration(ns) ┆ type: Duration(ns) ┆ type: List(Struct("name": Utf8, "id": Int32, "status": │ │ +│ │ ARROW:extension:metadata: {"namespace":"row"} ┆ index_name: message_log_time ┆ index_name: message_publish_time ┆ Struct("name": Utf8, "value": Int32), metadata: │ │ +│ │ ARROW:extension:name: TUID ┆ is_sorted: true ┆ is_sorted: true ┆ {"ARROW:extension:name": │ │ +│ │ is_sorted: true ┆ kind: index ┆ kind: index ┆ "rerun.datatypes.ProtobufEnum"}, "address": │ │ +│ │ kind: control ┆ ┆ ┆ Struct("street": Utf8, "city": Utf8))) │ │ +│ │ ┆ ┆ ┆ archetype: com.example.Person │ │ +│ │ ┆ ┆ ┆ component: com.example.Person:message │ │ +│ │ ┆ ┆ ┆ kind: data │ │ │ ╞═══════════════════════════════════════════════╪══════════════════════════════╪══════════════════════════════════╪══════════════════════════════════════════════════════════╡ │ │ │ row_[**REDACTED**] ┆ PT0.000000001S ┆ PT0.000000001S ┆ [{name: Alice, id: 123, status: {name: ACTIVE, value: │ │ │ │ ┆ ┆ ┆ 1}, address: {street: Main St, city: NYC}}] │ │ diff --git a/crates/store/re_mcap/src/layers/snapshots/re_mcap__layers__protobuf__integration_tests__field_combinations_without_presence_tracking.snap b/crates/store/re_mcap/src/layers/snapshots/re_mcap__layers__protobuf__integration_tests__field_combinations_without_presence_tracking.snap index 6db8f919ed5e..320eb0e6d082 100644 --- a/crates/store/re_mcap/src/layers/snapshots/re_mcap__layers__protobuf__integration_tests__field_combinations_without_presence_tracking.snap +++ b/crates/store/re_mcap/src/layers/snapshots/re_mcap__layers__protobuf__integration_tests__field_combinations_without_presence_tracking.snap @@ -11,11 +11,14 @@ expression: "format!(\"{:-240}\", &chunks[0])" │ ┌───────────────────────────────────────────────┬──────────────────────────────┬──────────────────────────────────┬──────────────────────────────────────────────────────────┐ │ │ │ RowId ┆ message_log_time ┆ message_publish_time ┆ com.example.Person:message │ │ │ │ --- ┆ --- ┆ --- ┆ --- │ │ -│ │ type: FixedSizeBinary[16] ┆ type: nullable Duration(ns) ┆ type: nullable Duration(ns) ┆ type: nullable List[nullable Struct[4]] │ │ -│ │ ARROW:extension:metadata: {"namespace":"row"} ┆ index_name: message_log_time ┆ index_name: message_publish_time ┆ archetype: com.example.Person │ │ -│ │ ARROW:extension:name: TUID ┆ is_sorted: true ┆ is_sorted: true ┆ component: com.example.Person:message │ │ -│ │ is_sorted: true ┆ kind: index ┆ kind: index ┆ kind: data │ │ -│ │ kind: control ┆ ┆ ┆ │ │ +│ │ type: non-null FixedSizeBinary(16) ┆ type: Duration(ns) ┆ type: Duration(ns) ┆ type: List(Struct("name": Utf8, "id": Int32, "status": │ │ +│ │ ARROW:extension:metadata: {"namespace":"row"} ┆ index_name: message_log_time ┆ index_name: message_publish_time ┆ Struct("name": Utf8, "value": Int32), metadata: │ │ +│ │ ARROW:extension:name: TUID ┆ is_sorted: true ┆ is_sorted: true ┆ {"ARROW:extension:name": │ │ +│ │ is_sorted: true ┆ kind: index ┆ kind: index ┆ "rerun.datatypes.ProtobufEnum"}, "address": │ │ +│ │ kind: control ┆ ┆ ┆ Struct("street": Utf8, "city": Utf8))) │ │ +│ │ ┆ ┆ ┆ archetype: com.example.Person │ │ +│ │ ┆ ┆ ┆ component: com.example.Person:message │ │ +│ │ ┆ ┆ ┆ kind: data │ │ │ ╞═══════════════════════════════════════════════╪══════════════════════════════╪══════════════════════════════════╪══════════════════════════════════════════════════════════╡ │ │ │ row_[**REDACTED**] ┆ PT0.000000001S ┆ PT0.000000001S ┆ [{name: Alice, id: 123, status: {name: ACTIVE, value: │ │ │ │ ┆ ┆ ┆ 1}, address: {street: Main St, city: NYC}}] │ │ diff --git a/crates/store/re_mcap/src/layers/snapshots/re_mcap__layers__protobuf__integration_tests__oneof_nested.snap b/crates/store/re_mcap/src/layers/snapshots/re_mcap__layers__protobuf__integration_tests__oneof_nested.snap index 3d0c99a7c7fc..dffdcff56876 100644 --- a/crates/store/re_mcap/src/layers/snapshots/re_mcap__layers__protobuf__integration_tests__oneof_nested.snap +++ b/crates/store/re_mcap/src/layers/snapshots/re_mcap__layers__protobuf__integration_tests__oneof_nested.snap @@ -2,23 +2,23 @@ source: crates/store/re_mcap/src/layers/protobuf.rs expression: "format!(\"{:-240}\", &chunks[0])" --- -┌───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ METADATA: │ -│ * entity_path: /scene_topic │ -│ * id: [**REDACTED**] │ -│ * version: [**REDACTED**] │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ ┌───────────────────────────────────────────────┬──────────────────────────────┬──────────────────────────────────┬─────────────────────────────────────────┐ │ -│ │ RowId ┆ message_log_time ┆ message_publish_time ┆ com.example.Scene:message │ │ -│ │ --- ┆ --- ┆ --- ┆ --- │ │ -│ │ type: FixedSizeBinary[16] ┆ type: nullable Duration(ns) ┆ type: nullable Duration(ns) ┆ type: nullable List[nullable Struct[1]] │ │ -│ │ ARROW:extension:metadata: {"namespace":"row"} ┆ index_name: message_log_time ┆ index_name: message_publish_time ┆ archetype: com.example.Scene │ │ -│ │ ARROW:extension:name: TUID ┆ is_sorted: true ┆ is_sorted: true ┆ component: com.example.Scene:message │ │ -│ │ is_sorted: true ┆ kind: index ┆ kind: index ┆ kind: data │ │ -│ │ kind: control ┆ ┆ ┆ │ │ -│ ╞═══════════════════════════════════════════════╪══════════════════════════════╪══════════════════════════════════╪═════════════════════════════════════════╡ │ -│ │ row_[**REDACTED**] ┆ PT0.000000001S ┆ PT0.000000001S ┆ [{object: {object: cube}}] │ │ -│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ -│ │ row_[**REDACTED**] ┆ PT0.000000002S ┆ PT0.000000002S ┆ [{object: {object: pyramid}}] │ │ -│ └───────────────────────────────────────────────┴──────────────────────────────┴──────────────────────────────────┴─────────────────────────────────────────┘ │ -└───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +┌────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ METADATA: │ +│ * entity_path: /scene_topic │ +│ * id: [**REDACTED**] │ +│ * version: [**REDACTED**] │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ ┌───────────────────────────────────────────────┬──────────────────────────────┬──────────────────────────────────┬──────────────────────────────────────────────────────┐ │ +│ │ RowId ┆ message_log_time ┆ message_publish_time ┆ com.example.Scene:message │ │ +│ │ --- ┆ --- ┆ --- ┆ --- │ │ +│ │ type: non-null FixedSizeBinary(16) ┆ type: Duration(ns) ┆ type: Duration(ns) ┆ type: List(Struct("object": Struct("object": Utf8))) │ │ +│ │ ARROW:extension:metadata: {"namespace":"row"} ┆ index_name: message_log_time ┆ index_name: message_publish_time ┆ archetype: com.example.Scene │ │ +│ │ ARROW:extension:name: TUID ┆ is_sorted: true ┆ is_sorted: true ┆ component: com.example.Scene:message │ │ +│ │ is_sorted: true ┆ kind: index ┆ kind: index ┆ kind: data │ │ +│ │ kind: control ┆ ┆ ┆ │ │ +│ ╞═══════════════════════════════════════════════╪══════════════════════════════╪══════════════════════════════════╪══════════════════════════════════════════════════════╡ │ +│ │ row_[**REDACTED**] ┆ PT0.000000001S ┆ PT0.000000001S ┆ [{object: {object: cube}}] │ │ +│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ +│ │ row_[**REDACTED**] ┆ PT0.000000002S ┆ PT0.000000002S ┆ [{object: {object: pyramid}}] │ │ +│ └───────────────────────────────────────────────┴──────────────────────────────┴──────────────────────────────────┴──────────────────────────────────────────────────────┘ │ +└────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ diff --git a/crates/store/re_mcap/src/layers/snapshots/re_mcap__layers__protobuf__integration_tests__oneof_top_level.snap b/crates/store/re_mcap/src/layers/snapshots/re_mcap__layers__protobuf__integration_tests__oneof_top_level.snap index fd1cac65cc28..76dcc3679836 100644 --- a/crates/store/re_mcap/src/layers/snapshots/re_mcap__layers__protobuf__integration_tests__oneof_top_level.snap +++ b/crates/store/re_mcap/src/layers/snapshots/re_mcap__layers__protobuf__integration_tests__oneof_top_level.snap @@ -2,25 +2,25 @@ source: crates/store/re_mcap/src/layers/protobuf.rs expression: "format!(\"{:-240}\", &chunks[0])" --- -┌───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ METADATA: │ -│ * entity_path: /color_topic │ -│ * id: [**REDACTED**] │ -│ * version: [**REDACTED**] │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ ┌───────────────────────────────────────────────┬──────────────────────────────┬──────────────────────────────────┬─────────────────────────────────────────┐ │ -│ │ RowId ┆ message_log_time ┆ message_publish_time ┆ com.example.Color:message │ │ -│ │ --- ┆ --- ┆ --- ┆ --- │ │ -│ │ type: FixedSizeBinary[16] ┆ type: nullable Duration(ns) ┆ type: nullable Duration(ns) ┆ type: nullable List[nullable Struct[1]] │ │ -│ │ ARROW:extension:metadata: {"namespace":"row"} ┆ index_name: message_log_time ┆ index_name: message_publish_time ┆ archetype: com.example.Color │ │ -│ │ ARROW:extension:name: TUID ┆ is_sorted: true ┆ is_sorted: true ┆ component: com.example.Color:message │ │ -│ │ is_sorted: true ┆ kind: index ┆ kind: index ┆ kind: data │ │ -│ │ kind: control ┆ ┆ ┆ │ │ -│ ╞═══════════════════════════════════════════════╪══════════════════════════════╪══════════════════════════════════╪═════════════════════════════════════════╡ │ -│ │ row_[**REDACTED**] ┆ PT0.000000001S ┆ PT0.000000001S ┆ [{object: box}] │ │ -│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ -│ │ row_[**REDACTED**] ┆ PT0.000000002S ┆ PT0.000000002S ┆ [{object: sphere}] │ │ -│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ -│ │ row_[**REDACTED**] ┆ PT0.000000003S ┆ PT0.000000003S ┆ [{object: cone}] │ │ -│ └───────────────────────────────────────────────┴──────────────────────────────┴──────────────────────────────────┴─────────────────────────────────────────┘ │ -└───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +┌────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ METADATA: │ +│ * entity_path: /color_topic │ +│ * id: [**REDACTED**] │ +│ * version: [**REDACTED**] │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ ┌───────────────────────────────────────────────┬──────────────────────────────┬──────────────────────────────────┬──────────────────────────────────────┐ │ +│ │ RowId ┆ message_log_time ┆ message_publish_time ┆ com.example.Color:message │ │ +│ │ --- ┆ --- ┆ --- ┆ --- │ │ +│ │ type: non-null FixedSizeBinary(16) ┆ type: Duration(ns) ┆ type: Duration(ns) ┆ type: List(Struct("object": Utf8)) │ │ +│ │ ARROW:extension:metadata: {"namespace":"row"} ┆ index_name: message_log_time ┆ index_name: message_publish_time ┆ archetype: com.example.Color │ │ +│ │ ARROW:extension:name: TUID ┆ is_sorted: true ┆ is_sorted: true ┆ component: com.example.Color:message │ │ +│ │ is_sorted: true ┆ kind: index ┆ kind: index ┆ kind: data │ │ +│ │ kind: control ┆ ┆ ┆ │ │ +│ ╞═══════════════════════════════════════════════╪══════════════════════════════╪══════════════════════════════════╪══════════════════════════════════════╡ │ +│ │ row_[**REDACTED**] ┆ PT0.000000001S ┆ PT0.000000001S ┆ [{object: box}] │ │ +│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ +│ │ row_[**REDACTED**] ┆ PT0.000000002S ┆ PT0.000000002S ┆ [{object: sphere}] │ │ +│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ +│ │ row_[**REDACTED**] ┆ PT0.000000003S ┆ PT0.000000003S ┆ [{object: cone}] │ │ +│ └───────────────────────────────────────────────┴──────────────────────────────┴──────────────────────────────────┴──────────────────────────────────────┘ │ +└────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ diff --git a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__create_table__create_table_data.snap b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__create_table__create_table_data.snap index f6829318eee1..396ed1e1a500 100644 --- a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__create_table__create_table_data.snap +++ b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__create_table__create_table_data.snap @@ -2,7 +2,7 @@ source: crates/store/re_redap_tests/src/tests/create_table.rs expression: returned_schema.format_snapshot() --- -column_a: Utf8 -column_b: nullable i64 -column_c: f64 -column_d: nullable bool +column_a: non-null Utf8 +column_b: Int64 +column_c: non-null Float64 +column_d: Boolean diff --git a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__dataset_schema__simple_dataset_schema.snap b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__dataset_schema__simple_dataset_schema.snap index cf1e86154887..c9bb1bebbf9f 100644 --- a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__dataset_schema__simple_dataset_schema.snap +++ b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__dataset_schema__simple_dataset_schema.snap @@ -5,14 +5,14 @@ expression: schema.format_snapshot() top-level metadata: [ sorbet:version: "0.1.3" ] -/another/one:example.MyPoints:colors: nullable List[nullable u32] [ +/another/one:example.MyPoints:colors: List(UInt32) [ rerun:archetype: "example.MyPoints" rerun:component: "example.MyPoints:colors" rerun:component_type: "example.MyColor" rerun:entity_path: "/another/one" rerun:kind: "data" ] -/another/one:example.MyPoints:labels: nullable List[nullable Utf8] [ +/another/one:example.MyPoints:labels: List(Utf8) [ rerun:archetype: "example.MyPoints" rerun:component: "example.MyPoints:labels" rerun:component_type: "example.MyLabel" @@ -20,21 +20,21 @@ top-level metadata: [ rerun:is_static: "true" rerun:kind: "data" ] -/another/one:example.MyPoints:points: nullable List[nullable Struct[2]] [ +/another/one:example.MyPoints:points: List(Struct("x": non-null Float32, "y": non-null Float32)) [ rerun:archetype: "example.MyPoints" rerun:component: "example.MyPoints:points" rerun:component_type: "example.MyPoint" rerun:entity_path: "/another/one" rerun:kind: "data" ] -/my/entity:example.MyPoints:colors: nullable List[nullable u32] [ +/my/entity:example.MyPoints:colors: List(UInt32) [ rerun:archetype: "example.MyPoints" rerun:component: "example.MyPoints:colors" rerun:component_type: "example.MyColor" rerun:entity_path: "/my/entity" rerun:kind: "data" ] -/my/entity:example.MyPoints:labels: nullable List[nullable Utf8] [ +/my/entity:example.MyPoints:labels: List(Utf8) [ rerun:archetype: "example.MyPoints" rerun:component: "example.MyPoints:labels" rerun:component_type: "example.MyLabel" @@ -42,21 +42,21 @@ top-level metadata: [ rerun:is_static: "true" rerun:kind: "data" ] -/my/entity:example.MyPoints:points: nullable List[nullable Struct[2]] [ +/my/entity:example.MyPoints:points: List(Struct("x": non-null Float32, "y": non-null Float32)) [ rerun:archetype: "example.MyPoints" rerun:component: "example.MyPoints:points" rerun:component_type: "example.MyPoint" rerun:entity_path: "/my/entity" rerun:kind: "data" ] -/my/other/entity:example.MyPoints:colors: nullable List[nullable u32] [ +/my/other/entity:example.MyPoints:colors: List(UInt32) [ rerun:archetype: "example.MyPoints" rerun:component: "example.MyPoints:colors" rerun:component_type: "example.MyColor" rerun:entity_path: "/my/other/entity" rerun:kind: "data" ] -/my/other/entity:example.MyPoints:labels: nullable List[nullable Utf8] [ +/my/other/entity:example.MyPoints:labels: List(Utf8) [ rerun:archetype: "example.MyPoints" rerun:component: "example.MyPoints:labels" rerun:component_type: "example.MyLabel" @@ -64,21 +64,21 @@ top-level metadata: [ rerun:is_static: "true" rerun:kind: "data" ] -/my/other/entity:example.MyPoints:points: nullable List[nullable Struct[2]] [ +/my/other/entity:example.MyPoints:points: List(Struct("x": non-null Float32, "y": non-null Float32)) [ rerun:archetype: "example.MyPoints" rerun:component: "example.MyPoints:points" rerun:component_type: "example.MyPoint" rerun:entity_path: "/my/other/entity" rerun:kind: "data" ] -/yet/another/one:example.MyPoints:colors: nullable List[nullable u32] [ +/yet/another/one:example.MyPoints:colors: List(UInt32) [ rerun:archetype: "example.MyPoints" rerun:component: "example.MyPoints:colors" rerun:component_type: "example.MyColor" rerun:entity_path: "/yet/another/one" rerun:kind: "data" ] -/yet/another/one:example.MyPoints:labels: nullable List[nullable Utf8] [ +/yet/another/one:example.MyPoints:labels: List(Utf8) [ rerun:archetype: "example.MyPoints" rerun:component: "example.MyPoints:labels" rerun:component_type: "example.MyLabel" @@ -86,18 +86,18 @@ top-level metadata: [ rerun:is_static: "true" rerun:kind: "data" ] -/yet/another/one:example.MyPoints:points: nullable List[nullable Struct[2]] [ +/yet/another/one:example.MyPoints:points: List(Struct("x": non-null Float32, "y": non-null Float32)) [ rerun:archetype: "example.MyPoints" rerun:component: "example.MyPoints:points" rerun:component_type: "example.MyPoint" rerun:entity_path: "/yet/another/one" rerun:kind: "data" ] -frame_nr: nullable i64 [ +frame_nr: Int64 [ rerun:index_name: "frame_nr" rerun:kind: "index" ] -rerun.controls.RowId: FixedSizeBinary[16] [ +rerun.controls.RowId: non-null FixedSizeBinary(16) [ ARROW:extension:metadata: "{\"namespace\":\"row\"}" ARROW:extension:name: "rerun.datatypes.TUID" rerun:kind: "control" diff --git a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__entries_table__entries_table_schema.snap b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__entries_table__entries_table_schema.snap index 5a386e6149f4..0e6ece31d4a4 100644 --- a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__entries_table__entries_table_schema.snap +++ b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__entries_table__entries_table_schema.snap @@ -5,8 +5,8 @@ expression: schema.format_snapshot() top-level metadata: [ sorbet:version: "0.1.3" ] -created_at: Timestamp(ns) -entry_kind: i32 -id: FixedSizeBinary[16] -name: Utf8 -updated_at: Timestamp(ns) +created_at: non-null Timestamp(ns) +entry_kind: non-null Int32 +id: non-null FixedSizeBinary(16) +name: non-null Utf8 +updated_at: non-null Timestamp(ns) diff --git a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__fetch_chunks__simple_dataset_fetch_chunk.snap b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__fetch_chunks__simple_dataset_fetch_chunk.snap index ef22848e4f01..a3e02677b26d 100644 --- a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__fetch_chunks__simple_dataset_fetch_chunk.snap +++ b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__fetch_chunks__simple_dataset_fetch_chunk.snap @@ -2,30 +2,31 @@ source: crates/store/re_redap_tests/src/tests/fetch_chunks.rs expression: printed --- -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ METADATA: │ -│ * entity_path: /my/entity │ -│ * id: chunk_00000000000000010000000000000001 │ -│ * version: 0.1.3 │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ ┌───────────────────────────────────────────────┬──────────────────────┬────────────────────────────────────┬─────────────────────────────────────────┐ │ -│ │ RowId ┆ frame_nr ┆ example.MyPoints:colors ┆ example.MyPoints:points │ │ -│ │ --- ┆ --- ┆ --- ┆ --- │ │ -│ │ type: FixedSizeBinary[16] ┆ type: nullable i64 ┆ type: nullable List[nullable u32] ┆ type: nullable List[nullable Struct[2]] │ │ -│ │ ARROW:extension:metadata: {"namespace":"row"} ┆ index_name: frame_nr ┆ archetype: example.MyPoints ┆ archetype: example.MyPoints │ │ -│ │ ARROW:extension:name: TUID ┆ is_sorted: true ┆ component: example.MyPoints:colors ┆ component: example.MyPoints:points │ │ -│ │ is_sorted: true ┆ kind: index ┆ component_type: example.MyColor ┆ component_type: example.MyPoint │ │ -│ │ kind: control ┆ ┆ kind: data ┆ kind: data │ │ -│ ╞═══════════════════════════════════════════════╪══════════════════════╪════════════════════════════════════╪═════════════════════════════════════════╡ │ -│ │ row_00000000000000010000000000000001 ┆ 10 ┆ [0] ┆ [{x: 0.0, y: 0.0}] │ │ -│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ -│ │ row_00000000000000010000000000000002 ┆ 20 ┆ [1] ┆ [{x: 1.0, y: 1.0}] │ │ -│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ -│ │ row_00000000000000010000000000000003 ┆ 30 ┆ [2] ┆ [{x: 2.0, y: 2.0}] │ │ -│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ -│ │ row_00000000000000010000000000000004 ┆ 40 ┆ [3] ┆ [{x: 3.0, y: 3.0}] │ │ -│ └───────────────────────────────────────────────┴──────────────────────┴────────────────────────────────────┴─────────────────────────────────────────┘ │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ METADATA: │ +│ * entity_path: /my/entity │ +│ * id: chunk_00000000000000010000000000000001 │ +│ * version: 0.1.3 │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ ┌───────────────────────────────────────────────┬──────────────────────┬────────────────────────────────────┬──────────────────────────────────────────────────────────┐ │ +│ │ RowId ┆ frame_nr ┆ example.MyPoints:colors ┆ example.MyPoints:points │ │ +│ │ --- ┆ --- ┆ --- ┆ --- │ │ +│ │ type: non-null FixedSizeBinary(16) ┆ type: Int64 ┆ type: List(UInt32) ┆ type: List(Struct("x": non-null Float32, "y": non-null │ │ +│ │ ARROW:extension:metadata: {"namespace":"row"} ┆ index_name: frame_nr ┆ archetype: example.MyPoints ┆ Float32)) │ │ +│ │ ARROW:extension:name: TUID ┆ is_sorted: true ┆ component: example.MyPoints:colors ┆ archetype: example.MyPoints │ │ +│ │ is_sorted: true ┆ kind: index ┆ component_type: example.MyColor ┆ component: example.MyPoints:points │ │ +│ │ kind: control ┆ ┆ kind: data ┆ component_type: example.MyPoint │ │ +│ │ ┆ ┆ ┆ kind: data │ │ +│ ╞═══════════════════════════════════════════════╪══════════════════════╪════════════════════════════════════╪══════════════════════════════════════════════════════════╡ │ +│ │ row_00000000000000010000000000000001 ┆ 10 ┆ [0] ┆ [{x: 0.0, y: 0.0}] │ │ +│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ +│ │ row_00000000000000010000000000000002 ┆ 20 ┆ [1] ┆ [{x: 1.0, y: 1.0}] │ │ +│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ +│ │ row_00000000000000010000000000000003 ┆ 30 ┆ [2] ┆ [{x: 2.0, y: 2.0}] │ │ +│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ +│ │ row_00000000000000010000000000000004 ┆ 40 ┆ [3] ┆ [{x: 3.0, y: 3.0}] │ │ +│ └───────────────────────────────────────────────┴──────────────────────┴────────────────────────────────────┴──────────────────────────────────────────────────────────┘ │ +└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ ┌────────────────────────────────────────────────────────────────────────────────────────┐ │ METADATA: │ │ * entity_path: /my/entity │ @@ -35,7 +36,7 @@ expression: printed │ ┌───────────────────────────────────────────────┬────────────────────────────────────┐ │ │ │ RowId ┆ example.MyPoints:labels │ │ │ │ --- ┆ --- │ │ -│ │ type: FixedSizeBinary[16] ┆ type: nullable List[nullable Utf8] │ │ +│ │ type: non-null FixedSizeBinary(16) ┆ type: List(Utf8) │ │ │ │ ARROW:extension:metadata: {"namespace":"row"} ┆ archetype: example.MyPoints │ │ │ │ ARROW:extension:name: TUID ┆ component: example.MyPoints:labels │ │ │ │ is_sorted: true ┆ component_type: example.MyLabel │ │ @@ -44,30 +45,31 @@ expression: printed │ │ row_00000000000000010000000000000005 ┆ [simple] │ │ │ └───────────────────────────────────────────────┴────────────────────────────────────┘ │ └────────────────────────────────────────────────────────────────────────────────────────┘ -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ METADATA: │ -│ * entity_path: /my/other/entity │ -│ * id: chunk_00000000000000010000000000000003 │ -│ * version: 0.1.3 │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ ┌───────────────────────────────────────────────┬──────────────────────┬────────────────────────────────────┬─────────────────────────────────────────┐ │ -│ │ RowId ┆ frame_nr ┆ example.MyPoints:colors ┆ example.MyPoints:points │ │ -│ │ --- ┆ --- ┆ --- ┆ --- │ │ -│ │ type: FixedSizeBinary[16] ┆ type: nullable i64 ┆ type: nullable List[nullable u32] ┆ type: nullable List[nullable Struct[2]] │ │ -│ │ ARROW:extension:metadata: {"namespace":"row"} ┆ index_name: frame_nr ┆ archetype: example.MyPoints ┆ archetype: example.MyPoints │ │ -│ │ ARROW:extension:name: TUID ┆ is_sorted: true ┆ component: example.MyPoints:colors ┆ component: example.MyPoints:points │ │ -│ │ is_sorted: true ┆ kind: index ┆ component_type: example.MyColor ┆ component_type: example.MyPoint │ │ -│ │ kind: control ┆ ┆ kind: data ┆ kind: data │ │ -│ ╞═══════════════════════════════════════════════╪══════════════════════╪════════════════════════════════════╪═════════════════════════════════════════╡ │ -│ │ row_00000000000000010000000000000006 ┆ 10 ┆ [0] ┆ [{x: 0.0, y: 0.0}] │ │ -│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ -│ │ row_00000000000000010000000000000007 ┆ 20 ┆ [1] ┆ [{x: 1.0, y: 1.0}] │ │ -│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ -│ │ row_00000000000000010000000000000008 ┆ 30 ┆ [2] ┆ [{x: 2.0, y: 2.0}] │ │ -│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ -│ │ row_00000000000000010000000000000009 ┆ 40 ┆ [3] ┆ [{x: 3.0, y: 3.0}] │ │ -│ └───────────────────────────────────────────────┴──────────────────────┴────────────────────────────────────┴─────────────────────────────────────────┘ │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ METADATA: │ +│ * entity_path: /my/other/entity │ +│ * id: chunk_00000000000000010000000000000003 │ +│ * version: 0.1.3 │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ ┌───────────────────────────────────────────────┬──────────────────────┬────────────────────────────────────┬──────────────────────────────────────────────────────────┐ │ +│ │ RowId ┆ frame_nr ┆ example.MyPoints:colors ┆ example.MyPoints:points │ │ +│ │ --- ┆ --- ┆ --- ┆ --- │ │ +│ │ type: non-null FixedSizeBinary(16) ┆ type: Int64 ┆ type: List(UInt32) ┆ type: List(Struct("x": non-null Float32, "y": non-null │ │ +│ │ ARROW:extension:metadata: {"namespace":"row"} ┆ index_name: frame_nr ┆ archetype: example.MyPoints ┆ Float32)) │ │ +│ │ ARROW:extension:name: TUID ┆ is_sorted: true ┆ component: example.MyPoints:colors ┆ archetype: example.MyPoints │ │ +│ │ is_sorted: true ┆ kind: index ┆ component_type: example.MyColor ┆ component: example.MyPoints:points │ │ +│ │ kind: control ┆ ┆ kind: data ┆ component_type: example.MyPoint │ │ +│ │ ┆ ┆ ┆ kind: data │ │ +│ ╞═══════════════════════════════════════════════╪══════════════════════╪════════════════════════════════════╪══════════════════════════════════════════════════════════╡ │ +│ │ row_00000000000000010000000000000006 ┆ 10 ┆ [0] ┆ [{x: 0.0, y: 0.0}] │ │ +│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ +│ │ row_00000000000000010000000000000007 ┆ 20 ┆ [1] ┆ [{x: 1.0, y: 1.0}] │ │ +│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ +│ │ row_00000000000000010000000000000008 ┆ 30 ┆ [2] ┆ [{x: 2.0, y: 2.0}] │ │ +│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ +│ │ row_00000000000000010000000000000009 ┆ 40 ┆ [3] ┆ [{x: 3.0, y: 3.0}] │ │ +│ └───────────────────────────────────────────────┴──────────────────────┴────────────────────────────────────┴──────────────────────────────────────────────────────────┘ │ +└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ ┌────────────────────────────────────────────────────────────────────────────────────────┐ │ METADATA: │ │ * entity_path: /my/other/entity │ @@ -77,7 +79,7 @@ expression: printed │ ┌───────────────────────────────────────────────┬────────────────────────────────────┐ │ │ │ RowId ┆ example.MyPoints:labels │ │ │ │ --- ┆ --- │ │ -│ │ type: FixedSizeBinary[16] ┆ type: nullable List[nullable Utf8] │ │ +│ │ type: non-null FixedSizeBinary(16) ┆ type: List(Utf8) │ │ │ │ ARROW:extension:metadata: {"namespace":"row"} ┆ archetype: example.MyPoints │ │ │ │ ARROW:extension:name: TUID ┆ component: example.MyPoints:labels │ │ │ │ is_sorted: true ┆ component_type: example.MyLabel │ │ @@ -86,30 +88,31 @@ expression: printed │ │ row_0000000000000001000000000000000a ┆ [simple] │ │ │ └───────────────────────────────────────────────┴────────────────────────────────────┘ │ └────────────────────────────────────────────────────────────────────────────────────────┘ -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ METADATA: │ -│ * entity_path: /my/entity │ -│ * id: chunk_00000000000000020000000000000001 │ -│ * version: 0.1.3 │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ ┌───────────────────────────────────────────────┬──────────────────────┬────────────────────────────────────┬─────────────────────────────────────────┐ │ -│ │ RowId ┆ frame_nr ┆ example.MyPoints:colors ┆ example.MyPoints:points │ │ -│ │ --- ┆ --- ┆ --- ┆ --- │ │ -│ │ type: FixedSizeBinary[16] ┆ type: nullable i64 ┆ type: nullable List[nullable u32] ┆ type: nullable List[nullable Struct[2]] │ │ -│ │ ARROW:extension:metadata: {"namespace":"row"} ┆ index_name: frame_nr ┆ archetype: example.MyPoints ┆ archetype: example.MyPoints │ │ -│ │ ARROW:extension:name: TUID ┆ is_sorted: true ┆ component: example.MyPoints:colors ┆ component: example.MyPoints:points │ │ -│ │ is_sorted: true ┆ kind: index ┆ component_type: example.MyColor ┆ component_type: example.MyPoint │ │ -│ │ kind: control ┆ ┆ kind: data ┆ kind: data │ │ -│ ╞═══════════════════════════════════════════════╪══════════════════════╪════════════════════════════════════╪═════════════════════════════════════════╡ │ -│ │ row_00000000000000020000000000000001 ┆ 10 ┆ [0] ┆ [{x: 0.0, y: 0.0}] │ │ -│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ -│ │ row_00000000000000020000000000000002 ┆ 20 ┆ [1] ┆ [{x: 1.0, y: 1.0}] │ │ -│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ -│ │ row_00000000000000020000000000000003 ┆ 30 ┆ [2] ┆ [{x: 2.0, y: 2.0}] │ │ -│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ -│ │ row_00000000000000020000000000000004 ┆ 40 ┆ [3] ┆ [{x: 3.0, y: 3.0}] │ │ -│ └───────────────────────────────────────────────┴──────────────────────┴────────────────────────────────────┴─────────────────────────────────────────┘ │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ METADATA: │ +│ * entity_path: /my/entity │ +│ * id: chunk_00000000000000020000000000000001 │ +│ * version: 0.1.3 │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ ┌───────────────────────────────────────────────┬──────────────────────┬────────────────────────────────────┬──────────────────────────────────────────────────────────┐ │ +│ │ RowId ┆ frame_nr ┆ example.MyPoints:colors ┆ example.MyPoints:points │ │ +│ │ --- ┆ --- ┆ --- ┆ --- │ │ +│ │ type: non-null FixedSizeBinary(16) ┆ type: Int64 ┆ type: List(UInt32) ┆ type: List(Struct("x": non-null Float32, "y": non-null │ │ +│ │ ARROW:extension:metadata: {"namespace":"row"} ┆ index_name: frame_nr ┆ archetype: example.MyPoints ┆ Float32)) │ │ +│ │ ARROW:extension:name: TUID ┆ is_sorted: true ┆ component: example.MyPoints:colors ┆ archetype: example.MyPoints │ │ +│ │ is_sorted: true ┆ kind: index ┆ component_type: example.MyColor ┆ component: example.MyPoints:points │ │ +│ │ kind: control ┆ ┆ kind: data ┆ component_type: example.MyPoint │ │ +│ │ ┆ ┆ ┆ kind: data │ │ +│ ╞═══════════════════════════════════════════════╪══════════════════════╪════════════════════════════════════╪══════════════════════════════════════════════════════════╡ │ +│ │ row_00000000000000020000000000000001 ┆ 10 ┆ [0] ┆ [{x: 0.0, y: 0.0}] │ │ +│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ +│ │ row_00000000000000020000000000000002 ┆ 20 ┆ [1] ┆ [{x: 1.0, y: 1.0}] │ │ +│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ +│ │ row_00000000000000020000000000000003 ┆ 30 ┆ [2] ┆ [{x: 2.0, y: 2.0}] │ │ +│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ +│ │ row_00000000000000020000000000000004 ┆ 40 ┆ [3] ┆ [{x: 3.0, y: 3.0}] │ │ +│ └───────────────────────────────────────────────┴──────────────────────┴────────────────────────────────────┴──────────────────────────────────────────────────────────┘ │ +└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ ┌────────────────────────────────────────────────────────────────────────────────────────┐ │ METADATA: │ │ * entity_path: /my/entity │ @@ -119,7 +122,7 @@ expression: printed │ ┌───────────────────────────────────────────────┬────────────────────────────────────┐ │ │ │ RowId ┆ example.MyPoints:labels │ │ │ │ --- ┆ --- │ │ -│ │ type: FixedSizeBinary[16] ┆ type: nullable List[nullable Utf8] │ │ +│ │ type: non-null FixedSizeBinary(16) ┆ type: List(Utf8) │ │ │ │ ARROW:extension:metadata: {"namespace":"row"} ┆ archetype: example.MyPoints │ │ │ │ ARROW:extension:name: TUID ┆ component: example.MyPoints:labels │ │ │ │ is_sorted: true ┆ component_type: example.MyLabel │ │ @@ -128,30 +131,31 @@ expression: printed │ │ row_00000000000000020000000000000005 ┆ [simple] │ │ │ └───────────────────────────────────────────────┴────────────────────────────────────┘ │ └────────────────────────────────────────────────────────────────────────────────────────┘ -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ METADATA: │ -│ * entity_path: /my/entity │ -│ * id: chunk_00000000000000030000000000000001 │ -│ * version: 0.1.3 │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ ┌───────────────────────────────────────────────┬──────────────────────┬────────────────────────────────────┬─────────────────────────────────────────┐ │ -│ │ RowId ┆ frame_nr ┆ example.MyPoints:colors ┆ example.MyPoints:points │ │ -│ │ --- ┆ --- ┆ --- ┆ --- │ │ -│ │ type: FixedSizeBinary[16] ┆ type: nullable i64 ┆ type: nullable List[nullable u32] ┆ type: nullable List[nullable Struct[2]] │ │ -│ │ ARROW:extension:metadata: {"namespace":"row"} ┆ index_name: frame_nr ┆ archetype: example.MyPoints ┆ archetype: example.MyPoints │ │ -│ │ ARROW:extension:name: TUID ┆ is_sorted: true ┆ component: example.MyPoints:colors ┆ component: example.MyPoints:points │ │ -│ │ is_sorted: true ┆ kind: index ┆ component_type: example.MyColor ┆ component_type: example.MyPoint │ │ -│ │ kind: control ┆ ┆ kind: data ┆ kind: data │ │ -│ ╞═══════════════════════════════════════════════╪══════════════════════╪════════════════════════════════════╪═════════════════════════════════════════╡ │ -│ │ row_00000000000000030000000000000001 ┆ 10 ┆ [0] ┆ [{x: 0.0, y: 0.0}] │ │ -│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ -│ │ row_00000000000000030000000000000002 ┆ 20 ┆ [1] ┆ [{x: 1.0, y: 1.0}] │ │ -│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ -│ │ row_00000000000000030000000000000003 ┆ 30 ┆ [2] ┆ [{x: 2.0, y: 2.0}] │ │ -│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ -│ │ row_00000000000000030000000000000004 ┆ 40 ┆ [3] ┆ [{x: 3.0, y: 3.0}] │ │ -│ └───────────────────────────────────────────────┴──────────────────────┴────────────────────────────────────┴─────────────────────────────────────────┘ │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ METADATA: │ +│ * entity_path: /my/entity │ +│ * id: chunk_00000000000000030000000000000001 │ +│ * version: 0.1.3 │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ ┌───────────────────────────────────────────────┬──────────────────────┬────────────────────────────────────┬──────────────────────────────────────────────────────────┐ │ +│ │ RowId ┆ frame_nr ┆ example.MyPoints:colors ┆ example.MyPoints:points │ │ +│ │ --- ┆ --- ┆ --- ┆ --- │ │ +│ │ type: non-null FixedSizeBinary(16) ┆ type: Int64 ┆ type: List(UInt32) ┆ type: List(Struct("x": non-null Float32, "y": non-null │ │ +│ │ ARROW:extension:metadata: {"namespace":"row"} ┆ index_name: frame_nr ┆ archetype: example.MyPoints ┆ Float32)) │ │ +│ │ ARROW:extension:name: TUID ┆ is_sorted: true ┆ component: example.MyPoints:colors ┆ archetype: example.MyPoints │ │ +│ │ is_sorted: true ┆ kind: index ┆ component_type: example.MyColor ┆ component: example.MyPoints:points │ │ +│ │ kind: control ┆ ┆ kind: data ┆ component_type: example.MyPoint │ │ +│ │ ┆ ┆ ┆ kind: data │ │ +│ ╞═══════════════════════════════════════════════╪══════════════════════╪════════════════════════════════════╪══════════════════════════════════════════════════════════╡ │ +│ │ row_00000000000000030000000000000001 ┆ 10 ┆ [0] ┆ [{x: 0.0, y: 0.0}] │ │ +│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ +│ │ row_00000000000000030000000000000002 ┆ 20 ┆ [1] ┆ [{x: 1.0, y: 1.0}] │ │ +│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ +│ │ row_00000000000000030000000000000003 ┆ 30 ┆ [2] ┆ [{x: 2.0, y: 2.0}] │ │ +│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ +│ │ row_00000000000000030000000000000004 ┆ 40 ┆ [3] ┆ [{x: 3.0, y: 3.0}] │ │ +│ └───────────────────────────────────────────────┴──────────────────────┴────────────────────────────────────┴──────────────────────────────────────────────────────────┘ │ +└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ ┌────────────────────────────────────────────────────────────────────────────────────────┐ │ METADATA: │ │ * entity_path: /my/entity │ @@ -161,7 +165,7 @@ expression: printed │ ┌───────────────────────────────────────────────┬────────────────────────────────────┐ │ │ │ RowId ┆ example.MyPoints:labels │ │ │ │ --- ┆ --- │ │ -│ │ type: FixedSizeBinary[16] ┆ type: nullable List[nullable Utf8] │ │ +│ │ type: non-null FixedSizeBinary(16) ┆ type: List(Utf8) │ │ │ │ ARROW:extension:metadata: {"namespace":"row"} ┆ archetype: example.MyPoints │ │ │ │ ARROW:extension:name: TUID ┆ component: example.MyPoints:labels │ │ │ │ is_sorted: true ┆ component_type: example.MyLabel │ │ @@ -170,30 +174,31 @@ expression: printed │ │ row_00000000000000030000000000000005 ┆ [simple] │ │ │ └───────────────────────────────────────────────┴────────────────────────────────────┘ │ └────────────────────────────────────────────────────────────────────────────────────────┘ -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ METADATA: │ -│ * entity_path: /another/one │ -│ * id: chunk_00000000000000030000000000000003 │ -│ * version: 0.1.3 │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ ┌───────────────────────────────────────────────┬──────────────────────┬────────────────────────────────────┬─────────────────────────────────────────┐ │ -│ │ RowId ┆ frame_nr ┆ example.MyPoints:colors ┆ example.MyPoints:points │ │ -│ │ --- ┆ --- ┆ --- ┆ --- │ │ -│ │ type: FixedSizeBinary[16] ┆ type: nullable i64 ┆ type: nullable List[nullable u32] ┆ type: nullable List[nullable Struct[2]] │ │ -│ │ ARROW:extension:metadata: {"namespace":"row"} ┆ index_name: frame_nr ┆ archetype: example.MyPoints ┆ archetype: example.MyPoints │ │ -│ │ ARROW:extension:name: TUID ┆ is_sorted: true ┆ component: example.MyPoints:colors ┆ component: example.MyPoints:points │ │ -│ │ is_sorted: true ┆ kind: index ┆ component_type: example.MyColor ┆ component_type: example.MyPoint │ │ -│ │ kind: control ┆ ┆ kind: data ┆ kind: data │ │ -│ ╞═══════════════════════════════════════════════╪══════════════════════╪════════════════════════════════════╪═════════════════════════════════════════╡ │ -│ │ row_00000000000000030000000000000006 ┆ 10 ┆ [0] ┆ [{x: 0.0, y: 0.0}] │ │ -│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ -│ │ row_00000000000000030000000000000007 ┆ 20 ┆ [1] ┆ [{x: 1.0, y: 1.0}] │ │ -│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ -│ │ row_00000000000000030000000000000008 ┆ 30 ┆ [2] ┆ [{x: 2.0, y: 2.0}] │ │ -│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ -│ │ row_00000000000000030000000000000009 ┆ 40 ┆ [3] ┆ [{x: 3.0, y: 3.0}] │ │ -│ └───────────────────────────────────────────────┴──────────────────────┴────────────────────────────────────┴─────────────────────────────────────────┘ │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ METADATA: │ +│ * entity_path: /another/one │ +│ * id: chunk_00000000000000030000000000000003 │ +│ * version: 0.1.3 │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ ┌───────────────────────────────────────────────┬──────────────────────┬────────────────────────────────────┬──────────────────────────────────────────────────────────┐ │ +│ │ RowId ┆ frame_nr ┆ example.MyPoints:colors ┆ example.MyPoints:points │ │ +│ │ --- ┆ --- ┆ --- ┆ --- │ │ +│ │ type: non-null FixedSizeBinary(16) ┆ type: Int64 ┆ type: List(UInt32) ┆ type: List(Struct("x": non-null Float32, "y": non-null │ │ +│ │ ARROW:extension:metadata: {"namespace":"row"} ┆ index_name: frame_nr ┆ archetype: example.MyPoints ┆ Float32)) │ │ +│ │ ARROW:extension:name: TUID ┆ is_sorted: true ┆ component: example.MyPoints:colors ┆ archetype: example.MyPoints │ │ +│ │ is_sorted: true ┆ kind: index ┆ component_type: example.MyColor ┆ component: example.MyPoints:points │ │ +│ │ kind: control ┆ ┆ kind: data ┆ component_type: example.MyPoint │ │ +│ │ ┆ ┆ ┆ kind: data │ │ +│ ╞═══════════════════════════════════════════════╪══════════════════════╪════════════════════════════════════╪══════════════════════════════════════════════════════════╡ │ +│ │ row_00000000000000030000000000000006 ┆ 10 ┆ [0] ┆ [{x: 0.0, y: 0.0}] │ │ +│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ +│ │ row_00000000000000030000000000000007 ┆ 20 ┆ [1] ┆ [{x: 1.0, y: 1.0}] │ │ +│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ +│ │ row_00000000000000030000000000000008 ┆ 30 ┆ [2] ┆ [{x: 2.0, y: 2.0}] │ │ +│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ +│ │ row_00000000000000030000000000000009 ┆ 40 ┆ [3] ┆ [{x: 3.0, y: 3.0}] │ │ +│ └───────────────────────────────────────────────┴──────────────────────┴────────────────────────────────────┴──────────────────────────────────────────────────────────┘ │ +└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ ┌────────────────────────────────────────────────────────────────────────────────────────┐ │ METADATA: │ │ * entity_path: /another/one │ @@ -203,7 +208,7 @@ expression: printed │ ┌───────────────────────────────────────────────┬────────────────────────────────────┐ │ │ │ RowId ┆ example.MyPoints:labels │ │ │ │ --- ┆ --- │ │ -│ │ type: FixedSizeBinary[16] ┆ type: nullable List[nullable Utf8] │ │ +│ │ type: non-null FixedSizeBinary(16) ┆ type: List(Utf8) │ │ │ │ ARROW:extension:metadata: {"namespace":"row"} ┆ archetype: example.MyPoints │ │ │ │ ARROW:extension:name: TUID ┆ component: example.MyPoints:labels │ │ │ │ is_sorted: true ┆ component_type: example.MyLabel │ │ @@ -212,30 +217,31 @@ expression: printed │ │ row_0000000000000003000000000000000a ┆ [simple] │ │ │ └───────────────────────────────────────────────┴────────────────────────────────────┘ │ └────────────────────────────────────────────────────────────────────────────────────────┘ -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ METADATA: │ -│ * entity_path: /yet/another/one │ -│ * id: chunk_00000000000000030000000000000005 │ -│ * version: 0.1.3 │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ ┌───────────────────────────────────────────────┬──────────────────────┬────────────────────────────────────┬─────────────────────────────────────────┐ │ -│ │ RowId ┆ frame_nr ┆ example.MyPoints:colors ┆ example.MyPoints:points │ │ -│ │ --- ┆ --- ┆ --- ┆ --- │ │ -│ │ type: FixedSizeBinary[16] ┆ type: nullable i64 ┆ type: nullable List[nullable u32] ┆ type: nullable List[nullable Struct[2]] │ │ -│ │ ARROW:extension:metadata: {"namespace":"row"} ┆ index_name: frame_nr ┆ archetype: example.MyPoints ┆ archetype: example.MyPoints │ │ -│ │ ARROW:extension:name: TUID ┆ is_sorted: true ┆ component: example.MyPoints:colors ┆ component: example.MyPoints:points │ │ -│ │ is_sorted: true ┆ kind: index ┆ component_type: example.MyColor ┆ component_type: example.MyPoint │ │ -│ │ kind: control ┆ ┆ kind: data ┆ kind: data │ │ -│ ╞═══════════════════════════════════════════════╪══════════════════════╪════════════════════════════════════╪═════════════════════════════════════════╡ │ -│ │ row_0000000000000003000000000000000b ┆ 10 ┆ [0] ┆ [{x: 0.0, y: 0.0}] │ │ -│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ -│ │ row_0000000000000003000000000000000c ┆ 20 ┆ [1] ┆ [{x: 1.0, y: 1.0}] │ │ -│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ -│ │ row_0000000000000003000000000000000d ┆ 30 ┆ [2] ┆ [{x: 2.0, y: 2.0}] │ │ -│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ -│ │ row_0000000000000003000000000000000e ┆ 40 ┆ [3] ┆ [{x: 3.0, y: 3.0}] │ │ -│ └───────────────────────────────────────────────┴──────────────────────┴────────────────────────────────────┴─────────────────────────────────────────┘ │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ METADATA: │ +│ * entity_path: /yet/another/one │ +│ * id: chunk_00000000000000030000000000000005 │ +│ * version: 0.1.3 │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ ┌───────────────────────────────────────────────┬──────────────────────┬────────────────────────────────────┬──────────────────────────────────────────────────────────┐ │ +│ │ RowId ┆ frame_nr ┆ example.MyPoints:colors ┆ example.MyPoints:points │ │ +│ │ --- ┆ --- ┆ --- ┆ --- │ │ +│ │ type: non-null FixedSizeBinary(16) ┆ type: Int64 ┆ type: List(UInt32) ┆ type: List(Struct("x": non-null Float32, "y": non-null │ │ +│ │ ARROW:extension:metadata: {"namespace":"row"} ┆ index_name: frame_nr ┆ archetype: example.MyPoints ┆ Float32)) │ │ +│ │ ARROW:extension:name: TUID ┆ is_sorted: true ┆ component: example.MyPoints:colors ┆ archetype: example.MyPoints │ │ +│ │ is_sorted: true ┆ kind: index ┆ component_type: example.MyColor ┆ component: example.MyPoints:points │ │ +│ │ kind: control ┆ ┆ kind: data ┆ component_type: example.MyPoint │ │ +│ │ ┆ ┆ ┆ kind: data │ │ +│ ╞═══════════════════════════════════════════════╪══════════════════════╪════════════════════════════════════╪══════════════════════════════════════════════════════════╡ │ +│ │ row_0000000000000003000000000000000b ┆ 10 ┆ [0] ┆ [{x: 0.0, y: 0.0}] │ │ +│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ +│ │ row_0000000000000003000000000000000c ┆ 20 ┆ [1] ┆ [{x: 1.0, y: 1.0}] │ │ +│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ +│ │ row_0000000000000003000000000000000d ┆ 30 ┆ [2] ┆ [{x: 2.0, y: 2.0}] │ │ +│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ +│ │ row_0000000000000003000000000000000e ┆ 40 ┆ [3] ┆ [{x: 3.0, y: 3.0}] │ │ +│ └───────────────────────────────────────────────┴──────────────────────┴────────────────────────────────────┴──────────────────────────────────────────────────────────┘ │ +└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ ┌────────────────────────────────────────────────────────────────────────────────────────┐ │ METADATA: │ │ * entity_path: /yet/another/one │ @@ -245,7 +251,7 @@ expression: printed │ ┌───────────────────────────────────────────────┬────────────────────────────────────┐ │ │ │ RowId ┆ example.MyPoints:labels │ │ │ │ --- ┆ --- │ │ -│ │ type: FixedSizeBinary[16] ┆ type: nullable List[nullable Utf8] │ │ +│ │ type: non-null FixedSizeBinary(16) ┆ type: List(Utf8) │ │ │ │ ARROW:extension:metadata: {"namespace":"row"} ┆ archetype: example.MyPoints │ │ │ │ ARROW:extension:name: TUID ┆ component: example.MyPoints:labels │ │ │ │ is_sorted: true ┆ component_type: example.MyLabel │ │ diff --git a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__query_dataset__empty_dataset_schema.snap b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__query_dataset__empty_dataset_schema.snap index 782b3f48f76a..c619984598a9 100644 --- a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__query_dataset__empty_dataset_schema.snap +++ b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__query_dataset__empty_dataset_schema.snap @@ -2,18 +2,18 @@ source: crates/store/re_redap_tests/src/tests/query_dataset.rs expression: required_chunk_info.format_schema_snapshot() --- -chunk_byte_len: u64 -chunk_entity_path: Utf8 [ +chunk_byte_len: non-null UInt64 +chunk_entity_path: non-null Utf8 [ rerun:kind: "control" ] -chunk_id: FixedSizeBinary[16] [ +chunk_id: non-null FixedSizeBinary(16) [ rerun:kind: "control" ] -chunk_is_static: bool [ +chunk_is_static: non-null Boolean [ rerun:kind: "control" ] -chunk_key: Binary -chunk_segment_id: Utf8 [ +chunk_key: non-null Binary +chunk_segment_id: non-null Utf8 [ rerun:kind: "control" ] -rerun_segment_layer: Utf8 +rerun_segment_layer: non-null Utf8 diff --git a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__query_dataset__simple_dataset_default_schema.snap b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__query_dataset__simple_dataset_default_schema.snap index 782b3f48f76a..c619984598a9 100644 --- a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__query_dataset__simple_dataset_default_schema.snap +++ b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__query_dataset__simple_dataset_default_schema.snap @@ -2,18 +2,18 @@ source: crates/store/re_redap_tests/src/tests/query_dataset.rs expression: required_chunk_info.format_schema_snapshot() --- -chunk_byte_len: u64 -chunk_entity_path: Utf8 [ +chunk_byte_len: non-null UInt64 +chunk_entity_path: non-null Utf8 [ rerun:kind: "control" ] -chunk_id: FixedSizeBinary[16] [ +chunk_id: non-null FixedSizeBinary(16) [ rerun:kind: "control" ] -chunk_is_static: bool [ +chunk_is_static: non-null Boolean [ rerun:kind: "control" ] -chunk_key: Binary -chunk_segment_id: Utf8 [ +chunk_key: non-null Binary +chunk_segment_id: non-null Utf8 [ rerun:kind: "control" ] -rerun_segment_layer: Utf8 +rerun_segment_layer: non-null Utf8 diff --git a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__query_dataset__simple_dataset_exclude_static_schema.snap b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__query_dataset__simple_dataset_exclude_static_schema.snap index 782b3f48f76a..c619984598a9 100644 --- a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__query_dataset__simple_dataset_exclude_static_schema.snap +++ b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__query_dataset__simple_dataset_exclude_static_schema.snap @@ -2,18 +2,18 @@ source: crates/store/re_redap_tests/src/tests/query_dataset.rs expression: required_chunk_info.format_schema_snapshot() --- -chunk_byte_len: u64 -chunk_entity_path: Utf8 [ +chunk_byte_len: non-null UInt64 +chunk_entity_path: non-null Utf8 [ rerun:kind: "control" ] -chunk_id: FixedSizeBinary[16] [ +chunk_id: non-null FixedSizeBinary(16) [ rerun:kind: "control" ] -chunk_is_static: bool [ +chunk_is_static: non-null Boolean [ rerun:kind: "control" ] -chunk_key: Binary -chunk_segment_id: Utf8 [ +chunk_key: non-null Binary +chunk_segment_id: non-null Utf8 [ rerun:kind: "control" ] -rerun_segment_layer: Utf8 +rerun_segment_layer: non-null Utf8 diff --git a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__query_dataset__simple_dataset_exclude_temporal_schema.snap b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__query_dataset__simple_dataset_exclude_temporal_schema.snap index 782b3f48f76a..c619984598a9 100644 --- a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__query_dataset__simple_dataset_exclude_temporal_schema.snap +++ b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__query_dataset__simple_dataset_exclude_temporal_schema.snap @@ -2,18 +2,18 @@ source: crates/store/re_redap_tests/src/tests/query_dataset.rs expression: required_chunk_info.format_schema_snapshot() --- -chunk_byte_len: u64 -chunk_entity_path: Utf8 [ +chunk_byte_len: non-null UInt64 +chunk_entity_path: non-null Utf8 [ rerun:kind: "control" ] -chunk_id: FixedSizeBinary[16] [ +chunk_id: non-null FixedSizeBinary(16) [ rerun:kind: "control" ] -chunk_is_static: bool [ +chunk_is_static: non-null Boolean [ rerun:kind: "control" ] -chunk_key: Binary -chunk_segment_id: Utf8 [ +chunk_key: non-null Binary +chunk_segment_id: non-null Utf8 [ rerun:kind: "control" ] -rerun_segment_layer: Utf8 +rerun_segment_layer: non-null Utf8 diff --git a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__query_dataset__simple_dataset_single_entity_schema.snap b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__query_dataset__simple_dataset_single_entity_schema.snap index 782b3f48f76a..c619984598a9 100644 --- a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__query_dataset__simple_dataset_single_entity_schema.snap +++ b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__query_dataset__simple_dataset_single_entity_schema.snap @@ -2,18 +2,18 @@ source: crates/store/re_redap_tests/src/tests/query_dataset.rs expression: required_chunk_info.format_schema_snapshot() --- -chunk_byte_len: u64 -chunk_entity_path: Utf8 [ +chunk_byte_len: non-null UInt64 +chunk_entity_path: non-null Utf8 [ rerun:kind: "control" ] -chunk_id: FixedSizeBinary[16] [ +chunk_id: non-null FixedSizeBinary(16) [ rerun:kind: "control" ] -chunk_is_static: bool [ +chunk_is_static: non-null Boolean [ rerun:kind: "control" ] -chunk_key: Binary -chunk_segment_id: Utf8 [ +chunk_key: non-null Binary +chunk_segment_id: non-null Utf8 [ rerun:kind: "control" ] -rerun_segment_layer: Utf8 +rerun_segment_layer: non-null Utf8 diff --git a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__query_dataset__simple_dataset_single_segment_schema.snap b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__query_dataset__simple_dataset_single_segment_schema.snap index 782b3f48f76a..c619984598a9 100644 --- a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__query_dataset__simple_dataset_single_segment_schema.snap +++ b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__query_dataset__simple_dataset_single_segment_schema.snap @@ -2,18 +2,18 @@ source: crates/store/re_redap_tests/src/tests/query_dataset.rs expression: required_chunk_info.format_schema_snapshot() --- -chunk_byte_len: u64 -chunk_entity_path: Utf8 [ +chunk_byte_len: non-null UInt64 +chunk_entity_path: non-null Utf8 [ rerun:kind: "control" ] -chunk_id: FixedSizeBinary[16] [ +chunk_id: non-null FixedSizeBinary(16) [ rerun:kind: "control" ] -chunk_is_static: bool [ +chunk_is_static: non-null Boolean [ rerun:kind: "control" ] -chunk_key: Binary -chunk_segment_id: Utf8 [ +chunk_key: non-null Binary +chunk_segment_id: non-null Utf8 [ rerun:kind: "control" ] -rerun_segment_layer: Utf8 +rerun_segment_layer: non-null Utf8 diff --git a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__query_dataset__simple_with_layer_schema.snap b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__query_dataset__simple_with_layer_schema.snap index 782b3f48f76a..c619984598a9 100644 --- a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__query_dataset__simple_with_layer_schema.snap +++ b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__query_dataset__simple_with_layer_schema.snap @@ -2,18 +2,18 @@ source: crates/store/re_redap_tests/src/tests/query_dataset.rs expression: required_chunk_info.format_schema_snapshot() --- -chunk_byte_len: u64 -chunk_entity_path: Utf8 [ +chunk_byte_len: non-null UInt64 +chunk_entity_path: non-null Utf8 [ rerun:kind: "control" ] -chunk_id: FixedSizeBinary[16] [ +chunk_id: non-null FixedSizeBinary(16) [ rerun:kind: "control" ] -chunk_is_static: bool [ +chunk_is_static: non-null Boolean [ rerun:kind: "control" ] -chunk_key: Binary -chunk_segment_id: Utf8 [ +chunk_key: non-null Binary +chunk_segment_id: non-null Utf8 [ rerun:kind: "control" ] -rerun_segment_layer: Utf8 +rerun_segment_layer: non-null Utf8 diff --git a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__query_dataset__with_query_default_schema.snap b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__query_dataset__with_query_default_schema.snap index 782b3f48f76a..c619984598a9 100644 --- a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__query_dataset__with_query_default_schema.snap +++ b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__query_dataset__with_query_default_schema.snap @@ -2,18 +2,18 @@ source: crates/store/re_redap_tests/src/tests/query_dataset.rs expression: required_chunk_info.format_schema_snapshot() --- -chunk_byte_len: u64 -chunk_entity_path: Utf8 [ +chunk_byte_len: non-null UInt64 +chunk_entity_path: non-null Utf8 [ rerun:kind: "control" ] -chunk_id: FixedSizeBinary[16] [ +chunk_id: non-null FixedSizeBinary(16) [ rerun:kind: "control" ] -chunk_is_static: bool [ +chunk_is_static: non-null Boolean [ rerun:kind: "control" ] -chunk_key: Binary -chunk_segment_id: Utf8 [ +chunk_key: non-null Binary +chunk_segment_id: non-null Utf8 [ rerun:kind: "control" ] -rerun_segment_layer: Utf8 +rerun_segment_layer: non-null Utf8 diff --git a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__query_dataset__with_query_latest_at_end_schema.snap b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__query_dataset__with_query_latest_at_end_schema.snap index 782b3f48f76a..c619984598a9 100644 --- a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__query_dataset__with_query_latest_at_end_schema.snap +++ b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__query_dataset__with_query_latest_at_end_schema.snap @@ -2,18 +2,18 @@ source: crates/store/re_redap_tests/src/tests/query_dataset.rs expression: required_chunk_info.format_schema_snapshot() --- -chunk_byte_len: u64 -chunk_entity_path: Utf8 [ +chunk_byte_len: non-null UInt64 +chunk_entity_path: non-null Utf8 [ rerun:kind: "control" ] -chunk_id: FixedSizeBinary[16] [ +chunk_id: non-null FixedSizeBinary(16) [ rerun:kind: "control" ] -chunk_is_static: bool [ +chunk_is_static: non-null Boolean [ rerun:kind: "control" ] -chunk_key: Binary -chunk_segment_id: Utf8 [ +chunk_key: non-null Binary +chunk_segment_id: non-null Utf8 [ rerun:kind: "control" ] -rerun_segment_layer: Utf8 +rerun_segment_layer: non-null Utf8 diff --git a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__query_dataset__with_query_none_schema.snap b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__query_dataset__with_query_none_schema.snap index 782b3f48f76a..c619984598a9 100644 --- a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__query_dataset__with_query_none_schema.snap +++ b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__query_dataset__with_query_none_schema.snap @@ -2,18 +2,18 @@ source: crates/store/re_redap_tests/src/tests/query_dataset.rs expression: required_chunk_info.format_schema_snapshot() --- -chunk_byte_len: u64 -chunk_entity_path: Utf8 [ +chunk_byte_len: non-null UInt64 +chunk_entity_path: non-null Utf8 [ rerun:kind: "control" ] -chunk_id: FixedSizeBinary[16] [ +chunk_id: non-null FixedSizeBinary(16) [ rerun:kind: "control" ] -chunk_is_static: bool [ +chunk_is_static: non-null Boolean [ rerun:kind: "control" ] -chunk_key: Binary -chunk_segment_id: Utf8 [ +chunk_key: non-null Binary +chunk_segment_id: non-null Utf8 [ rerun:kind: "control" ] -rerun_segment_layer: Utf8 +rerun_segment_layer: non-null Utf8 diff --git a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__query_dataset__with_query_range_all_schema.snap b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__query_dataset__with_query_range_all_schema.snap index 782b3f48f76a..c619984598a9 100644 --- a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__query_dataset__with_query_range_all_schema.snap +++ b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__query_dataset__with_query_range_all_schema.snap @@ -2,18 +2,18 @@ source: crates/store/re_redap_tests/src/tests/query_dataset.rs expression: required_chunk_info.format_schema_snapshot() --- -chunk_byte_len: u64 -chunk_entity_path: Utf8 [ +chunk_byte_len: non-null UInt64 +chunk_entity_path: non-null Utf8 [ rerun:kind: "control" ] -chunk_id: FixedSizeBinary[16] [ +chunk_id: non-null FixedSizeBinary(16) [ rerun:kind: "control" ] -chunk_is_static: bool [ +chunk_is_static: non-null Boolean [ rerun:kind: "control" ] -chunk_key: Binary -chunk_segment_id: Utf8 [ +chunk_key: non-null Binary +chunk_segment_id: non-null Utf8 [ rerun:kind: "control" ] -rerun_segment_layer: Utf8 +rerun_segment_layer: non-null Utf8 diff --git a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__query_filter__simple_dataset_default_schema.snap b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__query_filter__simple_dataset_default_schema.snap index c18ae55289d2..617f8a01ffe8 100644 --- a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__query_filter__simple_dataset_default_schema.snap +++ b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__query_filter__simple_dataset_default_schema.snap @@ -5,14 +5,14 @@ expression: results.format_schema_snapshot() top-level metadata: [ sorbet:version: "0.1.3" ] -/my/entity:example.MyPoints:colors: nullable List[nullable u32] [ +/my/entity:example.MyPoints:colors: List(UInt32) [ rerun:archetype: "example.MyPoints" rerun:component: "example.MyPoints:colors" rerun:component_type: "example.MyColor" rerun:entity_path: "/my/entity" rerun:kind: "data" ] -/my/entity:example.MyPoints:labels: nullable List[nullable Utf8] [ +/my/entity:example.MyPoints:labels: List(Utf8) [ rerun:archetype: "example.MyPoints" rerun:component: "example.MyPoints:labels" rerun:component_type: "example.MyLabel" @@ -20,15 +20,15 @@ top-level metadata: [ rerun:is_static: "true" rerun:kind: "data" ] -/my/entity:example.MyPoints:points: nullable List[nullable Struct[2]] [ +/my/entity:example.MyPoints:points: List(Struct("x": non-null Float32, "y": non-null Float32)) [ rerun:archetype: "example.MyPoints" rerun:component: "example.MyPoints:points" rerun:component_type: "example.MyPoint" rerun:entity_path: "/my/entity" rerun:kind: "data" ] -frame_nr: nullable i64 [ +frame_nr: Int64 [ rerun:index_name: "frame_nr" rerun:kind: "index" ] -rerun_segment_id: Utf8 +rerun_segment_id: non-null Utf8 diff --git a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__query_filter__simple_dataset_frame_nr_eq_schema.snap b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__query_filter__simple_dataset_frame_nr_eq_schema.snap index c18ae55289d2..617f8a01ffe8 100644 --- a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__query_filter__simple_dataset_frame_nr_eq_schema.snap +++ b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__query_filter__simple_dataset_frame_nr_eq_schema.snap @@ -5,14 +5,14 @@ expression: results.format_schema_snapshot() top-level metadata: [ sorbet:version: "0.1.3" ] -/my/entity:example.MyPoints:colors: nullable List[nullable u32] [ +/my/entity:example.MyPoints:colors: List(UInt32) [ rerun:archetype: "example.MyPoints" rerun:component: "example.MyPoints:colors" rerun:component_type: "example.MyColor" rerun:entity_path: "/my/entity" rerun:kind: "data" ] -/my/entity:example.MyPoints:labels: nullable List[nullable Utf8] [ +/my/entity:example.MyPoints:labels: List(Utf8) [ rerun:archetype: "example.MyPoints" rerun:component: "example.MyPoints:labels" rerun:component_type: "example.MyLabel" @@ -20,15 +20,15 @@ top-level metadata: [ rerun:is_static: "true" rerun:kind: "data" ] -/my/entity:example.MyPoints:points: nullable List[nullable Struct[2]] [ +/my/entity:example.MyPoints:points: List(Struct("x": non-null Float32, "y": non-null Float32)) [ rerun:archetype: "example.MyPoints" rerun:component: "example.MyPoints:points" rerun:component_type: "example.MyPoint" rerun:entity_path: "/my/entity" rerun:kind: "data" ] -frame_nr: nullable i64 [ +frame_nr: Int64 [ rerun:index_name: "frame_nr" rerun:kind: "index" ] -rerun_segment_id: Utf8 +rerun_segment_id: non-null Utf8 diff --git a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__query_filter__simple_dataset_seg_id_eq_schema.snap b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__query_filter__simple_dataset_seg_id_eq_schema.snap index c18ae55289d2..617f8a01ffe8 100644 --- a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__query_filter__simple_dataset_seg_id_eq_schema.snap +++ b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__query_filter__simple_dataset_seg_id_eq_schema.snap @@ -5,14 +5,14 @@ expression: results.format_schema_snapshot() top-level metadata: [ sorbet:version: "0.1.3" ] -/my/entity:example.MyPoints:colors: nullable List[nullable u32] [ +/my/entity:example.MyPoints:colors: List(UInt32) [ rerun:archetype: "example.MyPoints" rerun:component: "example.MyPoints:colors" rerun:component_type: "example.MyColor" rerun:entity_path: "/my/entity" rerun:kind: "data" ] -/my/entity:example.MyPoints:labels: nullable List[nullable Utf8] [ +/my/entity:example.MyPoints:labels: List(Utf8) [ rerun:archetype: "example.MyPoints" rerun:component: "example.MyPoints:labels" rerun:component_type: "example.MyLabel" @@ -20,15 +20,15 @@ top-level metadata: [ rerun:is_static: "true" rerun:kind: "data" ] -/my/entity:example.MyPoints:points: nullable List[nullable Struct[2]] [ +/my/entity:example.MyPoints:points: List(Struct("x": non-null Float32, "y": non-null Float32)) [ rerun:archetype: "example.MyPoints" rerun:component: "example.MyPoints:points" rerun:component_type: "example.MyPoint" rerun:entity_path: "/my/entity" rerun:kind: "data" ] -frame_nr: nullable i64 [ +frame_nr: Int64 [ rerun:index_name: "frame_nr" rerun:kind: "index" ] -rerun_segment_id: Utf8 +rerun_segment_id: non-null Utf8 diff --git a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__query_index_values__query_index_values_duration_all_valid_index_values_schema.snap b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__query_index_values__query_index_values_duration_all_valid_index_values_schema.snap index 4eff11795cf8..b3cd64b31eab 100644 --- a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__query_index_values__query_index_values_duration_all_valid_index_values_schema.snap +++ b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__query_index_values__query_index_values_duration_all_valid_index_values_schema.snap @@ -5,14 +5,14 @@ expression: results.format_schema_snapshot() top-level metadata: [ sorbet:version: "0.1.3" ] -/my/entity:example.MyPoints:colors: nullable List[nullable u32] [ +/my/entity:example.MyPoints:colors: List(UInt32) [ rerun:archetype: "example.MyPoints" rerun:component: "example.MyPoints:colors" rerun:component_type: "example.MyColor" rerun:entity_path: "/my/entity" rerun:kind: "data" ] -/my/entity:example.MyPoints:labels: nullable List[nullable Utf8] [ +/my/entity:example.MyPoints:labels: List(Utf8) [ rerun:archetype: "example.MyPoints" rerun:component: "example.MyPoints:labels" rerun:component_type: "example.MyLabel" @@ -20,15 +20,15 @@ top-level metadata: [ rerun:is_static: "true" rerun:kind: "data" ] -/my/entity:example.MyPoints:points: nullable List[nullable Struct[2]] [ +/my/entity:example.MyPoints:points: List(Struct("x": non-null Float32, "y": non-null Float32)) [ rerun:archetype: "example.MyPoints" rerun:component: "example.MyPoints:points" rerun:component_type: "example.MyPoint" rerun:entity_path: "/my/entity" rerun:kind: "data" ] -duration: nullable Duration(ns) [ +duration: Duration(ns) [ rerun:index_name: "duration" rerun:kind: "index" ] -rerun_segment_id: Utf8 +rerun_segment_id: non-null Utf8 diff --git a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__query_index_values__query_index_values_sequence_all_valid_index_values_schema.snap b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__query_index_values__query_index_values_sequence_all_valid_index_values_schema.snap index 0f839438cbe9..30c289175473 100644 --- a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__query_index_values__query_index_values_sequence_all_valid_index_values_schema.snap +++ b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__query_index_values__query_index_values_sequence_all_valid_index_values_schema.snap @@ -5,14 +5,14 @@ expression: results.format_schema_snapshot() top-level metadata: [ sorbet:version: "0.1.3" ] -/my/entity:example.MyPoints:colors: nullable List[nullable u32] [ +/my/entity:example.MyPoints:colors: List(UInt32) [ rerun:archetype: "example.MyPoints" rerun:component: "example.MyPoints:colors" rerun:component_type: "example.MyColor" rerun:entity_path: "/my/entity" rerun:kind: "data" ] -/my/entity:example.MyPoints:labels: nullable List[nullable Utf8] [ +/my/entity:example.MyPoints:labels: List(Utf8) [ rerun:archetype: "example.MyPoints" rerun:component: "example.MyPoints:labels" rerun:component_type: "example.MyLabel" @@ -20,15 +20,15 @@ top-level metadata: [ rerun:is_static: "true" rerun:kind: "data" ] -/my/entity:example.MyPoints:points: nullable List[nullable Struct[2]] [ +/my/entity:example.MyPoints:points: List(Struct("x": non-null Float32, "y": non-null Float32)) [ rerun:archetype: "example.MyPoints" rerun:component: "example.MyPoints:points" rerun:component_type: "example.MyPoint" rerun:entity_path: "/my/entity" rerun:kind: "data" ] -frame_nr: nullable i64 [ +frame_nr: Int64 [ rerun:index_name: "frame_nr" rerun:kind: "index" ] -rerun_segment_id: Utf8 +rerun_segment_id: non-null Utf8 diff --git a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__query_index_values__query_index_values_timestamp_all_valid_index_values_schema.snap b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__query_index_values__query_index_values_timestamp_all_valid_index_values_schema.snap index 14d20ceee0ce..cf7297836d68 100644 --- a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__query_index_values__query_index_values_timestamp_all_valid_index_values_schema.snap +++ b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__query_index_values__query_index_values_timestamp_all_valid_index_values_schema.snap @@ -5,14 +5,14 @@ expression: results.format_schema_snapshot() top-level metadata: [ sorbet:version: "0.1.3" ] -/my/entity:example.MyPoints:colors: nullable List[nullable u32] [ +/my/entity:example.MyPoints:colors: List(UInt32) [ rerun:archetype: "example.MyPoints" rerun:component: "example.MyPoints:colors" rerun:component_type: "example.MyColor" rerun:entity_path: "/my/entity" rerun:kind: "data" ] -/my/entity:example.MyPoints:labels: nullable List[nullable Utf8] [ +/my/entity:example.MyPoints:labels: List(Utf8) [ rerun:archetype: "example.MyPoints" rerun:component: "example.MyPoints:labels" rerun:component_type: "example.MyLabel" @@ -20,15 +20,15 @@ top-level metadata: [ rerun:is_static: "true" rerun:kind: "data" ] -/my/entity:example.MyPoints:points: nullable List[nullable Struct[2]] [ +/my/entity:example.MyPoints:points: List(Struct("x": non-null Float32, "y": non-null Float32)) [ rerun:archetype: "example.MyPoints" rerun:component: "example.MyPoints:points" rerun:component_type: "example.MyPoint" rerun:entity_path: "/my/entity" rerun:kind: "data" ] -rerun_segment_id: Utf8 -timestamp: nullable Timestamp(ns) [ +rerun_segment_id: non-null Utf8 +timestamp: Timestamp(ns) [ rerun:index_name: "timestamp" rerun:kind: "index" ] diff --git a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__register_segment__empty_manifest_schema.snap b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__register_segment__empty_manifest_schema.snap index 0d36538287ba..27c8709620e8 100644 --- a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__register_segment__empty_manifest_schema.snap +++ b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__register_segment__empty_manifest_schema.snap @@ -2,13 +2,13 @@ source: crates/store/re_redap_tests/src/tests/register_segment.rs expression: batch.format_schema_snapshot() --- -rerun_last_updated_at: Timestamp(ns) -rerun_layer_name: Utf8 -rerun_layer_type: Utf8 -rerun_num_chunks: u64 -rerun_registration_status: Utf8 -rerun_registration_time: Timestamp(ns) -rerun_schema_sha256: FixedSizeBinary[32] -rerun_segment_id: Utf8 -rerun_size_bytes: u64 -rerun_storage_url: Utf8 +rerun_last_updated_at: non-null Timestamp(ns) +rerun_layer_name: non-null Utf8 +rerun_layer_type: non-null Utf8 +rerun_num_chunks: non-null UInt64 +rerun_registration_status: non-null Utf8 +rerun_registration_time: non-null Timestamp(ns) +rerun_schema_sha256: non-null FixedSizeBinary(32) +rerun_segment_id: non-null Utf8 +rerun_size_bytes: non-null UInt64 +rerun_storage_url: non-null Utf8 diff --git a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__register_segment__empty_segments_schema.snap b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__register_segment__empty_segments_schema.snap index ccf338e1df78..21257c126e56 100644 --- a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__register_segment__empty_segments_schema.snap +++ b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__register_segment__empty_segments_schema.snap @@ -2,9 +2,9 @@ source: crates/store/re_redap_tests/src/tests/register_segment.rs expression: batch.format_schema_snapshot() --- -rerun_last_updated_at: Timestamp(ns) -rerun_layer_names: List[Utf8] -rerun_num_chunks: u64 -rerun_segment_id: Utf8 -rerun_size_bytes: u64 -rerun_storage_urls: List[Utf8] +rerun_last_updated_at: non-null Timestamp(ns) +rerun_layer_names: non-null List(non-null Utf8, field: 'rerun_layer_names') +rerun_num_chunks: non-null UInt64 +rerun_segment_id: non-null Utf8 +rerun_size_bytes: non-null UInt64 +rerun_storage_urls: non-null List(non-null Utf8, field: 'rerun_storage_urls') diff --git a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__register_segment__out_of_order_properties_manifest_schema.snap b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__register_segment__out_of_order_properties_manifest_schema.snap index 1ea90bb14db0..0483acaf87af 100644 --- a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__register_segment__out_of_order_properties_manifest_schema.snap +++ b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__register_segment__out_of_order_properties_manifest_schema.snap @@ -2,20 +2,20 @@ source: crates/store/re_redap_tests/src/tests/register_segment.rs expression: batch.format_schema_snapshot() --- -property:text_log:TextLog:text: nullable List[nullable Utf8] [ +property:text_log:TextLog:text: List(Utf8) [ rerun:archetype: "rerun.archetypes.TextLog" rerun:component: "TextLog:text" rerun:component_type: "rerun.components.Text" rerun:entity_path: "/__properties/text_log" rerun:kind: "data" ] -rerun_last_updated_at: Timestamp(ns) -rerun_layer_name: Utf8 -rerun_layer_type: Utf8 -rerun_num_chunks: u64 -rerun_registration_status: Utf8 -rerun_registration_time: Timestamp(ns) -rerun_schema_sha256: FixedSizeBinary[32] -rerun_segment_id: Utf8 -rerun_size_bytes: u64 -rerun_storage_url: Utf8 +rerun_last_updated_at: non-null Timestamp(ns) +rerun_layer_name: non-null Utf8 +rerun_layer_type: non-null Utf8 +rerun_num_chunks: non-null UInt64 +rerun_registration_status: non-null Utf8 +rerun_registration_time: non-null Timestamp(ns) +rerun_schema_sha256: non-null FixedSizeBinary(32) +rerun_segment_id: non-null Utf8 +rerun_size_bytes: non-null UInt64 +rerun_storage_url: non-null Utf8 diff --git a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__register_segment__out_of_order_properties_segments_schema.snap b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__register_segment__out_of_order_properties_segments_schema.snap index e58763bfe234..c59f946ddd8b 100644 --- a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__register_segment__out_of_order_properties_segments_schema.snap +++ b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__register_segment__out_of_order_properties_segments_schema.snap @@ -2,16 +2,16 @@ source: crates/store/re_redap_tests/src/tests/register_segment.rs expression: batch.format_schema_snapshot() --- -property:text_log:TextLog:text: nullable List[nullable Utf8] [ +property:text_log:TextLog:text: List(Utf8) [ rerun:archetype: "rerun.archetypes.TextLog" rerun:component: "TextLog:text" rerun:component_type: "rerun.components.Text" rerun:entity_path: "/__properties/text_log" rerun:kind: "data" ] -rerun_last_updated_at: Timestamp(ns) -rerun_layer_names: List[Utf8] -rerun_num_chunks: u64 -rerun_segment_id: Utf8 -rerun_size_bytes: u64 -rerun_storage_urls: List[Utf8] +rerun_last_updated_at: non-null Timestamp(ns) +rerun_layer_names: non-null List(non-null Utf8, field: 'rerun_layer_names') +rerun_num_chunks: non-null UInt64 +rerun_segment_id: non-null Utf8 +rerun_size_bytes: non-null UInt64 +rerun_storage_urls: non-null List(non-null Utf8, field: 'rerun_storage_urls') diff --git a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__register_segment__register_prefix_manifest_manifest_schema.snap b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__register_segment__register_prefix_manifest_manifest_schema.snap index 0d36538287ba..27c8709620e8 100644 --- a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__register_segment__register_prefix_manifest_manifest_schema.snap +++ b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__register_segment__register_prefix_manifest_manifest_schema.snap @@ -2,13 +2,13 @@ source: crates/store/re_redap_tests/src/tests/register_segment.rs expression: batch.format_schema_snapshot() --- -rerun_last_updated_at: Timestamp(ns) -rerun_layer_name: Utf8 -rerun_layer_type: Utf8 -rerun_num_chunks: u64 -rerun_registration_status: Utf8 -rerun_registration_time: Timestamp(ns) -rerun_schema_sha256: FixedSizeBinary[32] -rerun_segment_id: Utf8 -rerun_size_bytes: u64 -rerun_storage_url: Utf8 +rerun_last_updated_at: non-null Timestamp(ns) +rerun_layer_name: non-null Utf8 +rerun_layer_type: non-null Utf8 +rerun_num_chunks: non-null UInt64 +rerun_registration_status: non-null Utf8 +rerun_registration_time: non-null Timestamp(ns) +rerun_schema_sha256: non-null FixedSizeBinary(32) +rerun_segment_id: non-null Utf8 +rerun_size_bytes: non-null UInt64 +rerun_storage_url: non-null Utf8 diff --git a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__register_segment__register_prefix_segments_segments_schema.snap b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__register_segment__register_prefix_segments_segments_schema.snap index a3b74359e469..9ac7457802af 100644 --- a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__register_segment__register_prefix_segments_segments_schema.snap +++ b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__register_segment__register_prefix_segments_segments_schema.snap @@ -2,21 +2,21 @@ source: crates/store/re_redap_tests/src/tests/register_segment.rs expression: batch.format_schema_snapshot() --- -frame_nr:end: nullable i64 [ +frame_nr:end: Int64 [ rerun:index: "frame_nr" rerun:index_kind: "sequence" rerun:index_marker: "end" rerun:kind: "index" ] -frame_nr:start: nullable i64 [ +frame_nr:start: Int64 [ rerun:index: "frame_nr" rerun:index_kind: "sequence" rerun:index_marker: "start" rerun:kind: "index" ] -rerun_last_updated_at: Timestamp(ns) -rerun_layer_names: List[Utf8] -rerun_num_chunks: u64 -rerun_segment_id: Utf8 -rerun_size_bytes: u64 -rerun_storage_urls: List[Utf8] +rerun_last_updated_at: non-null Timestamp(ns) +rerun_layer_names: non-null List(non-null Utf8, field: 'rerun_layer_names') +rerun_num_chunks: non-null UInt64 +rerun_segment_id: non-null Utf8 +rerun_size_bytes: non-null UInt64 +rerun_storage_urls: non-null List(non-null Utf8, field: 'rerun_storage_urls') diff --git a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__register_segment__segment1_props_should_be_there_segments_schema.snap b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__register_segment__segment1_props_should_be_there_segments_schema.snap index 51895a2cb559..87777b1cea5f 100644 --- a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__register_segment__segment1_props_should_be_there_segments_schema.snap +++ b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__register_segment__segment1_props_should_be_there_segments_schema.snap @@ -2,14 +2,14 @@ source: crates/store/re_redap_tests/src/tests/register_segment.rs expression: batch.format_schema_snapshot() --- -property:prop:test: nullable List[nullable f64] [ +property:prop:test: List(Float64) [ rerun:component: "test" rerun:entity_path: "/__properties/prop" rerun:kind: "data" ] -rerun_last_updated_at: Timestamp(ns) -rerun_layer_names: List[Utf8] -rerun_num_chunks: u64 -rerun_segment_id: Utf8 -rerun_size_bytes: u64 -rerun_storage_urls: List[Utf8] +rerun_last_updated_at: non-null Timestamp(ns) +rerun_layer_names: non-null List(non-null Utf8, field: 'rerun_layer_names') +rerun_num_chunks: non-null UInt64 +rerun_segment_id: non-null Utf8 +rerun_size_bytes: non-null UInt64 +rerun_storage_urls: non-null List(non-null Utf8, field: 'rerun_storage_urls') diff --git a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__register_segment__simple_blueprint_manifest_schema.snap b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__register_segment__simple_blueprint_manifest_schema.snap index 0d36538287ba..27c8709620e8 100644 --- a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__register_segment__simple_blueprint_manifest_schema.snap +++ b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__register_segment__simple_blueprint_manifest_schema.snap @@ -2,13 +2,13 @@ source: crates/store/re_redap_tests/src/tests/register_segment.rs expression: batch.format_schema_snapshot() --- -rerun_last_updated_at: Timestamp(ns) -rerun_layer_name: Utf8 -rerun_layer_type: Utf8 -rerun_num_chunks: u64 -rerun_registration_status: Utf8 -rerun_registration_time: Timestamp(ns) -rerun_schema_sha256: FixedSizeBinary[32] -rerun_segment_id: Utf8 -rerun_size_bytes: u64 -rerun_storage_url: Utf8 +rerun_last_updated_at: non-null Timestamp(ns) +rerun_layer_name: non-null Utf8 +rerun_layer_type: non-null Utf8 +rerun_num_chunks: non-null UInt64 +rerun_registration_status: non-null Utf8 +rerun_registration_time: non-null Timestamp(ns) +rerun_schema_sha256: non-null FixedSizeBinary(32) +rerun_segment_id: non-null Utf8 +rerun_size_bytes: non-null UInt64 +rerun_storage_url: non-null Utf8 diff --git a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__register_segment__simple_blueprint_segments_schema.snap b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__register_segment__simple_blueprint_segments_schema.snap index a3b74359e469..9ac7457802af 100644 --- a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__register_segment__simple_blueprint_segments_schema.snap +++ b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__register_segment__simple_blueprint_segments_schema.snap @@ -2,21 +2,21 @@ source: crates/store/re_redap_tests/src/tests/register_segment.rs expression: batch.format_schema_snapshot() --- -frame_nr:end: nullable i64 [ +frame_nr:end: Int64 [ rerun:index: "frame_nr" rerun:index_kind: "sequence" rerun:index_marker: "end" rerun:kind: "index" ] -frame_nr:start: nullable i64 [ +frame_nr:start: Int64 [ rerun:index: "frame_nr" rerun:index_kind: "sequence" rerun:index_marker: "start" rerun:kind: "index" ] -rerun_last_updated_at: Timestamp(ns) -rerun_layer_names: List[Utf8] -rerun_num_chunks: u64 -rerun_segment_id: Utf8 -rerun_size_bytes: u64 -rerun_storage_urls: List[Utf8] +rerun_last_updated_at: non-null Timestamp(ns) +rerun_layer_names: non-null List(non-null Utf8, field: 'rerun_layer_names') +rerun_num_chunks: non-null UInt64 +rerun_segment_id: non-null Utf8 +rerun_size_bytes: non-null UInt64 +rerun_storage_urls: non-null List(non-null Utf8, field: 'rerun_storage_urls') diff --git a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__register_segment__simple_manifest_schema.snap b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__register_segment__simple_manifest_schema.snap index 0d36538287ba..27c8709620e8 100644 --- a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__register_segment__simple_manifest_schema.snap +++ b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__register_segment__simple_manifest_schema.snap @@ -2,13 +2,13 @@ source: crates/store/re_redap_tests/src/tests/register_segment.rs expression: batch.format_schema_snapshot() --- -rerun_last_updated_at: Timestamp(ns) -rerun_layer_name: Utf8 -rerun_layer_type: Utf8 -rerun_num_chunks: u64 -rerun_registration_status: Utf8 -rerun_registration_time: Timestamp(ns) -rerun_schema_sha256: FixedSizeBinary[32] -rerun_segment_id: Utf8 -rerun_size_bytes: u64 -rerun_storage_url: Utf8 +rerun_last_updated_at: non-null Timestamp(ns) +rerun_layer_name: non-null Utf8 +rerun_layer_type: non-null Utf8 +rerun_num_chunks: non-null UInt64 +rerun_registration_status: non-null Utf8 +rerun_registration_time: non-null Timestamp(ns) +rerun_schema_sha256: non-null FixedSizeBinary(32) +rerun_segment_id: non-null Utf8 +rerun_size_bytes: non-null UInt64 +rerun_storage_url: non-null Utf8 diff --git a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__register_segment__simple_segments_schema.snap b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__register_segment__simple_segments_schema.snap index a3b74359e469..9ac7457802af 100644 --- a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__register_segment__simple_segments_schema.snap +++ b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__register_segment__simple_segments_schema.snap @@ -2,21 +2,21 @@ source: crates/store/re_redap_tests/src/tests/register_segment.rs expression: batch.format_schema_snapshot() --- -frame_nr:end: nullable i64 [ +frame_nr:end: Int64 [ rerun:index: "frame_nr" rerun:index_kind: "sequence" rerun:index_marker: "end" rerun:kind: "index" ] -frame_nr:start: nullable i64 [ +frame_nr:start: Int64 [ rerun:index: "frame_nr" rerun:index_kind: "sequence" rerun:index_marker: "start" rerun:kind: "index" ] -rerun_last_updated_at: Timestamp(ns) -rerun_layer_names: List[Utf8] -rerun_num_chunks: u64 -rerun_segment_id: Utf8 -rerun_size_bytes: u64 -rerun_storage_urls: List[Utf8] +rerun_last_updated_at: non-null Timestamp(ns) +rerun_layer_names: non-null List(non-null Utf8, field: 'rerun_layer_names') +rerun_num_chunks: non-null UInt64 +rerun_segment_id: non-null Utf8 +rerun_size_bytes: non-null UInt64 +rerun_storage_urls: non-null List(non-null Utf8, field: 'rerun_storage_urls') diff --git a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__register_segment__simple_with_layers_manifest_schema.snap b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__register_segment__simple_with_layers_manifest_schema.snap index 0d36538287ba..27c8709620e8 100644 --- a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__register_segment__simple_with_layers_manifest_schema.snap +++ b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__register_segment__simple_with_layers_manifest_schema.snap @@ -2,13 +2,13 @@ source: crates/store/re_redap_tests/src/tests/register_segment.rs expression: batch.format_schema_snapshot() --- -rerun_last_updated_at: Timestamp(ns) -rerun_layer_name: Utf8 -rerun_layer_type: Utf8 -rerun_num_chunks: u64 -rerun_registration_status: Utf8 -rerun_registration_time: Timestamp(ns) -rerun_schema_sha256: FixedSizeBinary[32] -rerun_segment_id: Utf8 -rerun_size_bytes: u64 -rerun_storage_url: Utf8 +rerun_last_updated_at: non-null Timestamp(ns) +rerun_layer_name: non-null Utf8 +rerun_layer_type: non-null Utf8 +rerun_num_chunks: non-null UInt64 +rerun_registration_status: non-null Utf8 +rerun_registration_time: non-null Timestamp(ns) +rerun_schema_sha256: non-null FixedSizeBinary(32) +rerun_segment_id: non-null Utf8 +rerun_size_bytes: non-null UInt64 +rerun_storage_url: non-null Utf8 diff --git a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__register_segment__simple_with_layers_segments_schema.snap b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__register_segment__simple_with_layers_segments_schema.snap index a3b74359e469..9ac7457802af 100644 --- a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__register_segment__simple_with_layers_segments_schema.snap +++ b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__register_segment__simple_with_layers_segments_schema.snap @@ -2,21 +2,21 @@ source: crates/store/re_redap_tests/src/tests/register_segment.rs expression: batch.format_schema_snapshot() --- -frame_nr:end: nullable i64 [ +frame_nr:end: Int64 [ rerun:index: "frame_nr" rerun:index_kind: "sequence" rerun:index_marker: "end" rerun:kind: "index" ] -frame_nr:start: nullable i64 [ +frame_nr:start: Int64 [ rerun:index: "frame_nr" rerun:index_kind: "sequence" rerun:index_marker: "start" rerun:kind: "index" ] -rerun_last_updated_at: Timestamp(ns) -rerun_layer_names: List[Utf8] -rerun_num_chunks: u64 -rerun_segment_id: Utf8 -rerun_size_bytes: u64 -rerun_storage_urls: List[Utf8] +rerun_last_updated_at: non-null Timestamp(ns) +rerun_layer_names: non-null List(non-null Utf8, field: 'rerun_layer_names') +rerun_num_chunks: non-null UInt64 +rerun_segment_id: non-null Utf8 +rerun_size_bytes: non-null UInt64 +rerun_storage_urls: non-null List(non-null Utf8, field: 'rerun_storage_urls') diff --git a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__register_segment__simple_with_multiple_timelines_manifest_schema.snap b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__register_segment__simple_with_multiple_timelines_manifest_schema.snap index 1ea90bb14db0..0483acaf87af 100644 --- a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__register_segment__simple_with_multiple_timelines_manifest_schema.snap +++ b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__register_segment__simple_with_multiple_timelines_manifest_schema.snap @@ -2,20 +2,20 @@ source: crates/store/re_redap_tests/src/tests/register_segment.rs expression: batch.format_schema_snapshot() --- -property:text_log:TextLog:text: nullable List[nullable Utf8] [ +property:text_log:TextLog:text: List(Utf8) [ rerun:archetype: "rerun.archetypes.TextLog" rerun:component: "TextLog:text" rerun:component_type: "rerun.components.Text" rerun:entity_path: "/__properties/text_log" rerun:kind: "data" ] -rerun_last_updated_at: Timestamp(ns) -rerun_layer_name: Utf8 -rerun_layer_type: Utf8 -rerun_num_chunks: u64 -rerun_registration_status: Utf8 -rerun_registration_time: Timestamp(ns) -rerun_schema_sha256: FixedSizeBinary[32] -rerun_segment_id: Utf8 -rerun_size_bytes: u64 -rerun_storage_url: Utf8 +rerun_last_updated_at: non-null Timestamp(ns) +rerun_layer_name: non-null Utf8 +rerun_layer_type: non-null Utf8 +rerun_num_chunks: non-null UInt64 +rerun_registration_status: non-null Utf8 +rerun_registration_time: non-null Timestamp(ns) +rerun_schema_sha256: non-null FixedSizeBinary(32) +rerun_segment_id: non-null Utf8 +rerun_size_bytes: non-null UInt64 +rerun_storage_url: non-null Utf8 diff --git a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__register_segment__simple_with_multiple_timelines_segments_schema.snap b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__register_segment__simple_with_multiple_timelines_segments_schema.snap index ca7cea9c2ec5..e77e20ca230c 100644 --- a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__register_segment__simple_with_multiple_timelines_segments_schema.snap +++ b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__register_segment__simple_with_multiple_timelines_segments_schema.snap @@ -2,50 +2,50 @@ source: crates/store/re_redap_tests/src/tests/register_segment.rs expression: batch.format_schema_snapshot() --- -duration:end: nullable Duration(ns) [ +duration:end: Duration(ns) [ rerun:index: "duration" rerun:index_kind: "duration" rerun:index_marker: "end" rerun:kind: "index" ] -duration:start: nullable Duration(ns) [ +duration:start: Duration(ns) [ rerun:index: "duration" rerun:index_kind: "duration" rerun:index_marker: "start" rerun:kind: "index" ] -frame_nr:end: nullable i64 [ +frame_nr:end: Int64 [ rerun:index: "frame_nr" rerun:index_kind: "sequence" rerun:index_marker: "end" rerun:kind: "index" ] -frame_nr:start: nullable i64 [ +frame_nr:start: Int64 [ rerun:index: "frame_nr" rerun:index_kind: "sequence" rerun:index_marker: "start" rerun:kind: "index" ] -property:text_log:TextLog:text: nullable List[nullable Utf8] [ +property:text_log:TextLog:text: List(Utf8) [ rerun:archetype: "rerun.archetypes.TextLog" rerun:component: "TextLog:text" rerun:component_type: "rerun.components.Text" rerun:entity_path: "/__properties/text_log" rerun:kind: "data" ] -rerun_last_updated_at: Timestamp(ns) -rerun_layer_names: List[Utf8] -rerun_num_chunks: u64 -rerun_segment_id: Utf8 -rerun_size_bytes: u64 -rerun_storage_urls: List[Utf8] -timestamp:end: nullable Timestamp(ns) [ +rerun_last_updated_at: non-null Timestamp(ns) +rerun_layer_names: non-null List(non-null Utf8, field: 'rerun_layer_names') +rerun_num_chunks: non-null UInt64 +rerun_segment_id: non-null Utf8 +rerun_size_bytes: non-null UInt64 +rerun_storage_urls: non-null List(non-null Utf8, field: 'rerun_storage_urls') +timestamp:end: Timestamp(ns) [ rerun:index: "timestamp" rerun:index_kind: "timestamp" rerun:index_marker: "end" rerun:kind: "index" ] -timestamp:start: nullable Timestamp(ns) [ +timestamp:start: Timestamp(ns) [ rerun:index: "timestamp" rerun:index_kind: "timestamp" rerun:index_marker: "start" diff --git a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__register_segment__simple_with_properties_manifest_schema.snap b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__register_segment__simple_with_properties_manifest_schema.snap index 444de09dfc65..af69ec5b162f 100644 --- a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__register_segment__simple_with_properties_manifest_schema.snap +++ b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__register_segment__simple_with_properties_manifest_schema.snap @@ -2,27 +2,27 @@ source: crates/store/re_redap_tests/src/tests/register_segment.rs expression: batch.format_schema_snapshot() --- -property:points:Points2D:positions: nullable List[nullable FixedSizeList[nullable f32; 2]] [ +property:points:Points2D:positions: List(FixedSizeList(2 x Float32)) [ rerun:archetype: "rerun.archetypes.Points2D" rerun:component: "Points2D:positions" rerun:component_type: "rerun.components.Position2D" rerun:entity_path: "/__properties/points" rerun:kind: "data" ] -property:text_log:TextLog:text: nullable List[nullable Utf8] [ +property:text_log:TextLog:text: List(Utf8) [ rerun:archetype: "rerun.archetypes.TextLog" rerun:component: "TextLog:text" rerun:component_type: "rerun.components.Text" rerun:entity_path: "/__properties/text_log" rerun:kind: "data" ] -rerun_last_updated_at: Timestamp(ns) -rerun_layer_name: Utf8 -rerun_layer_type: Utf8 -rerun_num_chunks: u64 -rerun_registration_status: Utf8 -rerun_registration_time: Timestamp(ns) -rerun_schema_sha256: FixedSizeBinary[32] -rerun_segment_id: Utf8 -rerun_size_bytes: u64 -rerun_storage_url: Utf8 +rerun_last_updated_at: non-null Timestamp(ns) +rerun_layer_name: non-null Utf8 +rerun_layer_type: non-null Utf8 +rerun_num_chunks: non-null UInt64 +rerun_registration_status: non-null Utf8 +rerun_registration_time: non-null Timestamp(ns) +rerun_schema_sha256: non-null FixedSizeBinary(32) +rerun_segment_id: non-null Utf8 +rerun_size_bytes: non-null UInt64 +rerun_storage_url: non-null Utf8 diff --git a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__register_segment__simple_with_properties_segments_schema.snap b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__register_segment__simple_with_properties_segments_schema.snap index 4405ab59e3f2..65b7cbb1faae 100644 --- a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__register_segment__simple_with_properties_segments_schema.snap +++ b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__register_segment__simple_with_properties_segments_schema.snap @@ -2,35 +2,35 @@ source: crates/store/re_redap_tests/src/tests/register_segment.rs expression: batch.format_schema_snapshot() --- -frame_nr:end: nullable i64 [ +frame_nr:end: Int64 [ rerun:index: "frame_nr" rerun:index_kind: "sequence" rerun:index_marker: "end" rerun:kind: "index" ] -frame_nr:start: nullable i64 [ +frame_nr:start: Int64 [ rerun:index: "frame_nr" rerun:index_kind: "sequence" rerun:index_marker: "start" rerun:kind: "index" ] -property:points:Points2D:positions: nullable List[nullable FixedSizeList[nullable f32; 2]] [ +property:points:Points2D:positions: List(FixedSizeList(2 x Float32)) [ rerun:archetype: "rerun.archetypes.Points2D" rerun:component: "Points2D:positions" rerun:component_type: "rerun.components.Position2D" rerun:entity_path: "/__properties/points" rerun:kind: "data" ] -property:text_log:TextLog:text: nullable List[nullable Utf8] [ +property:text_log:TextLog:text: List(Utf8) [ rerun:archetype: "rerun.archetypes.TextLog" rerun:component: "TextLog:text" rerun:component_type: "rerun.components.Text" rerun:entity_path: "/__properties/text_log" rerun:kind: "data" ] -rerun_last_updated_at: Timestamp(ns) -rerun_layer_names: List[Utf8] -rerun_num_chunks: u64 -rerun_segment_id: Utf8 -rerun_size_bytes: u64 -rerun_storage_urls: List[Utf8] +rerun_last_updated_at: non-null Timestamp(ns) +rerun_layer_names: non-null List(non-null Utf8, field: 'rerun_layer_names') +rerun_num_chunks: non-null UInt64 +rerun_segment_id: non-null Utf8 +rerun_size_bytes: non-null UInt64 +rerun_storage_urls: non-null List(non-null Utf8, field: 'rerun_storage_urls') diff --git a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__rrd_manifest__fetch_chunks_from_rrd_manifest.snap b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__rrd_manifest__fetch_chunks_from_rrd_manifest.snap index 65c8a0f07699..4f1d55ff2926 100644 --- a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__rrd_manifest__fetch_chunks_from_rrd_manifest.snap +++ b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__rrd_manifest__fetch_chunks_from_rrd_manifest.snap @@ -2,30 +2,31 @@ source: crates/store/re_redap_tests/src/tests/rrd_manifest.rs expression: printed --- -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ METADATA: │ -│ * entity_path: /my/entity │ -│ * id: chunk_00000000000000010000000000000001 │ -│ * version: 0.1.3 │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ ┌───────────────────────────────────────────────┬──────────────────────┬────────────────────────────────────┬─────────────────────────────────────────┐ │ -│ │ RowId ┆ frame_nr ┆ example.MyPoints:colors ┆ example.MyPoints:points │ │ -│ │ --- ┆ --- ┆ --- ┆ --- │ │ -│ │ type: FixedSizeBinary[16] ┆ type: nullable i64 ┆ type: nullable List[nullable u32] ┆ type: nullable List[nullable Struct[2]] │ │ -│ │ ARROW:extension:metadata: {"namespace":"row"} ┆ index_name: frame_nr ┆ archetype: example.MyPoints ┆ archetype: example.MyPoints │ │ -│ │ ARROW:extension:name: TUID ┆ is_sorted: true ┆ component: example.MyPoints:colors ┆ component: example.MyPoints:points │ │ -│ │ is_sorted: true ┆ kind: index ┆ component_type: example.MyColor ┆ component_type: example.MyPoint │ │ -│ │ kind: control ┆ ┆ kind: data ┆ kind: data │ │ -│ ╞═══════════════════════════════════════════════╪══════════════════════╪════════════════════════════════════╪═════════════════════════════════════════╡ │ -│ │ row_00000000000000010000000000000001 ┆ 10 ┆ [0] ┆ [{x: 0.0, y: 0.0}] │ │ -│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ -│ │ row_00000000000000010000000000000002 ┆ 20 ┆ [1] ┆ [{x: 1.0, y: 1.0}] │ │ -│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ -│ │ row_00000000000000010000000000000003 ┆ 30 ┆ [2] ┆ [{x: 2.0, y: 2.0}] │ │ -│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ -│ │ row_00000000000000010000000000000004 ┆ 40 ┆ [3] ┆ [{x: 3.0, y: 3.0}] │ │ -│ └───────────────────────────────────────────────┴──────────────────────┴────────────────────────────────────┴─────────────────────────────────────────┘ │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ METADATA: │ +│ * entity_path: /my/entity │ +│ * id: chunk_00000000000000010000000000000001 │ +│ * version: 0.1.3 │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ ┌───────────────────────────────────────────────┬──────────────────────┬────────────────────────────────────┬──────────────────────────────────────────────────────────┐ │ +│ │ RowId ┆ frame_nr ┆ example.MyPoints:colors ┆ example.MyPoints:points │ │ +│ │ --- ┆ --- ┆ --- ┆ --- │ │ +│ │ type: non-null FixedSizeBinary(16) ┆ type: Int64 ┆ type: List(UInt32) ┆ type: List(Struct("x": non-null Float32, "y": non-null │ │ +│ │ ARROW:extension:metadata: {"namespace":"row"} ┆ index_name: frame_nr ┆ archetype: example.MyPoints ┆ Float32)) │ │ +│ │ ARROW:extension:name: TUID ┆ is_sorted: true ┆ component: example.MyPoints:colors ┆ archetype: example.MyPoints │ │ +│ │ is_sorted: true ┆ kind: index ┆ component_type: example.MyColor ┆ component: example.MyPoints:points │ │ +│ │ kind: control ┆ ┆ kind: data ┆ component_type: example.MyPoint │ │ +│ │ ┆ ┆ ┆ kind: data │ │ +│ ╞═══════════════════════════════════════════════╪══════════════════════╪════════════════════════════════════╪══════════════════════════════════════════════════════════╡ │ +│ │ row_00000000000000010000000000000001 ┆ 10 ┆ [0] ┆ [{x: 0.0, y: 0.0}] │ │ +│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ +│ │ row_00000000000000010000000000000002 ┆ 20 ┆ [1] ┆ [{x: 1.0, y: 1.0}] │ │ +│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ +│ │ row_00000000000000010000000000000003 ┆ 30 ┆ [2] ┆ [{x: 2.0, y: 2.0}] │ │ +│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ +│ │ row_00000000000000010000000000000004 ┆ 40 ┆ [3] ┆ [{x: 3.0, y: 3.0}] │ │ +│ └───────────────────────────────────────────────┴──────────────────────┴────────────────────────────────────┴──────────────────────────────────────────────────────────┘ │ +└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ ┌────────────────────────────────────────────────────────────────────────────────────────┐ │ METADATA: │ │ * entity_path: /my/entity │ @@ -35,7 +36,7 @@ expression: printed │ ┌───────────────────────────────────────────────┬────────────────────────────────────┐ │ │ │ RowId ┆ example.MyPoints:labels │ │ │ │ --- ┆ --- │ │ -│ │ type: FixedSizeBinary[16] ┆ type: nullable List[nullable Utf8] │ │ +│ │ type: non-null FixedSizeBinary(16) ┆ type: List(Utf8) │ │ │ │ ARROW:extension:metadata: {"namespace":"row"} ┆ archetype: example.MyPoints │ │ │ │ ARROW:extension:name: TUID ┆ component: example.MyPoints:labels │ │ │ │ is_sorted: true ┆ component_type: example.MyLabel │ │ diff --git a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__rrd_manifest__layered_segment_rrd_manifest_1_all_there_sorbet_schema.snap b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__rrd_manifest__layered_segment_rrd_manifest_1_all_there_sorbet_schema.snap index 7e808c8d5887..229d5eb9f8b9 100644 --- a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__rrd_manifest__layered_segment_rrd_manifest_1_all_there_sorbet_schema.snap +++ b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__rrd_manifest__layered_segment_rrd_manifest_1_all_there_sorbet_schema.snap @@ -5,20 +5,20 @@ expression: rrd_manifest.sorbet_schema.format_snapshot() top-level metadata: [ sorbet:version: "0.1.3" ] -/data:test: nullable List[nullable f32] [ +/data:test: List(Float32) [ rerun:component: "test" rerun:entity_path: "/data" rerun:is_static: "true" rerun:kind: "data" ] -/my/entity:example.MyPoints:colors: nullable List[nullable u32] [ +/my/entity:example.MyPoints:colors: List(UInt32) [ rerun:archetype: "example.MyPoints" rerun:component: "example.MyPoints:colors" rerun:component_type: "example.MyColor" rerun:entity_path: "/my/entity" rerun:kind: "data" ] -/my/entity:example.MyPoints:labels: nullable List[nullable Utf8] [ +/my/entity:example.MyPoints:labels: List(Utf8) [ rerun:archetype: "example.MyPoints" rerun:component: "example.MyPoints:labels" rerun:component_type: "example.MyLabel" @@ -26,18 +26,18 @@ top-level metadata: [ rerun:is_static: "true" rerun:kind: "data" ] -/my/entity:example.MyPoints:points: nullable List[nullable Struct[2]] [ +/my/entity:example.MyPoints:points: List(Struct("x": non-null Float32, "y": non-null Float32)) [ rerun:archetype: "example.MyPoints" rerun:component: "example.MyPoints:points" rerun:component_type: "example.MyPoint" rerun:entity_path: "/my/entity" rerun:kind: "data" ] -frame_nr: nullable i64 [ +frame_nr: Int64 [ rerun:index_name: "frame_nr" rerun:kind: "index" ] -rerun.controls.RowId: FixedSizeBinary[16] [ +rerun.controls.RowId: non-null FixedSizeBinary(16) [ ARROW:extension:metadata: "{\"namespace\":\"row\"}" ARROW:extension:name: "rerun.datatypes.TUID" rerun:kind: "control" diff --git a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__rrd_manifest__layered_segment_rrd_manifest_2_base_removed_sorbet_schema.snap b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__rrd_manifest__layered_segment_rrd_manifest_2_base_removed_sorbet_schema.snap index 1f5855822556..fa17a25b76b4 100644 --- a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__rrd_manifest__layered_segment_rrd_manifest_2_base_removed_sorbet_schema.snap +++ b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__rrd_manifest__layered_segment_rrd_manifest_2_base_removed_sorbet_schema.snap @@ -5,13 +5,13 @@ expression: rrd_manifest.sorbet_schema.format_snapshot() top-level metadata: [ sorbet:version: "0.1.3" ] -/data:test: nullable List[nullable f32] [ +/data:test: List(Float32) [ rerun:component: "test" rerun:entity_path: "/data" rerun:is_static: "true" rerun:kind: "data" ] -rerun.controls.RowId: FixedSizeBinary[16] [ +rerun.controls.RowId: non-null FixedSizeBinary(16) [ ARROW:extension:metadata: "{\"namespace\":\"row\"}" ARROW:extension:name: "rerun.datatypes.TUID" rerun:kind: "control" diff --git a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__rrd_manifest__rrd_manifest_sorbet_schema.snap b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__rrd_manifest__rrd_manifest_sorbet_schema.snap index c0fe81a91b3b..aace8e6a5d30 100644 --- a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__rrd_manifest__rrd_manifest_sorbet_schema.snap +++ b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__rrd_manifest__rrd_manifest_sorbet_schema.snap @@ -5,14 +5,14 @@ expression: rrd_manifest.sorbet_schema.format_snapshot() top-level metadata: [ sorbet:version: "0.1.3" ] -/my/entity:example.MyPoints:colors: nullable List[nullable u32] [ +/my/entity:example.MyPoints:colors: List(UInt32) [ rerun:archetype: "example.MyPoints" rerun:component: "example.MyPoints:colors" rerun:component_type: "example.MyColor" rerun:entity_path: "/my/entity" rerun:kind: "data" ] -/my/entity:example.MyPoints:labels: nullable List[nullable Utf8] [ +/my/entity:example.MyPoints:labels: List(Utf8) [ rerun:archetype: "example.MyPoints" rerun:component: "example.MyPoints:labels" rerun:component_type: "example.MyLabel" @@ -20,18 +20,18 @@ top-level metadata: [ rerun:is_static: "true" rerun:kind: "data" ] -/my/entity:example.MyPoints:points: nullable List[nullable Struct[2]] [ +/my/entity:example.MyPoints:points: List(Struct("x": non-null Float32, "y": non-null Float32)) [ rerun:archetype: "example.MyPoints" rerun:component: "example.MyPoints:points" rerun:component_type: "example.MyPoint" rerun:entity_path: "/my/entity" rerun:kind: "data" ] -frame_nr: nullable i64 [ +frame_nr: Int64 [ rerun:index_name: "frame_nr" rerun:kind: "index" ] -rerun.controls.RowId: FixedSizeBinary[16] [ +rerun.controls.RowId: non-null FixedSizeBinary(16) [ ARROW:extension:metadata: "{\"namespace\":\"row\"}" ARROW:extension:name: "rerun.datatypes.TUID" rerun:kind: "control" diff --git a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__unregister_segment__missing_1_should_be_empty_response_schema.snap b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__unregister_segment__missing_1_should_be_empty_response_schema.snap index de2dddcae99f..13f55ed9e024 100644 --- a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__unregister_segment__missing_1_should_be_empty_response_schema.snap +++ b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__unregister_segment__missing_1_should_be_empty_response_schema.snap @@ -2,13 +2,13 @@ source: crates/store/re_redap_tests/src/tests/unregister_segment.rs expression: batch.format_schema_snapshot() --- -rerun_last_updated_at: Timestamp(ns) -rerun_layer_name: Utf8 -rerun_layer_type: Utf8 -rerun_num_chunks: u64 -rerun_registration_status: Utf8 -rerun_registration_time: Timestamp(ns) -rerun_schema_sha256: FixedSizeBinary[32] -rerun_segment_id: Utf8 -rerun_size_bytes: u64 -rerun_storage_url: Utf8 +rerun_last_updated_at: non-null Timestamp(ns) +rerun_layer_name: non-null Utf8 +rerun_layer_type: non-null Utf8 +rerun_num_chunks: non-null UInt64 +rerun_registration_status: non-null Utf8 +rerun_registration_time: non-null Timestamp(ns) +rerun_schema_sha256: non-null FixedSizeBinary(32) +rerun_segment_id: non-null Utf8 +rerun_size_bytes: non-null UInt64 +rerun_storage_url: non-null Utf8 diff --git a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__unregister_segment__products_1_register_all_manifest_schema.snap b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__unregister_segment__products_1_register_all_manifest_schema.snap index de2dddcae99f..13f55ed9e024 100644 --- a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__unregister_segment__products_1_register_all_manifest_schema.snap +++ b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__unregister_segment__products_1_register_all_manifest_schema.snap @@ -2,13 +2,13 @@ source: crates/store/re_redap_tests/src/tests/unregister_segment.rs expression: batch.format_schema_snapshot() --- -rerun_last_updated_at: Timestamp(ns) -rerun_layer_name: Utf8 -rerun_layer_type: Utf8 -rerun_num_chunks: u64 -rerun_registration_status: Utf8 -rerun_registration_time: Timestamp(ns) -rerun_schema_sha256: FixedSizeBinary[32] -rerun_segment_id: Utf8 -rerun_size_bytes: u64 -rerun_storage_url: Utf8 +rerun_last_updated_at: non-null Timestamp(ns) +rerun_layer_name: non-null Utf8 +rerun_layer_type: non-null Utf8 +rerun_num_chunks: non-null UInt64 +rerun_registration_status: non-null Utf8 +rerun_registration_time: non-null Timestamp(ns) +rerun_schema_sha256: non-null FixedSizeBinary(32) +rerun_segment_id: non-null Utf8 +rerun_size_bytes: non-null UInt64 +rerun_storage_url: non-null Utf8 diff --git a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__unregister_segment__products_1_register_all_segments_schema.snap b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__unregister_segment__products_1_register_all_segments_schema.snap index 08c5f5f30d4c..06216fbdc832 100644 --- a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__unregister_segment__products_1_register_all_segments_schema.snap +++ b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__unregister_segment__products_1_register_all_segments_schema.snap @@ -1,10 +1,10 @@ --- source: crates/store/re_redap_tests/src/tests/unregister_segment.rs -expression: "batch.clone().filter_columns_by(|f|\nf.metadata().get(\"rerun:index_kind\").is_none()).unwrap().format_schema_snapshot()" +expression: filter_out_index_ranges(batch.clone()).format_schema_snapshot() --- -rerun_last_updated_at: Timestamp(ns) -rerun_layer_names: List[Utf8] -rerun_num_chunks: u64 -rerun_segment_id: Utf8 -rerun_size_bytes: u64 -rerun_storage_urls: List[Utf8] +rerun_last_updated_at: non-null Timestamp(ns) +rerun_layer_names: non-null List(non-null Utf8, field: 'rerun_layer_names') +rerun_num_chunks: non-null UInt64 +rerun_segment_id: non-null Utf8 +rerun_size_bytes: non-null UInt64 +rerun_storage_urls: non-null List(non-null Utf8, field: 'rerun_storage_urls') diff --git a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__unregister_segment__products_2_remove_layers_BD_for_segments_13_manifest_schema.snap b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__unregister_segment__products_2_remove_layers_BD_for_segments_13_manifest_schema.snap index de2dddcae99f..13f55ed9e024 100644 --- a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__unregister_segment__products_2_remove_layers_BD_for_segments_13_manifest_schema.snap +++ b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__unregister_segment__products_2_remove_layers_BD_for_segments_13_manifest_schema.snap @@ -2,13 +2,13 @@ source: crates/store/re_redap_tests/src/tests/unregister_segment.rs expression: batch.format_schema_snapshot() --- -rerun_last_updated_at: Timestamp(ns) -rerun_layer_name: Utf8 -rerun_layer_type: Utf8 -rerun_num_chunks: u64 -rerun_registration_status: Utf8 -rerun_registration_time: Timestamp(ns) -rerun_schema_sha256: FixedSizeBinary[32] -rerun_segment_id: Utf8 -rerun_size_bytes: u64 -rerun_storage_url: Utf8 +rerun_last_updated_at: non-null Timestamp(ns) +rerun_layer_name: non-null Utf8 +rerun_layer_type: non-null Utf8 +rerun_num_chunks: non-null UInt64 +rerun_registration_status: non-null Utf8 +rerun_registration_time: non-null Timestamp(ns) +rerun_schema_sha256: non-null FixedSizeBinary(32) +rerun_segment_id: non-null Utf8 +rerun_size_bytes: non-null UInt64 +rerun_storage_url: non-null Utf8 diff --git a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__unregister_segment__products_2_remove_layers_BD_for_segments_13_response_schema.snap b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__unregister_segment__products_2_remove_layers_BD_for_segments_13_response_schema.snap index de2dddcae99f..13f55ed9e024 100644 --- a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__unregister_segment__products_2_remove_layers_BD_for_segments_13_response_schema.snap +++ b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__unregister_segment__products_2_remove_layers_BD_for_segments_13_response_schema.snap @@ -2,13 +2,13 @@ source: crates/store/re_redap_tests/src/tests/unregister_segment.rs expression: batch.format_schema_snapshot() --- -rerun_last_updated_at: Timestamp(ns) -rerun_layer_name: Utf8 -rerun_layer_type: Utf8 -rerun_num_chunks: u64 -rerun_registration_status: Utf8 -rerun_registration_time: Timestamp(ns) -rerun_schema_sha256: FixedSizeBinary[32] -rerun_segment_id: Utf8 -rerun_size_bytes: u64 -rerun_storage_url: Utf8 +rerun_last_updated_at: non-null Timestamp(ns) +rerun_layer_name: non-null Utf8 +rerun_layer_type: non-null Utf8 +rerun_num_chunks: non-null UInt64 +rerun_registration_status: non-null Utf8 +rerun_registration_time: non-null Timestamp(ns) +rerun_schema_sha256: non-null FixedSizeBinary(32) +rerun_segment_id: non-null Utf8 +rerun_size_bytes: non-null UInt64 +rerun_storage_url: non-null Utf8 diff --git a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__unregister_segment__products_2_remove_layers_BD_for_segments_13_segments_schema.snap b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__unregister_segment__products_2_remove_layers_BD_for_segments_13_segments_schema.snap index 08c5f5f30d4c..06216fbdc832 100644 --- a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__unregister_segment__products_2_remove_layers_BD_for_segments_13_segments_schema.snap +++ b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__unregister_segment__products_2_remove_layers_BD_for_segments_13_segments_schema.snap @@ -1,10 +1,10 @@ --- source: crates/store/re_redap_tests/src/tests/unregister_segment.rs -expression: "batch.clone().filter_columns_by(|f|\nf.metadata().get(\"rerun:index_kind\").is_none()).unwrap().format_schema_snapshot()" +expression: filter_out_index_ranges(batch.clone()).format_schema_snapshot() --- -rerun_last_updated_at: Timestamp(ns) -rerun_layer_names: List[Utf8] -rerun_num_chunks: u64 -rerun_segment_id: Utf8 -rerun_size_bytes: u64 -rerun_storage_urls: List[Utf8] +rerun_last_updated_at: non-null Timestamp(ns) +rerun_layer_names: non-null List(non-null Utf8, field: 'rerun_layer_names') +rerun_num_chunks: non-null UInt64 +rerun_segment_id: non-null Utf8 +rerun_size_bytes: non-null UInt64 +rerun_storage_urls: non-null List(non-null Utf8, field: 'rerun_storage_urls') diff --git a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__unregister_segment__products_3_remove_layers_BD_for_all_segments_manifest_schema.snap b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__unregister_segment__products_3_remove_layers_BD_for_all_segments_manifest_schema.snap index de2dddcae99f..13f55ed9e024 100644 --- a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__unregister_segment__products_3_remove_layers_BD_for_all_segments_manifest_schema.snap +++ b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__unregister_segment__products_3_remove_layers_BD_for_all_segments_manifest_schema.snap @@ -2,13 +2,13 @@ source: crates/store/re_redap_tests/src/tests/unregister_segment.rs expression: batch.format_schema_snapshot() --- -rerun_last_updated_at: Timestamp(ns) -rerun_layer_name: Utf8 -rerun_layer_type: Utf8 -rerun_num_chunks: u64 -rerun_registration_status: Utf8 -rerun_registration_time: Timestamp(ns) -rerun_schema_sha256: FixedSizeBinary[32] -rerun_segment_id: Utf8 -rerun_size_bytes: u64 -rerun_storage_url: Utf8 +rerun_last_updated_at: non-null Timestamp(ns) +rerun_layer_name: non-null Utf8 +rerun_layer_type: non-null Utf8 +rerun_num_chunks: non-null UInt64 +rerun_registration_status: non-null Utf8 +rerun_registration_time: non-null Timestamp(ns) +rerun_schema_sha256: non-null FixedSizeBinary(32) +rerun_segment_id: non-null Utf8 +rerun_size_bytes: non-null UInt64 +rerun_storage_url: non-null Utf8 diff --git a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__unregister_segment__products_3_remove_layers_BD_for_all_segments_response_schema.snap b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__unregister_segment__products_3_remove_layers_BD_for_all_segments_response_schema.snap index de2dddcae99f..13f55ed9e024 100644 --- a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__unregister_segment__products_3_remove_layers_BD_for_all_segments_response_schema.snap +++ b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__unregister_segment__products_3_remove_layers_BD_for_all_segments_response_schema.snap @@ -2,13 +2,13 @@ source: crates/store/re_redap_tests/src/tests/unregister_segment.rs expression: batch.format_schema_snapshot() --- -rerun_last_updated_at: Timestamp(ns) -rerun_layer_name: Utf8 -rerun_layer_type: Utf8 -rerun_num_chunks: u64 -rerun_registration_status: Utf8 -rerun_registration_time: Timestamp(ns) -rerun_schema_sha256: FixedSizeBinary[32] -rerun_segment_id: Utf8 -rerun_size_bytes: u64 -rerun_storage_url: Utf8 +rerun_last_updated_at: non-null Timestamp(ns) +rerun_layer_name: non-null Utf8 +rerun_layer_type: non-null Utf8 +rerun_num_chunks: non-null UInt64 +rerun_registration_status: non-null Utf8 +rerun_registration_time: non-null Timestamp(ns) +rerun_schema_sha256: non-null FixedSizeBinary(32) +rerun_segment_id: non-null Utf8 +rerun_size_bytes: non-null UInt64 +rerun_storage_url: non-null Utf8 diff --git a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__unregister_segment__products_3_remove_layers_BD_for_all_segments_segments_schema.snap b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__unregister_segment__products_3_remove_layers_BD_for_all_segments_segments_schema.snap index 08c5f5f30d4c..06216fbdc832 100644 --- a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__unregister_segment__products_3_remove_layers_BD_for_all_segments_segments_schema.snap +++ b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__unregister_segment__products_3_remove_layers_BD_for_all_segments_segments_schema.snap @@ -1,10 +1,10 @@ --- source: crates/store/re_redap_tests/src/tests/unregister_segment.rs -expression: "batch.clone().filter_columns_by(|f|\nf.metadata().get(\"rerun:index_kind\").is_none()).unwrap().format_schema_snapshot()" +expression: filter_out_index_ranges(batch.clone()).format_schema_snapshot() --- -rerun_last_updated_at: Timestamp(ns) -rerun_layer_names: List[Utf8] -rerun_num_chunks: u64 -rerun_segment_id: Utf8 -rerun_size_bytes: u64 -rerun_storage_urls: List[Utf8] +rerun_last_updated_at: non-null Timestamp(ns) +rerun_layer_names: non-null List(non-null Utf8, field: 'rerun_layer_names') +rerun_num_chunks: non-null UInt64 +rerun_segment_id: non-null Utf8 +rerun_size_bytes: non-null UInt64 +rerun_storage_urls: non-null List(non-null Utf8, field: 'rerun_storage_urls') diff --git a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__unregister_segment__products_4_remove_all_layers_for_segments_23_manifest_schema.snap b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__unregister_segment__products_4_remove_all_layers_for_segments_23_manifest_schema.snap index de2dddcae99f..13f55ed9e024 100644 --- a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__unregister_segment__products_4_remove_all_layers_for_segments_23_manifest_schema.snap +++ b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__unregister_segment__products_4_remove_all_layers_for_segments_23_manifest_schema.snap @@ -2,13 +2,13 @@ source: crates/store/re_redap_tests/src/tests/unregister_segment.rs expression: batch.format_schema_snapshot() --- -rerun_last_updated_at: Timestamp(ns) -rerun_layer_name: Utf8 -rerun_layer_type: Utf8 -rerun_num_chunks: u64 -rerun_registration_status: Utf8 -rerun_registration_time: Timestamp(ns) -rerun_schema_sha256: FixedSizeBinary[32] -rerun_segment_id: Utf8 -rerun_size_bytes: u64 -rerun_storage_url: Utf8 +rerun_last_updated_at: non-null Timestamp(ns) +rerun_layer_name: non-null Utf8 +rerun_layer_type: non-null Utf8 +rerun_num_chunks: non-null UInt64 +rerun_registration_status: non-null Utf8 +rerun_registration_time: non-null Timestamp(ns) +rerun_schema_sha256: non-null FixedSizeBinary(32) +rerun_segment_id: non-null Utf8 +rerun_size_bytes: non-null UInt64 +rerun_storage_url: non-null Utf8 diff --git a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__unregister_segment__products_4_remove_all_layers_for_segments_23_response_schema.snap b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__unregister_segment__products_4_remove_all_layers_for_segments_23_response_schema.snap index de2dddcae99f..13f55ed9e024 100644 --- a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__unregister_segment__products_4_remove_all_layers_for_segments_23_response_schema.snap +++ b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__unregister_segment__products_4_remove_all_layers_for_segments_23_response_schema.snap @@ -2,13 +2,13 @@ source: crates/store/re_redap_tests/src/tests/unregister_segment.rs expression: batch.format_schema_snapshot() --- -rerun_last_updated_at: Timestamp(ns) -rerun_layer_name: Utf8 -rerun_layer_type: Utf8 -rerun_num_chunks: u64 -rerun_registration_status: Utf8 -rerun_registration_time: Timestamp(ns) -rerun_schema_sha256: FixedSizeBinary[32] -rerun_segment_id: Utf8 -rerun_size_bytes: u64 -rerun_storage_url: Utf8 +rerun_last_updated_at: non-null Timestamp(ns) +rerun_layer_name: non-null Utf8 +rerun_layer_type: non-null Utf8 +rerun_num_chunks: non-null UInt64 +rerun_registration_status: non-null Utf8 +rerun_registration_time: non-null Timestamp(ns) +rerun_schema_sha256: non-null FixedSizeBinary(32) +rerun_segment_id: non-null Utf8 +rerun_size_bytes: non-null UInt64 +rerun_storage_url: non-null Utf8 diff --git a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__unregister_segment__products_4_remove_all_layers_for_segments_23_segments_schema.snap b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__unregister_segment__products_4_remove_all_layers_for_segments_23_segments_schema.snap index 08c5f5f30d4c..06216fbdc832 100644 --- a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__unregister_segment__products_4_remove_all_layers_for_segments_23_segments_schema.snap +++ b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__unregister_segment__products_4_remove_all_layers_for_segments_23_segments_schema.snap @@ -1,10 +1,10 @@ --- source: crates/store/re_redap_tests/src/tests/unregister_segment.rs -expression: "batch.clone().filter_columns_by(|f|\nf.metadata().get(\"rerun:index_kind\").is_none()).unwrap().format_schema_snapshot()" +expression: filter_out_index_ranges(batch.clone()).format_schema_snapshot() --- -rerun_last_updated_at: Timestamp(ns) -rerun_layer_names: List[Utf8] -rerun_num_chunks: u64 -rerun_segment_id: Utf8 -rerun_size_bytes: u64 -rerun_storage_urls: List[Utf8] +rerun_last_updated_at: non-null Timestamp(ns) +rerun_layer_names: non-null List(non-null Utf8, field: 'rerun_layer_names') +rerun_num_chunks: non-null UInt64 +rerun_segment_id: non-null Utf8 +rerun_size_bytes: non-null UInt64 +rerun_storage_urls: non-null List(non-null Utf8, field: 'rerun_storage_urls') diff --git a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__unregister_segment__simple_1_register_all_manifest_schema.snap b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__unregister_segment__simple_1_register_all_manifest_schema.snap index de2dddcae99f..13f55ed9e024 100644 --- a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__unregister_segment__simple_1_register_all_manifest_schema.snap +++ b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__unregister_segment__simple_1_register_all_manifest_schema.snap @@ -2,13 +2,13 @@ source: crates/store/re_redap_tests/src/tests/unregister_segment.rs expression: batch.format_schema_snapshot() --- -rerun_last_updated_at: Timestamp(ns) -rerun_layer_name: Utf8 -rerun_layer_type: Utf8 -rerun_num_chunks: u64 -rerun_registration_status: Utf8 -rerun_registration_time: Timestamp(ns) -rerun_schema_sha256: FixedSizeBinary[32] -rerun_segment_id: Utf8 -rerun_size_bytes: u64 -rerun_storage_url: Utf8 +rerun_last_updated_at: non-null Timestamp(ns) +rerun_layer_name: non-null Utf8 +rerun_layer_type: non-null Utf8 +rerun_num_chunks: non-null UInt64 +rerun_registration_status: non-null Utf8 +rerun_registration_time: non-null Timestamp(ns) +rerun_schema_sha256: non-null FixedSizeBinary(32) +rerun_segment_id: non-null Utf8 +rerun_size_bytes: non-null UInt64 +rerun_storage_url: non-null Utf8 diff --git a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__unregister_segment__simple_1_register_all_segments_schema.snap b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__unregister_segment__simple_1_register_all_segments_schema.snap index 08c5f5f30d4c..06216fbdc832 100644 --- a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__unregister_segment__simple_1_register_all_segments_schema.snap +++ b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__unregister_segment__simple_1_register_all_segments_schema.snap @@ -1,10 +1,10 @@ --- source: crates/store/re_redap_tests/src/tests/unregister_segment.rs -expression: "batch.clone().filter_columns_by(|f|\nf.metadata().get(\"rerun:index_kind\").is_none()).unwrap().format_schema_snapshot()" +expression: filter_out_index_ranges(batch.clone()).format_schema_snapshot() --- -rerun_last_updated_at: Timestamp(ns) -rerun_layer_names: List[Utf8] -rerun_num_chunks: u64 -rerun_segment_id: Utf8 -rerun_size_bytes: u64 -rerun_storage_urls: List[Utf8] +rerun_last_updated_at: non-null Timestamp(ns) +rerun_layer_names: non-null List(non-null Utf8, field: 'rerun_layer_names') +rerun_num_chunks: non-null UInt64 +rerun_segment_id: non-null Utf8 +rerun_size_bytes: non-null UInt64 +rerun_storage_urls: non-null List(non-null Utf8, field: 'rerun_storage_urls') diff --git a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__unregister_segment__simple_2_remove_segment_id2_manifest_schema.snap b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__unregister_segment__simple_2_remove_segment_id2_manifest_schema.snap index de2dddcae99f..13f55ed9e024 100644 --- a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__unregister_segment__simple_2_remove_segment_id2_manifest_schema.snap +++ b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__unregister_segment__simple_2_remove_segment_id2_manifest_schema.snap @@ -2,13 +2,13 @@ source: crates/store/re_redap_tests/src/tests/unregister_segment.rs expression: batch.format_schema_snapshot() --- -rerun_last_updated_at: Timestamp(ns) -rerun_layer_name: Utf8 -rerun_layer_type: Utf8 -rerun_num_chunks: u64 -rerun_registration_status: Utf8 -rerun_registration_time: Timestamp(ns) -rerun_schema_sha256: FixedSizeBinary[32] -rerun_segment_id: Utf8 -rerun_size_bytes: u64 -rerun_storage_url: Utf8 +rerun_last_updated_at: non-null Timestamp(ns) +rerun_layer_name: non-null Utf8 +rerun_layer_type: non-null Utf8 +rerun_num_chunks: non-null UInt64 +rerun_registration_status: non-null Utf8 +rerun_registration_time: non-null Timestamp(ns) +rerun_schema_sha256: non-null FixedSizeBinary(32) +rerun_segment_id: non-null Utf8 +rerun_size_bytes: non-null UInt64 +rerun_storage_url: non-null Utf8 diff --git a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__unregister_segment__simple_2_remove_segment_id2_response_schema.snap b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__unregister_segment__simple_2_remove_segment_id2_response_schema.snap index de2dddcae99f..13f55ed9e024 100644 --- a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__unregister_segment__simple_2_remove_segment_id2_response_schema.snap +++ b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__unregister_segment__simple_2_remove_segment_id2_response_schema.snap @@ -2,13 +2,13 @@ source: crates/store/re_redap_tests/src/tests/unregister_segment.rs expression: batch.format_schema_snapshot() --- -rerun_last_updated_at: Timestamp(ns) -rerun_layer_name: Utf8 -rerun_layer_type: Utf8 -rerun_num_chunks: u64 -rerun_registration_status: Utf8 -rerun_registration_time: Timestamp(ns) -rerun_schema_sha256: FixedSizeBinary[32] -rerun_segment_id: Utf8 -rerun_size_bytes: u64 -rerun_storage_url: Utf8 +rerun_last_updated_at: non-null Timestamp(ns) +rerun_layer_name: non-null Utf8 +rerun_layer_type: non-null Utf8 +rerun_num_chunks: non-null UInt64 +rerun_registration_status: non-null Utf8 +rerun_registration_time: non-null Timestamp(ns) +rerun_schema_sha256: non-null FixedSizeBinary(32) +rerun_segment_id: non-null Utf8 +rerun_size_bytes: non-null UInt64 +rerun_storage_url: non-null Utf8 diff --git a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__unregister_segment__simple_2_remove_segment_id2_segments_schema.snap b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__unregister_segment__simple_2_remove_segment_id2_segments_schema.snap index 08c5f5f30d4c..06216fbdc832 100644 --- a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__unregister_segment__simple_2_remove_segment_id2_segments_schema.snap +++ b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__unregister_segment__simple_2_remove_segment_id2_segments_schema.snap @@ -1,10 +1,10 @@ --- source: crates/store/re_redap_tests/src/tests/unregister_segment.rs -expression: "batch.clone().filter_columns_by(|f|\nf.metadata().get(\"rerun:index_kind\").is_none()).unwrap().format_schema_snapshot()" +expression: filter_out_index_ranges(batch.clone()).format_schema_snapshot() --- -rerun_last_updated_at: Timestamp(ns) -rerun_layer_names: List[Utf8] -rerun_num_chunks: u64 -rerun_segment_id: Utf8 -rerun_size_bytes: u64 -rerun_storage_urls: List[Utf8] +rerun_last_updated_at: non-null Timestamp(ns) +rerun_layer_names: non-null List(non-null Utf8, field: 'rerun_layer_names') +rerun_num_chunks: non-null UInt64 +rerun_segment_id: non-null Utf8 +rerun_size_bytes: non-null UInt64 +rerun_storage_urls: non-null List(non-null Utf8, field: 'rerun_storage_urls') diff --git a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__unregister_segment__simple_3_remove_remaining_segments_manifest_schema.snap b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__unregister_segment__simple_3_remove_remaining_segments_manifest_schema.snap index de2dddcae99f..13f55ed9e024 100644 --- a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__unregister_segment__simple_3_remove_remaining_segments_manifest_schema.snap +++ b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__unregister_segment__simple_3_remove_remaining_segments_manifest_schema.snap @@ -2,13 +2,13 @@ source: crates/store/re_redap_tests/src/tests/unregister_segment.rs expression: batch.format_schema_snapshot() --- -rerun_last_updated_at: Timestamp(ns) -rerun_layer_name: Utf8 -rerun_layer_type: Utf8 -rerun_num_chunks: u64 -rerun_registration_status: Utf8 -rerun_registration_time: Timestamp(ns) -rerun_schema_sha256: FixedSizeBinary[32] -rerun_segment_id: Utf8 -rerun_size_bytes: u64 -rerun_storage_url: Utf8 +rerun_last_updated_at: non-null Timestamp(ns) +rerun_layer_name: non-null Utf8 +rerun_layer_type: non-null Utf8 +rerun_num_chunks: non-null UInt64 +rerun_registration_status: non-null Utf8 +rerun_registration_time: non-null Timestamp(ns) +rerun_schema_sha256: non-null FixedSizeBinary(32) +rerun_segment_id: non-null Utf8 +rerun_size_bytes: non-null UInt64 +rerun_storage_url: non-null Utf8 diff --git a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__unregister_segment__simple_3_remove_remaining_segments_response_schema.snap b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__unregister_segment__simple_3_remove_remaining_segments_response_schema.snap index de2dddcae99f..13f55ed9e024 100644 --- a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__unregister_segment__simple_3_remove_remaining_segments_response_schema.snap +++ b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__unregister_segment__simple_3_remove_remaining_segments_response_schema.snap @@ -2,13 +2,13 @@ source: crates/store/re_redap_tests/src/tests/unregister_segment.rs expression: batch.format_schema_snapshot() --- -rerun_last_updated_at: Timestamp(ns) -rerun_layer_name: Utf8 -rerun_layer_type: Utf8 -rerun_num_chunks: u64 -rerun_registration_status: Utf8 -rerun_registration_time: Timestamp(ns) -rerun_schema_sha256: FixedSizeBinary[32] -rerun_segment_id: Utf8 -rerun_size_bytes: u64 -rerun_storage_url: Utf8 +rerun_last_updated_at: non-null Timestamp(ns) +rerun_layer_name: non-null Utf8 +rerun_layer_type: non-null Utf8 +rerun_num_chunks: non-null UInt64 +rerun_registration_status: non-null Utf8 +rerun_registration_time: non-null Timestamp(ns) +rerun_schema_sha256: non-null FixedSizeBinary(32) +rerun_segment_id: non-null Utf8 +rerun_size_bytes: non-null UInt64 +rerun_storage_url: non-null Utf8 diff --git a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__unregister_segment__simple_3_remove_remaining_segments_segments_schema.snap b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__unregister_segment__simple_3_remove_remaining_segments_segments_schema.snap index 08c5f5f30d4c..06216fbdc832 100644 --- a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__unregister_segment__simple_3_remove_remaining_segments_segments_schema.snap +++ b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__unregister_segment__simple_3_remove_remaining_segments_segments_schema.snap @@ -1,10 +1,10 @@ --- source: crates/store/re_redap_tests/src/tests/unregister_segment.rs -expression: "batch.clone().filter_columns_by(|f|\nf.metadata().get(\"rerun:index_kind\").is_none()).unwrap().format_schema_snapshot()" +expression: filter_out_index_ranges(batch.clone()).format_schema_snapshot() --- -rerun_last_updated_at: Timestamp(ns) -rerun_layer_names: List[Utf8] -rerun_num_chunks: u64 -rerun_segment_id: Utf8 -rerun_size_bytes: u64 -rerun_storage_urls: List[Utf8] +rerun_last_updated_at: non-null Timestamp(ns) +rerun_layer_names: non-null List(non-null Utf8, field: 'rerun_layer_names') +rerun_num_chunks: non-null UInt64 +rerun_segment_id: non-null Utf8 +rerun_size_bytes: non-null UInt64 +rerun_storage_urls: non-null List(non-null Utf8, field: 'rerun_storage_urls') diff --git a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__unregister_segment__simple_4_reregister_all_manifest_schema.snap b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__unregister_segment__simple_4_reregister_all_manifest_schema.snap index de2dddcae99f..13f55ed9e024 100644 --- a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__unregister_segment__simple_4_reregister_all_manifest_schema.snap +++ b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__unregister_segment__simple_4_reregister_all_manifest_schema.snap @@ -2,13 +2,13 @@ source: crates/store/re_redap_tests/src/tests/unregister_segment.rs expression: batch.format_schema_snapshot() --- -rerun_last_updated_at: Timestamp(ns) -rerun_layer_name: Utf8 -rerun_layer_type: Utf8 -rerun_num_chunks: u64 -rerun_registration_status: Utf8 -rerun_registration_time: Timestamp(ns) -rerun_schema_sha256: FixedSizeBinary[32] -rerun_segment_id: Utf8 -rerun_size_bytes: u64 -rerun_storage_url: Utf8 +rerun_last_updated_at: non-null Timestamp(ns) +rerun_layer_name: non-null Utf8 +rerun_layer_type: non-null Utf8 +rerun_num_chunks: non-null UInt64 +rerun_registration_status: non-null Utf8 +rerun_registration_time: non-null Timestamp(ns) +rerun_schema_sha256: non-null FixedSizeBinary(32) +rerun_segment_id: non-null Utf8 +rerun_size_bytes: non-null UInt64 +rerun_storage_url: non-null Utf8 diff --git a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__unregister_segment__simple_4_reregister_all_segments_schema.snap b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__unregister_segment__simple_4_reregister_all_segments_schema.snap index 08c5f5f30d4c..06216fbdc832 100644 --- a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__unregister_segment__simple_4_reregister_all_segments_schema.snap +++ b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__unregister_segment__simple_4_reregister_all_segments_schema.snap @@ -1,10 +1,10 @@ --- source: crates/store/re_redap_tests/src/tests/unregister_segment.rs -expression: "batch.clone().filter_columns_by(|f|\nf.metadata().get(\"rerun:index_kind\").is_none()).unwrap().format_schema_snapshot()" +expression: filter_out_index_ranges(batch.clone()).format_schema_snapshot() --- -rerun_last_updated_at: Timestamp(ns) -rerun_layer_names: List[Utf8] -rerun_num_chunks: u64 -rerun_segment_id: Utf8 -rerun_size_bytes: u64 -rerun_storage_urls: List[Utf8] +rerun_last_updated_at: non-null Timestamp(ns) +rerun_layer_names: non-null List(non-null Utf8, field: 'rerun_layer_names') +rerun_num_chunks: non-null UInt64 +rerun_segment_id: non-null Utf8 +rerun_size_bytes: non-null UInt64 +rerun_storage_urls: non-null List(non-null Utf8, field: 'rerun_storage_urls') diff --git a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__unregister_segment__unregister_then_query_1_added_schema.snap b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__unregister_segment__unregister_then_query_1_added_schema.snap index 92631be63786..b11d6c924fd0 100644 --- a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__unregister_segment__unregister_then_query_1_added_schema.snap +++ b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__unregister_segment__unregister_then_query_1_added_schema.snap @@ -2,18 +2,18 @@ source: crates/store/re_redap_tests/src/tests/unregister_segment.rs expression: required_chunk_info.format_schema_snapshot() --- -chunk_byte_len: u64 -chunk_entity_path: Utf8 [ +chunk_byte_len: non-null UInt64 +chunk_entity_path: non-null Utf8 [ rerun:kind: "control" ] -chunk_id: FixedSizeBinary[16] [ +chunk_id: non-null FixedSizeBinary(16) [ rerun:kind: "control" ] -chunk_is_static: bool [ +chunk_is_static: non-null Boolean [ rerun:kind: "control" ] -chunk_key: Binary -chunk_segment_id: Utf8 [ +chunk_key: non-null Binary +chunk_segment_id: non-null Utf8 [ rerun:kind: "control" ] -rerun_segment_layer: Utf8 +rerun_segment_layer: non-null Utf8 diff --git a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__unregister_segment__unregister_then_query_2_removed_schema.snap b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__unregister_segment__unregister_then_query_2_removed_schema.snap index 92631be63786..b11d6c924fd0 100644 --- a/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__unregister_segment__unregister_then_query_2_removed_schema.snap +++ b/crates/store/re_redap_tests/src/tests/snapshots/re_redap_tests__tests__unregister_segment__unregister_then_query_2_removed_schema.snap @@ -2,18 +2,18 @@ source: crates/store/re_redap_tests/src/tests/unregister_segment.rs expression: required_chunk_info.format_schema_snapshot() --- -chunk_byte_len: u64 -chunk_entity_path: Utf8 [ +chunk_byte_len: non-null UInt64 +chunk_entity_path: non-null Utf8 [ rerun:kind: "control" ] -chunk_id: FixedSizeBinary[16] [ +chunk_id: non-null FixedSizeBinary(16) [ rerun:kind: "control" ] -chunk_is_static: bool [ +chunk_is_static: non-null Boolean [ rerun:kind: "control" ] -chunk_key: Binary -chunk_segment_id: Utf8 [ +chunk_key: non-null Binary +chunk_segment_id: non-null Utf8 [ rerun:kind: "control" ] -rerun_segment_layer: Utf8 +rerun_segment_layer: non-null Utf8 diff --git a/crates/store/re_sorbet/src/component_column_descriptor.rs b/crates/store/re_sorbet/src/component_column_descriptor.rs index 73e13fa3d99d..e51440eb1189 100644 --- a/crates/store/re_sorbet/src/component_column_descriptor.rs +++ b/crates/store/re_sorbet/src/component_column_descriptor.rs @@ -263,10 +263,9 @@ impl ComponentColumnDescriptor { dt => { re_log::warn_once!( - "Component '{}' on entity '{}' has unexpected non-list-array type: {}", + "Component '{}' on entity '{}' has unexpected non-list-array type: {dt}", self.component, self.entity_path, - re_arrow_util::format_data_type(&dt), ); dt } diff --git a/crates/store/re_types_core/src/result.rs b/crates/store/re_types_core/src/result.rs index ddfc90d6d914..5b3f87294ccc 100644 --- a/crates/store/re_types_core/src/result.rs +++ b/crates/store/re_types_core/src/result.rs @@ -2,7 +2,7 @@ use std::any; use std::fmt::Display; use std::ops::Deref; -use re_arrow_util::DisplayDataType; +use arrow::datatypes::DataType; // --- @@ -145,7 +145,7 @@ pub enum DeserializationError { #[error("Expected field {field_name:?} to be present in {datatype}")] MissingStructField { - datatype: DisplayDataType, + datatype: DataType, field_name: String, backtrace: Box<_Backtrace>, }, @@ -163,7 +163,7 @@ pub enum DeserializationError { #[error("Expected union arm {arm_name:?} (#{arm_index}) to be present in {datatype}")] MissingUnionArm { - datatype: DisplayDataType, + datatype: DataType, arm_name: String, arm_index: usize, backtrace: Box<_Backtrace>, @@ -171,8 +171,8 @@ pub enum DeserializationError { #[error("Expected {expected} but found {got} instead")] DatatypeMismatch { - expected: DisplayDataType, - got: DisplayDataType, + expected: DataType, + got: DataType, backtrace: Box<_Backtrace>, }, @@ -239,7 +239,7 @@ impl DeserializationError { field_name: impl AsRef, ) -> Self { Self::MissingStructField { - datatype: datatype.into().into(), + datatype: datatype.into(), field_name: field_name.as_ref().into(), backtrace: Box::new(std::backtrace::Backtrace::capture()), } @@ -268,7 +268,7 @@ impl DeserializationError { arm_index: usize, ) -> Self { Self::MissingUnionArm { - datatype: datatype.into().into(), + datatype: datatype.into(), arm_name: arm_name.as_ref().into(), arm_index, backtrace: Box::new(std::backtrace::Backtrace::capture()), @@ -281,8 +281,8 @@ impl DeserializationError { got: impl Into, ) -> Self { Self::DatatypeMismatch { - expected: expected.into().into(), - got: got.into().into(), + expected: expected.into(), + got: got.into(), backtrace: Box::new(std::backtrace::Backtrace::capture()), } } diff --git a/crates/top/re_sdk/tests/lenses/snapshots/lenses__lenses__operations__destructure_cast.snap b/crates/top/re_sdk/tests/lenses/snapshots/lenses__lenses__operations__destructure_cast.snap index 1e219ed6d65f..b0eab68c63f6 100644 --- a/crates/top/re_sdk/tests/lenses/snapshots/lenses__lenses__operations__destructure_cast.snap +++ b/crates/top/re_sdk/tests/lenses/snapshots/lenses__lenses__operations__destructure_cast.snap @@ -2,33 +2,33 @@ source: crates/top/re_sdk/tests/lenses/operations.rs expression: "format!(\"{chunk:-240}\")" --- -┌────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ METADATA: │ -│ * entity_path: /nullability/a │ -│ * id: [**REDACTED**] │ -│ * version: [**REDACTED**] │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ ┌───────────────────────────────────────────────┬────────────────────┬───────────────────────────────────┐ │ -│ │ RowId ┆ tick ┆ Scalars:scalars │ │ -│ │ --- ┆ --- ┆ --- │ │ -│ │ type: FixedSizeBinary[16] ┆ type: nullable i64 ┆ type: nullable List[nullable f64] │ │ -│ │ ARROW:extension:metadata: {"namespace":"row"} ┆ index_name: tick ┆ archetype: Scalars │ │ -│ │ ARROW:extension:name: TUID ┆ is_sorted: true ┆ component: Scalars:scalars │ │ -│ │ is_sorted: true ┆ kind: index ┆ component_type: Scalar │ │ -│ │ kind: control ┆ ┆ kind: data │ │ -│ ╞═══════════════════════════════════════════════╪════════════════════╪═══════════════════════════════════╡ │ -│ │ row_[**REDACTED**] ┆ 0 ┆ [0.0] │ │ -│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ -│ │ row_[**REDACTED**] ┆ 1 ┆ [1.0] │ │ -│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ -│ │ row_[**REDACTED**] ┆ 2 ┆ [] │ │ -│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ -│ │ row_[**REDACTED**] ┆ 3 ┆ null │ │ -│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ -│ │ row_[**REDACTED**] ┆ 4 ┆ [4.0] │ │ -│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ -│ │ row_[**REDACTED**] ┆ 5 ┆ [null] │ │ -│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ -│ │ row_[**REDACTED**] ┆ 6 ┆ [6.0] │ │ -│ └───────────────────────────────────────────────┴────────────────────┴───────────────────────────────────┘ │ -└────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +┌───────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ METADATA: │ +│ * entity_path: /nullability/a │ +│ * id: [**REDACTED**] │ +│ * version: [**REDACTED**] │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ ┌───────────────────────────────────────────────┬──────────────────┬────────────────────────────┐ │ +│ │ RowId ┆ tick ┆ Scalars:scalars │ │ +│ │ --- ┆ --- ┆ --- │ │ +│ │ type: non-null FixedSizeBinary(16) ┆ type: Int64 ┆ type: List(Float64) │ │ +│ │ ARROW:extension:metadata: {"namespace":"row"} ┆ index_name: tick ┆ archetype: Scalars │ │ +│ │ ARROW:extension:name: TUID ┆ is_sorted: true ┆ component: Scalars:scalars │ │ +│ │ is_sorted: true ┆ kind: index ┆ component_type: Scalar │ │ +│ │ kind: control ┆ ┆ kind: data │ │ +│ ╞═══════════════════════════════════════════════╪══════════════════╪════════════════════════════╡ │ +│ │ row_[**REDACTED**] ┆ 0 ┆ [0.0] │ │ +│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ +│ │ row_[**REDACTED**] ┆ 1 ┆ [1.0] │ │ +│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ +│ │ row_[**REDACTED**] ┆ 2 ┆ [] │ │ +│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ +│ │ row_[**REDACTED**] ┆ 3 ┆ null │ │ +│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ +│ │ row_[**REDACTED**] ┆ 4 ┆ [4.0] │ │ +│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ +│ │ row_[**REDACTED**] ┆ 5 ┆ [null] │ │ +│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ +│ │ row_[**REDACTED**] ┆ 6 ┆ [6.0] │ │ +│ └───────────────────────────────────────────────┴──────────────────┴────────────────────────────┘ │ +└───────────────────────────────────────────────────────────────────────────────────────────────────┘ diff --git a/crates/top/re_sdk/tests/lenses/snapshots/lenses__lenses__operations__destructure_only.snap b/crates/top/re_sdk/tests/lenses/snapshots/lenses__lenses__operations__destructure_only.snap index a521f247bbde..c8ee9c646387 100644 --- a/crates/top/re_sdk/tests/lenses/snapshots/lenses__lenses__operations__destructure_only.snap +++ b/crates/top/re_sdk/tests/lenses/snapshots/lenses__lenses__operations__destructure_only.snap @@ -2,33 +2,33 @@ source: crates/top/re_sdk/tests/lenses/operations.rs expression: "format!(\"{chunk:-240}\")" --- -┌────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ METADATA: │ -│ * entity_path: /nullability/b │ -│ * id: [**REDACTED**] │ -│ * version: [**REDACTED**] │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ ┌───────────────────────────────────────────────┬────────────────────┬───────────────────────────────────┐ │ -│ │ RowId ┆ tick ┆ Scalars:scalars │ │ -│ │ --- ┆ --- ┆ --- │ │ -│ │ type: FixedSizeBinary[16] ┆ type: nullable i64 ┆ type: nullable List[nullable f64] │ │ -│ │ ARROW:extension:metadata: {"namespace":"row"} ┆ index_name: tick ┆ archetype: Scalars │ │ -│ │ ARROW:extension:name: TUID ┆ is_sorted: true ┆ component: Scalars:scalars │ │ -│ │ is_sorted: true ┆ kind: index ┆ component_type: Scalar │ │ -│ │ kind: control ┆ ┆ kind: data │ │ -│ ╞═══════════════════════════════════════════════╪════════════════════╪═══════════════════════════════════╡ │ -│ │ row_[**REDACTED**] ┆ 0 ┆ [0.0] │ │ -│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ -│ │ row_[**REDACTED**] ┆ 1 ┆ [null] │ │ -│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ -│ │ row_[**REDACTED**] ┆ 2 ┆ [] │ │ -│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ -│ │ row_[**REDACTED**] ┆ 3 ┆ null │ │ -│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ -│ │ row_[**REDACTED**] ┆ 4 ┆ [4.0] │ │ -│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ -│ │ row_[**REDACTED**] ┆ 5 ┆ [null] │ │ -│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ -│ │ row_[**REDACTED**] ┆ 6 ┆ [6.0] │ │ -│ └───────────────────────────────────────────────┴────────────────────┴───────────────────────────────────┘ │ -└────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +┌───────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ METADATA: │ +│ * entity_path: /nullability/b │ +│ * id: [**REDACTED**] │ +│ * version: [**REDACTED**] │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ ┌───────────────────────────────────────────────┬──────────────────┬────────────────────────────┐ │ +│ │ RowId ┆ tick ┆ Scalars:scalars │ │ +│ │ --- ┆ --- ┆ --- │ │ +│ │ type: non-null FixedSizeBinary(16) ┆ type: Int64 ┆ type: List(Float64) │ │ +│ │ ARROW:extension:metadata: {"namespace":"row"} ┆ index_name: tick ┆ archetype: Scalars │ │ +│ │ ARROW:extension:name: TUID ┆ is_sorted: true ┆ component: Scalars:scalars │ │ +│ │ is_sorted: true ┆ kind: index ┆ component_type: Scalar │ │ +│ │ kind: control ┆ ┆ kind: data │ │ +│ ╞═══════════════════════════════════════════════╪══════════════════╪════════════════════════════╡ │ +│ │ row_[**REDACTED**] ┆ 0 ┆ [0.0] │ │ +│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ +│ │ row_[**REDACTED**] ┆ 1 ┆ [null] │ │ +│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ +│ │ row_[**REDACTED**] ┆ 2 ┆ [] │ │ +│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ +│ │ row_[**REDACTED**] ┆ 3 ┆ null │ │ +│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ +│ │ row_[**REDACTED**] ┆ 4 ┆ [4.0] │ │ +│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ +│ │ row_[**REDACTED**] ┆ 5 ┆ [null] │ │ +│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ +│ │ row_[**REDACTED**] ┆ 6 ┆ [6.0] │ │ +│ └───────────────────────────────────────────────┴──────────────────┴────────────────────────────┘ │ +└───────────────────────────────────────────────────────────────────────────────────────────────────┘ diff --git a/crates/top/re_sdk/tests/lenses/snapshots/lenses__lenses__operations__inner_count.snap b/crates/top/re_sdk/tests/lenses/snapshots/lenses__lenses__operations__inner_count.snap index 61b307c3af85..2016cce542b5 100644 --- a/crates/top/re_sdk/tests/lenses/snapshots/lenses__lenses__operations__inner_count.snap +++ b/crates/top/re_sdk/tests/lenses/snapshots/lenses__lenses__operations__inner_count.snap @@ -2,33 +2,33 @@ source: crates/top/re_sdk/tests/lenses/operations.rs expression: "format!(\"{chunk:-240}\")" --- -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ METADATA: │ -│ * entity_path: /nullability │ -│ * id: [**REDACTED**] │ -│ * version: [**REDACTED**] │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ ┌───────────────────────────────────────────────┬────────────────────┬───────────────────────────────────┬────────────────────────────────────┐ │ -│ │ RowId ┆ tick ┆ counts ┆ original │ │ -│ │ --- ┆ --- ┆ --- ┆ --- │ │ -│ │ type: FixedSizeBinary[16] ┆ type: nullable i64 ┆ type: nullable List[nullable i32] ┆ type: nullable List[nullable Utf8] │ │ -│ │ ARROW:extension:metadata: {"namespace":"row"} ┆ index_name: tick ┆ component: counts ┆ component: original │ │ -│ │ ARROW:extension:name: TUID ┆ is_sorted: true ┆ kind: data ┆ kind: data │ │ -│ │ is_sorted: true ┆ kind: index ┆ ┆ │ │ -│ │ kind: control ┆ ┆ ┆ │ │ -│ ╞═══════════════════════════════════════════════╪════════════════════╪═══════════════════════════════════╪════════════════════════════════════╡ │ -│ │ row_[**REDACTED**] ┆ 0 ┆ [1] ┆ [zero] │ │ -│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ -│ │ row_[**REDACTED**] ┆ 1 ┆ [2] ┆ [one, 1] │ │ -│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ -│ │ row_[**REDACTED**] ┆ 2 ┆ [0] ┆ [] │ │ -│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ -│ │ row_[**REDACTED**] ┆ 3 ┆ [1] ┆ [three] │ │ -│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ -│ │ row_[**REDACTED**] ┆ 4 ┆ null ┆ null │ │ -│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ -│ │ row_[**REDACTED**] ┆ 5 ┆ [1] ┆ [five] │ │ -│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ -│ │ row_[**REDACTED**] ┆ 6 ┆ [1] ┆ [null] │ │ -│ └───────────────────────────────────────────────┴────────────────────┴───────────────────────────────────┴────────────────────────────────────┘ │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +┌────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ METADATA: │ +│ * entity_path: /nullability │ +│ * id: [**REDACTED**] │ +│ * version: [**REDACTED**] │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ ┌───────────────────────────────────────────────┬──────────────────┬───────────────────┬─────────────────────┐ │ +│ │ RowId ┆ tick ┆ counts ┆ original │ │ +│ │ --- ┆ --- ┆ --- ┆ --- │ │ +│ │ type: non-null FixedSizeBinary(16) ┆ type: Int64 ┆ type: List(Int32) ┆ type: List(Utf8) │ │ +│ │ ARROW:extension:metadata: {"namespace":"row"} ┆ index_name: tick ┆ component: counts ┆ component: original │ │ +│ │ ARROW:extension:name: TUID ┆ is_sorted: true ┆ kind: data ┆ kind: data │ │ +│ │ is_sorted: true ┆ kind: index ┆ ┆ │ │ +│ │ kind: control ┆ ┆ ┆ │ │ +│ ╞═══════════════════════════════════════════════╪══════════════════╪═══════════════════╪═════════════════════╡ │ +│ │ row_[**REDACTED**] ┆ 0 ┆ [1] ┆ [zero] │ │ +│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ +│ │ row_[**REDACTED**] ┆ 1 ┆ [2] ┆ [one, 1] │ │ +│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ +│ │ row_[**REDACTED**] ┆ 2 ┆ [0] ┆ [] │ │ +│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ +│ │ row_[**REDACTED**] ┆ 3 ┆ [1] ┆ [three] │ │ +│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ +│ │ row_[**REDACTED**] ┆ 4 ┆ null ┆ null │ │ +│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ +│ │ row_[**REDACTED**] ┆ 5 ┆ [1] ┆ [five] │ │ +│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ +│ │ row_[**REDACTED**] ┆ 6 ┆ [1] ┆ [null] │ │ +│ └───────────────────────────────────────────────┴──────────────────┴───────────────────┴─────────────────────┘ │ +└────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ diff --git a/crates/top/re_sdk/tests/lenses/snapshots/lenses__lenses__operations__scatter_columns.snap b/crates/top/re_sdk/tests/lenses/snapshots/lenses__lenses__operations__scatter_columns.snap index 5d1ed8289557..05f810d36ac2 100644 --- a/crates/top/re_sdk/tests/lenses/snapshots/lenses__lenses__operations__scatter_columns.snap +++ b/crates/top/re_sdk/tests/lenses/snapshots/lenses__lenses__operations__scatter_columns.snap @@ -2,29 +2,29 @@ source: crates/top/re_sdk/tests/lenses/operations.rs expression: "format!(\"{chunk:-240}\")" --- -┌────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ METADATA: │ -│ * entity_path: /scatter_test/exploded │ -│ * id: [**REDACTED**] │ -│ * version: [**REDACTED**] │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ ┌───────────────────────────────────────────────┬──────────────────────────┬────────────────────┬────────────────────────────────────┐ │ -│ │ RowId ┆ my_timestamp ┆ tick ┆ exploded_strings │ │ -│ │ --- ┆ --- ┆ --- ┆ --- │ │ -│ │ type: FixedSizeBinary[16] ┆ type: nullable i64 ┆ type: nullable i64 ┆ type: nullable List[nullable Utf8] │ │ -│ │ ARROW:extension:metadata: {"namespace":"row"} ┆ index_name: my_timestamp ┆ index_name: tick ┆ component: exploded_strings │ │ -│ │ ARROW:extension:name: TUID ┆ is_sorted: true ┆ is_sorted: true ┆ kind: data │ │ -│ │ is_sorted: true ┆ kind: index ┆ kind: index ┆ │ │ -│ │ kind: control ┆ ┆ ┆ │ │ -│ ╞═══════════════════════════════════════════════╪══════════════════════════╪════════════════════╪════════════════════════════════════╡ │ -│ │ row_[**REDACTED**] ┆ 1 ┆ 1 ┆ [one] │ │ -│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ -│ │ row_[**REDACTED**] ┆ 2 ┆ 1 ┆ [two] │ │ -│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ -│ │ row_[**REDACTED**] ┆ 3 ┆ 1 ┆ [three] │ │ -│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ -│ │ row_[**REDACTED**] ┆ 4 ┆ 2 ┆ [four] │ │ -│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ -│ │ row_[**REDACTED**] ┆ 5 ┆ 3 ┆ null │ │ -│ └───────────────────────────────────────────────┴──────────────────────────┴────────────────────┴────────────────────────────────────┘ │ -└────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +┌───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ METADATA: │ +│ * entity_path: /scatter_test/exploded │ +│ * id: [**REDACTED**] │ +│ * version: [**REDACTED**] │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ ┌───────────────────────────────────────────────┬──────────────────────────┬──────────────────┬─────────────────────────────┐ │ +│ │ RowId ┆ my_timestamp ┆ tick ┆ exploded_strings │ │ +│ │ --- ┆ --- ┆ --- ┆ --- │ │ +│ │ type: non-null FixedSizeBinary(16) ┆ type: Int64 ┆ type: Int64 ┆ type: List(Utf8) │ │ +│ │ ARROW:extension:metadata: {"namespace":"row"} ┆ index_name: my_timestamp ┆ index_name: tick ┆ component: exploded_strings │ │ +│ │ ARROW:extension:name: TUID ┆ is_sorted: true ┆ is_sorted: true ┆ kind: data │ │ +│ │ is_sorted: true ┆ kind: index ┆ kind: index ┆ │ │ +│ │ kind: control ┆ ┆ ┆ │ │ +│ ╞═══════════════════════════════════════════════╪══════════════════════════╪══════════════════╪═════════════════════════════╡ │ +│ │ row_[**REDACTED**] ┆ 1 ┆ 1 ┆ [one] │ │ +│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ +│ │ row_[**REDACTED**] ┆ 2 ┆ 1 ┆ [two] │ │ +│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ +│ │ row_[**REDACTED**] ┆ 3 ┆ 1 ┆ [three] │ │ +│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ +│ │ row_[**REDACTED**] ┆ 4 ┆ 2 ┆ [four] │ │ +│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ +│ │ row_[**REDACTED**] ┆ 5 ┆ 3 ┆ null │ │ +│ └───────────────────────────────────────────────┴──────────────────────────┴──────────────────┴─────────────────────────────┘ │ +└───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ diff --git a/crates/top/re_sdk/tests/lenses/snapshots/lenses__lenses__operations__scatter_columns_static.snap b/crates/top/re_sdk/tests/lenses/snapshots/lenses__lenses__operations__scatter_columns_static.snap index 6f3d7e088a8e..7bc9dfa32cc9 100644 --- a/crates/top/re_sdk/tests/lenses/snapshots/lenses__lenses__operations__scatter_columns_static.snap +++ b/crates/top/re_sdk/tests/lenses/snapshots/lenses__lenses__operations__scatter_columns_static.snap @@ -2,29 +2,29 @@ source: crates/top/re_sdk/tests/lenses/operations.rs expression: "format!(\"{chunk:-240}\")" --- -┌───────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ METADATA: │ -│ * entity_path: /scatter_test/exploded │ -│ * id: [**REDACTED**] │ -│ * version: [**REDACTED**] │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ ┌───────────────────────────────────────────────┬──────────────────────────┬────────────────────────────────────┐ │ -│ │ RowId ┆ my_timestamp ┆ exploded_strings │ │ -│ │ --- ┆ --- ┆ --- │ │ -│ │ type: FixedSizeBinary[16] ┆ type: nullable i64 ┆ type: nullable List[nullable Utf8] │ │ -│ │ ARROW:extension:metadata: {"namespace":"row"} ┆ index_name: my_timestamp ┆ component: exploded_strings │ │ -│ │ ARROW:extension:name: TUID ┆ is_sorted: true ┆ kind: data │ │ -│ │ is_sorted: true ┆ kind: index ┆ │ │ -│ │ kind: control ┆ ┆ │ │ -│ ╞═══════════════════════════════════════════════╪══════════════════════════╪════════════════════════════════════╡ │ -│ │ row_[**REDACTED**] ┆ 1 ┆ [one] │ │ -│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ -│ │ row_[**REDACTED**] ┆ 2 ┆ [two] │ │ -│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ -│ │ row_[**REDACTED**] ┆ 3 ┆ [three] │ │ -│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ -│ │ row_[**REDACTED**] ┆ 4 ┆ [four] │ │ -│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ -│ │ row_[**REDACTED**] ┆ 5 ┆ null │ │ -│ └───────────────────────────────────────────────┴──────────────────────────┴────────────────────────────────────┘ │ -└───────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +┌────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ METADATA: │ +│ * entity_path: /scatter_test/exploded │ +│ * id: [**REDACTED**] │ +│ * version: [**REDACTED**] │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ ┌───────────────────────────────────────────────┬──────────────────────────┬─────────────────────────────┐ │ +│ │ RowId ┆ my_timestamp ┆ exploded_strings │ │ +│ │ --- ┆ --- ┆ --- │ │ +│ │ type: non-null FixedSizeBinary(16) ┆ type: Int64 ┆ type: List(Utf8) │ │ +│ │ ARROW:extension:metadata: {"namespace":"row"} ┆ index_name: my_timestamp ┆ component: exploded_strings │ │ +│ │ ARROW:extension:name: TUID ┆ is_sorted: true ┆ kind: data │ │ +│ │ is_sorted: true ┆ kind: index ┆ │ │ +│ │ kind: control ┆ ┆ │ │ +│ ╞═══════════════════════════════════════════════╪══════════════════════════╪═════════════════════════════╡ │ +│ │ row_[**REDACTED**] ┆ 1 ┆ [one] │ │ +│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ +│ │ row_[**REDACTED**] ┆ 2 ┆ [two] │ │ +│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ +│ │ row_[**REDACTED**] ┆ 3 ┆ [three] │ │ +│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ +│ │ row_[**REDACTED**] ┆ 4 ┆ [four] │ │ +│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ +│ │ row_[**REDACTED**] ┆ 5 ┆ null │ │ +│ └───────────────────────────────────────────────┴──────────────────────────┴─────────────────────────────┘ │ +└────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ diff --git a/crates/top/re_sdk/tests/lenses/snapshots/lenses__lenses__operations__single_static.snap b/crates/top/re_sdk/tests/lenses/snapshots/lenses__lenses__operations__single_static.snap index d0ff2133ef3f..f4d25fc9c272 100644 --- a/crates/top/re_sdk/tests/lenses/snapshots/lenses__lenses__operations__single_static.snap +++ b/crates/top/re_sdk/tests/lenses/snapshots/lenses__lenses__operations__single_static.snap @@ -2,21 +2,21 @@ source: crates/top/re_sdk/tests/lenses/operations.rs expression: "format!(\"{chunk:-240}\")" --- -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ METADATA: │ -│ * entity_path: /nullability/static │ -│ * id: [**REDACTED**] │ -│ * version: [**REDACTED**] │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ ┌───────────────────────────────────────────────┬────────────────────────────────────┬────────────────────────────────────┐ │ -│ │ RowId ┆ static_metadata_a ┆ static_metadata_b │ │ -│ │ --- ┆ --- ┆ --- │ │ -│ │ type: FixedSizeBinary[16] ┆ type: nullable List[nullable Utf8] ┆ type: nullable List[nullable Utf8] │ │ -│ │ ARROW:extension:metadata: {"namespace":"row"} ┆ component: static_metadata_a ┆ component: static_metadata_b │ │ -│ │ ARROW:extension:name: TUID ┆ kind: data ┆ kind: data │ │ -│ │ is_sorted: true ┆ ┆ │ │ -│ │ kind: control ┆ ┆ │ │ -│ ╞═══════════════════════════════════════════════╪════════════════════════════════════╪════════════════════════════════════╡ │ -│ │ row_[**REDACTED**] ┆ [static_metadata_a] ┆ [static_metadata_b] │ │ -│ └───────────────────────────────────────────────┴────────────────────────────────────┴────────────────────────────────────┘ │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ METADATA: │ +│ * entity_path: /nullability/static │ +│ * id: [**REDACTED**] │ +│ * version: [**REDACTED**] │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ ┌───────────────────────────────────────────────┬──────────────────────────────┬──────────────────────────────┐ │ +│ │ RowId ┆ static_metadata_a ┆ static_metadata_b │ │ +│ │ --- ┆ --- ┆ --- │ │ +│ │ type: non-null FixedSizeBinary(16) ┆ type: List(Utf8) ┆ type: List(Utf8) │ │ +│ │ ARROW:extension:metadata: {"namespace":"row"} ┆ component: static_metadata_a ┆ component: static_metadata_b │ │ +│ │ ARROW:extension:name: TUID ┆ kind: data ┆ kind: data │ │ +│ │ is_sorted: true ┆ ┆ │ │ +│ │ kind: control ┆ ┆ │ │ +│ ╞═══════════════════════════════════════════════╪══════════════════════════════╪══════════════════════════════╡ │ +│ │ row_[**REDACTED**] ┆ [static_metadata_a] ┆ [static_metadata_b] │ │ +│ └───────────────────────────────────────────────┴──────────────────────────────┴──────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ diff --git a/crates/utils/re_arrow_util/src/compare.rs b/crates/utils/re_arrow_util/src/compare.rs index b106a4041117..41d54ebde3b5 100644 --- a/crates/utils/re_arrow_util/src/compare.rs +++ b/crates/utils/re_arrow_util/src/compare.rs @@ -2,8 +2,6 @@ use anyhow::{Context as _, bail, ensure}; use half::f16; use itertools::izip; -use crate::format_data_type; - /// Are two arrays equal, ignoring small numeric differences? /// /// Returns `Ok` if similar. @@ -38,7 +36,7 @@ pub fn ensure_similar( for (i, (left_buff, right_buff)) in izip!(left_buffers, right_buffers).enumerate() { ensure_buffers_equal(left_buff, right_buff, data_type) - .with_context(|| format!("Datatype {}", format_data_type(data_type))) + .with_context(|| format!("Datatype {data_type}")) .with_context(|| format!("Buffer {i}"))?; } } @@ -52,7 +50,7 @@ pub fn ensure_similar( for (i, (left_child, right_child)) in izip!(left_children, right_children).enumerate() { ensure_similar(left_child, right_child) - .with_context(|| format!("Datatype {}", format_data_type(data_type))) + .with_context(|| format!("Datatype {data_type}")) .with_context(|| format!("Child {i}"))?; } } diff --git a/crates/utils/re_arrow_util/src/format.rs b/crates/utils/re_arrow_util/src/format.rs index a781724bb79f..3e23047dd645 100644 --- a/crates/utils/re_arrow_util/src/format.rs +++ b/crates/utils/re_arrow_util/src/format.rs @@ -9,7 +9,21 @@ use comfy_table::{Cell, Row, Table, presets}; use itertools::{Either, Itertools as _}; use re_tuid::Tuid; -use crate::{ArrowArrayDowncastRef as _, format_field_datatype}; +use crate::ArrowArrayDowncastRef as _; + +// --- + +/// Format the datatype of a field (column) with optional nullability +pub fn format_field_datatype(field: &Field) -> String { + if field.is_nullable() { + field.data_type().to_string() + } else { + // This follows the notation set by arrow-rs. + // If we change this, we should probably change + // arrow-rs and datafusion to match. + format!("non-null {}", field.data_type()) + } +} // --- diff --git a/crates/utils/re_arrow_util/src/format_data_type.rs b/crates/utils/re_arrow_util/src/format_data_type.rs deleted file mode 100644 index 67edd554b347..000000000000 --- a/crates/utils/re_arrow_util/src/format_data_type.rs +++ /dev/null @@ -1,173 +0,0 @@ -//! `arrow` has `ToString` implemented, but it is way too verbose. -//! -//! TODO(emilk): all this can go away once we update to Arrow 57. - -use std::fmt::Formatter; - -use arrow::datatypes::{DataType, Field, IntervalUnit, TimeUnit}; - -/// A wrapper around `DataType` that implements `Display` with a nice format. -/// -/// For use in error messages etc -#[derive(Clone)] -pub struct DisplayDataType(pub DataType); - -impl std::fmt::Display for DisplayDataType { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - DisplayDataTypeRef(&self.0).fmt(f) - } -} - -impl std::fmt::Debug for DisplayDataType { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - std::fmt::Display::fmt(self, f) - } -} - -impl From for DisplayDataType { - fn from(data_type: DataType) -> Self { - Self(data_type) - } -} - -/// Compact format of an arrow data type. -pub fn format_data_type(data_type: &DataType) -> String { - DisplayDataTypeRef(data_type).to_string() -} - -/// Format the datatype of a field (column) with optional nullability -pub fn format_field_datatype(field: &Field) -> String { - if field.is_nullable() { - format!("nullable {}", format_data_type(field.data_type())) - } else { - format_data_type(field.data_type()) - } -} - -#[repr(transparent)] -struct DisplayTimeUnit(TimeUnit); - -impl std::fmt::Display for DisplayTimeUnit { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - let s = match self.0 { - TimeUnit::Second => "s", - TimeUnit::Millisecond => "ms", - TimeUnit::Microsecond => "us", - TimeUnit::Nanosecond => "ns", - }; - f.write_str(s) - } -} - -// arrow has `ToString` implemented, but it is way too verbose. -#[repr(transparent)] -struct DisplayIntervalUnit(IntervalUnit); - -impl std::fmt::Display for DisplayIntervalUnit { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - let s = match self.0 { - IntervalUnit::YearMonth => "year/month", - IntervalUnit::DayTime => "day/time", - IntervalUnit::MonthDayNano => "month/day/nano", - }; - f.write_str(s) - } -} - -// arrow has `ToString` implemented, but it is way too verbose. -#[repr(transparent)] -struct DisplayDataTypeRef<'a>(&'a DataType); - -impl std::fmt::Display for DisplayDataTypeRef<'_> { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - let s = match &self.0 { - DataType::Null => "null", - DataType::Boolean => "bool", - DataType::Int8 => "i8", - DataType::Int16 => "i16", - DataType::Int32 => "i32", - DataType::Int64 => "i64", - DataType::UInt8 => "u8", - DataType::UInt16 => "u16", - DataType::UInt32 => "u32", - DataType::UInt64 => "u64", - DataType::Float16 => "f16", - DataType::Float32 => "f32", - DataType::Float64 => "f64", - DataType::Timestamp(unit, timezone) => { - let s = if let Some(tz) = timezone { - format!("Timestamp({}, {tz})", DisplayTimeUnit(*unit)) - } else { - format!("Timestamp({})", DisplayTimeUnit(*unit)) - }; - return f.write_str(&s); - } - DataType::Date32 => "Date32", - DataType::Date64 => "Date64", - DataType::Time32(unit) => { - let s = format!("Time32({})", DisplayTimeUnit(*unit)); - return f.write_str(&s); - } - DataType::Time64(unit) => { - let s = format!("Time64({})", DisplayTimeUnit(*unit)); - return f.write_str(&s); - } - DataType::Duration(unit) => { - let s = format!("Duration({})", DisplayTimeUnit(*unit)); - return f.write_str(&s); - } - DataType::Interval(unit) => { - let s = format!("Interval({})", DisplayIntervalUnit(*unit)); - return f.write_str(&s); - } - DataType::Binary => "Binary", - DataType::FixedSizeBinary(size) => return write!(f, "FixedSizeBinary[{size}]"), - DataType::LargeBinary => "LargeBinary", - DataType::Utf8 => "Utf8", - DataType::LargeUtf8 => "LargeUtf8", - DataType::List(field) => { - let s = format!("List[{}]", format_inner_field(field)); - return f.write_str(&s); - } - DataType::FixedSizeList(field, len) => { - let s = format!("FixedSizeList[{}; {len}]", format_inner_field(field)); - return f.write_str(&s); - } - DataType::LargeList(field) => { - let s = format!("LargeList[{}]", format_inner_field(field)); - return f.write_str(&s); - } - DataType::Struct(fields) => return write!(f, "Struct[{}]", fields.len()), - DataType::Union(fields, _) => return write!(f, "Union[{}]", fields.len()), - DataType::Map(field, _) => return write!(f, "Map[{}]", format_inner_field(field)), - DataType::Dictionary(key, value) => { - return write!(f, "Dictionary{{{}: {}}}", Self(key), Self(value)); - } - DataType::Decimal32(_, _) => "Decimal32", - DataType::Decimal64(_, _) => "Decimal64", - DataType::Decimal128(_, _) => "Decimal128", - DataType::Decimal256(_, _) => "Decimal256", - DataType::BinaryView => "BinaryView", - DataType::Utf8View => "Utf8View", - DataType::ListView(field) => { - return write!(f, "ListView[{}]", format_inner_field(field)); - } - DataType::LargeListView(field) => { - return write!(f, "LargeListView[{}]", format_inner_field(field)); - } - DataType::RunEndEncoded(_run_ends, values) => { - return write!(f, "RunEndEncoded[{}]", format_inner_field(values)); - } - }; - f.write_str(s) - } -} - -fn format_inner_field(field: &Field) -> String { - let datatype_display = DisplayDataTypeRef(field.data_type()); - if field.is_nullable() { - format!("nullable {datatype_display}") - } else { - datatype_display.to_string() - } -} diff --git a/crates/utils/re_arrow_util/src/lib.rs b/crates/utils/re_arrow_util/src/lib.rs index 0eb01d76c05c..98cc1ddf7ebd 100644 --- a/crates/utils/re_arrow_util/src/lib.rs +++ b/crates/utils/re_arrow_util/src/lib.rs @@ -4,7 +4,6 @@ mod arrays; mod batches; mod compare; mod format; -mod format_data_type; mod string_view; #[cfg(feature = "test")] mod test_extensions; @@ -19,10 +18,9 @@ pub use self::arrays::*; pub use self::batches::*; pub use self::compare::*; pub use self::format::{ - RecordBatchFormatOpts, format_record_batch, format_record_batch_opts, + RecordBatchFormatOpts, format_field_datatype, format_record_batch, format_record_batch_opts, format_record_batch_with_width, }; -pub use self::format_data_type::*; pub use self::string_view::*; #[cfg(feature = "test")] pub use self::test_extensions::*; diff --git a/crates/utils/re_arrow_util/src/test_extensions.rs b/crates/utils/re_arrow_util/src/test_extensions.rs index a7cd2081b661..a4c5d31470a0 100644 --- a/crates/utils/re_arrow_util/src/test_extensions.rs +++ b/crates/utils/re_arrow_util/src/test_extensions.rs @@ -390,15 +390,15 @@ impl SchemaTestExt for arrow::datatypes::Schema { format!( "{}: {}{}", field.name(), - if field.is_nullable() { "nullable " } else { "" }, - crate::format_data_type(field.data_type()) + if field.is_nullable() { "" } else { "non-null " }, + field.data_type() ) } else { format!( "{}: {}{} [\n {}\n]", field.name(), - if field.is_nullable() { "nullable " } else { "" }, - crate::format_data_type(field.data_type()), + if field.is_nullable() { "" } else { "non-null " }, + field.data_type(), field .metadata() .iter() diff --git a/crates/viewer/re_arrow_ui/src/datatype_ui.rs b/crates/viewer/re_arrow_ui/src/datatype_ui.rs index dbdf515c5f1d..c518673b485f 100644 --- a/crates/viewer/re_arrow_ui/src/datatype_ui.rs +++ b/crates/viewer/re_arrow_ui/src/datatype_ui.rs @@ -22,7 +22,7 @@ impl<'a> DataTypeUi<'a> { pub fn new(data_type: &'a DataType) -> Self { match data_type { DataType::Struct(fields) => DataTypeUi { - type_name: "struct".to_owned(), + type_name: "Struct".to_owned(), content: Some(Box::new(move |ui| { for field in fields { data_type_field_ui(ui, field); @@ -30,39 +30,39 @@ impl<'a> DataTypeUi<'a> { })), }, DataType::List(field) => DataTypeUi { - type_name: "list".to_owned(), + type_name: "List".to_owned(), content: Some(Box::new(move |ui| { data_type_field_ui(ui, field); })), }, DataType::ListView(field) => DataTypeUi { - type_name: "list view".to_owned(), + type_name: "ListView".to_owned(), content: Some(Box::new(move |ui| { data_type_field_ui(ui, field); })), }, DataType::FixedSizeList(field, size) => DataTypeUi { - type_name: format!("fixed-size list ({size})"), + type_name: format!("FixedSizeList({size})"), content: Some(Box::new(move |ui| { data_type_field_ui(ui, field); })), }, DataType::LargeList(field) => DataTypeUi { - type_name: "large list".to_owned(), + type_name: "LargeList".to_owned(), content: Some(Box::new(move |ui| { data_type_field_ui(ui, field); })), }, DataType::LargeListView(field) => DataTypeUi { - type_name: "large list view".to_owned(), + type_name: "LargeListView".to_owned(), content: Some(Box::new(move |ui| { data_type_field_ui(ui, field); })), }, DataType::Union(fields, mode) => { let label = match mode { - arrow::datatypes::UnionMode::Sparse => "sparse union", - arrow::datatypes::UnionMode::Dense => "dense union", + arrow::datatypes::UnionMode::Sparse => "Union(Sparse)", + arrow::datatypes::UnionMode::Dense => "Union(Dense)", }; DataTypeUi { type_name: label.to_owned(), @@ -74,17 +74,17 @@ impl<'a> DataTypeUi<'a> { } } DataType::Dictionary(_k, _v) => DataTypeUi { - type_name: "dictionary".to_owned(), + type_name: "Dictionary".to_owned(), content: None, }, DataType::Map(a, _) => DataTypeUi { - type_name: "map".to_owned(), + type_name: "Map".to_owned(), content: Some(Box::new(move |ui| { data_type_field_ui(ui, a); })), }, DataType::RunEndEncoded(a, b) => DataTypeUi { - type_name: "run-end encoded".to_owned(), + type_name: "RunEndEncoded".to_owned(), content: Some(Box::new(move |ui| { data_type_field_ui(ui, a); data_type_field_ui(ui, b); @@ -130,9 +130,12 @@ fn data_type_field_ui(ui: &mut egui::Ui, field: &arrow::datatypes::Field) { let data_type_ui = DataTypeUi::new(field.data_type()); let text = if field.is_nullable() { - format!("nullable {}", data_type_ui.type_name) + data_type_ui.type_name.clone() } else { - field.name().to_owned() + // This follows the notation set by arrow-rs. + // If we change this, we should probably change + // arrow-rs and datafusion to match. + format!("non-null {}", data_type_ui.type_name) }; let property = PropertyContent::new(field.name()) @@ -155,21 +158,21 @@ fn data_type_field_ui(ui: &mut egui::Ui, field: &arrow::datatypes::Field) { // TODO(#11071): there is some overlap here with `re_arrow_util::format` and `codegen`. pub(crate) fn simple_data_type_string(datatype: &DataType) -> Option<&'static str> { match datatype { - DataType::Null => Some("null"), - DataType::Boolean => Some("bool"), - DataType::Int8 => Some("int8"), - DataType::Int16 => Some("int16"), - DataType::Int32 => Some("int32"), - DataType::Int64 => Some("int64"), - DataType::UInt8 => Some("uint8"), - DataType::UInt16 => Some("uint16"), - DataType::UInt32 => Some("uint32"), - DataType::UInt64 => Some("uint64"), - DataType::Float16 => Some("float16"), - DataType::Float32 => Some("float32"), - DataType::Float64 => Some("float64"), - DataType::Utf8 | DataType::LargeUtf8 => Some("utf8"), - DataType::Binary | DataType::LargeBinary => Some("binary"), + DataType::Null => Some("Null"), + DataType::Boolean => Some("Boolean"), + DataType::Int8 => Some("Int8"), + DataType::Int16 => Some("Int16"), + DataType::Int32 => Some("Int32"), + DataType::Int64 => Some("Int64"), + DataType::UInt8 => Some("UInt8"), + DataType::UInt16 => Some("UInt16"), + DataType::UInt32 => Some("UInt32"), + DataType::UInt64 => Some("UInt64"), + DataType::Float16 => Some("Float16"), + DataType::Float32 => Some("Float32"), + DataType::Float64 => Some("Float64"), + DataType::Utf8 | DataType::LargeUtf8 => Some("Utf8"), + DataType::Binary | DataType::LargeBinary => Some("Binary"), _ => None, } } diff --git a/crates/viewer/re_arrow_ui/tests/snapshots/binary_array.png b/crates/viewer/re_arrow_ui/tests/snapshots/binary_array.png index 45537be71904..fb27601af27b 100644 --- a/crates/viewer/re_arrow_ui/tests/snapshots/binary_array.png +++ b/crates/viewer/re_arrow_ui/tests/snapshots/binary_array.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:52b58c9970eb1028f2009924fe9cbaebc7518846c3de577695f76762d3dd3562 -size 5539 +oid sha256:2e3481ec0d7d90dec583dbcea3390f2fe9ca6c0f492959dafedfcdd4724f8ef4 +size 5552 diff --git a/crates/viewer/re_arrow_ui/tests/snapshots/complex_map.png b/crates/viewer/re_arrow_ui/tests/snapshots/complex_map.png index 95ee563554b6..2d741be3aec9 100644 --- a/crates/viewer/re_arrow_ui/tests/snapshots/complex_map.png +++ b/crates/viewer/re_arrow_ui/tests/snapshots/complex_map.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5693d3f33717597460e58302abfb4d821e340a882060265cadd0ffa9617ca549 -size 13685 +oid sha256:a1a5196c39b00958cfc7e446b0d3aaf185c4d6a901dc68e5f98bbce1036a9ceb +size 13795 diff --git a/crates/viewer/re_arrow_ui/tests/snapshots/coordinates_struct.png b/crates/viewer/re_arrow_ui/tests/snapshots/coordinates_struct.png index c0b3bf915575..723016651d8a 100644 --- a/crates/viewer/re_arrow_ui/tests/snapshots/coordinates_struct.png +++ b/crates/viewer/re_arrow_ui/tests/snapshots/coordinates_struct.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c1e127a3c12160cafed28e2d36a998ff2fccd847e622f28b4e0b4989f8f1c5d2 -size 18634 +oid sha256:b43c9d8feda7d92a2e642011801695999dc4899723d333d9e2706064d817ae95 +size 18740 diff --git a/crates/viewer/re_arrow_ui/tests/snapshots/deeply_nested_struct.png b/crates/viewer/re_arrow_ui/tests/snapshots/deeply_nested_struct.png index a83521b790e6..4cbe6c521e51 100644 --- a/crates/viewer/re_arrow_ui/tests/snapshots/deeply_nested_struct.png +++ b/crates/viewer/re_arrow_ui/tests/snapshots/deeply_nested_struct.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:da77dc1df615c780541abb3af03c7fefd8e330df12e12865fca7054bd6e10664 -size 10906 +oid sha256:d3674adc1e4f7de48ae66f0a4a21a34da32592b1e0363e13a74c959f7636d6b2 +size 11003 diff --git a/crates/viewer/re_arrow_ui/tests/snapshots/dictionary_array.png b/crates/viewer/re_arrow_ui/tests/snapshots/dictionary_array.png index 2f6d1aaf2aeb..531457990b0f 100644 --- a/crates/viewer/re_arrow_ui/tests/snapshots/dictionary_array.png +++ b/crates/viewer/re_arrow_ui/tests/snapshots/dictionary_array.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d4c0a64f110ac1f6ff581678033fc65e8a008c8789edef2a53d5eb9008e048e2 -size 14630 +oid sha256:fd87f89eb19a4264b2f60fb6907f2b9ee775bc6779fc9646b6904805d2a437e5 +size 14592 diff --git a/crates/viewer/re_arrow_ui/tests/snapshots/fixed_size_list.png b/crates/viewer/re_arrow_ui/tests/snapshots/fixed_size_list.png index 38a43d549d32..0d200ad689a1 100644 --- a/crates/viewer/re_arrow_ui/tests/snapshots/fixed_size_list.png +++ b/crates/viewer/re_arrow_ui/tests/snapshots/fixed_size_list.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7b75cdb69874cf0d87ffc7a9c8781ad396a782dc6ffa75a199f698435eac2d6c -size 12421 +oid sha256:d00bd19e8797627d96ceea95f380fdcc2073efdd1404a3ccf0c8b27da1ef8551 +size 12560 diff --git a/crates/viewer/re_arrow_ui/tests/snapshots/large_binary_array.png b/crates/viewer/re_arrow_ui/tests/snapshots/large_binary_array.png index 1773ac969724..d42390bb5b9f 100644 --- a/crates/viewer/re_arrow_ui/tests/snapshots/large_binary_array.png +++ b/crates/viewer/re_arrow_ui/tests/snapshots/large_binary_array.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:348d2961ce7fbb50dac97f342cbd890034e70e4b0ff2138ed11c414bf6563733 -size 6147 +oid sha256:fe05c4ec81f75ade5e8bccd4ee0bfb7a4c153f2f3323fe325f4594621c58475c +size 6159 diff --git a/crates/viewer/re_arrow_ui/tests/snapshots/metadata_struct.png b/crates/viewer/re_arrow_ui/tests/snapshots/metadata_struct.png index 378234130e2b..0d2cf7685d14 100644 --- a/crates/viewer/re_arrow_ui/tests/snapshots/metadata_struct.png +++ b/crates/viewer/re_arrow_ui/tests/snapshots/metadata_struct.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bf8a7bd355b597f645efd174a03e064ad2a5f1657dcc9318d9433583773af689 -size 10716 +oid sha256:3f07a86ddf489a7625acb5c7eed06ffab36b0ad66bd97e8c3ba99ed416ea9086 +size 10811 diff --git a/crates/viewer/re_arrow_ui/tests/snapshots/mixed_lists.png b/crates/viewer/re_arrow_ui/tests/snapshots/mixed_lists.png index d2671d9af619..287fa5060538 100644 --- a/crates/viewer/re_arrow_ui/tests/snapshots/mixed_lists.png +++ b/crates/viewer/re_arrow_ui/tests/snapshots/mixed_lists.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:807eacc06269f439548065e882459c7ba48b641de5fa833311d4a5ce26de6490 -size 15510 +oid sha256:2aa040580a0ce24280b8f6fe65b8651a9a120ac32b5d8238b865453f549aaf91 +size 15626 diff --git a/crates/viewer/re_arrow_ui/tests/snapshots/optional_struct.png b/crates/viewer/re_arrow_ui/tests/snapshots/optional_struct.png index 017aa553e063..f487cfbc2a8b 100644 --- a/crates/viewer/re_arrow_ui/tests/snapshots/optional_struct.png +++ b/crates/viewer/re_arrow_ui/tests/snapshots/optional_struct.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7f4bdd11351ae92388e82261495176532473dccad1566739f69dbf4802ab7ce0 -size 9883 +oid sha256:70aab6c8e52083c88cfeec2530ad4d8082982db08dc9c0db5e796e446a3ec6ed +size 9979 diff --git a/crates/viewer/re_arrow_ui/tests/snapshots/simple_map.png b/crates/viewer/re_arrow_ui/tests/snapshots/simple_map.png index af4e2741d633..423ea79150e3 100644 --- a/crates/viewer/re_arrow_ui/tests/snapshots/simple_map.png +++ b/crates/viewer/re_arrow_ui/tests/snapshots/simple_map.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f4a270f3c04fbaef6fc59496b47041a3b80d9254a046ceae5304d1557e418a6b -size 6076 +oid sha256:4746213b20e27ef91753d7701fb44f4a8fc6bbd7f9b466288de717210c1f8fb1 +size 6173 diff --git a/crates/viewer/re_arrow_ui/tests/snapshots/union_array.png b/crates/viewer/re_arrow_ui/tests/snapshots/union_array.png index fbf906f5d71b..a0fed826dfb4 100644 --- a/crates/viewer/re_arrow_ui/tests/snapshots/union_array.png +++ b/crates/viewer/re_arrow_ui/tests/snapshots/union_array.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8fab6484531a87aa45585f4644487f140165c0b14d40fe255a5a9a24473f7b6c -size 5955 +oid sha256:6bc12a8a40a752431fa3858d6f54b6addd914b269d902e86b24a0d3a01907172 +size 6419 diff --git a/crates/viewer/re_chunk_store_ui/src/chunk_ui.rs b/crates/viewer/re_chunk_store_ui/src/chunk_ui.rs index d1e23be9b02b..cb641dc47dc9 100644 --- a/crates/viewer/re_chunk_store_ui/src/chunk_ui.rs +++ b/crates/viewer/re_chunk_store_ui/src/chunk_ui.rs @@ -89,7 +89,7 @@ impl ChunkUi { ( component, // TODO(#11071): use re_arrow_ui to format the datatype here - re_arrow_util::format_data_type(column.list_array.data_type()), + column.list_array.data_type().to_string(), ) }) .collect::>(); diff --git a/crates/viewer/re_component_ui/Cargo.toml b/crates/viewer/re_component_ui/Cargo.toml index c0c93dfed2a6..adbfa3d3bd91 100644 --- a/crates/viewer/re_component_ui/Cargo.toml +++ b/crates/viewer/re_component_ui/Cargo.toml @@ -19,7 +19,6 @@ workspace = true all-features = true [dependencies] -re_arrow_util.workspace = true re_data_ui.workspace = true # Needed for `item_ui`. re_format.workspace = true re_log.workspace = true diff --git a/crates/viewer/re_component_ui/src/variant_uis/redap_entry_kind.rs b/crates/viewer/re_component_ui/src/variant_uis/redap_entry_kind.rs index d6c18a4861fd..27c6f560f6a4 100644 --- a/crates/viewer/re_component_ui/src/variant_uis/redap_entry_kind.rs +++ b/crates/viewer/re_component_ui/src/variant_uis/redap_entry_kind.rs @@ -19,12 +19,7 @@ pub fn redap_entry_kind( let value = array .as_any() .downcast_ref::() - .ok_or_else(|| { - format!( - "unsupported arrow datatype: {}", - re_arrow_util::format_data_type(array.data_type()) - ) - })? + .ok_or_else(|| format!("unsupported arrow datatype: {}", array.data_type()))? .value(0); let kind = EntryKind::try_from(value); diff --git a/crates/viewer/re_component_ui/src/variant_uis/redap_uri_button.rs b/crates/viewer/re_component_ui/src/variant_uis/redap_uri_button.rs index 7e53c64e2ec9..b4efad436e44 100644 --- a/crates/viewer/re_component_ui/src/variant_uis/redap_uri_button.rs +++ b/crates/viewer/re_component_ui/src/variant_uis/redap_uri_button.rs @@ -25,12 +25,7 @@ pub fn redap_uri_button( let url_str = array .as_any() .downcast_ref::() - .ok_or_else(|| { - format!( - "unsupported arrow datatype: {}", - re_arrow_util::format_data_type(array.data_type()) - ) - })? + .ok_or_else(|| format!("unsupported arrow datatype: {}", array.data_type()))? .value(0); let uri = RedapUri::from_str(url_str)?; diff --git a/crates/viewer/re_data_ui/src/instance_path_ui.rs b/crates/viewer/re_data_ui/src/instance_path_ui.rs index 96a4fe8aa9e3..f27561eb9634 100644 --- a/crates/viewer/re_data_ui/src/instance_path_ui.rs +++ b/crates/viewer/re_data_ui/src/instance_path_ui.rs @@ -448,7 +448,7 @@ fn component_ui( re_ui::list_item::list_item_scope(ui, component_descr, |ui| { ui.list_item_flat_noninteractive(PropertyContent::new("Data type").value_text( // TODO(#11071): use re_arrow_ui to format the datatype here - re_arrow_util::format_data_type(&column.store_datatype), + column.store_datatype.to_string(), )); }); } diff --git a/crates/viewer/re_dataframe_ui/src/header_tooltip.rs b/crates/viewer/re_dataframe_ui/src/header_tooltip.rs index f00f197a77b4..1ce59d4cf302 100644 --- a/crates/viewer/re_dataframe_ui/src/header_tooltip.rs +++ b/crates/viewer/re_dataframe_ui/src/header_tooltip.rs @@ -160,7 +160,7 @@ fn datatype_ui(ui: &mut egui::Ui, column_name: &str, datatype: &arrow::datatypes egui::Button::image_and_text( re_ui::icons::COPY.as_image(), // TODO(#11071): use re_arrow_ui to format the datatype here - egui::RichText::new(re_arrow_util::format_data_type(datatype)).monospace(), + egui::RichText::new(datatype.to_string()).monospace(), ) .image_tint_follows_text_color(true), ) diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/header_tooltip_chunk_component_column.png b/crates/viewer/re_dataframe_ui/tests/snapshots/header_tooltip_chunk_component_column.png index 140658e771e0..0b2452bea811 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/header_tooltip_chunk_component_column.png +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/header_tooltip_chunk_component_column.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:550e6eb20a1ab76a9c35b2e37e348d9098b2efbfc7f3c6b5d84d16f3599f66c8 -size 33979 +oid sha256:683ec636a8ab1f664cfffa9c65e741c42f327ece27496f0aef4b2f7e4dc872c7 +size 34166 diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/header_tooltip_chunk_component_column_with_extras.png b/crates/viewer/re_dataframe_ui/tests/snapshots/header_tooltip_chunk_component_column_with_extras.png index 4eedf845c8ba..1bb7750fb459 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/header_tooltip_chunk_component_column_with_extras.png +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/header_tooltip_chunk_component_column_with_extras.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5f21f18648174894de58e9df7b42a23e5b43467290f9fbd22d0293b5aca4b417 -size 82008 +oid sha256:dce91406c038d6bb4c82c030c29128dab5139345945540ea9c4a6a6173d7241b +size 82209 diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/header_tooltip_dataframe_component_column.png b/crates/viewer/re_dataframe_ui/tests/snapshots/header_tooltip_dataframe_component_column.png index 4c552740c138..92d0349cf95f 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/header_tooltip_dataframe_component_column.png +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/header_tooltip_dataframe_component_column.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5dea70d8fa656ecf8930a13b1c4c6aaf39b2474f845e3cb7dd29ecb72606e95b -size 36696 +oid sha256:d8ad45f0725e26c8cc53d45a300173c9fb1d680cd638c6335a5baf0775a7c222 +size 36891 diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/header_tooltip_dataframe_component_column_with_extras.png b/crates/viewer/re_dataframe_ui/tests/snapshots/header_tooltip_dataframe_component_column_with_extras.png index d791bb2f170f..f2c9e37da7cb 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/header_tooltip_dataframe_component_column_with_extras.png +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/header_tooltip_dataframe_component_column_with_extras.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5d9137ee965f987742748f6b765098e387d40405c545cc041442ed84e3a5da4d -size 90707 +oid sha256:fe16fa8f0dc7ff992e1f3647ac01fc01e4a3531c0693fc32246efb3c9a558206 +size 90923 diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/header_tooltip_raw_field.png b/crates/viewer/re_dataframe_ui/tests/snapshots/header_tooltip_raw_field.png index 6a06337b3bda..813143d800dd 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/header_tooltip_raw_field.png +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/header_tooltip_raw_field.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1bccbc46009e3933a3160c99f6f300d05b209a44de805ac04c1130b5eab31474 -size 28027 +oid sha256:545eb2dfd1c687432e3379dc252024ef1ee1eba8ead891df73a81f3e878a1769 +size 28256 diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/header_tooltip_raw_field_user_and_sorbet_metadata.png b/crates/viewer/re_dataframe_ui/tests/snapshots/header_tooltip_raw_field_user_and_sorbet_metadata.png index 46d8cd309a13..e91a613b5522 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/header_tooltip_raw_field_user_and_sorbet_metadata.png +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/header_tooltip_raw_field_user_and_sorbet_metadata.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3725f9c00d35df3dca90ba0651cd5d9c5d17e251d008ad2fca6976a1f25ec2d8 -size 33024 +oid sha256:7f186e084b5bd450c641f0bbfa01cefdc88b2c7e6e797f0b818d7c7f1c793ff9 +size 33559 diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/header_tooltip_raw_field_user_and_sorbet_metadata_with_extras.png b/crates/viewer/re_dataframe_ui/tests/snapshots/header_tooltip_raw_field_user_and_sorbet_metadata_with_extras.png index bda594db05f5..9f0380524b39 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/header_tooltip_raw_field_user_and_sorbet_metadata_with_extras.png +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/header_tooltip_raw_field_user_and_sorbet_metadata_with_extras.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:31ed1fa7b3134b91edf79f0c910512ef7696cc7ad47079d0fb1320d6e86d6138 -size 69533 +oid sha256:163dfca4b1d0a1bd895c359dd1d5d671cc43683cfd3a662074c9aeaf01c8ffbe +size 70073 diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/header_tooltip_raw_field_user_metadata.png b/crates/viewer/re_dataframe_ui/tests/snapshots/header_tooltip_raw_field_user_metadata.png index 1a2aaceae9c3..d9d21d6f51cf 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/header_tooltip_raw_field_user_metadata.png +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/header_tooltip_raw_field_user_metadata.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4f805b40a66d98b15cd7a35defeedb72e431f094906ca6d22a6a2a0254ac760d -size 31569 +oid sha256:5bfb6bbce5a66ca47d7f149e711b221259c527dfbf004c8b51f67bd658a6be78 +size 32105 diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/header_tooltip_raw_field_user_metadata_with_extras.png b/crates/viewer/re_dataframe_ui/tests/snapshots/header_tooltip_raw_field_user_metadata_with_extras.png index 5128ce060fdd..7a7715c51c32 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/header_tooltip_raw_field_user_metadata_with_extras.png +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/header_tooltip_raw_field_user_metadata_with_extras.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:16f206dde54a2ff80e1149d1535990027e2837bbee8a8c7f4bd5a0a62f6161cc -size 59431 +oid sha256:200a50daebe825306922343ce16475c79995ef4d4efd3982608eca02fb7f5e26 +size 59974 diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/header_tooltip_raw_field_with_extras.png b/crates/viewer/re_dataframe_ui/tests/snapshots/header_tooltip_raw_field_with_extras.png index 976fd20f9cf2..59f0075d4565 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/header_tooltip_raw_field_with_extras.png +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/header_tooltip_raw_field_with_extras.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a004f56572efcb2d5c837ad45193068843569284bc12674249c5947e16afa82b -size 54796 +oid sha256:a1842b7457f62ab822aa5e1139d67ed31cb261be4a52761fdb79a3204b1a6884 +size 55036 diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/header_tooltip_row_field_nullable.png b/crates/viewer/re_dataframe_ui/tests/snapshots/header_tooltip_row_field_nullable.png index 6a06337b3bda..813143d800dd 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/header_tooltip_row_field_nullable.png +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/header_tooltip_row_field_nullable.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1bccbc46009e3933a3160c99f6f300d05b209a44de805ac04c1130b5eab31474 -size 28027 +oid sha256:545eb2dfd1c687432e3379dc252024ef1ee1eba8ead891df73a81f3e878a1769 +size 28256 diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/header_tooltip_row_field_nullable_with_extras.png b/crates/viewer/re_dataframe_ui/tests/snapshots/header_tooltip_row_field_nullable_with_extras.png index 976fd20f9cf2..59f0075d4565 100644 --- a/crates/viewer/re_dataframe_ui/tests/snapshots/header_tooltip_row_field_nullable_with_extras.png +++ b/crates/viewer/re_dataframe_ui/tests/snapshots/header_tooltip_row_field_nullable_with_extras.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a004f56572efcb2d5c837ad45193068843569284bc12674249c5947e16afa82b -size 54796 +oid sha256:a1842b7457f62ab822aa5e1139d67ed31cb261be4a52761fdb79a3204b1a6884 +size 55036 diff --git a/crates/viewer/re_view/Cargo.toml b/crates/viewer/re_view/Cargo.toml index a4e3b2fad7e1..502b19c4400d 100644 --- a/crates/viewer/re_view/Cargo.toml +++ b/crates/viewer/re_view/Cargo.toml @@ -23,7 +23,6 @@ default = [] [dependencies] re_arrow_combinators.workspace = true -re_arrow_util.workspace = true re_chunk_store.workspace = true re_entity_db.workspace = true re_log_types.workspace = true diff --git a/crates/viewer/re_view/src/lib.rs b/crates/viewer/re_view/src/lib.rs index 85d766b8dc12..7ec17f2c0897 100644 --- a/crates/viewer/re_view/src/lib.rs +++ b/crates/viewer/re_view/src/lib.rs @@ -38,7 +38,6 @@ pub use outlines::{ pub use query::{ DataResultQuery, latest_at_with_blueprint_resolved_data, range_with_blueprint_resolved_data, }; -use re_arrow_util::DisplayDataType; use re_log_types::external::arrow; pub use view_property_ui::{ view_property_component_ui, view_property_component_ui_custom, view_property_ui, @@ -64,8 +63,8 @@ pub enum ComponentMappingError { /// Failed to cast component data to target datatype. #[error("Failed to cast from {source_datatype} to {target_datatype}: {err}")] CastFailed { - source_datatype: DisplayDataType, - target_datatype: DisplayDataType, + source_datatype: arrow::datatypes::DataType, + target_datatype: arrow::datatypes::DataType, err: Arc, }, diff --git a/crates/viewer/re_view/src/query.rs b/crates/viewer/re_view/src/query.rs index 2648dcbb38d0..d21860a65a27 100644 --- a/crates/viewer/re_view/src/query.rs +++ b/crates/viewer/re_view/src/query.rs @@ -73,8 +73,8 @@ fn transform_chunk( if let Some(dt) = target_datatype { cast_list_array_values(&transformed, dt).map_err(|err| { ComponentMappingError::CastFailed { - source_datatype: transformed.data_type().clone().into(), - target_datatype: dt.clone().into(), + source_datatype: transformed.data_type().clone(), + target_datatype: dt.clone(), err: Arc::new(err), } }) diff --git a/crates/viewer/re_viewer_context/src/cache/video_stream_cache.rs b/crates/viewer/re_viewer_context/src/cache/video_stream_cache.rs index ae98b747ade4..ca81f32bc230 100644 --- a/crates/viewer/re_viewer_context/src/cache/video_stream_cache.rs +++ b/crates/viewer/re_viewer_context/src/cache/video_stream_cache.rs @@ -4,9 +4,9 @@ use std::sync::Arc; use std::sync::atomic::{AtomicBool, Ordering}; use ahash::HashMap; +use arrow::datatypes::DataType; use egui::NumExt as _; use parking_lot::RwLock; -use re_arrow_util::DisplayDataType; use re_byte_size::SizeBytes as _; use re_chunk::{ChunkId, EntityPath, Span, Timeline, TimelineName}; use re_chunk_store::{ChunkDirectLineageReport, ChunkStoreDiff, ChunkStoreEvent}; @@ -96,7 +96,7 @@ pub enum VideoStreamProcessingError { NoVideoSamplesFound, #[error("Unexpected arrow type for video sample {0}")] - InvalidVideoSampleType(DisplayDataType), + InvalidVideoSampleType(DataType), #[error("No codec specified.")] MissingCodec, @@ -931,7 +931,7 @@ fn read_samples_from_known_chunk( }; let (offsets, values) = re_arrow_util::blob_arrays_offsets_and_buffer(&raw_array).ok_or( - VideoStreamProcessingError::InvalidVideoSampleType(raw_array.data_type().clone().into()), + VideoStreamProcessingError::InvalidVideoSampleType(raw_array.data_type().clone()), )?; let lengths = offsets.lengths().collect::>(); @@ -1201,7 +1201,7 @@ fn read_samples_from_new_chunk( } let (offsets, values) = re_arrow_util::blob_arrays_offsets_and_buffer(&raw_array).ok_or( - VideoStreamProcessingError::InvalidVideoSampleType(raw_array.data_type().clone().into()), + VideoStreamProcessingError::InvalidVideoSampleType(raw_array.data_type().clone()), )?; let lengths = offsets.lengths().collect::>(); diff --git a/docs/content/reference/types/components/aggregation_policy.md b/docs/content/reference/types/components/aggregation_policy.md index 7133d3999baa..ce7c663f3fb0 100644 --- a/docs/content/reference/types/components/aggregation_policy.md +++ b/docs/content/reference/types/components/aggregation_policy.md @@ -33,7 +33,7 @@ Find both the minimum and maximum values in the range, then use the average of t ## Arrow datatype ``` -uint8 +UInt8 ``` ## API reference links diff --git a/docs/content/reference/types/components/albedo_factor.md b/docs/content/reference/types/components/albedo_factor.md index 8f371bad0df6..55929b63ae70 100644 --- a/docs/content/reference/types/components/albedo_factor.md +++ b/docs/content/reference/types/components/albedo_factor.md @@ -11,7 +11,7 @@ A color multiplier, usually applied to a whole entity, e.g. a mesh. ## Arrow datatype ``` -uint32 +UInt32 ``` ## API reference links diff --git a/docs/content/reference/types/components/annotation_context.md b/docs/content/reference/types/components/annotation_context.md index 4fff088f2271..00d4621e0cda 100644 --- a/docs/content/reference/types/components/annotation_context.md +++ b/docs/content/reference/types/components/annotation_context.md @@ -15,25 +15,25 @@ path. ## Arrow datatype ``` -List - keypoint_connections: List - } - }> +List(non-null Struct( + "class_id": non-null UInt16 + "class_description": non-null Struct( + "info": non-null Struct( + "id": non-null UInt16 + "label": Utf8 + "color": UInt32 + ) + "keypoint_annotations": non-null List(non-null Struct( + "id": non-null UInt16 + "label": Utf8 + "color": UInt32 + )) + "keypoint_connections": non-null List(non-null Struct( + "keypoint0": non-null UInt16 + "keypoint1": non-null UInt16 + )) + ) + )) ``` ## API reference links diff --git a/docs/content/reference/types/components/axis_length.md b/docs/content/reference/types/components/axis_length.md index 18b01e277046..bb08dd68f48a 100644 --- a/docs/content/reference/types/components/axis_length.md +++ b/docs/content/reference/types/components/axis_length.md @@ -11,7 +11,7 @@ The length of an axis in local units of the space. ## Arrow datatype ``` -float32 +Float32 ``` ## API reference links diff --git a/docs/content/reference/types/components/blob.md b/docs/content/reference/types/components/blob.md index 54112cf60bf8..d23c8c597f22 100644 --- a/docs/content/reference/types/components/blob.md +++ b/docs/content/reference/types/components/blob.md @@ -11,7 +11,7 @@ A binary blob of data. ## Arrow datatype ``` -List +List(non-null UInt8) ``` ## API reference links diff --git a/docs/content/reference/types/components/channel_id.md b/docs/content/reference/types/components/channel_id.md index f069c4ef7bb8..5781b1874781 100644 --- a/docs/content/reference/types/components/channel_id.md +++ b/docs/content/reference/types/components/channel_id.md @@ -13,7 +13,7 @@ Used to identify specific channels within an MCAP file. ## Arrow datatype ``` -uint16 +UInt16 ``` ## API reference links diff --git a/docs/content/reference/types/components/channel_message_counts.md b/docs/content/reference/types/components/channel_message_counts.md index 52e3760b25ef..78e849d94384 100644 --- a/docs/content/reference/types/components/channel_message_counts.md +++ b/docs/content/reference/types/components/channel_message_counts.md @@ -11,10 +11,10 @@ Used in MCAP statistics to track how many messages were recorded per channel. ## Arrow datatype ``` -List +List(non-null Struct( + "channel_id": non-null UInt16 + "message_count": non-null UInt64 + )) ``` ## API reference links diff --git a/docs/content/reference/types/components/class_id.md b/docs/content/reference/types/components/class_id.md index 17944c06f374..1020d5d32b05 100644 --- a/docs/content/reference/types/components/class_id.md +++ b/docs/content/reference/types/components/class_id.md @@ -11,7 +11,7 @@ A 16-bit ID representing a type of semantic class. ## Arrow datatype ``` -uint16 +UInt16 ``` ## API reference links diff --git a/docs/content/reference/types/components/clear_is_recursive.md b/docs/content/reference/types/components/clear_is_recursive.md index 6c2ba5c2552d..1458c4419882 100644 --- a/docs/content/reference/types/components/clear_is_recursive.md +++ b/docs/content/reference/types/components/clear_is_recursive.md @@ -11,7 +11,7 @@ Configures how a clear operation should behave - recursive or not. ## Arrow datatype ``` -boolean +Boolean ``` ## API reference links diff --git a/docs/content/reference/types/components/color.md b/docs/content/reference/types/components/color.md index da07fd835837..cae88c5af81c 100644 --- a/docs/content/reference/types/components/color.md +++ b/docs/content/reference/types/components/color.md @@ -14,7 +14,7 @@ byte is `R` and the least significant byte is `A`. ## Arrow datatype ``` -uint32 +UInt32 ``` ## API reference links diff --git a/docs/content/reference/types/components/colormap.md b/docs/content/reference/types/components/colormap.md index a82f1d5f6f75..e54c84cc9200 100644 --- a/docs/content/reference/types/components/colormap.md +++ b/docs/content/reference/types/components/colormap.md @@ -72,7 +72,7 @@ It interpolates from white to blue to purple to red to orange and back to white. ## Arrow datatype ``` -uint8 +UInt8 ``` ## API reference links diff --git a/docs/content/reference/types/components/count.md b/docs/content/reference/types/components/count.md index d97ccc3789e4..f4f0ea89b7e0 100644 --- a/docs/content/reference/types/components/count.md +++ b/docs/content/reference/types/components/count.md @@ -14,7 +14,7 @@ Used for counting various entities like messages, schemas, channels, etc. ## Arrow datatype ``` -uint64 +UInt64 ``` ## API reference links diff --git a/docs/content/reference/types/components/depth_meter.md b/docs/content/reference/types/components/depth_meter.md index 43fc2f2100b3..28f477862930 100644 --- a/docs/content/reference/types/components/depth_meter.md +++ b/docs/content/reference/types/components/depth_meter.md @@ -19,7 +19,7 @@ In 3D views on the other hand, this affects where the points of the point cloud ## Arrow datatype ``` -float32 +Float32 ``` ## API reference links diff --git a/docs/content/reference/types/components/draw_order.md b/docs/content/reference/types/components/draw_order.md index ffde6ee3a98d..3f1b5f52ae43 100644 --- a/docs/content/reference/types/components/draw_order.md +++ b/docs/content/reference/types/components/draw_order.md @@ -16,7 +16,7 @@ Draw order for entities with the same draw order is generally undefined. ## Arrow datatype ``` -float32 +Float32 ``` ## API reference links diff --git a/docs/content/reference/types/components/entity_path.md b/docs/content/reference/types/components/entity_path.md index c85f803e2388..66f3ab86da5a 100644 --- a/docs/content/reference/types/components/entity_path.md +++ b/docs/content/reference/types/components/entity_path.md @@ -11,7 +11,7 @@ A path to an entity, usually to reference some data that is part of the target e ## Arrow datatype ``` -utf8 +Utf8 ``` ## API reference links diff --git a/docs/content/reference/types/components/fill_mode.md b/docs/content/reference/types/components/fill_mode.md index c976aed45ba8..851b57f05187 100644 --- a/docs/content/reference/types/components/fill_mode.md +++ b/docs/content/reference/types/components/fill_mode.md @@ -30,7 +30,7 @@ The surface of the shape is filled in with a solid color. No lines are drawn. ## Arrow datatype ``` -uint8 +UInt8 ``` ## API reference links diff --git a/docs/content/reference/types/components/fill_ratio.md b/docs/content/reference/types/components/fill_ratio.md index 36f18a547e10..f003fe4b95f6 100644 --- a/docs/content/reference/types/components/fill_ratio.md +++ b/docs/content/reference/types/components/fill_ratio.md @@ -16,7 +16,7 @@ Defaults to 1.0. ## Arrow datatype ``` -float32 +Float32 ``` ## API reference links diff --git a/docs/content/reference/types/components/gamma_correction.md b/docs/content/reference/types/components/gamma_correction.md index a5d72e46daae..a54ee8f2a9ee 100644 --- a/docs/content/reference/types/components/gamma_correction.md +++ b/docs/content/reference/types/components/gamma_correction.md @@ -17,7 +17,7 @@ Defaults to 1.0 unless otherwise specified. ## Arrow datatype ``` -float32 +Float32 ``` ## API reference links diff --git a/docs/content/reference/types/components/geo_line_string.md b/docs/content/reference/types/components/geo_line_string.md index e3a656364101..34fdc8381e62 100644 --- a/docs/content/reference/types/components/geo_line_string.md +++ b/docs/content/reference/types/components/geo_line_string.md @@ -8,7 +8,7 @@ A geospatial line string expressed in [EPSG:4326](https://epsg.io/4326) latitude ## Arrow datatype ``` -List> +List(non-null FixedSizeList(2 x non-null Float64)) ``` ## API reference links diff --git a/docs/content/reference/types/components/graph_edge.md b/docs/content/reference/types/components/graph_edge.md index 002d4ef6010f..b900569eb0ad 100644 --- a/docs/content/reference/types/components/graph_edge.md +++ b/docs/content/reference/types/components/graph_edge.md @@ -11,10 +11,10 @@ An edge in a graph connecting two nodes. ## Arrow datatype ``` -Struct { - first: utf8 - second: utf8 -} +Struct( + "first": non-null Utf8 + "second": non-null Utf8 +) ``` ## API reference links diff --git a/docs/content/reference/types/components/graph_node.md b/docs/content/reference/types/components/graph_node.md index 638c20cf3217..60d9c9bc5945 100644 --- a/docs/content/reference/types/components/graph_node.md +++ b/docs/content/reference/types/components/graph_node.md @@ -11,7 +11,7 @@ A string-based ID representing a node in a graph. ## Arrow datatype ``` -utf8 +Utf8 ``` ## API reference links diff --git a/docs/content/reference/types/components/graph_type.md b/docs/content/reference/types/components/graph_type.md index 24aa3323c707..a84fc6beaa9d 100644 --- a/docs/content/reference/types/components/graph_type.md +++ b/docs/content/reference/types/components/graph_type.md @@ -15,7 +15,7 @@ The graph has directed edges. ## Arrow datatype ``` -uint8 +UInt8 ``` ## API reference links diff --git a/docs/content/reference/types/components/half_size2d.md b/docs/content/reference/types/components/half_size2d.md index ffeecbcdb9b6..9535bfbad93e 100644 --- a/docs/content/reference/types/components/half_size2d.md +++ b/docs/content/reference/types/components/half_size2d.md @@ -16,7 +16,7 @@ Negative sizes indicate that the box is flipped along the respective axis, but t ## Arrow datatype ``` -FixedSizeList<2, float32> +FixedSizeList(2 x non-null Float32) ``` ## API reference links diff --git a/docs/content/reference/types/components/half_size3d.md b/docs/content/reference/types/components/half_size3d.md index 836ce91e947b..f79f288ed28a 100644 --- a/docs/content/reference/types/components/half_size3d.md +++ b/docs/content/reference/types/components/half_size3d.md @@ -16,7 +16,7 @@ Negative sizes indicate that the box is flipped along the respective axis, but t ## Arrow datatype ``` -FixedSizeList<3, float32> +FixedSizeList(3 x non-null Float32) ``` ## API reference links diff --git a/docs/content/reference/types/components/image_buffer.md b/docs/content/reference/types/components/image_buffer.md index 601f857b54eb..96248c0dc8c7 100644 --- a/docs/content/reference/types/components/image_buffer.md +++ b/docs/content/reference/types/components/image_buffer.md @@ -13,7 +13,7 @@ To interpret the contents of this buffer, see, [`components.ImageFormat`](https: ## Arrow datatype ``` -List +List(non-null UInt8) ``` ## API reference links diff --git a/docs/content/reference/types/components/image_format.md b/docs/content/reference/types/components/image_format.md index 9733a14749c0..6f43999448b7 100644 --- a/docs/content/reference/types/components/image_format.md +++ b/docs/content/reference/types/components/image_format.md @@ -11,13 +11,13 @@ The metadata describing the contents of a [`components.ImageBuffer`](https://rer ## Arrow datatype ``` -Struct { - width: uint32 - height: uint32 - pixel_format: nullable uint8 - color_model: nullable uint8 - channel_datatype: nullable uint8 -} +Struct( + "width": non-null UInt32 + "height": non-null UInt32 + "pixel_format": UInt8 + "color_model": UInt8 + "channel_datatype": UInt8 +) ``` ## API reference links diff --git a/docs/content/reference/types/components/image_plane_distance.md b/docs/content/reference/types/components/image_plane_distance.md index f3f64e873292..cfa68e57ba63 100644 --- a/docs/content/reference/types/components/image_plane_distance.md +++ b/docs/content/reference/types/components/image_plane_distance.md @@ -13,7 +13,7 @@ This is only used for visualization purposes, and does not affect the projection ## Arrow datatype ``` -float32 +Float32 ``` ## API reference links diff --git a/docs/content/reference/types/components/interactive.md b/docs/content/reference/types/components/interactive.md index cf9996474b3c..19f4a7a0a7d8 100644 --- a/docs/content/reference/types/components/interactive.md +++ b/docs/content/reference/types/components/interactive.md @@ -13,7 +13,7 @@ Non interactive components are still visible, but mouse interactions in the view ## Arrow datatype ``` -boolean +Boolean ``` ## API reference links diff --git a/docs/content/reference/types/components/interpolation_mode.md b/docs/content/reference/types/components/interpolation_mode.md index 587760f973ae..b971ee42aa1c 100644 --- a/docs/content/reference/types/components/interpolation_mode.md +++ b/docs/content/reference/types/components/interpolation_mode.md @@ -27,7 +27,7 @@ The step occurs at the midpoint of the interval. ## Arrow datatype ``` -uint8 +UInt8 ``` ## API reference links diff --git a/docs/content/reference/types/components/key_value_pairs.md b/docs/content/reference/types/components/key_value_pairs.md index 3c71b8fcaea2..43c5c94cba2b 100644 --- a/docs/content/reference/types/components/key_value_pairs.md +++ b/docs/content/reference/types/components/key_value_pairs.md @@ -12,10 +12,10 @@ Each key-value pair is stored as a UTF-8 string mapping. ## Arrow datatype ``` -List +List(non-null Struct( + "first": non-null Utf8 + "second": non-null Utf8 + )) ``` ## API reference links diff --git a/docs/content/reference/types/components/keypoint_id.md b/docs/content/reference/types/components/keypoint_id.md index 630c9b1287f2..78835886b12d 100644 --- a/docs/content/reference/types/components/keypoint_id.md +++ b/docs/content/reference/types/components/keypoint_id.md @@ -11,7 +11,7 @@ A 16-bit ID representing a type of semantic keypoint within a class. ## Arrow datatype ``` -uint16 +UInt16 ``` ## API reference links diff --git a/docs/content/reference/types/components/lat_lon.md b/docs/content/reference/types/components/lat_lon.md index 9a09586739a2..b7da0773d334 100644 --- a/docs/content/reference/types/components/lat_lon.md +++ b/docs/content/reference/types/components/lat_lon.md @@ -11,7 +11,7 @@ A geospatial position expressed in [EPSG:4326](https://epsg.io/4326) latitude an ## Arrow datatype ``` -FixedSizeList<2, float64> +FixedSizeList(2 x non-null Float64) ``` ## API reference links diff --git a/docs/content/reference/types/components/length.md b/docs/content/reference/types/components/length.md index 11e70c7c055c..4cb026c9479d 100644 --- a/docs/content/reference/types/components/length.md +++ b/docs/content/reference/types/components/length.md @@ -14,7 +14,7 @@ axis or part of the entity this is the length of. ## Arrow datatype ``` -float32 +Float32 ``` ## API reference links diff --git a/docs/content/reference/types/components/line_strip2d.md b/docs/content/reference/types/components/line_strip2d.md index 3aec239297d7..f077ad310fba 100644 --- a/docs/content/reference/types/components/line_strip2d.md +++ b/docs/content/reference/types/components/line_strip2d.md @@ -19,7 +19,7 @@ The points will be connected in order, like so: ## Arrow datatype ``` -List> +List(non-null FixedSizeList(2 x non-null Float32)) ``` ## API reference links diff --git a/docs/content/reference/types/components/line_strip3d.md b/docs/content/reference/types/components/line_strip3d.md index f454ea54ceaf..df5a73a107c0 100644 --- a/docs/content/reference/types/components/line_strip3d.md +++ b/docs/content/reference/types/components/line_strip3d.md @@ -19,7 +19,7 @@ The points will be connected in order, like so: ## Arrow datatype ``` -List> +List(non-null FixedSizeList(3 x non-null Float32)) ``` ## API reference links diff --git a/docs/content/reference/types/components/linear_speed.md b/docs/content/reference/types/components/linear_speed.md index b687d2f6bc1c..e2cc36529fe9 100644 --- a/docs/content/reference/types/components/linear_speed.md +++ b/docs/content/reference/types/components/linear_speed.md @@ -11,7 +11,7 @@ Linear speed, used for translation speed for example. ## Arrow datatype ``` -float64 +Float64 ``` ## API reference links diff --git a/docs/content/reference/types/components/magnification_filter.md b/docs/content/reference/types/components/magnification_filter.md index e465277e2827..af363604cb35 100644 --- a/docs/content/reference/types/components/magnification_filter.md +++ b/docs/content/reference/types/components/magnification_filter.md @@ -20,7 +20,7 @@ Used as default for mesh rendering. ## Arrow datatype ``` -uint8 +UInt8 ``` ## API reference links diff --git a/docs/content/reference/types/components/marker_shape.md b/docs/content/reference/types/components/marker_shape.md index d01c57aad99b..ce59ecc8d36f 100644 --- a/docs/content/reference/types/components/marker_shape.md +++ b/docs/content/reference/types/components/marker_shape.md @@ -39,7 +39,7 @@ The visual appearance of a point in e.g. a 2D plot. ## Arrow datatype ``` -uint8 +UInt8 ``` ## API reference links diff --git a/docs/content/reference/types/components/marker_size.md b/docs/content/reference/types/components/marker_size.md index 05c5b462fb3c..45fcb1411eef 100644 --- a/docs/content/reference/types/components/marker_size.md +++ b/docs/content/reference/types/components/marker_size.md @@ -11,7 +11,7 @@ Radius of a marker of a point in e.g. a 2D plot, measured in UI points. ## Arrow datatype ``` -float32 +Float32 ``` ## API reference links diff --git a/docs/content/reference/types/components/media_type.md b/docs/content/reference/types/components/media_type.md index d8dc82435157..6f8a086ec2bf 100644 --- a/docs/content/reference/types/components/media_type.md +++ b/docs/content/reference/types/components/media_type.md @@ -14,7 +14,7 @@ consulted at . ## Arrow datatype ``` -utf8 +Utf8 ``` ## API reference links diff --git a/docs/content/reference/types/components/name.md b/docs/content/reference/types/components/name.md index a72a78c1d597..581972b7a703 100644 --- a/docs/content/reference/types/components/name.md +++ b/docs/content/reference/types/components/name.md @@ -11,7 +11,7 @@ A display name, typically for an entity or a item like a plot series. ## Arrow datatype ``` -utf8 +Utf8 ``` ## API reference links diff --git a/docs/content/reference/types/components/opacity.md b/docs/content/reference/types/components/opacity.md index 9c8b78aaf72a..90f4ae880962 100644 --- a/docs/content/reference/types/components/opacity.md +++ b/docs/content/reference/types/components/opacity.md @@ -14,7 +14,7 @@ Unless otherwise specified, the default value is 1. ## Arrow datatype ``` -float32 +Float32 ``` ## API reference links diff --git a/docs/content/reference/types/components/pinhole_projection.md b/docs/content/reference/types/components/pinhole_projection.md index e86ee6980514..740a54d7e415 100644 --- a/docs/content/reference/types/components/pinhole_projection.md +++ b/docs/content/reference/types/components/pinhole_projection.md @@ -21,7 +21,7 @@ Example: ## Arrow datatype ``` -FixedSizeList<9, float32> +FixedSizeList(9 x non-null Float32) ``` ## API reference links diff --git a/docs/content/reference/types/components/plane3d.md b/docs/content/reference/types/components/plane3d.md index 4dc7f25f7c0c..4e39945befbd 100644 --- a/docs/content/reference/types/components/plane3d.md +++ b/docs/content/reference/types/components/plane3d.md @@ -19,7 +19,7 @@ I.e. the plane with xyz = (2, 0, 0), d = 1 is equivalent to xyz = (1, 0, 0), d = ## Arrow datatype ``` -FixedSizeList<4, float32> +FixedSizeList(4 x non-null Float32) ``` ## API reference links diff --git a/docs/content/reference/types/components/position2d.md b/docs/content/reference/types/components/position2d.md index 78f839ff55db..03a77482a6b1 100644 --- a/docs/content/reference/types/components/position2d.md +++ b/docs/content/reference/types/components/position2d.md @@ -11,7 +11,7 @@ A position in 2D space. ## Arrow datatype ``` -FixedSizeList<2, float32> +FixedSizeList(2 x non-null Float32) ``` ## API reference links diff --git a/docs/content/reference/types/components/position3d.md b/docs/content/reference/types/components/position3d.md index 5bd68ea68e12..e54feb31b99e 100644 --- a/docs/content/reference/types/components/position3d.md +++ b/docs/content/reference/types/components/position3d.md @@ -11,7 +11,7 @@ A position in 3D space. ## Arrow datatype ``` -FixedSizeList<3, float32> +FixedSizeList(3 x non-null Float32) ``` ## API reference links diff --git a/docs/content/reference/types/components/radius.md b/docs/content/reference/types/components/radius.md index 2fb632d08d48..4647f72c4116 100644 --- a/docs/content/reference/types/components/radius.md +++ b/docs/content/reference/types/components/radius.md @@ -18,7 +18,7 @@ The Viewer's UI scaling defaults to the OS scaling which typically is 100% for f ## Arrow datatype ``` -float32 +Float32 ``` ## API reference links diff --git a/docs/content/reference/types/components/range1d.md b/docs/content/reference/types/components/range1d.md index 047e09c6de8c..28abbcaf8d69 100644 --- a/docs/content/reference/types/components/range1d.md +++ b/docs/content/reference/types/components/range1d.md @@ -11,7 +11,7 @@ A 1D range, specifying a lower and upper bound. ## Arrow datatype ``` -FixedSizeList<2, float64> +FixedSizeList(2 x non-null Float64) ``` ## API reference links diff --git a/docs/content/reference/types/components/resolution.md b/docs/content/reference/types/components/resolution.md index bb44d4cf0dd7..10ebc7880e4b 100644 --- a/docs/content/reference/types/components/resolution.md +++ b/docs/content/reference/types/components/resolution.md @@ -13,7 +13,7 @@ Typically in integer units, but for some use cases floating point may be used. ## Arrow datatype ``` -FixedSizeList<2, float32> +FixedSizeList(2 x non-null Float32) ``` ## API reference links diff --git a/docs/content/reference/types/components/rotation_axis_angle.md b/docs/content/reference/types/components/rotation_axis_angle.md index bffdd7330f51..3b4c8483824d 100644 --- a/docs/content/reference/types/components/rotation_axis_angle.md +++ b/docs/content/reference/types/components/rotation_axis_angle.md @@ -14,10 +14,10 @@ angle is zero in which case it is treated as an identity. ## Arrow datatype ``` -Struct { - axis: FixedSizeList<3, float32> - angle: float32 -} +Struct( + "axis": non-null FixedSizeList(3 x non-null Float32) + "angle": non-null Float32 +) ``` ## API reference links diff --git a/docs/content/reference/types/components/rotation_quat.md b/docs/content/reference/types/components/rotation_quat.md index 1b98aefc5960..811ed686b70f 100644 --- a/docs/content/reference/types/components/rotation_quat.md +++ b/docs/content/reference/types/components/rotation_quat.md @@ -15,7 +15,7 @@ If normalization fails the rotation is treated as an invalid transform. ## Arrow datatype ``` -FixedSizeList<4, float32> +FixedSizeList(4 x non-null Float32) ``` ## API reference links diff --git a/docs/content/reference/types/components/scalar.md b/docs/content/reference/types/components/scalar.md index fbd0fe5aea0e..32671e2d04ae 100644 --- a/docs/content/reference/types/components/scalar.md +++ b/docs/content/reference/types/components/scalar.md @@ -13,7 +13,7 @@ Used for time series plots. ## Arrow datatype ``` -float64 +Float64 ``` ## API reference links diff --git a/docs/content/reference/types/components/scale3d.md b/docs/content/reference/types/components/scale3d.md index de9f3fb8b524..55d18f36551f 100644 --- a/docs/content/reference/types/components/scale3d.md +++ b/docs/content/reference/types/components/scale3d.md @@ -15,7 +15,7 @@ Each component scales along the corresponding axis. ## Arrow datatype ``` -FixedSizeList<3, float32> +FixedSizeList(3 x non-null Float32) ``` ## API reference links diff --git a/docs/content/reference/types/components/schema_id.md b/docs/content/reference/types/components/schema_id.md index 340e3e252491..eb307447a95b 100644 --- a/docs/content/reference/types/components/schema_id.md +++ b/docs/content/reference/types/components/schema_id.md @@ -11,7 +11,7 @@ A 16-bit unique identifier for a schema within the MCAP file. ## Arrow datatype ``` -uint16 +UInt16 ``` ## API reference links diff --git a/docs/content/reference/types/components/show_labels.md b/docs/content/reference/types/components/show_labels.md index f27577c28a58..36239c351a14 100644 --- a/docs/content/reference/types/components/show_labels.md +++ b/docs/content/reference/types/components/show_labels.md @@ -15,7 +15,7 @@ blueprints. ## Arrow datatype ``` -boolean +Boolean ``` ## API reference links diff --git a/docs/content/reference/types/components/stroke_width.md b/docs/content/reference/types/components/stroke_width.md index d72145bb79b4..a27995e68a45 100644 --- a/docs/content/reference/types/components/stroke_width.md +++ b/docs/content/reference/types/components/stroke_width.md @@ -11,7 +11,7 @@ The width of a stroke specified in UI points. ## Arrow datatype ``` -float32 +Float32 ``` ## API reference links diff --git a/docs/content/reference/types/components/tensor_data.md b/docs/content/reference/types/components/tensor_data.md index 3de2724c3242..ead2b293624e 100644 --- a/docs/content/reference/types/components/tensor_data.md +++ b/docs/content/reference/types/components/tensor_data.md @@ -18,24 +18,24 @@ which stores a contiguous array of typed values. ## Arrow datatype ``` -Struct { - shape: List - names: nullable List - buffer: DenseUnion { - 0 = "_null_markers": nullable null - 1 = "U8": List - 2 = "U16": List - 3 = "U32": List - 4 = "U64": List - 5 = "I8": List - 6 = "I16": List - 7 = "I32": List - 8 = "I64": List - 9 = "F16": List - 10 = "F32": List - 11 = "F64": List - } -} +Struct( + "shape": non-null List(non-null UInt64) + "names": List(non-null Utf8) + "buffer": non-null Union(Dense, + 0: ("_null_markers": Null) + 1: ("U8": non-null List(non-null UInt8)) + 2: ("U16": non-null List(non-null UInt16)) + 3: ("U32": non-null List(non-null UInt32)) + 4: ("U64": non-null List(non-null UInt64)) + 5: ("I8": non-null List(non-null Int8)) + 6: ("I16": non-null List(non-null Int16)) + 7: ("I32": non-null List(non-null Int32)) + 8: ("I64": non-null List(non-null Int64)) + 9: ("F16": non-null List(non-null Float16)) + 10: ("F32": non-null List(non-null Float32)) + 11: ("F64": non-null List(non-null Float64)) + ) +) ``` ## API reference links diff --git a/docs/content/reference/types/components/tensor_dimension_index_selection.md b/docs/content/reference/types/components/tensor_dimension_index_selection.md index bea9c9e60993..9c3a8151753a 100644 --- a/docs/content/reference/types/components/tensor_dimension_index_selection.md +++ b/docs/content/reference/types/components/tensor_dimension_index_selection.md @@ -11,10 +11,10 @@ Specifies a concrete index on a tensor dimension. ## Arrow datatype ``` -Struct { - dimension: uint32 - index: uint64 -} +Struct( + "dimension": non-null UInt32 + "index": non-null UInt64 +) ``` ## API reference links diff --git a/docs/content/reference/types/components/tensor_height_dimension.md b/docs/content/reference/types/components/tensor_height_dimension.md index 52e67d9843a1..b365cf9c7ca3 100644 --- a/docs/content/reference/types/components/tensor_height_dimension.md +++ b/docs/content/reference/types/components/tensor_height_dimension.md @@ -11,10 +11,10 @@ Specifies which dimension to use for height. ## Arrow datatype ``` -Struct { - dimension: uint32 - invert: boolean -} +Struct( + "dimension": non-null UInt32 + "invert": non-null Boolean +) ``` ## API reference links diff --git a/docs/content/reference/types/components/tensor_width_dimension.md b/docs/content/reference/types/components/tensor_width_dimension.md index bcd47ae073d7..c0ba342b5446 100644 --- a/docs/content/reference/types/components/tensor_width_dimension.md +++ b/docs/content/reference/types/components/tensor_width_dimension.md @@ -11,10 +11,10 @@ Specifies which dimension to use for width. ## Arrow datatype ``` -Struct { - dimension: uint32 - invert: boolean -} +Struct( + "dimension": non-null UInt32 + "invert": non-null Boolean +) ``` ## API reference links diff --git a/docs/content/reference/types/components/texcoord2d.md b/docs/content/reference/types/components/texcoord2d.md index 9111894445e2..6ec55108a807 100644 --- a/docs/content/reference/types/components/texcoord2d.md +++ b/docs/content/reference/types/components/texcoord2d.md @@ -26,7 +26,7 @@ which places the origin at the bottom-left. ## Arrow datatype ``` -FixedSizeList<2, float32> +FixedSizeList(2 x non-null Float32) ``` ## API reference links diff --git a/docs/content/reference/types/components/text.md b/docs/content/reference/types/components/text.md index edff3bbf76ba..739929bbf891 100644 --- a/docs/content/reference/types/components/text.md +++ b/docs/content/reference/types/components/text.md @@ -11,7 +11,7 @@ A string of text, e.g. for labels and text documents. ## Arrow datatype ``` -utf8 +Utf8 ``` ## API reference links diff --git a/docs/content/reference/types/components/text_log_level.md b/docs/content/reference/types/components/text_log_level.md index 90976bc3ded6..a57c48bf959f 100644 --- a/docs/content/reference/types/components/text_log_level.md +++ b/docs/content/reference/types/components/text_log_level.md @@ -19,7 +19,7 @@ Recommended to be one of: ## Arrow datatype ``` -utf8 +Utf8 ``` ## API reference links diff --git a/docs/content/reference/types/components/timestamp.md b/docs/content/reference/types/components/timestamp.md index 213c53752bb9..4c7f1204c3ff 100644 --- a/docs/content/reference/types/components/timestamp.md +++ b/docs/content/reference/types/components/timestamp.md @@ -13,7 +13,7 @@ Should be an absolute time, i.e. relative to Unix Epoch. ## Arrow datatype ``` -int64 +Int64 ``` ## API reference links diff --git a/docs/content/reference/types/components/transform_frame_id.md b/docs/content/reference/types/components/transform_frame_id.md index 752df3759d54..727200c3afab 100644 --- a/docs/content/reference/types/components/transform_frame_id.md +++ b/docs/content/reference/types/components/transform_frame_id.md @@ -19,7 +19,7 @@ that the entity path may be using (defined by an [`archetypes.CoordinateFrame`]( ## Arrow datatype ``` -utf8 +Utf8 ``` ## API reference links diff --git a/docs/content/reference/types/components/transform_mat3x3.md b/docs/content/reference/types/components/transform_mat3x3.md index 29c5e5a3f039..5d553b61d220 100644 --- a/docs/content/reference/types/components/transform_mat3x3.md +++ b/docs/content/reference/types/components/transform_mat3x3.md @@ -23,7 +23,7 @@ row 2 | flat_columns[2] flat_columns[5] flat_columns[8] ## Arrow datatype ``` -FixedSizeList<9, float32> +FixedSizeList(9 x non-null Float32) ``` ## API reference links diff --git a/docs/content/reference/types/components/transform_relation.md b/docs/content/reference/types/components/transform_relation.md index 231249dcf722..03a340a78c46 100644 --- a/docs/content/reference/types/components/transform_relation.md +++ b/docs/content/reference/types/components/transform_relation.md @@ -23,7 +23,7 @@ From perspective of `parent/child`, the `parent` entity is translated 1 unit alo ## Arrow datatype ``` -uint8 +UInt8 ``` ## API reference links diff --git a/docs/content/reference/types/components/translation3d.md b/docs/content/reference/types/components/translation3d.md index 3b49dbf4291c..329b7fae824f 100644 --- a/docs/content/reference/types/components/translation3d.md +++ b/docs/content/reference/types/components/translation3d.md @@ -11,7 +11,7 @@ A translation vector in 3D space. ## Arrow datatype ``` -FixedSizeList<3, float32> +FixedSizeList(3 x non-null Float32) ``` ## API reference links diff --git a/docs/content/reference/types/components/triangle_indices.md b/docs/content/reference/types/components/triangle_indices.md index d129224c8394..61c1ca9f57f8 100644 --- a/docs/content/reference/types/components/triangle_indices.md +++ b/docs/content/reference/types/components/triangle_indices.md @@ -11,7 +11,7 @@ The three indices of a triangle in a triangle mesh. ## Arrow datatype ``` -FixedSizeList<3, uint32> +FixedSizeList(3 x non-null UInt32) ``` ## API reference links diff --git a/docs/content/reference/types/components/value_range.md b/docs/content/reference/types/components/value_range.md index 9dd254a78be2..41cc7429a731 100644 --- a/docs/content/reference/types/components/value_range.md +++ b/docs/content/reference/types/components/value_range.md @@ -12,7 +12,7 @@ Range of expected or valid values, specifying a lower and upper bound. ## Arrow datatype ``` -FixedSizeList<2, float64> +FixedSizeList(2 x non-null Float64) ``` ## API reference links diff --git a/docs/content/reference/types/components/vector2d.md b/docs/content/reference/types/components/vector2d.md index c53cc9f3fbe3..279db459d942 100644 --- a/docs/content/reference/types/components/vector2d.md +++ b/docs/content/reference/types/components/vector2d.md @@ -11,7 +11,7 @@ A vector in 2D space. ## Arrow datatype ``` -FixedSizeList<2, float32> +FixedSizeList(2 x non-null Float32) ``` ## API reference links diff --git a/docs/content/reference/types/components/vector3d.md b/docs/content/reference/types/components/vector3d.md index 5221f9d54c47..d677aad15d35 100644 --- a/docs/content/reference/types/components/vector3d.md +++ b/docs/content/reference/types/components/vector3d.md @@ -11,7 +11,7 @@ A vector in 3D space. ## Arrow datatype ``` -FixedSizeList<3, float32> +FixedSizeList(3 x non-null Float32) ``` ## API reference links diff --git a/docs/content/reference/types/components/video_codec.md b/docs/content/reference/types/components/video_codec.md index dc6973444130..491546b0784e 100644 --- a/docs/content/reference/types/components/video_codec.md +++ b/docs/content/reference/types/components/video_codec.md @@ -51,7 +51,7 @@ Enum value is the fourcc for 'hev1' (the WebCodec string assigned to this codec) ## Arrow datatype ``` -uint32 +UInt32 ``` ## API reference links diff --git a/docs/content/reference/types/components/video_sample.md b/docs/content/reference/types/components/video_sample.md index 244719f15c61..eec6fcb3ad4c 100644 --- a/docs/content/reference/types/components/video_sample.md +++ b/docs/content/reference/types/components/video_sample.md @@ -16,7 +16,7 @@ Keyframes may require additional data, for details see [`components.VideoCodec`] ## Arrow datatype ``` -List +List(non-null UInt8) ``` ## API reference links diff --git a/docs/content/reference/types/components/video_timestamp.md b/docs/content/reference/types/components/video_timestamp.md index 13ddaf965856..ad3cd82b2bdb 100644 --- a/docs/content/reference/types/components/video_timestamp.md +++ b/docs/content/reference/types/components/video_timestamp.md @@ -11,7 +11,7 @@ Timestamp inside a [`archetypes.AssetVideo`](https://rerun.io/docs/reference/typ ## Arrow datatype ``` -int64 +Int64 ``` ## API reference links diff --git a/docs/content/reference/types/components/view_coordinates.md b/docs/content/reference/types/components/view_coordinates.md index 745b22454730..b679530ce714 100644 --- a/docs/content/reference/types/components/view_coordinates.md +++ b/docs/content/reference/types/components/view_coordinates.md @@ -29,7 +29,7 @@ The following constants are used to represent the different directions: ## Arrow datatype ``` -FixedSizeList<3, uint8> +FixedSizeList(3 x non-null UInt8) ``` ## API reference links diff --git a/docs/content/reference/types/components/visible.md b/docs/content/reference/types/components/visible.md index c41fd1284567..8628b3714ab4 100644 --- a/docs/content/reference/types/components/visible.md +++ b/docs/content/reference/types/components/visible.md @@ -11,7 +11,7 @@ Whether the container, view, entity or instance is currently visible. ## Arrow datatype ``` -boolean +Boolean ``` ## API reference links diff --git a/docs/content/reference/types/datatypes/absolute_time_range.md b/docs/content/reference/types/datatypes/absolute_time_range.md index 0aa7a6b4b3e3..03a839add999 100644 --- a/docs/content/reference/types/datatypes/absolute_time_range.md +++ b/docs/content/reference/types/datatypes/absolute_time_range.md @@ -7,22 +7,22 @@ Two [`datatypes.TimeInt`](https://rerun.io/docs/reference/types/datatypes/time_i ## Fields #### `min` -Type: [`TimeInt`](../datatypes/time_int.md) +Type: non-null [`TimeInt`](../datatypes/time_int.md) Start of the range. #### `max` -Type: [`TimeInt`](../datatypes/time_int.md) +Type: non-null [`TimeInt`](../datatypes/time_int.md) End of the range (inclusive). ## Arrow datatype ``` -Struct { - min: int64 - max: int64 -} +Struct( + "min": non-null Int64 + "max": non-null Int64 +) ``` ## API reference links diff --git a/docs/content/reference/types/datatypes/angle.md b/docs/content/reference/types/datatypes/angle.md index 8972ce1e235b..ad49bd086b3f 100644 --- a/docs/content/reference/types/datatypes/angle.md +++ b/docs/content/reference/types/datatypes/angle.md @@ -8,7 +8,7 @@ Angle in radians. ## Arrow datatype ``` -float32 +Float32 ``` ## API reference links diff --git a/docs/content/reference/types/datatypes/annotation_info.md b/docs/content/reference/types/datatypes/annotation_info.md index 002265fdde51..bdb17950480f 100644 --- a/docs/content/reference/types/datatypes/annotation_info.md +++ b/docs/content/reference/types/datatypes/annotation_info.md @@ -10,28 +10,28 @@ The id refers either to a class or key-point id ## Fields #### `id` -Type: `uint16` +Type: non-null `UInt16` [`datatypes.ClassId`](https://rerun.io/docs/reference/types/datatypes/class_id) or [`datatypes.KeypointId`](https://rerun.io/docs/reference/types/datatypes/keypoint_id) to which this annotation info belongs. #### `label` -Type: nullable [`Utf8`](../datatypes/utf8.md) +Type: [`Utf8`](../datatypes/utf8.md) The label that will be shown in the UI. #### `color` -Type: nullable [`Rgba32`](../datatypes/rgba32.md) +Type: [`Rgba32`](../datatypes/rgba32.md) The color that will be applied to the annotated entity. ## Arrow datatype ``` -Struct { - id: uint16 - label: nullable utf8 - color: nullable uint32 -} +Struct( + "id": non-null UInt16 + "label": Utf8 + "color": UInt32 +) ``` ## API reference links diff --git a/docs/content/reference/types/datatypes/blob.md b/docs/content/reference/types/datatypes/blob.md index 389ccec848e1..5189d8b8659f 100644 --- a/docs/content/reference/types/datatypes/blob.md +++ b/docs/content/reference/types/datatypes/blob.md @@ -8,7 +8,7 @@ A binary blob of data. ## Arrow datatype ``` -List +List(non-null UInt8) ``` ## API reference links diff --git a/docs/content/reference/types/datatypes/bool.md b/docs/content/reference/types/datatypes/bool.md index 8a535a90aefe..0a296d3ece61 100644 --- a/docs/content/reference/types/datatypes/bool.md +++ b/docs/content/reference/types/datatypes/bool.md @@ -8,7 +8,7 @@ A single boolean. ## Arrow datatype ``` -boolean +Boolean ``` ## API reference links diff --git a/docs/content/reference/types/datatypes/channel_count_pair.md b/docs/content/reference/types/datatypes/channel_count_pair.md index d55b6ed8b5d4..053c8ea10cb4 100644 --- a/docs/content/reference/types/datatypes/channel_count_pair.md +++ b/docs/content/reference/types/datatypes/channel_count_pair.md @@ -7,22 +7,22 @@ A pair representing a channel ID and its associated message count. ## Fields #### `channel_id` -Type: [`UInt16`](../datatypes/uint16.md) +Type: non-null [`UInt16`](../datatypes/uint16.md) The channel ID. #### `message_count` -Type: [`UInt64`](../datatypes/uint64.md) +Type: non-null [`UInt64`](../datatypes/uint64.md) The message count for this channel. ## Arrow datatype ``` -Struct { - channel_id: uint16 - message_count: uint64 -} +Struct( + "channel_id": non-null UInt16 + "message_count": non-null UInt64 +) ``` ## API reference links diff --git a/docs/content/reference/types/datatypes/channel_datatype.md b/docs/content/reference/types/datatypes/channel_datatype.md index 8904ca772d93..bd767ccc65d2 100644 --- a/docs/content/reference/types/datatypes/channel_datatype.md +++ b/docs/content/reference/types/datatypes/channel_datatype.md @@ -44,7 +44,7 @@ How individual color channel components are encoded. ## Arrow datatype ``` -uint8 +UInt8 ``` ## API reference links diff --git a/docs/content/reference/types/datatypes/class_description.md b/docs/content/reference/types/datatypes/class_description.md index f77a8bd6226e..f4f2abc309d5 100644 --- a/docs/content/reference/types/datatypes/class_description.md +++ b/docs/content/reference/types/datatypes/class_description.md @@ -20,39 +20,39 @@ colored as described by the class's [`datatypes.AnnotationInfo`](https://rerun.i ## Fields #### `info` -Type: [`AnnotationInfo`](../datatypes/annotation_info.md) +Type: non-null [`AnnotationInfo`](../datatypes/annotation_info.md) The [`datatypes.AnnotationInfo`](https://rerun.io/docs/reference/types/datatypes/annotation_info) for the class. #### `keypoint_annotations` -Type: List of [`AnnotationInfo`](../datatypes/annotation_info.md) +Type: non-null List of [`AnnotationInfo`](../datatypes/annotation_info.md) The [`datatypes.AnnotationInfo`](https://rerun.io/docs/reference/types/datatypes/annotation_info) for all of the keypoints. #### `keypoint_connections` -Type: List of [`KeypointPair`](../datatypes/keypoint_pair.md) +Type: non-null List of [`KeypointPair`](../datatypes/keypoint_pair.md) The connections between keypoints. ## Arrow datatype ``` -Struct { - info: Struct { - id: uint16 - label: nullable utf8 - color: nullable uint32 - } - keypoint_annotations: List - keypoint_connections: List -} +Struct( + "info": non-null Struct( + "id": non-null UInt16 + "label": Utf8 + "color": UInt32 + ) + "keypoint_annotations": non-null List(non-null Struct( + "id": non-null UInt16 + "label": Utf8 + "color": UInt32 + )) + "keypoint_connections": non-null List(non-null Struct( + "keypoint0": non-null UInt16 + "keypoint1": non-null UInt16 + )) +) ``` ## API reference links diff --git a/docs/content/reference/types/datatypes/class_description_map_elem.md b/docs/content/reference/types/datatypes/class_description_map_elem.md index 979e6668b4c8..3f94b1cfa122 100644 --- a/docs/content/reference/types/datatypes/class_description_map_elem.md +++ b/docs/content/reference/types/datatypes/class_description_map_elem.md @@ -10,37 +10,37 @@ This is internal to [`components.AnnotationContext`](https://rerun.io/docs/refer ## Fields #### `class_id` -Type: [`ClassId`](../datatypes/class_id.md) +Type: non-null [`ClassId`](../datatypes/class_id.md) The key: the [`components.ClassId`](https://rerun.io/docs/reference/types/components/class_id). #### `class_description` -Type: [`ClassDescription`](../datatypes/class_description.md) +Type: non-null [`ClassDescription`](../datatypes/class_description.md) The value: class name, color, etc. ## Arrow datatype ``` -Struct { - class_id: uint16 - class_description: Struct { - info: Struct { - id: uint16 - label: nullable utf8 - color: nullable uint32 - } - keypoint_annotations: List - keypoint_connections: List - } -} +Struct( + "class_id": non-null UInt16 + "class_description": non-null Struct( + "info": non-null Struct( + "id": non-null UInt16 + "label": Utf8 + "color": UInt32 + ) + "keypoint_annotations": non-null List(non-null Struct( + "id": non-null UInt16 + "label": Utf8 + "color": UInt32 + )) + "keypoint_connections": non-null List(non-null Struct( + "keypoint0": non-null UInt16 + "keypoint1": non-null UInt16 + )) + ) +) ``` ## API reference links diff --git a/docs/content/reference/types/datatypes/class_id.md b/docs/content/reference/types/datatypes/class_id.md index 0dcace524b59..8778abb41128 100644 --- a/docs/content/reference/types/datatypes/class_id.md +++ b/docs/content/reference/types/datatypes/class_id.md @@ -8,7 +8,7 @@ A 16-bit ID representing a type of semantic class. ## Arrow datatype ``` -uint16 +UInt16 ``` ## API reference links diff --git a/docs/content/reference/types/datatypes/color_model.md b/docs/content/reference/types/datatypes/color_model.md index 4dfcedee3d72..47f505fd158d 100644 --- a/docs/content/reference/types/datatypes/color_model.md +++ b/docs/content/reference/types/datatypes/color_model.md @@ -26,7 +26,7 @@ Blue, Green, Red, Alpha ## Arrow datatype ``` -uint8 +UInt8 ``` ## API reference links diff --git a/docs/content/reference/types/datatypes/dvec2d.md b/docs/content/reference/types/datatypes/dvec2d.md index a337ea272965..3685940d2c74 100644 --- a/docs/content/reference/types/datatypes/dvec2d.md +++ b/docs/content/reference/types/datatypes/dvec2d.md @@ -8,7 +8,7 @@ A double-precision vector in 2D space. ## Arrow datatype ``` -FixedSizeList<2, float64> +FixedSizeList(2 x non-null Float64) ``` ## API reference links diff --git a/docs/content/reference/types/datatypes/entity_path.md b/docs/content/reference/types/datatypes/entity_path.md index 4080e38d07f9..f9b10ecd735d 100644 --- a/docs/content/reference/types/datatypes/entity_path.md +++ b/docs/content/reference/types/datatypes/entity_path.md @@ -8,7 +8,7 @@ A path to an entity in the `ChunkStore`. ## Arrow datatype ``` -utf8 +Utf8 ``` ## API reference links diff --git a/docs/content/reference/types/datatypes/float32.md b/docs/content/reference/types/datatypes/float32.md index 87fcedfce165..325719c81c7d 100644 --- a/docs/content/reference/types/datatypes/float32.md +++ b/docs/content/reference/types/datatypes/float32.md @@ -8,7 +8,7 @@ A single-precision 32-bit IEEE 754 floating point number. ## Arrow datatype ``` -float32 +Float32 ``` ## API reference links diff --git a/docs/content/reference/types/datatypes/float64.md b/docs/content/reference/types/datatypes/float64.md index dda7f71ebac1..37adff5d1f38 100644 --- a/docs/content/reference/types/datatypes/float64.md +++ b/docs/content/reference/types/datatypes/float64.md @@ -8,7 +8,7 @@ A double-precision 64-bit IEEE 754 floating point number. ## Arrow datatype ``` -float64 +Float64 ``` ## API reference links diff --git a/docs/content/reference/types/datatypes/image_format.md b/docs/content/reference/types/datatypes/image_format.md index c1476f30c5fa..25f18f864863 100644 --- a/docs/content/reference/types/datatypes/image_format.md +++ b/docs/content/reference/types/datatypes/image_format.md @@ -7,31 +7,31 @@ The metadata describing the contents of a [`components.ImageBuffer`](https://rer ## Fields #### `width` -Type: `uint32` +Type: non-null `UInt32` The width of the image in pixels. #### `height` -Type: `uint32` +Type: non-null `UInt32` The height of the image in pixels. #### `pixel_format` -Type: nullable [`PixelFormat`](../datatypes/pixel_format.md) +Type: [`PixelFormat`](../datatypes/pixel_format.md) Used mainly for chroma downsampled formats and differing number of bits per channel. If specified, this takes precedence over both [`datatypes.ColorModel`](https://rerun.io/docs/reference/types/datatypes/color_model) and [`datatypes.ChannelDatatype`](https://rerun.io/docs/reference/types/datatypes/channel_datatype) (which are ignored). #### `color_model` -Type: nullable [`ColorModel`](../datatypes/color_model.md) +Type: [`ColorModel`](../datatypes/color_model.md) L, RGB, RGBA, … Also requires a [`datatypes.ChannelDatatype`](https://rerun.io/docs/reference/types/datatypes/channel_datatype) to fully specify the pixel format. #### `channel_datatype` -Type: nullable [`ChannelDatatype`](../datatypes/channel_datatype.md) +Type: [`ChannelDatatype`](../datatypes/channel_datatype.md) The data type of each channel (e.g. the red channel) of the image data (U8, F16, …). @@ -40,13 +40,13 @@ Also requires a [`datatypes.ColorModel`](https://rerun.io/docs/reference/types/d ## Arrow datatype ``` -Struct { - width: uint32 - height: uint32 - pixel_format: nullable uint8 - color_model: nullable uint8 - channel_datatype: nullable uint8 -} +Struct( + "width": non-null UInt32 + "height": non-null UInt32 + "pixel_format": UInt8 + "color_model": UInt8 + "channel_datatype": UInt8 +) ``` ## API reference links diff --git a/docs/content/reference/types/datatypes/keypoint_id.md b/docs/content/reference/types/datatypes/keypoint_id.md index 63576c27e829..63691309a689 100644 --- a/docs/content/reference/types/datatypes/keypoint_id.md +++ b/docs/content/reference/types/datatypes/keypoint_id.md @@ -8,7 +8,7 @@ A 16-bit ID representing a type of semantic keypoint within a class. ## Arrow datatype ``` -uint16 +UInt16 ``` ## API reference links diff --git a/docs/content/reference/types/datatypes/keypoint_pair.md b/docs/content/reference/types/datatypes/keypoint_pair.md index 0c59a03b0e4c..b08a9bd1dc67 100644 --- a/docs/content/reference/types/datatypes/keypoint_pair.md +++ b/docs/content/reference/types/datatypes/keypoint_pair.md @@ -7,22 +7,22 @@ A connection between two [`datatypes.KeypointId`](https://rerun.io/docs/referenc ## Fields #### `keypoint0` -Type: [`KeypointId`](../datatypes/keypoint_id.md) +Type: non-null [`KeypointId`](../datatypes/keypoint_id.md) The first point of the pair. #### `keypoint1` -Type: [`KeypointId`](../datatypes/keypoint_id.md) +Type: non-null [`KeypointId`](../datatypes/keypoint_id.md) The second point of the pair. ## Arrow datatype ``` -Struct { - keypoint0: uint16 - keypoint1: uint16 -} +Struct( + "keypoint0": non-null UInt16 + "keypoint1": non-null UInt16 +) ``` ## API reference links diff --git a/docs/content/reference/types/datatypes/mat3x3.md b/docs/content/reference/types/datatypes/mat3x3.md index 341f0a00479d..1807f50ac829 100644 --- a/docs/content/reference/types/datatypes/mat3x3.md +++ b/docs/content/reference/types/datatypes/mat3x3.md @@ -17,7 +17,7 @@ row 2 | flat_columns[2] flat_columns[5] flat_columns[8] ## Arrow datatype ``` -FixedSizeList<9, float32> +FixedSizeList(9 x non-null Float32) ``` ## API reference links diff --git a/docs/content/reference/types/datatypes/mat4x4.md b/docs/content/reference/types/datatypes/mat4x4.md index fbf9af313114..06d769e22545 100644 --- a/docs/content/reference/types/datatypes/mat4x4.md +++ b/docs/content/reference/types/datatypes/mat4x4.md @@ -18,7 +18,7 @@ row 3 | flat_columns[3] flat_columns[7] flat_columns[11] flat_columns[15] ## Arrow datatype ``` -FixedSizeList<16, float32> +FixedSizeList(16 x non-null Float32) ``` ## API reference links diff --git a/docs/content/reference/types/datatypes/pixel_format.md b/docs/content/reference/types/datatypes/pixel_format.md index 9fefad7c16bc..cc903b5bdbca 100644 --- a/docs/content/reference/types/datatypes/pixel_format.md +++ b/docs/content/reference/types/datatypes/pixel_format.md @@ -99,7 +99,7 @@ the horizontal resolution of the Y plane. ## Arrow datatype ``` -uint8 +UInt8 ``` ## API reference links diff --git a/docs/content/reference/types/datatypes/plane3d.md b/docs/content/reference/types/datatypes/plane3d.md index 94cb6e572b38..83f76980fafa 100644 --- a/docs/content/reference/types/datatypes/plane3d.md +++ b/docs/content/reference/types/datatypes/plane3d.md @@ -16,7 +16,7 @@ I.e. the plane with xyz = (2, 0, 0), d = 1 is equivalent to xyz = (1, 0, 0), d = ## Arrow datatype ``` -FixedSizeList<4, float32> +FixedSizeList(4 x non-null Float32) ``` ## API reference links diff --git a/docs/content/reference/types/datatypes/quaternion.md b/docs/content/reference/types/datatypes/quaternion.md index 069032c0474d..b09d0be384fb 100644 --- a/docs/content/reference/types/datatypes/quaternion.md +++ b/docs/content/reference/types/datatypes/quaternion.md @@ -11,7 +11,7 @@ datastore as provided, when used in the Viewer Quaternions will always be normal ## Arrow datatype ``` -FixedSizeList<4, float32> +FixedSizeList(4 x non-null Float32) ``` ## API reference links diff --git a/docs/content/reference/types/datatypes/range1d.md b/docs/content/reference/types/datatypes/range1d.md index 1224a346a294..7458ad59104d 100644 --- a/docs/content/reference/types/datatypes/range1d.md +++ b/docs/content/reference/types/datatypes/range1d.md @@ -8,7 +8,7 @@ A 1D range, specifying a lower and upper bound. ## Arrow datatype ``` -FixedSizeList<2, float64> +FixedSizeList(2 x non-null Float64) ``` ## API reference links diff --git a/docs/content/reference/types/datatypes/range2d.md b/docs/content/reference/types/datatypes/range2d.md index 1eec1a3e6f8c..c1077dd642cb 100644 --- a/docs/content/reference/types/datatypes/range2d.md +++ b/docs/content/reference/types/datatypes/range2d.md @@ -7,22 +7,22 @@ An Axis-Aligned Bounding Box in 2D space, implemented as the minimum and maximum ## Fields #### `x_range` -Type: [`Range1D`](../datatypes/range1d.md) +Type: non-null [`Range1D`](../datatypes/range1d.md) The range of the X-axis (usually left and right bounds). #### `y_range` -Type: [`Range1D`](../datatypes/range1d.md) +Type: non-null [`Range1D`](../datatypes/range1d.md) The range of the Y-axis (usually top and bottom bounds). ## Arrow datatype ``` -Struct { - x_range: FixedSizeList<2, float64> - y_range: FixedSizeList<2, float64> -} +Struct( + "x_range": non-null FixedSizeList(2 x non-null Float64) + "y_range": non-null FixedSizeList(2 x non-null Float64) +) ``` ## API reference links diff --git a/docs/content/reference/types/datatypes/rgba32.md b/docs/content/reference/types/datatypes/rgba32.md index 3487f55895fa..ac6cd53c4faa 100644 --- a/docs/content/reference/types/datatypes/rgba32.md +++ b/docs/content/reference/types/datatypes/rgba32.md @@ -11,7 +11,7 @@ byte is `R` and the least significant byte is `A`. ## Arrow datatype ``` -uint32 +UInt32 ``` ## API reference links diff --git a/docs/content/reference/types/datatypes/rotation_axis_angle.md b/docs/content/reference/types/datatypes/rotation_axis_angle.md index af1de2c2c25f..204659ef6172 100644 --- a/docs/content/reference/types/datatypes/rotation_axis_angle.md +++ b/docs/content/reference/types/datatypes/rotation_axis_angle.md @@ -7,7 +7,7 @@ title: "RotationAxisAngle" ## Fields #### `axis` -Type: [`Vec3D`](../datatypes/vec3d.md) +Type: non-null [`Vec3D`](../datatypes/vec3d.md) Axis to rotate around. @@ -17,17 +17,17 @@ the rotation is treated as an invalid transform, unless the angle is zero in whi it is treated as an identity. #### `angle` -Type: [`Angle`](../datatypes/angle.md) +Type: non-null [`Angle`](../datatypes/angle.md) How much to rotate around the axis. ## Arrow datatype ``` -Struct { - axis: FixedSizeList<3, float32> - angle: float32 -} +Struct( + "axis": non-null FixedSizeList(3 x non-null Float32) + "angle": non-null Float32 +) ``` ## API reference links diff --git a/docs/content/reference/types/datatypes/tensor_buffer.md b/docs/content/reference/types/datatypes/tensor_buffer.md index 6f64e1bfd0a1..c978cf57e4f1 100644 --- a/docs/content/reference/types/datatypes/tensor_buffer.md +++ b/docs/content/reference/types/datatypes/tensor_buffer.md @@ -9,77 +9,77 @@ Tensor elements are stored in a contiguous buffer of a single type. ## Variants #### `U8` = 1 -Type: List of `uint8` +Type: non-null List of `UInt8` 8bit unsigned integer. #### `U16` = 2 -Type: List of `uint16` +Type: non-null List of `UInt16` 16bit unsigned integer. #### `U32` = 3 -Type: List of `uint32` +Type: non-null List of `UInt32` 32bit unsigned integer. #### `U64` = 4 -Type: List of `uint64` +Type: non-null List of `UInt64` 64bit unsigned integer. #### `I8` = 5 -Type: List of `int8` +Type: non-null List of `Int8` 8bit signed integer. #### `I16` = 6 -Type: List of `int16` +Type: non-null List of `Int16` 16bit signed integer. #### `I32` = 7 -Type: List of `int32` +Type: non-null List of `Int32` 32bit signed integer. #### `I64` = 8 -Type: List of `int64` +Type: non-null List of `Int64` 64bit signed integer. #### `F16` = 9 -Type: List of `float16` +Type: non-null List of `Float16` 16bit IEEE-754 floating point, also known as `half`. #### `F32` = 10 -Type: List of `float32` +Type: non-null List of `Float32` 32bit IEEE-754 floating point, also known as `float` or `single`. #### `F64` = 11 -Type: List of `float64` +Type: non-null List of `Float64` 64bit IEEE-754 floating point, also known as `double`. ## Arrow datatype ``` -DenseUnion { - 0 = "_null_markers": nullable null - 1 = "U8": List - 2 = "U16": List - 3 = "U32": List - 4 = "U64": List - 5 = "I8": List - 6 = "I16": List - 7 = "I32": List - 8 = "I64": List - 9 = "F16": List - 10 = "F32": List - 11 = "F64": List -} +Union(Dense, + 0: ("_null_markers": Null) + 1: ("U8": non-null List(non-null UInt8)) + 2: ("U16": non-null List(non-null UInt16)) + 3: ("U32": non-null List(non-null UInt32)) + 4: ("U64": non-null List(non-null UInt64)) + 5: ("I8": non-null List(non-null Int8)) + 6: ("I16": non-null List(non-null Int16)) + 7: ("I32": non-null List(non-null Int32)) + 8: ("I64": non-null List(non-null Int64)) + 9: ("F16": non-null List(non-null Float16)) + 10: ("F32": non-null List(non-null Float32)) + 11: ("F64": non-null List(non-null Float64)) +) ``` ## API reference links diff --git a/docs/content/reference/types/datatypes/tensor_data.md b/docs/content/reference/types/datatypes/tensor_data.md index abc09ba17823..21cf0b1b1fd3 100644 --- a/docs/content/reference/types/datatypes/tensor_data.md +++ b/docs/content/reference/types/datatypes/tensor_data.md @@ -14,12 +14,12 @@ which stores a contiguous array of typed values. ## Fields #### `shape` -Type: List of `uint64` +Type: non-null List of `UInt64` The shape of the tensor, i.e. the length of each dimension. #### `names` -Type: nullable List of `utf8` +Type: List of `Utf8` The names of the dimensions of the tensor (optional). @@ -30,31 +30,31 @@ and some constructors may produce a warning or even an error. Example: `["height", "width", "channel", "batch"]`. #### `buffer` -Type: [`TensorBuffer`](../datatypes/tensor_buffer.md) +Type: non-null [`TensorBuffer`](../datatypes/tensor_buffer.md) The content/data. ## Arrow datatype ``` -Struct { - shape: List - names: nullable List - buffer: DenseUnion { - 0 = "_null_markers": nullable null - 1 = "U8": List - 2 = "U16": List - 3 = "U32": List - 4 = "U64": List - 5 = "I8": List - 6 = "I16": List - 7 = "I32": List - 8 = "I64": List - 9 = "F16": List - 10 = "F32": List - 11 = "F64": List - } -} +Struct( + "shape": non-null List(non-null UInt64) + "names": List(non-null Utf8) + "buffer": non-null Union(Dense, + 0: ("_null_markers": Null) + 1: ("U8": non-null List(non-null UInt8)) + 2: ("U16": non-null List(non-null UInt16)) + 3: ("U32": non-null List(non-null UInt32)) + 4: ("U64": non-null List(non-null UInt64)) + 5: ("I8": non-null List(non-null Int8)) + 6: ("I16": non-null List(non-null Int16)) + 7: ("I32": non-null List(non-null Int32)) + 8: ("I64": non-null List(non-null Int64)) + 9: ("F16": non-null List(non-null Float16)) + 10: ("F32": non-null List(non-null Float32)) + 11: ("F64": non-null List(non-null Float64)) + ) +) ``` ## API reference links diff --git a/docs/content/reference/types/datatypes/tensor_dimension_index_selection.md b/docs/content/reference/types/datatypes/tensor_dimension_index_selection.md index 556d1d42cf7a..02b9bc93b7df 100644 --- a/docs/content/reference/types/datatypes/tensor_dimension_index_selection.md +++ b/docs/content/reference/types/datatypes/tensor_dimension_index_selection.md @@ -9,22 +9,22 @@ Selecting `dimension=2` and `index=42` is similar to doing `tensor[:, :, 42, :, ## Fields #### `dimension` -Type: `uint32` +Type: non-null `UInt32` The dimension number to select. #### `index` -Type: `uint64` +Type: non-null `UInt64` The index along the dimension to use. ## Arrow datatype ``` -Struct { - dimension: uint32 - index: uint64 -} +Struct( + "dimension": non-null UInt32 + "index": non-null UInt64 +) ``` ## API reference links diff --git a/docs/content/reference/types/datatypes/tensor_dimension_selection.md b/docs/content/reference/types/datatypes/tensor_dimension_selection.md index 3e1c9a74f1fa..f6599a9933ce 100644 --- a/docs/content/reference/types/datatypes/tensor_dimension_selection.md +++ b/docs/content/reference/types/datatypes/tensor_dimension_selection.md @@ -7,22 +7,22 @@ Selection of a single tensor dimension. ## Fields #### `dimension` -Type: `uint32` +Type: non-null `UInt32` The dimension number to select. #### `invert` -Type: `boolean` +Type: non-null `Boolean` Invert the direction of the dimension. ## Arrow datatype ``` -Struct { - dimension: uint32 - invert: boolean -} +Struct( + "dimension": non-null UInt32 + "invert": non-null Boolean +) ``` ## API reference links diff --git a/docs/content/reference/types/datatypes/time_int.md b/docs/content/reference/types/datatypes/time_int.md index 22d9df1be5b9..1cb12c0bb8ad 100644 --- a/docs/content/reference/types/datatypes/time_int.md +++ b/docs/content/reference/types/datatypes/time_int.md @@ -8,7 +8,7 @@ A 64-bit number describing either nanoseconds OR sequence numbers. ## Arrow datatype ``` -int64 +Int64 ``` ## API reference links diff --git a/docs/content/reference/types/datatypes/time_range.md b/docs/content/reference/types/datatypes/time_range.md index d9b458533530..daef6bcdf50a 100644 --- a/docs/content/reference/types/datatypes/time_range.md +++ b/docs/content/reference/types/datatypes/time_range.md @@ -7,32 +7,32 @@ Visible time range bounds for a specific timeline. ## Fields #### `start` -Type: [`TimeRangeBoundary`](../datatypes/time_range_boundary.md) +Type: non-null [`TimeRangeBoundary`](../datatypes/time_range_boundary.md) Low time boundary for sequence timeline. #### `end` -Type: [`TimeRangeBoundary`](../datatypes/time_range_boundary.md) +Type: non-null [`TimeRangeBoundary`](../datatypes/time_range_boundary.md) High time boundary for sequence timeline. ## Arrow datatype ``` -Struct { - start: DenseUnion { - 0 = "_null_markers": nullable null - 1 = "CursorRelative": int64 - 2 = "Absolute": int64 - 3 = "Infinite": nullable null - } - end: DenseUnion { - 0 = "_null_markers": nullable null - 1 = "CursorRelative": int64 - 2 = "Absolute": int64 - 3 = "Infinite": nullable null - } -} +Struct( + "start": non-null Union(Dense, + 0: ("_null_markers": Null) + 1: ("CursorRelative": non-null Int64) + 2: ("Absolute": non-null Int64) + 3: ("Infinite": Null) + ) + "end": non-null Union(Dense, + 0: ("_null_markers": Null) + 1: ("CursorRelative": non-null Int64) + 2: ("Absolute": non-null Int64) + 3: ("Infinite": Null) + ) +) ``` ## API reference links diff --git a/docs/content/reference/types/datatypes/time_range_boundary.md b/docs/content/reference/types/datatypes/time_range_boundary.md index 0d7439fce19a..c62a1e9c4a82 100644 --- a/docs/content/reference/types/datatypes/time_range_boundary.md +++ b/docs/content/reference/types/datatypes/time_range_boundary.md @@ -7,12 +7,12 @@ Left or right boundary of a time range. ## Variants #### `CursorRelative` = 1 -Type: [`TimeInt`](../datatypes/time_int.md) +Type: non-null [`TimeInt`](../datatypes/time_int.md) Boundary is a value relative to the time cursor. #### `Absolute` = 2 -Type: [`TimeInt`](../datatypes/time_int.md) +Type: non-null [`TimeInt`](../datatypes/time_int.md) Boundary is an absolute value. @@ -24,12 +24,12 @@ The boundary extends to infinity. ## Arrow datatype ``` -DenseUnion { - 0 = "_null_markers": nullable null - 1 = "CursorRelative": int64 - 2 = "Absolute": int64 - 3 = "Infinite": nullable null -} +Union(Dense, + 0: ("_null_markers": Null) + 1: ("CursorRelative": non-null Int64) + 2: ("Absolute": non-null Int64) + 3: ("Infinite": Null) +) ``` ## API reference links diff --git a/docs/content/reference/types/datatypes/uint16.md b/docs/content/reference/types/datatypes/uint16.md index a330f9082a49..1a42c31d2050 100644 --- a/docs/content/reference/types/datatypes/uint16.md +++ b/docs/content/reference/types/datatypes/uint16.md @@ -8,7 +8,7 @@ A 16bit unsigned integer. ## Arrow datatype ``` -uint16 +UInt16 ``` ## API reference links diff --git a/docs/content/reference/types/datatypes/uint32.md b/docs/content/reference/types/datatypes/uint32.md index d06e19db59ca..841e823de801 100644 --- a/docs/content/reference/types/datatypes/uint32.md +++ b/docs/content/reference/types/datatypes/uint32.md @@ -8,7 +8,7 @@ A 32bit unsigned integer. ## Arrow datatype ``` -uint32 +UInt32 ``` ## API reference links diff --git a/docs/content/reference/types/datatypes/uint64.md b/docs/content/reference/types/datatypes/uint64.md index 87d24c3c87d6..7bf992ad1982 100644 --- a/docs/content/reference/types/datatypes/uint64.md +++ b/docs/content/reference/types/datatypes/uint64.md @@ -8,7 +8,7 @@ A 64bit unsigned integer. ## Arrow datatype ``` -uint64 +UInt64 ``` ## API reference links diff --git a/docs/content/reference/types/datatypes/utf8.md b/docs/content/reference/types/datatypes/utf8.md index 8b6224d7ce62..87fce0e2a1ef 100644 --- a/docs/content/reference/types/datatypes/utf8.md +++ b/docs/content/reference/types/datatypes/utf8.md @@ -8,7 +8,7 @@ A string of text, encoded as UTF-8. ## Arrow datatype ``` -utf8 +Utf8 ``` ## API reference links diff --git a/docs/content/reference/types/datatypes/utf8pair.md b/docs/content/reference/types/datatypes/utf8pair.md index 670b24e56adc..99ef8d33e5ca 100644 --- a/docs/content/reference/types/datatypes/utf8pair.md +++ b/docs/content/reference/types/datatypes/utf8pair.md @@ -7,22 +7,22 @@ Stores a tuple of UTF-8 strings. ## Fields #### `first` -Type: [`Utf8`](../datatypes/utf8.md) +Type: non-null [`Utf8`](../datatypes/utf8.md) The first string. #### `second` -Type: [`Utf8`](../datatypes/utf8.md) +Type: non-null [`Utf8`](../datatypes/utf8.md) The second string. ## Arrow datatype ``` -Struct { - first: utf8 - second: utf8 -} +Struct( + "first": non-null Utf8 + "second": non-null Utf8 +) ``` ## API reference links diff --git a/docs/content/reference/types/datatypes/uuid.md b/docs/content/reference/types/datatypes/uuid.md index 0f7c5e9388e4..86fdb4190a08 100644 --- a/docs/content/reference/types/datatypes/uuid.md +++ b/docs/content/reference/types/datatypes/uuid.md @@ -8,7 +8,7 @@ A 16-byte UUID. ## Arrow datatype ``` -FixedSizeList<16, uint8> +FixedSizeList(16 x non-null UInt8) ``` ## API reference links diff --git a/docs/content/reference/types/datatypes/uvec2d.md b/docs/content/reference/types/datatypes/uvec2d.md index 8968a2524064..50a1008b0fa1 100644 --- a/docs/content/reference/types/datatypes/uvec2d.md +++ b/docs/content/reference/types/datatypes/uvec2d.md @@ -8,7 +8,7 @@ A uint32 vector in 2D space. ## Arrow datatype ``` -FixedSizeList<2, uint32> +FixedSizeList(2 x non-null UInt32) ``` ## API reference links diff --git a/docs/content/reference/types/datatypes/uvec3d.md b/docs/content/reference/types/datatypes/uvec3d.md index 6bdeab36e78a..681142d9200c 100644 --- a/docs/content/reference/types/datatypes/uvec3d.md +++ b/docs/content/reference/types/datatypes/uvec3d.md @@ -8,7 +8,7 @@ A uint32 vector in 3D space. ## Arrow datatype ``` -FixedSizeList<3, uint32> +FixedSizeList(3 x non-null UInt32) ``` ## API reference links diff --git a/docs/content/reference/types/datatypes/uvec4d.md b/docs/content/reference/types/datatypes/uvec4d.md index d10fa19b7945..09c043ba8761 100644 --- a/docs/content/reference/types/datatypes/uvec4d.md +++ b/docs/content/reference/types/datatypes/uvec4d.md @@ -8,7 +8,7 @@ A uint vector in 4D space. ## Arrow datatype ``` -FixedSizeList<4, uint32> +FixedSizeList(4 x non-null UInt32) ``` ## API reference links diff --git a/docs/content/reference/types/datatypes/vec2d.md b/docs/content/reference/types/datatypes/vec2d.md index a99473ea036d..e8bc39667198 100644 --- a/docs/content/reference/types/datatypes/vec2d.md +++ b/docs/content/reference/types/datatypes/vec2d.md @@ -8,7 +8,7 @@ A vector in 2D space. ## Arrow datatype ``` -FixedSizeList<2, float32> +FixedSizeList(2 x non-null Float32) ``` ## API reference links diff --git a/docs/content/reference/types/datatypes/vec3d.md b/docs/content/reference/types/datatypes/vec3d.md index 1c7cb06b0454..677b2fd3479e 100644 --- a/docs/content/reference/types/datatypes/vec3d.md +++ b/docs/content/reference/types/datatypes/vec3d.md @@ -8,7 +8,7 @@ A vector in 3D space. ## Arrow datatype ``` -FixedSizeList<3, float32> +FixedSizeList(3 x non-null Float32) ``` ## API reference links diff --git a/docs/content/reference/types/datatypes/vec4d.md b/docs/content/reference/types/datatypes/vec4d.md index 9a5973716c70..cc078effb9db 100644 --- a/docs/content/reference/types/datatypes/vec4d.md +++ b/docs/content/reference/types/datatypes/vec4d.md @@ -8,7 +8,7 @@ A vector in 4D space. ## Arrow datatype ``` -FixedSizeList<4, float32> +FixedSizeList(4 x non-null Float32) ``` ## API reference links diff --git a/docs/content/reference/types/datatypes/video_timestamp.md b/docs/content/reference/types/datatypes/video_timestamp.md index 24751d2b96d2..793cf2c55906 100644 --- a/docs/content/reference/types/datatypes/video_timestamp.md +++ b/docs/content/reference/types/datatypes/video_timestamp.md @@ -11,7 +11,7 @@ Presentation timestamps are typically measured as time since video start. ## Arrow datatype ``` -int64 +Int64 ``` ## API reference links diff --git a/docs/content/reference/types/datatypes/view_coordinates.md b/docs/content/reference/types/datatypes/view_coordinates.md index 7871759da51c..f6027ed6b483 100644 --- a/docs/content/reference/types/datatypes/view_coordinates.md +++ b/docs/content/reference/types/datatypes/view_coordinates.md @@ -26,7 +26,7 @@ The following constants are used to represent the different directions: ## Arrow datatype ``` -FixedSizeList<3, uint8> +FixedSizeList(3 x non-null UInt8) ``` ## API reference links diff --git a/docs/content/reference/types/datatypes/visible_time_range.md b/docs/content/reference/types/datatypes/visible_time_range.md index 75b6f7a9bc01..4122673c5f76 100644 --- a/docs/content/reference/types/datatypes/visible_time_range.md +++ b/docs/content/reference/types/datatypes/visible_time_range.md @@ -7,35 +7,35 @@ Visible time range bounds for a specific timeline. ## Fields #### `timeline` -Type: [`Utf8`](../datatypes/utf8.md) +Type: non-null [`Utf8`](../datatypes/utf8.md) Name of the timeline this applies to. #### `range` -Type: [`TimeRange`](../datatypes/time_range.md) +Type: non-null [`TimeRange`](../datatypes/time_range.md) Time range to use for this timeline. ## Arrow datatype ``` -Struct { - timeline: utf8 - range: Struct { - start: DenseUnion { - 0 = "_null_markers": nullable null - 1 = "CursorRelative": int64 - 2 = "Absolute": int64 - 3 = "Infinite": nullable null - } - end: DenseUnion { - 0 = "_null_markers": nullable null - 1 = "CursorRelative": int64 - 2 = "Absolute": int64 - 3 = "Infinite": nullable null - } - } -} +Struct( + "timeline": non-null Utf8 + "range": non-null Struct( + "start": non-null Union(Dense, + 0: ("_null_markers": Null) + 1: ("CursorRelative": non-null Int64) + 2: ("Absolute": non-null Int64) + 3: ("Infinite": Null) + ) + "end": non-null Union(Dense, + 0: ("_null_markers": Null) + 1: ("CursorRelative": non-null Int64) + 2: ("Absolute": non-null Int64) + 3: ("Infinite": Null) + ) + ) +) ``` ## API reference links diff --git a/pixi.toml b/pixi.toml index eec827c08686..d53797ab1c40 100644 --- a/pixi.toml +++ b/pixi.toml @@ -324,8 +324,8 @@ meilisearch = "meilisearch --db-path=./meilisearch/data.ms --dump-dir=./meilisea # Update the results of `insta` and `kittest` snapshot regression tests rs-update-snapshot-tests = "INSTA_UPDATE=always UPDATE_SNAPSHOTS=1 cargo nextest run --all-targets --all-features --cargo-quiet --no-fail-fast" -# Update the Python-based snapshot tests -py-update-snapshot-tests = { cmd = "uv run pytest --inline-snapshot=fix", depends-on = [ +# Update the Python-based snapshot tests (inline-snapshot for inline snapshots, --snapshot-update for syrupy .ambr files) +py-update-snapshot-tests = { cmd = "uv run pytest -vv rerun_py/tests --inline-snapshot=fix --snapshot-update", depends-on = [ "py-build", ] } diff --git a/rerun_py/tests/api_sandbox/test_current/test_catalog_basics.py b/rerun_py/tests/api_sandbox/test_current/test_catalog_basics.py index a04d16a6e25c..9b7d4e64b7d4 100644 --- a/rerun_py/tests/api_sandbox/test_current/test_catalog_basics.py +++ b/rerun_py/tests/api_sandbox/test_current/test_catalog_basics.py @@ -49,21 +49,21 @@ def test_catalog_basics(tmp_path: Path) -> None: df.drop("id", "created_at", "updated_at").filter(col("entry_kind") != 5).sort("name") ) == inline_snapshot( """\ -┌─────────────────────────────┐ -│ METADATA: │ -│ * version: 0.1.3 │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ ┌────────────┬────────────┐ │ -│ │ name ┆ entry_kind │ │ -│ │ --- ┆ --- │ │ -│ │ type: Utf8 ┆ type: i32 │ │ -│ ╞════════════╪════════════╡ │ -│ │ __entries ┆ 3 │ │ -│ ├╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┤ │ -│ │ my_dataset ┆ 1 │ │ -│ ├╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┤ │ -│ │ my_table ┆ 3 │ │ -│ └────────────┴────────────┘ │ -└─────────────────────────────┘\ +┌────────────────────────────────────────────────┐ +│ METADATA: │ +│ * version: 0.1.3 │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ ┌─────────────────────┬──────────────────────┐ │ +│ │ name ┆ entry_kind │ │ +│ │ --- ┆ --- │ │ +│ │ type: non-null Utf8 ┆ type: non-null Int32 │ │ +│ ╞═════════════════════╪══════════════════════╡ │ +│ │ __entries ┆ 3 │ │ +│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ +│ │ my_dataset ┆ 1 │ │ +│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ +│ │ my_table ┆ 3 │ │ +│ └─────────────────────┴──────────────────────┘ │ +└────────────────────────────────────────────────┘\ """ ) diff --git a/rerun_py/tests/api_sandbox/test_current/test_dataframe_api.py b/rerun_py/tests/api_sandbox/test_current/test_dataframe_api.py index 83eaefe84cf7..eaf484261c89 100644 --- a/rerun_py/tests/api_sandbox/test_current/test_dataframe_api.py +++ b/rerun_py/tests/api_sandbox/test_current/test_dataframe_api.py @@ -21,25 +21,25 @@ def test_dataframe_api_filter_segment_id(simple_dataset_prefix: Path) -> None: df = view.reader(index="timeline").sort("rerun_segment_id") assert str(df) == inline_snapshot("""\ -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ METADATA: │ -│ * version: 0.1.3 │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ ┌────────────────────┬──────────────────────────────┬───────────────────────────────────┬─────────────────────────────────────────────────────┐ │ -│ │ rerun_segment_id ┆ timeline ┆ /points:Points2D:colors ┆ /points:Points2D:positions │ │ -│ │ --- ┆ --- ┆ --- ┆ --- │ │ -│ │ type: Utf8 ┆ type: nullable Timestamp(ns) ┆ type: nullable List[nullable u32] ┆ type: nullable List[nullable FixedSizeList[f32; 2]] │ │ -│ │ ┆ index_name: timeline ┆ archetype: Points2D ┆ archetype: Points2D │ │ -│ │ ┆ kind: index ┆ component: Points2D:colors ┆ component: Points2D:positions │ │ -│ │ ┆ ┆ component_type: Color ┆ component_type: Position2D │ │ -│ │ ┆ ┆ entity_path: /points ┆ entity_path: /points │ │ -│ │ ┆ ┆ kind: data ┆ kind: data │ │ -│ ╞════════════════════╪══════════════════════════════╪═══════════════════════════════════╪═════════════════════════════════════════════════════╡ │ -│ │ simple_recording_0 ┆ 2000-01-01T00:00:00 ┆ [4278190335, 16711935] ┆ [[0.0, 1.0], [3.0, 4.0]] │ │ -│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ -│ │ simple_recording_2 ┆ 2000-01-01T00:00:02 ┆ [4278190847, 16712447] ┆ [[2.0, 3.0], [5.0, 6.0]] │ │ -│ └────────────────────┴──────────────────────────────┴───────────────────────────────────┴─────────────────────────────────────────────────────┘ │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘\ +┌───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ METADATA: │ +│ * version: 0.1.3 │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ ┌─────────────────────┬──────────────────────┬────────────────────────────┬─────────────────────────────────────────────────┐ │ +│ │ rerun_segment_id ┆ timeline ┆ /points:Points2D:colors ┆ /points:Points2D:positions │ │ +│ │ --- ┆ --- ┆ --- ┆ --- │ │ +│ │ type: non-null Utf8 ┆ type: Timestamp(ns) ┆ type: List(UInt32) ┆ type: List(FixedSizeList(2 x non-null Float32)) │ │ +│ │ ┆ index_name: timeline ┆ archetype: Points2D ┆ archetype: Points2D │ │ +│ │ ┆ kind: index ┆ component: Points2D:colors ┆ component: Points2D:positions │ │ +│ │ ┆ ┆ component_type: Color ┆ component_type: Position2D │ │ +│ │ ┆ ┆ entity_path: /points ┆ entity_path: /points │ │ +│ │ ┆ ┆ kind: data ┆ kind: data │ │ +│ ╞═════════════════════╪══════════════════════╪════════════════════════════╪═════════════════════════════════════════════════╡ │ +│ │ simple_recording_0 ┆ 2000-01-01T00:00:00 ┆ [4278190335, 16711935] ┆ [[0.0, 1.0], [3.0, 4.0]] │ │ +│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ +│ │ simple_recording_2 ┆ 2000-01-01T00:00:02 ┆ [4278190847, 16712447] ┆ [[2.0, 3.0], [5.0, 6.0]] │ │ +│ └─────────────────────┴──────────────────────┴────────────────────────────┴─────────────────────────────────────────────────┘ │ +└───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘\ """) table = df.to_arrow_table() diff --git a/rerun_py/tests/api_sandbox/test_current/test_dataset_basics.py b/rerun_py/tests/api_sandbox/test_current/test_dataset_basics.py index 5abd199b7e91..694163171897 100644 --- a/rerun_py/tests/api_sandbox/test_current/test_dataset_basics.py +++ b/rerun_py/tests/api_sandbox/test_current/test_dataset_basics.py @@ -43,25 +43,25 @@ def test_dataset_basics(complex_dataset_prefix: Path) -> None: "rerun_segment_id" ) ) == inline_snapshot("""\ -┌─────────────────────┬───────────────────┬──────────────────┬──────────────────┬──────────────────────────────┬──────────────────────────────┐ -│ rerun_segment_id ┆ rerun_layer_names ┆ rerun_num_chunks ┆ rerun_size_bytes ┆ timeline:end ┆ timeline:start │ -│ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- │ -│ type: Utf8 ┆ type: List[Utf8] ┆ type: u64 ┆ type: u64 ┆ type: nullable Timestamp(ns) ┆ type: nullable Timestamp(ns) │ -│ ┆ ┆ ┆ ┆ index: timeline ┆ index: timeline │ -│ ┆ ┆ ┆ ┆ index_kind: timestamp ┆ index_kind: timestamp │ -│ ┆ ┆ ┆ ┆ index_marker: end ┆ index_marker: start │ -│ ┆ ┆ ┆ ┆ kind: index ┆ kind: index │ -╞═════════════════════╪═══════════════════╪══════════════════╪══════════════════╪══════════════════════════════╪══════════════════════════════╡ -│ complex_recording_0 ┆ [base] ┆ 3 ┆ 4226 ┆ 2000-01-01T00:00:02 ┆ 2000-01-01T00:00:00 │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ complex_recording_1 ┆ [base] ┆ 3 ┆ 4226 ┆ 2000-01-01T00:00:03 ┆ 2000-01-01T00:00:01 │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ complex_recording_2 ┆ [base] ┆ 3 ┆ 4226 ┆ 2000-01-01T00:00:04 ┆ 2000-01-01T00:00:02 │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ complex_recording_3 ┆ [base] ┆ 3 ┆ 4226 ┆ 2000-01-01T00:00:05 ┆ 2000-01-01T00:00:03 │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ complex_recording_4 ┆ [base] ┆ 3 ┆ 4226 ┆ 2000-01-01T00:00:06 ┆ 2000-01-01T00:00:04 │ -└─────────────────────┴───────────────────┴──────────────────┴──────────────────┴──────────────────────────────┴──────────────────────────────┘\ +┌─────────────────────┬────────────────────────────────────────────────────────────────┬───────────────────────┬───────────────────────┬───────────────────────┬───────────────────────┐ +│ rerun_segment_id ┆ rerun_layer_names ┆ rerun_num_chunks ┆ rerun_size_bytes ┆ timeline:end ┆ timeline:start │ +│ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- │ +│ type: non-null Utf8 ┆ type: non-null List(non-null Utf8, field: 'rerun_layer_names') ┆ type: non-null UInt64 ┆ type: non-null UInt64 ┆ type: Timestamp(ns) ┆ type: Timestamp(ns) │ +│ ┆ ┆ ┆ ┆ index: timeline ┆ index: timeline │ +│ ┆ ┆ ┆ ┆ index_kind: timestamp ┆ index_kind: timestamp │ +│ ┆ ┆ ┆ ┆ index_marker: end ┆ index_marker: start │ +│ ┆ ┆ ┆ ┆ kind: index ┆ kind: index │ +╞═════════════════════╪════════════════════════════════════════════════════════════════╪═══════════════════════╪═══════════════════════╪═══════════════════════╪═══════════════════════╡ +│ complex_recording_0 ┆ [base] ┆ 3 ┆ 4226 ┆ 2000-01-01T00:00:02 ┆ 2000-01-01T00:00:00 │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ complex_recording_1 ┆ [base] ┆ 3 ┆ 4226 ┆ 2000-01-01T00:00:03 ┆ 2000-01-01T00:00:01 │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ complex_recording_2 ┆ [base] ┆ 3 ┆ 4226 ┆ 2000-01-01T00:00:04 ┆ 2000-01-01T00:00:02 │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ complex_recording_3 ┆ [base] ┆ 3 ┆ 4226 ┆ 2000-01-01T00:00:05 ┆ 2000-01-01T00:00:03 │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ complex_recording_4 ┆ [base] ┆ 3 ┆ 4226 ┆ 2000-01-01T00:00:06 ┆ 2000-01-01T00:00:04 │ +└─────────────────────┴────────────────────────────────────────────────────────────────┴───────────────────────┴───────────────────────┴───────────────────────┴───────────────────────┘\ """) @@ -121,17 +121,17 @@ def test_dataset_metadata(complex_dataset_prefix: Path, tmp_path: Path) -> None: ) assert (str(meta.reader())) == inline_snapshot("""\ -┌─────────────────────┬─────────────────────┐ -│ rerun_segment_id ┆ success │ -│ --- ┆ --- │ -│ type: nullable Utf8 ┆ type: nullable bool │ -╞═════════════════════╪═════════════════════╡ -│ complex_recording_0 ┆ true │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ complex_recording_1 ┆ false │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ complex_recording_4 ┆ true │ -└─────────────────────┴─────────────────────┘\ +┌─────────────────────┬───────────────┐ +│ rerun_segment_id ┆ success │ +│ --- ┆ --- │ +│ type: Utf8 ┆ type: Boolean │ +╞═════════════════════╪═══════════════╡ +│ complex_recording_0 ┆ true │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ complex_recording_1 ┆ false │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ complex_recording_4 ┆ true │ +└─────────────────────┴───────────────┘\ """) diff --git a/rerun_py/tests/api_sandbox/test_current/test_table_api.py b/rerun_py/tests/api_sandbox/test_current/test_table_api.py index 81a68b126db8..af8b81545df0 100644 --- a/rerun_py/tests/api_sandbox/test_current/test_table_api.py +++ b/rerun_py/tests/api_sandbox/test_current/test_table_api.py @@ -45,15 +45,15 @@ def test_table_api(tmp_path_factory: pytest.TempPathFactory) -> None: ) assert str(df.sort("rerun_segment_id")) == inline_snapshot("""\ -┌─────────────────────┬─────────────────────┐ -│ rerun_segment_id ┆ operator │ -│ --- ┆ --- │ -│ type: nullable Utf8 ┆ type: nullable Utf8 │ -╞═════════════════════╪═════════════════════╡ -│ segment_001 ┆ alice │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ segment_002 ┆ bob │ -└─────────────────────┴─────────────────────┘\ +┌──────────────────┬────────────┐ +│ rerun_segment_id ┆ operator │ +│ --- ┆ --- │ +│ type: Utf8 ┆ type: Utf8 │ +╞══════════════════╪════════════╡ +│ segment_001 ┆ alice │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ segment_002 ┆ bob │ +└──────────────────┴────────────┘\ """) table.append( @@ -62,17 +62,17 @@ def test_table_api(tmp_path_factory: pytest.TempPathFactory) -> None: ) assert str(df.sort("rerun_segment_id")) == inline_snapshot("""\ -┌─────────────────────┬─────────────────────┐ -│ rerun_segment_id ┆ operator │ -│ --- ┆ --- │ -│ type: nullable Utf8 ┆ type: nullable Utf8 │ -╞═════════════════════╪═════════════════════╡ -│ segment_001 ┆ alice │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ segment_002 ┆ bob │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ segment_003 ┆ carol │ -└─────────────────────┴─────────────────────┘\ +┌──────────────────┬────────────┐ +│ rerun_segment_id ┆ operator │ +│ --- ┆ --- │ +│ type: Utf8 ┆ type: Utf8 │ +╞══════════════════╪════════════╡ +│ segment_001 ┆ alice │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ segment_002 ┆ bob │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ segment_003 ┆ carol │ +└──────────────────┴────────────┘\ """) assert str(df) == str(client.ctx.table("my_table")) diff --git a/rerun_py/tests/api_sandbox/test_current/test_table_basics.py b/rerun_py/tests/api_sandbox/test_current/test_table_basics.py index f01645dc0779..ec52801be40c 100644 --- a/rerun_py/tests/api_sandbox/test_current/test_table_basics.py +++ b/rerun_py/tests/api_sandbox/test_current/test_table_basics.py @@ -34,19 +34,19 @@ def test_create_table_and_append(tmp_path: Path) -> None: df = table.reader() assert str(df.sort("id")) == inline_snapshot("""\ -┌────────────────────┬────────────────────┬─────────────────────┐ -│ id ┆ value ┆ enabled │ -│ --- ┆ --- ┆ --- │ -│ type: nullable i32 ┆ type: nullable f64 ┆ type: nullable bool │ -╞════════════════════╪════════════════════╪═════════════════════╡ -│ 1 ┆ 10.5 ┆ true │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ 2 ┆ 20.3 ┆ false │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ 3 ┆ 15.7 ┆ true │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ 4 ┆ 30.2 ┆ false │ -└────────────────────┴────────────────────┴─────────────────────┘\ +┌─────────────┬───────────────┬───────────────┐ +│ id ┆ value ┆ enabled │ +│ --- ┆ --- ┆ --- │ +│ type: Int32 ┆ type: Float64 ┆ type: Boolean │ +╞═════════════╪═══════════════╪═══════════════╡ +│ 1 ┆ 10.5 ┆ true │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ 2 ┆ 20.3 ┆ false │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ 3 ┆ 15.7 ┆ true │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ 4 ┆ 30.2 ┆ false │ +└─────────────┴───────────────┴───────────────┘\ """) @@ -79,23 +79,23 @@ def test_write_table_with_record_batches(tmp_path: Path) -> None: df = client.get_table(name="scores_table").reader() assert str(df.sort("id")) == inline_snapshot("""\ -┌────────────────────┬─────────────────────┬────────────────────┐ -│ id ┆ enabled ┆ score │ -│ --- ┆ --- ┆ --- │ -│ type: nullable i32 ┆ type: nullable bool ┆ type: nullable f64 │ -╞════════════════════╪═════════════════════╪════════════════════╡ -│ 1 ┆ true ┆ 95.5 │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ 2 ┆ false ┆ 87.3 │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ 3 ┆ true ┆ 91.2 │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ 4 ┆ true ┆ 88.7 │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ 5 ┆ true ┆ 93.1 │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ 6 ┆ false ┆ 85.4 │ -└────────────────────┴─────────────────────┴────────────────────┘\ +┌─────────────┬───────────────┬───────────────┐ +│ id ┆ enabled ┆ score │ +│ --- ┆ --- ┆ --- │ +│ type: Int32 ┆ type: Boolean ┆ type: Float64 │ +╞═════════════╪═══════════════╪═══════════════╡ +│ 1 ┆ true ┆ 95.5 │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ 2 ┆ false ┆ 87.3 │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ 3 ┆ true ┆ 91.2 │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ 4 ┆ true ┆ 88.7 │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ 5 ┆ true ┆ 93.1 │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ 6 ┆ false ┆ 85.4 │ +└─────────────┴───────────────┴───────────────┘\ """) @@ -115,17 +115,17 @@ def test_table_overwrite_mode(tmp_path: Path) -> None: df_after_append = client.get_table(name="data_table").reader() assert str(df_after_append.sort("id")) == inline_snapshot("""\ -┌────────────────────┬─────────────────────┐ -│ id ┆ category │ -│ --- ┆ --- │ -│ type: nullable i32 ┆ type: nullable Utf8 │ -╞════════════════════╪═════════════════════╡ -│ 1 ┆ A │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ 2 ┆ B │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ 3 ┆ C │ -└────────────────────┴─────────────────────┘\ +┌─────────────┬────────────┐ +│ id ┆ category │ +│ --- ┆ --- │ +│ type: Int32 ┆ type: Utf8 │ +╞═════════════╪════════════╡ +│ 1 ┆ A │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ 2 ┆ B │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ 3 ┆ C │ +└─────────────┴────────────┘\ """) # Overwrite with new data @@ -135,15 +135,15 @@ def test_table_overwrite_mode(tmp_path: Path) -> None: df_after_overwrite = client.get_table(name="data_table").reader() assert str(df_after_overwrite.sort("id")) == inline_snapshot("""\ -┌────────────────────┬─────────────────────┐ -│ id ┆ category │ -│ --- ┆ --- │ -│ type: nullable i32 ┆ type: nullable Utf8 │ -╞════════════════════╪═════════════════════╡ -│ 10 ┆ X │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ 20 ┆ Y │ -└────────────────────┴─────────────────────┘\ +┌─────────────┬────────────┐ +│ id ┆ category │ +│ --- ┆ --- │ +│ type: Int32 ┆ type: Utf8 │ +╞═════════════╪════════════╡ +│ 10 ┆ X │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ 20 ┆ Y │ +└─────────────┴────────────┘\ """) @@ -163,32 +163,32 @@ def test_read_table(tmp_path: Path) -> None: table_entry = client.get_table(name="products") df1 = table_entry.reader() assert str(df1.sort("product_id")) == inline_snapshot("""\ -┌────────────────────┬────────────────────┐ -│ product_id ┆ price │ -│ --- ┆ --- │ -│ type: nullable i32 ┆ type: nullable f64 │ -╞════════════════════╪════════════════════╡ -│ 101 ┆ 29.99 │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ 102 ┆ 49.99 │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ 103 ┆ 19.99 │ -└────────────────────┴────────────────────┘\ +┌─────────────┬───────────────┐ +│ product_id ┆ price │ +│ --- ┆ --- │ +│ type: Int32 ┆ type: Float64 │ +╞═════════════╪═══════════════╡ +│ 101 ┆ 29.99 │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ 102 ┆ 49.99 │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ 103 ┆ 19.99 │ +└─────────────┴───────────────┘\ """) # Method 2: via DataFusion SessionContext - useful for SQL queries ctx = client.ctx df2 = ctx.table("products") assert str(df2.sort("product_id")) == inline_snapshot("""\ -┌────────────────────┬────────────────────┐ -│ product_id ┆ price │ -│ --- ┆ --- │ -│ type: nullable i32 ┆ type: nullable f64 │ -╞════════════════════╪════════════════════╡ -│ 101 ┆ 29.99 │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ 102 ┆ 49.99 │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ 103 ┆ 19.99 │ -└────────────────────┴────────────────────┘\ +┌─────────────┬───────────────┐ +│ product_id ┆ price │ +│ --- ┆ --- │ +│ type: Int32 ┆ type: Float64 │ +╞═════════════╪═══════════════╡ +│ 101 ┆ 29.99 │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ 102 ┆ 49.99 │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ 103 ┆ 19.99 │ +└─────────────┴───────────────┘\ """) diff --git a/rerun_py/tests/api_sandbox/test_draft/test_dataframe_api.py b/rerun_py/tests/api_sandbox/test_draft/test_dataframe_api.py index 5076bcd61f29..111c9583e42a 100644 --- a/rerun_py/tests/api_sandbox/test_draft/test_dataframe_api.py +++ b/rerun_py/tests/api_sandbox/test_draft/test_dataframe_api.py @@ -18,25 +18,25 @@ def test_dataframe_api_filter_segment_id(basic_dataset: DatasetEntry) -> None: df = view.sort("rerun_segment_id") assert str(df) == inline_snapshot("""\ -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ METADATA: │ -│ * version: 0.1.3 │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ ┌────────────────────┬──────────────────────────────┬───────────────────────────────────┬─────────────────────────────────────────────────────┐ │ -│ │ rerun_segment_id ┆ timeline ┆ /points:Points2D:colors ┆ /points:Points2D:positions │ │ -│ │ --- ┆ --- ┆ --- ┆ --- │ │ -│ │ type: Utf8 ┆ type: nullable Timestamp(ns) ┆ type: nullable List[nullable u32] ┆ type: nullable List[nullable FixedSizeList[f32; 2]] │ │ -│ │ ┆ index_name: timeline ┆ archetype: Points2D ┆ archetype: Points2D │ │ -│ │ ┆ kind: index ┆ component: Points2D:colors ┆ component: Points2D:positions │ │ -│ │ ┆ ┆ component_type: Color ┆ component_type: Position2D │ │ -│ │ ┆ ┆ entity_path: /points ┆ entity_path: /points │ │ -│ │ ┆ ┆ kind: data ┆ kind: data │ │ -│ ╞════════════════════╪══════════════════════════════╪═══════════════════════════════════╪═════════════════════════════════════════════════════╡ │ -│ │ simple_recording_0 ┆ 2000-01-01T00:00:00 ┆ [4278190335, 16711935] ┆ [[0.0, 1.0], [3.0, 4.0]] │ │ -│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ -│ │ simple_recording_2 ┆ 2000-01-01T00:00:02 ┆ [4278190847, 16712447] ┆ [[2.0, 3.0], [5.0, 6.0]] │ │ -│ └────────────────────┴──────────────────────────────┴───────────────────────────────────┴─────────────────────────────────────────────────────┘ │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘\ +┌───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ METADATA: │ +│ * version: 0.1.3 │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ ┌─────────────────────┬──────────────────────┬────────────────────────────┬─────────────────────────────────────────────────┐ │ +│ │ rerun_segment_id ┆ timeline ┆ /points:Points2D:colors ┆ /points:Points2D:positions │ │ +│ │ --- ┆ --- ┆ --- ┆ --- │ │ +│ │ type: non-null Utf8 ┆ type: Timestamp(ns) ┆ type: List(UInt32) ┆ type: List(FixedSizeList(2 x non-null Float32)) │ │ +│ │ ┆ index_name: timeline ┆ archetype: Points2D ┆ archetype: Points2D │ │ +│ │ ┆ kind: index ┆ component: Points2D:colors ┆ component: Points2D:positions │ │ +│ │ ┆ ┆ component_type: Color ┆ component_type: Position2D │ │ +│ │ ┆ ┆ entity_path: /points ┆ entity_path: /points │ │ +│ │ ┆ ┆ kind: data ┆ kind: data │ │ +│ ╞═════════════════════╪══════════════════════╪════════════════════════════╪═════════════════════════════════════════════════╡ │ +│ │ simple_recording_0 ┆ 2000-01-01T00:00:00 ┆ [4278190335, 16711935] ┆ [[0.0, 1.0], [3.0, 4.0]] │ │ +│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ +│ │ simple_recording_2 ┆ 2000-01-01T00:00:02 ┆ [4278190847, 16712447] ┆ [[2.0, 3.0], [5.0, 6.0]] │ │ +│ └─────────────────────┴──────────────────────┴────────────────────────────┴─────────────────────────────────────────────────┘ │ +└───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘\ """) table = df.to_arrow_table() diff --git a/rerun_py/tests/api_sandbox/test_draft/test_dataset_basics.py b/rerun_py/tests/api_sandbox/test_draft/test_dataset_basics.py index 9b7793b78954..f94b82ee2326 100644 --- a/rerun_py/tests/api_sandbox/test_draft/test_dataset_basics.py +++ b/rerun_py/tests/api_sandbox/test_draft/test_dataset_basics.py @@ -53,25 +53,25 @@ def test_dataset_basics(complex_dataset_prefix: Path) -> None: "rerun_segment_id" ) ) == inline_snapshot("""\ -┌─────────────────────┬───────────────────┬──────────────────┬──────────────────┬──────────────────────────────┬──────────────────────────────┐ -│ rerun_segment_id ┆ rerun_layer_names ┆ rerun_num_chunks ┆ rerun_size_bytes ┆ timeline:end ┆ timeline:start │ -│ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- │ -│ type: Utf8 ┆ type: List[Utf8] ┆ type: u64 ┆ type: u64 ┆ type: nullable Timestamp(ns) ┆ type: nullable Timestamp(ns) │ -│ ┆ ┆ ┆ ┆ index: timeline ┆ index: timeline │ -│ ┆ ┆ ┆ ┆ index_kind: timestamp ┆ index_kind: timestamp │ -│ ┆ ┆ ┆ ┆ index_marker: end ┆ index_marker: start │ -│ ┆ ┆ ┆ ┆ kind: index ┆ kind: index │ -╞═════════════════════╪═══════════════════╪══════════════════╪══════════════════╪══════════════════════════════╪══════════════════════════════╡ -│ complex_recording_0 ┆ [base] ┆ 3 ┆ 4226 ┆ 2000-01-01T00:00:02 ┆ 2000-01-01T00:00:00 │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ complex_recording_1 ┆ [base] ┆ 3 ┆ 4226 ┆ 2000-01-01T00:00:03 ┆ 2000-01-01T00:00:01 │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ complex_recording_2 ┆ [base] ┆ 3 ┆ 4226 ┆ 2000-01-01T00:00:04 ┆ 2000-01-01T00:00:02 │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ complex_recording_3 ┆ [base] ┆ 3 ┆ 4226 ┆ 2000-01-01T00:00:05 ┆ 2000-01-01T00:00:03 │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ complex_recording_4 ┆ [base] ┆ 3 ┆ 4226 ┆ 2000-01-01T00:00:06 ┆ 2000-01-01T00:00:04 │ -└─────────────────────┴───────────────────┴──────────────────┴──────────────────┴──────────────────────────────┴──────────────────────────────┘\ +┌─────────────────────┬────────────────────────────────────────────────────────────────┬───────────────────────┬───────────────────────┬───────────────────────┬───────────────────────┐ +│ rerun_segment_id ┆ rerun_layer_names ┆ rerun_num_chunks ┆ rerun_size_bytes ┆ timeline:end ┆ timeline:start │ +│ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- │ +│ type: non-null Utf8 ┆ type: non-null List(non-null Utf8, field: 'rerun_layer_names') ┆ type: non-null UInt64 ┆ type: non-null UInt64 ┆ type: Timestamp(ns) ┆ type: Timestamp(ns) │ +│ ┆ ┆ ┆ ┆ index: timeline ┆ index: timeline │ +│ ┆ ┆ ┆ ┆ index_kind: timestamp ┆ index_kind: timestamp │ +│ ┆ ┆ ┆ ┆ index_marker: end ┆ index_marker: start │ +│ ┆ ┆ ┆ ┆ kind: index ┆ kind: index │ +╞═════════════════════╪════════════════════════════════════════════════════════════════╪═══════════════════════╪═══════════════════════╪═══════════════════════╪═══════════════════════╡ +│ complex_recording_0 ┆ [base] ┆ 3 ┆ 4226 ┆ 2000-01-01T00:00:02 ┆ 2000-01-01T00:00:00 │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ complex_recording_1 ┆ [base] ┆ 3 ┆ 4226 ┆ 2000-01-01T00:00:03 ┆ 2000-01-01T00:00:01 │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ complex_recording_2 ┆ [base] ┆ 3 ┆ 4226 ┆ 2000-01-01T00:00:04 ┆ 2000-01-01T00:00:02 │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ complex_recording_3 ┆ [base] ┆ 3 ┆ 4226 ┆ 2000-01-01T00:00:05 ┆ 2000-01-01T00:00:03 │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ complex_recording_4 ┆ [base] ┆ 3 ┆ 4226 ┆ 2000-01-01T00:00:06 ┆ 2000-01-01T00:00:04 │ +└─────────────────────┴────────────────────────────────────────────────────────────────┴───────────────────────┴───────────────────────┴───────────────────────┴───────────────────────┘\ """) @@ -100,34 +100,34 @@ def test_dataset_register(rrd_paths: list[Path]) -> None: ds.manifest().select("rerun_layer_name", "rerun_segment_id").sort("rerun_layer_name", "rerun_segment_id") ) == inline_snapshot( """\ -┌────────────────────────────────────────────┐ -│ METADATA: │ -│ * version: 0.1.3 │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ ┌──────────────────┬─────────────────────┐ │ -│ │ rerun_layer_name ┆ rerun_segment_id │ │ -│ │ --- ┆ --- │ │ -│ │ type: Utf8 ┆ type: Utf8 │ │ -│ ╞══════════════════╪═════════════════════╡ │ -│ │ base ┆ complex_recording_0 │ │ -│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ -│ │ extra ┆ complex_recording_1 │ │ -│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ -│ │ fiz ┆ complex_recording_2 │ │ -│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ -│ │ fuz ┆ complex_recording_3 │ │ -│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ -│ │ more ┆ complex_recording_0 │ │ -│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ -│ │ more ┆ complex_recording_1 │ │ -│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ -│ │ more ┆ complex_recording_2 │ │ -│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ -│ │ more ┆ complex_recording_3 │ │ -│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ -│ │ more ┆ complex_recording_4 │ │ -│ └──────────────────┴─────────────────────┘ │ -└────────────────────────────────────────────┘\ +┌───────────────────────────────────────────────┐ +│ METADATA: │ +│ * version: 0.1.3 │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ ┌─────────────────────┬─────────────────────┐ │ +│ │ rerun_layer_name ┆ rerun_segment_id │ │ +│ │ --- ┆ --- │ │ +│ │ type: non-null Utf8 ┆ type: non-null Utf8 │ │ +│ ╞═════════════════════╪═════════════════════╡ │ +│ │ base ┆ complex_recording_0 │ │ +│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ +│ │ extra ┆ complex_recording_1 │ │ +│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ +│ │ fiz ┆ complex_recording_2 │ │ +│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ +│ │ fuz ┆ complex_recording_3 │ │ +│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ +│ │ more ┆ complex_recording_0 │ │ +│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ +│ │ more ┆ complex_recording_1 │ │ +│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ +│ │ more ┆ complex_recording_2 │ │ +│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ +│ │ more ┆ complex_recording_3 │ │ +│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ +│ │ more ┆ complex_recording_4 │ │ +│ └─────────────────────┴─────────────────────┘ │ +└───────────────────────────────────────────────┘\ """ ) @@ -230,17 +230,17 @@ def test_dataset_metadata(complex_dataset_prefix: Path) -> None: ) assert (str(meta.reader())) == inline_snapshot("""\ -┌─────────────────────┬─────────────────────┐ -│ rerun_segment_id ┆ success │ -│ --- ┆ --- │ -│ type: nullable Utf8 ┆ type: nullable bool │ -╞═════════════════════╪═════════════════════╡ -│ complex_recording_0 ┆ true │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ complex_recording_1 ┆ false │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ complex_recording_4 ┆ true │ -└─────────────────────┴─────────────────────┘\ +┌─────────────────────┬───────────────┐ +│ rerun_segment_id ┆ success │ +│ --- ┆ --- │ +│ type: Utf8 ┆ type: Boolean │ +╞═════════════════════╪═══════════════╡ +│ complex_recording_0 ┆ true │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ complex_recording_1 ┆ false │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ complex_recording_4 ┆ true │ +└─────────────────────┴───────────────┘\ """) diff --git a/rerun_py/tests/api_sandbox/test_draft/test_dataset_views.py b/rerun_py/tests/api_sandbox/test_draft/test_dataset_views.py index a938bad5ebfc..b663ff96ced2 100644 --- a/rerun_py/tests/api_sandbox/test_draft/test_dataset_views.py +++ b/rerun_py/tests/api_sandbox/test_draft/test_dataset_views.py @@ -19,22 +19,22 @@ def test_dataset_view_filter_segments(complex_dataset: DatasetEntry, complex_met assert segment_stable_snapshot( simple_filt.segment_table(join_meta=complex_metadata).drop("property:RecordingInfo:start_time") ) == inline_snapshot("""\ -┌───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ METADATA: │ -│ * version: 0.1.3 │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ ┌─────────────────────┬───────────────────┬──────────────────┬──────────────────┬──────────────────────────────┬──────────────────────────────┬─────────────────────┐ │ -│ │ rerun_segment_id ┆ rerun_layer_names ┆ rerun_num_chunks ┆ rerun_size_bytes ┆ timeline:end ┆ timeline:start ┆ success │ │ -│ │ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- │ │ -│ │ type: Utf8 ┆ type: List[Utf8] ┆ type: u64 ┆ type: u64 ┆ type: nullable Timestamp(ns) ┆ type: nullable Timestamp(ns) ┆ type: nullable bool │ │ -│ │ ┆ ┆ ┆ ┆ index: timeline ┆ index: timeline ┆ │ │ -│ │ ┆ ┆ ┆ ┆ index_kind: timestamp ┆ index_kind: timestamp ┆ │ │ -│ │ ┆ ┆ ┆ ┆ index_marker: end ┆ index_marker: start ┆ │ │ -│ │ ┆ ┆ ┆ ┆ kind: index ┆ kind: index ┆ │ │ -│ ╞═════════════════════╪═══════════════════╪══════════════════╪══════════════════╪══════════════════════════════╪══════════════════════════════╪═════════════════════╡ │ -│ │ complex_recording_2 ┆ [base] ┆ 3 ┆ 4226 ┆ 2000-01-01T00:00:04 ┆ 2000-01-01T00:00:02 ┆ false │ │ -│ └─────────────────────┴───────────────────┴──────────────────┴──────────────────┴──────────────────────────────┴──────────────────────────────┴─────────────────────┘ │ -└───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘\ +┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ METADATA: │ +│ * version: 0.1.3 │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ ┌─────────────────────┬────────────────────────────────────────────────────────────────┬───────────────────────┬───────────────────────┬───────────────────────┬───────────────────────┬───────────────┐ │ +│ │ rerun_segment_id ┆ rerun_layer_names ┆ rerun_num_chunks ┆ rerun_size_bytes ┆ timeline:end ┆ timeline:start ┆ success │ │ +│ │ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- │ │ +│ │ type: non-null Utf8 ┆ type: non-null List(non-null Utf8, field: 'rerun_layer_names') ┆ type: non-null UInt64 ┆ type: non-null UInt64 ┆ type: Timestamp(ns) ┆ type: Timestamp(ns) ┆ type: Boolean │ │ +│ │ ┆ ┆ ┆ ┆ index: timeline ┆ index: timeline ┆ │ │ +│ │ ┆ ┆ ┆ ┆ index_kind: timestamp ┆ index_kind: timestamp ┆ │ │ +│ │ ┆ ┆ ┆ ┆ index_marker: end ┆ index_marker: start ┆ │ │ +│ │ ┆ ┆ ┆ ┆ kind: index ┆ kind: index ┆ │ │ +│ ╞═════════════════════╪════════════════════════════════════════════════════════════════╪═══════════════════════╪═══════════════════════╪═══════════════════════╪═══════════════════════╪═══════════════╡ │ +│ │ complex_recording_2 ┆ [base] ┆ 3 ┆ 4226 ┆ 2000-01-01T00:00:04 ┆ 2000-01-01T00:00:02 ┆ false │ │ +│ └─────────────────────┴────────────────────────────────────────────────────────────────┴───────────────────────┴───────────────────────┴───────────────────────┴───────────────────────┴───────────────┘ │ +└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘\ """) good_segments = complex_dataset.segment_table(join_meta=complex_metadata).filter(col("success")) @@ -46,24 +46,24 @@ def test_dataset_view_filter_segments(complex_dataset: DatasetEntry, complex_met assert segment_stable_snapshot( good_ds.segment_table().drop("property:RecordingInfo:start_time") ) == inline_snapshot("""\ -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ METADATA: │ -│ * version: 0.1.3 │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ ┌─────────────────────┬───────────────────┬──────────────────┬──────────────────┬──────────────────────────────┬──────────────────────────────┐ │ -│ │ rerun_segment_id ┆ rerun_layer_names ┆ rerun_num_chunks ┆ rerun_size_bytes ┆ timeline:end ┆ timeline:start │ │ -│ │ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- │ │ -│ │ type: Utf8 ┆ type: List[Utf8] ┆ type: u64 ┆ type: u64 ┆ type: nullable Timestamp(ns) ┆ type: nullable Timestamp(ns) │ │ -│ │ ┆ ┆ ┆ ┆ index: timeline ┆ index: timeline │ │ -│ │ ┆ ┆ ┆ ┆ index_kind: timestamp ┆ index_kind: timestamp │ │ -│ │ ┆ ┆ ┆ ┆ index_marker: end ┆ index_marker: start │ │ -│ │ ┆ ┆ ┆ ┆ kind: index ┆ kind: index │ │ -│ ╞═════════════════════╪═══════════════════╪══════════════════╪══════════════════╪══════════════════════════════╪══════════════════════════════╡ │ -│ │ complex_recording_1 ┆ [base] ┆ 3 ┆ 4226 ┆ 2000-01-01T00:00:03 ┆ 2000-01-01T00:00:01 │ │ -│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ -│ │ complex_recording_3 ┆ [base] ┆ 3 ┆ 4226 ┆ 2000-01-01T00:00:05 ┆ 2000-01-01T00:00:03 │ │ -│ └─────────────────────┴───────────────────┴──────────────────┴──────────────────┴──────────────────────────────┴──────────────────────────────┘ │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘\ +┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ METADATA: │ +│ * version: 0.1.3 │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ ┌─────────────────────┬────────────────────────────────────────────────────────────────┬───────────────────────┬───────────────────────┬───────────────────────┬───────────────────────┐ │ +│ │ rerun_segment_id ┆ rerun_layer_names ┆ rerun_num_chunks ┆ rerun_size_bytes ┆ timeline:end ┆ timeline:start │ │ +│ │ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- │ │ +│ │ type: non-null Utf8 ┆ type: non-null List(non-null Utf8, field: 'rerun_layer_names') ┆ type: non-null UInt64 ┆ type: non-null UInt64 ┆ type: Timestamp(ns) ┆ type: Timestamp(ns) │ │ +│ │ ┆ ┆ ┆ ┆ index: timeline ┆ index: timeline │ │ +│ │ ┆ ┆ ┆ ┆ index_kind: timestamp ┆ index_kind: timestamp │ │ +│ │ ┆ ┆ ┆ ┆ index_marker: end ┆ index_marker: start │ │ +│ │ ┆ ┆ ┆ ┆ kind: index ┆ kind: index │ │ +│ ╞═════════════════════╪════════════════════════════════════════════════════════════════╪═══════════════════════╪═══════════════════════╪═══════════════════════╪═══════════════════════╡ │ +│ │ complex_recording_1 ┆ [base] ┆ 3 ┆ 4226 ┆ 2000-01-01T00:00:03 ┆ 2000-01-01T00:00:01 │ │ +│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ +│ │ complex_recording_3 ┆ [base] ┆ 3 ┆ 4226 ┆ 2000-01-01T00:00:05 ┆ 2000-01-01T00:00:03 │ │ +│ └─────────────────────┴────────────────────────────────────────────────────────────────┴───────────────────────┴───────────────────────┴───────────────────────┴───────────────────────┘ │ +└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘\ """) @@ -128,27 +128,27 @@ def test_dataset_view_dataframe(complex_dataset: DatasetEntry) -> None: df = filtered.reader(index="timeline").sort("rerun_segment_id") assert str(df) == inline_snapshot("""\ -┌─────────────────────────────────────────────────────────────────────────────────────────────┐ -│ METADATA: │ -│ * version: 0.1.3 │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ ┌─────────────────────┬──────────────────────────────┬────────────────────────────────────┐ │ -│ │ rerun_segment_id ┆ timeline ┆ /text:TextLog:text │ │ -│ │ --- ┆ --- ┆ --- │ │ -│ │ type: Utf8 ┆ type: nullable Timestamp(ns) ┆ type: nullable List[nullable Utf8] │ │ -│ │ ┆ index_name: timeline ┆ archetype: TextLog │ │ -│ │ ┆ kind: index ┆ component: TextLog:text │ │ -│ │ ┆ ┆ component_type: Text │ │ -│ │ ┆ ┆ entity_path: /text │ │ -│ │ ┆ ┆ kind: data │ │ -│ ╞═════════════════════╪══════════════════════════════╪════════════════════════════════════╡ │ -│ │ complex_recording_0 ┆ 2000-01-01T00:00:00 ┆ [Hello] │ │ -│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ -│ │ complex_recording_0 ┆ 2000-01-01T00:00:02 ┆ [World] │ │ -│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ -│ │ complex_recording_2 ┆ 2000-01-01T00:00:02 ┆ [Hello] │ │ -│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ -│ │ complex_recording_2 ┆ 2000-01-01T00:00:04 ┆ [World] │ │ -│ └─────────────────────┴──────────────────────────────┴────────────────────────────────────┘ │ -└─────────────────────────────────────────────────────────────────────────────────────────────┘\ +┌──────────────────────────────────────────────────────────────────────────┐ +│ METADATA: │ +│ * version: 0.1.3 │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ ┌─────────────────────┬──────────────────────┬─────────────────────────┐ │ +│ │ rerun_segment_id ┆ timeline ┆ /text:TextLog:text │ │ +│ │ --- ┆ --- ┆ --- │ │ +│ │ type: non-null Utf8 ┆ type: Timestamp(ns) ┆ type: List(Utf8) │ │ +│ │ ┆ index_name: timeline ┆ archetype: TextLog │ │ +│ │ ┆ kind: index ┆ component: TextLog:text │ │ +│ │ ┆ ┆ component_type: Text │ │ +│ │ ┆ ┆ entity_path: /text │ │ +│ │ ┆ ┆ kind: data │ │ +│ ╞═════════════════════╪══════════════════════╪═════════════════════════╡ │ +│ │ complex_recording_0 ┆ 2000-01-01T00:00:00 ┆ [Hello] │ │ +│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ +│ │ complex_recording_0 ┆ 2000-01-01T00:00:02 ┆ [World] │ │ +│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ +│ │ complex_recording_2 ┆ 2000-01-01T00:00:02 ┆ [Hello] │ │ +│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ +│ │ complex_recording_2 ┆ 2000-01-01T00:00:04 ┆ [World] │ │ +│ └─────────────────────┴──────────────────────┴─────────────────────────┘ │ +└──────────────────────────────────────────────────────────────────────────┘\ """) diff --git a/rerun_py/tests/api_sandbox/test_draft/test_index_ranges.py b/rerun_py/tests/api_sandbox/test_draft/test_index_ranges.py index b833cfbdc6f3..4359108dbe85 100644 --- a/rerun_py/tests/api_sandbox/test_draft/test_index_ranges.py +++ b/rerun_py/tests/api_sandbox/test_draft/test_index_ranges.py @@ -12,30 +12,30 @@ def test_index_ranges(complex_dataset: DatasetEntry) -> None: df = complex_dataset.get_index_ranges().sort("rerun_segment_id") assert str(df) == inline_snapshot("""\ -┌───────────────────────────────────────────────────────────────────────────────────────┐ -│ METADATA: │ -│ * version: 0.1.3 │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ ┌─────────────────────┬──────────────────────────────┬──────────────────────────────┐ │ -│ │ rerun_segment_id ┆ timeline:start ┆ timeline:end │ │ -│ │ --- ┆ --- ┆ --- │ │ -│ │ type: Utf8 ┆ type: nullable Timestamp(ns) ┆ type: nullable Timestamp(ns) │ │ -│ │ ┆ index: timeline ┆ index: timeline │ │ -│ │ ┆ index_kind: timestamp ┆ index_kind: timestamp │ │ -│ │ ┆ index_marker: start ┆ index_marker: end │ │ -│ │ ┆ kind: index ┆ kind: index │ │ -│ ╞═════════════════════╪══════════════════════════════╪══════════════════════════════╡ │ -│ │ complex_recording_0 ┆ 2000-01-01T00:00:00 ┆ 2000-01-01T00:00:02 │ │ -│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ -│ │ complex_recording_1 ┆ 2000-01-01T00:00:01 ┆ 2000-01-01T00:00:03 │ │ -│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ -│ │ complex_recording_2 ┆ 2000-01-01T00:00:02 ┆ 2000-01-01T00:00:04 │ │ -│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ -│ │ complex_recording_3 ┆ 2000-01-01T00:00:03 ┆ 2000-01-01T00:00:05 │ │ -│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ -│ │ complex_recording_4 ┆ 2000-01-01T00:00:04 ┆ 2000-01-01T00:00:06 │ │ -│ └─────────────────────┴──────────────────────────────┴──────────────────────────────┘ │ -└───────────────────────────────────────────────────────────────────────────────────────┘\ +┌─────────────────────────────────────────────────────────────────────────┐ +│ METADATA: │ +│ * version: 0.1.3 │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ ┌─────────────────────┬───────────────────────┬───────────────────────┐ │ +│ │ rerun_segment_id ┆ timeline:start ┆ timeline:end │ │ +│ │ --- ┆ --- ┆ --- │ │ +│ │ type: non-null Utf8 ┆ type: Timestamp(ns) ┆ type: Timestamp(ns) │ │ +│ │ ┆ index: timeline ┆ index: timeline │ │ +│ │ ┆ index_kind: timestamp ┆ index_kind: timestamp │ │ +│ │ ┆ index_marker: start ┆ index_marker: end │ │ +│ │ ┆ kind: index ┆ kind: index │ │ +│ ╞═════════════════════╪═══════════════════════╪═══════════════════════╡ │ +│ │ complex_recording_0 ┆ 2000-01-01T00:00:00 ┆ 2000-01-01T00:00:02 │ │ +│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ +│ │ complex_recording_1 ┆ 2000-01-01T00:00:01 ┆ 2000-01-01T00:00:03 │ │ +│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ +│ │ complex_recording_2 ┆ 2000-01-01T00:00:02 ┆ 2000-01-01T00:00:04 │ │ +│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ +│ │ complex_recording_3 ┆ 2000-01-01T00:00:03 ┆ 2000-01-01T00:00:05 │ │ +│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ +│ │ complex_recording_4 ┆ 2000-01-01T00:00:04 ┆ 2000-01-01T00:00:06 │ │ +│ └─────────────────────┴───────────────────────┴───────────────────────┘ │ +└─────────────────────────────────────────────────────────────────────────┘\ """) @@ -45,24 +45,24 @@ def test_index_ranges_dataset_view(complex_dataset: DatasetEntry) -> None: df = view.get_index_ranges().sort("rerun_segment_id") assert str(df) == inline_snapshot("""\ -┌───────────────────────────────────────────────────────────────────────────────────────┐ -│ METADATA: │ -│ * version: 0.1.3 │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ ┌─────────────────────┬──────────────────────────────┬──────────────────────────────┐ │ -│ │ rerun_segment_id ┆ timeline:start ┆ timeline:end │ │ -│ │ --- ┆ --- ┆ --- │ │ -│ │ type: Utf8 ┆ type: nullable Timestamp(ns) ┆ type: nullable Timestamp(ns) │ │ -│ │ ┆ index: timeline ┆ index: timeline │ │ -│ │ ┆ index_kind: timestamp ┆ index_kind: timestamp │ │ -│ │ ┆ index_marker: start ┆ index_marker: end │ │ -│ │ ┆ kind: index ┆ kind: index │ │ -│ ╞═════════════════════╪══════════════════════════════╪══════════════════════════════╡ │ -│ │ complex_recording_0 ┆ 2000-01-01T00:00:00 ┆ 2000-01-01T00:00:02 │ │ -│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ -│ │ complex_recording_1 ┆ 2000-01-01T00:00:01 ┆ 2000-01-01T00:00:03 │ │ -│ └─────────────────────┴──────────────────────────────┴──────────────────────────────┘ │ -└───────────────────────────────────────────────────────────────────────────────────────┘\ +┌─────────────────────────────────────────────────────────────────────────┐ +│ METADATA: │ +│ * version: 0.1.3 │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ ┌─────────────────────┬───────────────────────┬───────────────────────┐ │ +│ │ rerun_segment_id ┆ timeline:start ┆ timeline:end │ │ +│ │ --- ┆ --- ┆ --- │ │ +│ │ type: non-null Utf8 ┆ type: Timestamp(ns) ┆ type: Timestamp(ns) │ │ +│ │ ┆ index: timeline ┆ index: timeline │ │ +│ │ ┆ index_kind: timestamp ┆ index_kind: timestamp │ │ +│ │ ┆ index_marker: start ┆ index_marker: end │ │ +│ │ ┆ kind: index ┆ kind: index │ │ +│ ╞═════════════════════╪═══════════════════════╪═══════════════════════╡ │ +│ │ complex_recording_0 ┆ 2000-01-01T00:00:00 ┆ 2000-01-01T00:00:02 │ │ +│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ +│ │ complex_recording_1 ┆ 2000-01-01T00:00:01 ┆ 2000-01-01T00:00:03 │ │ +│ └─────────────────────┴───────────────────────┴───────────────────────┘ │ +└─────────────────────────────────────────────────────────────────────────┘\ """) # Note: the ranges are relative to the actual data being filtered, see how the ranges differ here @@ -71,22 +71,22 @@ def test_index_ranges_dataset_view(complex_dataset: DatasetEntry) -> None: df = view.get_index_ranges().sort("rerun_segment_id") assert str(df) == inline_snapshot("""\ -┌───────────────────────────────────────────────────────────────────────────────────────┐ -│ METADATA: │ -│ * version: 0.1.3 │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ ┌─────────────────────┬──────────────────────────────┬──────────────────────────────┐ │ -│ │ rerun_segment_id ┆ timeline:start ┆ timeline:end │ │ -│ │ --- ┆ --- ┆ --- │ │ -│ │ type: Utf8 ┆ type: nullable Timestamp(ns) ┆ type: nullable Timestamp(ns) │ │ -│ │ ┆ index: timeline ┆ index: timeline │ │ -│ │ ┆ index_kind: timestamp ┆ index_kind: timestamp │ │ -│ │ ┆ index_marker: start ┆ index_marker: end │ │ -│ │ ┆ kind: index ┆ kind: index │ │ -│ ╞═════════════════════╪══════════════════════════════╪══════════════════════════════╡ │ -│ │ complex_recording_0 ┆ 2000-01-01T00:00:00 ┆ 2000-01-01T00:00:02 │ │ -│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ -│ │ complex_recording_1 ┆ 2000-01-01T00:00:01 ┆ 2000-01-01T00:00:03 │ │ -│ └─────────────────────┴──────────────────────────────┴──────────────────────────────┘ │ -└───────────────────────────────────────────────────────────────────────────────────────┘\ +┌─────────────────────────────────────────────────────────────────────────┐ +│ METADATA: │ +│ * version: 0.1.3 │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ ┌─────────────────────┬───────────────────────┬───────────────────────┐ │ +│ │ rerun_segment_id ┆ timeline:start ┆ timeline:end │ │ +│ │ --- ┆ --- ┆ --- │ │ +│ │ type: non-null Utf8 ┆ type: Timestamp(ns) ┆ type: Timestamp(ns) │ │ +│ │ ┆ index: timeline ┆ index: timeline │ │ +│ │ ┆ index_kind: timestamp ┆ index_kind: timestamp │ │ +│ │ ┆ index_marker: start ┆ index_marker: end │ │ +│ │ ┆ kind: index ┆ kind: index │ │ +│ ╞═════════════════════╪═══════════════════════╪═══════════════════════╡ │ +│ │ complex_recording_0 ┆ 2000-01-01T00:00:00 ┆ 2000-01-01T00:00:02 │ │ +│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ +│ │ complex_recording_1 ┆ 2000-01-01T00:00:01 ┆ 2000-01-01T00:00:03 │ │ +│ └─────────────────────┴───────────────────────┴───────────────────────┘ │ +└─────────────────────────────────────────────────────────────────────────┘\ """) diff --git a/rerun_py/tests/api_sandbox/test_draft/test_table_api.py b/rerun_py/tests/api_sandbox/test_draft/test_table_api.py index 1dd1374c12fe..f1e4c387c52a 100644 --- a/rerun_py/tests/api_sandbox/test_draft/test_table_api.py +++ b/rerun_py/tests/api_sandbox/test_draft/test_table_api.py @@ -36,15 +36,15 @@ def test_table_api() -> None: ) assert str(reader.sort("rerun_segment_id")) == inline_snapshot("""\ -┌─────────────────────┬─────────────────────┐ -│ rerun_segment_id ┆ operator │ -│ --- ┆ --- │ -│ type: nullable Utf8 ┆ type: nullable Utf8 │ -╞═════════════════════╪═════════════════════╡ -│ segment_001 ┆ alice │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ segment_002 ┆ bob │ -└─────────────────────┴─────────────────────┘\ +┌──────────────────┬────────────┐ +│ rerun_segment_id ┆ operator │ +│ --- ┆ --- │ +│ type: Utf8 ┆ type: Utf8 │ +╞══════════════════╪════════════╡ +│ segment_001 ┆ alice │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ segment_002 ┆ bob │ +└──────────────────┴────────────┘\ """) table.append( @@ -53,17 +53,17 @@ def test_table_api() -> None: ) assert str(reader.sort("rerun_segment_id")) == inline_snapshot("""\ -┌─────────────────────┬─────────────────────┐ -│ rerun_segment_id ┆ operator │ -│ --- ┆ --- │ -│ type: nullable Utf8 ┆ type: nullable Utf8 │ -╞═════════════════════╪═════════════════════╡ -│ segment_001 ┆ alice │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ segment_002 ┆ bob │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ segment_003 ┆ carol │ -└─────────────────────┴─────────────────────┘\ +┌──────────────────┬────────────┐ +│ rerun_segment_id ┆ operator │ +│ --- ┆ --- │ +│ type: Utf8 ┆ type: Utf8 │ +╞══════════════════╪════════════╡ +│ segment_001 ┆ alice │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ segment_002 ┆ bob │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ segment_003 ┆ carol │ +└──────────────────┴────────────┘\ """) assert str(reader) == str(client.ctx.table("my_table")) diff --git a/rerun_py/tests/api_sandbox/test_draft/test_table_basics.py b/rerun_py/tests/api_sandbox/test_draft/test_table_basics.py index d47410d997e8..ef68cfd5c622 100644 --- a/rerun_py/tests/api_sandbox/test_draft/test_table_basics.py +++ b/rerun_py/tests/api_sandbox/test_draft/test_table_basics.py @@ -29,19 +29,19 @@ def test_create_table_and_append() -> None: df = table.reader() assert str(df.sort("id")) == inline_snapshot("""\ -┌────────────────────┬────────────────────┬─────────────────────┐ -│ id ┆ value ┆ enabled │ -│ --- ┆ --- ┆ --- │ -│ type: nullable i32 ┆ type: nullable f64 ┆ type: nullable bool │ -╞════════════════════╪════════════════════╪═════════════════════╡ -│ 1 ┆ 10.5 ┆ true │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ 2 ┆ 20.3 ┆ false │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ 3 ┆ 15.7 ┆ true │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ 4 ┆ 30.2 ┆ false │ -└────────────────────┴────────────────────┴─────────────────────┘\ +┌─────────────┬───────────────┬───────────────┐ +│ id ┆ value ┆ enabled │ +│ --- ┆ --- ┆ --- │ +│ type: Int32 ┆ type: Float64 ┆ type: Boolean │ +╞═════════════╪═══════════════╪═══════════════╡ +│ 1 ┆ 10.5 ┆ true │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ 2 ┆ 20.3 ┆ false │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ 3 ┆ 15.7 ┆ true │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ 4 ┆ 30.2 ┆ false │ +└─────────────┴───────────────┴───────────────┘\ """) @@ -74,23 +74,23 @@ def test_write_table_with_record_batches() -> None: df = client.get_table(name="scores_table").reader() assert str(df.sort("id")) == inline_snapshot("""\ -┌────────────────────┬─────────────────────┬────────────────────┐ -│ id ┆ enabled ┆ score │ -│ --- ┆ --- ┆ --- │ -│ type: nullable i32 ┆ type: nullable bool ┆ type: nullable f64 │ -╞════════════════════╪═════════════════════╪════════════════════╡ -│ 1 ┆ true ┆ 95.5 │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ 2 ┆ false ┆ 87.3 │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ 3 ┆ true ┆ 91.2 │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ 4 ┆ true ┆ 88.7 │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ 5 ┆ true ┆ 93.1 │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ 6 ┆ false ┆ 85.4 │ -└────────────────────┴─────────────────────┴────────────────────┘\ +┌─────────────┬───────────────┬───────────────┐ +│ id ┆ enabled ┆ score │ +│ --- ┆ --- ┆ --- │ +│ type: Int32 ┆ type: Boolean ┆ type: Float64 │ +╞═════════════╪═══════════════╪═══════════════╡ +│ 1 ┆ true ┆ 95.5 │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ 2 ┆ false ┆ 87.3 │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ 3 ┆ true ┆ 91.2 │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ 4 ┆ true ┆ 88.7 │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ 5 ┆ true ┆ 93.1 │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ 6 ┆ false ┆ 85.4 │ +└─────────────┴───────────────┴───────────────┘\ """) @@ -113,18 +113,18 @@ def test_table_overwrite_mode() -> None: df_after_append = client.get_table(name="data_table").reader() assert str(df_after_append.sort("id")) == inline_snapshot("""\ -┌──────────────────────┬─────────────────────┐ -│ id ┆ category │ -│ --- ┆ --- │ -│ type: nullable i32 ┆ type: nullable Utf8 │ -│ is_table_index: true ┆ │ -╞══════════════════════╪═════════════════════╡ -│ 1 ┆ A │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ 2 ┆ B │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ 3 ┆ C │ -└──────────────────────┴─────────────────────┘\ +┌──────────────────────┬────────────┐ +│ id ┆ category │ +│ --- ┆ --- │ +│ type: Int32 ┆ type: Utf8 │ +│ is_table_index: true ┆ │ +╞══════════════════════╪════════════╡ +│ 1 ┆ A │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ 2 ┆ B │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ 3 ┆ C │ +└──────────────────────┴────────────┘\ """) # Overwrite with new data @@ -134,32 +134,32 @@ def test_table_overwrite_mode() -> None: df_after_overwrite = client.get_table(name="data_table").reader() assert str(df_after_overwrite.sort("id")) == inline_snapshot("""\ -┌──────────────────────┬─────────────────────┐ -│ id ┆ category │ -│ --- ┆ --- │ -│ type: nullable i32 ┆ type: nullable Utf8 │ -│ is_table_index: true ┆ │ -╞══════════════════════╪═════════════════════╡ -│ 10 ┆ X │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ 20 ┆ Y │ -└──────────────────────┴─────────────────────┘\ +┌──────────────────────┬────────────┐ +│ id ┆ category │ +│ --- ┆ --- │ +│ type: Int32 ┆ type: Utf8 │ +│ is_table_index: true ┆ │ +╞══════════════════════╪════════════╡ +│ 10 ┆ X │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ 20 ┆ Y │ +└──────────────────────┴────────────┘\ """) table.upsert(id=20, category="Z") df_after_upsert = client.get_table(name="data_table").reader() assert str(df_after_upsert.sort("id")) == inline_snapshot("""\ -┌──────────────────────┬─────────────────────┐ -│ id ┆ category │ -│ --- ┆ --- │ -│ type: nullable i32 ┆ type: nullable Utf8 │ -│ is_table_index: true ┆ │ -╞══════════════════════╪═════════════════════╡ -│ 10 ┆ X │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ 20 ┆ Z │ -└──────────────────────┴─────────────────────┘\ +┌──────────────────────┬────────────┐ +│ id ┆ category │ +│ --- ┆ --- │ +│ type: Int32 ┆ type: Utf8 │ +│ is_table_index: true ┆ │ +╞══════════════════════╪════════════╡ +│ 10 ┆ X │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ 20 ┆ Z │ +└──────────────────────┴────────────┘\ """) @@ -179,32 +179,32 @@ def test_read_table() -> None: table_entry = client.get_table(name="products") df1 = table_entry.reader() assert str(df1.sort("product_id")) == inline_snapshot("""\ -┌────────────────────┬────────────────────┐ -│ product_id ┆ price │ -│ --- ┆ --- │ -│ type: nullable i32 ┆ type: nullable f64 │ -╞════════════════════╪════════════════════╡ -│ 101 ┆ 29.99 │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ 102 ┆ 49.99 │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ 103 ┆ 19.99 │ -└────────────────────┴────────────────────┘\ +┌─────────────┬───────────────┐ +│ product_id ┆ price │ +│ --- ┆ --- │ +│ type: Int32 ┆ type: Float64 │ +╞═════════════╪═══════════════╡ +│ 101 ┆ 29.99 │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ 102 ┆ 49.99 │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ 103 ┆ 19.99 │ +└─────────────┴───────────────┘\ """) # Method 2: via DataFusion SessionContext - useful for SQL queries ctx = client.ctx df2 = ctx.table("products") assert str(df2.sort("product_id")) == inline_snapshot("""\ -┌────────────────────┬────────────────────┐ -│ product_id ┆ price │ -│ --- ┆ --- │ -│ type: nullable i32 ┆ type: nullable f64 │ -╞════════════════════╪════════════════════╡ -│ 101 ┆ 29.99 │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ 102 ┆ 49.99 │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ 103 ┆ 19.99 │ -└────────────────────┴────────────────────┘\ +┌─────────────┬───────────────┐ +│ product_id ┆ price │ +│ --- ┆ --- │ +│ type: Int32 ┆ type: Float64 │ +╞═════════════╪═══════════════╡ +│ 101 ┆ 29.99 │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ 102 ┆ 49.99 │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ 103 ┆ 19.99 │ +└─────────────┴───────────────┘\ """) diff --git a/rerun_py/tests/e2e_redap_tests/__snapshots__/test_dataset_views.ambr b/rerun_py/tests/e2e_redap_tests/__snapshots__/test_dataset_views.ambr index 85869a6541de..2b2421da9214 100644 --- a/rerun_py/tests/e2e_redap_tests/__snapshots__/test_dataset_views.ambr +++ b/rerun_py/tests/e2e_redap_tests/__snapshots__/test_dataset_views.ambr @@ -1,87 +1,87 @@ # serializer version: 1 # name: test_dataframe_api_using_index_values ''' - ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ - │ METADATA: │ - │ * version: 0.1.3 │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ ┌──────────────────────────────────┬───────────────────────────────┬─────────────────────────────────────────────────────┬─────────────────────────────────────────────────────┐ │ - │ │ rerun_segment_id ┆ time_1 ┆ /obj1:Points3D:positions ┆ /obj2:Points3D:positions │ │ - │ │ --- ┆ --- ┆ --- ┆ --- │ │ - │ │ type: Utf8 ┆ type: nullable Timestamp(ns) ┆ type: nullable List[nullable FixedSizeList[f32; 3]] ┆ type: nullable List[nullable FixedSizeList[f32; 3]] │ │ - │ │ ┆ index_name: time_1 ┆ archetype: Points3D ┆ archetype: Points3D │ │ - │ │ ┆ kind: index ┆ component: Points3D:positions ┆ component: Points3D:positions │ │ - │ │ ┆ ┆ component_type: Position3D ┆ component_type: Position3D │ │ - │ │ ┆ ┆ entity_path: /obj1 ┆ entity_path: /obj2 │ │ - │ │ ┆ ┆ kind: data ┆ kind: data │ │ - │ ╞══════════════════════════════════╪═══════════════════════════════╪═════════════════════════════════════════════════════╪═════════════════════════════════════════════════════╡ │ - │ │ 3ee345b2e801448cace33a1097b9b49b ┆ 2024-01-15T10:30:45.123456789 ┆ null ┆ [[19.0, 1.0, 0.0]] │ │ - │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ - │ │ 3ee345b2e801448cace33a1097b9b49b ┆ 2024-01-15T10:34:45.123456789 ┆ [[3.0, 0.0, 0.0]] ┆ [[13.0, 1.0, 0.0]] │ │ - │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ - │ │ 3ee345b2e801448cace33a1097b9b49b ┆ 2024-01-15T10:36:45.123456789 ┆ [[4.0, 0.0, 0.0]] ┆ [[1.0, 1.0, 0.0]] │ │ - │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ - │ │ 3ee345b2e801448cace33a1097b9b49b ┆ 2024-01-15T10:38:45.123456789 ┆ [[5.0, 0.0, 0.0]] ┆ [[5.0, 1.0, 0.0]] │ │ - │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ - │ │ 3ee345b2e801448cace33a1097b9b49b ┆ 2024-01-15T10:40:45.123456789 ┆ [[6.0, 0.0, 0.0]] ┆ [[25.0, 1.0, 0.0]] │ │ - │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ - │ │ 3ee345b2e801448cace33a1097b9b49b ┆ 2024-01-15T10:44:45.123456789 ┆ null ┆ null │ │ - │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ - │ │ 3ee345b2e801448cace33a1097b9b49b ┆ 2024-01-15T10:46:45.123456789 ┆ null ┆ [[22.0, 1.0, 0.0]] │ │ - │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ - │ │ 3ee345b2e801448cace33a1097b9b49b ┆ 2024-01-15T10:48:45.123456789 ┆ null ┆ [[9.0, 1.0, 0.0]] │ │ - │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ - │ │ 3ee345b2e801448cace33a1097b9b49b ┆ 2024-01-15T10:50:45.123456789 ┆ null ┆ [[2.0, 1.0, 0.0]] │ │ - │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ - │ │ 3ee345b2e801448cace33a1097b9b49b ┆ 2024-01-15T10:52:45.123456789 ┆ [[12.0, 0.0, 0.0]] ┆ [[4.0, 1.0, 0.0]] │ │ - │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ - │ │ 3ee345b2e801448cace33a1097b9b49b ┆ 2024-01-15T11:00:45.123456789 ┆ null ┆ [[20.0, 1.0, 0.0]] │ │ - │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ - │ │ 3ee345b2e801448cace33a1097b9b49b ┆ 2024-01-15T11:02:45.123456789 ┆ [[17.0, 0.0, 0.0]] ┆ [[17.0, 1.0, 0.0]] │ │ - │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ - │ │ 3ee345b2e801448cace33a1097b9b49b ┆ 2024-01-15T11:04:45.123456789 ┆ [[18.0, 0.0, 0.0]] ┆ [[21.0, 1.0, 0.0]] │ │ - │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ - │ │ 3ee345b2e801448cace33a1097b9b49b ┆ 2024-01-15T11:12:45.123456789 ┆ null ┆ [[14.0, 1.0, 0.0]] │ │ - │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ - │ │ 3ee345b2e801448cace33a1097b9b49b ┆ 2024-01-15T11:14:45.123456789 ┆ null ┆ [[8.0, 1.0, 0.0]] │ │ - │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ - │ │ 3ee345b2e801448cace33a1097b9b49b ┆ 2024-01-15T11:16:45.123456789 ┆ [[24.0, 0.0, 0.0]] ┆ [[18.0, 1.0, 0.0]] │ │ - │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ - │ │ 3ee345b2e801448cace33a1097b9b49b ┆ 2024-01-15T11:18:45.123456789 ┆ [[25.0, 0.0, 0.0]] ┆ [[23.0, 1.0, 0.0]] │ │ - │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ - │ │ 68224eead5ed40838b3f3bdb0edfd2b2 ┆ 2024-01-15T10:30:45.123456789 ┆ null ┆ null │ │ - │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ - │ │ 68224eead5ed40838b3f3bdb0edfd2b2 ┆ 2024-01-15T10:32:45.123456789 ┆ null ┆ [[25.0, 1.0, 0.0]] │ │ - │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ - │ │ 68224eead5ed40838b3f3bdb0edfd2b2 ┆ 2024-01-15T10:34:45.123456789 ┆ [[3.0, 0.0, 0.0]] ┆ null │ │ - │ └──────────────────────────────────┴───────────────────────────────┴─────────────────────────────────────────────────────┴─────────────────────────────────────────────────────┘ │ - └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ + ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ METADATA: │ + │ * version: 0.1.3 │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ ┌──────────────────────────────────┬───────────────────────────────┬─────────────────────────────────────────────────┬─────────────────────────────────────────────────┐ │ + │ │ rerun_segment_id ┆ time_1 ┆ /obj1:Points3D:positions ┆ /obj2:Points3D:positions │ │ + │ │ --- ┆ --- ┆ --- ┆ --- │ │ + │ │ type: non-null Utf8 ┆ type: Timestamp(ns) ┆ type: List(FixedSizeList(3 x non-null Float32)) ┆ type: List(FixedSizeList(3 x non-null Float32)) │ │ + │ │ ┆ index_name: time_1 ┆ archetype: Points3D ┆ archetype: Points3D │ │ + │ │ ┆ kind: index ┆ component: Points3D:positions ┆ component: Points3D:positions │ │ + │ │ ┆ ┆ component_type: Position3D ┆ component_type: Position3D │ │ + │ │ ┆ ┆ entity_path: /obj1 ┆ entity_path: /obj2 │ │ + │ │ ┆ ┆ kind: data ┆ kind: data │ │ + │ ╞══════════════════════════════════╪═══════════════════════════════╪═════════════════════════════════════════════════╪═════════════════════════════════════════════════╡ │ + │ │ 3ee345b2e801448cace33a1097b9b49b ┆ 2024-01-15T10:30:45.123456789 ┆ null ┆ [[19.0, 1.0, 0.0]] │ │ + │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ + │ │ 3ee345b2e801448cace33a1097b9b49b ┆ 2024-01-15T10:34:45.123456789 ┆ [[3.0, 0.0, 0.0]] ┆ [[13.0, 1.0, 0.0]] │ │ + │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ + │ │ 3ee345b2e801448cace33a1097b9b49b ┆ 2024-01-15T10:36:45.123456789 ┆ [[4.0, 0.0, 0.0]] ┆ [[1.0, 1.0, 0.0]] │ │ + │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ + │ │ 3ee345b2e801448cace33a1097b9b49b ┆ 2024-01-15T10:38:45.123456789 ┆ [[5.0, 0.0, 0.0]] ┆ [[5.0, 1.0, 0.0]] │ │ + │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ + │ │ 3ee345b2e801448cace33a1097b9b49b ┆ 2024-01-15T10:40:45.123456789 ┆ [[6.0, 0.0, 0.0]] ┆ [[25.0, 1.0, 0.0]] │ │ + │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ + │ │ 3ee345b2e801448cace33a1097b9b49b ┆ 2024-01-15T10:44:45.123456789 ┆ null ┆ null │ │ + │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ + │ │ 3ee345b2e801448cace33a1097b9b49b ┆ 2024-01-15T10:46:45.123456789 ┆ null ┆ [[22.0, 1.0, 0.0]] │ │ + │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ + │ │ 3ee345b2e801448cace33a1097b9b49b ┆ 2024-01-15T10:48:45.123456789 ┆ null ┆ [[9.0, 1.0, 0.0]] │ │ + │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ + │ │ 3ee345b2e801448cace33a1097b9b49b ┆ 2024-01-15T10:50:45.123456789 ┆ null ┆ [[2.0, 1.0, 0.0]] │ │ + │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ + │ │ 3ee345b2e801448cace33a1097b9b49b ┆ 2024-01-15T10:52:45.123456789 ┆ [[12.0, 0.0, 0.0]] ┆ [[4.0, 1.0, 0.0]] │ │ + │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ + │ │ 3ee345b2e801448cace33a1097b9b49b ┆ 2024-01-15T11:00:45.123456789 ┆ null ┆ [[20.0, 1.0, 0.0]] │ │ + │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ + │ │ 3ee345b2e801448cace33a1097b9b49b ┆ 2024-01-15T11:02:45.123456789 ┆ [[17.0, 0.0, 0.0]] ┆ [[17.0, 1.0, 0.0]] │ │ + │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ + │ │ 3ee345b2e801448cace33a1097b9b49b ┆ 2024-01-15T11:04:45.123456789 ┆ [[18.0, 0.0, 0.0]] ┆ [[21.0, 1.0, 0.0]] │ │ + │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ + │ │ 3ee345b2e801448cace33a1097b9b49b ┆ 2024-01-15T11:12:45.123456789 ┆ null ┆ [[14.0, 1.0, 0.0]] │ │ + │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ + │ │ 3ee345b2e801448cace33a1097b9b49b ┆ 2024-01-15T11:14:45.123456789 ┆ null ┆ [[8.0, 1.0, 0.0]] │ │ + │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ + │ │ 3ee345b2e801448cace33a1097b9b49b ┆ 2024-01-15T11:16:45.123456789 ┆ [[24.0, 0.0, 0.0]] ┆ [[18.0, 1.0, 0.0]] │ │ + │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ + │ │ 3ee345b2e801448cace33a1097b9b49b ┆ 2024-01-15T11:18:45.123456789 ┆ [[25.0, 0.0, 0.0]] ┆ [[23.0, 1.0, 0.0]] │ │ + │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ + │ │ 68224eead5ed40838b3f3bdb0edfd2b2 ┆ 2024-01-15T10:30:45.123456789 ┆ null ┆ null │ │ + │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ + │ │ 68224eead5ed40838b3f3bdb0edfd2b2 ┆ 2024-01-15T10:32:45.123456789 ┆ null ┆ [[25.0, 1.0, 0.0]] │ │ + │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ + │ │ 68224eead5ed40838b3f3bdb0edfd2b2 ┆ 2024-01-15T10:34:45.123456789 ┆ [[3.0, 0.0, 0.0]] ┆ null │ │ + │ └──────────────────────────────────┴───────────────────────────────┴─────────────────────────────────────────────────┴─────────────────────────────────────────────────┘ │ + └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ Data truncated due to size. ''' # --- # name: test_dataframe_api_using_index_values.1 ''' - ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ - │ METADATA: │ - │ * version: 0.1.3 │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ ┌──────────────────────────────────┬───────────────────────────────┬─────────────────────────────────────────────────────┬─────────────────────────────────────────────────────┐ │ - │ │ rerun_segment_id ┆ time_1 ┆ /obj1:Points3D:positions ┆ /obj2:Points3D:positions │ │ - │ │ --- ┆ --- ┆ --- ┆ --- │ │ - │ │ type: Utf8 ┆ type: nullable Timestamp(ns) ┆ type: nullable List[nullable FixedSizeList[f32; 3]] ┆ type: nullable List[nullable FixedSizeList[f32; 3]] │ │ - │ │ ┆ index_name: time_1 ┆ archetype: Points3D ┆ archetype: Points3D │ │ - │ │ ┆ kind: index ┆ component: Points3D:positions ┆ component: Points3D:positions │ │ - │ │ ┆ ┆ component_type: Position3D ┆ component_type: Position3D │ │ - │ │ ┆ ┆ entity_path: /obj1 ┆ entity_path: /obj2 │ │ - │ │ ┆ ┆ kind: data ┆ kind: data │ │ - │ ╞══════════════════════════════════╪═══════════════════════════════╪═════════════════════════════════════════════════════╪═════════════════════════════════════════════════════╡ │ - │ │ 3ee345b2e801448cace33a1097b9b49b ┆ 2024-01-15T10:34:45.123456789 ┆ [[3.0, 0.0, 0.0]] ┆ [[13.0, 1.0, 0.0]] │ │ - │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ - │ │ 3ee345b2e801448cace33a1097b9b49b ┆ 2024-01-15T10:44:45.123456789 ┆ null ┆ null │ │ - │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ - │ │ 68224eead5ed40838b3f3bdb0edfd2b2 ┆ 2024-01-15T10:40:45.123456789 ┆ [[6.0, 0.0, 0.0]] ┆ null │ │ - │ └──────────────────────────────────┴───────────────────────────────┴─────────────────────────────────────────────────────┴─────────────────────────────────────────────────────┘ │ - └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ + ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ METADATA: │ + │ * version: 0.1.3 │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ ┌──────────────────────────────────┬───────────────────────────────┬─────────────────────────────────────────────────┬─────────────────────────────────────────────────┐ │ + │ │ rerun_segment_id ┆ time_1 ┆ /obj1:Points3D:positions ┆ /obj2:Points3D:positions │ │ + │ │ --- ┆ --- ┆ --- ┆ --- │ │ + │ │ type: non-null Utf8 ┆ type: Timestamp(ns) ┆ type: List(FixedSizeList(3 x non-null Float32)) ┆ type: List(FixedSizeList(3 x non-null Float32)) │ │ + │ │ ┆ index_name: time_1 ┆ archetype: Points3D ┆ archetype: Points3D │ │ + │ │ ┆ kind: index ┆ component: Points3D:positions ┆ component: Points3D:positions │ │ + │ │ ┆ ┆ component_type: Position3D ┆ component_type: Position3D │ │ + │ │ ┆ ┆ entity_path: /obj1 ┆ entity_path: /obj2 │ │ + │ │ ┆ ┆ kind: data ┆ kind: data │ │ + │ ╞══════════════════════════════════╪═══════════════════════════════╪═════════════════════════════════════════════════╪═════════════════════════════════════════════════╡ │ + │ │ 3ee345b2e801448cace33a1097b9b49b ┆ 2024-01-15T10:34:45.123456789 ┆ [[3.0, 0.0, 0.0]] ┆ [[13.0, 1.0, 0.0]] │ │ + │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ + │ │ 3ee345b2e801448cace33a1097b9b49b ┆ 2024-01-15T10:44:45.123456789 ┆ null ┆ null │ │ + │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ + │ │ 68224eead5ed40838b3f3bdb0edfd2b2 ┆ 2024-01-15T10:40:45.123456789 ┆ [[6.0, 0.0, 0.0]] ┆ null │ │ + │ └──────────────────────────────────┴───────────────────────────────┴─────────────────────────────────────────────────┴─────────────────────────────────────────────────┘ │ + └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ ''' # --- # name: test_dataframe_api_using_index_values.2 @@ -104,61 +104,61 @@ # --- # name: test_dataframe_api_using_index_values_dataframe ''' - ┌────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ - │ METADATA: │ - │ * version: 0.1.3 │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ ┌──────────────────────────────────┬───────────────────────────────┬─────────────────────────────────────────────────────┐ │ - │ │ rerun_segment_id ┆ time_1 ┆ /obj2:Points3D:positions │ │ - │ │ --- ┆ --- ┆ --- │ │ - │ │ type: Utf8 ┆ type: nullable Timestamp(ns) ┆ type: nullable List[nullable FixedSizeList[f32; 3]] │ │ - │ │ ┆ index_name: time_1 ┆ archetype: Points3D │ │ - │ │ ┆ kind: index ┆ component: Points3D:positions │ │ - │ │ ┆ ┆ component_type: Position3D │ │ - │ │ ┆ ┆ entity_path: /obj2 │ │ - │ │ ┆ ┆ kind: data │ │ - │ ╞══════════════════════════════════╪═══════════════════════════════╪═════════════════════════════════════════════════════╡ │ - │ │ 141a866deb2d49f69eb3215e8a404ffc ┆ 2024-01-15T10:30:45.123456789 ┆ [[38.0, 1.0, 0.0]] │ │ - │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ - │ │ 141a866deb2d49f69eb3215e8a404ffc ┆ 2024-01-15T10:32:45.123456789 ┆ [[35.0, 1.0, 0.0]] │ │ - │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ - │ │ 141a866deb2d49f69eb3215e8a404ffc ┆ 2024-01-15T10:34:45.123456789 ┆ null │ │ - │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ - │ │ 141a866deb2d49f69eb3215e8a404ffc ┆ 2024-01-15T10:36:45.123456789 ┆ [[1.0, 1.0, 0.0]] │ │ - │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ - │ │ 141a866deb2d49f69eb3215e8a404ffc ┆ 2024-01-15T10:38:45.123456789 ┆ [[9.0, 1.0, 0.0]] │ │ - │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ - │ │ 141a866deb2d49f69eb3215e8a404ffc ┆ 2024-01-15T10:40:45.123456789 ┆ null │ │ - │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ - │ │ 141a866deb2d49f69eb3215e8a404ffc ┆ 2024-01-15T10:42:45.123456789 ┆ [[33.0, 1.0, 0.0]] │ │ - │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ - │ │ 141a866deb2d49f69eb3215e8a404ffc ┆ 2024-01-15T10:44:45.123456789 ┆ [[25.0, 1.0, 0.0]] │ │ - │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ - │ │ 141a866deb2d49f69eb3215e8a404ffc ┆ 2024-01-15T10:48:45.123456789 ┆ null │ │ - │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ - │ │ 141a866deb2d49f69eb3215e8a404ffc ┆ 2024-01-15T10:52:45.123456789 ┆ [[6.0, 1.0, 0.0]] │ │ - │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ - │ │ 141a866deb2d49f69eb3215e8a404ffc ┆ 2024-01-15T10:54:45.123456789 ┆ [[17.0, 1.0, 0.0]] │ │ - │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ - │ │ 141a866deb2d49f69eb3215e8a404ffc ┆ 2024-01-15T10:56:45.123456789 ┆ [[48.0, 1.0, 0.0]] │ │ - │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ - │ │ 141a866deb2d49f69eb3215e8a404ffc ┆ 2024-01-15T10:58:45.123456789 ┆ [[10.0, 1.0, 0.0]] │ │ - │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ - │ │ 141a866deb2d49f69eb3215e8a404ffc ┆ 2024-01-15T11:02:45.123456789 ┆ [[5.0, 1.0, 0.0]] │ │ - │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ - │ │ 141a866deb2d49f69eb3215e8a404ffc ┆ 2024-01-15T11:04:45.123456789 ┆ null │ │ - │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ - │ │ 141a866deb2d49f69eb3215e8a404ffc ┆ 2024-01-15T11:06:45.123456789 ┆ [[11.0, 1.0, 0.0]] │ │ - │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ - │ │ 141a866deb2d49f69eb3215e8a404ffc ┆ 2024-01-15T11:08:45.123456789 ┆ [[32.0, 1.0, 0.0]] │ │ - │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ - │ │ 141a866deb2d49f69eb3215e8a404ffc ┆ 2024-01-15T11:10:45.123456789 ┆ [[31.0, 1.0, 0.0]] │ │ - │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ - │ │ 141a866deb2d49f69eb3215e8a404ffc ┆ 2024-01-15T11:16:45.123456789 ┆ [[24.0, 1.0, 0.0]] │ │ - │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ - │ │ 141a866deb2d49f69eb3215e8a404ffc ┆ 2024-01-15T11:18:45.123456789 ┆ [[30.0, 1.0, 0.0]] │ │ - │ └──────────────────────────────────┴───────────────────────────────┴─────────────────────────────────────────────────────┘ │ - └────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ + ┌────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ METADATA: │ + │ * version: 0.1.3 │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ ┌──────────────────────────────────┬───────────────────────────────┬─────────────────────────────────────────────────┐ │ + │ │ rerun_segment_id ┆ time_1 ┆ /obj2:Points3D:positions │ │ + │ │ --- ┆ --- ┆ --- │ │ + │ │ type: non-null Utf8 ┆ type: Timestamp(ns) ┆ type: List(FixedSizeList(3 x non-null Float32)) │ │ + │ │ ┆ index_name: time_1 ┆ archetype: Points3D │ │ + │ │ ┆ kind: index ┆ component: Points3D:positions │ │ + │ │ ┆ ┆ component_type: Position3D │ │ + │ │ ┆ ┆ entity_path: /obj2 │ │ + │ │ ┆ ┆ kind: data │ │ + │ ╞══════════════════════════════════╪═══════════════════════════════╪═════════════════════════════════════════════════╡ │ + │ │ 141a866deb2d49f69eb3215e8a404ffc ┆ 2024-01-15T10:30:45.123456789 ┆ [[38.0, 1.0, 0.0]] │ │ + │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ + │ │ 141a866deb2d49f69eb3215e8a404ffc ┆ 2024-01-15T10:32:45.123456789 ┆ [[35.0, 1.0, 0.0]] │ │ + │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ + │ │ 141a866deb2d49f69eb3215e8a404ffc ┆ 2024-01-15T10:34:45.123456789 ┆ null │ │ + │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ + │ │ 141a866deb2d49f69eb3215e8a404ffc ┆ 2024-01-15T10:36:45.123456789 ┆ [[1.0, 1.0, 0.0]] │ │ + │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ + │ │ 141a866deb2d49f69eb3215e8a404ffc ┆ 2024-01-15T10:38:45.123456789 ┆ [[9.0, 1.0, 0.0]] │ │ + │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ + │ │ 141a866deb2d49f69eb3215e8a404ffc ┆ 2024-01-15T10:40:45.123456789 ┆ null │ │ + │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ + │ │ 141a866deb2d49f69eb3215e8a404ffc ┆ 2024-01-15T10:42:45.123456789 ┆ [[33.0, 1.0, 0.0]] │ │ + │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ + │ │ 141a866deb2d49f69eb3215e8a404ffc ┆ 2024-01-15T10:44:45.123456789 ┆ [[25.0, 1.0, 0.0]] │ │ + │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ + │ │ 141a866deb2d49f69eb3215e8a404ffc ┆ 2024-01-15T10:48:45.123456789 ┆ null │ │ + │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ + │ │ 141a866deb2d49f69eb3215e8a404ffc ┆ 2024-01-15T10:52:45.123456789 ┆ [[6.0, 1.0, 0.0]] │ │ + │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ + │ │ 141a866deb2d49f69eb3215e8a404ffc ┆ 2024-01-15T10:54:45.123456789 ┆ [[17.0, 1.0, 0.0]] │ │ + │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ + │ │ 141a866deb2d49f69eb3215e8a404ffc ┆ 2024-01-15T10:56:45.123456789 ┆ [[48.0, 1.0, 0.0]] │ │ + │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ + │ │ 141a866deb2d49f69eb3215e8a404ffc ┆ 2024-01-15T10:58:45.123456789 ┆ [[10.0, 1.0, 0.0]] │ │ + │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ + │ │ 141a866deb2d49f69eb3215e8a404ffc ┆ 2024-01-15T11:02:45.123456789 ┆ [[5.0, 1.0, 0.0]] │ │ + │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ + │ │ 141a866deb2d49f69eb3215e8a404ffc ┆ 2024-01-15T11:04:45.123456789 ┆ null │ │ + │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ + │ │ 141a866deb2d49f69eb3215e8a404ffc ┆ 2024-01-15T11:06:45.123456789 ┆ [[11.0, 1.0, 0.0]] │ │ + │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ + │ │ 141a866deb2d49f69eb3215e8a404ffc ┆ 2024-01-15T11:08:45.123456789 ┆ [[32.0, 1.0, 0.0]] │ │ + │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ + │ │ 141a866deb2d49f69eb3215e8a404ffc ┆ 2024-01-15T11:10:45.123456789 ┆ [[31.0, 1.0, 0.0]] │ │ + │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ + │ │ 141a866deb2d49f69eb3215e8a404ffc ┆ 2024-01-15T11:16:45.123456789 ┆ [[24.0, 1.0, 0.0]] │ │ + │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ + │ │ 141a866deb2d49f69eb3215e8a404ffc ┆ 2024-01-15T11:18:45.123456789 ┆ [[30.0, 1.0, 0.0]] │ │ + │ └──────────────────────────────────┴───────────────────────────────┴─────────────────────────────────────────────────┘ │ + └────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ Data truncated due to size. ''' # --- @@ -196,29 +196,29 @@ # --- # name: test_dataframe_api_using_index_values_same_indices_on_all_segments ''' - ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ - │ METADATA: │ - │ * version: 0.1.3 │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ ┌──────────────────────────────────┬───────────────────────────────┬─────────────────────────────────────────────────────┬─────────────────────────────────────────────────────┐ │ - │ │ rerun_segment_id ┆ time_1 ┆ /obj1:Points3D:positions ┆ /obj2:Points3D:positions │ │ - │ │ --- ┆ --- ┆ --- ┆ --- │ │ - │ │ type: Utf8 ┆ type: nullable Timestamp(ns) ┆ type: nullable List[nullable FixedSizeList[f32; 3]] ┆ type: nullable List[nullable FixedSizeList[f32; 3]] │ │ - │ │ ┆ index_name: time_1 ┆ archetype: Points3D ┆ archetype: Points3D │ │ - │ │ ┆ kind: index ┆ component: Points3D:positions ┆ component: Points3D:positions │ │ - │ │ ┆ ┆ component_type: Position3D ┆ component_type: Position3D │ │ - │ │ ┆ ┆ entity_path: /obj1 ┆ entity_path: /obj2 │ │ - │ │ ┆ ┆ kind: data ┆ kind: data │ │ - │ ╞══════════════════════════════════╪═══════════════════════════════╪═════════════════════════════════════════════════════╪═════════════════════════════════════════════════════╡ │ - │ │ 3ee345b2e801448cace33a1097b9b49b ┆ 2024-01-15T10:34:45.123456789 ┆ [[3.0, 0.0, 0.0]] ┆ [[13.0, 1.0, 0.0]] │ │ - │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ - │ │ 3ee345b2e801448cace33a1097b9b49b ┆ 2024-01-15T10:44:45.123456789 ┆ null ┆ null │ │ - │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ - │ │ 68224eead5ed40838b3f3bdb0edfd2b2 ┆ 2024-01-15T10:34:45.123456789 ┆ [[3.0, 0.0, 0.0]] ┆ null │ │ - │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ - │ │ 68224eead5ed40838b3f3bdb0edfd2b2 ┆ 2024-01-15T10:44:45.123456789 ┆ [[8.0, 0.0, 0.0]] ┆ [[5.0, 1.0, 0.0]] │ │ - │ └──────────────────────────────────┴───────────────────────────────┴─────────────────────────────────────────────────────┴─────────────────────────────────────────────────────┘ │ - └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ + ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ METADATA: │ + │ * version: 0.1.3 │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ ┌──────────────────────────────────┬───────────────────────────────┬─────────────────────────────────────────────────┬─────────────────────────────────────────────────┐ │ + │ │ rerun_segment_id ┆ time_1 ┆ /obj1:Points3D:positions ┆ /obj2:Points3D:positions │ │ + │ │ --- ┆ --- ┆ --- ┆ --- │ │ + │ │ type: non-null Utf8 ┆ type: Timestamp(ns) ┆ type: List(FixedSizeList(3 x non-null Float32)) ┆ type: List(FixedSizeList(3 x non-null Float32)) │ │ + │ │ ┆ index_name: time_1 ┆ archetype: Points3D ┆ archetype: Points3D │ │ + │ │ ┆ kind: index ┆ component: Points3D:positions ┆ component: Points3D:positions │ │ + │ │ ┆ ┆ component_type: Position3D ┆ component_type: Position3D │ │ + │ │ ┆ ┆ entity_path: /obj1 ┆ entity_path: /obj2 │ │ + │ │ ┆ ┆ kind: data ┆ kind: data │ │ + │ ╞══════════════════════════════════╪═══════════════════════════════╪═════════════════════════════════════════════════╪═════════════════════════════════════════════════╡ │ + │ │ 3ee345b2e801448cace33a1097b9b49b ┆ 2024-01-15T10:34:45.123456789 ┆ [[3.0, 0.0, 0.0]] ┆ [[13.0, 1.0, 0.0]] │ │ + │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ + │ │ 3ee345b2e801448cace33a1097b9b49b ┆ 2024-01-15T10:44:45.123456789 ┆ null ┆ null │ │ + │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ + │ │ 68224eead5ed40838b3f3bdb0edfd2b2 ┆ 2024-01-15T10:34:45.123456789 ┆ [[3.0, 0.0, 0.0]] ┆ null │ │ + │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ + │ │ 68224eead5ed40838b3f3bdb0edfd2b2 ┆ 2024-01-15T10:44:45.123456789 ┆ [[8.0, 0.0, 0.0]] ┆ [[5.0, 1.0, 0.0]] │ │ + │ └──────────────────────────────────┴───────────────────────────────┴─────────────────────────────────────────────────┴─────────────────────────────────────────────────┘ │ + └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ ''' # --- # name: test_dataframe_api_using_index_values_same_indices_on_all_segments.1 @@ -526,61 +526,61 @@ # --- # name: test_dataset_view_reader ''' - ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ - │ METADATA: │ - │ * version: 0.1.3 │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ ┌─────────────────────────────────────────────────────┬──────────────────────┬──────────────────────────────┬──────────────────────────────────┬───────────────────────────────┬─────────────────────────────┬────────────────────┐ │ - │ │ /obj1:Points3D:positions ┆ log_tick ┆ log_time ┆ rerun_segment_id ┆ time_1 ┆ time_2 ┆ time_3 │ │ - │ │ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- │ │ - │ │ type: nullable List[nullable FixedSizeList[f32; 3]] ┆ type: nullable i64 ┆ type: nullable Timestamp(ns) ┆ type: Utf8 ┆ type: nullable Timestamp(ns) ┆ type: nullable Duration(ns) ┆ type: nullable i64 │ │ - │ │ archetype: Points3D ┆ index_name: log_tick ┆ index_name: log_time ┆ ┆ index_name: time_1 ┆ index_name: time_2 ┆ index_name: time_3 │ │ - │ │ component: Points3D:positions ┆ kind: index ┆ kind: index ┆ ┆ kind: index ┆ kind: index ┆ kind: index │ │ - │ │ component_type: Position3D ┆ ┆ ┆ ┆ ┆ ┆ │ │ - │ │ entity_path: /obj1 ┆ ┆ ┆ ┆ ┆ ┆ │ │ - │ │ kind: data ┆ ┆ ┆ ┆ ┆ ┆ │ │ - │ ╞═════════════════════════════════════════════════════╪══════════════════════╪══════════════════════════════╪══════════════════════════════════╪═══════════════════════════════╪═════════════════════════════╪════════════════════╡ │ - │ │ [[1.0, 0.0, 0.0]] ┆ 25 ┆ 2025-11-03T20:35:21.079952 ┆ 141a866deb2d49f69eb3215e8a404ffc ┆ 2024-01-15T10:30:45.123456789 ┆ PT12900S ┆ 46 │ │ - │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ - │ │ [[2.0, 0.0, 0.0]] ┆ 34 ┆ 2025-11-03T20:35:21.080080 ┆ 141a866deb2d49f69eb3215e8a404ffc ┆ 2024-01-15T10:32:45.123456789 ┆ PT12000S ┆ 6 │ │ - │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ - │ │ [[3.0, 0.0, 0.0]] ┆ 96 ┆ 2025-11-03T20:35:21.080980 ┆ 141a866deb2d49f69eb3215e8a404ffc ┆ 2024-01-15T10:34:45.123456789 ┆ PT9900S ┆ 30 │ │ - │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ - │ │ [[4.0, 0.0, 0.0]] ┆ 86 ┆ 2025-11-03T20:35:21.080836 ┆ 141a866deb2d49f69eb3215e8a404ffc ┆ 2024-01-15T10:36:45.123456789 ┆ PT1800S ┆ 50 │ │ - │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ - │ │ [[5.0, 0.0, 0.0]] ┆ 41 ┆ 2025-11-03T20:35:21.080180 ┆ 141a866deb2d49f69eb3215e8a404ffc ┆ 2024-01-15T10:38:45.123456789 ┆ PT4200S ┆ 15 │ │ - │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ - │ │ [[6.0, 0.0, 0.0]] ┆ 97 ┆ 2025-11-03T20:35:21.080997 ┆ 141a866deb2d49f69eb3215e8a404ffc ┆ 2024-01-15T10:40:45.123456789 ┆ PT13500S ┆ 9 │ │ - │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ - │ │ [[7.0, 0.0, 0.0]] ┆ 31 ┆ 2025-11-03T20:35:21.080037 ┆ 141a866deb2d49f69eb3215e8a404ffc ┆ 2024-01-15T10:42:45.123456789 ┆ PT11400S ┆ 47 │ │ - │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ - │ │ [[8.0, 0.0, 0.0]] ┆ 54 ┆ 2025-11-03T20:35:21.080362 ┆ 141a866deb2d49f69eb3215e8a404ffc ┆ 2024-01-15T10:44:45.123456789 ┆ PT9000S ┆ null │ │ - │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ - │ │ [[10.0, 0.0, 0.0]] ┆ 82 ┆ 2025-11-03T20:35:21.080779 ┆ 141a866deb2d49f69eb3215e8a404ffc ┆ 2024-01-15T10:48:45.123456789 ┆ null ┆ 27 │ │ - │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ - │ │ [[12.0, 0.0, 0.0]] ┆ 72 ┆ 2025-11-03T20:35:21.080635 ┆ 141a866deb2d49f69eb3215e8a404ffc ┆ 2024-01-15T10:52:45.123456789 ┆ PT3300S ┆ 10 │ │ - │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ - │ │ [[13.0, 0.0, 0.0]] ┆ 75 ┆ 2025-11-03T20:35:21.080678 ┆ 141a866deb2d49f69eb3215e8a404ffc ┆ 2024-01-15T10:54:45.123456789 ┆ PT6600S ┆ 12 │ │ - │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ - │ │ [[14.0, 0.0, 0.0]] ┆ 17 ┆ 2025-11-03T20:35:21.079838 ┆ 141a866deb2d49f69eb3215e8a404ffc ┆ 2024-01-15T10:56:45.123456789 ┆ PT15900S ┆ 17 │ │ - │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ - │ │ [[15.0, 0.0, 0.0]] ┆ 9 ┆ 2025-11-03T20:35:21.079716 ┆ 141a866deb2d49f69eb3215e8a404ffc ┆ 2024-01-15T10:58:45.123456789 ┆ PT4500S ┆ 21 │ │ - │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ - │ │ [[17.0, 0.0, 0.0]] ┆ 69 ┆ 2025-11-03T20:35:21.080591 ┆ 141a866deb2d49f69eb3215e8a404ffc ┆ 2024-01-15T11:02:45.123456789 ┆ PT3000S ┆ 5 │ │ - │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ - │ │ [[18.0, 0.0, 0.0]] ┆ 62 ┆ 2025-11-03T20:35:21.080486 ┆ 141a866deb2d49f69eb3215e8a404ffc ┆ 2024-01-15T11:04:45.123456789 ┆ PT9600S ┆ 8 │ │ - │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ - │ │ [[19.0, 0.0, 0.0]] ┆ 46 ┆ 2025-11-03T20:35:21.080249 ┆ 141a866deb2d49f69eb3215e8a404ffc ┆ 2024-01-15T11:06:45.123456789 ┆ PT4800S ┆ 34 │ │ - │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ - │ │ [[20.0, 0.0, 0.0]] ┆ 57 ┆ 2025-11-03T20:35:21.080412 ┆ 141a866deb2d49f69eb3215e8a404ffc ┆ 2024-01-15T11:08:45.123456789 ┆ PT11100S ┆ 13 │ │ - │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ - │ │ [[21.0, 0.0, 0.0]] ┆ 114 ┆ 2025-11-03T20:35:21.081240 ┆ 141a866deb2d49f69eb3215e8a404ffc ┆ 2024-01-15T11:10:45.123456789 ┆ null ┆ null │ │ - │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ - │ │ [[24.0, 0.0, 0.0]] ┆ 91 ┆ 2025-11-03T20:35:21.080911 ┆ 141a866deb2d49f69eb3215e8a404ffc ┆ 2024-01-15T11:16:45.123456789 ┆ PT8700S ┆ 40 │ │ - │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ - │ │ [[25.0, 0.0, 0.0]] ┆ 38 ┆ 2025-11-03T20:35:21.080138 ┆ 141a866deb2d49f69eb3215e8a404ffc ┆ 2024-01-15T11:18:45.123456789 ┆ PT10500S ┆ 3 │ │ - │ └─────────────────────────────────────────────────────┴──────────────────────┴──────────────────────────────┴──────────────────────────────────┴───────────────────────────────┴─────────────────────────────┴────────────────────┘ │ - └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ + ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ METADATA: │ + │ * version: 0.1.3 │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ ┌─────────────────────────────────────────────────┬──────────────────────┬────────────────────────────┬──────────────────────────────────┬───────────────────────────────┬────────────────────┬────────────────────┐ │ + │ │ /obj1:Points3D:positions ┆ log_tick ┆ log_time ┆ rerun_segment_id ┆ time_1 ┆ time_2 ┆ time_3 │ │ + │ │ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- │ │ + │ │ type: List(FixedSizeList(3 x non-null Float32)) ┆ type: Int64 ┆ type: Timestamp(ns) ┆ type: non-null Utf8 ┆ type: Timestamp(ns) ┆ type: Duration(ns) ┆ type: Int64 │ │ + │ │ archetype: Points3D ┆ index_name: log_tick ┆ index_name: log_time ┆ ┆ index_name: time_1 ┆ index_name: time_2 ┆ index_name: time_3 │ │ + │ │ component: Points3D:positions ┆ kind: index ┆ kind: index ┆ ┆ kind: index ┆ kind: index ┆ kind: index │ │ + │ │ component_type: Position3D ┆ ┆ ┆ ┆ ┆ ┆ │ │ + │ │ entity_path: /obj1 ┆ ┆ ┆ ┆ ┆ ┆ │ │ + │ │ kind: data ┆ ┆ ┆ ┆ ┆ ┆ │ │ + │ ╞═════════════════════════════════════════════════╪══════════════════════╪════════════════════════════╪══════════════════════════════════╪═══════════════════════════════╪════════════════════╪════════════════════╡ │ + │ │ [[1.0, 0.0, 0.0]] ┆ 25 ┆ 2025-11-03T20:35:21.079952 ┆ 141a866deb2d49f69eb3215e8a404ffc ┆ 2024-01-15T10:30:45.123456789 ┆ PT12900S ┆ 46 │ │ + │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ + │ │ [[2.0, 0.0, 0.0]] ┆ 34 ┆ 2025-11-03T20:35:21.080080 ┆ 141a866deb2d49f69eb3215e8a404ffc ┆ 2024-01-15T10:32:45.123456789 ┆ PT12000S ┆ 6 │ │ + │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ + │ │ [[3.0, 0.0, 0.0]] ┆ 96 ┆ 2025-11-03T20:35:21.080980 ┆ 141a866deb2d49f69eb3215e8a404ffc ┆ 2024-01-15T10:34:45.123456789 ┆ PT9900S ┆ 30 │ │ + │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ + │ │ [[4.0, 0.0, 0.0]] ┆ 86 ┆ 2025-11-03T20:35:21.080836 ┆ 141a866deb2d49f69eb3215e8a404ffc ┆ 2024-01-15T10:36:45.123456789 ┆ PT1800S ┆ 50 │ │ + │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ + │ │ [[5.0, 0.0, 0.0]] ┆ 41 ┆ 2025-11-03T20:35:21.080180 ┆ 141a866deb2d49f69eb3215e8a404ffc ┆ 2024-01-15T10:38:45.123456789 ┆ PT4200S ┆ 15 │ │ + │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ + │ │ [[6.0, 0.0, 0.0]] ┆ 97 ┆ 2025-11-03T20:35:21.080997 ┆ 141a866deb2d49f69eb3215e8a404ffc ┆ 2024-01-15T10:40:45.123456789 ┆ PT13500S ┆ 9 │ │ + │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ + │ │ [[7.0, 0.0, 0.0]] ┆ 31 ┆ 2025-11-03T20:35:21.080037 ┆ 141a866deb2d49f69eb3215e8a404ffc ┆ 2024-01-15T10:42:45.123456789 ┆ PT11400S ┆ 47 │ │ + │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ + │ │ [[8.0, 0.0, 0.0]] ┆ 54 ┆ 2025-11-03T20:35:21.080362 ┆ 141a866deb2d49f69eb3215e8a404ffc ┆ 2024-01-15T10:44:45.123456789 ┆ PT9000S ┆ null │ │ + │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ + │ │ [[10.0, 0.0, 0.0]] ┆ 82 ┆ 2025-11-03T20:35:21.080779 ┆ 141a866deb2d49f69eb3215e8a404ffc ┆ 2024-01-15T10:48:45.123456789 ┆ null ┆ 27 │ │ + │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ + │ │ [[12.0, 0.0, 0.0]] ┆ 72 ┆ 2025-11-03T20:35:21.080635 ┆ 141a866deb2d49f69eb3215e8a404ffc ┆ 2024-01-15T10:52:45.123456789 ┆ PT3300S ┆ 10 │ │ + │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ + │ │ [[13.0, 0.0, 0.0]] ┆ 75 ┆ 2025-11-03T20:35:21.080678 ┆ 141a866deb2d49f69eb3215e8a404ffc ┆ 2024-01-15T10:54:45.123456789 ┆ PT6600S ┆ 12 │ │ + │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ + │ │ [[14.0, 0.0, 0.0]] ┆ 17 ┆ 2025-11-03T20:35:21.079838 ┆ 141a866deb2d49f69eb3215e8a404ffc ┆ 2024-01-15T10:56:45.123456789 ┆ PT15900S ┆ 17 │ │ + │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ + │ │ [[15.0, 0.0, 0.0]] ┆ 9 ┆ 2025-11-03T20:35:21.079716 ┆ 141a866deb2d49f69eb3215e8a404ffc ┆ 2024-01-15T10:58:45.123456789 ┆ PT4500S ┆ 21 │ │ + │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ + │ │ [[17.0, 0.0, 0.0]] ┆ 69 ┆ 2025-11-03T20:35:21.080591 ┆ 141a866deb2d49f69eb3215e8a404ffc ┆ 2024-01-15T11:02:45.123456789 ┆ PT3000S ┆ 5 │ │ + │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ + │ │ [[18.0, 0.0, 0.0]] ┆ 62 ┆ 2025-11-03T20:35:21.080486 ┆ 141a866deb2d49f69eb3215e8a404ffc ┆ 2024-01-15T11:04:45.123456789 ┆ PT9600S ┆ 8 │ │ + │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ + │ │ [[19.0, 0.0, 0.0]] ┆ 46 ┆ 2025-11-03T20:35:21.080249 ┆ 141a866deb2d49f69eb3215e8a404ffc ┆ 2024-01-15T11:06:45.123456789 ┆ PT4800S ┆ 34 │ │ + │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ + │ │ [[20.0, 0.0, 0.0]] ┆ 57 ┆ 2025-11-03T20:35:21.080412 ┆ 141a866deb2d49f69eb3215e8a404ffc ┆ 2024-01-15T11:08:45.123456789 ┆ PT11100S ┆ 13 │ │ + │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ + │ │ [[21.0, 0.0, 0.0]] ┆ 114 ┆ 2025-11-03T20:35:21.081240 ┆ 141a866deb2d49f69eb3215e8a404ffc ┆ 2024-01-15T11:10:45.123456789 ┆ null ┆ null │ │ + │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ + │ │ [[24.0, 0.0, 0.0]] ┆ 91 ┆ 2025-11-03T20:35:21.080911 ┆ 141a866deb2d49f69eb3215e8a404ffc ┆ 2024-01-15T11:16:45.123456789 ┆ PT8700S ┆ 40 │ │ + │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ + │ │ [[25.0, 0.0, 0.0]] ┆ 38 ┆ 2025-11-03T20:35:21.080138 ┆ 141a866deb2d49f69eb3215e8a404ffc ┆ 2024-01-15T11:18:45.123456789 ┆ PT10500S ┆ 3 │ │ + │ └─────────────────────────────────────────────────┴──────────────────────┴────────────────────────────┴──────────────────────────────────┴───────────────────────────────┴────────────────────┴────────────────────┘ │ + └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ Data truncated due to size. ''' # --- diff --git a/rerun_py/tests/e2e_redap_tests/test_index_ranges.py b/rerun_py/tests/e2e_redap_tests/test_index_ranges.py index 577cbdb4d140..e0a664788de5 100644 --- a/rerun_py/tests/e2e_redap_tests/test_index_ranges.py +++ b/rerun_py/tests/e2e_redap_tests/test_index_ranges.py @@ -29,40 +29,40 @@ def test_index_ranges(readonly_test_dataset: DatasetEntry) -> None: ) assert str(df) == inline_snapshot("""\ -┌────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ METADATA: │ -│ * version: 0.1.3 │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ ┌──────────────────────────────────┬──────────────────────┬──────────────────────┬──────────────────────────────┬──────────────────────────────┬───────────────────────────────┬───────────────────────────────┬─────────────────────────────┬─────────────────────────────┬──────────────────────┬──────────────────────┐ │ -│ │ rerun_segment_id ┆ log_tick:start ┆ log_tick:end ┆ log_time:start ┆ log_time:end ┆ time_1:start ┆ time_1:end ┆ time_2:start ┆ time_2:end ┆ time_3:start ┆ time_3:end │ │ -│ │ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- │ │ -│ │ type: Utf8 ┆ type: nullable i64 ┆ type: nullable i64 ┆ type: nullable Timestamp(ns) ┆ type: nullable Timestamp(ns) ┆ type: nullable Timestamp(ns) ┆ type: nullable Timestamp(ns) ┆ type: nullable Duration(ns) ┆ type: nullable Duration(ns) ┆ type: nullable i64 ┆ type: nullable i64 │ │ -│ │ ┆ index: log_tick ┆ index: log_tick ┆ index: log_time ┆ index: log_time ┆ index: time_1 ┆ index: time_1 ┆ index: time_2 ┆ index: time_2 ┆ index: time_3 ┆ index: time_3 │ │ -│ │ ┆ index_kind: sequence ┆ index_kind: sequence ┆ index_kind: timestamp ┆ index_kind: timestamp ┆ index_kind: timestamp ┆ index_kind: timestamp ┆ index_kind: duration ┆ index_kind: duration ┆ index_kind: sequence ┆ index_kind: sequence │ │ -│ │ ┆ index_marker: start ┆ index_marker: end ┆ index_marker: start ┆ index_marker: end ┆ index_marker: start ┆ index_marker: end ┆ index_marker: start ┆ index_marker: end ┆ index_marker: start ┆ index_marker: end │ │ -│ │ ┆ kind: index ┆ kind: index ┆ kind: index ┆ kind: index ┆ kind: index ┆ kind: index ┆ kind: index ┆ kind: index ┆ kind: index ┆ kind: index │ │ -│ ╞══════════════════════════════════╪══════════════════════╪══════════════════════╪══════════════════════════════╪══════════════════════════════╪═══════════════════════════════╪═══════════════════════════════╪═════════════════════════════╪═════════════════════════════╪══════════════════════╪══════════════════════╡ │ -│ │ 141a866deb2d49f69eb3215e8a404ffc ┆ 1 ┆ 115 ┆ 2025-11-03T20:35:21.079585 ┆ 2025-11-03T20:35:21.081253 ┆ 2024-01-15T10:30:45.123456789 ┆ 2024-01-15T12:04:45.123456789 ┆ PT1800S ┆ PT16200S ┆ 1 ┆ 50 │ │ -│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ -│ │ 24598969c97a4154a1ad0a262ee31b97 ┆ 1 ┆ 57 ┆ 2025-11-03T20:35:21.088656 ┆ 2025-11-03T20:35:21.089483 ┆ 2024-01-15T10:30:45.123456789 ┆ 2024-01-15T11:18:45.123456789 ┆ PT1800S ┆ PT9000S ┆ 1 ┆ 22 │ │ -│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ -│ │ 3ee345b2e801448cace33a1097b9b49b ┆ 1 ┆ 60 ┆ 2025-11-03T20:35:21.077357 ┆ 2025-11-03T20:35:21.078218 ┆ 2024-01-15T10:30:45.123456789 ┆ 2024-01-15T11:18:45.123456789 ┆ PT1800S ┆ PT9000S ┆ 1 ┆ 25 │ │ -│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ -│ │ 45e562f3abc24cfbbcf49ad30fa04b47 ┆ 1 ┆ 58 ┆ 2025-11-03T20:35:21.053657 ┆ 2025-11-03T20:35:21.054504 ┆ 2024-01-15T10:30:45.123456789 ┆ 2024-01-15T11:18:45.123456789 ┆ PT1800S ┆ PT9000S ┆ 1 ┆ 25 │ │ -│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ -│ │ 526f111faae1465d865d80e9a5c9eb6d ┆ 1 ┆ 64 ┆ 2025-11-03T20:35:21.046720 ┆ 2025-11-03T20:35:21.047718 ┆ 2024-01-15T10:30:45.123456789 ┆ 2024-01-15T11:16:45.123456789 ┆ PT1800S ┆ PT9000S ┆ 1 ┆ 25 │ │ -│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ -│ │ 68224eead5ed40838b3f3bdb0edfd2b2 ┆ 1 ┆ 59 ┆ 2025-11-03T20:35:21.058931 ┆ 2025-11-03T20:35:21.059812 ┆ 2024-01-15T10:30:45.123456789 ┆ 2024-01-15T11:18:45.123456789 ┆ PT1800S ┆ PT9000S ┆ 1 ┆ 25 │ │ -│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ -│ │ 793d8cf8a1764d9c902bbd6f851e5e43 ┆ 1 ┆ 63 ┆ 2025-11-03T20:35:21.083036 ┆ 2025-11-03T20:35:21.083967 ┆ 2024-01-15T10:30:45.123456789 ┆ 2024-01-15T11:16:45.123456789 ┆ PT2100S ┆ PT8700S ┆ 2 ┆ 25 │ │ -│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ -│ │ 89b618fcbb1e427aa82cd43edb7c3bec ┆ 1 ┆ 115 ┆ 2025-11-03T20:35:21.049729 ┆ 2025-11-03T20:35:21.051456 ┆ 2024-01-15T10:30:45.123456789 ┆ 2024-01-15T12:08:45.123456789 ┆ PT1800S ┆ PT16500S ┆ 1 ┆ 50 │ │ -│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ -│ │ 8a81e65b49a647a592aaf41b4004a381 ┆ 1 ┆ 117 ┆ 2025-11-03T20:35:21.073535 ┆ 2025-11-03T20:35:21.075319 ┆ 2024-01-15T10:30:45.123456789 ┆ 2024-01-15T12:08:45.123456789 ┆ PT1800S ┆ PT16500S ┆ 1 ┆ 49 │ │ -│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ -│ │ 909bfa81104c4b7b82f49b3dd3e8653e ┆ 1 ┆ 58 ┆ 2025-11-03T20:35:21.064523 ┆ 2025-11-03T20:35:21.065506 ┆ 2024-01-15T10:30:45.123456789 ┆ 2024-01-15T11:18:45.123456789 ┆ PT1800S ┆ PT9000S ┆ 3 ┆ 25 │ │ -│ └──────────────────────────────────┴──────────────────────┴──────────────────────┴──────────────────────────────┴──────────────────────────────┴───────────────────────────────┴───────────────────────────────┴─────────────────────────────┴─────────────────────────────┴──────────────────────┴──────────────────────┘ │ -└────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘\ +┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ METADATA: │ +│ * version: 0.1.3 │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ ┌──────────────────────────────────┬──────────────────────┬──────────────────────┬────────────────────────────┬────────────────────────────┬───────────────────────────────┬───────────────────────────────┬──────────────────────┬──────────────────────┬──────────────────────┬──────────────────────┐ │ +│ │ rerun_segment_id ┆ log_tick:start ┆ log_tick:end ┆ log_time:start ┆ log_time:end ┆ time_1:start ┆ time_1:end ┆ time_2:start ┆ time_2:end ┆ time_3:start ┆ time_3:end │ │ +│ │ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- │ │ +│ │ type: non-null Utf8 ┆ type: Int64 ┆ type: Int64 ┆ type: Timestamp(ns) ┆ type: Timestamp(ns) ┆ type: Timestamp(ns) ┆ type: Timestamp(ns) ┆ type: Duration(ns) ┆ type: Duration(ns) ┆ type: Int64 ┆ type: Int64 │ │ +│ │ ┆ index: log_tick ┆ index: log_tick ┆ index: log_time ┆ index: log_time ┆ index: time_1 ┆ index: time_1 ┆ index: time_2 ┆ index: time_2 ┆ index: time_3 ┆ index: time_3 │ │ +│ │ ┆ index_kind: sequence ┆ index_kind: sequence ┆ index_kind: timestamp ┆ index_kind: timestamp ┆ index_kind: timestamp ┆ index_kind: timestamp ┆ index_kind: duration ┆ index_kind: duration ┆ index_kind: sequence ┆ index_kind: sequence │ │ +│ │ ┆ index_marker: start ┆ index_marker: end ┆ index_marker: start ┆ index_marker: end ┆ index_marker: start ┆ index_marker: end ┆ index_marker: start ┆ index_marker: end ┆ index_marker: start ┆ index_marker: end │ │ +│ │ ┆ kind: index ┆ kind: index ┆ kind: index ┆ kind: index ┆ kind: index ┆ kind: index ┆ kind: index ┆ kind: index ┆ kind: index ┆ kind: index │ │ +│ ╞══════════════════════════════════╪══════════════════════╪══════════════════════╪════════════════════════════╪════════════════════════════╪═══════════════════════════════╪═══════════════════════════════╪══════════════════════╪══════════════════════╪══════════════════════╪══════════════════════╡ │ +│ │ 141a866deb2d49f69eb3215e8a404ffc ┆ 1 ┆ 115 ┆ 2025-11-03T20:35:21.079585 ┆ 2025-11-03T20:35:21.081253 ┆ 2024-01-15T10:30:45.123456789 ┆ 2024-01-15T12:04:45.123456789 ┆ PT1800S ┆ PT16200S ┆ 1 ┆ 50 │ │ +│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ +│ │ 24598969c97a4154a1ad0a262ee31b97 ┆ 1 ┆ 57 ┆ 2025-11-03T20:35:21.088656 ┆ 2025-11-03T20:35:21.089483 ┆ 2024-01-15T10:30:45.123456789 ┆ 2024-01-15T11:18:45.123456789 ┆ PT1800S ┆ PT9000S ┆ 1 ┆ 22 │ │ +│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ +│ │ 3ee345b2e801448cace33a1097b9b49b ┆ 1 ┆ 60 ┆ 2025-11-03T20:35:21.077357 ┆ 2025-11-03T20:35:21.078218 ┆ 2024-01-15T10:30:45.123456789 ┆ 2024-01-15T11:18:45.123456789 ┆ PT1800S ┆ PT9000S ┆ 1 ┆ 25 │ │ +│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ +│ │ 45e562f3abc24cfbbcf49ad30fa04b47 ┆ 1 ┆ 58 ┆ 2025-11-03T20:35:21.053657 ┆ 2025-11-03T20:35:21.054504 ┆ 2024-01-15T10:30:45.123456789 ┆ 2024-01-15T11:18:45.123456789 ┆ PT1800S ┆ PT9000S ┆ 1 ┆ 25 │ │ +│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ +│ │ 526f111faae1465d865d80e9a5c9eb6d ┆ 1 ┆ 64 ┆ 2025-11-03T20:35:21.046720 ┆ 2025-11-03T20:35:21.047718 ┆ 2024-01-15T10:30:45.123456789 ┆ 2024-01-15T11:16:45.123456789 ┆ PT1800S ┆ PT9000S ┆ 1 ┆ 25 │ │ +│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ +│ │ 68224eead5ed40838b3f3bdb0edfd2b2 ┆ 1 ┆ 59 ┆ 2025-11-03T20:35:21.058931 ┆ 2025-11-03T20:35:21.059812 ┆ 2024-01-15T10:30:45.123456789 ┆ 2024-01-15T11:18:45.123456789 ┆ PT1800S ┆ PT9000S ┆ 1 ┆ 25 │ │ +│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ +│ │ 793d8cf8a1764d9c902bbd6f851e5e43 ┆ 1 ┆ 63 ┆ 2025-11-03T20:35:21.083036 ┆ 2025-11-03T20:35:21.083967 ┆ 2024-01-15T10:30:45.123456789 ┆ 2024-01-15T11:16:45.123456789 ┆ PT2100S ┆ PT8700S ┆ 2 ┆ 25 │ │ +│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ +│ │ 89b618fcbb1e427aa82cd43edb7c3bec ┆ 1 ┆ 115 ┆ 2025-11-03T20:35:21.049729 ┆ 2025-11-03T20:35:21.051456 ┆ 2024-01-15T10:30:45.123456789 ┆ 2024-01-15T12:08:45.123456789 ┆ PT1800S ┆ PT16500S ┆ 1 ┆ 50 │ │ +│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ +│ │ 8a81e65b49a647a592aaf41b4004a381 ┆ 1 ┆ 117 ┆ 2025-11-03T20:35:21.073535 ┆ 2025-11-03T20:35:21.075319 ┆ 2024-01-15T10:30:45.123456789 ┆ 2024-01-15T12:08:45.123456789 ┆ PT1800S ┆ PT16500S ┆ 1 ┆ 49 │ │ +│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ +│ │ 909bfa81104c4b7b82f49b3dd3e8653e ┆ 1 ┆ 58 ┆ 2025-11-03T20:35:21.064523 ┆ 2025-11-03T20:35:21.065506 ┆ 2024-01-15T10:30:45.123456789 ┆ 2024-01-15T11:18:45.123456789 ┆ PT1800S ┆ PT9000S ┆ 3 ┆ 25 │ │ +│ └──────────────────────────────────┴──────────────────────┴──────────────────────┴────────────────────────────┴────────────────────────────┴───────────────────────────────┴───────────────────────────────┴──────────────────────┴──────────────────────┴──────────────────────┴──────────────────────┘ │ +└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘\ """) @@ -89,24 +89,24 @@ def test_index_ranges_dataset_view(readonly_test_dataset: DatasetEntry) -> None: df = view.get_index_ranges().sort("rerun_segment_id").select(*column_orders) assert str(df) == inline_snapshot("""\ -┌────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ METADATA: │ -│ * version: 0.1.3 │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ ┌──────────────────────────────────┬──────────────────────┬──────────────────────┬──────────────────────────────┬──────────────────────────────┬───────────────────────────────┬───────────────────────────────┬─────────────────────────────┬─────────────────────────────┬──────────────────────┬──────────────────────┐ │ -│ │ rerun_segment_id ┆ log_tick:start ┆ log_tick:end ┆ log_time:start ┆ log_time:end ┆ time_1:start ┆ time_1:end ┆ time_2:start ┆ time_2:end ┆ time_3:start ┆ time_3:end │ │ -│ │ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- │ │ -│ │ type: Utf8 ┆ type: nullable i64 ┆ type: nullable i64 ┆ type: nullable Timestamp(ns) ┆ type: nullable Timestamp(ns) ┆ type: nullable Timestamp(ns) ┆ type: nullable Timestamp(ns) ┆ type: nullable Duration(ns) ┆ type: nullable Duration(ns) ┆ type: nullable i64 ┆ type: nullable i64 │ │ -│ │ ┆ index: log_tick ┆ index: log_tick ┆ index: log_time ┆ index: log_time ┆ index: time_1 ┆ index: time_1 ┆ index: time_2 ┆ index: time_2 ┆ index: time_3 ┆ index: time_3 │ │ -│ │ ┆ index_kind: sequence ┆ index_kind: sequence ┆ index_kind: timestamp ┆ index_kind: timestamp ┆ index_kind: timestamp ┆ index_kind: timestamp ┆ index_kind: duration ┆ index_kind: duration ┆ index_kind: sequence ┆ index_kind: sequence │ │ -│ │ ┆ index_marker: start ┆ index_marker: end ┆ index_marker: start ┆ index_marker: end ┆ index_marker: start ┆ index_marker: end ┆ index_marker: start ┆ index_marker: end ┆ index_marker: start ┆ index_marker: end │ │ -│ │ ┆ kind: index ┆ kind: index ┆ kind: index ┆ kind: index ┆ kind: index ┆ kind: index ┆ kind: index ┆ kind: index ┆ kind: index ┆ kind: index │ │ -│ ╞══════════════════════════════════╪══════════════════════╪══════════════════════╪══════════════════════════════╪══════════════════════════════╪═══════════════════════════════╪═══════════════════════════════╪═════════════════════════════╪═════════════════════════════╪══════════════════════╪══════════════════════╡ │ -│ │ 141a866deb2d49f69eb3215e8a404ffc ┆ 1 ┆ 115 ┆ 2025-11-03T20:35:21.079585 ┆ 2025-11-03T20:35:21.081253 ┆ 2024-01-15T10:30:45.123456789 ┆ 2024-01-15T12:04:45.123456789 ┆ PT1800S ┆ PT16200S ┆ 1 ┆ 50 │ │ -│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ -│ │ 3ee345b2e801448cace33a1097b9b49b ┆ 1 ┆ 60 ┆ 2025-11-03T20:35:21.077357 ┆ 2025-11-03T20:35:21.078218 ┆ 2024-01-15T10:30:45.123456789 ┆ 2024-01-15T11:18:45.123456789 ┆ PT1800S ┆ PT9000S ┆ 1 ┆ 25 │ │ -│ └──────────────────────────────────┴──────────────────────┴──────────────────────┴──────────────────────────────┴──────────────────────────────┴───────────────────────────────┴───────────────────────────────┴─────────────────────────────┴─────────────────────────────┴──────────────────────┴──────────────────────┘ │ -└────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘\ +┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ METADATA: │ +│ * version: 0.1.3 │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ ┌──────────────────────────────────┬──────────────────────┬──────────────────────┬────────────────────────────┬────────────────────────────┬───────────────────────────────┬───────────────────────────────┬──────────────────────┬──────────────────────┬──────────────────────┬──────────────────────┐ │ +│ │ rerun_segment_id ┆ log_tick:start ┆ log_tick:end ┆ log_time:start ┆ log_time:end ┆ time_1:start ┆ time_1:end ┆ time_2:start ┆ time_2:end ┆ time_3:start ┆ time_3:end │ │ +│ │ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- │ │ +│ │ type: non-null Utf8 ┆ type: Int64 ┆ type: Int64 ┆ type: Timestamp(ns) ┆ type: Timestamp(ns) ┆ type: Timestamp(ns) ┆ type: Timestamp(ns) ┆ type: Duration(ns) ┆ type: Duration(ns) ┆ type: Int64 ┆ type: Int64 │ │ +│ │ ┆ index: log_tick ┆ index: log_tick ┆ index: log_time ┆ index: log_time ┆ index: time_1 ┆ index: time_1 ┆ index: time_2 ┆ index: time_2 ┆ index: time_3 ┆ index: time_3 │ │ +│ │ ┆ index_kind: sequence ┆ index_kind: sequence ┆ index_kind: timestamp ┆ index_kind: timestamp ┆ index_kind: timestamp ┆ index_kind: timestamp ┆ index_kind: duration ┆ index_kind: duration ┆ index_kind: sequence ┆ index_kind: sequence │ │ +│ │ ┆ index_marker: start ┆ index_marker: end ┆ index_marker: start ┆ index_marker: end ┆ index_marker: start ┆ index_marker: end ┆ index_marker: start ┆ index_marker: end ┆ index_marker: start ┆ index_marker: end │ │ +│ │ ┆ kind: index ┆ kind: index ┆ kind: index ┆ kind: index ┆ kind: index ┆ kind: index ┆ kind: index ┆ kind: index ┆ kind: index ┆ kind: index │ │ +│ ╞══════════════════════════════════╪══════════════════════╪══════════════════════╪════════════════════════════╪════════════════════════════╪═══════════════════════════════╪═══════════════════════════════╪══════════════════════╪══════════════════════╪══════════════════════╪══════════════════════╡ │ +│ │ 141a866deb2d49f69eb3215e8a404ffc ┆ 1 ┆ 115 ┆ 2025-11-03T20:35:21.079585 ┆ 2025-11-03T20:35:21.081253 ┆ 2024-01-15T10:30:45.123456789 ┆ 2024-01-15T12:04:45.123456789 ┆ PT1800S ┆ PT16200S ┆ 1 ┆ 50 │ │ +│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ +│ │ 3ee345b2e801448cace33a1097b9b49b ┆ 1 ┆ 60 ┆ 2025-11-03T20:35:21.077357 ┆ 2025-11-03T20:35:21.078218 ┆ 2024-01-15T10:30:45.123456789 ┆ 2024-01-15T11:18:45.123456789 ┆ PT1800S ┆ PT9000S ┆ 1 ┆ 25 │ │ +│ └──────────────────────────────────┴──────────────────────┴──────────────────────┴────────────────────────────┴────────────────────────────┴───────────────────────────────┴───────────────────────────────┴──────────────────────┴──────────────────────┴──────────────────────┴──────────────────────┘ │ +└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘\ """) # Note: the ranges are relative to the actual data being filtered, see how the ranges differ here @@ -115,22 +115,22 @@ def test_index_ranges_dataset_view(readonly_test_dataset: DatasetEntry) -> None: df = view.get_index_ranges().sort("rerun_segment_id").select(*column_orders) assert str(df) == inline_snapshot("""\ -┌────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ METADATA: │ -│ * version: 0.1.3 │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ ┌──────────────────────────────────┬──────────────────────┬──────────────────────┬──────────────────────────────┬──────────────────────────────┬───────────────────────────────┬───────────────────────────────┬─────────────────────────────┬─────────────────────────────┬──────────────────────┬──────────────────────┐ │ -│ │ rerun_segment_id ┆ log_tick:start ┆ log_tick:end ┆ log_time:start ┆ log_time:end ┆ time_1:start ┆ time_1:end ┆ time_2:start ┆ time_2:end ┆ time_3:start ┆ time_3:end │ │ -│ │ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- │ │ -│ │ type: Utf8 ┆ type: nullable i64 ┆ type: nullable i64 ┆ type: nullable Timestamp(ns) ┆ type: nullable Timestamp(ns) ┆ type: nullable Timestamp(ns) ┆ type: nullable Timestamp(ns) ┆ type: nullable Duration(ns) ┆ type: nullable Duration(ns) ┆ type: nullable i64 ┆ type: nullable i64 │ │ -│ │ ┆ index: log_tick ┆ index: log_tick ┆ index: log_time ┆ index: log_time ┆ index: time_1 ┆ index: time_1 ┆ index: time_2 ┆ index: time_2 ┆ index: time_3 ┆ index: time_3 │ │ -│ │ ┆ index_kind: sequence ┆ index_kind: sequence ┆ index_kind: timestamp ┆ index_kind: timestamp ┆ index_kind: timestamp ┆ index_kind: timestamp ┆ index_kind: duration ┆ index_kind: duration ┆ index_kind: sequence ┆ index_kind: sequence │ │ -│ │ ┆ index_marker: start ┆ index_marker: end ┆ index_marker: start ┆ index_marker: end ┆ index_marker: start ┆ index_marker: end ┆ index_marker: start ┆ index_marker: end ┆ index_marker: start ┆ index_marker: end │ │ -│ │ ┆ kind: index ┆ kind: index ┆ kind: index ┆ kind: index ┆ kind: index ┆ kind: index ┆ kind: index ┆ kind: index ┆ kind: index ┆ kind: index │ │ -│ ╞══════════════════════════════════╪══════════════════════╪══════════════════════╪══════════════════════════════╪══════════════════════════════╪═══════════════════════════════╪═══════════════════════════════╪═════════════════════════════╪═════════════════════════════╪══════════════════════╪══════════════════════╡ │ -│ │ 141a866deb2d49f69eb3215e8a404ffc ┆ 1 ┆ 115 ┆ 2025-11-03T20:35:21.079585 ┆ 2025-11-03T20:35:21.081253 ┆ 2024-01-15T10:30:45.123456789 ┆ 2024-01-15T12:04:45.123456789 ┆ PT1800S ┆ PT16200S ┆ 1 ┆ 50 │ │ -│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ -│ │ 3ee345b2e801448cace33a1097b9b49b ┆ 1 ┆ 60 ┆ 2025-11-03T20:35:21.077357 ┆ 2025-11-03T20:35:21.078218 ┆ 2024-01-15T10:30:45.123456789 ┆ 2024-01-15T11:18:45.123456789 ┆ PT1800S ┆ PT9000S ┆ 1 ┆ 25 │ │ -│ └──────────────────────────────────┴──────────────────────┴──────────────────────┴──────────────────────────────┴──────────────────────────────┴───────────────────────────────┴───────────────────────────────┴─────────────────────────────┴─────────────────────────────┴──────────────────────┴──────────────────────┘ │ -└────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘\ +┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ METADATA: │ +│ * version: 0.1.3 │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ ┌──────────────────────────────────┬──────────────────────┬──────────────────────┬────────────────────────────┬────────────────────────────┬───────────────────────────────┬───────────────────────────────┬──────────────────────┬──────────────────────┬──────────────────────┬──────────────────────┐ │ +│ │ rerun_segment_id ┆ log_tick:start ┆ log_tick:end ┆ log_time:start ┆ log_time:end ┆ time_1:start ┆ time_1:end ┆ time_2:start ┆ time_2:end ┆ time_3:start ┆ time_3:end │ │ +│ │ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- │ │ +│ │ type: non-null Utf8 ┆ type: Int64 ┆ type: Int64 ┆ type: Timestamp(ns) ┆ type: Timestamp(ns) ┆ type: Timestamp(ns) ┆ type: Timestamp(ns) ┆ type: Duration(ns) ┆ type: Duration(ns) ┆ type: Int64 ┆ type: Int64 │ │ +│ │ ┆ index: log_tick ┆ index: log_tick ┆ index: log_time ┆ index: log_time ┆ index: time_1 ┆ index: time_1 ┆ index: time_2 ┆ index: time_2 ┆ index: time_3 ┆ index: time_3 │ │ +│ │ ┆ index_kind: sequence ┆ index_kind: sequence ┆ index_kind: timestamp ┆ index_kind: timestamp ┆ index_kind: timestamp ┆ index_kind: timestamp ┆ index_kind: duration ┆ index_kind: duration ┆ index_kind: sequence ┆ index_kind: sequence │ │ +│ │ ┆ index_marker: start ┆ index_marker: end ┆ index_marker: start ┆ index_marker: end ┆ index_marker: start ┆ index_marker: end ┆ index_marker: start ┆ index_marker: end ┆ index_marker: start ┆ index_marker: end │ │ +│ │ ┆ kind: index ┆ kind: index ┆ kind: index ┆ kind: index ┆ kind: index ┆ kind: index ┆ kind: index ┆ kind: index ┆ kind: index ┆ kind: index │ │ +│ ╞══════════════════════════════════╪══════════════════════╪══════════════════════╪════════════════════════════╪════════════════════════════╪═══════════════════════════════╪═══════════════════════════════╪══════════════════════╪══════════════════════╪══════════════════════╪══════════════════════╡ │ +│ │ 141a866deb2d49f69eb3215e8a404ffc ┆ 1 ┆ 115 ┆ 2025-11-03T20:35:21.079585 ┆ 2025-11-03T20:35:21.081253 ┆ 2024-01-15T10:30:45.123456789 ┆ 2024-01-15T12:04:45.123456789 ┆ PT1800S ┆ PT16200S ┆ 1 ┆ 50 │ │ +│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ +│ │ 3ee345b2e801448cace33a1097b9b49b ┆ 1 ┆ 60 ┆ 2025-11-03T20:35:21.077357 ┆ 2025-11-03T20:35:21.078218 ┆ 2024-01-15T10:30:45.123456789 ┆ 2024-01-15T11:18:45.123456789 ┆ PT1800S ┆ PT9000S ┆ 1 ┆ 25 │ │ +│ └──────────────────────────────────┴──────────────────────┴──────────────────────┴────────────────────────────┴────────────────────────────┴───────────────────────────────┴───────────────────────────────┴──────────────────────┴──────────────────────┴──────────────────────┴──────────────────────┘ │ +└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘\ """) diff --git a/rerun_py/tests/e2e_redap_tests/test_registration.py b/rerun_py/tests/e2e_redap_tests/test_registration.py index 5024652e5630..f63c3c2705f9 100644 --- a/rerun_py/tests/e2e_redap_tests/test_registration.py +++ b/rerun_py/tests/e2e_redap_tests/test_registration.py @@ -170,27 +170,27 @@ def test_register_unregister_batch( df = ds.filter_contents("/points").reader(index="log_time").sort("rerun_segment_id").drop("log_time") assert str(df) == inline_snapshot("""\ -┌───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ METADATA: │ -│ * version: 0.1.3 │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ ┌──────────────────────────────────────┬──────────────────────┬─────────────────────────────────────────────────────┐ │ -│ │ rerun_segment_id ┆ log_tick ┆ /points:Points2D:positions │ │ -│ │ --- ┆ --- ┆ --- │ │ -│ │ type: Utf8 ┆ type: nullable i64 ┆ type: nullable List[nullable FixedSizeList[f32; 2]] │ │ -│ │ ┆ index_name: log_tick ┆ archetype: Points2D │ │ -│ │ ┆ kind: index ┆ component: Points2D:positions │ │ -│ │ ┆ ┆ component_type: Position2D │ │ -│ │ ┆ ┆ entity_path: /points │ │ -│ │ ┆ ┆ kind: data │ │ -│ ╞══════════════════════════════════════╪══════════════════════╪═════════════════════════════════════════════════════╡ │ -│ │ aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa ┆ 0 ┆ [[0.0, 0.0]] │ │ -│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ -│ │ bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb ┆ 0 ┆ [[1.0, 1.0]] │ │ -│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ -│ │ cccccccc-cccc-cccc-cccc-cccccccccccc ┆ 0 ┆ [[2.0, 2.0]] │ │ -│ └──────────────────────────────────────┴──────────────────────┴─────────────────────────────────────────────────────┘ │ -└───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘\ +┌───────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ METADATA: │ +│ * version: 0.1.3 │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ ┌──────────────────────────────────────┬──────────────────────┬─────────────────────────────────────────────────┐ │ +│ │ rerun_segment_id ┆ log_tick ┆ /points:Points2D:positions │ │ +│ │ --- ┆ --- ┆ --- │ │ +│ │ type: non-null Utf8 ┆ type: Int64 ┆ type: List(FixedSizeList(2 x non-null Float32)) │ │ +│ │ ┆ index_name: log_tick ┆ archetype: Points2D │ │ +│ │ ┆ kind: index ┆ component: Points2D:positions │ │ +│ │ ┆ ┆ component_type: Position2D │ │ +│ │ ┆ ┆ entity_path: /points │ │ +│ │ ┆ ┆ kind: data │ │ +│ ╞══════════════════════════════════════╪══════════════════════╪═════════════════════════════════════════════════╡ │ +│ │ aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa ┆ 0 ┆ [[0.0, 0.0]] │ │ +│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ +│ │ bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb ┆ 0 ┆ [[1.0, 1.0]] │ │ +│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │ +│ │ cccccccc-cccc-cccc-cccc-cccccccccccc ┆ 0 ┆ [[2.0, 2.0]] │ │ +│ └──────────────────────────────────────┴──────────────────────┴─────────────────────────────────────────────────┘ │ +└───────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘\ """) ds.unregister(segments_to_drop=[recording_ids[0], recording_ids[2]], layers_to_drop=[]) @@ -203,23 +203,23 @@ def test_register_unregister_batch( df = ds.filter_contents("/points").reader(index="log_time").sort("rerun_segment_id").drop("log_time") assert str(df) == inline_snapshot("""\ -┌───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ METADATA: │ -│ * version: 0.1.3 │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ ┌──────────────────────────────────────┬──────────────────────┬─────────────────────────────────────────────────────┐ │ -│ │ rerun_segment_id ┆ log_tick ┆ /points:Points2D:positions │ │ -│ │ --- ┆ --- ┆ --- │ │ -│ │ type: Utf8 ┆ type: nullable i64 ┆ type: nullable List[nullable FixedSizeList[f32; 2]] │ │ -│ │ ┆ index_name: log_tick ┆ archetype: Points2D │ │ -│ │ ┆ kind: index ┆ component: Points2D:positions │ │ -│ │ ┆ ┆ component_type: Position2D │ │ -│ │ ┆ ┆ entity_path: /points │ │ -│ │ ┆ ┆ kind: data │ │ -│ ╞══════════════════════════════════════╪══════════════════════╪═════════════════════════════════════════════════════╡ │ -│ │ bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb ┆ 0 ┆ [[1.0, 1.0]] │ │ -│ └──────────────────────────────────────┴──────────────────────┴─────────────────────────────────────────────────────┘ │ -└───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘\ +┌───────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ METADATA: │ +│ * version: 0.1.3 │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ ┌──────────────────────────────────────┬──────────────────────┬─────────────────────────────────────────────────┐ │ +│ │ rerun_segment_id ┆ log_tick ┆ /points:Points2D:positions │ │ +│ │ --- ┆ --- ┆ --- │ │ +│ │ type: non-null Utf8 ┆ type: Int64 ┆ type: List(FixedSizeList(2 x non-null Float32)) │ │ +│ │ ┆ index_name: log_tick ┆ archetype: Points2D │ │ +│ │ ┆ kind: index ┆ component: Points2D:positions │ │ +│ │ ┆ ┆ component_type: Position2D │ │ +│ │ ┆ ┆ entity_path: /points │ │ +│ │ ┆ ┆ kind: data │ │ +│ ╞══════════════════════════════════════╪══════════════════════╪═════════════════════════════════════════════════╡ │ +│ │ bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb ┆ 0 ┆ [[1.0, 1.0]] │ │ +│ └──────────────────────────────────────┴──────────────────────┴─────────────────────────────────────────────────┘ │ +└───────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘\ """) From fd03bf01e118c4c1245e4d08c2e41563cf707d29 Mon Sep 17 00:00:00 2001 From: Antoine Beyeler <49431240+abey79@users.noreply.github.com> Date: Tue, 3 Mar 2026 14:35:19 +0100 Subject: [PATCH 022/513] Allow re-registering the same blueprint to a dataset ### Related - closes https://linear.app/rerun/issue/RR-3904/verify-we-can-overwrite-registered-blueprints ### What Re-registering the same blueprint URI to a dataset should to fail. It did before this PR, which now registers blueprints with `on_duplicate=OnDuplicateSegmentLayer.REPLACE`. ### Testing Added a regression test. Source-Ref: 1952e703ea4cb6fa90b72ded9ee004061e44a04d --- rerun_py/rerun_sdk/rerun/catalog/_entry.py | 2 +- .../e2e_redap_tests/test_blueprint_dataset.py | 33 +++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/rerun_py/rerun_sdk/rerun/catalog/_entry.py b/rerun_py/rerun_sdk/rerun/catalog/_entry.py index 03c7d6b064f7..afc21cc34da2 100644 --- a/rerun_py/rerun_sdk/rerun/catalog/_entry.py +++ b/rerun_py/rerun_sdk/rerun/catalog/_entry.py @@ -205,7 +205,7 @@ def register_blueprint(self, uri: str, set_default: bool = True) -> None: if blueprint_dataset is None: raise LookupError("a blueprint dataset is not configured for this dataset") - segment_id = blueprint_dataset.register(uri).wait().segment_ids[0] + segment_id = blueprint_dataset.register(uri, on_duplicate=OnDuplicateSegmentLayer.REPLACE).wait().segment_ids[0] if set_default: self.set_default_blueprint(segment_id) diff --git a/rerun_py/tests/e2e_redap_tests/test_blueprint_dataset.py b/rerun_py/tests/e2e_redap_tests/test_blueprint_dataset.py index 58dc38d83a75..968390a48272 100644 --- a/rerun_py/tests/e2e_redap_tests/test_blueprint_dataset.py +++ b/rerun_py/tests/e2e_redap_tests/test_blueprint_dataset.py @@ -64,3 +64,36 @@ def test_configure_blueprint_dataset(entry_factory: EntryFactory, tmp_path: Path ds.set_default_blueprint(second_blueprint_name) assert second_blueprint_name == ds.default_blueprint() + + +@pytest.mark.local_only +def test_reregister_same_blueprint(entry_factory: EntryFactory, tmp_path: Path) -> None: + """Re-registering the same blueprint should succeed, not raise AlreadyExistsError (regression test for RR-3904).""" + + # Create a recording and save it to a temporary file + rrd_path = tmp_path / "recording.rrd" + rec = rr.RecordingStream("rerun_example_dataset_blueprint") + rec.save(rrd_path) + rec.log("points", rr.Points2D([[0, 0], [1, 1]])) + rec.flush() + + # Create a blueprint and save it to a temporary file + rbl_path = tmp_path / "blueprint.rbl" + blueprint = rrb.Blueprint(rrb.Spatial2DView(visual_bounds=rrb.VisualBounds2D(x_range=[-1, 2], y_range=[-1, 2]))) + blueprint.save("rerun_example_dataset_blueprint", rbl_path) + + # Create a new dataset + ds = entry_factory.create_dataset("reregister_blueprint_dataset") + + # Register our recording to the dataset + ds.register(rrd_path.absolute().as_uri()).wait() + + # Register the blueprint + ds.register_blueprint(rbl_path.absolute().as_uri()) + + bds = ds.blueprint_dataset() + assert bds is not None + assert len(bds.segment_ids()) == 1 + + # Re-register the exact same blueprint — this should not raise + ds.register_blueprint(rbl_path.absolute().as_uri()) From 62b8ac3fdec5abad48e9dba3ecbbbc6219acfbf7 Mon Sep 17 00:00:00 2001 From: Antoine Beyeler <49431240+abey79@users.noreply.github.com> Date: Tue, 3 Mar 2026 15:59:12 +0100 Subject: [PATCH 023/513] Fix `using_index_value` not accepting pyarrow data of the correct types ### Related - closes https://linear.app/rerun/issue/RR-3905/using-index-values-doesnt-accept-pyarrow-durations ### What `using_index_values` now accept int64/duration ns/timestamp ns arrow arrays. ### Testing Added/improved related tests Source-Ref: 196cb5867aba8bfee89318f5d81689ebdaa40817 --- rerun_py/src/catalog/type_aliases.rs | 48 +++++++++++++++---- .../e2e_redap_tests/test_dataset_views.py | 20 ++++++++ rerun_py/tests/unit/test_type_aliases.py | 3 ++ 3 files changed, 61 insertions(+), 10 deletions(-) diff --git a/rerun_py/src/catalog/type_aliases.rs b/rerun_py/src/catalog/type_aliases.rs index ab962b586e47..9bad0149120c 100644 --- a/rerun_py/src/catalog/type_aliases.rs +++ b/rerun_py/src/catalog/type_aliases.rs @@ -1,7 +1,9 @@ use std::collections::BTreeSet; use std::str::FromStr as _; -use arrow::array::{ArrayData, Int64Array, make_array}; +use arrow::array::{ArrayData, ArrayRef, Int64Array, make_array}; +use arrow::compute::cast; +use arrow::datatypes::DataType; use arrow::pyarrow::PyArrowType; use numpy::PyArrayMethods as _; use pyo3::exceptions::{PyTypeError, PyValueError}; @@ -88,15 +90,46 @@ impl<'py> FromPyObject<'py> for IndexValuesLike<'py> { } } +/// Cast an Arrow array to [`Int64Array`], handling timestamp and duration types +/// transparently. +/// +/// Timestamp and duration arrays (e.g. from `get_index_ranges()`) share the same +/// physical representation as int64, so the cast is essentially free. +fn as_int64_array(array: &ArrayRef) -> PyResult { + if let Some(arr) = array.downcast_array_ref::() { + return Ok(arr.clone()); + } + + if matches!( + array.data_type(), + DataType::Timestamp(_, _) | DataType::Duration(_) + ) { + let cast_array = cast(array, &DataType::Int64).map_err(|err| { + PyTypeError::new_err(format!( + "Failed to cast {} to int64: {err}", + array.data_type() + )) + })?; + return cast_array + .downcast_array_ref::() + .cloned() + .ok_or_else(|| { + PyTypeError::new_err(format!("Failed to cast {} to int64.", array.data_type())) + }); + } + + Err(PyTypeError::new_err( + "pyarrow.Array for IndexValuesLike must be of type int64, timestamp, or duration.", + )) +} + impl IndexValuesLike<'_> { pub fn to_index_values(&self) -> PyResult> { match self { Self::PyArrow(array) => { let array = make_array(array.0.clone()); - let int_array = array.downcast_array_ref::().ok_or_else(|| { - PyTypeError::new_err("pyarrow.Array for IndexValuesLike must be of type int64.") - })?; + let int_array = as_int64_array(&array)?; let values: BTreeSet = int_array .iter() @@ -143,12 +176,7 @@ impl IndexValuesLike<'_> { let chunk = chunk?.extract::>()?; let array = make_array(chunk.0.clone()); - let int_array = - array.downcast_array_ref::().ok_or_else(|| { - PyTypeError::new_err( - "pyarrow.Array for IndexValuesLike must be of type int64.", - ) - })?; + let int_array = as_int64_array(&array)?; values.extend( int_array diff --git a/rerun_py/tests/e2e_redap_tests/test_dataset_views.py b/rerun_py/tests/e2e_redap_tests/test_dataset_views.py index 3be04b4867c3..c0855c1c2301 100644 --- a/rerun_py/tests/e2e_redap_tests/test_dataset_views.py +++ b/rerun_py/tests/e2e_redap_tests/test_dataset_views.py @@ -304,3 +304,23 @@ def test_dataframe_api_using_index_values_dataframe( ) assert str(df) == snapshot + + +@pytest.mark.parametrize( + "arrow_type", + [ + pa.int64(), + pa.timestamp("ns"), + pa.duration("ns"), + ], +) +def test_using_index_values_with_arrow_types(readonly_test_dataset: DatasetEntry, arrow_type: pa.DataType) -> None: + """Regression test for RR-3905. All arrow types returned by get_index_ranges() should be accepted.""" + + values = pa.array([1_000_000_000, 2_000_000_000], type=arrow_type) + + # This should not fail + readonly_test_dataset.reader( + index="time_1", + using_index_values=values, + ) diff --git a/rerun_py/tests/unit/test_type_aliases.py b/rerun_py/tests/unit/test_type_aliases.py index 903820ecc065..0c0f726326d1 100644 --- a/rerun_py/tests/unit/test_type_aliases.py +++ b/rerun_py/tests/unit/test_type_aliases.py @@ -25,8 +25,11 @@ (SOME_ARRAY.astype("datetime64[ns]"), SOME_ARRAY, nullcontext()), (SOME_ARRAY.astype("datetime64[ns]").astype("datetime64[ms]"), SOME_ARRAY, nullcontext()), (pa.array(SOME_ARRAY), SOME_ARRAY, nullcontext()), + (pa.array(SOME_ARRAY, type=pa.timestamp("ns")), SOME_ARRAY, nullcontext()), + (pa.array(SOME_ARRAY, type=pa.duration("ns")), SOME_ARRAY, nullcontext()), # Check error modes (SOME_ARRAY.astype(np.float32), SOME_ARRAY, pytest.raises(TypeError, match="IndexValuesLike must be a")), + (pa.array(SOME_ARRAY, type=pa.float64()), SOME_ARRAY, pytest.raises(TypeError, match="IndexValuesLike")), ], ) def test_index_values_like_to_index_values( From 2d9701073e5f6f7945c0b4107a2664bb851ac44a Mon Sep 17 00:00:00 2001 From: Isse Date: Tue, 3 Mar 2026 17:05:01 +0100 Subject: [PATCH 024/513] Fix drag'n'drop issue on web ### Related - Closes RR-3908 ### What Adds a request redraw when files are dropped which seems to fix the described issue Source-Ref: a27bc66db4d2529bb93fb4817818a5d79afc8921 --- crates/viewer/re_viewer/src/app.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/viewer/re_viewer/src/app.rs b/crates/viewer/re_viewer/src/app.rs index 911cfce566bf..4e5dafb07e4d 100644 --- a/crates/viewer/re_viewer/src/app.rs +++ b/crates/viewer/re_viewer/src/app.rs @@ -3007,6 +3007,8 @@ impl App { return; } + egui_ctx.request_repaint(); + let mut force_store_info = false; for file in dropped_files { From eb0b67af333256c9eafebe86f42e56923c077955 Mon Sep 17 00:00:00 2001 From: Isse Date: Tue, 3 Mar 2026 17:21:10 +0100 Subject: [PATCH 025/513] Remove collapsing time gaps for performance reasons ### Related - Closes RR-3784 ### What The main goal of this PR is to remove `TimeHistogram`s. Which are expensive both performance-wise on ingestion and memory-wise. This replaces the functionality it was used for: - A new structure to continuously keep track of row counts per timeline for the recording based on store events. - Stepping forward/backwards by directly querying the chunk store. Has some performance implications, on biggy it's about 0.4 ms for me. But this only happens on user input of cmd+arrow. - Full timeline range by directly querying the chunk store, this function already existed. - As PR title says for changelog reasons, removes timeline gap collapsing. This turned out to be hard to do in a good, yet cheap way. So removing it now and we'll see if users scream about it. [Related slack thread](https://rerunio.slack.com/archives/C08TEFXNHB2/p1772115930394889). A lot of the additions is tests for the new row count cache and time seeking functions. Probably easiest reviewed commit by commit. But can skip commits regarding tracking chunk time ranges, as that was removed in a separate commit. Source-Ref: 2a621f96daa3e26c6e29351c192025a63970b3b6 --- ARCHITECTURE.md | 11 +- Cargo.lock | 15 - Cargo.toml | 2 - crates/store/re_chunk/src/chunk.rs | 30 + crates/store/re_chunk_store/src/query.rs | 384 ++++- crates/store/re_entity_db/Cargo.toml | 1 - .../src/data_meta_per_timeline.rs | 464 ++++++ crates/store/re_entity_db/src/entity_db.rs | 64 +- crates/store/re_entity_db/src/lib.rs | 3 +- .../src/time_histogram_per_timeline.rs | 483 ------- crates/utils/re_int_histogram/Cargo.toml | 40 - crates/utils/re_int_histogram/README.md | 16 - .../benches/int_histogram_benchmark.rs | 113 -- crates/utils/re_int_histogram/src/lib.rs | 129 -- crates/utils/re_int_histogram/src/tree.rs | 1262 ----------------- .../re_int_histogram/tests/memory_test.rs | 96 -- .../memory_test__Int64Histogram.snap | 13 - .../tests/snapshots/memory_test__btree.snap | 13 - crates/viewer/re_memory_view/examples/demo.rs | 7 - crates/viewer/re_test_context/src/lib.rs | 7 +- crates/viewer/re_time_panel/Cargo.toml | 1 - crates/viewer/re_time_panel/src/time_axis.rs | 149 +- .../re_time_panel/src/time_control_ui.rs | 10 +- crates/viewer/re_time_panel/src/time_panel.rs | 45 +- .../re_time_panel/tests/time_panel_tests.rs | 40 - crates/viewer/re_viewer/src/app.rs | 8 +- crates/viewer/re_viewer/src/app_state.rs | 6 +- .../re_viewer_context/src/time_control.rs | 367 +++-- 28 files changed, 1180 insertions(+), 2599 deletions(-) create mode 100644 crates/store/re_entity_db/src/data_meta_per_timeline.rs delete mode 100644 crates/store/re_entity_db/src/time_histogram_per_timeline.rs delete mode 100644 crates/utils/re_int_histogram/Cargo.toml delete mode 100644 crates/utils/re_int_histogram/README.md delete mode 100644 crates/utils/re_int_histogram/benches/int_histogram_benchmark.rs delete mode 100644 crates/utils/re_int_histogram/src/lib.rs delete mode 100644 crates/utils/re_int_histogram/src/tree.rs delete mode 100644 crates/utils/re_int_histogram/tests/memory_test.rs delete mode 100644 crates/utils/re_int_histogram/tests/snapshots/memory_test__Int64Histogram.snap delete mode 100644 crates/utils/re_int_histogram/tests/snapshots/memory_test__btree.snap diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index bc40ce2fdf15..fe4d551f31db 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -99,11 +99,11 @@ Of course, this will only take us so far. In the future we plan on caching queri Here is an overview of the crates included in the project: - - - - - + + + + + [possible values: recording_info, schema, stats, protobuf, raw, ros2_reflection, ros2msg] ...snip ``` --------- Source-Ref: 018cf9ff90d46ee4316bb247a67587cd4fcf34cb Co-authored-by: Michael Grupp --- crates/store/re_mcap/src/layers/mod.rs | 9 +++++++++ crates/top/rerun/src/commands/mcap/mod.rs | 12 ++++++++++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/crates/store/re_mcap/src/layers/mod.rs b/crates/store/re_mcap/src/layers/mod.rs index 5e6d2d500235..af9d6f87f1e3 100644 --- a/crates/store/re_mcap/src/layers/mod.rs +++ b/crates/store/re_mcap/src/layers/mod.rs @@ -371,6 +371,15 @@ impl LayerRegistry { self } + /// Returns all registered layer identifiers (file + message) as strings. + pub fn all_identifiers(&self) -> Vec { + self.file_factories + .keys() + .chain(self.msg_factories.keys()) + .map(|id| id.to_string()) + .collect() + } + /// Produce a filtered registry that only contains `selected` layers. pub fn select(&self, selected: &SelectedLayers) -> Self { let file_factories = self diff --git a/crates/top/rerun/src/commands/mcap/mod.rs b/crates/top/rerun/src/commands/mcap/mod.rs index e79eb38b0dd4..c0ba20adbdd0 100644 --- a/crates/top/rerun/src/commands/mcap/mod.rs +++ b/crates/top/rerun/src/commands/mcap/mod.rs @@ -5,10 +5,18 @@ use std::io::BufWriter; use clap::Subcommand; use re_log_encoding::Encoder; use re_log_types::{LogMsg, RecordingId}; -use re_mcap::{LayerIdentifier, SelectedLayers}; +use re_mcap::{LayerIdentifier, LayerRegistry, SelectedLayers}; use re_sdk::external::re_data_loader::McapLoader; use re_sdk::{ApplicationId, DataLoader, DataLoaderSettings, LoadedData}; +fn possible_layers() -> clap::builder::PossibleValuesParser { + static LAYER_IDS: std::sync::LazyLock> = + std::sync::LazyLock::new(|| LayerRegistry::all_builtin(true).all_identifiers()); + clap::builder::PossibleValuesParser::new( + LAYER_IDS.iter().map(String::as_str).collect::>(), + ) +} + #[derive(Debug, Clone, clap::Parser)] pub struct ConvertCommand { /// Paths to read from. Reads from standard input if none are specified. @@ -23,7 +31,7 @@ pub struct ConvertCommand { application_id: Option, /// Specifies which layers to apply during conversion. - #[clap(short = 'l', long = "layer")] + #[clap(short = 'l', long = "layer", value_parser = possible_layers())] selected_layers: Vec, /// Disable using the raw layer as a fallback for unsupported channels. From 5e66ae8eab6fe96e1dec0f4f219abe4d53768ec1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 5 Mar 2026 16:16:53 +0000 Subject: [PATCH 047/513] Bump geopandas from 1.1.1 to 1.1.2 in /rerun Bumps [geopandas](https://github.com/geopandas/geopandas) from 1.1.1 to 1.1.2.
Changelog

Sourced from geopandas's changelog.

Version 1.1.2 (December 22, 2025)

Bug fixes:

  • Fix an issue that caused an error in GeoDataFrame.from_features when there is no properties field (#3599).
  • Fix read_file and to_file errors (#3682)
  • Fix read_parquet with to_pandas_kwargs for complex (list/struct) arrow types (#3640)
  • value_counts on GeoSeries now preserves CRS in index (#3669)
  • Fix f-string placeholders appearing in error messages when pyogrio cannot be imported (#3682).
  • Fix read_parquet with to_pandas_kwargs for complex (list/struct) arrow types (#3640).
  • .to_json now provides a clearer error message when called on a GeoDataFrame without an active geometry column (#3648).
  • Calling del gdf["geometry"] now will downcast to a pd.DataFrame if there are no geometry columns left in the dataframe (#3648).
  • Fix SQL injection in to_postgis via geometry column name (#3681).
Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=geopandas&package-manager=uv&previous-version=1.1.1&new-version=1.1.2)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/rerun-io/reality/network/alerts).
Signed-off-by: dependabot[bot] Source-Ref: baab428346160e7887b276d4723ba9491fe02929 Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Emil Ernerfeldt --- uv.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/uv.lock b/uv.lock index 1c66349ad26f..7c48605f72ee 100644 --- a/uv.lock +++ b/uv.lock @@ -1214,7 +1214,7 @@ wheels = [ [[package]] name = "geopandas" -version = "1.1.1" +version = "1.1.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, @@ -1226,9 +1226,9 @@ dependencies = [ { name = "pyproj", version = "3.7.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "shapely" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8c/76/e1960ba846f153ab109575242abf89dc98f8e057faa32f3decf4cce9247a/geopandas-1.1.1.tar.gz", hash = "sha256:1745713f64d095c43e72e08e753dbd271678254b24f2e01db8cdb8debe1d293d", size = 332655, upload-time = "2025-06-26T21:04:56.57Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8d/24/5eb5685d7bf89d64218919379f882d19a60f8219d66d833c83b1cf264c95/geopandas-1.1.2.tar.gz", hash = "sha256:33f7b33565c46a45b8459a2ab699ec943fdbb5716e58e251b3c413cf7783106c", size = 336037, upload-time = "2025-12-22T21:06:13.749Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0b/70/d5cd0696eff08e62fdbdebe5b46527facb4e7220eabe0ac6225efab50168/geopandas-1.1.1-py3-none-any.whl", hash = "sha256:589e61aaf39b19828843df16cb90234e72897e2579be236f10eee0d052ad98e8", size = 338365, upload-time = "2025-06-26T21:04:55.139Z" }, + { url = "https://files.pythonhosted.org/packages/54/e4/fac19dc34cb686c96011388b813ff7b858a70681e5ce6ce7698e5021b0f4/geopandas-1.1.2-py3-none-any.whl", hash = "sha256:2bb0b1052cb47378addb4ba54c47f8d4642dcbda9b61375638274f49d9f0bb0d", size = 341734, upload-time = "2025-12-22T21:06:12.498Z" }, ] [[package]] From d58dc2a4fe5f3cd465900d230e338bae127996d8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 5 Mar 2026 16:17:09 +0000 Subject: [PATCH 048/513] Bump protobuf from 6.33.2 to 6.33.5 in /rerun Bumps [protobuf](https://github.com/protocolbuffers/protobuf) from 6.33.2 to 6.33.5.
Release notes

Sourced from protobuf's releases.

Protocol Buffers v34.0-rc1

Announcements

Bazel

Compiler

C++

... (truncated)

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=protobuf&package-manager=uv&previous-version=6.33.2&new-version=6.33.5)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/rerun-io/reality/network/alerts).
Signed-off-by: dependabot[bot] Source-Ref: 60c84f9a60968eb243e30e1804e915694ca56bf0 Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Emil Ernerfeldt --- uv.lock | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/uv.lock b/uv.lock index 7c48605f72ee..897d74114294 100644 --- a/uv.lock +++ b/uv.lock @@ -3862,17 +3862,17 @@ wheels = [ [[package]] name = "protobuf" -version = "6.33.2" +version = "6.33.5" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/34/44/e49ecff446afeec9d1a66d6bbf9adc21e3c7cea7803a920ca3773379d4f6/protobuf-6.33.2.tar.gz", hash = "sha256:56dc370c91fbb8ac85bc13582c9e373569668a290aa2e66a590c2a0d35ddb9e4", size = 444296, upload-time = "2025-12-06T00:17:53.311Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/25/7c72c307aafc96fa87062aa6291d9f7c94836e43214d43722e86037aac02/protobuf-6.33.5.tar.gz", hash = "sha256:6ddcac2a081f8b7b9642c09406bc6a4290128fce5f471cddd165960bb9119e5c", size = 444465, upload-time = "2026-01-29T21:51:33.494Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/bc/91/1e3a34881a88697a7354ffd177e8746e97a722e5e8db101544b47e84afb1/protobuf-6.33.2-cp310-abi3-win32.whl", hash = "sha256:87eb388bd2d0f78febd8f4c8779c79247b26a5befad525008e49a6955787ff3d", size = 425603, upload-time = "2025-12-06T00:17:41.114Z" }, - { url = "https://files.pythonhosted.org/packages/64/20/4d50191997e917ae13ad0a235c8b42d8c1ab9c3e6fd455ca16d416944355/protobuf-6.33.2-cp310-abi3-win_amd64.whl", hash = "sha256:fc2a0e8b05b180e5fc0dd1559fe8ebdae21a27e81ac77728fb6c42b12c7419b4", size = 436930, upload-time = "2025-12-06T00:17:43.278Z" }, - { url = "https://files.pythonhosted.org/packages/b2/ca/7e485da88ba45c920fb3f50ae78de29ab925d9e54ef0de678306abfbb497/protobuf-6.33.2-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:d9b19771ca75935b3a4422957bc518b0cecb978b31d1dd12037b088f6bcc0e43", size = 427621, upload-time = "2025-12-06T00:17:44.445Z" }, - { url = "https://files.pythonhosted.org/packages/7d/4f/f743761e41d3b2b2566748eb76bbff2b43e14d5fcab694f494a16458b05f/protobuf-6.33.2-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:b5d3b5625192214066d99b2b605f5783483575656784de223f00a8d00754fc0e", size = 324460, upload-time = "2025-12-06T00:17:45.678Z" }, - { url = "https://files.pythonhosted.org/packages/b1/fa/26468d00a92824020f6f2090d827078c09c9c587e34cbfd2d0c7911221f8/protobuf-6.33.2-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:8cd7640aee0b7828b6d03ae518b5b4806fdfc1afe8de82f79c3454f8aef29872", size = 339168, upload-time = "2025-12-06T00:17:46.813Z" }, - { url = "https://files.pythonhosted.org/packages/56/13/333b8f421738f149d4fe5e49553bc2a2ab75235486259f689b4b91f96cec/protobuf-6.33.2-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:1f8017c48c07ec5859106533b682260ba3d7c5567b1ca1f24297ce03384d1b4f", size = 323270, upload-time = "2025-12-06T00:17:48.253Z" }, - { url = "https://files.pythonhosted.org/packages/0e/15/4f02896cc3df04fc465010a4c6a0cd89810f54617a32a70ef531ed75d61c/protobuf-6.33.2-py3-none-any.whl", hash = "sha256:7636aad9bb01768870266de5dc009de2d1b936771b38a793f73cbbf279c91c5c", size = 170501, upload-time = "2025-12-06T00:17:52.211Z" }, + { url = "https://files.pythonhosted.org/packages/b1/79/af92d0a8369732b027e6d6084251dd8e782c685c72da161bd4a2e00fbabb/protobuf-6.33.5-cp310-abi3-win32.whl", hash = "sha256:d71b040839446bac0f4d162e758bea99c8251161dae9d0983a3b88dee345153b", size = 425769, upload-time = "2026-01-29T21:51:21.751Z" }, + { url = "https://files.pythonhosted.org/packages/55/75/bb9bc917d10e9ee13dee8607eb9ab963b7cf8be607c46e7862c748aa2af7/protobuf-6.33.5-cp310-abi3-win_amd64.whl", hash = "sha256:3093804752167bcab3998bec9f1048baae6e29505adaf1afd14a37bddede533c", size = 437118, upload-time = "2026-01-29T21:51:24.022Z" }, + { url = "https://files.pythonhosted.org/packages/a2/6b/e48dfc1191bc5b52950246275bf4089773e91cb5ba3592621723cdddca62/protobuf-6.33.5-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:a5cb85982d95d906df1e2210e58f8e4f1e3cdc088e52c921a041f9c9a0386de5", size = 427766, upload-time = "2026-01-29T21:51:25.413Z" }, + { url = "https://files.pythonhosted.org/packages/4e/b1/c79468184310de09d75095ed1314b839eb2f72df71097db9d1404a1b2717/protobuf-6.33.5-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:9b71e0281f36f179d00cbcb119cb19dec4d14a81393e5ea220f64b286173e190", size = 324638, upload-time = "2026-01-29T21:51:26.423Z" }, + { url = "https://files.pythonhosted.org/packages/c5/f5/65d838092fd01c44d16037953fd4c2cc851e783de9b8f02b27ec4ffd906f/protobuf-6.33.5-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:8afa18e1d6d20af15b417e728e9f60f3aa108ee76f23c3b2c07a2c3b546d3afd", size = 339411, upload-time = "2026-01-29T21:51:27.446Z" }, + { url = "https://files.pythonhosted.org/packages/9b/53/a9443aa3ca9ba8724fdfa02dd1887c1bcd8e89556b715cfbacca6b63dbec/protobuf-6.33.5-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:cbf16ba3350fb7b889fca858fb215967792dc125b35c7976ca4818bee3521cf0", size = 323465, upload-time = "2026-01-29T21:51:28.925Z" }, + { url = "https://files.pythonhosted.org/packages/57/bf/2086963c69bdac3d7cff1cc7ff79b8ce5ea0bec6797a017e1be338a46248/protobuf-6.33.5-py3-none-any.whl", hash = "sha256:69915a973dd0f60f31a08b8318b73eab2bd6a392c79184b3612226b0a3f8ec02", size = 170687, upload-time = "2026-01-29T21:51:32.557Z" }, ] [[package]] From 10a42b6e8021463e2e8829b0915ff7fc2105cf57 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 5 Mar 2026 20:19:02 +0100 Subject: [PATCH 049/513] Upgrade `jsonwebtoken` to 10.3 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Security advisory * https://security.snyk.io/vuln/SNYK-RUST-JSONWEBTOKEN-15189005 * https://github.com/advisories/GHSA-h395-gr6q-cpjc ## Crypto jsonwebtoken 10 introduced mandatory crypto: https://github.com/Keats/jsonwebtoken/blob/master/CHANGELOG.md#1000-2025-09-29 So this PR adds a bunch of new crate dependencies… as if we didn't have enough already 😭 EDIT: oh great, this just pulls in another RUST-SEC: https://rustsec.org/advisories/RUSTSEC-2023-0071 Source-Ref: df3969dff4a214b789ca568256cce5a5e5b601a5 --- Cargo.lock | 86 ++++++++++++------- Cargo.toml | 5 +- crates/utils/re_auth/Cargo.toml | 16 +++- crates/utils/re_auth/src/crypto_provider.rs | 93 +++++++++++++++++++++ crates/utils/re_auth/src/lib.rs | 3 + crates/utils/re_auth/src/provider.rs | 2 + 6 files changed, 172 insertions(+), 33 deletions(-) create mode 100644 crates/utils/re_auth/src/crypto_provider.rs diff --git a/Cargo.lock b/Cargo.lock index b4ebff2256e6..9d8ef6e131d4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1331,9 +1331,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.19.0" +version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" [[package]] name = "byte-slice-cast" @@ -1594,9 +1594,9 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "cfg_aliases" @@ -1875,7 +1875,7 @@ version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" dependencies = [ - "getrandom 0.2.16", + "getrandom 0.2.17", "once_cell", "tiny-keccak", ] @@ -4227,9 +4227,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", "js-sys", @@ -4641,6 +4641,15 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + [[package]] name = "home" version = "0.5.11" @@ -5265,9 +5274,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.15" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] name = "jiff" @@ -5382,15 +5391,16 @@ dependencies = [ [[package]] name = "jsonwebtoken" -version = "9.3.1" +version = "10.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a87cc7a48537badeae96744432de36f4be2b4a34a05a5ef32e9dd8a1c169dde" +checksum = "0529410abe238729a60b108898784df8984c87f6054c9c4fcacc47e4803c1ce1" dependencies = [ "base64 0.22.1", + "getrandom 0.2.17", "js-sys", - "ring", "serde", "serde_json", + "signature", ] [[package]] @@ -5541,7 +5551,7 @@ dependencies = [ "arrow-schema", "arrow-select", "bytes", - "getrandom 0.2.16", + "getrandom 0.2.17", "half", "jsonb", "num-traits", @@ -6353,9 +6363,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.6" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "memmap2" @@ -7977,9 +7987,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.101" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] @@ -8279,9 +8289,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.41" +version = "1.0.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" dependencies = [ "proc-macro2", ] @@ -8345,7 +8355,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.16", + "getrandom 0.2.17", ] [[package]] @@ -8539,7 +8549,9 @@ dependencies = [ "base64 0.22.1", "directories", "ehttp", + "getrandom 0.2.17", "getrandom 0.3.3", + "hmac", "http", "indicatif", "jiff", @@ -8553,6 +8565,7 @@ dependencies = [ "serde", "serde_json", "sha2", + "signature", "thiserror 2.0.17", "tiny_http", "tokio", @@ -10803,7 +10816,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" dependencies = [ - "getrandom 0.2.16", + "getrandom 0.2.17", "libredox", "thiserror 2.0.17", ] @@ -11191,7 +11204,7 @@ checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", - "getrandom 0.2.16", + "getrandom 0.2.17", "libc", "untrusted", "windows-sys 0.52.0", @@ -11430,9 +11443,9 @@ checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" -version = "1.0.20" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984" [[package]] name = "same-file" @@ -11632,15 +11645,15 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.145" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ "itoa", "memchr", - "ryu", "serde", "serde_core", + "zmij", ] [[package]] @@ -11768,6 +11781,15 @@ dependencies = [ "libc", ] +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "rand_core 0.6.4", +] + [[package]] name = "simd-adler32" version = "0.3.7" @@ -13184,9 +13206,9 @@ checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" [[package]] name = "unicode-ident" -version = "1.0.19" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-segmentation" @@ -15002,6 +15024,12 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f06ae92f42f5e5c42443fd094f245eb656abf56dd7cce9b8b263236565e00f2" +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" + [[package]] name = "zopfli" version = "0.8.3" diff --git a/Cargo.toml b/Cargo.toml index 051f2f60fcc8..739c61c400df 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -258,12 +258,14 @@ flatbuffers = "25.9.23" futures = "0.3.31" futures-util = "0.3.31" getrandom = "0.3.3" +getrandom02 = { package = "getrandom", version = "0.2.17" } glam = { version = "0.30.8", features = ["debug-glam-assert", "serde"] } glob = "0.3.3" gltf = "1.4" h264-reader = "0.8.0" half = { version = "2.6.0", features = ["bytemuck"] } hexasphere = "16.0.0" +hmac = "0.12.1" home = "0.5.11" http = "1.3.1" http-body = "1.0.1" @@ -280,7 +282,7 @@ insta = "1.43" itertools = "0.14.0" jiff = { version = "0.2.15", features = ["js"] } js-sys = "0.3.77" -jsonwebtoken = { version = "9.3", default-features = false } +jsonwebtoken = { version = "10.3", default-features = false } lance = { version = "2.0.0", default-features = false } # When you update this, also update the list of features enabled for `datafusion` (~50 lines up) lance-index = { version = "2.0.0", default-features = false } lance-linalg = { version = "2.0.0", default-features = false } @@ -361,6 +363,7 @@ serde_bytes = "0.11.19" serde_json = { version = "1.0", default-features = false, features = ["std"] } serde-wasm-bindgen = "0.6.5" sha2 = "0.10.9" +signature = { version = "2.2", features = ["std"] } similar-asserts = "1.7.0" slotmap = { version = "1.0.7", features = ["serde"] } smallvec = { version = "1.15", features = ["const_generics", "union"] } diff --git a/crates/utils/re_auth/Cargo.toml b/crates/utils/re_auth/Cargo.toml index d9c2b5267e79..3fef30923100 100644 --- a/crates/utils/re_auth/Cargo.toml +++ b/crates/utils/re_auth/Cargo.toml @@ -24,7 +24,13 @@ workspace = true [features] cli = ["dep:indicatif", "dep:webbrowser", "oauth"] -oauth = ["dep:directories", "dep:ehttp", "dep:getrandom", "dep:sha2", "dep:tiny_http", "dep:uuid"] +oauth = ["dep:directories", "dep:ehttp", "dep:getrandom", "dep:tiny_http", "dep:uuid"] + +[package.metadata.cargo-shear] +ignored = [ + "getrandom", # transitive dependency +] + [dependencies] re_analytics.workspace = true @@ -34,8 +40,11 @@ async-trait.workspace = true base64.workspace = true http.workspace = true jiff = { workspace = true, features = ["serde"] } +hmac.workspace = true jsonwebtoken.workspace = true parking_lot.workspace = true +sha2.workspace = true +signature.workspace = true saturating_cast.workspace = true serde.workspace = true serde_json.workspace = true @@ -48,11 +57,10 @@ url.workspace = true ## Optional dependencies getrandom = { workspace = true, optional = true } indicatif = { workspace = true, optional = true } -sha2 = { workspace = true, optional = true } uuid = { workspace = true, optional = true, features = ["v4"] } webbrowser = { workspace = true, optional = true } - +# Native: [target.'cfg(not(target_arch = "wasm32"))'.dependencies] directories = { workspace = true, optional = true } ehttp = { workspace = true, optional = true, features = ["json", "native-async"] } @@ -60,11 +68,13 @@ rand = { workspace = true, features = ["std", "std_rng", "os_rng"] } serde.workspace = true tiny_http = { workspace = true, optional = true } +# Web: [target.'cfg(target_arch = "wasm32")'.dependencies] ehttp = { workspace = true, optional = true, features = ["json"] } js-sys.workspace = true wasm-bindgen.workspace = true web-sys = { workspace = true, features = ["Location", "Storage", "Window"] } +getrandom02 = { workspace = true, features = ["js"] } [dev-dependencies] rand = { workspace = true, features = ["std", "std_rng"] } diff --git a/crates/utils/re_auth/src/crypto_provider.rs b/crates/utils/re_auth/src/crypto_provider.rs new file mode 100644 index 000000000000..23c752cc9bc7 --- /dev/null +++ b/crates/utils/re_auth/src/crypto_provider.rs @@ -0,0 +1,93 @@ +//! Minimal [`CryptoProvider`] for `jsonwebtoken` that only supports HS256. + +use hmac::{Hmac, Mac as _}; +use jsonwebtoken::crypto::{CryptoProvider, JwkUtils, JwtSigner, JwtVerifier}; +use jsonwebtoken::errors::{Error, ErrorKind}; +use jsonwebtoken::{Algorithm, DecodingKey, EncodingKey}; +use sha2::Sha256; +use signature::{Signer, Verifier}; + +type HmacSha256 = Hmac; + +struct Hs256Signer(HmacSha256); + +impl Hs256Signer { + fn new(key: &EncodingKey) -> Result { + let inner = HmacSha256::new_from_slice(key.try_get_hmac_secret()?) + .map_err(|_ignored| ErrorKind::InvalidKeyFormat)?; + Ok(Self(inner)) + } +} + +impl Signer> for Hs256Signer { + fn try_sign(&self, msg: &[u8]) -> Result, signature::Error> { + let mut mac = self.0.clone(); + mac.update(msg); + Ok(mac.finalize().into_bytes().to_vec()) + } +} + +impl JwtSigner for Hs256Signer { + fn algorithm(&self) -> Algorithm { + Algorithm::HS256 + } +} + +struct Hs256Verifier(HmacSha256); + +impl Hs256Verifier { + fn new(key: &DecodingKey) -> Result { + let inner = HmacSha256::new_from_slice(key.try_get_hmac_secret()?) + .map_err(|_ignored| ErrorKind::InvalidKeyFormat)?; + Ok(Self(inner)) + } +} + +impl Verifier> for Hs256Verifier { + fn verify(&self, msg: &[u8], signature: &Vec) -> Result<(), signature::Error> { + let mut mac = self.0.clone(); + mac.update(msg); + mac.verify_slice(signature) + .map_err(signature::Error::from_source) + } +} + +impl JwtVerifier for Hs256Verifier { + fn algorithm(&self) -> Algorithm { + Algorithm::HS256 + } +} + +fn unsupported_algorithm(algo: &Algorithm) -> Error { + re_log::debug_panic!("DEBUG PANIC: unsupported algorithm: {algo:?}"); + + ErrorKind::InvalidAlgorithm.into() +} + +fn signer_factory(algorithm: &Algorithm, key: &EncodingKey) -> Result, Error> { + match algorithm { + Algorithm::HS256 => Ok(Box::new(Hs256Signer::new(key)?)), + other => Err(unsupported_algorithm(other)), + } +} + +fn verifier_factory( + algorithm: &Algorithm, + key: &DecodingKey, +) -> Result, Error> { + match algorithm { + Algorithm::HS256 => Ok(Box::new(Hs256Verifier::new(key)?)), + other => Err(unsupported_algorithm(other)), + } +} + +pub static PROVIDER: CryptoProvider = CryptoProvider { + signer_factory, + verifier_factory, + jwk_utils: JwkUtils::new_unimplemented(), +}; + +/// Install our minimal [`CryptoProvider`]. Safe to call multiple times. +pub fn install() { + PROVIDER.install_default().ok(); +} diff --git a/crates/utils/re_auth/src/lib.rs b/crates/utils/re_auth/src/lib.rs index b58428a13244..0c4d45ac0651 100644 --- a/crates/utils/re_auth/src/lib.rs +++ b/crates/utils/re_auth/src/lib.rs @@ -7,6 +7,9 @@ //! **Warning!** This approach should only be seen as a stop-gap until we have //! integration of _real_ identity-providers, most likely based on `OpenID` Connect. +#[cfg(not(target_arch = "wasm32"))] +mod crypto_provider; + #[cfg(not(target_arch = "wasm32"))] mod error; diff --git a/crates/utils/re_auth/src/provider.rs b/crates/utils/re_auth/src/provider.rs index d8e8dbc128a7..296f4b8e0702 100644 --- a/crates/utils/re_auth/src/provider.rs +++ b/crates/utils/re_auth/src/provider.rs @@ -228,6 +228,7 @@ fn generate_secret_key(mut rng: impl rand::Rng, length: usize) -> Vec { impl RedapProvider { /// Create an authentication provider from a secret key. pub fn from_secret_key(secret_key: SecretKey) -> Self { + crate::crypto_provider::install(); Self { secret_key, #[cfg(feature = "oauth")] @@ -237,6 +238,7 @@ impl RedapProvider { /// Create an authentication provider from a secret key encoded as base64. pub fn from_secret_key_base64(secret_key: &str) -> Result { + crate::crypto_provider::install(); Ok(Self { secret_key: SecretKey::from_base64(secret_key)?, #[cfg(feature = "oauth")] From 0a47b41dba92e4462f5ecfbd710213522a8e51a1 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 5 Mar 2026 23:20:18 +0100 Subject: [PATCH 050/513] =?UTF-8?q?Handle=20`=3Furl=3Drerun+http://?= =?UTF-8?q?=E2=80=A6`=20in=20web=20viewer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Closes https://github.com/rerun-io/rerun/issues/12679 * Closes RR-3940 Forgetting to escape urls before pasting them behind the `?url=…` query parameter is an easy mistake to make. Using `rerun+http` was a mistake. We should have gone with `rerun-http` Source-Ref: 6a09c3d59603c138ef70d6a7ac411bd7b1d6bbcb --- crates/store/re_uri/src/redap_uri.rs | 30 ++++++++++++++++--- .../viewer/re_viewer_context/src/open_url.rs | 21 +++++++++++-- crates/viewer/re_web_viewer_server/src/lib.rs | 7 +++++ 3 files changed, 51 insertions(+), 7 deletions(-) diff --git a/crates/store/re_uri/src/redap_uri.rs b/crates/store/re_uri/src/redap_uri.rs index 6334ce140c58..457232166707 100644 --- a/crates/store/re_uri/src/redap_uri.rs +++ b/crates/store/re_uri/src/redap_uri.rs @@ -61,21 +61,27 @@ impl std::fmt::Display for RedapUri { impl std::str::FromStr for RedapUri { type Err = Error; - fn from_str(value: &str) -> Result { + fn from_str(input: &str) -> Result { + // If someone manually visits `https://rerun.io/viewer?url=rerun+https://…` then + // that `+` will be turned into a space. So let's gracefully handle that here: + let input = &input + .replace("rerun http", "rerun+http") + .replace("rerun https", "rerun+https"); + // Hacky, but I don't want to have to memorize ports. - let default_localhost_port = if value.contains("/proxy") { + let default_localhost_port = if input.contains("/proxy") { DEFAULT_PROXY_PORT } else { DEFAULT_REDAP_PORT }; - let (origin, http_url) = Origin::replace_and_parse(value, Some(default_localhost_port))?; + let (origin, http_url) = Origin::replace_and_parse(input, Some(default_localhost_port))?; // :warning: We limit the amount of segments, which might need to be // adjusted when adding additional resources. let segments = http_url .path_segments() - .ok_or_else(|| Error::UnexpectedBaseUrl(value.to_owned()))? + .ok_or_else(|| Error::UnexpectedBaseUrl(input.to_owned()))? .take(2) .filter(|s| !s.is_empty()) // handle trailing slashes .collect::>(); @@ -394,6 +400,22 @@ mod tests { assert_eq!(address.unwrap(), expected); } + #[test] + fn test_proxy_endpoint_with_space() { + let url = "rerun http://127.0.0.1:9876/proxy"; + let address: Result = url.parse(); + + let expected = RedapUri::Proxy(ProxyUri { + origin: Origin { + scheme: Scheme::RerunHttp, + host: url::Host::Ipv4(Ipv4Addr::LOCALHOST), + port: 9876, + }, + }); + + assert_eq!(address.unwrap(), expected); + } + #[test] fn test_parsing() { let test_cases = [ diff --git a/crates/viewer/re_viewer_context/src/open_url.rs b/crates/viewer/re_viewer_context/src/open_url.rs index 929fa7e0b365..b8759d475b03 100644 --- a/crates/viewer/re_viewer_context/src/open_url.rs +++ b/crates/viewer/re_viewer_context/src/open_url.rs @@ -763,13 +763,19 @@ fn handle_web_event_listener(egui_ctx: &egui::Context, command_sender: &CommandS #[cfg(test)] mod tests { - use std::str::FromStr as _; + use std::{ + net::{Ipv4Addr, SocketAddrV4}, + str::FromStr as _, + }; use re_entity_db::{EntityDb, EntityPath, InstancePath}; use re_log_channel::LogSource; use re_log_types::{EntryId, StoreId, StoreKind, TableId}; - use re_uri::external::url::{self, Url}; - use re_uri::{CatalogUri, DatasetSegmentUri, Fragment}; + use re_uri::{CatalogUri, DatasetSegmentUri, Fragment, Scheme}; + use re_uri::{ + Origin, + external::url::{self, Url}, + }; use super::ViewerOpenUrl; use crate::{Item, Route, StoreHub}; @@ -783,6 +789,15 @@ mod tests { ViewerOpenUrl::RedapCatalog(re_uri::CatalogUri::from_str(url).unwrap()) ); + let url = "rerun http://127.0.0.1:9876/proxy"; + assert_eq!( + ViewerOpenUrl::from_str(url).unwrap(), + ViewerOpenUrl::RedapProxy(re_uri::ProxyUri::new(Origin::from_scheme_and_socket_addr( + Scheme::RerunHttp, + SocketAddrV4::new(Ipv4Addr::LOCALHOST, 9876).into() + ))) + ); + // RedapEntry let entry_id = EntryId::new(); let url = format!("rerun://localhost:51234/entry/{entry_id}"); diff --git a/crates/viewer/re_web_viewer_server/src/lib.rs b/crates/viewer/re_web_viewer_server/src/lib.rs index 8a0f3765cb33..af612339c109 100644 --- a/crates/viewer/re_web_viewer_server/src/lib.rs +++ b/crates/viewer/re_web_viewer_server/src/lib.rs @@ -81,6 +81,13 @@ pub enum WebViewerServerError { /// Typed port for use with [`WebViewerServer`] pub struct WebViewerServerPort(pub u16); +impl From for WebViewerServerPort { + #[inline] + fn from(port: u16) -> Self { + Self(port) + } +} + impl WebViewerServerPort { /// Port to use with [`WebViewerServer::new`] when you want the OS to pick a port for you. /// From 53d286417bf228afe9987e8855b20caa0c846c67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jochen=20G=C3=B6rtler?= Date: Fri, 6 Mar 2026 09:11:23 +0100 Subject: [PATCH 051/513] Support plotting components that contain `FixedSizeListArray` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### Related * Closes RR-3446. * Related to RR-3435. ### What That was quite the tour through our system 😅. I opted for casting in `Flatten` to avoid duplicating big amounts of code. Maybe a generic version could make its way into the `Selector` in the future. Source-Ref: f97bf795ea295495c52bbfe99743898c15ef6d67 --- .../store/re_arrow_combinators/src/reshape.rs | 44 ++++++--- .../re_arrow_combinators/src/selector/mod.rs | 98 ++++++++++++------- .../src/selector/runtime.rs | 13 +-- .../tests/test_selector.rs | 54 +++++++++- .../tests/test_transform.rs | 58 ++++++++++- crates/viewer/re_view/src/query.rs | 51 +++++----- .../re_view_time_series/tests/blueprint.rs | 96 +++++++++++++++++- .../snapshots/transform3d_time_series.png | 3 + .../tests/snapshots/source_component_5.png | 4 +- 9 files changed, 333 insertions(+), 88 deletions(-) create mode 100644 crates/viewer/re_view_time_series/tests/snapshots/transform3d_time_series.png diff --git a/crates/store/re_arrow_combinators/src/reshape.rs b/crates/store/re_arrow_combinators/src/reshape.rs index 47f097e3f373..e4a124404802 100644 --- a/crates/store/re_arrow_combinators/src/reshape.rs +++ b/crates/store/re_arrow_combinators/src/reshape.rs @@ -1,12 +1,13 @@ //! Transforms that extract and reshape arrays. +use std::borrow::Cow; use std::sync::Arc; use arrow::array::{ Array, ArrayRef, BooleanBufferBuilder, FixedSizeListArray, ListArray, StructArray, UInt32Array, UInt64Array, }; -use arrow::buffer::{NullBuffer, OffsetBuffer}; +use arrow::buffer::{NullBuffer, OffsetBuffer, ScalarBuffer}; use arrow::datatypes::{ArrowNativeType as _, Field}; use re_log::debug_assert_eq; @@ -75,10 +76,26 @@ impl Transform for GetField { } } +/// Converts a `FixedSizeListArray` to a `ListArray` by synthesizing offsets. +fn fixed_size_list_to_list(fixed: &FixedSizeListArray) -> Result { + let (field, size, values, nulls) = fixed.clone().into_parts(); + let len = i32::try_from(fixed.len()).map_err(|_err| Error::OffsetOverflow { + actual: fixed.len(), + expected_type: "i32", + })?; + let offsets: Vec = (0..=len).map(|i| i * size).collect(); + Ok(ListArray::new( + field, + OffsetBuffer::new(ScalarBuffer::from(offsets)), + values, + nulls, + )) +} + /// Flattens a nested list array by one level. /// -/// Takes `List>` and flattens it to `List` by concatenating all inner lists -/// within each outer list row. +/// Takes `List>` or `List>` and flattens it to `List` by +/// concatenating all inner lists within each outer list row. /// /// # Example /// @@ -103,16 +120,19 @@ impl Transform for Flatten { fn transform(&self, source: &ListArray) -> Result { let values = source.values(); - // The values should be a ListArray that we want to flatten - let inner_list = - values - .as_any() - .downcast_ref::() - .ok_or_else(|| Error::TypeMismatch { - expected: "List".to_owned(), + // The values should be a ListArray (or FixedSizeListArray) that we want to flatten + let inner_list: Cow<'_, ListArray> = + if let Some(list) = values.as_any().downcast_ref::() { + Cow::Borrowed(list) + } else if let Some(fixed) = values.as_any().downcast_ref::() { + Cow::Owned(fixed_size_list_to_list(fixed)?) + } else { + return Err(Error::TypeMismatch { + expected: "List or FixedSizeList".to_owned(), actual: values.data_type().clone(), - context: "Flatten expects List>".to_owned(), - })?; + context: "Flatten expects List> or List>".to_owned(), + }); + }; let outer_offsets = source.offsets(); let inner_offsets = inner_list.offsets(); diff --git a/crates/store/re_arrow_combinators/src/selector/mod.rs b/crates/store/re_arrow_combinators/src/selector/mod.rs index 69421bdb1355..1f968584053f 100644 --- a/crates/store/re_arrow_combinators/src/selector/mod.rs +++ b/crates/store/re_arrow_combinators/src/selector/mod.rs @@ -30,7 +30,7 @@ mod runtime; use arrow::{ array::{Array as _, ListArray}, - datatypes::{DataType, Field}, + datatypes::{DataType, Field, Fields}, }; use vec1::Vec1; @@ -118,6 +118,52 @@ pub enum Error { Runtime(#[from] crate::Error), } +/// Dispatch a single datatype: enqueue structs, unwrap lists, or check the predicate. +fn process_datatype<'a, P>( + mut path: Vec, + datatype: &'a DataType, + predicate: &P, + result: &mut Vec<(Selector, DataType)>, + queue: &mut std::collections::VecDeque<(Vec, &'a Fields)>, +) where + P: Fn(&DataType) -> bool, +{ + match datatype { + DataType::Struct(fields) => { + queue.push_back((path, fields)); + } + DataType::List(inner) | DataType::FixedSizeList(inner, ..) => { + path.push(Segment { + kind: SegmentKind::Each, + suppressed: false, + }); + match inner.data_type() { + DataType::Struct(nested_fields) => { + queue.push_back((path, nested_fields)); + } + DataType::FixedSizeList(field, ..) => { + let dt = field.data_type(); + if predicate(dt) { + path.push(Segment { + kind: SegmentKind::Each, + suppressed: false, + }); + result.push((Selector(Expr::Path(path)), dt.clone())); + } + } + dt if predicate(dt) => { + result.push((Selector(Expr::Path(path)), dt.clone())); + } + _ => {} + } + } + dt if predicate(dt) => { + result.push((Selector(Expr::Path(path)), dt.clone())); + } + _ => {} + } +} + /// Extract nested fields from a struct array that match a predicate. /// /// Returns `None` if no fields match the predicate, or if `datatype` is not a `DataType::Struct`. @@ -128,15 +174,15 @@ pub fn extract_nested_fields

( where P: Fn(&DataType) -> bool, { - let DataType::Struct(fields) = datatype else { - return None; - }; - let mut result = Vec::new(); let mut queue = std::collections::VecDeque::new(); - // Initialize queue with root fields - queue.push_back((Vec::new(), fields)); + match datatype { + DataType::Struct(_) | DataType::List(_) | DataType::FixedSizeList(..) => { + process_datatype(Vec::new(), datatype, &predicate, &mut result, &mut queue); + } + _ => return None, + } // Breadth-first traversal while let Some((path, fields)) = queue.pop_front() { @@ -146,37 +192,13 @@ where kind: SegmentKind::Field(field.name().clone()), suppressed: false, }); - - match field.data_type() { - DataType::Struct(nested_fields) => { - // Queue nested struct for later processing - queue.push_back((field_path, nested_fields)); - } - DataType::List(inner) => { - // Add the Each segment to unwrap the list - field_path.push(Segment { - kind: SegmentKind::Each, - suppressed: false, - }); - - match inner.data_type() { - DataType::Struct(nested_fields) => { - // Queue nested struct within list for later processing - queue.push_back((field_path, nested_fields)); - } - dt if predicate(dt) => { - // Direct match on list inner type - result.push((Selector(Expr::Path(field_path)), dt.clone())); - } - _ => {} - } - } - dt if predicate(dt) => { - // Direct match on field type - result.push((Selector(Expr::Path(field_path)), dt.clone())); - } - _ => {} - } + process_datatype( + field_path, + field.data_type(), + &predicate, + &mut result, + &mut queue, + ); } } diff --git a/crates/store/re_arrow_combinators/src/selector/runtime.rs b/crates/store/re_arrow_combinators/src/selector/runtime.rs index ae1e0d7f9792..834abf216735 100644 --- a/crates/store/re_arrow_combinators/src/selector/runtime.rs +++ b/crates/store/re_arrow_combinators/src/selector/runtime.rs @@ -2,7 +2,7 @@ //! //! This module implements execution of expressions against Arrow [`ListArray`]s. -use arrow::array::{Array as _, ListArray}; +use arrow::array::{Array as _, FixedSizeListArray, ListArray}; use crate::{ Transform as _, @@ -32,6 +32,10 @@ pub fn execute_per_row(expr: &Expr, source: &ListArray) -> Result(array: &ListArray) -> bool { + array.values().as_any().downcast_ref::().is_some() +} + impl SegmentKind { fn execute(&self, source: &ListArray) -> Result { match self { @@ -43,11 +47,8 @@ impl SegmentKind { // In Arrow's columnar context, [] flattens one level of list nesting // while preserving row count, rather than exploding to create new rows. // This reinterprets jq's streaming iteration as structural unwrapping. - if source - .values() - .as_any() - .downcast_ref::() - .is_some() + if values_downcasts_to::(source) + || values_downcasts_to::(source) { // Flatten nested lists: List> -> List Flatten::new().transform(source) diff --git a/crates/store/re_arrow_combinators/tests/test_selector.rs b/crates/store/re_arrow_combinators/tests/test_selector.rs index c85fd8ec1b8f..38a7b7726a3b 100644 --- a/crates/store/re_arrow_combinators/tests/test_selector.rs +++ b/crates/store/re_arrow_combinators/tests/test_selector.rs @@ -314,6 +314,37 @@ fn execute_index_on_fixed_size_list() -> Result<(), Error> { Ok(()) } +#[test] +fn execute_each_on_fixed_size_list() -> Result<(), Error> { + // Build List> + // Row 0: [[1,2,3], [4,5,6]] -> flatten to [1,2,3,4,5,6] + // Row 1: [[7,8,9]] -> flatten to [7,8,9] + + let values = Int32Array::from(vec![1, 2, 3, 4, 5, 6, 7, 8, 9]); + let fixed_field = Arc::new(Field::new("item", DataType::Int32, true)); + let fixed_list = FixedSizeListArray::new(fixed_field, 3, Arc::new(values), None); + + let offsets = OffsetBuffer::new(vec![0, 2, 3].into()); + let list_field = Arc::new(Field::new_list_field(fixed_list.data_type().clone(), true)); + let array = ListArray::new(list_field, offsets, Arc::new(fixed_list), None); + + let result = ".[]".parse::()?.execute_per_row(&array)?.unwrap(); + + insta::assert_snapshot!(format!("{}", DisplayRB(result)), @" + ┌────────────────────┐ + │ col │ + │ --- │ + │ type: List(Int32) │ + ╞════════════════════╡ + │ [1, 2, 3, 4, 5, 6] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [7, 8, 9] │ + └────────────────────┘ + "); + + Ok(()) +} + #[test] fn execute_optional_field() -> Result<(), Error> { let array = fixtures::nested_struct_column(); @@ -390,20 +421,24 @@ fn extract_scalar_fields_from_nested_struct() { Field::new("c", DataType::Int32, true), ]); + let e_field = Field::new_list_field(DataType::Float32, false); + let root_fields = Fields::from(vec![ Field::new("a", DataType::Struct(bc_fields), true), Field::new("d", DataType::Int32, true), + Field::new("e", DataType::FixedSizeList(e_field.into(), 3), true), ]); let datatype = DataType::Struct(root_fields); let result = re_arrow_combinators::extract_nested_fields(&datatype, |dt| { - matches!(dt, DataType::Float64 | DataType::Int32) + matches!(dt, DataType::Float64 | DataType::Float32 | DataType::Int32) }) .expect("Should find nested fields"); insta::assert_snapshot!(formatted(result), @" .d (Int32) + .e[] (Float32) .a.b (Float64) .a.c (Int32) "); @@ -417,28 +452,37 @@ fn extract_scalar_fields_from_nested_list_struct() { // │ └─ c: [Int32] // └─ d: [Float64] - let b_list = DataType::List(Arc::new(Field::new_list_field(DataType::Float64, true))); - let c_list = DataType::List(Arc::new(Field::new_list_field(DataType::Int32, true))); + let b_list = DataType::List(Field::new_list_field(DataType::Float64, true).into()); + let c_list = DataType::List(Field::new_list_field(DataType::Int32, true).into()); let bc_fields = Fields::from(vec![ Field::new("b", b_list, true), Field::new("c", c_list, true), ]); - let d_list = DataType::List(Arc::new(Field::new_list_field(DataType::Float64, true))); + let d_list = DataType::List(Field::new_list_field(DataType::Float64, true).into()); + let e_list = DataType::List( + Field::new_list_field( + DataType::FixedSizeList(Field::new_list_field(DataType::Float32, false).into(), 3), + true, + ) + .into(), + ); let root_fields = Fields::from(vec![ Field::new("a", DataType::Struct(bc_fields), true), Field::new("d", d_list, true), + Field::new("e", e_list, true), ]); let datatype = DataType::Struct(root_fields); let result = re_arrow_combinators::extract_nested_fields(&datatype, |dt| { - matches!(dt, DataType::Float64 | DataType::Int32) + matches!(dt, DataType::Float64 | DataType::Float32 | DataType::Int32) }) .expect("Should find nested fields"); insta::assert_snapshot!(formatted(result), @" .d[] (Float64) + .e[][] (Float32) .a.b[] (Float64) .a.c[] (Int32) "); diff --git a/crates/store/re_arrow_combinators/tests/test_transform.rs b/crates/store/re_arrow_combinators/tests/test_transform.rs index 5cde79b21a5f..8c47ebae9639 100644 --- a/crates/store/re_arrow_combinators/tests/test_transform.rs +++ b/crates/store/re_arrow_combinators/tests/test_transform.rs @@ -7,7 +7,9 @@ use re_arrow_combinators::Selector; use re_arrow_combinators::Transform as _; use re_arrow_combinators::cast::{ListToFixedSizeList, PrimitiveCast}; use re_arrow_combinators::map::{MapFixedSizeList, MapList, MapPrimitive, ReplaceNull}; -use re_arrow_combinators::reshape::{PromoteInnerNulls, RowMajorToColumnMajor, StructToFixedList}; +use re_arrow_combinators::reshape::{ + Flatten, PromoteInnerNulls, RowMajorToColumnMajor, StructToFixedList, +}; use util::DisplayRB; use crate::util::fixtures; @@ -502,3 +504,57 @@ fn test_promote_inner_nulls_nested_struct() { └────────────────────────────────────────────────┘ "#); } + +#[test] +fn test_flatten_fixed_size_list() { + let array = fixtures::nested_list_struct_column(); + + // Produces List> instead of creating a new test case from scratch. + let source: ListArray = Selector::from_str(".poses[]") + .unwrap() + .then(MapList::new(StructToFixedList::new(["x", "y"]))) + .transform(&array) + .unwrap(); + + insta::assert_snapshot!(format!("{}", DisplayRB(source.clone())), @" + ┌────────────────────────────────────────┐ + │ col │ + │ --- │ + │ type: List(FixedSizeList(2 x Float64)) │ + ╞════════════════════════════════════════╡ + │ [[1.0, 2.0], [3.0, 4.0]] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [[5.0, 6.0]] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ null │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [[7.0, null], [9.0, 10.0]] │ + └────────────────────────────────────────┘ + "); + + let result = Flatten::new().transform(&source).unwrap(); + + insta::assert_snapshot!(format!("{}", DisplayRB(result)), @" + ┌────────────────────────┐ + │ col │ + │ --- │ + │ type: List(Float64) │ + ╞════════════════════════╡ + │ [1.0, 2.0, 3.0, 4.0] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [5.0, 6.0] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ null │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [7.0, null, 9.0, 10.0] │ + └────────────────────────┘ + "); +} diff --git a/crates/viewer/re_view/src/query.rs b/crates/viewer/re_view/src/query.rs index d21860a65a27..1c0e34523e9c 100644 --- a/crates/viewer/re_view/src/query.rs +++ b/crates/viewer/re_view/src/query.rs @@ -18,33 +18,32 @@ use crate::blueprint_resolved_results::{ }; use crate::{BlueprintResolvedResults, ComponentMappingError}; -/// Casts the values in a `ListArray` to the target datatype. +/// Casts to a `ListArray` with values matching `target_value_datatype`. /// -/// Returns the array unchanged if already the correct type (zero-copy). -fn cast_list_array_values( +/// Returns `source` unchanged if already the correct type (zero-copy). +fn cast_list_array( source: &arrow::array::ListArray, - target_datatype: &arrow::datatypes::DataType, + target_list_datatype: &arrow::datatypes::DataType, ) -> Result { - let values = source.values(); - - // Happy path: already the right type - if values.data_type() == target_datatype { + // Happy path: already the right type. + if source.data_type() == target_list_datatype { return Ok(source.clone()); } - // Cast the values - let casted_values = arrow::compute::cast(values, target_datatype)?; - - // Rebuild the ListArray with casted values - arrow::array::ListArray::try_new( - Arc::new(arrow::datatypes::Field::new_list_field( - target_datatype.clone(), - true, - )), - source.offsets().clone(), - casted_values, - source.nulls().cloned(), - ) + // Cast the entire list array to the target type, handling both value type + // changes (e.g., Int32 → Float32) and structural changes (e.g., FixedSizeList → List). + let casted = arrow::compute::cast(source, target_list_datatype)?; + + casted + .as_any() + .downcast_ref::() + .cloned() + .ok_or_else(|| { + arrow::error::ArrowError::CastError(format!( + "Expected ListArray after cast, got {:?}", + casted.data_type() + )) + }) } /// Applies a selector (if present) and casts the component for known datatypes (if required). @@ -71,10 +70,16 @@ fn transform_chunk( // Apply casting if target datatype is known. if let Some(dt) = target_datatype { - cast_list_array_values(&transformed, dt).map_err(|err| { + let target_list_datatype = arrow::datatypes::DataType::List(Arc::new( + // TODO(grtlr): Ideally we'd make a more informed guess about nullability here. + // But in the context of components setting the `ListArray` to nullable is the safe choice. + arrow::datatypes::Field::new_list_field(dt.clone(), true), + )); + + cast_list_array(&transformed, &target_list_datatype).map_err(|err| { ComponentMappingError::CastFailed { source_datatype: transformed.data_type().clone(), - target_datatype: dt.clone(), + target_datatype: target_list_datatype, err: Arc::new(err), } }) diff --git a/crates/viewer/re_view_time_series/tests/blueprint.rs b/crates/viewer/re_view_time_series/tests/blueprint.rs index 5caa88ecf8db..854e0532388a 100644 --- a/crates/viewer/re_view_time_series/tests/blueprint.rs +++ b/crates/viewer/re_view_time_series/tests/blueprint.rs @@ -5,7 +5,7 @@ use re_log_types::external::arrow::array::{ }; use re_log_types::external::arrow::datatypes::{DataType, Field}; use re_log_types::{EntityPath, TimePoint, Timeline, TimelineName}; -use re_sdk_types::archetypes::{self, Scalars, SeriesLines, SeriesPoints}; +use re_sdk_types::archetypes::{self, Scalars, SeriesLines, SeriesPoints, Transform3D}; use re_sdk_types::blueprint; use re_sdk_types::blueprint::archetypes::VisibleTimeRanges; use re_sdk_types::datatypes::{self, TimeRange}; @@ -670,3 +670,97 @@ fn setup_blueprint_with_explicit_mapping_nested(test_context: &mut TestContext) blueprint.add_view_at_root(view) }) } + +#[test] +pub fn test_transform3d_time_series() { + let mut test_context = TestContext::new(); + test_context.register_view_class::(); + + let timeline = test_context.active_timeline().unwrap(); + log_data_transform3d(&mut test_context, timeline); + + let view_id = setup_blueprint_transform3d(&mut test_context); + + let size = egui::vec2(300.0, 300.0); + let mut snapshot_results = SnapshotResults::new(); + snapshot_results.add(test_context.run_view_ui_and_save_snapshot( + view_id, + "transform3d_time_series", + size, + None, + )); +} + +fn log_data_transform3d(test_context: &mut TestContext, timeline: re_log_types::Timeline) { + for i in 0..=MAX_TIME { + let timepoint = TimePoint::from([(timeline, i)]); + let t = i as f64 / 8.0; + + // Phase offsets ensure no translation component is ever exactly zero. + let translation = [ + (t + 0.2).sin() as f32, + (t * 0.7 + 0.4).sin() as f32, + (t * 0.5 + 0.6).sin() as f32, + ]; + + // Rotate around a general axis (1, 2, 3) with an initial angle offset + // so all four quaternion components are always non-zero. + let angle = t as f32 + 0.5; + let half = angle / 2.0; + let norm = (1.0_f32 + 4.0 + 9.0).sqrt(); + let (ax, ay, az) = (1.0 / norm, 2.0 / norm, 3.0 / norm); + + test_context.log_entity("plots/transform", |builder| { + builder.with_archetype_auto_row( + timepoint, + &Transform3D::from_translation_rotation( + translation, + datatypes::Quaternion::from_xyzw([ + ax * half.sin(), + ay * half.sin(), + az * half.sin(), + half.cos(), + ]), + ), + ) + }); + } +} + +fn setup_blueprint_transform3d(test_context: &mut TestContext) -> ViewId { + test_context.setup_viewport_blueprint(|ctx, blueprint| { + use re_sdk_types::blueprint::datatypes::{ComponentSourceKind, VisualizerComponentMapping}; + + let view = ViewBlueprint::new_with_root_wildcard(TimeSeriesView::identifier()); + + let translation_source = Transform3D::descriptor_translation().component.as_str(); + let quaternion_source = Transform3D::descriptor_quaternion().component.as_str(); + + ctx.save_visualizers( + &EntityPath::from("plots/transform"), + view.id, + [ + SeriesLines::new().visualizer().with_mappings([ + blueprint::components::VisualizerComponentMapping(VisualizerComponentMapping { + target: Scalars::descriptor_scalars().component.as_str().into(), + source_kind: ComponentSourceKind::SourceComponent, + source_component: Some(translation_source.into()), + // TODO(RR-3435): Once we can index into `FixedSizeListArray` using selectors, + // we should unroll the translation components via `[0]` to test that too. + selector: Some("[]".into()), + }), + ]), + SeriesLines::new().visualizer().with_mappings([ + blueprint::components::VisualizerComponentMapping(VisualizerComponentMapping { + target: Scalars::descriptor_scalars().component.as_str().into(), + source_kind: ComponentSourceKind::SourceComponent, + source_component: Some(quaternion_source.into()), + selector: Some("[]".into()), + }), + ]), + ], + ); + + blueprint.add_view_at_root(view) + }) +} diff --git a/crates/viewer/re_view_time_series/tests/snapshots/transform3d_time_series.png b/crates/viewer/re_view_time_series/tests/snapshots/transform3d_time_series.png new file mode 100644 index 000000000000..a31571320401 --- /dev/null +++ b/crates/viewer/re_view_time_series/tests/snapshots/transform3d_time_series.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6eed4c1d082ca7b87ec71bec2ec1912f69fa3f08628e5b5e447c3164fa84f0d4 +size 51490 diff --git a/tests/rust/re_integration_test/tests/snapshots/source_component_5.png b/tests/rust/re_integration_test/tests/snapshots/source_component_5.png index 8334b1f45531..ee6a3fe35abb 100644 --- a/tests/rust/re_integration_test/tests/snapshots/source_component_5.png +++ b/tests/rust/re_integration_test/tests/snapshots/source_component_5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1fa89b9acac8a453265e70348fde035fb1604a6ce2d984c0395d85d53a2c8a5a -size 220345 +oid sha256:e1e5f443625080420fa0f7d5e94d270dd609d0b80cac13383a74a5aa35eecd7d +size 215633 From d3eb132817f38f651839851a1878fdc33beae159 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 6 Mar 2026 10:07:39 +0100 Subject: [PATCH 052/513] Misc clippy 1.94 fixes (mostly) automatic clippy fixes using latest stable rust Source-Ref: 0cbca5982eafaefd6fd21926cecf7b8617be2410 --- .../re_dev_tools/src/build_examples/snippets.rs | 13 +++++-------- .../re_types_builder/src/codegen/python/mod.rs | 4 ++-- .../build/re_types_builder/src/codegen/rust/util.rs | 2 +- crates/build/re_types_builder/src/objects.rs | 4 ++-- crates/store/re_chunk_store/src/gc.rs | 8 ++++---- crates/store/re_chunk_store/src/query.rs | 8 ++++---- crates/store/re_data_loader/src/lerobot/common.rs | 2 +- .../re_log_encoding/src/rrd/footer/builders.rs | 6 +++--- .../parsers/ros2msg/geometry_msgs/pose_stamped.rs | 2 +- .../benches/transform_resolution_cache_bench.rs | 2 +- crates/utils/re_perf_telemetry/src/lib.rs | 6 ++---- crates/utils/re_video/src/av1.rs | 8 ++++---- crates/utils/re_video/src/player/mod.rs | 6 +----- crates/viewer/re_renderer/src/mesh.rs | 6 ++---- crates/viewer/re_renderer/src/renderer/lines.rs | 3 +-- .../viewer/re_renderer/src/renderer/point_cloud.rs | 3 +-- crates/viewer/re_view_graph/src/layout/request.rs | 8 ++++---- crates/viewer/re_view_spatial/src/picking.rs | 2 +- .../viewer/re_view_time_series/tests/blueprint.rs | 2 +- .../src/view/visualizer_entity_subscriber.rs | 12 +++++------- 20 files changed, 46 insertions(+), 61 deletions(-) diff --git a/crates/build/re_dev_tools/src/build_examples/snippets.rs b/crates/build/re_dev_tools/src/build_examples/snippets.rs index 28e748f43661..a459c0f29077 100644 --- a/crates/build/re_dev_tools/src/build_examples/snippets.rs +++ b/crates/build/re_dev_tools/src/build_examples/snippets.rs @@ -150,14 +150,11 @@ fn collect_snippets_recursively( let backwards_check_opted_out = is_opted_out_backwards_check; if meta.is_dir() { - snippets.extend( - collect_snippets_recursively( - Utf8Path::from_path(&path).unwrap(), - config, - snippet_root_path, - )? - .into_iter(), - ); + snippets.extend(collect_snippets_recursively( + Utf8Path::from_path(&path).unwrap(), + config, + snippet_root_path, + )?); continue; } diff --git a/crates/build/re_types_builder/src/codegen/python/mod.rs b/crates/build/re_types_builder/src/codegen/python/mod.rs index 85df9da8ee84..0c6aac1a5485 100644 --- a/crates/build/re_types_builder/src/codegen/python/mod.rs +++ b/crates/build/re_types_builder/src/codegen/python/mod.rs @@ -716,7 +716,7 @@ fn write_init_file( let path = kind_path.join("__init__.py"); let mut code = String::new(); - let manifest = quote_manifest(mods.iter().flat_map(|(_, names)| names.iter())); + let manifest = quote_manifest(mods.values().flat_map(|names| names.iter())); code.push_indented(0, format!("# {}", autogen_warning!()), 2); code.push_unindented( " @@ -1437,7 +1437,7 @@ fn quote_examples(examples: Vec>, lines: &mut Vec) { lines.push(format!("### `{name}`:")); } lines.push("```python".into()); - lines.extend(example.lines.into_iter()); + lines.extend(example.lines); lines.push("```".into()); if let Some(image) = &image { lines.extend( diff --git a/crates/build/re_types_builder/src/codegen/rust/util.rs b/crates/build/re_types_builder/src/codegen/rust/util.rs index da75d12be0df..32de3a04c4b1 100644 --- a/crates/build/re_types_builder/src/codegen/rust/util.rs +++ b/crates/build/re_types_builder/src/codegen/rust/util.rs @@ -350,7 +350,7 @@ pub fn doc_as_lines( } lines.push("```ignore".into()); - lines.extend(example.lines.into_iter()); + lines.extend(example.lines); lines.push("```".into()); if let Some(image) = &image { diff --git a/crates/build/re_types_builder/src/objects.rs b/crates/build/re_types_builder/src/objects.rs index 982133424d46..b291898d00d2 100644 --- a/crates/build/re_types_builder/src/objects.rs +++ b/crates/build/re_types_builder/src/objects.rs @@ -669,8 +669,8 @@ impl Object { class.is_enum() || val .union_type() - .filter(|utype| utype.base_type() != FbsBaseType::None) - .is_some() + .as_ref() + .is_some_and(|utype| utype.base_type() != FbsBaseType::None) }) .map(|val| { ObjectField::from_raw_enum_value(reporter, include_dir_path, enums, objs, enm, &val) diff --git a/crates/store/re_chunk_store/src/gc.rs b/crates/store/re_chunk_store/src/gc.rs index f096573d8e63..96dc7fd77c5d 100644 --- a/crates/store/re_chunk_store/src/gc.rs +++ b/crates/store/re_chunk_store/src/gc.rs @@ -276,10 +276,10 @@ impl ChunkStore { self.temporal_chunk_ids_per_entity_per_component .values() .flat_map(|temporal_chunk_ids_per_timeline| { - temporal_chunk_ids_per_timeline.iter().flat_map( - |(_timeline, temporal_chunk_ids_per_component)| { - temporal_chunk_ids_per_component.iter().flat_map( - |(_, temporal_chunk_ids_per_time)| { + temporal_chunk_ids_per_timeline.values().flat_map( + |temporal_chunk_ids_per_component| { + temporal_chunk_ids_per_component.values().flat_map( + |temporal_chunk_ids_per_time| { itertools::chain!( temporal_chunk_ids_per_time .per_start_time diff --git a/crates/store/re_chunk_store/src/query.rs b/crates/store/re_chunk_store/src/query.rs index eb1477bd8076..4cc7559fd821 100644 --- a/crates/store/re_chunk_store/src/query.rs +++ b/crates/store/re_chunk_store/src/query.rs @@ -291,8 +291,8 @@ impl ChunkStore { .get(entity_path) .map(|temporal_chunk_ids_per_timeline| { temporal_chunk_ids_per_timeline - .iter() - .flat_map(|(_, temporal_chunk_ids_per_component)| { + .values() + .flat_map(|temporal_chunk_ids_per_component| { temporal_chunk_ids_per_component.keys().copied() }) .collect() @@ -330,8 +330,8 @@ impl ChunkStore { .get(entity_path) .map(|temporal_chunk_ids_per_timeline| { temporal_chunk_ids_per_timeline - .iter() - .flat_map(|(_, temporal_chunk_ids_per_component)| { + .values() + .flat_map(|temporal_chunk_ids_per_component| { temporal_chunk_ids_per_component.keys().copied() }) .collect() diff --git a/crates/store/re_data_loader/src/lerobot/common.rs b/crates/store/re_data_loader/src/lerobot/common.rs index 2afd00a578f7..9475d2f5309f 100644 --- a/crates/store/re_data_loader/src/lerobot/common.rs +++ b/crates/store/re_data_loader/src/lerobot/common.rs @@ -85,7 +85,7 @@ pub fn load_and_stream_common( return; }; - for chunk in std::iter::once(initial).chain(chunks.into_iter()) { + for chunk in std::iter::once(initial).chain(chunks) { let data = LoadedData::Chunk(loader_name.to_owned(), store_id.clone(), chunk); if send_crossbeam(tx, data).is_err() { diff --git a/crates/store/re_log_encoding/src/rrd/footer/builders.rs b/crates/store/re_log_encoding/src/rrd/footer/builders.rs index 3fecaa70cd3b..b1bfadb2057a 100644 --- a/crates/store/re_log_encoding/src/rrd/footer/builders.rs +++ b/crates/store/re_log_encoding/src/rrd/footer/builders.rs @@ -345,8 +345,8 @@ impl RrdManifestBuilder { )) as ArrayRef; let columns_static = columns_static - .into_iter() - .flat_map(|(_desc, col)| [create_index_has_data_array(col.has_static_data)]); + .into_values() + .flat_map(|col| [create_index_has_data_array(col.has_static_data)]); let columns_temporal = columns_temporal.values().flat_map(|col| { [ @@ -355,7 +355,7 @@ impl RrdManifestBuilder { ] }); - let columns = columns.into_iter().flat_map(|(_key, col)| { + let columns = columns.into_values().flat_map(|col| { [ create_index_bound_array(col.timeline.typ(), &col.index.starts_inclusive), create_index_bound_array(col.timeline.typ(), &col.index.ends_inclusive), diff --git a/crates/store/re_mcap/src/parsers/ros2msg/geometry_msgs/pose_stamped.rs b/crates/store/re_mcap/src/parsers/ros2msg/geometry_msgs/pose_stamped.rs index d1e4a1a7c957..f30415782d63 100644 --- a/crates/store/re_mcap/src/parsers/ros2msg/geometry_msgs/pose_stamped.rs +++ b/crates/store/re_mcap/src/parsers/ros2msg/geometry_msgs/pose_stamped.rs @@ -88,7 +88,7 @@ impl MessageParser for PoseStampedMessageParser { timelines.clone(), pose_components .into_iter() - .chain(frame_components.into_iter()) + .chain(frame_components) .collect(), )?; diff --git a/crates/store/re_tf/benches/transform_resolution_cache_bench.rs b/crates/store/re_tf/benches/transform_resolution_cache_bench.rs index 282ef3eccf13..a2b1dc02df75 100644 --- a/crates/store/re_tf/benches/transform_resolution_cache_bench.rs +++ b/crates/store/re_tf/benches/transform_resolution_cache_bench.rs @@ -51,7 +51,7 @@ fn setup_store() -> (EntityDb, Vec) { } let chunk = builder.build().unwrap(); - events.extend(entity_db.add_chunk(&Arc::new(chunk)).unwrap().into_iter()); + events.extend(entity_db.add_chunk(&Arc::new(chunk)).unwrap()); } } (entity_db, events) diff --git a/crates/utils/re_perf_telemetry/src/lib.rs b/crates/utils/re_perf_telemetry/src/lib.rs index 90c9ae083c81..b97d41a489ca 100644 --- a/crates/utils/re_perf_telemetry/src/lib.rs +++ b/crates/utils/re_perf_telemetry/src/lib.rs @@ -150,10 +150,8 @@ impl opentelemetry::propagation::Injector for TraceHeaders { fn set(&mut self, key: &str, value: String) { match key { Self::TRACEPARENT_KEY => self.traceparent = value, - Self::TRACESTATE_KEY => { - if !value.is_empty() { - self.tracestate = Some(value); - } + Self::TRACESTATE_KEY if !value.is_empty() => { + self.tracestate = Some(value); } _ => {} } diff --git a/crates/utils/re_video/src/av1.rs b/crates/utils/re_video/src/av1.rs index 93f49e24e405..b1e4b8cacfe4 100644 --- a/crates/utils/re_video/src/av1.rs +++ b/crates/utils/re_video/src/av1.rs @@ -56,10 +56,10 @@ pub fn detect_av1_keyframe_start(data: &[u8]) -> Result { - if is_keyframe(&mut cursor).map_err(DetectGopStartError::Av1ParserError)? { - keyframe_found = true; - } + ObuType::Frame | ObuType::FrameHeader + if is_keyframe(&mut cursor).map_err(DetectGopStartError::Av1ParserError)? => + { + keyframe_found = true; } _ => { // Skip other OBUs diff --git a/crates/utils/re_video/src/player/mod.rs b/crates/utils/re_video/src/player/mod.rs index 5be1ca655c01..a8ff0ef25182 100644 --- a/crates/utils/re_video/src/player/mod.rs +++ b/crates/utils/re_video/src/player/mod.rs @@ -732,11 +732,7 @@ impl VideoPlayer { let max_last_sample_idx = requested_sample_idx + self.sample_decoder.max_num_samples_to_enqueue_ahead(); - loop { - let Some(last_enqueued) = self.last_enqueued else { - break; - }; - + while let Some(last_enqueued) = self.last_enqueued { // Enqueued enough samples as described above? let enqueued_min_amount = last_enqueued >= min_last_sample_idx; let enqueued_max_amount = last_enqueued + 1 >= max_last_sample_idx; diff --git a/crates/viewer/re_renderer/src/mesh.rs b/crates/viewer/re_renderer/src/mesh.rs index 52f861b993dc..b38d3c1bb119 100644 --- a/crates/viewer/re_renderer/src/mesh.rs +++ b/crates/viewer/re_renderer/src/mesh.rs @@ -367,10 +367,8 @@ impl GpuMesh { // The bind group layout must be in sync with the mesh renderer. let mesh_bind_group_layout = ctx.renderer::().bind_group_layout; - for (material, uniform_buffer_binding) in data - .materials - .iter() - .zip(uniform_buffer_bindings.into_iter()) + for (material, uniform_buffer_binding) in + data.materials.iter().zip(uniform_buffer_bindings) { let bind_group = pools.bind_groups.alloc( device, diff --git a/crates/viewer/re_renderer/src/renderer/lines.rs b/crates/viewer/re_renderer/src/renderer/lines.rs index a608a0850bf4..f54211f09dcb 100644 --- a/crates/viewer/re_renderer/src/renderer/lines.rs +++ b/crates/viewer/re_renderer/src/renderer/lines.rs @@ -495,8 +495,7 @@ impl LineDrawData { .into_iter(); let mut start_vertex_for_next_batch = 0; - for (batch_info, uniform_buffer_binding) in - batches.iter().zip(uniform_buffer_bindings.into_iter()) + for (batch_info, uniform_buffer_binding) in batches.iter().zip(uniform_buffer_bindings) { let line_vertex_range_end = (start_vertex_for_next_batch + batch_info.line_vertex_count) diff --git a/crates/viewer/re_renderer/src/renderer/point_cloud.rs b/crates/viewer/re_renderer/src/renderer/point_cloud.rs index 1e8404dfd759..b52c4619106d 100644 --- a/crates/viewer/re_renderer/src/renderer/point_cloud.rs +++ b/crates/viewer/re_renderer/src/renderer/point_cloud.rs @@ -355,8 +355,7 @@ impl PointCloudDrawData { .into_iter(); let mut start_point_for_next_batch = 0; - for (batch_info, uniform_buffer_binding) in - batches.iter().zip(uniform_buffer_bindings.into_iter()) + for (batch_info, uniform_buffer_binding) in batches.iter().zip(uniform_buffer_bindings) { let point_vertex_range_end = start_point_for_next_batch + batch_info.point_count; let mut active_phases = enum_set![DrawPhase::Opaque | DrawPhase::PickingLayer]; diff --git a/crates/viewer/re_view_graph/src/layout/request.rs b/crates/viewer/re_view_graph/src/layout/request.rs index 6a0e8fa8c863..1b5973ce33c0 100644 --- a/crates/viewer/re_view_graph/src/layout/request.rs +++ b/crates/viewer/re_view_graph/src/layout/request.rs @@ -87,14 +87,14 @@ impl LayoutRequest { /// Returns all nodes from all graphs in this request. pub(super) fn all_nodes(&self) -> impl Iterator + '_ { self.graphs - .iter() - .flat_map(|(_, graph)| graph.nodes.iter().map(|(k, v)| (*k, v))) + .values() + .flat_map(|graph| graph.nodes.iter().map(|(k, v)| (*k, v))) } /// Returns all edges from all graphs in this request. pub(super) fn all_edges(&self) -> impl Iterator + '_ { self.graphs - .iter() - .flat_map(|(_, graph)| graph.edges.iter().map(|(k, v)| (*k, v.as_slice()))) + .values() + .flat_map(|graph| graph.edges.iter().map(|(k, v)| (*k, v.as_slice()))) } } diff --git a/crates/viewer/re_view_spatial/src/picking.rs b/crates/viewer/re_view_spatial/src/picking.rs index 5ede1fc30a1b..c101bfc026ef 100644 --- a/crates/viewer/re_view_spatial/src/picking.rs +++ b/crates/viewer/re_view_spatial/src/picking.rs @@ -136,7 +136,7 @@ impl PickingContext { ); let mut image_hits = picking_textured_rects(self, images); - image_hits.sort_by(|a, b| b.depth_offset.cmp(&a.depth_offset)); + image_hits.sort_by_key(|a| std::cmp::Reverse(a.depth_offset)); let ui_hits = picking_ui_rects(self, ui_rects); diff --git a/crates/viewer/re_view_time_series/tests/blueprint.rs b/crates/viewer/re_view_time_series/tests/blueprint.rs index 854e0532388a..5a0abc4fa7d5 100644 --- a/crates/viewer/re_view_time_series/tests/blueprint.rs +++ b/crates/viewer/re_view_time_series/tests/blueprint.rs @@ -570,7 +570,7 @@ pub fn test_builtin_enum_not_visualizable_as_scalar() { data_result_tree.lookup_result_by_path(EntityPath::from("plots/markers_only").hash()); assert!( - result.is_none() || result.is_some_and(|r| r.visualizer_instructions.is_empty()), + result.is_none_or(|r| r.visualizer_instructions.is_empty()), "Entity with only a builtin enum component (MarkerShape/UInt8) should not have any \ active visualizers, even when explicitly configured in the blueprint, but got: {result:?}", ); diff --git a/crates/viewer/re_viewer_context/src/view/visualizer_entity_subscriber.rs b/crates/viewer/re_viewer_context/src/view/visualizer_entity_subscriber.rs index 06960920235a..77d358eee511 100644 --- a/crates/viewer/re_viewer_context/src/view/visualizer_entity_subscriber.rs +++ b/crates/viewer/re_viewer_context/src/view/visualizer_entity_subscriber.rs @@ -189,13 +189,11 @@ fn process_entity_components( }: re_chunk_store::ChunkMeta, ) { // Update indicated_entities. - if relevant_archetype.is_none() - || relevant_archetype.is_some_and(|archetype| { - components - .iter() - .any(|c| c.descriptor.archetype == Some(archetype)) - }) - { + if relevant_archetype.is_none_or(|archetype| { + components + .iter() + .any(|c| c.descriptor.archetype == Some(archetype)) + }) { store_mapping .indicated_entities .0 From 787e6cc087e57a831f2d4a9e906636a1f46ebf4d Mon Sep 17 00:00:00 2001 From: Michael Grupp Date: Fri, 6 Mar 2026 11:42:00 +0100 Subject: [PATCH 053/513] Add `--timestamp-offset-ns` option to MCAP CLI ### Related Closes [RR-3924](https://linear.app/rerun/issue/RR-3924) ### What See help string: ``` /// If set, an offset in nanoseconds to add to all timestamp timelines. /// /// This can be used to shift all timestamps of the MCAP file if they are not yet /// relative to the UNIX epoch. /// /// Duration and sequence timelines are not affected by this offset. ``` ### Testing Tested manually with an MCAP file. ### Compatibility just an option --------- Source-Ref: cbbe710ce2f0bc71cc572542124e14634cbc9302 Co-authored-by: ntjohnson1 <24689722+ntjohnson1@users.noreply.github.com> --- crates/store/re_chunk/src/chunk.rs | 17 ++++++++++ crates/store/re_data_loader/src/lib.rs | 5 +++ .../re_data_loader/src/loader_mcap/loader.rs | 32 ++++++++++++++++--- crates/top/re_sdk/src/recording_stream.rs | 1 + crates/top/rerun/src/commands/mcap/mod.rs | 11 +++++++ docs/content/reference/cli.md | 7 ++++ 6 files changed, 69 insertions(+), 4 deletions(-) diff --git a/crates/store/re_chunk/src/chunk.rs b/crates/store/re_chunk/src/chunk.rs index c2319b98e309..3adbc21891cf 100644 --- a/crates/store/re_chunk/src/chunk.rs +++ b/crates/store/re_chunk/src/chunk.rs @@ -1481,6 +1481,23 @@ impl TimeColumn { .map(|&t| TimeInt::new_temporal(t)) } } + + /// Returns a new [`TimeColumn`] with all time values offset by `offset_ns` nanoseconds. + /// + /// Uses saturating arithmetic. + pub fn offset_by_nanos(&self, offset_ns: i64) -> Self { + let new_times: Vec = self + .times + .iter() + .map(|&t| NonMinI64::saturating_from_i64(t.saturating_add(offset_ns)).get()) + .collect(); + + Self::new( + Some(self.is_sorted), + self.timeline, + ArrowScalarBuffer::from(new_times), + ) + } } impl Chunk { diff --git a/crates/store/re_data_loader/src/lib.rs b/crates/store/re_data_loader/src/lib.rs index f65976060e0c..f3e69afe416a 100644 --- a/crates/store/re_data_loader/src/lib.rs +++ b/crates/store/re_data_loader/src/lib.rs @@ -94,6 +94,9 @@ pub struct DataLoaderSettings { /// /// Defaults to `false`. pub follow: bool, + + /// If set, an offset in nanoseconds to add to all `TimestampNs` time columns. + pub timestamp_offset_ns: Option, } impl DataLoaderSettings { @@ -107,6 +110,7 @@ impl DataLoaderSettings { entity_path_prefix: None, timepoint: None, follow: false, + timestamp_offset_ns: None, } } @@ -138,6 +142,7 @@ impl DataLoaderSettings { entity_path_prefix, timepoint, follow: _, + timestamp_offset_ns: _, } = self; let mut args = Vec::new(); diff --git a/crates/store/re_data_loader/src/loader_mcap/loader.rs b/crates/store/re_data_loader/src/loader_mcap/loader.rs index a5f6988bd671..c19755d61cbb 100644 --- a/crates/store/re_data_loader/src/loader_mcap/loader.rs +++ b/crates/store/re_data_loader/src/loader_mcap/loader.rs @@ -240,26 +240,33 @@ pub fn load_mcap( return Ok(()); } + let timestamp_offset_ns = settings.timestamp_offset_ns; + let mut send_chunk = |chunk: re_chunk::Chunk| { // Apply lenses if configured, otherwise forward the chunk directly. if let Some(lenses) = lenses { for result in lenses.apply(&chunk) { match result { Ok(transformed_chunk) => { - send_chunk_to_channel(tx, &store_id, transformed_chunk); + send_chunk_to_channel( + tx, + &store_id, + transformed_chunk, + timestamp_offset_ns, + ); } Err(partial_chunk) => { for error in partial_chunk.errors() { re_log::error_once!("Lens error: {error}"); } if let Some(chunk) = partial_chunk.take() { - send_chunk_to_channel(tx, &store_id, chunk); + send_chunk_to_channel(tx, &store_id, chunk, timestamp_offset_ns); } } } } } else { - send_chunk_to_channel(tx, &store_id, chunk); + send_chunk_to_channel(tx, &store_id, chunk, timestamp_offset_ns); } }; @@ -277,7 +284,24 @@ pub fn load_mcap( Ok(()) } -fn send_chunk_to_channel(tx: &Sender, store_id: &StoreId, chunk: re_chunk::Chunk) { +fn send_chunk_to_channel( + tx: &Sender, + store_id: &StoreId, + mut chunk: re_chunk::Chunk, + timestamp_offset_ns: Option, +) { + if let Some(offset_ns) = timestamp_offset_ns { + let offset_timelines: Vec<_> = chunk + .timelines() + .values() + .filter(|time_col| time_col.timeline().typ() == re_log_types::TimeType::TimestampNs) + .map(|time_col| time_col.offset_by_nanos(offset_ns)) + .collect(); + for time_col in offset_timelines { + chunk.add_timeline(time_col).ok(); + } + } + if send_crossbeam( tx, LoadedData::Chunk(MCAP_LOADER_NAME.to_owned(), store_id.clone(), chunk), diff --git a/crates/top/re_sdk/src/recording_stream.rs b/crates/top/re_sdk/src/recording_stream.rs index 2477305bed15..5755279d49f8 100644 --- a/crates/top/re_sdk/src/recording_stream.rs +++ b/crates/top/re_sdk/src/recording_stream.rs @@ -1417,6 +1417,7 @@ impl RecordingStream { }) .unwrap_or_default() }), + timestamp_offset_ns: None, }; if prefer_current_recording { diff --git a/crates/top/rerun/src/commands/mcap/mod.rs b/crates/top/rerun/src/commands/mcap/mod.rs index c0ba20adbdd0..d9965df17684 100644 --- a/crates/top/rerun/src/commands/mcap/mod.rs +++ b/crates/top/rerun/src/commands/mcap/mod.rs @@ -47,6 +47,15 @@ pub struct ConvertCommand { /// output. #[clap(long = "recording-id")] recording_id: Option, + + /// If set, an offset in nanoseconds to add to all timestamp timelines. + /// + /// This can be used to shift all timestamps of the MCAP file if they are not yet + /// relative to the UNIX epoch. + /// + /// Duration and sequence timelines are not affected by this offset. + #[clap(long = "timestamp-offset-ns")] + timestamp_offset_ns: Option, } impl ConvertCommand { @@ -58,6 +67,7 @@ impl ConvertCommand { recording_id, selected_layers, disable_raw_fallback, + timestamp_offset_ns, } = self; let start_time = std::time::Instant::now(); @@ -92,6 +102,7 @@ impl ConvertCommand { loader.load_from_path( &DataLoaderSettings { application_id: Some(application_id), + timestamp_offset_ns: *timestamp_offset_ns, ..DataLoaderSettings::recommended(recording_id) }, path_to_input_mcap.into(), diff --git a/docs/content/reference/cli.md b/docs/content/reference/cli.md index 6b6848312478..17edc287792a 100644 --- a/docs/content/reference/cli.md +++ b/docs/content/reference/cli.md @@ -357,6 +357,13 @@ Convert an .mcap file to an .rrd. > > When this flag is set and multiple input .rdd files are specified, blueprint activation commands will be dropped from the resulting output. +* `--timestamp-offset-ns ` +> If set, an offset in nanoseconds to add to all timestamp timelines. +> +> This can be used to shift all timestamps of the MCAP file if they are not yet relative to the UNIX epoch. +> +> Duration and sequence timelines are not affected by this offset. + ## rerun rrd Manipulate the contents of .rrd and .rbl files. From af1f737838afe980ae98dd561a3cf4fffec50bd8 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 6 Mar 2026 11:49:05 +0100 Subject: [PATCH 054/513] Update a bunch of crate dependencies Source-Ref: c3b4465b69924b477394623e9d1125d93121e78b --- Cargo.lock | 1026 ++++++++++++++++--------------- Cargo.toml | 148 ++--- crates/utils/re_case/src/lib.rs | 4 +- deny.toml | 20 +- 4 files changed, 609 insertions(+), 589 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9d8ef6e131d4..1f0a7586fc4a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -173,7 +173,7 @@ checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", "const-random", - "getrandom 0.3.3", + "getrandom 0.3.4", "once_cell", "serde", "version_check", @@ -217,7 +217,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef6978589202a00cd7e118380c448a08b6ed394c3a8df3a430d0898e3a42d046" dependencies = [ "android-properties", - "bitflags 2.9.4", + "bitflags 2.11.0", "cc", "cesu8", "jni", @@ -314,9 +314,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.100" +version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" [[package]] name = "approx" @@ -327,15 +327,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "arbitrary" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" -dependencies = [ - "derive_arbitrary", -] - [[package]] name = "arboard" version = "3.6.1" @@ -364,20 +355,19 @@ checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" [[package]] name = "argh" -version = "0.1.13" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34ff18325c8a36b82f992e533ece1ec9f9a9db446bd1c14d4f936bac88fcd240" +checksum = "d32c2462e89541e6687e684d97310015d64a0627b61106fc472156a38f61cd1e" dependencies = [ "argh_derive", "argh_shared", - "rust-fuzzy-search", ] [[package]] name = "argh_derive" -version = "0.1.13" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adb7b2b83a50d329d5d8ccc620f5c7064028828538bdf5646acd60dc1f767803" +checksum = "ccc2a031b364bd099fed016feb1ccfca2c3549d63c16f330cfc40b27b7692231" dependencies = [ "argh_shared", "proc-macro2", @@ -387,9 +377,9 @@ dependencies = [ [[package]] name = "argh_shared" -version = "0.1.13" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a464143cc82dedcdc3928737445362466b7674b5db4e2eb8e869846d6d84f4f6" +checksum = "5b9abea17ef74821d1d3490aee9e0749d731445d965b7512308b2aa00c90079e" dependencies = [ "serde", ] @@ -613,7 +603,7 @@ version = "57.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c872d36b7bf2a6a6a2b40de9156265f0242910791db366a2c17476ba8330d68" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "serde_core", "serde_json", ] @@ -761,7 +751,7 @@ dependencies = [ "futures-lite", "parking", "polling", - "rustix 1.1.2", + "rustix 1.1.4", "slab", "windows-sys 0.61.2", ] @@ -792,7 +782,7 @@ dependencies = [ "cfg-if", "event-listener", "futures-lite", - "rustix 1.1.2", + "rustix 1.1.4", ] [[package]] @@ -818,7 +808,7 @@ dependencies = [ "cfg-if", "futures-core", "futures-io", - "rustix 1.1.2", + "rustix 1.1.4", "signal-hook-registry", "slab", "windows-sys 0.61.2", @@ -978,9 +968,9 @@ dependencies = [ [[package]] name = "axum" -version = "0.8.6" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a18ed336352031311f4e0b4dd2ff392d4fbb370777c9d18d7fc9d7359f73871" +checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8" dependencies = [ "axum-core", "bytes", @@ -1030,9 +1020,9 @@ dependencies = [ [[package]] name = "az" -version = "1.2.1" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b7e4c2464d97fe331d41de9d5db0def0a96f4d823b8b32a2efd503578988973" +checksum = "be5eb007b7cacc6c660343e96f650fedf4b5a77512399eb952ca6642cf8d13f7" [[package]] name = "backtrace" @@ -1148,12 +1138,12 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.9.4" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" dependencies = [ "bytemuck", - "serde", + "serde_core", ] [[package]] @@ -1331,9 +1321,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.20.2" +version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" [[package]] name = "byte-slice-cast" @@ -1343,9 +1333,9 @@ checksum = "7575182f7272186991736b70173b0ea045398f984bf5ebbb3804736ce1330c9d" [[package]] name = "bytemuck" -version = "1.24.0" +version = "1.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" dependencies = [ "bytemuck_derive", ] @@ -1421,7 +1411,7 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "log", "polling", "rustix 0.38.44", @@ -1443,33 +1433,24 @@ dependencies = [ [[package]] name = "camino" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "276a59bf2b2c967788139340c9f0c5b12d7fd6630315c15c217e559de85d2609" +checksum = "e629a66d692cb9ff1a1c664e41771b3dcaf961985a9774c0eb0bd1b51cf60a48" dependencies = [ "serde_core", ] [[package]] name = "cargo-manifest" -version = "0.19.1" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d8af896b707212cd0e99c112a78c9497dd32994192a463ed2f7419d29bd8c6" +checksum = "99c6f1c3eb197d17dde4f1a1170fbe7ce3e5f23f1166ae480875a9f4a9c11c40" dependencies = [ "serde", - "thiserror 2.0.17", + "thiserror 2.0.18", "toml 0.8.23", ] -[[package]] -name = "cargo-platform" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84982c6c0ae343635a3a4ee6dedef965513735c8b183caa7289fa6e27399ebd4" -dependencies = [ - "serde", -] - [[package]] name = "cargo-platform" version = "0.3.1" @@ -1491,49 +1472,18 @@ dependencies = [ "wasm-bindgen-cli-support", ] -[[package]] -name = "cargo-util-schemas" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dc1a6f7b5651af85774ae5a34b4e8be397d9cf4bc063b7e6dbd99a841837830" -dependencies = [ - "semver", - "serde", - "serde-untagged", - "serde-value", - "thiserror 2.0.17", - "toml 0.8.23", - "unicode-xid", - "url", -] - -[[package]] -name = "cargo_metadata" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cfca2aaa699835ba88faf58a06342a314a950d2b9686165e038286c30316868" -dependencies = [ - "camino", - "cargo-platform 0.2.0", - "cargo-util-schemas", - "semver", - "serde", - "serde_json", - "thiserror 2.0.17", -] - [[package]] name = "cargo_metadata" -version = "0.23.0" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "981a6f317983eec002839b90fae7411a85621410ae591a9cab2ecf5cb5744873" +checksum = "ef987d17b0a113becdd19d3d0022d04d7ef41f9efe4f3fb63ac44ba61df3ade9" dependencies = [ "camino", - "cargo-platform 0.3.1", + "cargo-platform", "semver", "serde", "serde_json", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -1615,9 +1565,9 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.42" +version = "0.4.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" +checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" dependencies = [ "iana-time-zone", "js-sys", @@ -1681,9 +1631,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.48" +version = "4.5.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2134bb3ea021b78629caa971416385309e0131b351b25e01dc16fb54e1b5fae" +checksum = "2797f34da339ce31042b27d23607e051786132987f595b02ba4f6a6dffb7030a" dependencies = [ "clap_builder", "clap_derive", @@ -1691,9 +1641,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.48" +version = "4.5.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2ba64afa3c0a6df7fa517765e31314e983f51dda798ffba27b988194fb65dc9" +checksum = "24a241312cea5059b13574bb9b3861cabf758b879c15190b37b6d6fd63ab6876" dependencies = [ "anstream", "anstyle", @@ -1703,9 +1653,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.47" +version = "4.5.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbfd7eae0b0f1a6e63d4b13c9c478de77c2eb546fba158ad50b4203dc24b9f9c" +checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5" dependencies = [ "heck", "proc-macro2", @@ -1715,9 +1665,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.5" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" +checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831" [[package]] name = "clean-path" @@ -1797,9 +1747,9 @@ dependencies = [ [[package]] name = "comfy-table" -version = "7.2.1" +version = "7.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b03b7db8e0b4b2fdad6c551e634134e99ec000e5c8c3b6856c65e8bbaded7a3b" +checksum = "958c5d6ecf1f214b4c2bbbbf6ab9523a864bd136dcf71a7e8904799acfe1ad47" dependencies = [ "crossterm", "unicode-segmentation", @@ -1929,9 +1879,9 @@ dependencies = [ [[package]] name = "convert_case" -version = "0.6.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +checksum = "affbf0190ed2caf063e3def54ff444b449371d55c58e513a95ab98eca50adb49" dependencies = [ "unicode-segmentation", ] @@ -1992,7 +1942,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "core-foundation 0.10.1", "libc", ] @@ -2137,11 +2087,11 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "crossterm_winapi", "document-features", "parking_lot", - "rustix 1.1.2", + "rustix 1.1.4", "winapi", ] @@ -3042,17 +2992,6 @@ dependencies = [ "serde_core", ] -[[package]] -name = "derive_arbitrary" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - [[package]] name = "devserver_lib" version = "0.4.2" @@ -3125,7 +3064,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "block2 0.6.2", "libc", "objc2 0.6.3", @@ -3144,9 +3083,9 @@ dependencies = [ [[package]] name = "dlib" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" +checksum = "ab8ecd87370524b461f8557c119c405552c396ed91fc0a8eec68679eab26f94a" dependencies = [ "libloading 0.8.9", ] @@ -3162,9 +3101,9 @@ dependencies = [ [[package]] name = "document-features" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95249b50c6c185bee49034bcb378a49dc2b5dff0be90ff6616d31d64febab05d" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" dependencies = [ "litrs", ] @@ -3266,7 +3205,7 @@ dependencies = [ "accesskit", "ahash", "backtrace", - "bitflags 2.9.4", + "bitflags 2.11.0", "emath", "epaint", "log", @@ -3290,7 +3229,7 @@ dependencies = [ "epaint", "log", "profiling", - "thiserror 2.0.17", + "thiserror 2.0.18", "type-map", "web-time", "wgpu", @@ -3583,18 +3522,18 @@ dependencies = [ [[package]] name = "env_filter" -version = "0.1.3" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" +checksum = "7a1c3cc8e57274ec99de65301228b537f1e4eedc1b8e0f9411c6caac8ae7308f" dependencies = [ "log", ] [[package]] name = "env_logger" -version = "0.11.8" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" +checksum = "b2daee4ea451f429a58296525ddf28b45a3b64f1acf6587e2067437bb11e218d" dependencies = [ "anstream", "anstyle", @@ -3635,17 +3574,6 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" -[[package]] -name = "erased-serde" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "259d404d09818dec19332e31d94558aeb442fea04c817006456c24b5460bbd4b" -dependencies = [ - "serde", - "serde_core", - "typeid", -] - [[package]] name = "errno" version = "0.3.14" @@ -3750,9 +3678,9 @@ dependencies = [ [[package]] name = "ffmpeg-sidecar" -version = "2.2.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "869812b38b887a5fb7291e5551677476c4696a395ae6676955f42f01e6a5d1c1" +checksum = "f076483fb6efcf02e4abcf3e9388d30123346f85b9a96e8fe834718951b945ed" dependencies = [ "anyhow", ] @@ -3765,9 +3693,9 @@ checksum = "0399f9d26e5191ce32c498bebd31e7a3ceabc2745f0ac54af3f335126c3f24b3" [[package]] name = "fixed" -version = "1.29.0" +version = "1.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707070ccf8c4173548210893a0186e29c266901b71ed20cd9e2ca0193dfe95c3" +checksum = "c566da967934c6c7ee0458a9773de9b2a685bd2ce26a3b28ddfc740e640182f5" dependencies = [ "az", "bytemuck", @@ -3796,11 +3724,11 @@ checksum = "c1671b620ba6e60c11c62b0ea5fec4f8621991e7b1229fa13c010a2cd04e4342" [[package]] name = "flatbuffers" -version = "25.9.23" +version = "25.12.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09b6620799e7340ebd9968d2e0708eb82cf1971e9a16821e2091b6d6e475eed5" +checksum = "35f6839d7b3b98adde531effaf34f0c2badc6f4735d26fe74709d8e513a96ef3" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "rustc_version", ] @@ -3820,6 +3748,12 @@ name = "float-cmp" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" + +[[package]] +name = "float-cmp" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b09cf3155332e944990140d967ff5eceb70df778b34f77d8075db46e4704e6d8" dependencies = [ "num-traits", ] @@ -3945,9 +3879,9 @@ checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" [[package]] name = "futures" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" dependencies = [ "futures-channel", "futures-core", @@ -3960,9 +3894,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" dependencies = [ "futures-core", "futures-sink", @@ -3970,15 +3904,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" [[package]] name = "futures-executor" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" dependencies = [ "futures-core", "futures-task", @@ -3987,9 +3921,9 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" [[package]] name = "futures-lite" @@ -4006,9 +3940,9 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", @@ -4017,15 +3951,15 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" [[package]] name = "futures-task" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" [[package]] name = "futures-timer" @@ -4035,9 +3969,9 @@ checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" [[package]] name = "futures-util" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ "futures-channel", "futures-core", @@ -4047,7 +3981,6 @@ dependencies = [ "futures-task", "memchr", "pin-project-lite", - "pin-utils", "slab", ] @@ -4234,24 +4167,37 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "wasi 0.11.1+wasi-snapshot-preview1", + "wasi", "wasm-bindgen", ] [[package]] name = "getrandom" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "js-sys", "libc", - "r-efi", - "wasi 0.14.7+wasi-0.2.4", + "r-efi 5.3.0", + "wasip2", "wasm-bindgen", ] +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", + "wasip2", + "wasip3", +] + [[package]] name = "gimli" version = "0.26.2" @@ -4282,9 +4228,9 @@ dependencies = [ [[package]] name = "glam" -version = "0.30.8" +version = "0.30.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e12d847aeb25f41be4c0ec9587d624e9cd631bc007a8fd7ce3f5851e064c6460" +checksum = "19fc433e8437a212d1b6f1e68c7824af3aed907da60afa994e7f542d18d12aa9" dependencies = [ "bytemuck", "serde_core", @@ -4366,7 +4312,7 @@ version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "12124de845cacfebedff80e877bb37b5b75c34c5a4c89e47e1cdd67fb6041325" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "cfg_aliases", "cgl", "dispatch2", @@ -4432,7 +4378,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbcd2dba93594b227a1f57ee09b8b9da8892c34d55aa332e034a228d0fe6a171" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "gpu-alloc-types", ] @@ -4442,7 +4388,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98ff03b468aa837d70984d55f5d3f846f6ec31fe34bbb97c4f85219caeee1ca4" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", ] [[package]] @@ -4463,7 +4409,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b89c83349105e3732062a895becfc71a8f921bb71ecbbdd8ff99263e3b53a0ca" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "gpu-descriptor-types", "hashbrown 0.15.5", ] @@ -4474,7 +4420,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdf242682df893b86f33a73828fb09ca4b2d3bb6cc95249707fc684d27484b91" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", ] [[package]] @@ -4652,11 +4598,11 @@ dependencies = [ [[package]] name = "home" -version = "0.5.11" +version = "0.5.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" +checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -4667,12 +4613,11 @@ checksum = "e9025058dae765dee5070ec375f591e2ba14638c63feff74f13805a72e523163" [[package]] name = "http" -version = "1.3.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" dependencies = [ "bytes", - "fnv", "itoa", ] @@ -5109,9 +5054,9 @@ dependencies = [ [[package]] name = "indicatif" -version = "0.18.0" +version = "0.18.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70a646d946d06bedbbc4cac4c218acf4bbf2d87757a784857025f4d447e4e1cd" +checksum = "25470f23803092da7d239834776d653104d551bc4d7eacaf31e6837854b8e9eb" dependencies = [ "console 0.16.1", "portable-atomic", @@ -5147,7 +5092,7 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f37dccff2791ab604f9babef0ba14fbe0be30bd368dc541e2b08d07c8aa908f3" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "inotify-sys", "libc", ] @@ -5163,9 +5108,9 @@ dependencies = [ [[package]] name = "insta" -version = "1.43.2" +version = "1.46.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46fdb647ebde000f43b5b53f773c30cf9b0cb4300453208713fa38b2c70935a0" +checksum = "e82db8c87c7f1ccecb34ce0c24399b8a73081427f3c7c50a5d597925356115e4" dependencies = [ "console 0.15.11", "globset", @@ -5175,6 +5120,7 @@ dependencies = [ "regex", "serde", "similar", + "tempfile", "walkdir", ] @@ -5274,15 +5220,15 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.17" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "jiff" -version = "0.2.15" +version = "0.2.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be1f93b8b1eb69c77f24bbb0afdf66f54b632ee39af40ca21c4365a1d7347e49" +checksum = "1a3546dc96b6d42c5f24902af9e2538e82e39ad350b0c766eb3fbf2d8f3d8359" dependencies = [ "jiff-static", "jiff-tzdb-platform", @@ -5290,16 +5236,16 @@ dependencies = [ "log", "portable-atomic", "portable-atomic-util", - "serde", + "serde_core", "wasm-bindgen", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "jiff-static" -version = "0.2.15" +version = "0.2.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4" +checksum = "2a8c8b344124222efd714b73bb41f8b5120b27a7cc1c75593a6ff768d9d05aa4" dependencies = [ "proc-macro2", "quote", @@ -5349,7 +5295,7 @@ version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" dependencies = [ - "getrandom 0.3.3", + "getrandom 0.3.4", "libc", ] @@ -5952,6 +5898,12 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + [[package]] name = "lenses" version = "0.31.0-alpha.1+dev" @@ -6072,7 +6024,7 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1580801010e535496706ba011c15f8532df6b42297d2e471fec38ceadd8c0638" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "libc", "redox_syscall 0.5.18", ] @@ -6106,9 +6058,9 @@ checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "linux-raw-sys" -version = "0.11.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "litemap" @@ -6118,9 +6070,9 @@ checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" [[package]] name = "litrs" -version = "0.4.2" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5e54036fe321fd421e10d732f155734c4e4afd610dd556d9a82833ab3ee0bed" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" [[package]] name = "lock_api" @@ -6324,9 +6276,9 @@ dependencies = [ [[package]] name = "mcap" -version = "0.23.3" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "900ec4fb152ab00bd02fe030787593dfd661b7711f7917ac732b85c22c773387" +checksum = "43908ab970f3a880b02834055a1e04221a3056f442a65ae9111f63e550e7daa5" dependencies = [ "bimap", "binrw", @@ -6363,9 +6315,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.8.0" +version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" [[package]] name = "memmap2" @@ -6378,9 +6330,9 @@ dependencies = [ [[package]] name = "memmap2" -version = "0.9.8" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843a98750cd611cc2965a8213b53b43e715f13c37a9e096c6408e69990961db7" +checksum = "714098028fe011992e1c3962653c96b2d578c4b4bce9036e15ff220319b1e0e3" dependencies = [ "libc", ] @@ -6410,7 +6362,7 @@ version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00c15a6f673ff72ddcc22394663290f870fb224c1bfce55734a75c414150e605" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "block", "core-graphics-types 0.2.0", "foreign-types", @@ -6542,7 +6494,7 @@ checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" dependencies = [ "libc", "log", - "wasi 0.11.1+wasi-snapshot-preview1", + "wasi", "windows-sys 0.59.0", ] @@ -6608,7 +6560,7 @@ checksum = "12b2e757b11b47345d44e7760e45458339bc490463d9548cd8651c53ae523153" dependencies = [ "arrayvec", "bit-set", - "bitflags 2.9.4", + "bitflags 2.11.0", "cfg-if", "cfg_aliases", "codespan-reporting", @@ -6622,7 +6574,7 @@ dependencies = [ "once_cell", "rustc-hash 1.1.0", "spirv", - "thiserror 2.0.17", + "thiserror 2.0.18", "unicode-ident", ] @@ -6662,7 +6614,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "jni-sys", "log", "ndk-sys", @@ -6698,7 +6650,7 @@ version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "cfg-if", "cfg_aliases", "libc", @@ -6736,7 +6688,7 @@ version = "8.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "crossbeam-channel", "fsevent-sys", "inotify", @@ -6757,9 +6709,9 @@ checksum = "5e0826a989adedc2a244799e823aece04662b66609d96af8dff7ac6df9a8925d" [[package]] name = "ntapi" -version = "0.4.1" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" +checksum = "c3b335231dfd352ffb0f8017f3b6027a4917f7df785ea2143d8af2adc66980ae" dependencies = [ "winapi", ] @@ -6928,7 +6880,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "block2 0.5.1", "libc", "objc2 0.5.2", @@ -6944,7 +6896,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "block2 0.6.2", "libc", "objc2 0.6.3", @@ -6965,7 +6917,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "block2 0.5.1", "objc2 0.5.2", "objc2-core-location", @@ -6978,7 +6930,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "objc2 0.6.3", "objc2-foundation 0.3.2", ] @@ -7000,7 +6952,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "block2 0.5.1", "objc2 0.5.2", "objc2-foundation 0.2.2", @@ -7012,7 +6964,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b402a653efbb5e82ce4df10683b6b28027616a2715e90009947d50b8dd298fa" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "objc2 0.6.3", "objc2-foundation 0.3.2", ] @@ -7023,7 +6975,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "dispatch2", "objc2 0.6.3", ] @@ -7034,7 +6986,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "dispatch2", "objc2 0.6.3", "objc2-core-foundation", @@ -7081,7 +7033,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "objc2 0.6.3", "objc2-core-foundation", "objc2-core-graphics", @@ -7093,7 +7045,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d425caf1df73233f29fd8a5c3e5edbc30d2d4307870f802d18f00d83dc5141a6" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "objc2 0.6.3", "objc2-core-foundation", "objc2-core-graphics", @@ -7112,7 +7064,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "block2 0.5.1", "dispatch", "libc", @@ -7125,7 +7077,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "objc2 0.6.3", "objc2-core-foundation", ] @@ -7136,7 +7088,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "objc2 0.6.3", "objc2-core-foundation", ] @@ -7159,7 +7111,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "block2 0.5.1", "objc2 0.5.2", "objc2-foundation 0.2.2", @@ -7171,7 +7123,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "block2 0.5.1", "objc2 0.5.2", "objc2-foundation 0.2.2", @@ -7184,7 +7136,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "objc2 0.6.3", "objc2-foundation 0.3.2", ] @@ -7205,7 +7157,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "block2 0.5.1", "objc2 0.5.2", "objc2-cloud-kit 0.2.2", @@ -7237,7 +7189,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "block2 0.5.1", "objc2 0.5.2", "objc2-core-location", @@ -7268,7 +7220,7 @@ dependencies = [ "itertools 0.14.0", "parking_lot", "percent-encoding", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "tracing", "url", @@ -7342,7 +7294,7 @@ dependencies = [ "futures-sink", "js-sys", "pin-project-lite", - "thiserror 2.0.17", + "thiserror 2.0.18", "tracing", ] @@ -7385,7 +7337,7 @@ dependencies = [ "opentelemetry_sdk", "prost", "reqwest", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "tonic", "tracing", @@ -7416,7 +7368,7 @@ dependencies = [ "opentelemetry", "percent-encoding", "rand 0.9.2", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "tokio-stream", ] @@ -7685,16 +7637,6 @@ dependencies = [ "indexmap 2.13.0", ] -[[package]] -name = "petgraph" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" -dependencies = [ - "fixedbitset 0.5.7", - "indexmap 2.13.0", -] - [[package]] name = "petgraph" version = "0.8.3" @@ -7797,9 +7739,9 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] name = "pin-utils" @@ -7911,7 +7853,7 @@ dependencies = [ "concurrent-queue", "hermit-abi", "pin-project-lite", - "rustix 1.1.2", + "rustix 1.1.4", "windows-sys 0.61.2", ] @@ -7923,9 +7865,9 @@ checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3" [[package]] name = "portable-atomic" -version = "1.11.1" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" [[package]] name = "portable-atomic-util" @@ -8039,9 +7981,9 @@ dependencies = [ [[package]] name = "prost" -version = "0.14.1" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7231bd9b3d3d33c86b58adbac74b5ec0ad9f496b19d22801d773636feaa95f3d" +checksum = "d2ea70524a2f82d518bce41317d0fae74151505651af45faf1ffbd6fd33f0568" dependencies = [ "bytes", "prost-derive", @@ -8049,16 +7991,15 @@ dependencies = [ [[package]] name = "prost-build" -version = "0.14.1" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac6c3320f9abac597dcbc668774ef006702672474aad53c6d596b62e487b40b1" +checksum = "343d3bd7056eda839b03204e68deff7d1b13aba7af2b2fd16890697274262ee7" dependencies = [ "heck", "itertools 0.14.0", "log", "multimap", - "once_cell", - "petgraph 0.7.1", + "petgraph 0.8.3", "prettyplease", "prost", "prost-types", @@ -8069,9 +8010,9 @@ dependencies = [ [[package]] name = "prost-derive" -version = "0.14.1" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9120690fafc389a67ba3803df527d0ec9cbbc9cc45e4cc20b332996dfb672425" +checksum = "27c6023962132f4b30eb4c172c91ce92d933da334c59c23cddee82358ddafb0b" dependencies = [ "anyhow", "itertools 0.14.0", @@ -8082,9 +8023,9 @@ dependencies = [ [[package]] name = "prost-reflect" -version = "0.16.2" +version = "0.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89a3ac73ec9a9118131a4594c9d336631a07852220a1d0ae03ee36b04503a063" +checksum = "b89455ef41ed200cafc47c76c552ee7792370ac420497e551f16123a9135f76e" dependencies = [ "logos", "prost", @@ -8093,9 +8034,9 @@ dependencies = [ [[package]] name = "prost-types" -version = "0.14.1" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9b4db3d6da204ed77bb26ba83b6122a73aeb2e87e25fbf7ad2e84c4ccbf8f72" +checksum = "8991c4cbdb8bc5b11f0b074ffe286c30e523de90fee5ba8132f1399f23cb3dd7" dependencies = [ "prost", ] @@ -8146,7 +8087,7 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e8bbe1a966bd2f362681a44f6edce3c2310ac21e4d5067a6e7ec396297a6ea0" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "memchr", "unicase", ] @@ -8246,7 +8187,7 @@ dependencies = [ "rustc-hash 2.1.1", "rustls", "socket2 0.5.10", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "tracing", "web-time", @@ -8259,7 +8200,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49df843a9161c85bb8aae55f101bc0bac8bcafd637a620d9122fd7e0b2f7422e" dependencies = [ "bytes", - "getrandom 0.3.3", + "getrandom 0.3.4", "lru-slab", "rand 0.9.2", "ring", @@ -8267,7 +8208,7 @@ dependencies = [ "rustls", "rustls-pki-types", "slab", - "thiserror 2.0.17", + "thiserror 2.0.18", "tinyvec", "tracing", "web-time", @@ -8289,9 +8230,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.44" +version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" dependencies = [ "proc-macro2", ] @@ -8302,6 +8243,12 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + [[package]] name = "radium" version = "0.7.0" @@ -8364,7 +8311,7 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ - "getrandom 0.3.3", + "getrandom 0.3.4", ] [[package]] @@ -8427,7 +8374,7 @@ version = "11.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6df7ab838ed27997ba19a4664507e6f82b41fe6e20be42929332156e5e85146" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", ] [[package]] @@ -8488,7 +8435,7 @@ dependencies = [ "serde", "serde_json", "sha2", - "thiserror 2.0.17", + "thiserror 2.0.18", "url", "uuid", "web-sys", @@ -8502,7 +8449,7 @@ dependencies = [ "insta", "re_arrow_util", "re_log", - "thiserror 2.0.17", + "thiserror 2.0.18", "vec1", ] @@ -8538,7 +8485,7 @@ dependencies = [ "re_tracing", "re_tuid", "serde_json", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -8550,7 +8497,7 @@ dependencies = [ "directories", "ehttp", "getrandom 0.2.17", - "getrandom 0.3.3", + "getrandom 0.3.4", "hmac", "http", "indicatif", @@ -8566,7 +8513,7 @@ dependencies = [ "serde_json", "sha2", "signature", - "thiserror 2.0.17", + "thiserror 2.0.18", "tiny_http", "tokio", "tonic", @@ -8582,7 +8529,7 @@ dependencies = [ name = "re_backoff" version = "0.31.0-alpha.1+dev" dependencies = [ - "getrandom 0.3.3", + "getrandom 0.3.4", "js-sys", "rand 0.9.2", "tokio", @@ -8630,7 +8577,7 @@ name = "re_build_tools" version = "0.31.0-alpha.1+dev" dependencies = [ "anyhow", - "cargo_metadata 0.23.0", + "cargo_metadata", "glob", "jiff", "regex-lite", @@ -8697,7 +8644,7 @@ dependencies = [ "re_tuid", "re_types_core", "similar-asserts", - "thiserror 2.0.17", + "thiserror 2.0.18", "tracing", ] @@ -8731,7 +8678,7 @@ dependencies = [ "saturating_cast", "similar-asserts", "tap", - "thiserror 2.0.17", + "thiserror 2.0.18", "web-time", ] @@ -8837,7 +8784,7 @@ dependencies = [ "insta", "itertools 0.14.0", "mcap", - "memmap2 0.9.8", + "memmap2 0.9.10", "notify", "parking_lot", "parquet", @@ -8861,7 +8808,7 @@ dependencies = [ "re_video", "serde", "serde_json", - "thiserror 2.0.17", + "thiserror 2.0.18", "urdf-rs", "walkdir", ] @@ -8991,7 +8938,7 @@ dependencies = [ "serde", "static_assertions", "strum", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", ] @@ -9007,7 +8954,7 @@ dependencies = [ "datafusion", "futures", "futures-util", - "getrandom 0.3.3", + "getrandom 0.3.4", "log", "parking_lot", "re_arrow_util", @@ -9033,7 +8980,7 @@ dependencies = [ "anyhow", "argh", "camino", - "cargo_metadata 0.23.0", + "cargo_metadata", "crossbeam", "glob", "indicatif", @@ -9048,7 +8995,7 @@ dependencies = [ "serde", "serde_json", "tempfile", - "toml 0.9.8", + "toml 0.9.12+spec-1.1.0", "ureq", "url", "wasm-bindgen-cli-support", @@ -9088,7 +9035,7 @@ dependencies = [ "similar-asserts", "static_assertions", "tap", - "thiserror 2.0.17", + "thiserror 2.0.18", "web-time", ] @@ -9125,7 +9072,7 @@ dependencies = [ "re_sorbet", "re_tracing", "re_uri", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "tokio-stream", "tonic", @@ -9208,7 +9155,7 @@ dependencies = [ "re_log", "re_log_types", "re_sdk_types", - "thiserror 2.0.17", + "thiserror 2.0.18", "vec1", ] @@ -9241,7 +9188,7 @@ dependencies = [ "re_tracing", "re_uri", "serde", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -9273,7 +9220,7 @@ dependencies = [ "re_types_core", "sha2", "similar-asserts", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "tokio-stream", "tracing", @@ -9316,7 +9263,7 @@ dependencies = [ "serde", "similar-asserts", "static_assertions", - "thiserror 2.0.17", + "thiserror 2.0.18", "typenum", "uuid", "web-time", @@ -9345,7 +9292,7 @@ dependencies = [ "serde", "serde_bytes", "strum", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -9457,7 +9404,7 @@ dependencies = [ "re_tuid", "re_types_core", "serde", - "thiserror 2.0.17", + "thiserror 2.0.18", "tonic", "tonic-prost", "tower", @@ -9504,7 +9451,7 @@ dependencies = [ "re_types_core", "seq-macro", "similar-asserts", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -9528,7 +9475,7 @@ dependencies = [ "assert_matches", "atomig", "av-data", - "bitflags 2.9.4", + "bitflags 2.11.0", "cc", "cfg-if", "libc", @@ -9624,7 +9571,7 @@ dependencies = [ "re_protos", "re_uri", "serde", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "tokio-stream", "tonic", @@ -9672,7 +9619,7 @@ version = "0.31.0-alpha.1+dev" dependencies = [ "ahash", "anyhow", - "bitflags 2.9.4", + "bitflags 2.11.0", "bytemuck", "cfg_aliases", "clean-path", @@ -9681,7 +9628,7 @@ dependencies = [ "document-features", "ecolor", "enumset", - "getrandom 0.3.3", + "getrandom 0.3.4", "glam", "gltf", "half", @@ -9710,7 +9657,7 @@ dependencies = [ "smallvec", "static_assertions", "stl_io", - "thiserror 2.0.17", + "thiserror 2.0.18", "tobj", "type-map", "unindent", @@ -9750,7 +9697,7 @@ version = "0.31.0-alpha.1+dev" dependencies = [ "anyhow", "serde", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -9758,7 +9705,7 @@ name = "re_rvl" version = "0.31.0-alpha.1+dev" dependencies = [ "byteorder", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -9799,7 +9746,7 @@ dependencies = [ "serde", "serde_json", "similar-asserts", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "uuid", "webbrowser", @@ -9842,7 +9789,7 @@ dependencies = [ "serde", "similar-asserts", "smallvec", - "thiserror 2.0.17", + "thiserror 2.0.18", "tiff", "uuid", ] @@ -9925,7 +9872,7 @@ dependencies = [ "re_types_core", "serde", "tempfile", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "tokio-stream", "tokio-util", @@ -9953,7 +9900,7 @@ dependencies = [ "re_types_core", "semver", "strum", - "thiserror 2.0.17", + "thiserror 2.0.18", "tracing", "web-time", ] @@ -10033,7 +9980,7 @@ version = "0.31.0-alpha.1+dev" dependencies = [ "ahash", "arrow", - "bitflags 2.9.4", + "bitflags 2.11.0", "criterion", "glam", "insta", @@ -10050,7 +9997,7 @@ dependencies = [ "re_mutex", "re_sdk_types", "re_tracing", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -10102,7 +10049,7 @@ dependencies = [ "bytemuck", "criterion", "document-features", - "getrandom 0.3.3", + "getrandom 0.3.4", "rand 0.9.2", "re_byte_size", "re_log", @@ -10143,7 +10090,7 @@ dependencies = [ "serde", "syn 2.0.117", "tempfile", - "toml 0.9.8", + "toml 0.9.12+spec-1.1.0", "unindent", "xshell", ] @@ -10154,7 +10101,7 @@ version = "0.31.0-alpha.1+dev" dependencies = [ "anyhow", "arrow", - "bitflags 2.9.4", + "bitflags 2.11.0", "bytemuck", "criterion", "document-features", @@ -10171,7 +10118,7 @@ dependencies = [ "re_tuid", "serde", "similar-asserts", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -10187,7 +10134,7 @@ dependencies = [ "egui_extras", "egui_kittest", "egui_tiles", - "getrandom 0.3.3", + "getrandom 0.3.4", "itertools 0.14.0", "jiff", "notify", @@ -10224,7 +10171,7 @@ dependencies = [ "re_tuid", "serde", "static_assertions", - "thiserror 2.0.17", + "thiserror 2.0.18", "url", ] @@ -10258,7 +10205,7 @@ dependencies = [ "scuffle-bytes-util", "serde", "smallvec", - "thiserror 2.0.17", + "thiserror 2.0.18", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", @@ -10287,7 +10234,7 @@ dependencies = [ "re_ui", "re_viewer_context", "re_viewport_blueprint", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -10398,7 +10345,7 @@ dependencies = [ "ahash", "anyhow", "arrow", - "bitflags 2.9.4", + "bitflags 2.11.0", "bytemuck", "egui", "glam", @@ -10439,7 +10386,7 @@ dependencies = [ "saturating_cast", "serde", "smallvec", - "thiserror 2.0.17", + "thiserror 2.0.18", "vec1", ] @@ -10465,7 +10412,7 @@ dependencies = [ "re_view", "re_viewer_context", "re_viewport_blueprint", - "thiserror 2.0.17", + "thiserror 2.0.18", "wgpu", ] @@ -10628,7 +10575,7 @@ dependencies = [ "strum_macros", "tap", "tempfile", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "tokio-stream", "url", @@ -10647,7 +10594,7 @@ dependencies = [ "anyhow", "arrow", "bit-vec", - "bitflags 2.9.4", + "bitflags 2.11.0", "bytemuck", "camino", "crossbeam", @@ -10706,7 +10653,7 @@ dependencies = [ "smallvec", "static_assertions", "strum_macros", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "uuid", "vec1", @@ -10766,7 +10713,7 @@ dependencies = [ "re_viewer_context", "slotmap", "smallvec", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -10777,9 +10724,9 @@ dependencies = [ "re_analytics", "re_build_tools", "re_log", - "thiserror 2.0.17", + "thiserror 2.0.18", "tiny_http", - "zip 2.4.2", + "zip 8.2.0", ] [[package]] @@ -10807,7 +10754,7 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", ] [[package]] @@ -10818,7 +10765,7 @@ checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" dependencies = [ "getrandom 0.2.17", "libredox", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -10849,7 +10796,7 @@ checksum = "78c81d000a2c524133cc00d2f92f019d399e57906c3b7119271a2495354fe895" dependencies = [ "cfg-if", "libc", - "rustix 1.1.2", + "rustix 1.1.4", "windows 0.61.3", ] @@ -10878,9 +10825,9 @@ dependencies = [ [[package]] name = "regex-lite" -version = "0.1.7" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "943f41321c63ef1c92fd763bfe054d2668f7f225a5c29f0105903dc2fc04ba30" +checksum = "cab834c73d247e67f4fae452806d17d3c7501756d98c8808d7c9c7aa7d18f973" [[package]] name = "regex-syntax" @@ -11124,7 +11071,7 @@ dependencies = [ "rustls", "strum", "strum_macros", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "tokio-stream", "tonic", @@ -11233,7 +11180,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db09040cc89e461f1a265139777a2bde7f8d8c67c4936f700c63ce3e2904d468" dependencies = [ "base64 0.22.1", - "bitflags 2.9.4", + "bitflags 2.11.0", "serde", "serde_derive", "unicode-ident", @@ -11294,12 +11241,6 @@ dependencies = [ "webbrowser", ] -[[package]] -name = "rust-fuzzy-search" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a157657054ffe556d8858504af8a672a054a6e0bd9e8ee531059100c0fa11bb2" - [[package]] name = "rust-stemmers" version = "1.2.0" @@ -11339,23 +11280,23 @@ dependencies = [ [[package]] name = "rustdoc-json" -version = "0.9.7" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab44348a3493c8a852182d0da3e6d92fe340dd099a745652f276ebbb2d34a330" +checksum = "fb043802e453091b5cbf79b8e6a3ff4c3ae4487d2ccf6c38842bff19865eb7c9" dependencies = [ "cargo-manifest", - "cargo_metadata 0.21.0", + "cargo_metadata", "serde", - "thiserror 2.0.17", - "toml 0.8.23", + "thiserror 2.0.18", + "toml 1.0.3+spec-1.1.0", "tracing", ] [[package]] name = "rustdoc-types" -version = "0.56.0" +version = "0.57.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27bf787c529efe523ed9eb6dcdbaa5954d34329f08d5c243fce928441826ca90" +checksum = "23539a7b40955b4fb2635f072adb28019c2d6a9dee3708bd25ce7b357a309352" dependencies = [ "serde", "serde_derive", @@ -11367,7 +11308,7 @@ version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "errno", "libc", "linux-raw-sys 0.4.15", @@ -11376,22 +11317,22 @@ dependencies = [ [[package]] name = "rustix" -version = "1.1.2" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "errno", "libc", - "linux-raw-sys 0.11.0", + "linux-raw-sys 0.12.1", "windows-sys 0.61.2", ] [[package]] name = "rustls" -version = "0.23.32" +version = "0.23.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd3c25631629d034ce7cd9940adc9d45762d46de2b0f57193c4443b92c6d4d40" +checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" dependencies = [ "log", "once_cell", @@ -11443,9 +11384,9 @@ checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" -version = "1.0.22" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] name = "same-file" @@ -11519,7 +11460,7 @@ version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271720403f46ca04f7ba6f55d438f8bd878d6b8ca0a1046e8228c4145bcbb316" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "core-foundation 0.10.1", "core-foundation-sys", "libc", @@ -11568,28 +11509,6 @@ dependencies = [ "serde_derive", ] -[[package]] -name = "serde-untagged" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9faf48a4a2d2693be24c6289dbe26552776eb7737074e6722891fadbe6c5058" -dependencies = [ - "erased-serde", - "serde", - "serde_core", - "typeid", -] - -[[package]] -name = "serde-value" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" -dependencies = [ - "ordered-float 2.10.1", - "serde", -] - [[package]] name = "serde-wasm-bindgen" version = "0.6.5" @@ -11645,15 +11564,15 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.149" +version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" dependencies = [ "itoa", "memchr", + "ryu", "serde", "serde_core", - "zmij", ] [[package]] @@ -11870,9 +11789,9 @@ checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" [[package]] name = "slotmap" -version = "1.0.7" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a" +checksum = "bdd58c3c93c3d278ca835519292445cb4b0d4dc59ccfdf7ceadaab3f8aeb4038" dependencies = [ "serde", "version_check", @@ -11893,13 +11812,13 @@ version = "0.19.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "calloop", "calloop-wayland-source", "cursor-icon", "libc", "log", - "memmap2 0.9.8", + "memmap2 0.9.10", "rustix 0.38.44", "thiserror 1.0.69", "wayland-backend", @@ -12018,7 +11937,7 @@ version = "0.3.0+sdk-1.3.268.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", ] [[package]] @@ -12092,12 +12011,12 @@ checksum = "e51f1e89f093f99e7432c491c382b88a6860a5adbe6bf02574bf0a08efff1978" [[package]] name = "stl_io" -version = "0.8.5" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cff2e145168af9fef3b518ac0c6f9849c407b3df8a28582ced9f1fda510aa34c" +checksum = "da63e75b86345156b191c021b3ce2a13b973941ecdb8c70d6f00cbbfe0076ed7" dependencies = [ "byteorder", - "float-cmp", + "float-cmp 0.10.0", ] [[package]] @@ -12106,7 +12025,7 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" dependencies = [ - "float-cmp", + "float-cmp 0.9.0", ] [[package]] @@ -12248,7 +12167,7 @@ dependencies = [ "lru 0.12.5", "lz4_flex 0.11.5", "measure_time", - "memmap2 0.9.8", + "memmap2 0.9.10", "once_cell", "oneshot", "rayon", @@ -12267,7 +12186,7 @@ dependencies = [ "tantivy-stacker", "tantivy-tokenizer-api", "tempfile", - "thiserror 2.0.17", + "thiserror 2.0.18", "time", "uuid", "winapi", @@ -12381,14 +12300,14 @@ checksum = "df7f62577c25e07834649fc3b39fafdc597c0a3527dc1c60129201ccfcbaa50c" [[package]] name = "tempfile" -version = "3.23.0" +version = "3.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" +checksum = "82a72c767771b47409d2345987fda8628641887d5466101319899796367354a0" dependencies = [ "fastrand", - "getrandom 0.3.3", + "getrandom 0.4.2", "once_cell", - "rustix 1.1.2", + "rustix 1.1.4", "windows-sys 0.61.2", ] @@ -12460,11 +12379,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl 2.0.17", + "thiserror-impl 2.0.18", ] [[package]] @@ -12480,9 +12399,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", @@ -12659,9 +12578,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.49.0" +version = "1.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" +checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" dependencies = [ "bytes", "libc", @@ -12696,9 +12615,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.17" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" +checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" dependencies = [ "futures-core", "pin-project-lite", @@ -12708,9 +12627,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.16" +version = "0.7.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" dependencies = [ "bytes", "futures-core", @@ -12734,14 +12653,14 @@ dependencies = [ [[package]] name = "toml" -version = "0.9.8" +version = "0.9.12+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8" +checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" dependencies = [ "indexmap 2.13.0", "serde_core", "serde_spanned 1.0.4", - "toml_datetime 0.7.3", + "toml_datetime 0.7.5+spec-1.1.0", "toml_parser", "toml_writer", "winnow", @@ -12753,10 +12672,12 @@ version = "1.0.3+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7614eaf19ad818347db24addfa201729cf2a9b6fdfd9eb0ab870fcacc606c0c" dependencies = [ + "indexmap 2.13.0", "serde_core", "serde_spanned 1.0.4", "toml_datetime 1.0.0+spec-1.1.0", "toml_parser", + "toml_writer", "winnow", ] @@ -12771,9 +12692,9 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.7.3" +version = "0.7.5+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" dependencies = [ "serde_core", ] @@ -12797,7 +12718,6 @@ dependencies = [ "serde", "serde_spanned 0.6.9", "toml_datetime 0.6.11", - "toml_write", "winnow", ] @@ -12808,7 +12728,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3effe7c0e86fdff4f69cdd2ccc1b96f933e24811c5441d44904e8683e27184b" dependencies = [ "indexmap 2.13.0", - "toml_datetime 0.7.3", + "toml_datetime 0.7.5+spec-1.1.0", "toml_parser", "winnow", ] @@ -12822,17 +12742,11 @@ dependencies = [ "winnow", ] -[[package]] -name = "toml_write" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" - [[package]] name = "toml_writer" -version = "1.0.4" +version = "1.0.6+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df8b2b54733674ad286d16267dcfc7a71ed5c776e4ac7aa3c3e2561f7c637bf2" +checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607" [[package]] name = "tonic" @@ -12939,7 +12853,7 @@ dependencies = [ "httparse", "js-sys", "pin-project", - "thiserror 2.0.17", + "thiserror 2.0.18", "tonic", "tower-service", "wasm-bindgen", @@ -12950,9 +12864,9 @@ dependencies = [ [[package]] name = "tower" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" dependencies = [ "futures-core", "futures-util", @@ -12969,11 +12883,11 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.6.6" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "bytes", "futures-util", "http", @@ -13000,9 +12914,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.41" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ "log", "pin-project-lite", @@ -13012,9 +12926,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.30" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", @@ -13023,9 +12937,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.34" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", "valuable", @@ -13044,16 +12958,13 @@ dependencies = [ [[package]] name = "tracing-opentelemetry" -version = "0.32.0" +version = "0.32.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e6e5658463dd88089aba75c7791e1d3120633b1bfde22478b28f625a9bb1b8e" +checksum = "1ac28f2d093c6c477eaa76b23525478f38de514fa9aeb1285738d4b97a9552fc" dependencies = [ "js-sys", "opentelemetry", - "opentelemetry_sdk", - "rustversion", "smallvec", - "thiserror 2.0.17", "tracing", "tracing-core", "tracing-log", @@ -13073,9 +12984,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.20" +version = "0.3.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5" +checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" dependencies = [ "matchers", "nu-ansi-term", @@ -13170,10 +13081,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a" [[package]] -name = "typeid" -version = "1.0.3" +name = "typed-path" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" +checksum = "8e28f89b80c87b8fb0cf04ab448d5dd0dd0ade2f8891bae878de66a75a28600e" [[package]] name = "typenum" @@ -13206,9 +13117,9 @@ checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" [[package]] name = "unicode-ident" -version = "1.0.24" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" +checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" [[package]] name = "unicode-segmentation" @@ -13286,14 +13197,15 @@ dependencies = [ [[package]] name = "url" -version = "2.5.7" +version = "2.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" dependencies = [ "form_urlencoded", "idna", "percent-encoding", "serde", + "serde_derive", ] [[package]] @@ -13344,13 +13256,13 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.18.1" +version = "1.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" +checksum = "b672338555252d43fd2240c714dc444b8c6fb0a5c5335e65a07bba7742735ddb" dependencies = [ - "getrandom 0.3.3", + "getrandom 0.4.2", "js-sys", - "serde", + "serde_core", "wasm-bindgen", ] @@ -13422,22 +13334,23 @@ dependencies = [ [[package]] name = "walkers" -version = "0.50.0" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a443c518c081ef29fafc4c53c32c6f8cba953b019854080214cad1e6d47c297" +checksum = "b715d486776584cab429e176be1785bc328a9067061be903cb88ff2ddd76d36a" dependencies = [ "bytes", "egui", "egui_extras", "futures", "geo-types", + "getrandom 0.2.17", "http-cache-reqwest", "image", "log", "lru 0.16.3", "reqwest", "reqwest-middleware", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "wasm-bindgen-futures", ] @@ -13455,8 +13368,8 @@ dependencies = [ "log", "rayon", "walrus-macro", - "wasm-encoder", - "wasmparser", + "wasm-encoder 0.214.0", + "wasmparser 0.214.0", ] [[package]] @@ -13487,21 +13400,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] -name = "wasi" -version = "0.14.7+wasi-0.2.4" +name = "wasip2" +version = "1.0.1+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" dependencies = [ - "wasip2", + "wit-bindgen 0.46.0", ] [[package]] -name = "wasip2" -version = "1.0.1+wasi-0.2.4" +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" dependencies = [ - "wit-bindgen", + "wit-bindgen 0.51.0", ] [[package]] @@ -13640,7 +13553,7 @@ dependencies = [ "leb128", "log", "walrus", - "wasmparser", + "wasmparser 0.214.0", ] [[package]] @@ -13664,6 +13577,28 @@ dependencies = [ "leb128", ] +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser 0.244.0", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap 2.13.0", + "wasm-encoder 0.244.0", + "wasmparser 0.244.0", +] + [[package]] name = "wasm-streams" version = "0.4.2" @@ -13684,13 +13619,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5309c1090e3e84dad0d382f42064e9933fdaedb87e468cc239f0eabea73ddcb6" dependencies = [ "ahash", - "bitflags 2.9.4", + "bitflags 2.11.0", "hashbrown 0.14.5", "indexmap 2.13.0", "semver", "serde", ] +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags 2.11.0", + "hashbrown 0.15.5", + "indexmap 2.13.0", + "semver", +] + [[package]] name = "wayland-backend" version = "0.3.11" @@ -13699,7 +13646,7 @@ checksum = "673a33c33048a5ade91a6b139580fa174e19fb0d23f396dca9fa15f2e1e49b35" dependencies = [ "cc", "downcast-rs 1.2.1", - "rustix 1.1.2", + "rustix 1.1.4", "scoped-tls", "smallvec", "wayland-sys", @@ -13711,8 +13658,8 @@ version = "0.31.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c66a47e840dc20793f2264eb4b3e4ecb4b75d91c0dd4af04b456128e0bdd449d" dependencies = [ - "bitflags 2.9.4", - "rustix 1.1.2", + "bitflags 2.11.0", + "rustix 1.1.4", "wayland-backend", "wayland-scanner", ] @@ -13723,7 +13670,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "cursor-icon", "wayland-backend", ] @@ -13745,7 +13692,7 @@ version = "0.32.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "efa790ed75fbfd71283bd2521a1cfdc022aabcc28bdcff00851f9e4ae88d9901" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "wayland-backend", "wayland-client", "wayland-scanner", @@ -13757,7 +13704,7 @@ version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fd38cdad69b56ace413c6bcc1fbf5acc5e2ef4af9d5f8f1f9570c0c83eae175" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "wayland-backend", "wayland-client", "wayland-protocols", @@ -13770,7 +13717,7 @@ version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1cb6cdc73399c0e06504c437fe3cf886f25568dd5454473d565085b36d6a8bbf" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "wayland-backend", "wayland-client", "wayland-protocols", @@ -13790,9 +13737,9 @@ dependencies = [ [[package]] name = "wayland-sys" -version = "0.31.7" +version = "0.31.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34949b42822155826b41db8e5d0c1be3a2bd296c747577a43a3e6daefc296142" +checksum = "81d2bd69b1dadd601d0e98ef2fc9339a1b1e00cec5ee7545a77b5a0f52a90394" dependencies = [ "dlib", "log", @@ -13822,9 +13769,9 @@ dependencies = [ [[package]] name = "webbrowser" -version = "1.0.5" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aaf4f3c0ba838e82b4e5ccc4157003fb8c324ee24c058470ffb82820becbde98" +checksum = "3f00bb839c1cf1e3036066614cbdcd035ecf215206691ea646aa3c60a24f68f2" dependencies = [ "core-foundation 0.10.1", "jni", @@ -13867,7 +13814,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfe68bac7cde125de7a731c3400723cadaaf1703795ad3f4805f187459cd7a77" dependencies = [ "arrayvec", - "bitflags 2.9.4", + "bitflags 2.11.0", "cfg-if", "cfg_aliases", "document-features", @@ -13898,7 +13845,7 @@ dependencies = [ "arrayvec", "bit-set", "bit-vec", - "bitflags 2.9.4", + "bitflags 2.11.0", "bytemuck", "cfg_aliases", "document-features", @@ -13913,7 +13860,7 @@ dependencies = [ "raw-window-handle", "rustc-hash 1.1.0", "smallvec", - "thiserror 2.0.17", + "thiserror 2.0.18", "wgpu-core-deps-apple", "wgpu-core-deps-emscripten", "wgpu-core-deps-wasm", @@ -13968,7 +13915,7 @@ dependencies = [ "arrayvec", "ash", "bit-set", - "bitflags 2.9.4", + "bitflags 2.11.0", "block", "bytemuck", "cfg-if", @@ -13999,7 +13946,7 @@ dependencies = [ "raw-window-handle", "renderdoc-sys", "smallvec", - "thiserror 2.0.17", + "thiserror 2.0.18", "wasm-bindgen", "web-sys", "wgpu-types", @@ -14013,11 +13960,11 @@ version = "27.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "afdcf84c395990db737f2dd91628706cb31e86d72e53482320d368e52b5da5eb" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "bytemuck", "js-sys", "log", - "thiserror 2.0.17", + "thiserror 2.0.18", "web-sys", ] @@ -14573,14 +14520,14 @@ checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "winit" -version = "0.30.12" +version = "0.30.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c66d4b9ed69c4009f6321f762d6e61ad8a2389cd431b97cb1e146812e9e6c732" +checksum = "a6755fa58a9f8350bd1e472d4c3fcc25f824ec358933bba33306d0b63df5978d" dependencies = [ "ahash", "android-activity", "atomic-waker", - "bitflags 2.9.4", + "bitflags 2.11.0", "block2 0.5.1", "bytemuck", "calloop", @@ -14592,7 +14539,7 @@ dependencies = [ "dpi", "js-sys", "libc", - "memmap2 0.9.8", + "memmap2 0.9.10", "ndk", "objc2 0.5.2", "objc2-app-kit 0.2.2", @@ -14637,6 +14584,94 @@ version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap 2.13.0", + "prettyplease", + "syn 2.0.117", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn 2.0.117", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags 2.11.0", + "indexmap 2.13.0", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder 0.244.0", + "wasm-metadata", + "wasmparser 0.244.0", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap 2.13.0", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser 0.244.0", +] + [[package]] name = "wkb" version = "0.9.2" @@ -14721,7 +14756,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "dlib", "log", "once_cell", @@ -15003,18 +15038,15 @@ dependencies = [ [[package]] name = "zip" -version = "2.4.2" +version = "8.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fabe6324e908f85a1c52063ce7aa26b68dcb7eb6dbc83a2d148403c9bc3eba50" +checksum = "b680f2a0cd479b4cff6e1233c483fdead418106eae419dc60200ae9850f6d004" dependencies = [ - "arbitrary", "crc32fast", - "crossbeam-utils", - "displaydoc", "flate2", "indexmap 2.13.0", "memchr", - "thiserror 2.0.17", + "typed-path", "zopfli", ] @@ -15024,12 +15056,6 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f06ae92f42f5e5c42443fd094f245eb656abf56dd7cce9b8b263236565e00f2" -[[package]] -name = "zmij" -version = "1.0.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" - [[package]] name = "zopfli" version = "0.8.3" diff --git a/Cargo.toml b/Cargo.toml index 739c61c400df..418542ee7360 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -184,15 +184,15 @@ egui_dnd = { version = "0.14.0" } egui_plot = "0.34.1" # https://github.com/emilk/egui_plot egui_table = "0.7.0" # https://github.com/rerun-io/egui_table egui_tiles = "0.14.1" # https://github.com/rerun-io/egui_tiles -walkers = "0.50.0" +walkers = "0.52.0" # All of our direct external dependencies should be found here: ahash = "0.8" -anyhow = { version = "1.0", default-features = false } -argh = "0.1.13" +anyhow = { version = "1.0.102", default-features = false } +argh = "0.1.15" arrayvec = "0.7" array-init = "2.1" -arrow = { version = "57.0.0", default-features = false, features = [ +arrow = { version = "57.3.0", default-features = false, features = [ # NOTE: Similar to `datafusion`, we enable many features on a workspace level # to avoid re-compilation when changing compile targets. "ffi", @@ -201,33 +201,33 @@ arrow = { version = "57.0.0", default-features = false, features = [ ] } async-stream = "0.3" async-trait = "0.1.89" -axum = "0.8.6" +axum = "0.8.8" backtrace = "0.3" base64 = "0.22" bincode = "1.3" bit-vec = "0.8" -bitflags = { version = "2.9", features = ["bytemuck"] } -bytemuck = { version = "1.24", features = ["extern_crate_alloc"] } +bitflags = { version = "2.11", features = ["bytemuck"] } +bytemuck = { version = "1.25", features = ["extern_crate_alloc"] } byteorder = "1.5.0" bytes = "1.11.1" -camino = "1.2" -cargo_metadata = "0.23.0" +camino = "1.2.2" +cargo_metadata = "0.23.1" cargo-run-wasm = "0.4.0" cdr-encoding = "0.10.2" -cfg_aliases = "0.2.0" -cfg-if = "1.0" -chrono = { version = "0.4.42", default-features = false } # Needed for datafusion, see `re_datafusion`'s Cargo.toml +cfg_aliases = "0.2.1" +cfg-if = "1.0.4" +chrono = { version = "0.4.44", default-features = false } # Needed for datafusion, see `re_datafusion`'s Cargo.toml clang-format = "0.3" -clap = { version = "4.5", features = ["derive"] } -clean-path = "0.2.0" +clap = { version = "4.5.60", features = ["derive"] } +clean-path = "0.2.1" colored = "2.2" # Old b/c of dify -comfy-table = { version = "7.2", default-features = false } +comfy-table = { version = "7.2.2", default-features = false } console_error_panic_hook = "0.1.7" const_format = "0.2.35" -convert_case = "0.6.0" -criterion = "0.5.0" +convert_case = "0.11.0" +criterion = "0.5.1" cros-codecs = "0.0.6" -crossbeam = "0.8.0" +crossbeam = "0.8.4" dae-parser = "0.11.0" datafusion = { version = "51.0.0", default-features = false, features = [ # NOTE: we enable the same features everywhere @@ -245,55 +245,55 @@ datafusion = { version = "51.0.0", default-features = false, features = [ ] } datafusion-ffi = "51.0.0" directories = "6.0" -document-features = "0.2.11" +document-features = "0.2.12" econtext = "0.2.0" # Prints error contexts on crashes ehttp = "0.6.0" enumset = "1.1.10" -env_filter = { version = "0.1.3", default-features = false } -env_logger = { version = "0.11.8", default-features = false } -ffmpeg-sidecar = { version = "2.2.0", default-features = false } -fixed = { version = "1.29", default-features = false } +env_filter = { version = "1.0.0", default-features = false } +env_logger = { version = "0.11.9", default-features = false } +ffmpeg-sidecar = { version = "2.4.0", default-features = false } +fixed = { version = "1.30", default-features = false } fjadra = "0.2.1" -flatbuffers = "25.9.23" -futures = "0.3.31" -futures-util = "0.3.31" -getrandom = "0.3.3" +flatbuffers = "25.12.19" +futures = "0.3.32" +futures-util = "0.3.32" +getrandom = "0.3.4" getrandom02 = { package = "getrandom", version = "0.2.17" } -glam = { version = "0.30.8", features = ["debug-glam-assert", "serde"] } +glam = { version = "0.30.10", features = ["debug-glam-assert", "serde"] } glob = "0.3.3" gltf = "1.4" h264-reader = "0.8.0" -half = { version = "2.6.0", features = ["bytemuck"] } -hexasphere = "16.0.0" +half = { version = "2.7.1", features = ["bytemuck"] } +hexasphere = "16.0.0" # Update in tandem with glam hmac = "0.12.1" -home = "0.5.11" -http = "1.3.1" +home = "0.5.12" +http = "1.4.0" http-body = "1.0.1" image = { version = "0.25.6", default-features = false, features = ["jpeg", "png"] } indent = "0.1.1" -indexmap = { version = "2.11", features = [ +indexmap = { version = "2.13", features = [ # indexmap version chosen to align with other dependencies "std", "serde", ] } -indicatif = "0.18.0" # Progress bar +indicatif = "0.18.4" # Progress bar infer = "0.16.0" # infer MIME type by checking the magic number signaturefer MIME type by checking the magic number signature -insta = "1.43" +insta = "1.46" itertools = "0.14.0" -jiff = { version = "0.2.15", features = ["js"] } +jiff = { version = "0.2.23", features = ["js"] } js-sys = "0.3.77" jsonwebtoken = { version = "10.3", default-features = false } lance = { version = "2.0.0", default-features = false } # When you update this, also update the list of features enabled for `datafusion` (~50 lines up) lance-index = { version = "2.0.0", default-features = false } lance-linalg = { version = "2.0.0", default-features = false } -libc = "0.2.176" +libc = "0.2.182" linked-hash-map = { version = "0.5.6", default-features = false } -log = "0.4.28" +log = "0.4.29" log-once = "0.4.1" lz4_flex = "0.12" macaw = "0.30.0" -mcap = "0.23.3" -memmap2 = "0.9.8" +mcap = "0.24.0" +memmap2 = "0.9.10" memory-stats = "1.2" mimalloc = { version = "0.1.48", features = ["v3"] } mime_guess2 = "2.3" # infer MIME type by file extension, and map mime to file extension @@ -308,35 +308,35 @@ num-traits = "0.2.19" numpy = "0.26.0" objc2-app-kit = "0.3.2" opentelemetry = { version = "0.31.0", features = ["metrics"] } -opentelemetry-appender-tracing = "0.31.0" +opentelemetry-appender-tracing = "0.31.1" opentelemetry-http = "0.31.0" opentelemetry-otlp = { version = "0.31.0", features = ["gzip-tonic"] } opentelemetry_sdk = { version = "0.31.0", features = ["rt-tokio"] } ordered-float = "5.1.0" parking_lot = { version = "0.12.5", features = ["serde"] } -parquet = { version = "57.0.0", default-features = false } +parquet = { version = "57.3.0", default-features = false } paste = "1.0" pathdiff = "0.2.3" percent-encoding = "2.3" pico-args = "0.5.0" -pin-project-lite = "0.2.16" +pin-project-lite = "0.2.17" ply-rs-bw = { version = "=3.0.0", default-features = false } # ply-rs-bw has released semver breaking changes in patch releases before. Fool me once, shame on you… poll-promise = "0.3.0" pollster = "0.4.0" prettyplease = "0.2.37" -proc-macro2 = { version = "1.0", default-features = false } +proc-macro2 = { version = "1.0.106", default-features = false } profiling = { version = "1.0.17", default-features = false } prometheus-client = "0.24.0" -prost = "0.14.1" -prost-build = "0.14.1" -prost-reflect = "0.16.1" -prost-types = "0.14.1" +prost = "0.14.3" +prost-build = "0.14.3" +prost-reflect = "0.16.3" +prost-types = "0.14.3" protoc-prebuilt = "0.3.0" puffin = "0.19.1" puffin_http = "0.16.1" pyo3 = "0.26.0" pyo3-build-config = "0.26.0" -quote = "1.0" +quote = "1.0.45" rand = { version = "0.9.2", default-features = false, features = [ "small_rng", "std", @@ -345,17 +345,17 @@ rand = { version = "0.9.2", default-features = false, features = [ rand_distr = { version = "0.5.1", default-features = false, features = ["std"] } raw-window-handle = "0.6.2" rayon = "1.11" -regex-lite = "0.1.7" +regex-lite = "0.1.9" rexif = "0.7.5" rfd = { version = "0.17.2", default-features = false, features = ["xdg-portal"] } ron = { version = "0.11.0", features = ["integer128"] } roxmltree = "0.20.0" -rustdoc-json = "0.9.7" -rustdoc-types = "0.56.0" -rustls = { version = "0.23.32", default-features = false } +rustdoc-json = "0.9.9" +rustdoc-types = "0.57.1" +rustls = { version = "0.23.37", default-features = false } saturating_cast = "0.1" scuffle-av1 = "0.1.4" -scuffle-bytes-util = "0.1.4" +scuffle-bytes-util = "0.1.5" semver = "1.0.27" seq-macro = "0.3.6" serde = { version = "1.0", features = ["derive"] } @@ -365,36 +365,36 @@ serde-wasm-bindgen = "0.6.5" sha2 = "0.10.9" signature = { version = "2.2", features = ["std"] } similar-asserts = "1.7.0" -slotmap = { version = "1.0.7", features = ["serde"] } +slotmap = { version = "1.1.1", features = ["serde"] } smallvec = { version = "1.15", features = ["const_generics", "union"] } static_assertions = "1.1" -stl_io = "0.8.5" +stl_io = "0.10.0" strum = { version = "0.26.3", features = ["derive"] } # need to update re_rav1d first strum_macros = "0.26.4" # need to update re_rav1d first sublime_fuzzy = "0.7.0" syn = "2.0" sysinfo = { version = "0.30.13", default-features = false } tap = "1.0.1" -tempfile = "3.23" -thiserror = "2.0.17" +tempfile = "3.26" +thiserror = "2.0.18" tiff = "0.9.1" tiny_http = { version = "0.12.0", default-features = false } tobj = "4.0" -tokio = { version = "1.47.1", default-features = false } -tokio-stream = "0.1.17" -tokio-util = { version = "0.7.16", default-features = false } -toml = { version = "0.9.8", default-features = false } +tokio = { version = "1.50.0", default-features = false } +tokio-stream = "0.1.18" +tokio-util = { version = "0.7.18", default-features = false } +toml = { version = "0.9.12", default-features = false } tonic = { version = "0.14.2", default-features = false } tonic-prost = { version = "0.14.2", default-features = false } tonic-prost-build = { version = "0.14.2", default-features = false } tonic-web = "0.14.2" tonic-web-wasm-client = "0.8.0" -tower = "0.5.2" -tower-http = "0.6.6" +tower = "0.5.3" +tower-http = "0.6.8" tower-service = "0.3.3" -tracing = "0.1.41" -tracing-opentelemetry = "0.32.0" -tracing-subscriber = { version = "0.3.20", features = ["tracing-log", "fmt", "env-filter"] } +tracing = "0.1.44" +tracing-opentelemetry = "0.32.1" +tracing-subscriber = { version = "0.3.22", features = ["tracing-log", "fmt", "env-filter"] } tracing-tracy = { version = "0.11.4", default-features = false, features = [ "broadcast", "callstack-inlines", @@ -409,8 +409,8 @@ typenum = "1.19" unindent = "0.2.4" urdf-rs = "0.9.0" ureq = "2.12.1" -url = "2.5" -uuid = { version = "1.18", features = ["serde", "v4", "js"] } +url = "2.5.8" +uuid = { version = "1.21", features = ["serde", "v4", "js"] } vec1 = { version = "1.12", features = ["serde", "smallvec-v1"] } walkdir = "2.5" # TODO(#8766): `rerun_js/web-viewer/build-wasm.mjs` is HIGHLY sensitive to changes in `wasm-bindgen`. @@ -422,14 +422,14 @@ walkdir = "2.5" # Do not make this an `=` dependency, because that may break Rust users’ builds when a newer # version is released, even if they are not building the web viewer. # For details see https://github.com/rerun-io/rerun/issues/8766 -wasm-bindgen = "0.2.100" # ⚠️ read above notice before touching this! +wasm-bindgen = "=0.2.100" # ⚠️ read above notice before touching this! wasm-bindgen-cli-support = "=0.2.100" # ⚠️ read above notice before touching this! wasm-bindgen-futures = "0.4.50" -wayland-sys = "0.31.7" web-sys = "0.3.77" +wayland-sys = "0.31.9" web-time = "1.1.0" -webbrowser = "1.0" -winit = { version = "0.30.12", default-features = false } +webbrowser = "1.1" +winit = { version = "0.30.13", default-features = false } # TODO(andreas): Try to get rid of `fragile-send-sync-non-atomic-wasm`. This requires re_renderer being aware of single-thread restriction on resources. # See also https://gpuweb.github.io/gpuweb/explainer/#multithreading-transfer (unsolved part of the Spec as of writing!) wgpu = { version = "27.0.1", default-features = false, features = [ @@ -451,7 +451,7 @@ wgpu = { version = "27.0.1", default-features = false, features = [ ] } xshell = "0.2.7" xxhash-rust = { version = "0.8", features = ["xxh32"] } -zip = { version = "2.1", default-features = false, features = ["deflate"] } +zip = { version = "8.2", default-features = false, features = ["deflate"] } # --------------------------------------------------------------------------------- [profile] diff --git a/crates/utils/re_case/src/lib.rs b/crates/utils/re_case/src/lib.rs index afe98d5b8151..cf3c5895db66 100644 --- a/crates/utils/re_case/src/lib.rs +++ b/crates/utils/re_case/src/lib.rs @@ -15,7 +15,7 @@ pub fn to_snake_case(s: &str) -> String { Boundary::LowerUpper, ]) .set_pattern(Pattern::Lowercase) - .set_delim("_"); + .set_delimiter("_"); let mut parts: Vec<_> = s.split('.').map(ToOwned::to_owned).collect(); if let Some(last) = parts.last_mut() { @@ -185,7 +185,7 @@ pub fn to_human_case(s: &str) -> String { Boundary::LowerUpper, ]) .set_pattern(Pattern::Sentence) - .set_delim(" "); + .set_delimiter(" "); let mut parts: Vec<_> = s.split('.').map(ToOwned::to_owned).collect(); if let Some(last) = parts.last_mut() { diff --git a/deny.toml b/deny.toml index 9cde63452787..ea826c437058 100644 --- a/deny.toml +++ b/deny.toml @@ -58,20 +58,21 @@ deny = [ ] skip = [ { name = "base64" }, # Too popular + { name = "bitflags" }, # core-graphics & png uses an older version. { name = "block2" }, # Old version via rfd - { name = "bzip2" }, # Remove after https://github.com/apache/datafusion/pull/17509 closes - { name = "console" }, # smallish - { name = "cargo-platform" }, # used by rustdoc-json { name = "cargo_metadata" }, # used by rustdoc-json + { name = "cargo-platform" }, # used by rustdoc-json + { name = "console" }, # smallish { name = "core-foundation" }, # Currently, e.g. `webbrowser` and `winit` use different versions. { name = "core-graphics-types" }, # wgpu requires 0.2 while winit is still on 0.1 { name = "downcast-rs" }, # eco-system is transitioning from 1 to 2 - { name = "event-listener" }, # remove after https://github.com/lance-format/lance/pull/4834 closes + { name = "float-cmp" }, { name = "gimli" }, # wasm-bindgen { name = "hashbrown" }, # Old version used by polar-rs - { name = "kurbo" }, # Different versions in egui_extras/resvg and epaint/vello_cpu { name = "itertools" }, # Too popular + { name = "kurbo" }, # Different versions in egui_extras/resvg and epaint/vello_cpu { name = "libloading" }, # datafusion-ffi needs an older version than wgpu + { name = "linux-raw-sys" }, # because of two rustix versions. { name = "lru" }, # because of lance { name = "lz4_flex" }, # the Arrow ecosystem is a bit behind, but it's fine, this is a very tiny, flat dependency { name = "memmap2" }, # because of walkers @@ -80,19 +81,12 @@ skip = [ { name = "objc2-foundation" }, # `accesskit_macos` uses a different version than `arboard` { name = "objc2" }, # `accesskit_macos` uses a different version than `arboard` { name = "ordered-float" }, # Old version being used by parquet, but super small! - { name = "petgraph" }, # Remove after next release due to https://github.com/tokio-rs/prost/pull/1327 - { name = "prost-build" }, # waiting new lance update - { name = "prost-derive" }, # waiting new lance update - { name = "prost-types" }, # waiting new lance update - { name = "prost" }, # waiting new lance update { name = "quick-xml" }, # because of urdf-rs { name = "redox_syscall" }, # Plenty of versions in the wild { name = "rustc-hash" }, # numpy with compatible pyo3 requires different version than wgpu + { name = "rustix" }, # tantivy uses an old version. { name = "socket2" }, # tonic, axum, … { name = "unicode-width" }, # walkers depends on 0.1 via http-cache-request / cacache - { name = "rustix" }, # tantivy uses an old version. - { name = "linux-raw-sys" }, # because of two rustix versions. - { name = "bitflags" }, # core-graphics & png uses an older version. { name = "zip" }, # duplicated by `protoc-prebuilt`, which uses an ancient version. ] skip-tree = [ From 0b43178d2c6198cb3fd801d104dc53f2f434fd15 Mon Sep 17 00:00:00 2001 From: Michael Grupp Date: Fri, 6 Mar 2026 11:58:08 +0100 Subject: [PATCH 055/513] Decode MCAP metadata records into `__properties` ### Related Closes [RR-3919](https://linear.app/rerun/issue/RR-3919) ### What Adds an MCAP file decoding layer for [metadata records](https://mcap.dev/spec#metadata-op0x0c), which are optional key-value maps (static per file). The layer adds these as `KeyValue` components in an `McapMetadata` archetype to the RRD `__properties` (similar to already existing `McapStatistics`). ### Testing * unit test * Tested with MCAP files containing metadata records. ### Compatibility Just adding extra properties to MCAP RRDs. --------- Source-Ref: 53f428db0f5e4e90c884fc3b0086d86dc3a32a39 Co-authored-by: ntjohnson1 <24689722+ntjohnson1@users.noreply.github.com> --- .../test_mcap_loader__tests__ros2.snap | 4 +- crates/store/re_mcap/src/layers/metadata.rs | 140 ++++++++++++++++++ crates/store/re_mcap/src/layers/mod.rs | 3 + .../mcap/cli-reference.md | 2 + .../howto/logging-and-ingestion/mcap.md | 1 + 5 files changed, 148 insertions(+), 2 deletions(-) create mode 100644 crates/store/re_mcap/src/layers/metadata.rs diff --git a/crates/store/re_data_loader/tests/snapshots/test_mcap_loader__tests__ros2.snap b/crates/store/re_data_loader/tests/snapshots/test_mcap_loader__tests__ros2.snap index 3c6b501d986d..60577e9a205e 100644 --- a/crates/store/re_data_loader/tests/snapshots/test_mcap_loader__tests__ros2.snap +++ b/crates/store/re_data_loader/tests/snapshots/test_mcap_loader__tests__ros2.snap @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bba7c07804bddf7af15748520973b4a1065f67cdabc4cb50bd8a7db860d1607b -size 264983 +oid sha256:5ab4c373cd567961dc5614554d7d65b8a05830f028d6d047c40a7a21d5aa8810 +size 266168 diff --git a/crates/store/re_mcap/src/layers/metadata.rs b/crates/store/re_mcap/src/layers/metadata.rs new file mode 100644 index 000000000000..0c046e55395e --- /dev/null +++ b/crates/store/re_mcap/src/layers/metadata.rs @@ -0,0 +1,140 @@ +use re_chunk::{Chunk, EntityPath, RowId, TimePoint}; +use re_sdk_types::{ + Component as _, ComponentBatch as _, ComponentDescriptor, SerializedComponentBatch, components, + datatypes, +}; + +use super::{Layer, LayerIdentifier}; +use crate::Error; + +/// Extracts [`mcap::records::Metadata`] records from an MCAP file as a single static chunk. +/// +/// Outputs a single `McapMetadata` archetype at [`EntityPath::properties()`], +/// with one [`components::KeyValuePairs`] component per metadata record. +#[derive(Debug, Default)] +pub struct McapMetadataLayer; + +const ARCHETYPE_NAME: &str = "McapMetadata"; + +impl Layer for McapMetadataLayer { + fn identifier() -> LayerIdentifier { + "metadata".into() + } + + fn process( + &mut self, + mcap_bytes: &[u8], + summary: &mcap::Summary, + emit: &mut dyn FnMut(Chunk), + ) -> Result<(), Error> { + if summary.metadata_indexes.is_empty() { + return Ok(()); + } + + let mut batches: Vec = Vec::new(); + + for index in &summary.metadata_indexes { + let metadata = match mcap::read::metadata(mcap_bytes, index) { + Ok(metadata) => metadata, + Err(err) => { + re_log::warn_once!( + "Failed to read MCAP metadata record '{}': {err}", + index.name + ); + continue; + } + }; + + re_log::debug!( + "Processing MCAP metadata record '{}' with {} entries", + metadata.name, + metadata.metadata.len(), + ); + + let pairs: Vec<_> = metadata + .metadata + .iter() + .map(|(key, value)| datatypes::Utf8Pair { + first: key.clone().into(), + second: value.clone().into(), + }) + .collect(); + let kv = components::KeyValuePairs(pairs); + + batches.push(kv.try_serialized(ComponentDescriptor { + archetype: Some(ARCHETYPE_NAME.into()), + component: metadata.name.into(), + component_type: Some(components::KeyValuePairs::name()), + })?); + } + + if !batches.is_empty() { + let chunk = Chunk::builder(EntityPath::properties()) + .with_serialized_batches(RowId::new(), TimePoint::STATIC, batches) + .build()?; + emit(chunk); + } + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use std::collections::BTreeMap; + use std::io; + + use re_chunk::Chunk; + + use crate::LayerRegistry; + + use super::*; + + /// Helper function to run the metadata layer and collect emitted chunks. + fn run_metadata_layer(buffer: &[u8]) -> Vec { + let reader = io::Cursor::new(buffer); + let summary = crate::read_summary(reader) + .expect("failed to read summary") + .expect("no summary found"); + + let mut chunks = Vec::new(); + let registry = LayerRegistry::empty().register_file_layer::(); + registry + .plan(&summary) + .expect("failed to plan") + .run(buffer, &summary, &mut |chunk| chunks.push(chunk)) + .expect("failed to run layer"); + chunks + } + + /// Tests that multiple metadata records are merged into a single chunk with one component per metadata. + #[test] + fn test_multiple_metadata_records() { + let buffer = { + let cursor = io::Cursor::new(Vec::new()); + let mut writer = mcap::Writer::new(cursor).expect("failed to create writer"); + + for i in 0..3 { + let mut key_values = BTreeMap::new(); + key_values.insert("index".to_owned(), i.to_string()); + writer + .write_metadata(&mcap::records::Metadata { + name: format!("meta_{i}"), + metadata: key_values, + }) + .expect("failed to write metadata"); + } + + writer.finish().expect("failed to finish writer"); + writer.into_inner().into_inner() + }; + + let chunks = run_metadata_layer(&buffer); + assert_eq!(chunks.len(), 1, "all metadata in a single chunk"); + + let chunk = &chunks[0]; + assert_eq!(chunk.entity_path(), &EntityPath::properties()); + assert!(chunk.is_static()); + assert_eq!(chunk.num_components(), 3); + } +} diff --git a/crates/store/re_mcap/src/layers/mod.rs b/crates/store/re_mcap/src/layers/mod.rs index af9d6f87f1e3..b0c21f2e56b9 100644 --- a/crates/store/re_mcap/src/layers/mod.rs +++ b/crates/store/re_mcap/src/layers/mod.rs @@ -1,3 +1,4 @@ +mod metadata; mod protobuf; mod raw; mod recording_info; @@ -11,6 +12,7 @@ use std::collections::{BTreeMap, BTreeSet}; use re_chunk::external::nohash_hasher::IntMap; use re_chunk::{Chunk, EntityPath}; +pub use self::metadata::McapMetadataLayer; pub use self::protobuf::McapProtobufLayer; pub use self::raw::McapRawLayer; pub use self::recording_info::McapRecordingInfoLayer; @@ -319,6 +321,7 @@ impl LayerRegistry { let mut registry = Self::empty() // file layers: .register_file_layer::() + .register_file_layer::() .register_file_layer::() .register_file_layer::() // message layers (priority order): diff --git a/docs/content/concepts/logging-and-ingestion/mcap/cli-reference.md b/docs/content/concepts/logging-and-ingestion/mcap/cli-reference.md index c04ca42c6678..852072f3d4bc 100644 --- a/docs/content/concepts/logging-and-ingestion/mcap/cli-reference.md +++ b/docs/content/concepts/logging-and-ingestion/mcap/cli-reference.md @@ -60,6 +60,7 @@ Decoding: - **`raw`**: Preserve original message bytes - **`schema`**: Extract metadata and schema information - **`stats`**: Compute file and channel statistics +- **`metadata`**: Extract metadata records into RRD `__properties`, if present - **`protobuf`**: Decode protobuf messages using into generic Arrow data without Rerun visualization components - **`recording_info`**: Extract recording session metadata @@ -80,6 +81,7 @@ rerun mcap convert input.mcap \ -l raw \ -l schema \ -l stats \ + -l metadata \ -l protobuf \ -l ros2msg \ -l foxglove \ diff --git a/docs/content/howto/logging-and-ingestion/mcap.md b/docs/content/howto/logging-and-ingestion/mcap.md index 2e4e8c8ba17a..8e67a18d811e 100644 --- a/docs/content/howto/logging-and-ingestion/mcap.md +++ b/docs/content/howto/logging-and-ingestion/mcap.md @@ -61,6 +61,7 @@ Each layer extracts different types of information from the MCAP source and each - **`raw`**: Logs the unprocessed message bytes as Rerun blobs without any interpretation - **`schema`**: Extracts metadata about channels, topics, and schemas - **`stats`**: Extracts file-level metrics like message counts, time ranges, and channel statistics +- **`metadata`** Extracts metadata records (if present) into the `__properties` of the RRD - **`protobuf`**: Automatically decodes protobuf-encoded messages using reflection - **`ros2msg`**: Provides semantic conversion of common ROS2 message types into Rerun's visualization components - **`ros2_reflection`**: Automatically decodes ROS2 messages using reflection From a53c683bb70594bb0fa83ebbd68b29d882817a33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jochen=20G=C3=B6rtler?= Date: Fri, 6 Mar 2026 13:15:44 +0100 Subject: [PATCH 056/513] Add `!` operator to `Selector` to assert non-`null` values ### Related * Closes RR-3889. * Related to RR-3766. * Follow up of #936. ### What This PR introduces a new `!` operator that basically means: _"I assert this field to be present. If it is `null` then don't return an output at all (in the context of this selector)."_ > [!IMPORTANT] > This is not present in the `jq` language. It has a similar meaning in TypeScript though, where it is used as a compile-time assertion. Introducing the new operator crucially keeps the `jq` syntax and semantics outside of it intact, which means less risk downstream. I have considered other alternatives: * changing the semantics of `?` * always doing `PromoteInnerNulls` implicitly * writing a `Selector` function * Having the user write out exception logic manually Source-Ref: db355a70fde5f98db8e88dfcd8f842034300f888 --- .../store/re_arrow_combinators/src/reshape.rs | 2 +- .../src/selector/lexer.rs | 19 +++ .../re_arrow_combinators/src/selector/mod.rs | 31 ++++- .../src/selector/parser.rs | 115 ++++++++++++++-- .../src/selector/runtime.rs | 16 ++- .../tests/test_selector.rs | 126 ++++++++++++++++++ .../tests/test_transform.rs | 62 +-------- crates/store/re_lenses/src/ast.rs | 13 -- 8 files changed, 284 insertions(+), 100 deletions(-) diff --git a/crates/store/re_arrow_combinators/src/reshape.rs b/crates/store/re_arrow_combinators/src/reshape.rs index e4a124404802..b6e7274b9f96 100644 --- a/crates/store/re_arrow_combinators/src/reshape.rs +++ b/crates/store/re_arrow_combinators/src/reshape.rs @@ -338,7 +338,7 @@ impl Transform for StructToFixedList { } /// Promotes lists with all inner `nulls` to outer `nulls`. -pub struct PromoteInnerNulls; +pub(crate) struct PromoteInnerNulls; impl Transform for PromoteInnerNulls { type Source = ListArray; diff --git a/crates/store/re_arrow_combinators/src/selector/lexer.rs b/crates/store/re_arrow_combinators/src/selector/lexer.rs index fd9117519f7b..53160e617416 100644 --- a/crates/store/re_arrow_combinators/src/selector/lexer.rs +++ b/crates/store/re_arrow_combinators/src/selector/lexer.rs @@ -17,6 +17,7 @@ pub enum TokenType { Dot, Pipe, QuestionMark, + ExclamationMark, } #[derive(Debug, PartialEq, Eq, thiserror::Error, Clone)] @@ -46,6 +47,7 @@ impl std::fmt::Display for TokenType { Self::Dot => write!(f, "."), Self::Pipe => write!(f, "|"), Self::QuestionMark => write!(f, "?"), + Self::ExclamationMark => write!(f, "!"), } } } @@ -144,6 +146,7 @@ impl<'a> Lexer<'a> { // Single-char tokens '|' => Ok(Some(self.make_token(TokenType::Pipe))), '?' => Ok(Some(self.make_token(TokenType::QuestionMark))), + '!' => Ok(Some(self.make_token(TokenType::ExclamationMark))), '[' => Ok(Some(self.make_token(TokenType::LBracket))), ']' => Ok(Some(self.make_token(TokenType::RBracket))), @@ -254,6 +257,22 @@ mod test { ); } + #[test] + fn exclamation_mark() { + assert_eq!( + extract_inner(Lexer::new(".foo!").scan_tokens().unwrap()), + vec![TokenType::Field("foo".into()), TokenType::ExclamationMark,] + ); + assert_eq!( + extract_inner(Lexer::new(".foo?!").scan_tokens().unwrap()), + vec![ + TokenType::Field("foo".into()), + TokenType::QuestionMark, + TokenType::ExclamationMark, + ] + ); + } + #[test] fn numbers() { assert_eq!( diff --git a/crates/store/re_arrow_combinators/src/selector/mod.rs b/crates/store/re_arrow_combinators/src/selector/mod.rs index 1f968584053f..582080289d58 100644 --- a/crates/store/re_arrow_combinators/src/selector/mod.rs +++ b/crates/store/re_arrow_combinators/src/selector/mod.rs @@ -7,13 +7,14 @@ //! //! The selector syntax is a subset of `jq`: //! -//! | Syntax | Meaning | Example | -//! |-------------|--------------------------------------------------|----------------| -//! | `.field` | Access a named field in a struct | `.location` | -//! | `[]` | Iterate over every element of a list | `.poses[]` | -//! | `[N]` | Index into a list by position | `.[0]` | -//! | `?` | Error suppression / optional operator | `.field?` | -//! | `\|` | Pipe the output of one expression to another | `.foo \| .bar` | +//! | Syntax | Meaning | Example | +//! |-------------|------------------------------------------------------------|----------------| +//! | `.field` | Access a named field in a struct | `.location` | +//! | `[]` | Iterate over every element of a list | `.poses[]` | +//! | `[N]` | Index into a list by position | `.[0]` | +//! | `?` | Error suppression / optional operator | `.field?` | +//! | `!` | Assert non-null (promotes all-null rows to outer nulls) | `.field!` | +//! | `\|` | Pipe the output of one expression to another | `.foo \| .bar` | //! //! Segments can be chained without an explicit pipe: `.poses[].x` is equivalent to `.poses[] | .x`. //! @@ -23,6 +24,19 @@ //! * **No filters, arithmetic, or built-in functions** — only path navigation and iteration are supported. //! * **No quoted field names or string interpolation** — field names must be bare identifiers //! (alphanumeric, `-`, `_`). +//! +//! # Protobuf and null handling +//! +//! The `?` and `!` operators exist primarily to handle Arrow columns produced from protobuf +//! messages. Proto3 `optional` fields have **presence tracking**: when a field is unset the +//! corresponding Arrow column contains `null` rather than the type's default value. Navigating +//! into a struct with optional sub-fields can therefore yield lists whose inner values are all +//! null (e.g. `[null]` instead of a top-level `null`). +//! +//! * `?` suppresses errors when a field is entirely absent from the schema, which happens +//! during schema evolution or when optional columns are omitted. +//! * `!` promotes rows where **all** inner values are null to an outer null, collapsing +//! `[null]` → `null` so downstream consumers see clean nullability. mod lexer; mod parser; @@ -136,6 +150,7 @@ fn process_datatype<'a, P>( path.push(Segment { kind: SegmentKind::Each, suppressed: false, + assert_non_null: false, }); match inner.data_type() { DataType::Struct(nested_fields) => { @@ -147,6 +162,7 @@ fn process_datatype<'a, P>( path.push(Segment { kind: SegmentKind::Each, suppressed: false, + assert_non_null: false, }); result.push((Selector(Expr::Path(path)), dt.clone())); } @@ -191,6 +207,7 @@ where field_path.push(Segment { kind: SegmentKind::Field(field.name().clone()), suppressed: false, + assert_non_null: false, }); process_datatype( field_path, diff --git a/crates/store/re_arrow_combinators/src/selector/parser.rs b/crates/store/re_arrow_combinators/src/selector/parser.rs index 19e0894a5c53..cc741b624063 100644 --- a/crates/store/re_arrow_combinators/src/selector/parser.rs +++ b/crates/store/re_arrow_combinators/src/selector/parser.rs @@ -11,7 +11,7 @@ //! Expr → Term ( ( '|' | ε ) Term )* //! Term → Segment+ //! | DOT -//! Segment → Primary '?'? +//! Segment → Primary ( '?' | '!' )* //! Primary → FIELD //! | '[' INTEGER ']' //! | '[' ']' @@ -50,7 +50,12 @@ impl std::fmt::Display for SegmentKind { #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Segment { pub kind: SegmentKind, + + /// When `true`, errors from this segment are suppressed (the `?` operator). pub suppressed: bool, + + /// When `true`, rows where all inner values are null will not produce output (the `!` operator). + pub assert_non_null: bool, } impl std::fmt::Display for Segment { @@ -59,6 +64,9 @@ impl std::fmt::Display for Segment { if self.suppressed { write!(f, "?")?; } + if self.assert_non_null { + write!(f, "!")?; + } Ok(()) } } @@ -177,20 +185,28 @@ where ) } - fn peek_question_mark(&mut self) -> bool { - if let Some(token) = self.tokens.peek() - && token.typ == TokenType::QuestionMark - { - self.tokens.next(); - return true; - } - false - } - fn segment(&mut self) -> Result { let kind = self.primary()?; - let suppressed = self.peek_question_mark(); - Ok(Segment { kind, suppressed }) + let mut suppressed = false; + let mut assert_non_null = false; + while let Some(token) = self.tokens.peek() { + match token.typ { + TokenType::QuestionMark => { + self.tokens.next(); + suppressed = true; + } + TokenType::ExclamationMark => { + self.tokens.next(); + assert_non_null = true; + } + _ => break, + } + } + Ok(Segment { + kind, + suppressed, + assert_non_null, + }) } fn primary(&mut self) -> Result { @@ -260,6 +276,7 @@ mod test { Segment { kind: SegmentKind::Field(name.into()), suppressed: false, + assert_non_null: false, } } @@ -267,6 +284,15 @@ mod test { Segment { kind: SegmentKind::Field(name.into()), suppressed: true, + assert_non_null: false, + } + } + + fn field_nn(name: &str) -> Segment { + Segment { + kind: SegmentKind::Field(name.into()), + suppressed: false, + assert_non_null: true, } } @@ -274,6 +300,7 @@ mod test { Segment { kind: SegmentKind::Index(n), suppressed: false, + assert_non_null: false, } } @@ -281,6 +308,15 @@ mod test { Segment { kind: SegmentKind::Index(n), suppressed: true, + assert_non_null: false, + } + } + + fn index_nn(n: u64) -> Segment { + Segment { + kind: SegmentKind::Index(n), + suppressed: false, + assert_non_null: true, } } @@ -288,6 +324,7 @@ mod test { Segment { kind: SegmentKind::Each, suppressed: false, + assert_non_null: false, } } @@ -295,6 +332,7 @@ mod test { Segment { kind: SegmentKind::Each, suppressed: true, + assert_non_null: false, } } @@ -452,4 +490,55 @@ mod test { let expr = parse(".[]?").unwrap(); assert_eq!(expr.to_string(), "[]?"); } + + #[test] + fn non_null_field() { + assert_eq!(parse(".foo!"), Ok(path(vec![field_nn("foo")]))); + assert_eq!( + parse(".foo!.bar"), + Ok(path(vec![field_nn("foo"), field("bar")])) + ); + } + + #[test] + fn non_null_index() { + assert_eq!(parse(".[0]!"), Ok(path(vec![index_nn(0)]))); + } + + #[test] + fn non_null_combined_with_optional() { + // Both `?` and `!` on the same segment + assert_eq!( + parse(".foo?!"), + Ok(path(vec![Segment { + kind: SegmentKind::Field("foo".into()), + suppressed: true, + assert_non_null: true, + }])) + ); + assert_eq!( + parse(".foo!?"), + Ok(path(vec![Segment { + kind: SegmentKind::Field("foo".into()), + suppressed: true, + assert_non_null: true, + }])) + ); + } + + #[test] + fn test_display_non_null() { + let expr = parse(".foo!").unwrap(); + assert_eq!(expr.to_string(), ".foo!"); + + let expr = parse(".foo!.bar").unwrap(); + assert_eq!(expr.to_string(), ".foo!.bar"); + + let expr = parse(".[0]!").unwrap(); + assert_eq!(expr.to_string(), "[0]!"); + + // Combined: `?` is displayed before `!` + let expr = parse(".foo?!").unwrap(); + assert_eq!(expr.to_string(), ".foo?!"); + } } diff --git a/crates/store/re_arrow_combinators/src/selector/runtime.rs b/crates/store/re_arrow_combinators/src/selector/runtime.rs index 834abf216735..3576fe5ae6db 100644 --- a/crates/store/re_arrow_combinators/src/selector/runtime.rs +++ b/crates/store/re_arrow_combinators/src/selector/runtime.rs @@ -8,7 +8,7 @@ use crate::{ Transform as _, index::GetIndexList, map::MapList, - reshape::{Flatten, GetField}, + reshape::{Flatten, GetField, PromoteInnerNulls}, }; use super::parser::{Expr, Segment, SegmentKind}; @@ -66,14 +66,20 @@ impl SegmentKind { impl Segment { fn execute(&self, source: &ListArray) -> Result, crate::Error> { - match self.kind.execute(source) { - Ok(result) => Ok(Some(result)), + let result = match self.kind.execute(source) { + Ok(result) => result, // TODO(RR-3435): FixedSizeListArray errors must be suppressed via `?`, but ListArray should not need it. Err(err) if self.suppressed => { re_log::trace!("Suppressed segment `{self}` suppressed error: {err}"); - Ok(None) + return Ok(None); } - Err(err) => Err(err), + Err(err) => return Err(err), + }; + + if self.assert_non_null { + Ok(Some(PromoteInnerNulls.transform(&result)?)) + } else { + Ok(Some(result)) } } } diff --git a/crates/store/re_arrow_combinators/tests/test_selector.rs b/crates/store/re_arrow_combinators/tests/test_selector.rs index 38a7b7726a3b..b68b59522b7d 100644 --- a/crates/store/re_arrow_combinators/tests/test_selector.rs +++ b/crates/store/re_arrow_combinators/tests/test_selector.rs @@ -401,6 +401,132 @@ fn execute_optional_each_suppressed() -> Result<(), Error> { Ok(()) } +#[test] +fn execute_non_null_field() -> Result<(), Error> { + let array = fixtures::nested_struct_column(); + + // Without `!`, row 1 is `[null]` (inner null within a list) + let without = ".location" + .parse::()? + .execute_per_row(&array)? + .unwrap(); + + insta::assert_snapshot!(format!("{}", DisplayRB(without)), @r#" + ┌────────────────────────────────────────────────┐ + │ col │ + │ --- │ + │ type: List(Struct("x": Float64, "y": Float64)) │ + ╞════════════════════════════════════════════════╡ + │ [{x: 1.0, y: 2.0}] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [null] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ null │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [{x: 3.0, y: 4.0}, {x: 5.0, y: 6.0}] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [null, {x: 7.0, y: 8.0}] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [null, null] │ + └────────────────────────────────────────────────┘ + "#); + + // With `!`, all-null rows ([null] and [null, null]) are promoted to outer nulls + let result = ".location!" + .parse::()? + .execute_per_row(&array)? + .unwrap(); + + insta::assert_snapshot!(format!("{}", DisplayRB(result)), @r#" + ┌────────────────────────────────────────────────┐ + │ col │ + │ --- │ + │ type: List(Struct("x": Float64, "y": Float64)) │ + ╞════════════════════════════════════════════════╡ + │ [{x: 1.0, y: 2.0}] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ null │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ null │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [{x: 3.0, y: 4.0}, {x: 5.0, y: 6.0}] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [null, {x: 7.0, y: 8.0}] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ null │ + └────────────────────────────────────────────────┘ + "#); + + Ok(()) +} + +#[test] +fn execute_non_null_nested() -> Result<(), Error> { + let array = fixtures::nested_struct_column(); + + // Without `!`, row 1 is `[null]` (inner null within a list) + let without = ".location" + .parse::()? + .execute_per_row(&array)? + .unwrap(); + + insta::assert_snapshot!(format!("{}", DisplayRB(without)), @r#" + ┌────────────────────────────────────────────────┐ + │ col │ + │ --- │ + │ type: List(Struct("x": Float64, "y": Float64)) │ + ╞════════════════════════════════════════════════╡ + │ [{x: 1.0, y: 2.0}] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [null] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ null │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [{x: 3.0, y: 4.0}, {x: 5.0, y: 6.0}] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [null, {x: 7.0, y: 8.0}] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [null, null] │ + └────────────────────────────────────────────────┘ + "#); + + // With `!` on the intermediate field, null locations are promoted before accessing `.x` + let result = ".location!.x" + .parse::()? + .execute_per_row(&array)? + .unwrap(); + + insta::assert_snapshot!(format!("{}", DisplayRB(result)), @r" + ┌─────────────────────┐ + │ col │ + │ --- │ + │ type: List(Float64) │ + ╞═════════════════════╡ + │ [1.0] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ null │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ null │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [3.0, 5.0] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ [null, 7.0] │ + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ + │ null │ + └─────────────────────┘ + "); + + Ok(()) +} + fn formatted(pair: impl IntoIterator) -> String { pair.into_iter() .map(|(sel, dt)| format!("{sel} ({dt})")) diff --git a/crates/store/re_arrow_combinators/tests/test_transform.rs b/crates/store/re_arrow_combinators/tests/test_transform.rs index 8c47ebae9639..05653737bd7a 100644 --- a/crates/store/re_arrow_combinators/tests/test_transform.rs +++ b/crates/store/re_arrow_combinators/tests/test_transform.rs @@ -7,9 +7,7 @@ use re_arrow_combinators::Selector; use re_arrow_combinators::Transform as _; use re_arrow_combinators::cast::{ListToFixedSizeList, PrimitiveCast}; use re_arrow_combinators::map::{MapFixedSizeList, MapList, MapPrimitive, ReplaceNull}; -use re_arrow_combinators::reshape::{ - Flatten, PromoteInnerNulls, RowMajorToColumnMajor, StructToFixedList, -}; +use re_arrow_combinators::reshape::{Flatten, RowMajorToColumnMajor, StructToFixedList}; use util::DisplayRB; use crate::util::fixtures; @@ -447,64 +445,6 @@ fn test_map_list_outer_nullability_identity() { "); } -#[test] -fn test_promote_inner_nulls_nested_struct() { - let array = fixtures::nested_struct_column(); - - let location = Selector::from_str(".location") - .unwrap() - .transform(&array) - .unwrap(); - - // Before: row 1 is `[null]` and row 5 is `[null, null]` - insta::assert_snapshot!(format!("{}", DisplayRB(location.clone())), @r#" - ┌────────────────────────────────────────────────┐ - │ col │ - │ --- │ - │ type: List(Struct("x": Float64, "y": Float64)) │ - ╞════════════════════════════════════════════════╡ - │ [{x: 1.0, y: 2.0}] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [null] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ null │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [{x: 3.0, y: 4.0}, {x: 5.0, y: 6.0}] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [null, {x: 7.0, y: 8.0}] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [null, null] │ - └────────────────────────────────────────────────┘ - "#); - - // After: row 1 `[null]` and row 6 `[null, null]` are promoted to outer `null`s - let result = PromoteInnerNulls.transform(&location).unwrap(); - - insta::assert_snapshot!(format!("{}", DisplayRB(result)), @r#" - ┌────────────────────────────────────────────────┐ - │ col │ - │ --- │ - │ type: List(Struct("x": Float64, "y": Float64)) │ - ╞════════════════════════════════════════════════╡ - │ [{x: 1.0, y: 2.0}] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ null │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ null │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [{x: 3.0, y: 4.0}, {x: 5.0, y: 6.0}] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ [null, {x: 7.0, y: 8.0}] │ - ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ null │ - └────────────────────────────────────────────────┘ - "#); -} - #[test] fn test_flatten_fixed_size_list() { let array = fixtures::nested_list_struct_column(); diff --git a/crates/store/re_lenses/src/ast.rs b/crates/store/re_lenses/src/ast.rs index afe3732fd9e3..f7f637ddd75f 100644 --- a/crates/store/re_lenses/src/ast.rs +++ b/crates/store/re_lenses/src/ast.rs @@ -144,9 +144,6 @@ pub enum Op { /// Converts timestamp structs with `seconds`/`nanos` or `sec`/`nsec` fields to total nanoseconds. TimeSpecToNanos, - /// Promotes lists where all inner values are null to outer nulls. - PromoteInnerNulls, - /// A user-defined arbitrary function to convert a component column. Func(CustomFn), } @@ -167,7 +164,6 @@ impl std::fmt::Debug for Op { f.debug_tuple("StringSuffixNonEmpty").field(suffix).finish() } Self::TimeSpecToNanos => f.debug_struct("TimeSpecToNanos").finish(), - Self::PromoteInnerNulls => f.debug_struct("PromoteInnerNulls").finish(), Self::Func(_) => f.debug_tuple("Func").field(&"").finish(), } } @@ -245,11 +241,6 @@ impl Op { Self::TimeSpecToNanos } - /// Promotes lists where all inner values are null to outer nulls. - pub fn promote_inner_nulls() -> Self { - Self::PromoteInnerNulls - } - /// A user-defined arbitrary function to convert a component column. pub fn func(func: F) -> Self where @@ -301,10 +292,6 @@ impl Op { .transform(list_array) .map_err(Into::into) .map(Some), - Self::PromoteInnerNulls => reshape::PromoteInnerNulls - .transform(list_array) - .map_err(Into::into) - .map(Some), Self::Func(func) => func(list_array).map(Some), } } From 0d985702eb03c7518ced87139184239270a88b00 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 6 Mar 2026 13:41:10 +0100 Subject: [PATCH 057/513] Fix weird tooltip sizes in streams view * Closes https://linear.app/rerun/issue/RR-3971/broken-hover-in-timepanel-long-paths Source-Ref: 0a8ca0dad86ca69b9c74f8a0ed203ced85a3a41b --- crates/viewer/re_time_panel/src/time_panel.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/viewer/re_time_panel/src/time_panel.rs b/crates/viewer/re_time_panel/src/time_panel.rs index 06098ab446a2..419488efaa08 100644 --- a/crates/viewer/re_time_panel/src/time_panel.rs +++ b/crates/viewer/re_time_panel/src/time_panel.rs @@ -34,7 +34,7 @@ use crate::time_control_ui::TimeControlUi; use crate::time_ranges_ui::{self, TimeRangesUi}; use crate::{MOVE_TIME_CURSOR_ICON, data_density_graph, paint_ticks, time_selection_ui}; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Hash)] pub struct TimePanelItem { pub entity_path: EntityPath, pub component: Option, @@ -1153,7 +1153,7 @@ impl TimePanel { egui::Tooltip::always_open( ui.ctx().clone(), ui.layer_id(), - egui::Id::new("data_tooltip"), + egui::Id::new((item, "data_tooltip")), // give each item a unique tooltip id egui::PopupAnchor::Pointer, ) .gap(12.0) From ffd168732b991ffb39ee67049a5f62681bff7e87 Mon Sep 17 00:00:00 2001 From: Daniel Duberg Date: Fri, 6 Mar 2026 13:57:44 +0100 Subject: [PATCH 058/513] Add any scalar example ### What This adds the in-repo any scalar example. https://github.com/user-attachments/assets/195f0660-0585-4e11-bd82-33bc307895bb ### Checklist - [x] I have read and agree to [Contributor Guide](https://github.com/rerun-io/rerun/blob/main/CONTRIBUTING.md) and the [Code of Conduct](https://github.com/rerun-io/rerun/blob/main/CODE_OF_CONDUCT.md) - [x] I've included a screenshot or gif (if applicable) - [x] The PR title and labels are set such as to maximize their usefulness for the next release's CHANGELOG To run all checks from `main`, comment on the PR with `@rerun-bot full-check`. --------- Source-Ref: 870dccc39d7e048527d4fc96389553fc9fba6ad4 Co-authored-by: Andreas Reich --- examples/manifest.toml | 2 + examples/python/any_scalar/README.md | 145 ++++++++++++++ .../python/any_scalar/any_scalar/__init__.py | 0 .../python/any_scalar/any_scalar/__main__.py | 46 +++++ .../any_scalar/any_scalar/market_demo.py | 160 +++++++++++++++ .../any_scalar/any_scalar/robotics_demo.py | 188 ++++++++++++++++++ examples/python/any_scalar/pyproject.toml | 12 ++ 7 files changed, 553 insertions(+) create mode 100644 examples/python/any_scalar/README.md create mode 100644 examples/python/any_scalar/any_scalar/__init__.py create mode 100755 examples/python/any_scalar/any_scalar/__main__.py create mode 100644 examples/python/any_scalar/any_scalar/market_demo.py create mode 100644 examples/python/any_scalar/any_scalar/robotics_demo.py create mode 100644 examples/python/any_scalar/pyproject.toml diff --git a/examples/manifest.toml b/examples/manifest.toml index 1fbdbca9b345..ffb4cf070eea 100644 --- a/examples/manifest.toml +++ b/examples/manifest.toml @@ -50,6 +50,7 @@ examples = [ "mcap", "eye_control", "ros_tf", + "any_scalar", ] [categories.generative-vision] @@ -168,6 +169,7 @@ examples = [ "imu_signals", "eye_control", "webpage", + "any_scalar", ] # These are examples that we explicitly exclude from our website. You can check that all examples are either included diff --git a/examples/python/any_scalar/README.md b/examples/python/any_scalar/README.md new file mode 100644 index 000000000000..9a23c6f1f197 --- /dev/null +++ b/examples/python/any_scalar/README.md @@ -0,0 +1,145 @@ + + + + +*A 6-minute narrated walkthrough of using the Rerun UI to plot arbitrary scalar data from a dataset (MCAP) is available on [Youtube](https://www.youtube.com/embed/G9Xxf0sNYcQ?si=jfb-WrY9WrFGh6mB).* + +## Overview + +This example demonstrates how to visualize arbitrary data, even when it was not logged with specific Rerun-semantics. With the **"Any Scalar"** feature, you can log complex data structures (like dictionaries or structs) once and use **Selectors** in the Blueprint to "pick" which internal fields to plot. + +**Key Benefits:** + +* **Decoupled Logging:** You no longer need to log separate scalar entities for every value you want to graph. +* **Selective Visualization:** Use a single data stream to power multiple different views by targeting specific component fields (e.g., `.position` or `.close`). + +--- + +## Run the code + +To run this example, make sure you have the [required Python version](https://ref.rerun.io/docs/python/main/common#supported-python-versions), the Rerun repository checked out and the latest SDK installed: + +```sh +pip install --upgrade rerun-sdk # install the latest Rerun SDK +git clone git@github.com:rerun-io/rerun.git # Clone the repository +cd rerun +git checkout latest # Check out the commit matching the latest SDK release +``` + +Install the necessary libraries specified in the requirements file: + +```sh +pip install -e examples/python/any_scalar +``` + +To experiment with the provided example, simply execute the main Python script: + +```sh +python -m any_scalar --demo robotics # Simulated PID control +python -m any_scalar --demo market # Real-time stock performance +``` + +If you wish to explore additional features, use the CLI with the `--help` option for guidance: +```sh +python -m any_scalar --help +``` + +--- + +## Guided demos + +### 1. Robotics: PID controller telemetry + +**Goal:** Visualize a control loop's internal state without logging separate scalars for every field. + +In `robotics_demo.py`, we simulate a joint controller. Instead of logging `error`, `effort`, and `position` as individual Rerun entities, we log a single **Telemetry struct** per time step. + +**Tutorial highlights:** + +- **Decoupled logging:** We log one `ControllerTelemetry` object. Later, in the Blueprint, we "pick" which parts to see. +- **Visualizer mapping:** Notice how the `Error` field is plotted twice: once as a **Line** (to see trends) and once as **Points** (to see individual sample timing). +- **Step interpolation:** The `Effort` signal uses `StepAfter` interpolation, which accurately reflects how a digital controller holds its output constant between steps. +- **Boolean plotting:** The `is_stable` flag is visualized as a step-function scalar (0/1). + +What you should see when running `python -m any_scalar --demo robotics`: + + + + + + + + + +### 2. Market data: relative performance + +**Goal:** Compare multiple live data streams (tickers) using a centralized selector. + +In `market_demo.py`, we fetch real stock data. We log the raw prices and a "normalized" % change field. + +**Tutorial highlights:** + +- **Dynamic normalization:** We log the price relative to the morning opening. +- **Dynamic archetype:** We log the stock data as a dictionary using `rerun.DynamicArchetype`. +- **Selectors:** We use [`jq`](https://jqlang.org/)-style selectors, like `.prices.normalized` and `.prices.close`, to power different parts of the dashboard. + +What you should see when running `python -m any_scalar --demo market`: + + + + + + + + + +### 3. Load datasets directly into the viewer + +**Goal:** Plot values from dataset files without writing any code. + +Because Rerun can now plot **Any Scalar**, you can drag an `.mcap` or `.rrd` file into the viewer and create a `Time Series` view. Use the UI in the viewer to drill into nested ROS messages or telemetry logs and start plotting immediately. + +> [!TIP] +> **Watch the video at the top of this page** to see a step-by-step walkthrough of how to use the UI to plot any field from an MCAP/RRD file. + +--- + +## The "Magic": component mapping + +### What is "Any Scalar"? + +Traditionally, to plot a graph, you had to log data specifically as one of Rerun's `Scalar` archetypes. With **Any Scalar**, you can log complex blobs (Dictionaries, TypedDicts, Arrow Structs) and Rerun will let you "map" internal fields to visualizers. + +### Selectors (jq-style) + +Rerun uses a path syntax inspired by [`jq`](https://jqlang.org/) to reach into your data: +- `.state.position` -> reaches into the `state` dict and finds `position`. +- `.prices.normalized` -> pulls the calculated performance from the market tick. + +### Benefits + +1. **Developer velocity:** Log your entire state object once; decide what to plot later in the UI. +2. **Smaller files:** Less metadata overhead than logging 50 separate entities. +3. **Flexibility:** Change what you are visualizing in the Blueprint without restarting your simulation or re-running your data pipeline. + +--- + +## Resources + +- [Customize views](https://rerun.io/docs/concepts/visualization/customize-views) +- [Plot any scalar](https://rerun.io/docs/howto/visualization/plot-any-scalar) +- [Component Mappings Guide](https://rerun.io/docs/howto/visualization/component-mappings) + +--- + +## Used Rerun types + +[`DynamicArchetype`](https://ref.rerun.io/docs/python/stable/common/custom_data/#rerun.dynamic_archetype.DynamicArchetype), [`SeriesLines`](https://rerun.io/docs/reference/types/archetypes/series_lines), [`SeriesPoints`](https://rerun.io/docs/reference/types/archetypes/series_points) + diff --git a/examples/python/any_scalar/any_scalar/__init__.py b/examples/python/any_scalar/any_scalar/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/examples/python/any_scalar/any_scalar/__main__.py b/examples/python/any_scalar/any_scalar/__main__.py new file mode 100755 index 000000000000..186a51c2dd1c --- /dev/null +++ b/examples/python/any_scalar/any_scalar/__main__.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python3 +""" +A single entry point to run the Rerun any_scalar_example demos. + +You can run either the robotics demo or the market demo: + python -m any_scalar_example --demo robotics + python -m any_scalar_example --demo market +""" + +from __future__ import annotations + +import argparse +import rerun as rr + +from any_scalar import robotics_demo, market_demo + + +def main() -> None: + parser = argparse.ArgumentParser(description="Rerun Any Scalar Example") + parser.add_argument( + "--demo", + type=str, + default="robotics", + choices=["robotics", "market"], + help="Which demo to run (default: robotics)", + ) + + # Add standard Rerun arguments + rr.script_add_args(parser) + args = parser.parse_args() + + # Initialize Rerun SDK + rr.script_setup(args, "rerun_example_any_scalar") + + if args.demo == "robotics": + robotics_demo.run_robotics_simulation() + rr.send_blueprint(robotics_demo.generate_blueprint()) + elif args.demo == "market": + market_demo.run_market_demo() + rr.send_blueprint(market_demo.generate_blueprint()) + + rr.script_teardown(args) + + +if __name__ == "__main__": + main() diff --git a/examples/python/any_scalar/any_scalar/market_demo.py b/examples/python/any_scalar/any_scalar/market_demo.py new file mode 100644 index 000000000000..c06d1d4fd54a --- /dev/null +++ b/examples/python/any_scalar/any_scalar/market_demo.py @@ -0,0 +1,160 @@ +#!/usr/bin/env python3 +""" +Market Data Comparison Demo +Shows features for "Any Scalar" visualization: +- Normalizing multiple real-time tickers (NVDA, AAPL, MSFT, GOOGL, AMD, INTC). +- Comparing relative performance using Blueprint Selectors. +""" + +from __future__ import annotations + +import rerun as rr +import rerun.blueprint as rrb +from rerun.blueprint.datatypes import ComponentSourceKind, VisualizerComponentMapping +import yfinance as yf + + +def log_market_data(tickers: list[str]) -> None: + """Fetch, normalize, and log real market data for multiple tickers.""" + print(f"Fetching market data for {tickers}…") + # Fetch 7 days (max for 1m interval) to ensure we have data even on weekends/holidays + df = yf.download(tickers, period="7d", interval="1m", progress=False) + + if df.empty: + print(f"Warning: No data found for {tickers}.") + return + + # Find the most recent date that has at least 100 data points + # This ensures we do not show a nearly empty chart if the market just opened. + daily_counts = df.groupby(df.index.date).size() + valid_dates = daily_counts[daily_counts >= 100].index + + if len(valid_dates) == 0: + print("Warning: No day found with at least 100 data points. Using the most recent day with any data.") + last_date = df.index.max().date() + else: + last_date = max(valid_dates) + + df = df[df.index.date == last_date] + + for ticker in tickers: + try: + if len(tickers) > 1: + ticker_data = df.xs(ticker, axis=1, level=1).dropna() + else: + ticker_data = df.dropna() + + if ticker_data.empty: + continue + + timestamps = ticker_data.index + # Normalization: Scale relative to the first Close price (%) + baseline = float(ticker_data.iloc[0]["Close"]) + + # Nested Any Scalar: prices.close, prices.normalized, details.volume + market_ticks = [ + [ + { + "prices": { + "close": float(row["Close"]), + "normalized": (float(row["Close"]) / baseline - 1.0) * 100.0, + }, + "details": {"volume": float(row["Volume"])}, + } + ] + for _, row in ticker_data.iterrows() + ] + + rr.send_columns( + f"market/{ticker}", + indexes=[rr.TimeColumn("market_time", timestamp=timestamps)], + columns=[*rr.DynamicArchetype.columns(archetype="MarketTelemetry", components={"data": market_ticks})], + ) + except Exception as e: + print(f"Error processing {ticker}: {e}") + + +def run_market_demo() -> None: + """Run the market data demo and log data.""" + tickers = ["NVDA", "AAPL", "MSFT", "GOOGL", "AMD", "INTC"] + log_market_data(tickers) + + +def generate_blueprint() -> rrb.Blueprint: + """Generate the blueprint for the market demo.""" + tickers = ["NVDA", "AAPL", "MSFT", "GOOGL", "AMD", "INTC"] + colors = { + "NVDA": [118, 185, 0], # NVIDIA Green + "AAPL": [85, 85, 85], # Apple Gray + "MSFT": [0, 164, 239], # Microsoft Blue + "GOOGL": [219, 68, 55], # Google Red + "AMD": [237, 28, 36], # AMD Red + "INTC": [0, 104, 181], # Intel Blue + } + return rrb.Blueprint( + rrb.Vertical( + rrb.Horizontal( + rrb.TimeSeriesView( + name="Market Relative Performance (%)", + origin="/market", + overrides={ + f"market/{ticker}": [ + rr.SeriesLines(names=f"{ticker} (Rel)", colors=colors.get(ticker)).visualizer( + mappings=[ + VisualizerComponentMapping( + target="Scalars:scalars", + source_kind=ComponentSourceKind.SourceComponent, + source_component="MarketTelemetry:data", + selector=".prices.normalized", + ) + ] + ) + ] + for ticker in tickers + }, + axis_x=rrb.TimeAxis( + view_range=rrb.TimeRange( + start=rrb.TimeRangeBoundary.infinite(), end=rrb.TimeRangeBoundary.infinite() + ) + ), + ), + rrb.TimeSeriesView( + name="Market Close", + origin="/market", + overrides={ + f"market/{ticker}": [ + rr.SeriesLines(names=f"{ticker} (Close)", colors=colors.get(ticker)).visualizer( + mappings=[ + VisualizerComponentMapping( + target="Scalars:scalars", + source_kind=ComponentSourceKind.SourceComponent, + source_component="MarketTelemetry:data", + selector=".prices.close", + ) + ] + ) + ] + for ticker in tickers + }, + axis_x=rrb.TimeAxis( + view_range=rrb.TimeRange( + start=rrb.TimeRangeBoundary.infinite(), end=rrb.TimeRangeBoundary.infinite() + ) + ), + ), + ), + rrb.DataframeView(name="Market Inspector", origin="/market"), + ) + ) + + +def main() -> None: + rr.init("rerun_example_any_scalar_market", spawn=True) + + run_market_demo() + + rr.send_blueprint(generate_blueprint()) + + +if __name__ == "__main__": + main() diff --git a/examples/python/any_scalar/any_scalar/robotics_demo.py b/examples/python/any_scalar/any_scalar/robotics_demo.py new file mode 100644 index 000000000000..e56227085006 --- /dev/null +++ b/examples/python/any_scalar/any_scalar/robotics_demo.py @@ -0,0 +1,188 @@ +#!/usr/bin/env python3 +""" +Robotics PID Controller Demo +Shows features for "Any Scalar" visualization: +- Multiple visualizers (Lines + Points) on the same telemetry field. +- Step interpolation for discrete control effort and state flags. +- Boolean scalar plotting. +""" + +from __future__ import annotations + +import rerun as rr +import rerun.blueprint as rrb +from rerun.blueprint.datatypes import ComponentSourceKind, VisualizerComponentMapping +import numpy as np + + +def simulate_robot_controller() -> None: + """Simulate a 1-DOF joint with a PID controller tracking a trajectory.""" + print("Simulating robot PID controller…") + + # PID Constants + Kp, Ki, Kd = 12.0, 1.5, 0.8 + dt = 0.05 + steps = 400 + + # State + position = 0.0 + velocity = 0.0 + integral = 0.0 + prev_error = 0.0 + + telemetry_data = [] + + for i in range(steps): + t = i * dt + + # Trajectory + setpoint = 2.0 * np.sin(1.0 * t) + 0.5 * np.cos(3.0 * t) + + # Controller + error = setpoint - position + integral += error * dt + derivative = (error - prev_error) / dt + effort = Kp * error + Ki * integral + Kd * derivative + prev_error = error + + # Physics + acceleration = effort - 0.5 * velocity + velocity += acceleration * dt + position += velocity * dt + + # Deeply nested telemetry: state, control, status + # This showcases Rerun's powerful jq-style selectors for hierarchical data. + telemetry_data.append([ + { + "state": { + "setpoint": float(setpoint), + "position": float(position), + }, + "control": { + "error": float(error), + "effort": float(effort), + }, + "status": {"is_stable": bool(abs(error) < 0.1)}, + } + ]) + + # Log telemetry using send_columns + rr.send_columns( + "robot/joint_0", + indexes=[rr.TimeColumn("robot_step", sequence=np.arange(steps))], + columns=[*rr.DynamicArchetype.columns(archetype="ControllerTelemetry", components={"data": telemetry_data})], + ) + + +def run_robotics_simulation() -> None: + """Run the robotics simulation and log data.""" + simulate_robot_controller() + + +def generate_blueprint() -> rrb.Blueprint: + """Generate the blueprint for the robotics demo.""" + return rrb.Blueprint( + rrb.Vertical( + # View 1: Main Control Performance (Multiple Visualizers + Nested Selectors) + rrb.TimeSeriesView( + name="Control Performance", + origin="/robot/joint_0", + overrides={ + "robot/joint_0": [ + rr.SeriesLines(names="Setpoint", colors=[100, 100, 255]).visualizer( + mappings=[ + VisualizerComponentMapping( + target="Scalars:scalars", + source_kind=ComponentSourceKind.SourceComponent, + source_component="ControllerTelemetry:data", + selector=".state.setpoint", + ) + ] + ), + rr.SeriesLines(names="Position", colors=[255, 100, 0]).visualizer( + mappings=[ + VisualizerComponentMapping( + target="Scalars:scalars", + source_kind=ComponentSourceKind.SourceComponent, + source_component="ControllerTelemetry:data", + selector=".state.position", + ) + ] + ), + # Multiple visualizers on the SAME field (.control.error) + rr.SeriesLines(names="Error (Line)", colors=[255, 0, 0]).visualizer( + mappings=[ + VisualizerComponentMapping( + target="Scalars:scalars", + source_kind=ComponentSourceKind.SourceComponent, + source_component="ControllerTelemetry:data", + selector=".control.error", + ) + ] + ), + rr.SeriesPoints(names="Error (Dots)", colors=[255, 0, 0]).visualizer( + mappings=[ + VisualizerComponentMapping( + target="Scalars:scalars", + source_kind=ComponentSourceKind.SourceComponent, + source_component="ControllerTelemetry:data", + selector=".control.error", + ) + ] + ), + ] + }, + ), + # View 2: Internals (Step Interpolation & Nested Booleans) + rrb.TimeSeriesView( + name="Controller Internals", + origin="/robot/joint_0", + overrides={ + "robot/joint_0": [ + rr.SeriesLines(names="Effort", colors=[0, 200, 200], interpolation_mode="StepAfter").visualizer( + mappings=[ + VisualizerComponentMapping( + target="Scalars:scalars", + source_kind=ComponentSourceKind.SourceComponent, + source_component="ControllerTelemetry:data", + selector=".control.effort", + ) + ] + ), + rr.SeriesLines( + names="Stability Flag", colors=[0, 255, 0], interpolation_mode="StepAfter" + ).visualizer( + mappings=[ + VisualizerComponentMapping( + target="Scalars:scalars", + source_kind=ComponentSourceKind.SourceComponent, + source_component="ControllerTelemetry:data", + selector=".status.is_stable", + ) + ] + ), + ] + }, + ), + # View 3: Dataframe Inspector + rrb.DataframeView( + name="Telemetry Inspector", + origin="/robot/joint_0", + query=rrb.archetypes.DataframeQuery( + auto_scroll=True, + ), + ), + ) + ) + + +def main() -> None: + rr.init("rerun_example_any_scalar_robotics", spawn=True) + + run_robotics_simulation() + + rr.send_blueprint(generate_blueprint()) + + +if __name__ == "__main__": + main() diff --git a/examples/python/any_scalar/pyproject.toml b/examples/python/any_scalar/pyproject.toml new file mode 100644 index 000000000000..942cd78f8b40 --- /dev/null +++ b/examples/python/any_scalar/pyproject.toml @@ -0,0 +1,12 @@ +[project] +name = "any_scalar" +version = "0.1.0" +readme = "README.md" +dependencies = ["psutil", "pyarrow", "rerun-sdk", "yfinance"] + +[project.scripts] +any_scalar = "any_scalar.__main__:main" + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" From d8bd248376bd7c1440071b5210c2f8a63f566877 Mon Sep 17 00:00:00 2001 From: Isse Date: Fri, 6 Mar 2026 14:26:40 +0100 Subject: [PATCH 059/513] Show tooltip even when hovering play head in the time series view ### Related - Closes RR-1458 - Closes RR-2829 ### What Instead of allocating a response in egui, this handles interactions manually so that the plot is the one that is hovered. https://github.com/user-attachments/assets/c1609e35-2d53-4669-a59f-f0611b85c90b Source-Ref: 09bda74584d15935f26ec970a67e971a2d1eeed8 --- crates/viewer/re_ui/src/ui_ext.rs | 21 +++++++--- .../re_view_time_series/src/view_class.rs | 42 ++++++++++++------- 2 files changed, 42 insertions(+), 21 deletions(-) diff --git a/crates/viewer/re_ui/src/ui_ext.rs b/crates/viewer/re_ui/src/ui_ext.rs index 15b747cb556a..ed3b38c99e39 100644 --- a/crates/viewer/re_ui/src/ui_ext.rs +++ b/crates/viewer/re_ui/src/ui_ext.rs @@ -952,21 +952,30 @@ pub trait UiExt { x: f32, y: Rangef, ) { - let ui = self.ui(); - let stroke = if let Some(response) = response { - ui.visuals().widgets.style(response).fg_stroke + let style = if let Some(response) = response { + self.ui().visuals().widgets.style(response) } else { - ui.visuals().widgets.inactive.fg_stroke + &self.ui().visuals().widgets.inactive }; + self.paint_time_cursor_with_style(painter, style, x, y); + } + /// Like [`Self::paint_time_cursor`], but with an explicit widget style. + fn paint_time_cursor_with_style( + &self, + painter: &egui::Painter, + style: &egui::style::WidgetVisuals, + x: f32, + y: Rangef, + ) { let Rangef { min: y_min, max: y_max, } = y; let stroke = egui::Stroke { - width: 1.5 * stroke.width, - color: stroke.color, + width: 1.5 * style.fg_stroke.width, + color: style.fg_stroke.color, }; let w = 10.0; diff --git a/crates/viewer/re_view_time_series/src/view_class.rs b/crates/viewer/re_view_time_series/src/view_class.rs index 116e87a17468..e75ba9956b5d 100644 --- a/crates/viewer/re_view_time_series/src/view_class.rs +++ b/crates/viewer/re_view_time_series/src/view_class.rs @@ -779,7 +779,7 @@ impl ViewClass for TimeSeriesView { .map(|x| transform.position_from_point(&PlotPoint::new(x, 0.0)).x); if let Some(time_x) = time_x { - draw_time_cursor(ctx, ui, &response, &transform, time_offset, time_x); + paint_time_cursor(ctx, ui, &response, &transform, time_offset, time_x); } // Can determine whether we're resetting only now since we need to know whether there's a plot item hovered. @@ -1074,26 +1074,35 @@ fn all_scalar_mappings( ) } -fn draw_time_cursor( +fn paint_time_cursor( ctx: &ViewerContext<'_>, ui: &egui::Ui, response: &egui::Response, transform: &egui_plot::PlotTransform, time_offset: i64, mut time_x: f32, -) -> egui::Response { +) { let interact_radius = ui.style().interaction.resize_grab_radius_side; let line_rect = egui::Rect::from_x_y_ranges(time_x..=time_x, response.rect.y_range()) .expand(interact_radius); let time_drag_id = ui.id().with("time_drag"); - let time_cursor_response = ui - .interact(line_rect, time_drag_id, egui::Sense::drag()) - .on_hover_and_drag_cursor(egui::CursorIcon::ResizeHorizontal); + let pointer_pos = ui.input(|i| i.pointer.hover_pos()); + let is_near = ui.rect_contains_pointer(line_rect); + let is_being_dragged = ui.is_being_dragged(time_drag_id); + + if is_near || is_being_dragged { + ui.ctx().set_cursor_icon(egui::CursorIcon::ResizeHorizontal); + } - if time_cursor_response.dragged() - && let Some(pointer_pos) = ui.input(|i| i.pointer.hover_pos()) + if is_near + && !is_being_dragged + && ui.input(|i| i.pointer.button_pressed(egui::PointerButton::Primary)) { + ui.set_dragged_id(time_drag_id); + } + + if is_being_dragged && let Some(pointer_pos) = pointer_pos { let aim_radius = ui.input(|i| i.aim_radius()); let new_offset_time = egui::emath::smart_aim::best_in_range_f64( transform @@ -1114,13 +1123,16 @@ fn draw_time_cursor( ]); } - ui.paint_time_cursor( - ui.painter(), - Some(&time_cursor_response), - time_x, - time_cursor_response.rect.y_range(), - ); - time_cursor_response + let highlighted = is_near || is_being_dragged; + let style = if is_being_dragged { + &ui.visuals().widgets.active + } else if highlighted { + &ui.visuals().widgets.hovered + } else { + &ui.visuals().widgets.inactive + }; + + ui.paint_time_cursor_with_style(ui.painter(), style, time_x, response.rect.y_range()); } fn reset_view(ctx: &ViewerContext<'_>, time_axis: &ViewProperty, scalar_axis: &ViewProperty) { From 09814c0a83423f9efd3913c9314171b8fc8e309c Mon Sep 17 00:00:00 2001 From: Isaac Blankenau <119615777+iblnkn@users.noreply.github.com> Date: Fri, 6 Mar 2026 08:33:49 -0600 Subject: [PATCH 060/513] Adding snippet showing how to register a dataset as a subset of an existing dataset Snippet example for creating a new sub-dataset. This addresses RR-3646. In order to get this working with local OSS server, in-memory chunk stores had to be "decoupled from dataset/segment/layer object hierarchy". See https://github.com/rerun-io/reality/pull/849. Source-Ref: 1ffb5d83b7c47c28cbc3379d19c55a9deac9d0a9 --- .../howto/query-and-transform/sub_dataset.md | 43 ++++++++++ docs/snippets/INDEX.md | 1 + docs/snippets/all/howto/sub_dataset.py | 79 +++++++++++++++++++ docs/snippets/snippets.toml | 11 +++ 4 files changed, 134 insertions(+) create mode 100644 docs/content/howto/query-and-transform/sub_dataset.md create mode 100644 docs/snippets/all/howto/sub_dataset.py diff --git a/docs/content/howto/query-and-transform/sub_dataset.md b/docs/content/howto/query-and-transform/sub_dataset.md new file mode 100644 index 000000000000..b3ce28e07119 --- /dev/null +++ b/docs/content/howto/query-and-transform/sub_dataset.md @@ -0,0 +1,43 @@ +--- +title: Creating sub-datasets +order: 115 +--- + +When experimenting with new features it's often practical to work with a subset of data without modifying the original. +A sub-dataset references the same underlying RRD files so no data is copied. + +The dependencies in this example are contained in `rerun-sdk[all]`. + +## Setup + +Simplified setup to launch the local server for demonstration. +In practice you'll connect to your cloud instance. + +snippet: howto/sub_dataset[setup] + +## Helper function + +Query the source dataset's [manifest](../../concepts/query-and-transform/catalog-object-model.md) for storage URLs per (segment, layer) pair and re-register them into a new dataset. + +snippet: howto/sub_dataset[create_sub_dataset] + +## Selecting segments + +Select segments by any criteria — a hardcoded list, a slice, or a filtered query based on segment properties or metadata joins. + +snippet: howto/sub_dataset[select_segments] + +## Creating the sub-dataset + +snippet: howto/sub_dataset[create] + +## Verifying the result + +snippet: howto/sub_dataset[verify] + +## Cleanup + +Delete the sub-dataset when it is no longer needed. +This only removes the dataset entry from the catalog. The underlying RRD storage is not affected. + +snippet: howto/sub_dataset[cleanup] diff --git a/docs/snippets/INDEX.md b/docs/snippets/INDEX.md index c2702a8b413e..dcaaa514f958 100644 --- a/docs/snippets/INDEX.md +++ b/docs/snippets/INDEX.md @@ -27,6 +27,7 @@ Use it to quickly find copy-pastable snippets of code for any Rerun feature you' | **Query Data Platform** | `query_images` | Query various image representations | [🐍](https://github.com/rerun-io/rerun/blob/main/docs/snippets/all/howto/query_images.py) | | | | **Query Data Platform** | `query_videos` | Query video streams | [🐍](https://github.com/rerun-io/rerun/blob/main/docs/snippets/all/howto/query_videos.py) | | | | **Query Data Platform** | `query_video_keyframes` | Query video streams efficiently using keyframe information | [🐍](https://github.com/rerun-io/rerun/blob/main/docs/snippets/all/howto/query_video_keyframes.py) | | | +| **Query Data Platform** | `sub_dataset` | Create a new dataset from a subset of segments of an existing dataset | [🐍](https://github.com/rerun-io/rerun/blob/main/docs/snippets/all/howto/sub_dataset.py) | | | | **Query Data Platform** | `time_alignment` | Efficiently time align multirate columns | [🐍](https://github.com/rerun-io/rerun/blob/main/docs/snippets/all/howto/time_alignment.py) | | | | **Query Data Platform** | `view_operations` | Leverage filters to more efficiently perform downstream queries | [🐍](https://github.com/rerun-io/rerun/blob/main/docs/snippets/all/howto/view_operations.py) | | | | **Setting recording properties** | `recording_properties` | Sets the recording properties | [🐍](https://github.com/rerun-io/rerun/blob/main/docs/snippets/all/concepts/recording_properties.py) | [🦀](https://github.com/rerun-io/rerun/blob/main/docs/snippets/all/concepts/recording_properties.rs) | [🌊](https://github.com/rerun-io/rerun/blob/main/docs/snippets/all/concepts/recording_properties.cpp) | diff --git a/docs/snippets/all/howto/sub_dataset.py b/docs/snippets/all/howto/sub_dataset.py new file mode 100644 index 000000000000..9bd22e98a927 --- /dev/null +++ b/docs/snippets/all/howto/sub_dataset.py @@ -0,0 +1,79 @@ +"""Create a new dataset from a subset of segments of an existing dataset.""" + +# region: setup +from __future__ import annotations + +from pathlib import Path + +import pyarrow as pa +import rerun as rr +from datafusion import col, functions as F, lit + +sample_5_path = Path(__file__).parents[4] / "tests" / "assets" / "rrd" / "sample_5" + +server = rr.server.Server(datasets={"sample_dataset": sample_5_path}) +CATALOG_URL = server.url() +client = rr.catalog.CatalogClient(CATALOG_URL) +source_dataset = client.get_dataset(name="sample_dataset") +# endregion: setup + + +# region: create_sub_dataset +def create_sub_dataset( + client: rr.catalog.CatalogClient, + source: rr.catalog.DatasetEntry, + name: str, + segment_ids: list[str], +) -> rr.catalog.DatasetEntry: + """Create a new dataset containing a subset of segments from an existing dataset.""" + + # Query the manifest for storage URLs of the selected segments + manifest = pa.table( + source.manifest() + .filter(F.in_list(col("rerun_segment_id"), [lit(s) for s in segment_ids])) + .select("rerun_storage_url", "rerun_layer_name") + ) + + sub_dataset = client.create_dataset(name) + + if manifest.num_rows > 0: + uris = manifest.column("rerun_storage_url").to_pylist() + layers = manifest.column("rerun_layer_name").to_pylist() + sub_dataset.register(uris, layer_name=layers).wait() + + return sub_dataset + + +# endregion: create_sub_dataset + +# region: select_segments +# View available segments +print("Available segments:") +print(source_dataset.segment_table().select("rerun_segment_id").sort("rerun_segment_id")) + +# Select a subset — here we pick the first 3 segments. +all_segment_ids = source_dataset.segment_ids() +subset_ids = all_segment_ids[:3] +# endregion: select_segments + +# region: create +sub_dataset = create_sub_dataset(client, source_dataset, "my_experiment", subset_ids) +# endregion: create + +# region: verify +print("\nSub-dataset segments:") +print(sub_dataset.segment_table().select("rerun_segment_id", "rerun_layer_names").sort("rerun_segment_id")) + +print("\nSub-dataset manifest:") +print( + sub_dataset.manifest() + .select("rerun_segment_id", "rerun_layer_name", "rerun_storage_url") + .sort("rerun_segment_id", "rerun_layer_name") +) +# endregion: verify + +# region: cleanup +# When done experimenting, delete the sub-dataset. +# This only removes the dataset entry — the underlying RRD storage is not affected. +sub_dataset.delete() +# endregion: cleanup diff --git a/docs/snippets/snippets.toml b/docs/snippets/snippets.toml index e52642364540..fcf736f3a343 100644 --- a/docs/snippets/snippets.toml +++ b/docs/snippets/snippets.toml @@ -30,6 +30,7 @@ features = [ "howto/query_images", "howto/query_videos", "howto/query_video_keyframes", + "howto/sub_dataset", "howto/time_alignment", "howto/view_operations", ], @@ -138,6 +139,7 @@ backwards_check = [ "howto/query_images", "howto/query_videos", "howto/query_video_keyframes", + "howto/sub_dataset", "howto/time_alignment", "howto/query-and-transform/segment_url", "howto/view_operations", @@ -260,6 +262,10 @@ backwards_check = [ "cpp", # Not implemented "rust", # Not implemented ] +"howto/sub_dataset" = [ + "cpp", # Not implemented + "rust", # Not implemented +] "howto/lerobot_export" = [ "py", # lerobot is not part of our environment as it causes dependency issues "cpp", # Not implemented @@ -536,6 +542,11 @@ quick_start = [ # These examples don't have exactly the same implementation. "py", "rust", ] +"howto/sub_dataset" = [ # Cloud examples don't generate rrds + "cpp", + "py", + "rust", +] "howto/lerobot_export" = [ # Cloud examples don't generate rrds "cpp", "py", From 9c547ce5d1f70815f77c0bab084cfa69dcfeb956 Mon Sep 17 00:00:00 2001 From: Andreas Reich Date: Fri, 6 Mar 2026 15:58:04 +0100 Subject: [PATCH 061/513] Fix showing empty label boxes for various 2d & 3d visualizations Previously, when setting a label to empty string, we'd show an empty label box like so: image This is one of the many places where fallbacks (aka implicit defaults) have behavior inconsistency accross ui and visualizers: the ui says "empty string" but the visualizer will actually not invoke the fallback, thus ending up with empty array. These inconsistencies are in the process of being fixed by enforcing fallback evaluation at a lower level, but this makes the issue problematic in the first place since suddenly I got empty string and thus empty text boxes everywhere. I figured instead I we could keep the empty label boxes on string and fix the fallback to be empty array, but I figure it's just needlessly complex: I don't really want to distinguish empty array from empty string in the ui for labels right now, so this fix is drastically simpler and I don't see a _real_ drawback with it. Source-Ref: d648f84c2f2e450d47f527edca78e4ce7b044e86 --- .../viewer/re_view_spatial/src/visualizers/utilities/labels.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/viewer/re_view_spatial/src/visualizers/utilities/labels.rs b/crates/viewer/re_view_spatial/src/visualizers/utilities/labels.rs index 391aad14fbd1..0b4dd913cecc 100644 --- a/crates/viewer/re_view_spatial/src/visualizers/utilities/labels.rs +++ b/crates/viewer/re_view_spatial/src/visualizers/utilities/labels.rs @@ -167,7 +167,8 @@ pub fn process_labels<'a, P: 'a>( itertools::izip!(label_positions, labels, colors) .enumerate() .filter_map(move |(i, (position, label, color))| { - label.map(|label| UiLabel { + let label = label?; + (!label.is_empty()).then(|| UiLabel { text: label, style: if *color == Color32::PLACEHOLDER { UiLabelStyle::Default From 76bd562982cc15572c743cc818dfe07836dd2b9a Mon Sep 17 00:00:00 2001 From: Andreas Reich Date: Fri, 6 Mar 2026 16:33:32 +0100 Subject: [PATCH 062/513] Enable WebGPU rendering on Safari (MacOS Tahoe+ only) ### Related * Fixes https://github.com/rerun-io/rerun/issues/10609 ### What We previously kept WebGPU on MacOS disabled since until recently, the experimental WebGPU support on Sequoia Safari would cause our viewer to crash (because of bugs on the WebGPU implementation). Sequoia now no longer offers the experimental WebGPU flag - this _does_ require an update on the user's end, but it has been out for a while now. * [x] test that Sequoia Safari picks WebGL and it runs fine * [x] test that Tahoe Safari picks WebGPU and it runs fine Source-Ref: b96ec5b8fb969cba55f9700b985c95ad08bc89bf --- crates/viewer/re_renderer/src/device_caps.rs | 20 +------------------- 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/crates/viewer/re_renderer/src/device_caps.rs b/crates/viewer/re_renderer/src/device_caps.rs index 837665e9673b..ed4f23010ac5 100644 --- a/crates/viewer/re_renderer/src/device_caps.rs +++ b/crates/viewer/re_renderer/src/device_caps.rs @@ -541,8 +541,7 @@ pub fn default_backends() -> wgpu::Backends { // For changing the backend we use standard wgpu env var, i.e. WGPU_BACKEND. wgpu::Backends::from_env() .unwrap_or(wgpu::Backends::VULKAN | wgpu::Backends::METAL | wgpu::Backends::GL) - } else if is_safari_browser() || is_firefox_browser() { - // TODO(#10609): Fix WebGPU on Safari + } else if is_firefox_browser() { // TODO(#11009): Fix videos on WebGPU firefox wgpu::Backends::GL } else { @@ -619,23 +618,6 @@ pub fn validate_graphics_backend_applicability(backend: wgpu::Backend) -> Result Ok(()) } -/// Are we running inside the Safari browser? -pub fn is_safari_browser() -> bool { - #[cfg(target_arch = "wasm32")] - fn is_safari_browser_inner() -> Option { - use web_sys::wasm_bindgen::JsValue; - let window = web_sys::window()?; - Some(window.has_own_property(&JsValue::from("safari"))) - } - - #[cfg(not(target_arch = "wasm32"))] - fn is_safari_browser_inner() -> Option { - None - } - - is_safari_browser_inner().unwrap_or(false) -} - /// Are we running inside the Firefox browser? pub fn is_firefox_browser() -> bool { #[cfg(target_arch = "wasm32")] From ad8be22d0231d615f9fd42570cd2924bb3ef3cfa Mon Sep 17 00:00:00 2001 From: Antoine Beyeler <49431240+abey79@users.noreply.github.com> Date: Fri, 6 Mar 2026 17:14:14 +0100 Subject: [PATCH 063/513] Remove type hints from all our docstrings ### What Titie. These docstring are redundant, unused, and bound to rot. Also, we've been inconsistent with that for ages, leading LLMs to perpetuate this inconsistency. Source-Ref: 4487eaf610e1b248d256c2311443d05b15b63c1d --- rerun_py/rerun_bindings/rerun_bindings.pyi | 8 +++--- rerun_py/rerun_sdk/rerun/__init__.py | 12 ++++----- rerun_py/rerun_sdk/rerun/_baseclasses.py | 10 +++---- rerun_py/rerun_sdk/rerun/_legacy_notebook.py | 20 +++++++------- rerun_py/rerun_sdk/rerun/_properties.py | 4 +-- rerun_py/rerun_sdk/rerun/_script_helpers.py | 12 ++++----- rerun_py/rerun_sdk/rerun/catalog/_entry.py | 10 +++---- rerun_py/rerun_sdk/rerun/catalog/_schema.py | 8 +++--- .../rerun/experimental/_viewer_client.py | 2 +- rerun_py/rerun_sdk/rerun/notebook.py | 10 +++---- rerun_py/rerun_sdk/rerun/recording_stream.py | 26 +++++++++---------- rerun_py/rerun_sdk/rerun/time.py | 4 +-- 12 files changed, 63 insertions(+), 63 deletions(-) diff --git a/rerun_py/rerun_bindings/rerun_bindings.pyi b/rerun_py/rerun_bindings/rerun_bindings.pyi index 6f0828ab8ebd..5b7a3ed9519c 100644 --- a/rerun_py/rerun_bindings/rerun_bindings.pyi +++ b/rerun_py/rerun_bindings/rerun_bindings.pyi @@ -288,19 +288,19 @@ class ChunkBatcherConfig: Parameters ---------- - flush_tick : int | float | timedelta | None + flush_tick: Duration of the periodic tick, by default `None`. Equivalent to setting: `RERUN_FLUSH_TICK_SECS` environment variable. - flush_num_bytes : int | None + flush_num_bytes: Flush if the accumulated payload has a size in bytes equal or greater than this, by default `None`. Equivalent to setting: `RERUN_FLUSH_NUM_BYTES` environment variable. - flush_num_rows : int | None + flush_num_rows: Flush if the accumulated payload has a number of rows equal or greater than this, by default `None`. Equivalent to setting: `RERUN_FLUSH_NUM_ROWS` environment variable. - chunk_max_rows_if_unsorted : int | None + chunk_max_rows_if_unsorted: Split a chunk if it contains >= rows than this threshold and one or more of its timelines are unsorted, by default `None`. Equivalent to setting: `RERUN_CHUNK_MAX_ROWS_IF_UNSORTED` environment variable. diff --git a/rerun_py/rerun_sdk/rerun/__init__.py b/rerun_py/rerun_sdk/rerun/__init__.py index 9b88d2b22fac..d99df8949b54 100644 --- a/rerun_py/rerun_sdk/rerun/__init__.py +++ b/rerun_py/rerun_sdk/rerun/__init__.py @@ -282,7 +282,7 @@ def init( Parameters ---------- - application_id : str + application_id: Your Rerun recordings will be categorized by this application id, so try to pick a unique one for each application that uses the Rerun SDK. @@ -294,7 +294,7 @@ def init( and will be treated specially by the Rerun Viewer. In particular, it will opt-in to more analytics, and will also seed the global random number generator deterministically. - recording_id : Optional[str] + recording_id: Set the recording ID that this process is logging to, as a UUIDv4. The default recording_id is based on `multiprocessing.current_process().authkey` @@ -305,7 +305,7 @@ def init( processes to log to the same Rerun instance (and be part of the same recording), you will need to manually assign them all the same recording_id. Any random UUIDv4 will work, or copy the recording id for the parent process. - spawn : bool + spawn: Spawn a Rerun Viewer and stream logging data to it. Short for calling `spawn` separately. If you don't call this, log events will be buffered indefinitely until @@ -480,11 +480,11 @@ def notebook_show( Parameters ---------- - width : int + width: The width of the viewer in pixels. - height : int + height: The height of the viewer in pixels. - blueprint : BlueprintLike + blueprint: A blueprint object to send to the viewer. It will be made active and set as the default blueprint in the recording. diff --git a/rerun_py/rerun_sdk/rerun/_baseclasses.py b/rerun_py/rerun_sdk/rerun/_baseclasses.py index 82a409cfaed9..51ebaace1333 100644 --- a/rerun_py/rerun_sdk/rerun/_baseclasses.py +++ b/rerun_py/rerun_sdk/rerun/_baseclasses.py @@ -175,9 +175,9 @@ def __init__(self, data: T | None, strict: bool | None = None) -> None: Parameters ---------- - data : T | None + data: The data to convert into an Arrow array. - strict : bool | None + strict: Whether to raise an exception if the data cannot be converted into an Arrow array. If None, the value defaults to the value of the `rerun.strict` global setting. @@ -208,7 +208,7 @@ def _converter(cls, data: T | None) -> BaseBatch[T] | None: Parameters ---------- - data : T | None + data: The data to convert into an Arrow array. Returns @@ -249,9 +249,9 @@ def _native_to_pa_array(data: T, data_type: pa.DataType) -> pa.Array: Parameters ---------- - data : T + data: The data to convert into an Arrow array. - data_type : pa.DataType + data_type: The Arrow data type of the data. Returns diff --git a/rerun_py/rerun_sdk/rerun/_legacy_notebook.py b/rerun_py/rerun_sdk/rerun/_legacy_notebook.py index 40b7f495e8f8..506009c61ce6 100644 --- a/rerun_py/rerun_sdk/rerun/_legacy_notebook.py +++ b/rerun_py/rerun_sdk/rerun/_legacy_notebook.py @@ -77,16 +77,16 @@ def as_html( Parameters ---------- - width : int + width: The width of the viewer in pixels. - height : int + height: The height of the viewer in pixels. - app_url : str + app_url: Alternative HTTP url to find the Rerun web viewer. This will default to using `https://app.rerun.io` or localhost if [rerun.start_web_viewer_server][] has been called. - timeout_ms : int + timeout_ms: The number of milliseconds to wait for the Rerun web viewer to load. - blueprint : BlueprintLike + blueprint: The blueprint to display in the viewer. recording: Specifies the [`rerun.RecordingStream`][] to use. @@ -149,16 +149,16 @@ def legacy_notebook_show( Parameters ---------- - width : int + width: The width of the viewer in pixels. - height : int + height: The height of the viewer in pixels. - app_url : str + app_url: Alternative HTTP url to find the Rerun web viewer. This will default to using `https://app.rerun.io` or localhost if [rerun.start_web_viewer_server][] has been called. - timeout_ms : int + timeout_ms: The number of milliseconds to wait for the Rerun web viewer to load. - blueprint : BlueprintLike + blueprint: The blueprint to display in the viewer. recording: Specifies the [`rerun.RecordingStream`][] to use. diff --git a/rerun_py/rerun_sdk/rerun/_properties.py b/rerun_py/rerun_sdk/rerun/_properties.py index bd46fae312d6..a88d2b8a720f 100644 --- a/rerun_py/rerun_sdk/rerun/_properties.py +++ b/rerun_py/rerun_sdk/rerun/_properties.py @@ -52,7 +52,7 @@ def send_recording_name(name: str, recording: RecordingStream | None = None) -> Parameters ---------- - name : str + name: The name of the recording. recording: @@ -73,7 +73,7 @@ def send_recording_start_time_nanos(nanos: int, recording: RecordingStream | Non Parameters ---------- - nanos : int + nanos: The start time of the recording in nanoseconds since UNIX epoch. recording: diff --git a/rerun_py/rerun_sdk/rerun/_script_helpers.py b/rerun_py/rerun_sdk/rerun/_script_helpers.py index 73f42832e474..3b781c14480a 100644 --- a/rerun_py/rerun_sdk/rerun/_script_helpers.py +++ b/rerun_py/rerun_sdk/rerun/_script_helpers.py @@ -38,7 +38,7 @@ def script_add_args(parser: ArgumentParser) -> None: Parameters ---------- - parser : ArgumentParser + parser: The parser to add arguments to. """ @@ -77,11 +77,11 @@ def script_setup( Parameters ---------- - args : Namespace + args: The parsed arguments from `parser.parse_args()`. - application_id : str + application_id: The application ID to use for the viewer. - recording_id : Optional[str] + recording_id: Set the recording ID that this process is logging to, as a UUIDv4. The default recording_id is based on `multiprocessing.current_process().authkey` @@ -92,7 +92,7 @@ def script_setup( processes to log to the same Rerun instance (and be part of the same recording), you will need to manually assign them all the same recording_id. Any random UUIDv4 will work, or copy the recording id for the parent process. - default_blueprint + default_blueprint: Optionally set a default blueprint to use for this application. If the application already has an active blueprint, the new blueprint won't become active until the user clicks the "reset blueprint" button. If you want to activate the new blueprint @@ -131,7 +131,7 @@ def script_teardown(args: Namespace) -> None: Parameters ---------- - args : Namespace + args: The parsed arguments from `parser.parse_args()`. """ diff --git a/rerun_py/rerun_sdk/rerun/catalog/_entry.py b/rerun_py/rerun_sdk/rerun/catalog/_entry.py index afc21cc34da2..530b1aaee1fd 100644 --- a/rerun_py/rerun_sdk/rerun/catalog/_entry.py +++ b/rerun_py/rerun_sdk/rerun/catalog/_entry.py @@ -128,7 +128,7 @@ def set_name(self, name: str) -> None: Parameters ---------- - name : str + name: New name for the entry """ @@ -144,7 +144,7 @@ def update(self, *, name: str | None = None) -> None: Parameters ---------- - name : str | None + name: New name for the entry """ @@ -565,7 +565,7 @@ def filter_contents(self, exprs: str | Sequence[str]) -> DatasetView: Parameters ---------- - exprs : str | Sequence[str] + exprs: Entity path expression or list of entity path expressions. Passing `[]` results in filtering out all contents. @@ -808,7 +808,7 @@ def __init__(self, internal: DatasetViewInternal) -> None: Parameters ---------- - internal : DatasetViewInternal + internal: The internal Rust-side DatasetView object. """ @@ -1050,7 +1050,7 @@ def filter_contents(self, exprs: str | Sequence[str]) -> DatasetView: Parameters ---------- - exprs : str | Sequence[str] + exprs: Entity path expression or list of entity path expressions. Passing `[]` results in filtering out all contents. diff --git a/rerun_py/rerun_sdk/rerun/catalog/_schema.py b/rerun_py/rerun_sdk/rerun/catalog/_schema.py index 667d0d3be959..a0fd20a551f3 100644 --- a/rerun_py/rerun_sdk/rerun/catalog/_schema.py +++ b/rerun_py/rerun_sdk/rerun/catalog/_schema.py @@ -28,7 +28,7 @@ def __init__(self, inner: SchemaInternal) -> None: Parameters ---------- - inner : SchemaInternal + inner: The internal schema object from the bindings. """ @@ -109,9 +109,9 @@ def column_for(self, entity_path: str, component: str) -> ComponentColumnDescrip Parameters ---------- - entity_path : str + entity_path: The entity path to look up. - component : str + component: The component to look up. Example: `Points3D:positions`. Returns @@ -130,7 +130,7 @@ def column_for_selector( Parameters ---------- - selector : str | ComponentColumnSelector | ComponentColumnDescriptor + selector: The selector to look up. String arguments are expected to follow the format: `":"` diff --git a/rerun_py/rerun_sdk/rerun/experimental/_viewer_client.py b/rerun_py/rerun_sdk/rerun/experimental/_viewer_client.py index a7de13401a9f..bcc89db52ccd 100644 --- a/rerun_py/rerun_sdk/rerun/experimental/_viewer_client.py +++ b/rerun_py/rerun_sdk/rerun/experimental/_viewer_client.py @@ -27,7 +27,7 @@ def __init__(self, addr: str = "127.0.0.1:9876") -> None: Parameters ---------- - addr : str + addr: The address of the viewer to connect to, in the format "host:port". Defaults to "127.0.0.1:9876" for a local viewer. diff --git a/rerun_py/rerun_sdk/rerun/notebook.py b/rerun_py/rerun_sdk/rerun/notebook.py index 0fb5667296d2..97586b91e2cd 100644 --- a/rerun_py/rerun_sdk/rerun/notebook.py +++ b/rerun_py/rerun_sdk/rerun/notebook.py @@ -60,9 +60,9 @@ def set_default_size(*, width: int | None, height: int | None) -> None: Parameters ---------- - width : int + width: The width of the viewer in pixels. - height : int + height: The height of the viewer in pixels. """ @@ -207,11 +207,11 @@ def add_recording( Parameters ---------- - recording : RecordingStream + recording: Specifies the [`rerun.RecordingStream`][] to use. If left unspecified, defaults to the current active data recording, if there is one. See also: [`rerun.init`][], [`rerun.set_global_data_recording`][]. - blueprint : BlueprintLike + blueprint: A blueprint object to send to the viewer. It will be made active and set as the default blueprint in the recording. @@ -314,7 +314,7 @@ def display(self, block_until_ready: bool = False) -> None: Parameters ---------- - block_until_ready : bool + block_until_ready: Whether to block until the viewer is ready to receive data. If this is `False`, the viewer will still be displayed, but logged data will likely be queued until the viewer becomes ready at the end of cell execution. diff --git a/rerun_py/rerun_sdk/rerun/recording_stream.py b/rerun_py/rerun_sdk/rerun/recording_stream.py index e6e5d7214c0b..eb928f02d09d 100644 --- a/rerun_py/rerun_sdk/rerun/recording_stream.py +++ b/rerun_py/rerun_sdk/rerun/recording_stream.py @@ -259,14 +259,14 @@ def __init__( Parameters ---------- - application_id : str + application_id: Your Rerun recordings will be categorized by this application id, so try to pick a unique one for each application that uses the Rerun SDK. For example, if you have one application doing object detection and another doing camera calibration, you could have `rerun.init("object_detector")` and `rerun.init("calibrator")`. - recording_id : Optional[str] + recording_id: Set the recording ID that this process is logging to, as a UUIDv4. The default recording_id is based on `multiprocessing.current_process().authkey` @@ -277,13 +277,13 @@ def __init__( processes to log to the same Rerun instance (and be part of the same recording), you will need to manually assign them all the same recording_id. Any random UUIDv4 will work, or copy the recording id for the parent process. - make_default : bool + make_default: If true (_not_ the default), the newly initialized recording will replace the current active one (if any) in the global scope. - make_thread_default : bool + make_thread_default: If true (_not_ the default), the newly initialized recording will replace the current active one (if any) in the thread-local scope. - default_enabled : bool + default_enabled: Should Rerun logging be on by default? Can be overridden with the RERUN env-var, e.g. `RERUN=on` or `RERUN=off`. send_properties @@ -763,11 +763,11 @@ def notebook_show( Parameters ---------- - width : int + width: The width of the viewer in pixels. - height : int + height: The height of the viewer in pixels. - blueprint : BlueprintLike + blueprint: A blueprint object to send to the viewer. It will be made active and set as the default blueprint in the recording. @@ -821,7 +821,7 @@ def send_recording_name(self, name: str) -> None: Parameters ---------- - name : str + name: The name of the recording. """ @@ -838,7 +838,7 @@ def send_recording_start_time_nanos(self, nanos: int) -> None: Parameters ---------- - nanos : int + nanos: The start time of the recording. """ @@ -883,7 +883,7 @@ def set_time( Parameters ---------- - timeline : str + timeline: The name of the timeline to set the time for. sequence: Used for sequential indices, like `frame_nr`. @@ -916,7 +916,7 @@ def disable_timeline(self, timeline: str) -> None: Parameters ---------- - timeline : str + timeline: The name of the timeline to clear the time for. """ @@ -1337,7 +1337,7 @@ def job(name: str) -> None: Parameters ---------- - application_id : str + application_id: The application ID that this recording is associated with. """ diff --git a/rerun_py/rerun_sdk/rerun/time.py b/rerun_py/rerun_sdk/rerun/time.py index d64c45695b5b..4882bdf77d15 100644 --- a/rerun_py/rerun_sdk/rerun/time.py +++ b/rerun_py/rerun_sdk/rerun/time.py @@ -63,7 +63,7 @@ def set_time( Parameters ---------- - timeline : str + timeline: The name of the timeline to set the time for. recording: Specifies the [`rerun.RecordingStream`][] to use. @@ -165,7 +165,7 @@ def disable_timeline(timeline: str, recording: RecordingStream | None = None) -> Parameters ---------- - timeline : str + timeline: The name of the timeline to clear the time for. recording: Specifies the [`rerun.RecordingStream`][] to use. From c19939fca69bab53ebb6b297621f9cf10f569fe8 Mon Sep 17 00:00:00 2001 From: Andreas Reich Date: Fri, 6 Mar 2026 17:55:26 +0100 Subject: [PATCH 064/513] Fix re_renderer example checking & forbid `wgpu::PollType` on the web Source-Ref: 3c89c5202ebbca4d8dde5aa4027879e07de2fd80 --- crates/viewer/re_renderer/src/context.rs | 27 ++++++++++--------- .../viewer/re_renderer_examples/framework.rs | 2 +- scripts/ci/rust_checks.py | 6 ++++- scripts/clippy_wasm/clippy.toml | 3 ++- 4 files changed, 22 insertions(+), 16 deletions(-) diff --git a/crates/viewer/re_renderer/src/context.rs b/crates/viewer/re_renderer/src/context.rs index 74fc06031729..6e3435669c05 100644 --- a/crates/viewer/re_renderer/src/context.rs +++ b/crates/viewer/re_renderer/src/context.rs @@ -366,25 +366,26 @@ impl RenderContext { .len() .saturating_sub(Self::MAX_NUM_INFLIGHT_QUEUE_SUBMISSIONS); - if let Some(newest_submission_to_wait_for) = self + if let Some(_newest_submission_to_wait_for) = self .inflight_queue_submissions .drain(0..num_submissions_to_wait_for) .next_back() { // Disable error reporting on Web: - // * On WebGPU poll is a no-op and we don't get here. - // * On WebGL we'll just immediately timeout since we can't actually wait for frames. - if !cfg!(target_arch = "wasm32") - && let Err(err) = self.device.poll(wgpu::PollType::Wait { - submission_index: Some(newest_submission_to_wait_for), - timeout: None, - }) + // * On WebGPU poll is a no-op. + // * On WebGL we'd just immediately timeout since we can't actually wait for frames. + #[cfg(not(target_arch = "wasm32"))] { - re_log::warn_once!( - "Failed to limit number of in-flight GPU frames to {}: {:?}", - Self::MAX_NUM_INFLIGHT_QUEUE_SUBMISSIONS, - err - ); + if let Err(err) = self.device.poll(wgpu::PollType::Wait { + submission_index: Some(_newest_submission_to_wait_for), + timeout: None, + }) { + re_log::warn_once!( + "Failed to limit number of in-flight GPU frames to {}: {:?}", + Self::MAX_NUM_INFLIGHT_QUEUE_SUBMISSIONS, + err + ); + } } } } diff --git a/crates/viewer/re_renderer_examples/framework.rs b/crates/viewer/re_renderer_examples/framework.rs index 2547a9bcf5b8..368a1dae574b 100644 --- a/crates/viewer/re_renderer_examples/framework.rs +++ b/crates/viewer/re_renderer_examples/framework.rs @@ -397,7 +397,7 @@ pub fn start() { #[expect(deprecated)] let window = event_loop.create_window(window).unwrap(); - use winit::platform::web::WindowExtWebSys; + use winit::platform::web::WindowExtWebSys as _; let canvas = window.canvas().expect("Couldn't get canvas"); canvas.style().set_css_text("height: 100%; width: 100%;"); web_sys::window() diff --git a/scripts/ci/rust_checks.py b/scripts/ci/rust_checks.py index d223c389a1a0..6df59b399b26 100755 --- a/scripts/ci/rust_checks.py +++ b/scripts/ci/rust_checks.py @@ -313,7 +313,11 @@ def wasm(results: list[Result]) -> None: ) # Check re_renderer examples for wasm32. results.append( - run_cargo("check", "--target wasm32-unknown-unknown --target-dir target_wasm -p re_renderer --examples"), + run_cargo( + "clippy", + "--target wasm32-unknown-unknown --target-dir target_wasm -p re_renderer_examples", + clippy_conf="scripts/clippy_wasm", # Use ./scripts/clippy_wasm/clippy.toml + ), ) diff --git a/scripts/clippy_wasm/clippy.toml b/scripts/clippy_wasm/clippy.toml index 9cfa9dd58db2..0b176f2702f3 100644 --- a/scripts/clippy_wasm/clippy.toml +++ b/scripts/clippy_wasm/clippy.toml @@ -108,9 +108,9 @@ disallowed-methods = [ { path = "std::time::SystemTime::now", reason = "use `web-time` or `time` crates instead for wasm/web compatibility" }, { path = "tokio::spawn", reason = "no Tokio runtime on web" }, { path = "tokio::sync::mpsc::unbounded_channel", reason = "Use bounded channels on native to apply backpressure." }, - { path = "tokio::time::sleep", reason = "no Tokio runtime on web" }, { path = "tokio::task::spawn_blocking", reason = "no Tokio runtime on web" }, { path = "tokio::task::spawn", reason = "no Tokio runtime on web" }, + { path = "tokio::time::sleep", reason = "no Tokio runtime on web" }, ] # https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_types @@ -126,6 +126,7 @@ disallowed-types = [ { path = "std::sync::RwLock", reason = "Use parking_lot instead" }, { path = "std::thread::Builder", reason = "Cannot spawn threads on wasm" }, # { path = "std::path::PathBuf" }, # We allow having paths, just not resolving them to files + { path = "wgpu::PollType", reason = "Cannot block on Web. Technically only `wgpu::PollType::Wait` is disallowed, but we can't check for that. `wgpu::PollType::Poll` is technically fine, but it is a no-op on WebGPU, so if we rely on it this may hint at a problem." }, ] # Allow-list of words for markdown in docstrings https://rust-lang.github.io/rust-clippy/master/index.html#doc_markdown From 5d5c2f2f52f2f275c9528846fd5863986576986b Mon Sep 17 00:00:00 2001 From: Andreas Reich Date: Fri, 6 Mar 2026 17:55:01 +0100 Subject: [PATCH 065/513] Allow all visualizers to be created for any datatype match MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### Related * Fixes https://github.com/rerun-io/rerun/issues/12661 ### What Unlocks almost every visualizer to pick up almost every component. In 0.30 we allowed * time series (lines & points) to pick up anything that matched the arrow datatype of a scalar (plus allow some casting) * have any _optional_ component to However, we still limited "required components" of any other visualizer to pick up arbitrary datatype matches. For example the positions on a `Points3D` still had to be logged exactly like we expect them. This PR does away with this restriction and changes how we formulate the "visualizability constraints", i.e. the rules that govern whether the Viewer allows creation a visualizer on a given entity! This is extremely powerful since it allows runtime reinterpretation not only for fully custom data, but also for builtin data. For example you can now reinterpret your mesh as a point cloud. Or your graph node 2d points as simple Points2D. Or your points as arrows https://github.com/user-attachments/assets/d5d6515c-02ea-4a2b-923a-c033ac6d6716 It does unfortunately also highlight some weirdness in our datamodel. For instance you can create 3D boxes from your points, but it will start off by using positions for half_sizes since we require half_sizes to be present.. but then you can change half_sizes to a custom value and change the centers: https://github.com/user-attachments/assets/100010cf-c307-442a-bcfc-48040cdf9299 In other places you might encounter strange errors we still have to fix. E.g. here: "why is the depth range broken when reinterpreting this encoded image as depth image?" image ### Testing The new "buffer + format" bit is a bit hairy so it got its own test suite. Otherwise there's some coverage by existing integration tests, see https://rerun-io.github.io/kitdiff/?url=https://github.com/rerun-io/reality/pull/969 --------- Source-Ref: 930aa5dd94f3ada634b7126c033a28d3b910b2a0 Co-authored-by: Claude Opus 4.6 Co-authored-by: Jochen Görtler --- Cargo.lock | 1 - .../re_arrow_combinators/src/selector/mod.rs | 12 +- .../re_selection_panel/src/visualizer_ui.rs | 280 ++++--- .../viewer/re_test_viewport/src/test_view.rs | 6 +- crates/viewer/re_view/src/lib.rs | 12 + .../src/visualizer_system.rs | 6 +- .../re_view_graph/src/visualizers/edges.rs | 7 +- .../re_view_graph/src/visualizers/nodes.rs | 7 +- .../src/visualizers/geo_line_strings.rs | 8 +- .../re_view_map/src/visualizers/geo_points.rs | 8 +- .../src/visualizers/arrows2d.rs | 5 +- .../src/visualizers/arrows3d.rs | 5 +- .../src/visualizers/assets3d.rs | 8 +- .../src/visualizers/boxes2d.rs | 5 +- .../src/visualizers/boxes3d.rs | 6 +- .../src/visualizers/cameras.rs | 5 +- .../src/visualizers/capsules3d.rs | 65 +- .../src/visualizers/cylinders3d.rs | 65 +- .../src/visualizers/depth_images.rs | 8 +- .../src/visualizers/ellipsoids.rs | 6 +- .../src/visualizers/encoded_depth_image.rs | 7 +- .../src/visualizers/encoded_image.rs | 7 +- .../re_view_spatial/src/visualizers/images.rs | 8 +- .../src/visualizers/lines2d.rs | 7 +- .../src/visualizers/lines3d.rs | 7 +- .../re_view_spatial/src/visualizers/meshes.rs | 8 +- .../src/visualizers/points2d.rs | 5 +- .../src/visualizers/points3d.rs | 6 +- .../src/visualizers/segmentation_images.rs | 8 +- .../src/visualizers/transform_axes_3d.rs | 30 +- .../video/video_frame_reference.rs | 5 +- .../src/visualizers/video/video_stream.rs | 7 +- .../re_view_tensor/src/visualizer_system.rs | 5 +- .../src/visualizer_system.rs | 6 +- .../re_view_text_log/src/visualizer_system.rs | 5 +- .../src/line_visualizer_system.rs | 16 +- .../src/point_visualizer_system.rs | 15 +- .../re_view_time_series/src/view_class.rs | 36 +- crates/viewer/re_viewer_context/Cargo.toml | 1 - crates/viewer/re_viewer_context/src/lib.rs | 23 +- .../src/typed_entity_collections.rs | 73 +- .../viewer/re_viewer_context/src/view/mod.rs | 9 +- .../src/view/visualizability_constraints.rs | 329 ++++++++ .../src/view/visualizer_entity_subscriber.rs | 793 +++++++++++++----- .../src/view/visualizer_system.rs | 139 +-- .../src/points3d_color_visualizer.rs | 26 +- .../src/height_field_visualizer.rs | 8 +- .../tests/add_entity_to_view_test.rs | 12 +- ..._container_from_blueprint_panel_menu_1.png | 4 +- ..._container_from_blueprint_panel_menu_2.png | 4 +- ..._container_from_blueprint_panel_menu_3.png | 4 +- .../add_entity_to_view_bar_chart_1.png | 4 +- .../add_entity_to_view_bar_chart_2.png | 4 +- .../add_entity_to_view_bar_chart_3.png | 4 +- .../add_entity_to_view_bar_chart_4.png | 4 +- .../add_entity_to_view_bar_chart_5.png | 4 +- .../add_entity_to_view_boxes2d_1.png | 4 +- .../add_entity_to_view_boxes2d_2.png | 4 +- .../add_entity_to_view_boxes2d_3.png | 4 +- .../add_entity_to_view_boxes2d_4.png | 4 +- .../add_entity_to_view_boxes2d_5.png | 4 +- .../add_entity_to_view_boxes3d_1.png | 4 +- .../add_entity_to_view_boxes3d_2.png | 4 +- .../add_entity_to_view_boxes3d_3.png | 4 +- .../add_entity_to_view_boxes3d_4.png | 4 +- .../add_entity_to_view_boxes3d_5.png | 4 +- .../snapshots/add_entity_to_view_tensor_1.png | 4 +- .../snapshots/add_entity_to_view_tensor_2.png | 4 +- .../snapshots/add_entity_to_view_tensor_3.png | 4 +- .../snapshots/add_entity_to_view_tensor_4.png | 4 +- .../snapshots/add_entity_to_view_tensor_5.png | 4 +- .../add_entity_to_view_text_log_1.png | 4 +- .../add_entity_to_view_text_log_2.png | 4 +- .../add_entity_to_view_text_log_3.png | 4 +- .../add_entity_to_view_text_log_4.png | 4 +- .../add_entity_to_view_text_log_5.png | 4 +- .../tests/snapshots/add_visualizer_axes_4.png | 4 +- .../tests/snapshots/add_visualizer_axes_5.png | 4 +- .../drag_view_to_other_view_bottom_1.png | 4 +- .../drag_view_to_other_view_bottom_2.png | 4 +- .../drag_view_to_other_view_center_1.png | 4 +- .../drag_view_to_other_view_center_2.png | 4 +- .../drag_view_to_other_view_left_1.png | 4 +- .../drag_view_to_other_view_left_2.png | 4 +- .../drag_view_to_other_view_right_1.png | 4 +- .../drag_view_to_other_view_right_2.png | 4 +- .../drag_view_to_other_view_top_1.png | 4 +- .../drag_view_to_other_view_top_2.png | 4 +- .../snapshots/heuristics_mixed_all_root.png | 4 +- .../multi_container_drag_container_1.png | 4 +- .../multi_container_drag_container_2.png | 4 +- .../multi_container_drag_container_3.png | 4 +- .../multi_container_drag_container_4.png | 4 +- .../multi_container_drag_container_5.png | 4 +- .../multi_container_drag_container_6.png | 4 +- .../multi_container_drag_single_view_1.png | 4 +- .../multi_container_drag_single_view_2.png | 4 +- .../multi_container_drag_single_view_3.png | 4 +- .../snapshots/multi_container_many_views.png | 4 +- .../snapshots/resize_view_horizontal_1.png | 4 +- .../snapshots/resize_view_horizontal_2.png | 4 +- .../snapshots/resize_view_horizontal_3.png | 4 +- .../snapshots/resize_view_vertical_1.png | 4 +- .../snapshots/resize_view_vertical_2.png | 4 +- .../snapshots/resize_view_vertical_3.png | 4 +- 105 files changed, 1640 insertions(+), 714 deletions(-) create mode 100644 crates/viewer/re_viewer_context/src/view/visualizability_constraints.rs diff --git a/Cargo.lock b/Cargo.lock index 1f0a7586fc4a..a2831c01d7d5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10593,7 +10593,6 @@ dependencies = [ "ahash", "anyhow", "arrow", - "bit-vec", "bitflags 2.11.0", "bytemuck", "camino", diff --git a/crates/store/re_arrow_combinators/src/selector/mod.rs b/crates/store/re_arrow_combinators/src/selector/mod.rs index 582080289d58..f756d1f08b96 100644 --- a/crates/store/re_arrow_combinators/src/selector/mod.rs +++ b/crates/store/re_arrow_combinators/src/selector/mod.rs @@ -143,6 +143,9 @@ fn process_datatype<'a, P>( P: Fn(&DataType) -> bool, { match datatype { + dt if predicate(dt) => { + result.push((Selector(Expr::Path(path)), dt.clone())); + } DataType::Struct(fields) => { queue.push_back((path, fields)); } @@ -153,6 +156,9 @@ fn process_datatype<'a, P>( assert_non_null: false, }); match inner.data_type() { + dt if predicate(dt) => { + result.push((Selector(Expr::Path(path)), dt.clone())); + } DataType::Struct(nested_fields) => { queue.push_back((path, nested_fields)); } @@ -167,15 +173,9 @@ fn process_datatype<'a, P>( result.push((Selector(Expr::Path(path)), dt.clone())); } } - dt if predicate(dt) => { - result.push((Selector(Expr::Path(path)), dt.clone())); - } _ => {} } } - dt if predicate(dt) => { - result.push((Selector(Expr::Path(path)), dt.clone())); - } _ => {} } } diff --git a/crates/viewer/re_selection_panel/src/visualizer_ui.rs b/crates/viewer/re_selection_panel/src/visualizer_ui.rs index d3d31ac71f3b..d47bb835846a 100644 --- a/crates/viewer/re_selection_panel/src/visualizer_ui.rs +++ b/crates/viewer/re_selection_panel/src/visualizer_ui.rs @@ -20,9 +20,9 @@ use re_view::{ BlueprintResolvedResultsExt as _, ChunksWithComponent, latest_at_with_blueprint_resolved_data, }; use re_viewer_context::{ - AnyPhysicalDatatypeRequirement, BlueprintContext as _, DataResult, DatatypeMatch, - PerVisualizerTypeInViewClass, TryShowEditUiResult, UiLayout, ViewContext, ViewSystemIdentifier, - VisualizableEntities, VisualizableReason, VisualizerCollection, VisualizerComponentMappings, + BlueprintContext as _, DataResult, DatatypeMatch, PerVisualizerTypeInViewClass, + TryShowEditUiResult, UiLayout, ViewContext, ViewSystemIdentifier, VisualizableEntities, + VisualizableReason, VisualizerCollection, VisualizerComponentMappings, VisualizerComponentSource, VisualizerInstruction, VisualizerQueryInfo, VisualizerReportSeverity, VisualizerSystem, VisualizerViewReport, }; @@ -503,30 +503,27 @@ fn collect_source_component_options( // TODO(andreas): Right now we are _more_ flexible for required components, because there we also support // casting in some special cases. Eventually this should always be the case, leaving us always with a list of valid physical types that we filter on. - let allowed_physical_types = if let re_viewer_context::RequiredComponents::AnyPhysicalDatatype( - AnyPhysicalDatatypeRequirement { - target_component, - physical_types, - .. - }, - ) = &query_info.required - && target_component == &mapping_ctx.target_component() - { - physical_types.clone() - } else { - // Get arrow datatype of the target component. - let reflection = mapping_ctx.viewer_ctx().reflection(); - let Some(target_component_reflection) = reflection.components.get(target_component_type) - else { - // No reflection for target component type, that should never happen. - re_log::warn_once!( - "No reflection information for visualizer target component type {:?} found. Unable to determine valid component mappings.", - target_component_type - ); - return Vec::new(); + let allowed_physical_types = + if let re_viewer_context::VisualizabilityConstraints::SingleRequiredComponent(constraint) = + &query_info.constraints + && constraint.target_component() == mapping_ctx.target_component() + { + constraint.physical_types().clone() + } else { + // Get arrow datatype of the target component. + let reflection = mapping_ctx.viewer_ctx().reflection(); + let Some(target_component_reflection) = + reflection.components.get(target_component_type) + else { + // No reflection for target component type, that should never happen. + re_log::warn_once!( + "No reflection information for visualizer target component type {:?} found. Unable to determine valid component mappings.", + target_component_type + ); + return Vec::new(); + }; + std::iter::once(target_component_reflection.datatype.clone()).collect() }; - std::iter::once(target_component_reflection.datatype.clone()).collect() - }; entity_components_with_datatype .iter() @@ -999,61 +996,106 @@ fn menu_add_new_visualizer( data_result: &DataResult, active_visualizers: &[VisualizerInstruction], available_visualizers: &[ViewSystemIdentifier], +) { + ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend); + + // Determine which visualizers are recommended. + let visualizable_entities_per_visualizer = ctx + .viewer_ctx + .collect_visualizable_entities_for_view_class(ctx.view_class_identifier); + let recommended_visualizers = ctx.view_class().recommended_visualizers_for_entity( + &data_result.entity_path, + &visualizable_entities_per_visualizer, + ctx.viewer_ctx.indicated_entities_per_visualizer, + ); + + let (recommended, other): (Vec<_>, Vec<_>) = available_visualizers + .iter() + .copied() + .partition(|vis| recommended_visualizers.0.contains_key(vis)); + + // Don't show categorization if either group is empty. + let show_sections = !recommended.is_empty() && !other.is_empty(); + + if show_sections { + ui.add(re_ui::ComboItemHeader::new("Recommended:")); + } + for visualizer_type in &recommended { + add_new_visualizer_button(ctx, ui, data_result, active_visualizers, *visualizer_type); + } + + if show_sections { + ui.add(re_ui::ComboItemHeader::new("Other:")); + } + for visualizer_type in &other { + add_new_visualizer_button(ctx, ui, data_result, active_visualizers, *visualizer_type); + } +} + +fn add_new_visualizer_button( + ctx: &ViewContext<'_>, + ui: &mut egui::Ui, + data_result: &DataResult, + active_visualizers: &[VisualizerInstruction], + visualizer_type: ViewSystemIdentifier, ) { let override_base_path = data_result.override_base_path(); - ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend); + let already_active = active_visualizers + .iter() + .any(|v| v.visualizer_type == visualizer_type); - for visualizer_type in available_visualizers { - if ui.button(visualizer_type.as_str()).clicked() { - let component_mappings = component_mappings_for_new_visualizer( - ctx, - active_visualizers, - visualizer_type, - &data_result.entity_path, - ); + if ui + .add(ComboItem::new(visualizer_type.as_str()).selected(already_active)) + .clicked() + { + let component_mappings = component_mappings_for_new_visualizer( + ctx, + active_visualizers, + &visualizer_type, + &data_result.entity_path, + ); - // To add a visualizer we have to do two things: - // * add a visualizer type information for that new visualizer instruction - // * add an element to the list of active visualizer ids - let new_instruction = VisualizerInstruction::new( - VisualizerInstructionId::new_random(), - *visualizer_type, + // To add a visualizer we have to do two things: + // * add a visualizer type information for that new visualizer instruction + // * add an element to the list of active visualizer ids + let new_instruction = VisualizerInstruction::new( + VisualizerInstructionId::new_random(), + visualizer_type, + override_base_path, + component_mappings, + ); + let active_visualizer_archetype = ActiveVisualizers::new( + active_visualizers + .iter() + .map(|v| &v.id) + .chain(std::iter::once(&new_instruction.id)) + .map(|v| v.0), + ); + + // If this is the first time we log `ActiveVisualizers`, we have to write out the instructions for all + // visualizers which would be entirely heuristically generated at this point! + let did_not_yet_persist_active_visualizers = ctx + .blueprint_db() + .latest_at( + ctx.blueprint_query(), override_base_path, - component_mappings, - ); - let active_visualizer_archetype = ActiveVisualizers::new( - active_visualizers + ActiveVisualizers::all_components() .iter() - .map(|v| &v.id) - .chain(std::iter::once(&new_instruction.id)) - .map(|v| v.0), - ); - - // If this is the first time we log `ActiveVisualizers`, we have to write out the instructions for all - // visualizers which would be entirely heuristically generated at this point! - let did_not_yet_persist_active_visualizers = ctx - .blueprint_db() - .latest_at( - ctx.blueprint_query(), - override_base_path, - ActiveVisualizers::all_components() - .iter() - .map(|c| c.component), - ) - .components - .is_empty(); - if did_not_yet_persist_active_visualizers { - for instruction in active_visualizers { - instruction.write_instruction_to_blueprint(ctx.viewer_ctx); - } + .map(|c| c.component), + ) + .components + .is_empty(); + if did_not_yet_persist_active_visualizers { + for instruction in active_visualizers { + instruction.write_instruction_to_blueprint(ctx.viewer_ctx); } + } - ctx.save_blueprint_archetype(override_base_path.clone(), &active_visualizer_archetype); - new_instruction.write_instruction_to_blueprint(ctx.viewer_ctx); + ctx.save_blueprint_archetype(override_base_path.clone(), &active_visualizer_archetype); + new_instruction.write_instruction_to_blueprint(ctx.viewer_ctx); - ui.close(); - } + ui.close(); } } @@ -1123,33 +1165,15 @@ fn component_mappings_for_required_components_from_visualizability( visualizer_type: ViewSystemIdentifier, entity_path: &EntityPath, visualizable_entities_per_visualizer: &PerVisualizerTypeInViewClass, -) -> impl Iterator { +) -> Vec { // Look up why this entity is visualizable for this visualizer type. let reason = visualizable_entities_per_visualizer .get(&visualizer_type) .and_then(|entities| entities.get(entity_path)); - let Some(VisualizableReason::DatatypeMatchAny { - matches, - target_component, - }) = reason - else { - if reason.is_none() { - re_log::debug_panic!( - "Entity {entity_path:?} is not visualizable for {visualizer_type:?}, but was offered as an available visualizer" - ); - re_log::warn_once!( - "Entity {entity_path:?} is not visualizable for {visualizer_type:?}" - ); - } - // For non-datatype-match reasons (ExactMatchAll, ExactMatchAny, Always), - // the default identity mapping is correct as it will pick in builtin components. - return Either::Left(std::iter::once(VisualizerComponentMappings::default())); - }; - - // Set up an expression for all possible mappings given this visualization reason. - Either::Right( - matches + match reason { + Some(VisualizableReason::SingleRequiredComponentMatch(matches)) => matches + .matches .iter() .flat_map(|(source_component, match_info)| match match_info { DatatypeMatch::PhysicalDatatypeOnly { selectors, .. } if !selectors.is_empty() => { @@ -1168,8 +1192,74 @@ fn component_mappings_for_required_components_from_visualizability( }, )), }) - .map(|mapping| std::iter::once((*target_component, mapping)).collect()), - ) + .map(|mapping| std::iter::once((matches.target_component, mapping)).collect()) + .collect(), + + Some(VisualizableReason::BufferAndFormatMatch(matches)) => { + // Each (buffer_source, format_component) pair produces a candidate mapping set. + // Buffer matches with nested field selectors expand into multiple candidates. + let format_mappings: Vec<_> = matches + .format_matches + .iter() + .map( + |format_component| VisualizerComponentSource::SourceComponent { + source_component: *format_component, + selector: String::new(), + }, + ) + .collect(); + + matches + .buffer_matches + .iter() + .flat_map(|(source_component, match_info)| { + let buffer_sources: Vec<_> = match match_info { + DatatypeMatch::PhysicalDatatypeOnly { selectors, .. } + if !selectors.is_empty() => + { + selectors + .iter() + .map(|(selector, _)| VisualizerComponentSource::SourceComponent { + source_component: *source_component, + selector: selector.to_string(), + }) + .collect() + } + _ => vec![VisualizerComponentSource::SourceComponent { + source_component: *source_component, + selector: String::new(), + }], + }; + buffer_sources.into_iter().flat_map(|buffer_mapping| { + format_mappings.iter().map(move |format_mapping| { + [ + (matches.buffer_target, buffer_mapping.clone()), + (matches.format_target, format_mapping.clone()), + ] + .into_iter() + .collect() + }) + }) + }) + .collect() + } + + // For non-datatype-match reasons (ExactMatchAny, Always), + // the default identity mapping is correct as it will pick in builtin components. + Some(VisualizableReason::ExactMatchAny | VisualizableReason::Always) => { + vec![VisualizerComponentMappings::default()] + } + + None => { + re_log::debug_panic!( + "Entity {entity_path:?} is not visualizable for {visualizer_type:?}, but was offered as an available visualizer" + ); + re_log::warn_once!( + "Entity {entity_path:?} is not visualizable for {visualizer_type:?}, but was offered as an available visualizer" + ); + vec![VisualizerComponentMappings::default()] + } + } } /// Lists all visualizers that are _not_ active for the given entity but could be. diff --git a/crates/viewer/re_test_viewport/src/test_view.rs b/crates/viewer/re_test_viewport/src/test_view.rs index 9430b5f833e9..0907b057b8d0 100644 --- a/crates/viewer/re_test_viewport/src/test_view.rs +++ b/crates/viewer/re_test_viewport/src/test_view.rs @@ -1,5 +1,6 @@ use re_chunk::EntityPath; use re_log_types::example_components::MyPoint; +use re_sdk_types::Archetype as _; use re_ui::Help; use re_viewer_context::external::re_chunk_store::external::re_chunk; use re_viewer_context::{ @@ -30,7 +31,10 @@ impl VisualizerSystem for TestSystem { &self, _app_options: &re_viewer_context::AppOptions, ) -> re_viewer_context::VisualizerQueryInfo { - VisualizerQueryInfo::from_archetype::() + VisualizerQueryInfo::single_required_component::( + &re_log_types::example_components::MyPoints::descriptor_points(), + &re_log_types::example_components::MyPoints::all_components(), + ) } fn execute( diff --git a/crates/viewer/re_view/src/lib.rs b/crates/viewer/re_view/src/lib.rs index 7ec17f2c0897..fd82b27077c5 100644 --- a/crates/viewer/re_view/src/lib.rs +++ b/crates/viewer/re_view/src/lib.rs @@ -136,6 +136,18 @@ pub fn clamped_or<'a, T>(values: &'a [T], if_empty: &'a T) -> impl Iterator( + values: &[T], + if_empty: impl Fn() -> T, +) -> impl Iterator { + let repeated = values.last().cloned().unwrap_or_else(if_empty); + values.iter().cloned().chain(std::iter::repeat(repeated)) +} + /// Clamp the last value in `values` in order to reach a length of `clamped_len`. /// /// Returns an empty vector if values is empty. diff --git a/crates/viewer/re_view_bar_chart/src/visualizer_system.rs b/crates/viewer/re_view_bar_chart/src/visualizer_system.rs index e1536ab2a07d..4316057cdf6c 100644 --- a/crates/viewer/re_view_bar_chart/src/visualizer_system.rs +++ b/crates/viewer/re_view_bar_chart/src/visualizer_system.rs @@ -3,6 +3,7 @@ use std::collections::BTreeMap; use re_chunk_store::LatestAtQuery; use re_entity_db::EntityPath; use re_sdk_types::{ + Archetype as _, archetypes::BarChart, components::{self, Length}, datatypes, @@ -41,7 +42,10 @@ impl VisualizerSystem for BarChartVisualizerSystem { &self, _app_options: &re_viewer_context::AppOptions, ) -> VisualizerQueryInfo { - VisualizerQueryInfo::from_archetype::() + VisualizerQueryInfo::single_required_component::( + &BarChart::descriptor_values(), + &BarChart::all_components(), + ) } fn execute( diff --git a/crates/viewer/re_view_graph/src/visualizers/edges.rs b/crates/viewer/re_view_graph/src/visualizers/edges.rs index 2898b409fe98..c503e460d920 100644 --- a/crates/viewer/re_view_graph/src/visualizers/edges.rs +++ b/crates/viewer/re_view_graph/src/visualizers/edges.rs @@ -1,7 +1,7 @@ use re_chunk::LatestAtQuery; use re_log_types::{EntityPath, Instance}; use re_sdk_types::archetypes::{self, GraphEdges}; -use re_sdk_types::{self, components, datatypes}; +use re_sdk_types::{self, Archetype as _, components, datatypes}; use re_view::{DataResultQuery as _, VisualizerInstructionQueryResults}; use re_viewer_context::{ self, IdentifiedViewSystem, ViewContext, ViewContextCollection, ViewQuery, @@ -41,7 +41,10 @@ impl VisualizerSystem for EdgesVisualizer { &self, _app_options: &re_viewer_context::AppOptions, ) -> VisualizerQueryInfo { - VisualizerQueryInfo::from_archetype::() + VisualizerQueryInfo::single_required_component::( + &archetypes::GraphEdges::descriptor_edges(), + &archetypes::GraphEdges::all_components(), + ) } /// Populates the visualizer with data from the store. diff --git a/crates/viewer/re_view_graph/src/visualizers/nodes.rs b/crates/viewer/re_view_graph/src/visualizers/nodes.rs index d92ad5056b05..d821c2be877e 100644 --- a/crates/viewer/re_view_graph/src/visualizers/nodes.rs +++ b/crates/viewer/re_view_graph/src/visualizers/nodes.rs @@ -7,7 +7,7 @@ use re_sdk_types::blueprint::components::VisualizerInstructionId; use re_sdk_types::components::{ Color, {self}, }; -use re_sdk_types::{self, ArrowString, archetypes}; +use re_sdk_types::{self, Archetype as _, ArrowString, archetypes}; use re_view::{DataResultQuery as _, VisualizerInstructionQueryResults}; use re_viewer_context::{ self, IdentifiedViewSystem, ViewContext, ViewContextCollection, ViewQuery, @@ -63,7 +63,10 @@ impl VisualizerSystem for NodeVisualizer { &self, _app_options: &re_viewer_context::AppOptions, ) -> VisualizerQueryInfo { - VisualizerQueryInfo::from_archetype::() + VisualizerQueryInfo::single_required_component::( + &archetypes::GraphNodes::descriptor_node_ids(), + &archetypes::GraphNodes::all_components(), + ) } /// Populates the visualizer with data from the store. diff --git a/crates/viewer/re_view_map/src/visualizers/geo_line_strings.rs b/crates/viewer/re_view_map/src/visualizers/geo_line_strings.rs index 248252596de0..528dc545fd42 100644 --- a/crates/viewer/re_view_map/src/visualizers/geo_line_strings.rs +++ b/crates/viewer/re_view_map/src/visualizers/geo_line_strings.rs @@ -1,8 +1,9 @@ use re_log_types::{EntityPath, Instance}; use re_renderer::PickingLayerInstanceId; use re_renderer::renderer::{LineDrawDataError, LineStripFlags}; +use re_sdk_types::Archetype as _; use re_sdk_types::archetypes::GeoLineStrings; -use re_sdk_types::components::{Color, Radius}; +use re_sdk_types::components::{Color, GeoLineString, Radius}; use re_view::{DataResultQuery as _, VisualizerInstructionQueryResults}; use re_viewer_context::{ IdentifiedViewSystem, ViewContext, ViewContextCollection, ViewHighlights, ViewQuery, @@ -35,7 +36,10 @@ impl VisualizerSystem for GeoLineStringsVisualizer { &self, _app_options: &re_viewer_context::AppOptions, ) -> VisualizerQueryInfo { - VisualizerQueryInfo::from_archetype::() + VisualizerQueryInfo::single_required_component::( + &GeoLineStrings::descriptor_line_strings(), + &GeoLineStrings::all_components(), + ) } fn execute( diff --git a/crates/viewer/re_view_map/src/visualizers/geo_points.rs b/crates/viewer/re_view_map/src/visualizers/geo_points.rs index d7a6d90a2ab2..0bb2d021caf6 100644 --- a/crates/viewer/re_view_map/src/visualizers/geo_points.rs +++ b/crates/viewer/re_view_map/src/visualizers/geo_points.rs @@ -1,8 +1,9 @@ use re_log_types::EntityPath; use re_renderer::PickingLayerInstanceId; use re_renderer::renderer::PointCloudDrawDataError; +use re_sdk_types::Archetype as _; use re_sdk_types::archetypes::GeoPoints; -use re_sdk_types::components::Radius; +use re_sdk_types::components::{LatLon, Radius}; use re_view::{ AnnotationSceneContext, DataResultQuery as _, VisualizerInstructionQueryResults, process_annotation_slices, process_color_slice, @@ -38,7 +39,10 @@ impl VisualizerSystem for GeoPointsVisualizer { &self, _app_options: &re_viewer_context::AppOptions, ) -> VisualizerQueryInfo { - VisualizerQueryInfo::from_archetype::() + VisualizerQueryInfo::single_required_component::( + &GeoPoints::descriptor_positions(), + &GeoPoints::all_components(), + ) } fn execute( diff --git a/crates/viewer/re_view_spatial/src/visualizers/arrows2d.rs b/crates/viewer/re_view_spatial/src/visualizers/arrows2d.rs index dc89ff4ee7ef..365569e739be 100644 --- a/crates/viewer/re_view_spatial/src/visualizers/arrows2d.rs +++ b/crates/viewer/re_view_spatial/src/visualizers/arrows2d.rs @@ -175,7 +175,10 @@ impl VisualizerSystem for Arrows2DVisualizer { &self, _app_options: &re_viewer_context::AppOptions, ) -> VisualizerQueryInfo { - VisualizerQueryInfo::from_archetype::() + VisualizerQueryInfo::single_required_component::( + &Arrows2D::descriptor_vectors(), + &Arrows2D::all_components(), + ) } fn execute( diff --git a/crates/viewer/re_view_spatial/src/visualizers/arrows3d.rs b/crates/viewer/re_view_spatial/src/visualizers/arrows3d.rs index 40a9a6dcf541..d73a3da40010 100644 --- a/crates/viewer/re_view_spatial/src/visualizers/arrows3d.rs +++ b/crates/viewer/re_view_spatial/src/visualizers/arrows3d.rs @@ -177,7 +177,10 @@ impl VisualizerSystem for Arrows3DVisualizer { &self, _app_options: &re_viewer_context::AppOptions, ) -> VisualizerQueryInfo { - VisualizerQueryInfo::from_archetype::() + VisualizerQueryInfo::single_required_component::( + &Arrows3D::descriptor_vectors(), + &Arrows3D::all_components(), + ) } fn execute( diff --git a/crates/viewer/re_view_spatial/src/visualizers/assets3d.rs b/crates/viewer/re_view_spatial/src/visualizers/assets3d.rs index e2ad8cd99c98..90742289e2a8 100644 --- a/crates/viewer/re_view_spatial/src/visualizers/assets3d.rs +++ b/crates/viewer/re_view_spatial/src/visualizers/assets3d.rs @@ -2,9 +2,10 @@ use re_chunk_store::RowId; use re_log_types::hash::Hash64; use re_log_types::{Instance, TimeInt}; use re_renderer::renderer::GpuMeshInstance; +use re_sdk_types::Archetype as _; use re_sdk_types::ArrowString; use re_sdk_types::archetypes::Asset3D; -use re_sdk_types::components::AlbedoFactor; +use re_sdk_types::components::{AlbedoFactor, Blob}; use re_viewer_context::{ IdentifiedViewSystem, QueryContext, ViewContext, ViewContextCollection, ViewQuery, ViewSystemExecutionError, VisualizerExecutionOutput, VisualizerQueryInfo, VisualizerSystem, @@ -115,7 +116,10 @@ impl VisualizerSystem for Asset3DVisualizer { &self, _app_options: &re_viewer_context::AppOptions, ) -> VisualizerQueryInfo { - VisualizerQueryInfo::from_archetype::() + VisualizerQueryInfo::single_required_component::( + &Asset3D::descriptor_blob(), + &Asset3D::all_components(), + ) } fn execute( diff --git a/crates/viewer/re_view_spatial/src/visualizers/boxes2d.rs b/crates/viewer/re_view_spatial/src/visualizers/boxes2d.rs index c79b0b83ce89..fcb48cea2a3c 100644 --- a/crates/viewer/re_view_spatial/src/visualizers/boxes2d.rs +++ b/crates/viewer/re_view_spatial/src/visualizers/boxes2d.rs @@ -175,7 +175,10 @@ impl VisualizerSystem for Boxes2DVisualizer { &self, _app_options: &re_viewer_context::AppOptions, ) -> VisualizerQueryInfo { - VisualizerQueryInfo::from_archetype::() + VisualizerQueryInfo::single_required_component::( + &Boxes2D::descriptor_half_sizes(), + &Boxes2D::all_components(), + ) } fn execute( diff --git a/crates/viewer/re_view_spatial/src/visualizers/boxes3d.rs b/crates/viewer/re_view_spatial/src/visualizers/boxes3d.rs index dcc085291938..f20650e72ffc 100644 --- a/crates/viewer/re_view_spatial/src/visualizers/boxes3d.rs +++ b/crates/viewer/re_view_spatial/src/visualizers/boxes3d.rs @@ -1,6 +1,7 @@ use std::iter; use re_chunk_store::external::re_chunk::ChunkComponentIterItem; +use re_sdk_types::Archetype as _; use re_sdk_types::archetypes::Boxes3D; use re_sdk_types::components::{ClassId, Color, FillMode, HalfSize3D, Radius, ShowLabels}; use re_sdk_types::{ArrowString, components}; @@ -103,7 +104,10 @@ impl VisualizerSystem for Boxes3DVisualizer { &self, _app_options: &re_viewer_context::AppOptions, ) -> VisualizerQueryInfo { - VisualizerQueryInfo::from_archetype::() + VisualizerQueryInfo::single_required_component::( + &Boxes3D::descriptor_half_sizes(), + &Boxes3D::all_components(), + ) } fn execute( diff --git a/crates/viewer/re_view_spatial/src/visualizers/cameras.rs b/crates/viewer/re_view_spatial/src/visualizers/cameras.rs index 3cbf9101f77c..dee2dca6a060 100644 --- a/crates/viewer/re_view_spatial/src/visualizers/cameras.rs +++ b/crates/viewer/re_view_spatial/src/visualizers/cameras.rs @@ -220,7 +220,10 @@ impl VisualizerSystem for CamerasVisualizer { &self, _app_options: &re_viewer_context::AppOptions, ) -> VisualizerQueryInfo { - VisualizerQueryInfo::from_archetype::() + VisualizerQueryInfo::single_required_component::( + &Pinhole::descriptor_image_from_camera(), + &Pinhole::all_components(), + ) } fn execute( diff --git a/crates/viewer/re_view_spatial/src/visualizers/capsules3d.rs b/crates/viewer/re_view_spatial/src/visualizers/capsules3d.rs index c5d6dac6297e..3d1948491bf1 100644 --- a/crates/viewer/re_view_spatial/src/visualizers/capsules3d.rs +++ b/crates/viewer/re_view_spatial/src/visualizers/capsules3d.rs @@ -4,11 +4,12 @@ use ordered_float::NotNan; use re_chunk_store::external::re_chunk::ChunkComponentIterItem; use re_sdk_types::archetypes::Capsules3D; use re_sdk_types::components::{ClassId, Color, FillMode, HalfSize3D, Length, Radius, ShowLabels}; -use re_sdk_types::{ArrowString, components}; -use re_view::clamped_or_nothing; +use re_sdk_types::{Archetype as _, ArrowString, components}; +use re_view::clamped_or_else; use re_viewer_context::{ IdentifiedViewSystem, QueryContext, ViewContext, ViewContextCollection, ViewQuery, ViewSystemExecutionError, VisualizerExecutionOutput, VisualizerQueryInfo, VisualizerSystem, + typed_fallback_for, }; use super::SpatialViewVisualizerData; @@ -41,11 +42,18 @@ impl Capsules3DVisualizer { // Number of instances is determined by whichever is *longer* of `lengths` and `radii`. // The other component is clamped (last value repeated) to match. let num_instances = batch.radii.len().max(batch.lengths.len()); - let lengths_iter = clamped_or_nothing(batch.lengths, num_instances); - let radii_iter = clamped_or_nothing(batch.radii, num_instances); + let lengths_iter = clamped_or_else(batch.lengths, || { + typed_fallback_for(query_context, Capsules3D::descriptor_lengths().component) + }) + .take(num_instances); + let radii: Vec = clamped_or_else(batch.radii, || { + typed_fallback_for(query_context, Capsules3D::descriptor_radii().component) + }) + .take(num_instances) + .collect(); - let half_sizes: Vec = radii_iter - .clone() + let half_sizes: Vec = radii + .iter() .map(|&Radius(radius)| HalfSize3D::splat(clean_length(radius.0))) .collect(); @@ -61,8 +69,8 @@ impl Capsules3DVisualizer { }; let meshes = lengths_iter - .zip(radii_iter) - .map(|(&Length(length), &Radius(radius))| { + .zip(radii.iter()) + .map(|(Length(length), &Radius(radius))| { let ratio = clean_length(length.0 / radius.0); // Avoid generating extremely similar meshes by rounding the ratio. @@ -110,9 +118,9 @@ impl Capsules3DVisualizer { struct Capsules3DComponentData<'a> { // Point of views lengths: &'a [Length], - radii: &'a [Radius], // Clamped to edge + radii: &'a [Radius], translations: &'a [components::Translation3D], rotation_axis_angles: ChunkComponentIterItem, quaternions: &'a [components::RotationQuat], @@ -137,7 +145,14 @@ impl VisualizerSystem for Capsules3DVisualizer { &self, _app_options: &re_viewer_context::AppOptions, ) -> VisualizerQueryInfo { - VisualizerQueryInfo::from_archetype::() + // The required component of the archetype is actually both lengths and radii, + // But while that makes sense for the user-facing API, we can just use lengths as the required component for visualizability purposes. + // This makes the requirements easier, and also means that if a user only provides length, + // they will still get a visualizer with (editable) default radii instead of no visualizer at all. + VisualizerQueryInfo::single_required_component::( + &Capsules3D::descriptor_lengths(), + &Capsules3D::all_components(), + ) } fn execute( @@ -163,31 +178,11 @@ impl VisualizerSystem for Capsules3DVisualizer { &output, preferred_view_kind, |ctx, spatial_ctx, results| { + // See comment on `visualizer_query_info` for the rationale of treating only lengths as required. + // (instead of lengths and radii as noted in the archetype definition). let all_lengths = results.iter_required(Capsules3D::descriptor_lengths().component); - if all_lengths.is_empty() { - return Ok(()); - } - let all_radii = results.iter_required(Capsules3D::descriptor_radii().component); - if all_radii.is_empty() { - return Ok(()); - } - let num_lengths: usize = all_lengths - .chunks() - .iter() - .flat_map(|chunk| chunk.iter_slices::()) - .map(|lengths| lengths.len()) - .sum(); - let num_radii: usize = all_radii - .chunks() - .iter() - .flat_map(|chunk| chunk.iter_slices::()) - .map(|radii| radii.len()) - .sum(); - let num_instances = num_lengths.max(num_radii); - if num_instances == 0 { - return Ok(()); - } + let all_radii = results.iter_optional(Capsules3D::descriptor_radii().component); let all_translations = results.iter_optional(Capsules3D::descriptor_translations().component); let all_rotation_axis_angles = @@ -205,7 +200,7 @@ impl VisualizerSystem for Capsules3DVisualizer { let all_class_ids = results.iter_optional(Capsules3D::descriptor_class_ids().component); - let data = re_query::range_zip_2x9( + let data = re_query::range_zip_1x10( all_lengths.slice::(), all_radii.slice::(), all_translations.slice::<[f32; 3]>(), @@ -235,7 +230,7 @@ impl VisualizerSystem for Capsules3DVisualizer { )| { Capsules3DComponentData { lengths: bytemuck::cast_slice(lengths), - radii: bytemuck::cast_slice(radii), + radii: radii.map_or(&[], bytemuck::cast_slice), translations: translations.map_or(&[], bytemuck::cast_slice), rotation_axis_angles: rotation_axis_angles.unwrap_or_default(), quaternions: quaternions.map_or(&[], bytemuck::cast_slice), diff --git a/crates/viewer/re_view_spatial/src/visualizers/cylinders3d.rs b/crates/viewer/re_view_spatial/src/visualizers/cylinders3d.rs index 3d9e5c035edd..0df40e589512 100644 --- a/crates/viewer/re_view_spatial/src/visualizers/cylinders3d.rs +++ b/crates/viewer/re_view_spatial/src/visualizers/cylinders3d.rs @@ -3,11 +3,12 @@ use std::iter; use re_chunk_store::external::re_chunk::ChunkComponentIterItem; use re_sdk_types::archetypes::Cylinders3D; use re_sdk_types::components::{ClassId, Color, FillMode, HalfSize3D, Length, Radius, ShowLabels}; -use re_sdk_types::{ArrowString, components}; -use re_view::clamped_or_nothing; +use re_sdk_types::{Archetype as _, ArrowString, components}; +use re_view::clamped_or_else; use re_viewer_context::{ IdentifiedViewSystem, QueryContext, ViewContext, ViewContextCollection, ViewQuery, ViewSystemExecutionError, VisualizerExecutionOutput, VisualizerQueryInfo, VisualizerSystem, + typed_fallback_for, }; use super::SpatialViewVisualizerData; @@ -40,12 +41,24 @@ impl Cylinders3DVisualizer { // Number of instances is determined by whichever is *longer* of `lengths` and `radii`. // The other component is clamped (last value repeated) to match. let num_instances = batch.radii.len().max(batch.lengths.len()); - let lengths_iter = clamped_or_nothing(batch.lengths, num_instances); - let radii_iter = clamped_or_nothing(batch.radii, num_instances); + let lengths_iter = clamped_or_else(batch.lengths, || { + typed_fallback_for::( + query_context, + Cylinders3D::descriptor_lengths().component, + ) + }) + .take(num_instances); + let radii_iter = clamped_or_else(batch.radii, || { + typed_fallback_for::( + query_context, + Cylinders3D::descriptor_radii().component, + ) + }) + .take(num_instances); let half_sizes: Vec = lengths_iter .zip(radii_iter) - .map(|(&Length(length), &Radius(radius))| { + .map(|(Length(length), Radius(radius))| { let radius = clean_length(radius.0); // Cylinder radius is already half the diameter, so we can use it directly. // Length is the full length, so we divide by 2 to get the half size. @@ -100,9 +113,9 @@ impl Cylinders3DVisualizer { struct Cylinders3DComponentData<'a> { // Point of views lengths: &'a [Length], - radii: &'a [Radius], // Clamped to edge + radii: &'a [Radius], centers: &'a [components::Translation3D], rotation_axis_angles: ChunkComponentIterItem, quaternions: &'a [components::RotationQuat], @@ -127,7 +140,14 @@ impl VisualizerSystem for Cylinders3DVisualizer { &self, _app_options: &re_viewer_context::AppOptions, ) -> VisualizerQueryInfo { - VisualizerQueryInfo::from_archetype::() + // The required component of the archetype is actually both lengths and radii, + // But while that makes sense for the user-facing API, we can just use lengths as the required component for visualizability purposes. + // This makes the requirements easier, and also means that if a user only provides length, + // they will still get a visualizer with (editable) default radii instead of no visualizer at all. + VisualizerQueryInfo::single_required_component::( + &Cylinders3D::descriptor_lengths(), + &Cylinders3D::all_components(), + ) } fn execute( @@ -153,31 +173,12 @@ impl VisualizerSystem for Cylinders3DVisualizer { &output, preferred_view_kind, |ctx, spatial_ctx, results| { + // See comment on `visualizer_query_info` for the rationale of treating only lengths as required. + // (instead of lengths and radii as noted in the archetype definition). let all_lengths = results.iter_required(Cylinders3D::descriptor_lengths().component); - if all_lengths.is_empty() { - return Ok(()); - } - let all_radii = results.iter_required(Cylinders3D::descriptor_radii().component); - if all_radii.is_empty() { - return Ok(()); - } - let num_lengths: usize = all_lengths - .chunks() - .iter() - .flat_map(|chunk| chunk.iter_slices::()) - .map(|lengths| lengths.len()) - .sum(); - let num_radii: usize = all_radii - .chunks() - .iter() - .flat_map(|chunk| chunk.iter_slices::()) - .map(|radii| radii.len()) - .sum(); - let num_instances = num_lengths.max(num_radii); - if num_instances == 0 { - return Ok(()); - } + + let all_radii = results.iter_optional(Cylinders3D::descriptor_radii().component); let all_centers = results.iter_optional(Cylinders3D::descriptor_centers().component); let all_rotation_axis_angles = @@ -195,7 +196,7 @@ impl VisualizerSystem for Cylinders3DVisualizer { let all_class_ids = results.iter_optional(Cylinders3D::descriptor_class_ids().component); - let data = re_query::range_zip_2x9( + let data = re_query::range_zip_1x10( all_lengths.slice::(), all_radii.slice::(), all_centers.slice::<[f32; 3]>(), @@ -225,7 +226,7 @@ impl VisualizerSystem for Cylinders3DVisualizer { )| { Cylinders3DComponentData { lengths: bytemuck::cast_slice(lengths), - radii: bytemuck::cast_slice(radii), + radii: radii.map_or(&[], bytemuck::cast_slice), centers: centers.map_or(&[], bytemuck::cast_slice), rotation_axis_angles: rotation_axis_angles.unwrap_or_default(), quaternions: quaternions.map_or(&[], bytemuck::cast_slice), diff --git a/crates/viewer/re_view_spatial/src/visualizers/depth_images.rs b/crates/viewer/re_view_spatial/src/visualizers/depth_images.rs index 61d6cceef32e..689b503bbe5f 100644 --- a/crates/viewer/re_view_spatial/src/visualizers/depth_images.rs +++ b/crates/viewer/re_view_spatial/src/visualizers/depth_images.rs @@ -4,7 +4,7 @@ use re_entity_db::EntityPath; use re_log_types::EntityPathHash; use re_renderer::renderer::{ColormappedTexture, DepthCloud, DepthClouds}; use re_sdk_types::archetypes::DepthImage; -use re_sdk_types::components::{Colormap, DepthMeter, FillRatio, ImageFormat}; +use re_sdk_types::components::{Colormap, DepthMeter, FillRatio, ImageBuffer, ImageFormat}; use re_sdk_types::{Archetype as _, ArchetypeName, ComponentIdentifier}; use re_viewer_context::{ ColormapWithRange, IdentifiedViewSystem, ImageInfo, ImageStatsCache, QueryContext, @@ -233,7 +233,11 @@ impl VisualizerSystem for DepthImageVisualizer { &self, _app_options: &re_viewer_context::AppOptions, ) -> VisualizerQueryInfo { - VisualizerQueryInfo::from_archetype::() + VisualizerQueryInfo::buffer_and_format::( + &DepthImage::descriptor_buffer(), + &DepthImage::descriptor_format(), + &DepthImage::all_components(), + ) } fn execute( diff --git a/crates/viewer/re_view_spatial/src/visualizers/ellipsoids.rs b/crates/viewer/re_view_spatial/src/visualizers/ellipsoids.rs index 3d08729b7d95..8cbb6cad5949 100644 --- a/crates/viewer/re_view_spatial/src/visualizers/ellipsoids.rs +++ b/crates/viewer/re_view_spatial/src/visualizers/ellipsoids.rs @@ -1,6 +1,7 @@ use std::iter; use re_chunk_store::external::re_chunk::ChunkComponentIterItem; +use re_sdk_types::Archetype as _; use re_sdk_types::archetypes::Ellipsoids3D; use re_sdk_types::components::{ClassId, Color, FillMode, HalfSize3D, Radius, ShowLabels}; use re_sdk_types::{ArrowString, components}; @@ -108,7 +109,10 @@ impl VisualizerSystem for Ellipsoids3DVisualizer { &self, _app_options: &re_viewer_context::AppOptions, ) -> VisualizerQueryInfo { - VisualizerQueryInfo::from_archetype::() + VisualizerQueryInfo::single_required_component::( + &Ellipsoids3D::descriptor_half_sizes(), + &Ellipsoids3D::all_components(), + ) } fn execute( diff --git a/crates/viewer/re_view_spatial/src/visualizers/encoded_depth_image.rs b/crates/viewer/re_view_spatial/src/visualizers/encoded_depth_image.rs index 7dce886610e5..ed085eec81bd 100644 --- a/crates/viewer/re_view_spatial/src/visualizers/encoded_depth_image.rs +++ b/crates/viewer/re_view_spatial/src/visualizers/encoded_depth_image.rs @@ -4,7 +4,7 @@ use re_log_types::EntityPathHash; use re_sdk_types::{ Archetype as _, archetypes::EncodedDepthImage, - components::{Colormap, MediaType}, + components::{Blob, Colormap, MediaType}, }; use re_viewer_context::{ IdentifiedViewSystem, ImageDecodeCache, ViewContext, ViewContextCollection, ViewQuery, @@ -53,7 +53,10 @@ impl VisualizerSystem for EncodedDepthImageVisualizer { &self, _app_options: &re_viewer_context::AppOptions, ) -> VisualizerQueryInfo { - VisualizerQueryInfo::from_archetype::() + VisualizerQueryInfo::single_required_component::( + &EncodedDepthImage::descriptor_blob(), + &EncodedDepthImage::all_components(), + ) } fn execute( diff --git a/crates/viewer/re_view_spatial/src/visualizers/encoded_image.rs b/crates/viewer/re_view_spatial/src/visualizers/encoded_image.rs index 15a2cf1be776..b12accdfe537 100644 --- a/crates/viewer/re_view_spatial/src/visualizers/encoded_image.rs +++ b/crates/viewer/re_view_spatial/src/visualizers/encoded_image.rs @@ -1,6 +1,6 @@ use re_sdk_types::Archetype as _; use re_sdk_types::archetypes::EncodedImage; -use re_sdk_types::components::{MediaType, Opacity}; +use re_sdk_types::components::{Blob, MediaType, Opacity}; use re_view::VisualizerInstructionQueryResults; use re_viewer_context::{ IdentifiedViewSystem, ImageDecodeCache, QueryContext, ViewContext, ViewContextCollection, @@ -38,7 +38,10 @@ impl VisualizerSystem for EncodedImageVisualizer { &self, _app_options: &re_viewer_context::AppOptions, ) -> VisualizerQueryInfo { - VisualizerQueryInfo::from_archetype::() + VisualizerQueryInfo::single_required_component::( + &EncodedImage::descriptor_blob(), + &EncodedImage::all_components(), + ) } fn execute( diff --git a/crates/viewer/re_view_spatial/src/visualizers/images.rs b/crates/viewer/re_view_spatial/src/visualizers/images.rs index 6a335d37b80e..24259747a19a 100644 --- a/crates/viewer/re_view_spatial/src/visualizers/images.rs +++ b/crates/viewer/re_view_spatial/src/visualizers/images.rs @@ -1,6 +1,6 @@ use re_sdk_types::Archetype as _; use re_sdk_types::archetypes::Image; -use re_sdk_types::components::{ImageFormat, Opacity}; +use re_sdk_types::components::{ImageBuffer, ImageFormat, Opacity}; use re_sdk_types::image::ImageKind; use re_viewer_context::{ IdentifiedViewSystem, ImageInfo, QueryContext, ViewContext, ViewContextCollection, ViewQuery, @@ -43,7 +43,11 @@ impl VisualizerSystem for ImageVisualizer { &self, _app_options: &re_viewer_context::AppOptions, ) -> VisualizerQueryInfo { - VisualizerQueryInfo::from_archetype::() + VisualizerQueryInfo::buffer_and_format::( + &Image::descriptor_buffer(), + &Image::descriptor_format(), + &Image::all_components(), + ) } fn execute( diff --git a/crates/viewer/re_view_spatial/src/visualizers/lines2d.rs b/crates/viewer/re_view_spatial/src/visualizers/lines2d.rs index a0f43e38029d..5f5db3615782 100644 --- a/crates/viewer/re_view_spatial/src/visualizers/lines2d.rs +++ b/crates/viewer/re_view_spatial/src/visualizers/lines2d.rs @@ -2,7 +2,7 @@ use re_log_types::Instance; use re_renderer::renderer::LineStripFlags; use re_renderer::{LineDrawableBuilder, PickingLayerInstanceId}; use re_sdk_types::archetypes::LineStrips2D; -use re_sdk_types::components::{ClassId, Color, Radius, ShowLabels}; +use re_sdk_types::components::{ClassId, Color, LineStrip2D, Radius, ShowLabels}; use re_sdk_types::{Archetype as _, ArrowString}; use re_view::{process_annotation_slices, process_color_slice}; use re_viewer_context::{ @@ -162,7 +162,10 @@ impl VisualizerSystem for Lines2DVisualizer { &self, _app_options: &re_viewer_context::AppOptions, ) -> VisualizerQueryInfo { - VisualizerQueryInfo::from_archetype::() + VisualizerQueryInfo::single_required_component::( + &LineStrips2D::descriptor_strips(), + &LineStrips2D::all_components(), + ) } fn execute( diff --git a/crates/viewer/re_view_spatial/src/visualizers/lines3d.rs b/crates/viewer/re_view_spatial/src/visualizers/lines3d.rs index 54ad32d60849..885c238c4636 100644 --- a/crates/viewer/re_view_spatial/src/visualizers/lines3d.rs +++ b/crates/viewer/re_view_spatial/src/visualizers/lines3d.rs @@ -2,7 +2,7 @@ use re_log_types::Instance; use re_renderer::PickingLayerInstanceId; use re_renderer::renderer::LineStripFlags; use re_sdk_types::archetypes::LineStrips3D; -use re_sdk_types::components::{ClassId, Color, Radius, ShowLabels}; +use re_sdk_types::components::{ClassId, Color, LineStrip3D, Radius, ShowLabels}; use re_sdk_types::{Archetype as _, ArrowString}; use re_view::{process_annotation_slices, process_color_slice}; use re_viewer_context::{ @@ -172,7 +172,10 @@ impl VisualizerSystem for Lines3DVisualizer { &self, _app_options: &re_viewer_context::AppOptions, ) -> VisualizerQueryInfo { - VisualizerQueryInfo::from_archetype::() + VisualizerQueryInfo::single_required_component::( + &LineStrips3D::descriptor_strips(), + &LineStrips3D::all_components(), + ) } fn execute( diff --git a/crates/viewer/re_view_spatial/src/visualizers/meshes.rs b/crates/viewer/re_view_spatial/src/visualizers/meshes.rs index 3e6854b16920..5fcf448f53d7 100644 --- a/crates/viewer/re_view_spatial/src/visualizers/meshes.rs +++ b/crates/viewer/re_view_spatial/src/visualizers/meshes.rs @@ -3,8 +3,9 @@ use re_log_types::hash::Hash64; use re_log_types::{Instance, TimeInt}; use re_renderer::RenderContext; use re_renderer::renderer::GpuMeshInstance; +use re_sdk_types::Archetype as _; use re_sdk_types::archetypes::Mesh3D; -use re_sdk_types::components::ImageFormat; +use re_sdk_types::components::{ImageFormat, Position3D}; use re_viewer_context::{ IdentifiedViewSystem, QueryContext, ViewContext, ViewContextCollection, ViewQuery, ViewSystemExecutionError, VisualizerExecutionOutput, VisualizerQueryInfo, VisualizerSystem, @@ -116,7 +117,10 @@ impl VisualizerSystem for Mesh3DVisualizer { &self, _app_options: &re_viewer_context::AppOptions, ) -> VisualizerQueryInfo { - VisualizerQueryInfo::from_archetype::() + VisualizerQueryInfo::single_required_component::( + &Mesh3D::descriptor_vertex_positions(), + &Mesh3D::all_components(), + ) } fn execute( diff --git a/crates/viewer/re_view_spatial/src/visualizers/points2d.rs b/crates/viewer/re_view_spatial/src/visualizers/points2d.rs index 6bcaa9f3122a..9bfa78db65e0 100644 --- a/crates/viewer/re_view_spatial/src/visualizers/points2d.rs +++ b/crates/viewer/re_view_spatial/src/visualizers/points2d.rs @@ -180,7 +180,10 @@ impl VisualizerSystem for Points2DVisualizer { &self, _app_options: &re_viewer_context::AppOptions, ) -> VisualizerQueryInfo { - VisualizerQueryInfo::from_archetype::() + VisualizerQueryInfo::single_required_component::( + &Points2D::descriptor_positions(), + &Points2D::all_components(), + ) } fn execute( diff --git a/crates/viewer/re_view_spatial/src/visualizers/points3d.rs b/crates/viewer/re_view_spatial/src/visualizers/points3d.rs index 0c3f25001a8d..d1e6a7f4cce1 100644 --- a/crates/viewer/re_view_spatial/src/visualizers/points3d.rs +++ b/crates/viewer/re_view_spatial/src/visualizers/points3d.rs @@ -1,5 +1,6 @@ use itertools::Itertools as _; use re_renderer::{LineDrawableBuilder, PickingLayerInstanceId, PointCloudBuilder}; +use re_sdk_types::Archetype as _; use re_sdk_types::ArrowString; use re_sdk_types::archetypes::Points3D; use re_sdk_types::components::{ClassId, Color, KeypointId, Position3D, Radius, ShowLabels}; @@ -178,7 +179,10 @@ impl VisualizerSystem for Points3DVisualizer { &self, _app_options: &re_viewer_context::AppOptions, ) -> VisualizerQueryInfo { - VisualizerQueryInfo::from_archetype::() + VisualizerQueryInfo::single_required_component::( + &Points3D::descriptor_positions(), + &Points3D::all_components(), + ) } fn execute( diff --git a/crates/viewer/re_view_spatial/src/visualizers/segmentation_images.rs b/crates/viewer/re_view_spatial/src/visualizers/segmentation_images.rs index e9bbfaa4ee88..ce0f711048ac 100644 --- a/crates/viewer/re_view_spatial/src/visualizers/segmentation_images.rs +++ b/crates/viewer/re_view_spatial/src/visualizers/segmentation_images.rs @@ -1,6 +1,6 @@ use re_sdk_types::Archetype as _; use re_sdk_types::archetypes::SegmentationImage; -use re_sdk_types::components::{ImageFormat, Opacity}; +use re_sdk_types::components::{ImageBuffer, ImageFormat, Opacity}; use re_sdk_types::image::ImageKind; use re_viewer_context::{ IdentifiedViewSystem, ImageInfo, ViewContext, ViewContextCollection, ViewQuery, @@ -41,7 +41,11 @@ impl VisualizerSystem for SegmentationImageVisualizer { &self, _app_options: &re_viewer_context::AppOptions, ) -> VisualizerQueryInfo { - VisualizerQueryInfo::from_archetype::() + VisualizerQueryInfo::buffer_and_format::( + &SegmentationImage::descriptor_buffer(), + &SegmentationImage::descriptor_format(), + &SegmentationImage::all_components(), + ) } fn execute( diff --git a/crates/viewer/re_view_spatial/src/visualizers/transform_axes_3d.rs b/crates/viewer/re_view_spatial/src/visualizers/transform_axes_3d.rs index 00ffb011b5da..2e3f68170c07 100644 --- a/crates/viewer/re_view_spatial/src/visualizers/transform_axes_3d.rs +++ b/crates/viewer/re_view_spatial/src/visualizers/transform_axes_3d.rs @@ -7,8 +7,8 @@ use re_sdk_types::archetypes::{ use re_sdk_types::components::{AxisLength, ShowLabels}; use re_view::latest_at_with_blueprint_resolved_data; use re_viewer_context::{ - IdentifiedViewSystem, RequiredComponents, ViewContext, ViewContextCollection, ViewQuery, - ViewSystemExecutionError, VisualizerExecutionOutput, VisualizerQueryInfo, + IdentifiedViewSystem, ViewContext, ViewContextCollection, ViewQuery, ViewSystemExecutionError, + VisualizabilityConstraints, VisualizerExecutionOutput, VisualizerQueryInfo, VisualizerReportSeverity, VisualizerSystem, }; @@ -38,19 +38,19 @@ impl VisualizerSystem for TransformAxes3DVisualizer { &self, _app_options: &re_viewer_context::AppOptions, ) -> VisualizerQueryInfo { - let mut query_info = VisualizerQueryInfo::from_archetype::(); - - // Make this visualizer available for any entity with Transform3D components - query_info.required = RequiredComponents::AnyComponent( - Transform3D::all_component_identifiers() - .chain(CoordinateFrame::all_component_identifiers()) - .chain(InstancePoses3D::all_component_identifiers()) - .chain(Pinhole::all_component_identifiers()) - .chain(TransformAxes3D::all_component_identifiers()) - .collect(), - ); - - query_info + VisualizerQueryInfo { + relevant_archetype: Some(TransformAxes3D::name()), + // Make this visualizer available for any entity with Transform3D components + constraints: VisualizabilityConstraints::AnyBuiltinComponent( + Transform3D::all_component_identifiers() + .chain(CoordinateFrame::all_component_identifiers()) + .chain(InstancePoses3D::all_component_identifiers()) + .chain(Pinhole::all_component_identifiers()) + .chain(TransformAxes3D::all_component_identifiers()) + .collect(), + ), + queried: TransformAxes3D::all_components().iter().cloned().collect(), + } } fn execute( diff --git a/crates/viewer/re_view_spatial/src/visualizers/video/video_frame_reference.rs b/crates/viewer/re_view_spatial/src/visualizers/video/video_frame_reference.rs index ffdfcae01b8c..86a071fbfd2c 100644 --- a/crates/viewer/re_view_spatial/src/visualizers/video/video_frame_reference.rs +++ b/crates/viewer/re_view_spatial/src/visualizers/video/video_frame_reference.rs @@ -45,7 +45,10 @@ impl VisualizerSystem for VideoFrameReferenceVisualizer { &self, _app_options: &re_viewer_context::AppOptions, ) -> VisualizerQueryInfo { - VisualizerQueryInfo::from_archetype::() + VisualizerQueryInfo::single_required_component::( + &VideoFrameReference::descriptor_timestamp(), + &VideoFrameReference::all_components(), + ) } fn execute( diff --git a/crates/viewer/re_view_spatial/src/visualizers/video/video_stream.rs b/crates/viewer/re_view_spatial/src/visualizers/video/video_stream.rs index 6ec681c8f2f8..6564a44fbddd 100644 --- a/crates/viewer/re_view_spatial/src/visualizers/video/video_stream.rs +++ b/crates/viewer/re_view_spatial/src/visualizers/video/video_stream.rs @@ -1,6 +1,6 @@ use re_sdk_types::Archetype as _; use re_sdk_types::archetypes::VideoStream; -use re_sdk_types::components::Opacity; +use re_sdk_types::components::{Opacity, VideoCodec}; use re_view::DataResultQuery as _; use re_viewer_context::{ IdentifiedViewSystem, VideoStreamCache, VideoStreamProcessingError, ViewClass as _, @@ -44,7 +44,10 @@ impl VisualizerSystem for VideoStreamVisualizer { &self, _app_options: &re_viewer_context::AppOptions, ) -> VisualizerQueryInfo { - VisualizerQueryInfo::from_archetype::() + VisualizerQueryInfo::single_required_component::( + &VideoStream::descriptor_codec(), + &VideoStream::all_components(), + ) } fn execute( diff --git a/crates/viewer/re_view_tensor/src/visualizer_system.rs b/crates/viewer/re_view_tensor/src/visualizer_system.rs index 305a4e358a87..5ada0de958c6 100644 --- a/crates/viewer/re_view_tensor/src/visualizer_system.rs +++ b/crates/viewer/re_view_tensor/src/visualizer_system.rs @@ -31,7 +31,10 @@ impl VisualizerSystem for TensorSystem { &self, _app_options: &re_viewer_context::AppOptions, ) -> VisualizerQueryInfo { - VisualizerQueryInfo::from_archetype::() + VisualizerQueryInfo::single_required_component::( + &Tensor::descriptor_data(), + &Tensor::all_components(), + ) } fn execute( diff --git a/crates/viewer/re_view_text_document/src/visualizer_system.rs b/crates/viewer/re_view_text_document/src/visualizer_system.rs index 996a14f1c350..552d8b7ee1d7 100644 --- a/crates/viewer/re_view_text_document/src/visualizer_system.rs +++ b/crates/viewer/re_view_text_document/src/visualizer_system.rs @@ -1,4 +1,5 @@ use re_chunk_store::LatestAtQuery; +use re_sdk_types::Archetype as _; use re_sdk_types::archetypes::TextDocument; use re_sdk_types::components; use re_view::DataResultQuery as _; @@ -32,7 +33,10 @@ impl VisualizerSystem for TextDocumentSystem { &self, _app_options: &re_viewer_context::AppOptions, ) -> VisualizerQueryInfo { - VisualizerQueryInfo::from_archetype::() + VisualizerQueryInfo::single_required_component::( + &TextDocument::descriptor_text(), + &TextDocument::all_components(), + ) } fn execute( diff --git a/crates/viewer/re_view_text_log/src/visualizer_system.rs b/crates/viewer/re_view_text_log/src/visualizer_system.rs index 1d1b45d5693c..876191834a9e 100644 --- a/crates/viewer/re_view_text_log/src/visualizer_system.rs +++ b/crates/viewer/re_view_text_log/src/visualizer_system.rs @@ -39,7 +39,10 @@ impl VisualizerSystem for TextLogSystem { &self, _app_options: &re_viewer_context::AppOptions, ) -> VisualizerQueryInfo { - VisualizerQueryInfo::from_archetype::() + VisualizerQueryInfo::single_required_component::( + &TextLog::descriptor_text(), + &TextLog::all_components(), + ) } fn execute( diff --git a/crates/viewer/re_view_time_series/src/line_visualizer_system.rs b/crates/viewer/re_view_time_series/src/line_visualizer_system.rs index c5c9d383b0d9..42a142898a27 100644 --- a/crates/viewer/re_view_time_series/src/line_visualizer_system.rs +++ b/crates/viewer/re_view_time_series/src/line_visualizer_system.rs @@ -2,13 +2,12 @@ use itertools::Itertools as _; use rayon::prelude::*; use re_chunk_store::{LatestAtQuery, RangeQuery, RowId}; use re_log_types::{EntityPath, TimeInt}; -use re_sdk_types::components::{AggregationPolicy, InterpolationMode, StrokeWidth}; +use re_sdk_types::components::{self, AggregationPolicy, InterpolationMode, StrokeWidth}; use re_sdk_types::{Archetype as _, archetypes}; -use re_sdk_types::{Component as _, components}; use re_view::range_with_blueprint_resolved_data; use re_viewer_context::external::re_entity_db::InstancePath; use re_viewer_context::{ - AnyPhysicalDatatypeRequirement, IdentifiedViewSystem, ViewContext, ViewQuery, + IdentifiedViewSystem, SingleRequiredComponentConstraint, ViewContext, ViewQuery, ViewSystemExecutionError, VisualizerExecutionOutput, VisualizerQueryInfo, VisualizerReportSeverity, VisualizerSystem, typed_fallback_for, }; @@ -38,12 +37,11 @@ impl VisualizerSystem for SeriesLinesSystem { ) -> VisualizerQueryInfo { VisualizerQueryInfo { relevant_archetype: archetypes::SeriesLines::name().into(), - required: AnyPhysicalDatatypeRequirement { - target_component: archetypes::Scalars::descriptor_scalars().component, - semantic_type: components::Scalar::name(), - physical_types: util::series_supported_datatypes().into_iter().collect(), - allow_static_data: false, - } + constraints: SingleRequiredComponentConstraint::new::( + &archetypes::Scalars::descriptor_scalars(), + ) + .with_additional_physical_types(util::series_supported_datatypes()) + .with_allow_static_data(false) .into(), queried: archetypes::Scalars::all_components() diff --git a/crates/viewer/re_view_time_series/src/point_visualizer_system.rs b/crates/viewer/re_view_time_series/src/point_visualizer_system.rs index 7298ee734d5e..3b07eaa06e79 100644 --- a/crates/viewer/re_view_time_series/src/point_visualizer_system.rs +++ b/crates/viewer/re_view_time_series/src/point_visualizer_system.rs @@ -2,11 +2,11 @@ use itertools::Itertools as _; use rayon::prelude::*; use re_sdk_types::archetypes::SeriesPoints; use re_sdk_types::components::{self, MarkerShape, MarkerSize}; -use re_sdk_types::{Archetype as _, Component as _, Loggable as _, archetypes}; +use re_sdk_types::{Archetype as _, Loggable as _, archetypes}; use re_view::{clamped_or_nothing, range_with_blueprint_resolved_data}; use re_viewer_context::external::re_entity_db::InstancePath; use re_viewer_context::{ - AnyPhysicalDatatypeRequirement, IdentifiedViewSystem, ViewContext, ViewQuery, + IdentifiedViewSystem, SingleRequiredComponentConstraint, ViewContext, ViewQuery, ViewSystemExecutionError, VisualizerExecutionOutput, VisualizerQueryInfo, VisualizerReportSeverity, VisualizerSystem, typed_fallback_for, }; @@ -39,12 +39,11 @@ impl VisualizerSystem for SeriesPointsSystem { ) -> VisualizerQueryInfo { VisualizerQueryInfo { relevant_archetype: archetypes::SeriesPoints::name().into(), - required: AnyPhysicalDatatypeRequirement { - target_component: archetypes::Scalars::descriptor_scalars().component, - semantic_type: components::Scalar::name(), - physical_types: util::series_supported_datatypes().into_iter().collect(), - allow_static_data: false, - } + constraints: SingleRequiredComponentConstraint::new::( + &archetypes::Scalars::descriptor_scalars(), + ) + .with_additional_physical_types(util::series_supported_datatypes()) + .with_allow_static_data(false) .into(), queried: archetypes::Scalars::all_components() .iter() diff --git a/crates/viewer/re_view_time_series/src/view_class.rs b/crates/viewer/re_view_time_series/src/view_class.rs index e75ba9956b5d..2d0fc0a2f06f 100644 --- a/crates/viewer/re_view_time_series/src/view_class.rs +++ b/crates/viewer/re_view_time_series/src/view_class.rs @@ -21,11 +21,11 @@ use re_view::view_property_ui; use re_viewer_context::{ BlueprintContext as _, DataResultInteractionAddress, DatatypeMatch, IdentifiedViewSystem as _, IndicatedEntities, PerVisualizerType, PerVisualizerTypeInViewClass, QueryRange, - RecommendedMappings, RecommendedView, RecommendedVisualizers, SystemExecutionOutput, - TimeControlCommand, ViewClass, ViewClassExt as _, ViewClassRegistryError, ViewHighlights, - ViewId, ViewQuery, ViewSpawnHeuristics, ViewState, ViewStateExt as _, ViewSystemExecutionError, - ViewSystemIdentifier, ViewerContext, VisualizableEntities, VisualizableReason, - VisualizerComponentSource, + RecommendedMappings, RecommendedView, RecommendedVisualizers, SingleRequiredComponentMatch, + SystemExecutionOutput, TimeControlCommand, ViewClass, ViewClassExt as _, + ViewClassRegistryError, ViewHighlights, ViewId, ViewQuery, ViewSpawnHeuristics, ViewState, + ViewStateExt as _, ViewSystemExecutionError, ViewSystemIdentifier, ViewerContext, + VisualizableEntities, VisualizableReason, VisualizerComponentSource, }; use re_viewport_blueprint::ViewProperty; use smallvec::SmallVec; @@ -350,10 +350,12 @@ impl ViewClass for TimeSeriesView { // For the "add visualizer" menu, offer a SeriesLine for every possible scalar mapping. // Unlike `recommended_visualizers_for_entity` / `all_scalar_mappings`, we don't filter // by indication or recommended datatypes — we show everything that could be visualized. - let VisualizableReason::DatatypeMatchAny { - target_component: _, - matches, - } = visualizable_entities?.get(&data_result.entity_path)? + let VisualizableReason::SingleRequiredComponentMatch( + SingleRequiredComponentMatch { + target_component: _, + matches, + }, + ) = visualizable_entities?.get(&data_result.entity_path)? else { return None; }; @@ -956,13 +958,10 @@ const RECOMMENDED_DATATYPES: &[DataType] = fn all_scalar_mappings( reason: &VisualizableReason, ) -> impl Iterator { - let re_viewer_context::VisualizableReason::DatatypeMatchAny { - target_component: _, - matches, - } = reason - else { + let re_viewer_context::VisualizableReason::SingleRequiredComponentMatch(m) = reason else { return Either::Left(std::iter::empty()); }; + let matches = &m.matches; let target = Scalars::descriptor_scalars(); @@ -1477,6 +1476,7 @@ pub fn make_range_sane(y_range: Range1D) -> Range1D { #[cfg(test)] mod tests { use super::*; + use re_viewer_context::SingleRequiredComponentMatch; #[test] fn test_help_view() { @@ -1504,7 +1504,7 @@ mod tests { let entity_path = EntityPath::from("sensor/data"); let viz = build_visualizable_entities( &entity_path, - VisualizableReason::DatatypeMatchAny { + VisualizableReason::SingleRequiredComponentMatch(SingleRequiredComponentMatch { target_component: Scalars::descriptor_scalars().component, matches: std::iter::once(( Scalars::descriptor_scalars().component, @@ -1515,7 +1515,7 @@ mod tests { }, )) .collect(), - }, + }), ); let indicated = PerVisualizerType::default(); let result = @@ -1530,7 +1530,7 @@ mod tests { let entity_path = EntityPath::from("sensor/data"); let viz = build_visualizable_entities( &entity_path, - VisualizableReason::DatatypeMatchAny { + VisualizableReason::SingleRequiredComponentMatch(SingleRequiredComponentMatch { target_component: Scalars::descriptor_scalars().component, matches: std::iter::once(( Scalars::descriptor_scalars().component, @@ -1540,7 +1540,7 @@ mod tests { }, )) .collect(), - }, + }), ); let indicated = PerVisualizerType::default(); let result = diff --git a/crates/viewer/re_viewer_context/Cargo.toml b/crates/viewer/re_viewer_context/Cargo.toml index 4c771be1c33f..0cb1ea9272a4 100644 --- a/crates/viewer/re_viewer_context/Cargo.toml +++ b/crates/viewer/re_viewer_context/Cargo.toml @@ -54,7 +54,6 @@ re_video = { workspace = true, features = ["serde"] } ahash.workspace = true anyhow.workspace = true arrow.workspace = true -bit-vec.workspace = true bitflags.workspace = true bytemuck.workspace = true camino.workspace = true diff --git a/crates/viewer/re_viewer_context/src/lib.rs b/crates/viewer/re_viewer_context/src/lib.rs index 387a640a6052..2daa54604d5a 100644 --- a/crates/viewer/re_viewer_context/src/lib.rs +++ b/crates/viewer/re_viewer_context/src/lib.rs @@ -110,8 +110,9 @@ pub use self::time_control::{ TimeControlResponse, TimeControlUpdateParams, TimeView, time_panel_blueprint_entity_path, }; pub use self::typed_entity_collections::{ - DatatypeMatch, IndicatedEntities, PerVisualizerInstruction, PerVisualizerType, - PerVisualizerTypeInViewClass, VisualizableEntities, VisualizableReason, + BufferAndFormatMatch, DatatypeMatch, IndicatedEntities, PerVisualizerInstruction, + PerVisualizerType, PerVisualizerTypeInViewClass, SingleRequiredComponentMatch, + VisualizableEntities, VisualizableReason, }; pub use self::undo::BlueprintUndoState; pub use self::utils::{ @@ -119,18 +120,18 @@ pub use self::utils::{ video_timestamp_component_to_video_time, }; pub use self::view::{ - AnyPhysicalDatatypeRequirement, DataResult, IdentifiedViewSystem, OptionalViewEntityHighlight, + BufferAndFormatConstraint, DataResult, IdentifiedViewSystem, OptionalViewEntityHighlight, PerSystemEntities, RecommendedMappings, RecommendedView, RecommendedVisualizers, - RequiredComponents, SystemExecutionOutput, ViewClass, ViewClassExt, ViewClassLayoutPriority, - ViewClassPlaceholder, ViewClassRegistry, ViewClassRegistryError, ViewContext, - ViewContextCollection, ViewContextSystem, ViewContextSystemOncePerFrameResult, + SingleRequiredComponentConstraint, SystemExecutionOutput, ViewClass, ViewClassExt, + ViewClassLayoutPriority, ViewClassPlaceholder, ViewClassRegistry, ViewClassRegistryError, + ViewContext, ViewContextCollection, ViewContextSystem, ViewContextSystemOncePerFrameResult, ViewEntityHighlight, ViewHighlights, ViewOutlineMasks, ViewQuery, ViewSpawnHeuristics, ViewState, ViewStateExt, ViewStates, ViewSystemExecutionError, ViewSystemIdentifier, - ViewSystemRegistrator, ViewSystemState, VisualizerCollection, VisualizerComponentMappings, - VisualizerComponentSource, VisualizerExecutionOutput, VisualizerInstruction, - VisualizerInstructionReport, VisualizerInstructionsPerType, VisualizerQueryInfo, - VisualizerReportContext, VisualizerReportSeverity, VisualizerSystem, VisualizerTypeReport, - VisualizerViewReport, VisualizersSectionOutput, VisualizersSectionUi, + ViewSystemRegistrator, ViewSystemState, VisualizabilityConstraints, VisualizerCollection, + VisualizerComponentMappings, VisualizerComponentSource, VisualizerExecutionOutput, + VisualizerInstruction, VisualizerInstructionReport, VisualizerInstructionsPerType, + VisualizerQueryInfo, VisualizerReportContext, VisualizerReportSeverity, VisualizerSystem, + VisualizerTypeReport, VisualizerViewReport, VisualizersSectionOutput, VisualizersSectionUi, }; pub use self::viewer_context::ViewerContext; pub use self::visitor_flow_control::VisitorControlFlow; // Historical reasons diff --git a/crates/viewer/re_viewer_context/src/typed_entity_collections.rs b/crates/viewer/re_viewer_context/src/typed_entity_collections.rs index c720b3a71191..fc0ef5f0ed56 100644 --- a/crates/viewer/re_viewer_context/src/typed_entity_collections.rs +++ b/crates/viewer/re_viewer_context/src/typed_entity_collections.rs @@ -10,7 +10,7 @@ use re_types_core::ViewClassIdentifier; use crate::ViewSystemIdentifier; -/// Types of matches when matching [`crate::RequiredComponents::AnyPhysicalDatatype`]. +/// Types of matches when matching [`crate::VisualizabilityConstraints::SingleRequiredComponent`]. #[derive(Clone, Debug)] pub enum DatatypeMatch { /// Only the physical datatype was matched, but semantics aren't the native ones. @@ -58,39 +58,80 @@ impl DatatypeMatch { } } +/// [`crate::VisualizabilityConstraints::SingleRequiredComponent`] matched for this entity with the given components. +#[derive(Clone, Debug)] +pub struct SingleRequiredComponentMatch { + /// The component that needs to be mapped to one of the matches. + pub target_component: ComponentIdentifier, + + /// Matches that the target component should map to. + /// + /// Guaranteed to have at least one entry. + pub matches: IntMap, +} + +/// [`crate::VisualizabilityConstraints::BufferAndFormat`] matched for this entity. +/// +/// Both a buffer component (matched by arrow datatype) and a format component +/// (matched by arrow datatype AND semantic type) were found on the entity. +#[derive(Clone, Debug)] +pub struct BufferAndFormatMatch { + /// The buffer slot on the visualizer that needs to be mapped. + pub buffer_target: ComponentIdentifier, + + /// The format slot on the visualizer that needs to be mapped. + pub format_target: ComponentIdentifier, + + /// All entity components whose arrow datatype matched the buffer's expected type. + /// + /// Guaranteed to have at least one entry. + pub buffer_matches: IntMap, + + /// The entity components that matched the format (by arrow datatype AND semantic type). + /// + /// Guaranteed to have at least one entry. + pub format_matches: IntSet, +} + /// Describes why a given entity was marked as visualizable. #[derive(Clone, Debug)] pub enum VisualizableReason { /// The entity is visualizable because all entities are visualizable for this type. Always, - /// [`crate::RequiredComponents::AllComponents`] matched for this entity. - ExactMatchAll, - - /// [`crate::RequiredComponents::AnyComponent`] matched for this entity. + /// [`crate::VisualizabilityConstraints::AnyBuiltinComponent`] matched for this entity. ExactMatchAny, - /// [`crate::RequiredComponents::AnyPhysicalDatatype`] matched for this entity with the given components. - DatatypeMatchAny { - /// The component that needs to be mapped to one of the matches. - target_component: ComponentIdentifier, + /// See [`SingleRequiredComponentMatch`]. + SingleRequiredComponentMatch(SingleRequiredComponentMatch), - /// Matches that the target component should map to. - /// - /// Guaranteed to have at least one entry. - matches: IntMap, - }, + /// See [`BufferAndFormatMatch`]. + BufferAndFormatMatch(BufferAndFormatMatch), } impl VisualizableReason { /// Returns true if this match reason is a perfect match for the given component identifier. pub fn full_native_match(&self, component_identifier: ComponentIdentifier) -> bool { match self { - Self::Always | Self::ExactMatchAll | Self::ExactMatchAny => true, - Self::DatatypeMatchAny { matches, .. } => matches + Self::Always | Self::ExactMatchAny => true, + + Self::SingleRequiredComponentMatch(m) => m + .matches .get(&component_identifier) .map(|info| matches!(info, DatatypeMatch::NativeSemantics { .. })) .unwrap_or(false), + + Self::BufferAndFormatMatch(m) => { + // Format is always native by construction (semantic match required). + if m.format_matches.contains(&component_identifier) { + return true; + } + // Check if the buffer has a native semantic match. + m.buffer_matches + .get(&component_identifier) + .map(|info| matches!(info, DatatypeMatch::NativeSemantics { .. })) + .unwrap_or(false) + } } } } diff --git a/crates/viewer/re_viewer_context/src/view/mod.rs b/crates/viewer/re_viewer_context/src/view/mod.rs index 59f699ba33ad..52b558b2062f 100644 --- a/crates/viewer/re_viewer_context/src/view/mod.rs +++ b/crates/viewer/re_viewer_context/src/view/mod.rs @@ -15,6 +15,7 @@ mod view_context; mod view_context_system; mod view_query; mod view_states; +mod visualizability_constraints; mod visualizer_entity_subscriber; mod visualizer_system; @@ -41,10 +42,12 @@ pub use view_query::{ VisualizerComponentSource, VisualizerInstruction, VisualizerInstructionsPerType, }; pub use view_states::ViewStates; +pub use visualizability_constraints::{ + BufferAndFormatConstraint, SingleRequiredComponentConstraint, VisualizabilityConstraints, +}; pub use visualizer_system::{ - AnyPhysicalDatatypeRequirement, RequiredComponents, VisualizerCollection, - VisualizerExecutionOutput, VisualizerInstructionReport, VisualizerQueryInfo, - VisualizerReportContext, VisualizerReportSeverity, VisualizerSystem, + VisualizerCollection, VisualizerExecutionOutput, VisualizerInstructionReport, + VisualizerQueryInfo, VisualizerReportContext, VisualizerReportSeverity, VisualizerSystem, }; // --------------------------------------------------------------------------- diff --git a/crates/viewer/re_viewer_context/src/view/visualizability_constraints.rs b/crates/viewer/re_viewer_context/src/view/visualizability_constraints.rs new file mode 100644 index 000000000000..f539beb45821 --- /dev/null +++ b/crates/viewer/re_viewer_context/src/view/visualizability_constraints.rs @@ -0,0 +1,329 @@ +use re_chunk::{ComponentIdentifier, ComponentType}; +use re_sdk_types::{ComponentDescriptor, ComponentSet}; + +use crate::typed_entity_collections::DatatypeMatch; + +pub type DatatypeSet = std::collections::BTreeSet; + +/// A visualizability specification that relies on a single required component with a known semantic type and a set of supported physical types. +/// +/// A visualizer with this constraint is visualizable iff there's at least one component on the entity +/// that matches one of the given physical types. +/// +/// We additionally store the `target_component` in order to know which component on the visualizer is the required one. +/// The `semantic_type` furthermore informs heuristics/recommendations for what Rerun type is a good fit. +/// +/// If either side of the match is a known builtin enum, a semantic match is required +/// (plain physical type overlap like `UInt8` is not sufficient). +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct SingleRequiredComponentConstraint { + /// The required component that this requirement is targeting. + target_component: ComponentIdentifier, + + /// The semantic type the visualizer is working with. + /// + /// Matches with the semantic type are generally preferred. + /// For builtin enum types, a semantic match is **required**. + semantic_type: ComponentType, + + /// All supported physical Arrow data types. + /// + /// Has to contain the physical data type that is covered by the Rerun semantic type. + physical_types: DatatypeSet, + + /// If false, ignores all static components. + /// + /// This is useful if you rely on ranges queries as done by the time series view. + allow_static_data: bool, +} + +impl From for VisualizabilityConstraints { + fn from(req: SingleRequiredComponentConstraint) -> Self { + Self::SingleRequiredComponent(req) + } +} + +impl SingleRequiredComponentConstraint { + pub fn new( + target_component_descriptor: &ComponentDescriptor, + ) -> Self { + re_log::debug_assert_eq!( + target_component_descriptor.component_type, + Some(C::name()), + "Component type doesn't match target descriptor's type.", + ); + + Self { + target_component: target_component_descriptor.component, + semantic_type: C::name(), + physical_types: std::iter::once(C::arrow_datatype()).collect(), + allow_static_data: true, + } + } + + /// Adds additional physical types to the constraint. + pub fn with_additional_physical_types( + mut self, + additional_types: impl IntoIterator, + ) -> Self { + self.physical_types.extend(additional_types); + self + } + + /// Sets whether static-only components should be considered. + pub fn with_allow_static_data(mut self, allow: bool) -> Self { + self.allow_static_data = allow; + self + } + + /// The required component that this requirement is targeting. + pub fn target_component(&self) -> ComponentIdentifier { + self.target_component + } + + /// Whether static-only components should be considered. + /// + /// If false, ignores all static components when evaluating this constraint. + pub fn allow_static_data(&self) -> bool { + self.allow_static_data + } + + /// All supported physical Arrow data types. + pub fn physical_types(&self) -> &DatatypeSet { + &self.physical_types + } + + /// Check if an incoming component's Arrow datatype matches this constraint. + /// + /// Returns `Some(DatatypeMatch)` when the component satisfies the constraint, `None` otherwise. + /// + /// If either side is a known builtin enum type, a semantic match is required. + pub(crate) fn check_datatype_match( + &self, + known_enum_types: &nohash_hasher::IntSet, + incoming_arrow_datatype: &arrow::datatypes::DataType, + incoming_component_type: Option, + incoming_component: ComponentIdentifier, + ) -> Option { + use re_arrow_combinators::extract_nested_fields; + + let is_physical_match = self.physical_types.contains(incoming_arrow_datatype); + let is_semantic_match = incoming_component_type == Some(self.semantic_type); + + // Builtin enum types should only match via native semantics, never via physical datatype alone. + // This applies in both directions: + // - Incoming data that is an enum shouldn't match a non-enum visualizer physically + // (e.g. `FillMode` (UInt8) shouldn't be picked up by a visualizer that accepts UInt8). + // - A visualizer that requires an enum type shouldn't accept non-enum data physically + // (e.g. a `FillMode` visualizer shouldn't pick up arbitrary UInt8 data). + let incoming_is_enum = + incoming_component_type.is_some_and(|ct| known_enum_types.contains(&ct)); + let constraint_is_enum = known_enum_types.contains(&self.semantic_type); + if (incoming_is_enum || constraint_is_enum) && !is_semantic_match { + return None; + } + + match (is_physical_match, is_semantic_match) { + (false, false) => { + // No direct match - try nested field access + extract_nested_fields(incoming_arrow_datatype, |dt| { + self.physical_types.contains(dt) + }) + .map(|selectors| DatatypeMatch::PhysicalDatatypeOnly { + arrow_datatype: incoming_arrow_datatype.clone(), + component_type: incoming_component_type, + selectors: selectors.into(), + }) + } + + (true, false) => Some(DatatypeMatch::PhysicalDatatypeOnly { + arrow_datatype: incoming_arrow_datatype.clone(), + component_type: incoming_component_type, + selectors: Vec::new(), + }), + + (true, true) => Some(DatatypeMatch::NativeSemantics { + arrow_datatype: incoming_arrow_datatype.clone(), + component_type: incoming_component_type, + }), + + (false, true) => { + re_log::warn_once!( + "Component {incoming_component:?} matched semantic type {:?} but none of the expected physical arrow types {incoming_arrow_datatype:?} for this semantic type.", + self.semantic_type, + ); + None + } + } + } +} + +/// A visualizability constraint for image-like visualizers that require both a buffer and a format component. +/// +/// - **Buffer**: always matched against `Blob`'s arrow datatype (see [`Self::buffer_arrow_datatype`]). +/// Semantic type is recorded as a hint for heuristics but is not required for matching. +/// I.e. it behaves exactly like the required component in [`SingleRequiredComponentConstraint`] (but with a fixed physical type). +/// - **Format**: matched by both arrow datatype AND semantic type (exact match required for both). +/// +/// Buffer and format may arrive on an entity at different times (different chunks/events). +/// The subscriber tracks partial progress and promotes the entity to visualizable once both are satisfied. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct BufferAndFormatConstraint { + /// The buffer component slot on the visualizer. + buffer_target: ComponentIdentifier, + + /// The semantic type of the buffer component (hint only — not required for matching). + buffer_semantic_type: ComponentType, + + /// The format component slot on the visualizer. + format_target: ComponentIdentifier, + + /// The semantic type the format component must have (exact match required). + format_semantic_type: ComponentType, + + /// The arrow datatype the format component must have (exact match required). + format_arrow_datatype: arrow::datatypes::DataType, +} + +impl BufferAndFormatConstraint { + /// Creates a new buffer-and-format constraint. + pub fn new( + buffer_descriptor: &ComponentDescriptor, + format_descriptor: &ComponentDescriptor, + ) -> Self { + re_log::debug_assert_eq!( + buffer_descriptor.component_type, + Some(Buffer::name()), + "Buffer component type doesn't match descriptor's type.", + ); + re_log::debug_assert_eq!( + format_descriptor.component_type, + Some(Format::name()), + "Format component type doesn't match descriptor's type.", + ); + + Self::new_with_type( + buffer_descriptor.component, + Buffer::name(), + format_descriptor.component, + Format::name(), + Format::arrow_datatype(), + ) + } + + /// Creates a new buffer-and-format constraint from raw identifiers and arrow datatypes. + /// + /// Unlike [`Self::new`], this does not require concrete component types and is useful for tests. + pub fn new_with_type( + buffer_target: ComponentIdentifier, + buffer_semantic_type: ComponentType, + format_target: ComponentIdentifier, + format_semantic_type: ComponentType, + format_arrow_datatype: arrow::datatypes::DataType, + ) -> Self { + Self { + buffer_target, + buffer_semantic_type, + format_target, + format_semantic_type, + format_arrow_datatype, + } + } + + /// The arrow datatype used to match the buffer component. + /// + /// This is always [`re_sdk_types::datatypes::Blob`]'s arrow datatype, since all image-like + /// buffers are opaque byte blobs regardless of the specific image archetype. + // TODO(andreas): It would be great if we could support `BinaryArray` as well! + pub fn buffer_arrow_datatype() -> arrow::datatypes::DataType { + ::arrow_datatype() + } + + /// The buffer component slot on the visualizer. + pub fn buffer_target(&self) -> ComponentIdentifier { + self.buffer_target + } + + /// The format component slot on the visualizer. + pub fn format_target(&self) -> ComponentIdentifier { + self.format_target + } + + /// Check if an incoming component matches the buffer side of this constraint. + /// + /// Matches by arrow datatype (direct or via nested field extraction). + /// Semantic type match is recorded but not required. + pub(crate) fn check_buffer_match( + &self, + incoming_arrow_datatype: &arrow::datatypes::DataType, + descriptor: &re_sdk_types::ComponentDescriptor, + ) -> Option { + use re_arrow_combinators::extract_nested_fields; + + let is_physical_match = *incoming_arrow_datatype == Self::buffer_arrow_datatype(); + let is_semantic = descriptor.component_type == Some(self.buffer_semantic_type); + + match (is_physical_match, is_semantic) { + (true, true) => Some(DatatypeMatch::NativeSemantics { + arrow_datatype: incoming_arrow_datatype.clone(), + component_type: descriptor.component_type, + }), + (true, false) => Some(DatatypeMatch::PhysicalDatatypeOnly { + arrow_datatype: incoming_arrow_datatype.clone(), + component_type: descriptor.component_type, + selectors: Vec::new(), + }), + (false, _) => { + // No direct match — try nested field access. + extract_nested_fields(incoming_arrow_datatype, |dt| { + *dt == Self::buffer_arrow_datatype() + }) + .map(|selectors| DatatypeMatch::PhysicalDatatypeOnly { + arrow_datatype: incoming_arrow_datatype.clone(), + component_type: descriptor.component_type, + selectors: selectors.into(), + }) + } + } + } + + /// Check if an incoming component matches the format side of this constraint. + /// + /// Requires both arrow datatype AND semantic type to match. + pub(crate) fn check_format_match( + &self, + incoming_arrow_datatype: &arrow::datatypes::DataType, + descriptor: &re_sdk_types::ComponentDescriptor, + ) -> bool { + *incoming_arrow_datatype == self.format_arrow_datatype + && descriptor.component_type == Some(self.format_semantic_type) + } +} + +impl From for VisualizabilityConstraints { + fn from(req: BufferAndFormatConstraint) -> Self { + Self::BufferAndFormat(req) + } +} + +/// Specifies how component requirements should be evaluated for visualizer entity matching. +/// Only on a successful match with an entity will the visualizer even be considered for that entity. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum VisualizabilityConstraints { + /// No component requirements - all entities are visualizable. + None, + + /// Entity must have _any one_ of these components. + AnyBuiltinComponent(ComponentSet), + + /// Entity must have _any one_ of these physical Arrow data types. + /// + /// For instance, we may not put views into the "recommended" section or visualizer entities proactively unless they support the native type. + SingleRequiredComponent(SingleRequiredComponentConstraint), + + /// Entity must have both a buffer component (matched by arrow datatype) and a format + /// component (matched by arrow datatype AND semantic type). + /// + /// See [`BufferAndFormatConstraint`] for details. + BufferAndFormat(BufferAndFormatConstraint), +} diff --git a/crates/viewer/re_viewer_context/src/view/visualizer_entity_subscriber.rs b/crates/viewer/re_viewer_context/src/view/visualizer_entity_subscriber.rs index 77d358eee511..c19bb75aa1d8 100644 --- a/crates/viewer/re_viewer_context/src/view/visualizer_entity_subscriber.rs +++ b/crates/viewer/re_viewer_context/src/view/visualizer_entity_subscriber.rs @@ -2,20 +2,20 @@ use std::collections::hash_map::Entry; use std::sync::Arc; use ahash::HashMap; -use bit_vec::BitVec; -use nohash_hasher::{IntMap, IntSet}; -use re_arrow_combinators::extract_nested_fields; +use nohash_hasher::IntSet; use re_chunk::{ArchetypeName, ComponentIdentifier, ComponentType}; use re_chunk_store::{ChunkStoreEvent, ChunkStoreSubscriber}; -use re_log::{debug_assert, debug_panic}; -use re_log_types::{EntityPath, EntityPathHash, StoreId}; -use re_sdk_types::ComponentSet; +use re_log::debug_panic; +use re_log_types::{EntityPath, StoreId}; -use crate::typed_entity_collections::DatatypeMatch; -use crate::view::visualizer_system::{AnyPhysicalDatatypeRequirement, DatatypeSet}; +use nohash_hasher::IntMap; + +use crate::typed_entity_collections::{ + BufferAndFormatMatch, DatatypeMatch, SingleRequiredComponentMatch, VisualizableReason, +}; use crate::{ - IdentifiedViewSystem, IndicatedEntities, RequiredComponents, ViewSystemIdentifier, - VisualizableEntities, VisualizerSystem, typed_entity_collections::VisualizableReason, + IdentifiedViewSystem, IndicatedEntities, ViewSystemIdentifier, VisualizabilityConstraints, + VisualizableEntities, VisualizerSystem, }; /// A store subscriber that keep track which entities in a store can be @@ -39,12 +39,12 @@ pub struct VisualizerEntitySubscriber { /// The mode for checking component requirements. /// - /// See [`crate::VisualizerQueryInfo::required`] - requirement: Requirement, + /// See [`crate::VisualizerQueryInfo::constraints`] + constraints: VisualizabilityConstraints, /// Lists all known builtin enums components. /// - /// Used by [`Requirement::AnyPhysicalDatatype`] to skip physical-only matches + /// Used by [`VisualizabilityConstraints::SingleRequiredComponent`] to skip physical-only matches /// for enum types (which should only match via native semantics). // TODO(andreas): It would be great if we could just always access the latest reflection data, but this is really hard to pipe through to a store subscriber. known_builtin_enum_components: Arc>, @@ -52,46 +52,17 @@ pub struct VisualizerEntitySubscriber { per_store_mapping: HashMap, } -#[derive(Debug, Clone, PartialEq, Eq)] -struct AllComponentsRequirement { - /// Assigns each required component an index. - required_components_indices: IntMap, -} - -#[derive(Debug, Clone, PartialEq, Eq)] -struct AnyComponentRequirement { - relevant_components: ComponentSet, -} - -/// Internal representation of how to check required components. +/// Per-entity state for a [`VisualizabilityConstraints::BufferAndFormat`] constraint. /// -/// Corresponds to [`RequiredComponents`]. -#[derive(Debug, Clone, PartialEq, Eq)] -enum Requirement { - /// All entities match. - None, - - /// Entity must have all tracked components. - AllComponents(AllComponentsRequirement), - - /// Entity must have at least one component. - AnyComponent(AnyComponentRequirement), - - /// Entity must have at least one compatible data type. - AnyPhysicalDatatype(AnyPhysicalDatatypeRequirement), +/// Buffer and format components may arrive in separate chunk store events, so we keep accumulating them here. +#[derive(Default)] +struct BufferAndFormatEntityState { + all_buffer_matches: IntMap, + all_formats_matches: IntSet, } #[derive(Default)] struct VisualizerEntityMapping { - /// For each entity, which of the required components are present. - /// - /// Last bit is used for the data-based-visualizability filter. - /// - /// In order of `required_components`. - // TODO(andreas): We could just limit the number of required components to 32 or 64 and - // then use a single u32/u64 as a bitmap. - required_component_and_filter_bitmap_per_entity: IntMap, - /// Which entities the visualizer can be applied to. visualizable_entities: VisualizableEntities, @@ -100,36 +71,83 @@ struct VisualizerEntityMapping { /// Special case: /// If the visualizer has no relevant archetypes, this list will contain all entities in the store. indicated_entities: IndicatedEntities, -} -impl From for AllComponentsRequirement { - fn from(value: ComponentSet) -> Self { - Self { - required_components_indices: value - .into_iter() - .enumerate() - .map(|(i, name)| (name, i)) - .collect(), - } - } + /// Per-entity state for [`VisualizabilityConstraints::BufferAndFormat`] constraints. + /// + /// Only populated when the requirement is [`VisualizabilityConstraints::BufferAndFormat`]. + buffer_and_format_state: IntMap, } -impl From for AnyComponentRequirement { - fn from(value: ComponentSet) -> Self { - Self { - relevant_components: value, - } - } -} +impl VisualizerEntityMapping { + /// Adds a visualizability reason for the given entity and combines it with an existing one if any. + /// + /// Changing the type of reason is a usage error and will cause a debug panic and is ignored on release builds. + fn add_visualizability_reason( + &mut self, + entity_path: &EntityPath, + visualizer: &ViewSystemIdentifier, + new_reason: VisualizableReason, + ) { + match self.visualizable_entities.0.entry(entity_path.clone()) { + Entry::Occupied(mut occupied_entry) => { + let debug_panic_for_incompatible_reason = || { + debug_panic!( + "entity {entity_path:?} already marked visualizable for visualizer {visualizer:?} with an incompatible reason", + ); + }; + + match occupied_entry.get_mut() { + VisualizableReason::Always => { + if matches!(new_reason, VisualizableReason::Always) { + // No change, already visualizable for all reasons. + } else { + debug_panic_for_incompatible_reason(); + } + } + + VisualizableReason::ExactMatchAny => { + if matches!(new_reason, VisualizableReason::ExactMatchAny) { + // No change, already visualizable for any builtin component. + } else { + debug_panic_for_incompatible_reason(); + } + } + + VisualizableReason::SingleRequiredComponentMatch(matches) => { + if let VisualizableReason::SingleRequiredComponentMatch(new_match) = + new_reason + { + re_log::debug_assert_eq!( + new_match.target_component, + matches.target_component + ); + matches.matches.extend(new_match.matches); + } else { + debug_panic_for_incompatible_reason(); + } + } + + VisualizableReason::BufferAndFormatMatch(matches) => { + if let VisualizableReason::BufferAndFormatMatch(new_match) = new_reason { + re_log::debug_assert_eq!( + new_match.buffer_target, + matches.buffer_target + ); + re_log::debug_assert_eq!( + new_match.format_target, + matches.format_target + ); + matches.buffer_matches.extend(new_match.buffer_matches); + matches.format_matches.extend(new_match.format_matches); + } else { + debug_panic_for_incompatible_reason(); + } + } + } + } -impl From for Requirement { - fn from(value: RequiredComponents) -> Self { - match value { - RequiredComponents::None => Self::None, - RequiredComponents::AllComponents(components) => Self::AllComponents(components.into()), - RequiredComponents::AnyComponent(components) => Self::AnyComponent(components.into()), - RequiredComponents::AnyPhysicalDatatype(requirement) => { - Self::AnyPhysicalDatatype(requirement) + Entry::Vacant(vacant_entry) => { + vacant_entry.insert(new_reason); } } } @@ -146,7 +164,7 @@ impl VisualizerEntitySubscriber { Self { visualizer: T::identifier(), relevant_archetype: visualizer_query_info.relevant_archetype, - requirement: visualizer_query_info.required.into(), + constraints: visualizer_query_info.constraints, known_builtin_enum_components, per_store_mapping: Default::default(), } @@ -178,7 +196,7 @@ impl VisualizerEntitySubscriber { /// This is the shared core logic between physical chunk additions and virtual manifest additions. fn process_entity_components( relevant_archetype: Option, - requirement: &Requirement, + constraints: &VisualizabilityConstraints, visualizer: &ViewSystemIdentifier, known_enum_types: &IntSet, store_mapping: &mut VisualizerEntityMapping, @@ -201,8 +219,8 @@ fn process_entity_components( } // Check component requirements. - match requirement { - Requirement::None => { + match constraints { + VisualizabilityConstraints::None => { re_log::trace!( "Entity {entity_path:?} in store {store_id:?} may now be visualizable by {visualizer:?} (no requirements)", ); @@ -213,51 +231,7 @@ fn process_entity_components( .insert(entity_path.clone(), VisualizableReason::Always); } - Requirement::AllComponents(AllComponentsRequirement { - required_components_indices, - }) => { - let required_components_bitmap = store_mapping - .required_component_and_filter_bitmap_per_entity - .entry(entity_path.hash()) - .or_insert_with(|| { - // An empty set would mean that all entities will never be "visualizable", - // because `.all()` is always false for an empty set. - debug_assert!( - !required_components_indices.is_empty(), - "[DEBUG ASSERT] encountered empty set of required components for `RequiredComponentMode::All`" - ); - BitVec::from_elem(required_components_indices.len(), false) - }); - - // Early-out: if all required components are already present, we already - // marked this entity as visualizable in a previous event. - if required_components_bitmap.all() { - return; - } - - for c in components { - if let Some(index) = required_components_indices.get(&c.descriptor.component) - && c.has_data - { - required_components_bitmap.set(*index, true); - } - } - - if required_components_bitmap.all() { - re_log::trace!( - "Entity {entity_path:?} in store {store_id:?} may now be visualizable by {visualizer:?}", - ); - - store_mapping - .visualizable_entities - .0 - .insert(entity_path.clone(), VisualizableReason::ExactMatchAll); - } - } - - Requirement::AnyComponent(AnyComponentRequirement { - relevant_components, - }) => { + VisualizabilityConstraints::AnyBuiltinComponent(relevant_components) => { let has_any_component = components .iter() .any(|c| relevant_components.contains(&c.descriptor.component) && c.has_data); @@ -274,16 +248,11 @@ fn process_entity_components( } } - Requirement::AnyPhysicalDatatype(AnyPhysicalDatatypeRequirement { - target_component, - semantic_type, - physical_types, - allow_static_data, - }) => { + VisualizabilityConstraints::SingleRequiredComponent(constraint) => { let mut has_any_datatype = false; for c in components { - if !allow_static_data && c.is_static_only { + if !constraint.allow_static_data() && c.is_static_only { continue; } @@ -291,23 +260,25 @@ fn process_entity_components( continue; }; - if let Some(match_info) = check_datatype_match( + if let Some(match_info) = constraint.check_datatype_match( known_enum_types, arrow_datatype, c.descriptor.component_type, - semantic_type, - physical_types, c.descriptor.component, ) && c.has_data { has_any_datatype = true; - insert_datatype_match( - &mut store_mapping.visualizable_entities, + + store_mapping.add_visualizability_reason( &entity_path, - c.descriptor.component, - *target_component, - match_info, visualizer, + VisualizableReason::SingleRequiredComponentMatch( + SingleRequiredComponentMatch { + target_component: constraint.target_component(), + matches: std::iter::once((c.descriptor.component, match_info)) + .collect(), + }, + ), ); } } @@ -318,93 +289,53 @@ fn process_entity_components( ); } } - } -} -/// Check if an Arrow datatype matches the physical/semantic requirements. -fn check_datatype_match( - known_enum_types: &IntSet, - arrow_datatype: &arrow::datatypes::DataType, - component_type: Option, - semantic_type: &ComponentType, - physical_types: &DatatypeSet, - component: ComponentIdentifier, -) -> Option { - let is_physical_match = physical_types.contains(arrow_datatype); - let is_semantic_match = component_type == Some(*semantic_type); - - // Builtin enum types (registered in the reflection) should only - // match via native semantics, never via physical datatype alone. - // This prevents e.g. a `rerun.components.FillMode` (UInt8) from - // being picked up by a visualizer that happens to accept UInt8 data. - let is_known_enum = component_type.is_some_and(|ct| known_enum_types.contains(&ct)); - if is_known_enum && !is_semantic_match { - return None; - } + VisualizabilityConstraints::BufferAndFormat(constraint) => { + for c in components { + if !c.has_data { + continue; + } - match (is_physical_match, is_semantic_match) { - (false, false) => { - // No direct match - try nested field access - extract_nested_fields(arrow_datatype, |dt| physical_types.contains(dt)).map( - |selectors| DatatypeMatch::PhysicalDatatypeOnly { - arrow_datatype: arrow_datatype.clone(), - component_type, - selectors: selectors.into(), - }, - ) - } + let Some(arrow_datatype) = &c.inner_arrow_datatype else { + continue; + }; - (true, false) => Some(DatatypeMatch::PhysicalDatatypeOnly { - arrow_datatype: arrow_datatype.clone(), - component_type, - selectors: Vec::new(), - }), + let buffer_match = constraint.check_buffer_match(arrow_datatype, &c.descriptor); + let is_format_match = constraint.check_format_match(arrow_datatype, &c.descriptor); + if buffer_match.is_none() && !is_format_match { + continue; + } - (true, true) => Some(DatatypeMatch::NativeSemantics { - arrow_datatype: arrow_datatype.clone(), - component_type, - }), + let state = store_mapping + .buffer_and_format_state + .entry(entity_path.clone()) + .or_default(); - (false, true) => { - re_log::warn_once!( - "Component {component:?} matched semantic type {semantic_type:?} but none of the expected physical arrow types {arrow_datatype:?} for this semantic type.", - ); - None - } - } -} + if let Some(buffer_match) = buffer_match { + state + .all_buffer_matches + .insert(c.descriptor.component, buffer_match); + } + if is_format_match { + state.all_formats_matches.insert(c.descriptor.component); + } -/// Insert a datatype match for an entity into the visualizable entities map. -fn insert_datatype_match( - visualizable_entities: &mut VisualizableEntities, - entity_path: &EntityPath, - component: ComponentIdentifier, - target_component: ComponentIdentifier, - match_info: DatatypeMatch, - visualizer: &ViewSystemIdentifier, -) { - match visualizable_entities.0.entry(entity_path.clone()) { - Entry::Occupied(mut occupied_entry) => { - if let VisualizableReason::DatatypeMatchAny { - matches, - target_component: previous_target, - } = occupied_entry.get_mut() - { - re_log::debug_assert_eq!(&target_component, previous_target); - matches.insert(component, match_info); - } else { - debug_panic!( - "entity {entity_path:?} already marked visualizable for visualizer {visualizer:?} with a different reason than `DatatypeMatchAny`", - ); + if !state.all_buffer_matches.is_empty() && !state.all_formats_matches.is_empty() { + let buffer_matches = state.all_buffer_matches.clone(); + let format_components = state.all_formats_matches.clone(); + store_mapping.add_visualizability_reason( + &entity_path, + visualizer, + VisualizableReason::BufferAndFormatMatch(BufferAndFormatMatch { + buffer_target: constraint.buffer_target(), + format_target: constraint.format_target(), + buffer_matches, + format_matches: format_components, + }), + ); + } } } - - Entry::Vacant(vacant_entry) => { - vacant_entry.insert(VisualizableReason::DatatypeMatchAny { - target_component, - matches: std::iter::once((component, match_info)).collect(), - }); - } } } @@ -445,7 +376,7 @@ impl ChunkStoreSubscriber for VisualizerEntitySubscriber { process_entity_components( self.relevant_archetype, - &self.requirement, + &self.constraints, &self.visualizer, &self.known_builtin_enum_components, store_mapping, @@ -457,7 +388,7 @@ impl ChunkStoreSubscriber for VisualizerEntitySubscriber { for meta in virtual_add.chunk_metas() { process_entity_components( self.relevant_archetype, - &self.requirement, + &self.constraints, &self.visualizer, &self.known_builtin_enum_components, store_mapping, @@ -473,3 +404,427 @@ impl ChunkStoreSubscriber for VisualizerEntitySubscriber { } } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::BufferAndFormatConstraint; + use arrow::array::ArrayRef; + use re_chunk::{Chunk, RowId}; + use re_chunk_store::{ + ChunkDirectLineageReport, ChunkStoreDiff, ChunkStoreDiffAddition, ChunkStoreEvent, + ChunkStoreGeneration, + }; + use re_log_types::TimePoint; + use re_sdk_types::ComponentDescriptor; + + const BUFFER_CTYPE: &str = "test.components.Buffer"; + const FORMAT_CTYPE: &str = "test.components.Format"; + + fn test_constraint() -> BufferAndFormatConstraint { + BufferAndFormatConstraint::new_with_type( + "TestArch:buffer".into(), + BUFFER_CTYPE.into(), + "TestArch:format".into(), + FORMAT_CTYPE.into(), + arrow::datatypes::DataType::UInt32, + ) + } + + fn test_store_id() -> StoreId { + StoreId::random(re_log_types::StoreKind::Recording, "test_app") + } + + /// Create a subscriber with a [`VisualizabilityConstraints::BufferAndFormat`] constraint. + fn test_subscriber_with_buffer_and_format_constraint() -> VisualizerEntitySubscriber { + VisualizerEntitySubscriber { + visualizer: "TestVisualizer".into(), + relevant_archetype: None, + constraints: VisualizabilityConstraints::BufferAndFormat(test_constraint()), + known_builtin_enum_components: Arc::new(IntSet::default()), + per_store_mapping: Default::default(), + } + } + + /// Build a `ComponentDescriptor` with the given component identifier and optional semantic type. + fn descriptor(component: &str, component_type: Option<&str>) -> ComponentDescriptor { + ComponentDescriptor { + archetype: None, + component: component.into(), + component_type: component_type.map(Into::into), + } + } + + /// Build a minimal chunk with the given entity path and component columns. + /// + /// Each entry is `(descriptor, arrow_datatype)` — a single-element array of the + /// given type is created as data so that `has_data` is `true`. + fn make_chunk( + entity: &EntityPath, + columns: &[(ComponentDescriptor, arrow::datatypes::DataType)], + ) -> Arc { + let row = columns.iter().map(|(desc, dt)| { + let array: ArrayRef = arrow::array::new_null_array(dt, 1); + (desc.clone(), array) + }); + Arc::new( + Chunk::builder(entity.clone()) + .with_row(RowId::new(), TimePoint::default(), row) + .build() + .expect("failed to build test chunk"), + ) + } + + /// Wrap a chunk into a single `ChunkStoreEvent` (addition). + fn addition_event(store_id: &StoreId, chunk: Arc) -> ChunkStoreEvent { + ChunkStoreEvent { + store_id: store_id.clone(), + store_generation: ChunkStoreGeneration::default(), + event_id: 0, + diff: ChunkStoreDiff::Addition(ChunkStoreDiffAddition { + chunk_before_processing: Arc::clone(&chunk), + chunk_after_processing: chunk, + direct_lineage: ChunkDirectLineageReport::Volatile, + }), + } + } + + /// Assert that the subscriber has marked the entity as visualizable with a `BufferAndFormatMatch`. + /// + /// Returns the match struct for further inspection. + fn expect_buffer_and_format_visualizable<'a>( + subscriber: &'a VisualizerEntitySubscriber, + store_id: &StoreId, + entity: &EntityPath, + ) -> &'a BufferAndFormatMatch { + let entities = subscriber + .visualizable_entities(store_id) + .expect("store should exist"); + let reason = entities.get(entity).expect("entity should be visualizable"); + match reason { + VisualizableReason::BufferAndFormatMatch(m) => m, + other => panic!("expected BufferAndFormatMatch, got {other:?}"), + } + } + + fn assert_not_visualizable( + subscriber: &VisualizerEntitySubscriber, + store_id: &StoreId, + entity: &EntityPath, + ) { + let is_visualizable = subscriber + .visualizable_entities(store_id) + .is_some_and(|e| e.contains_key(entity)); + assert!( + !is_visualizable, + "entity {entity} should NOT be visualizable yet" + ); + } + + // ---- Tests ---- + + #[test] + fn both_buffer_and_format_in_one_event() { + let store_id = test_store_id(); + let entity: EntityPath = "/test/entity".into(); + let mut sub = test_subscriber_with_buffer_and_format_constraint(); + + let chunk = make_chunk( + &entity, + &[ + ( + descriptor("buf", Some(BUFFER_CTYPE)), + BufferAndFormatConstraint::buffer_arrow_datatype(), + ), + ( + descriptor("fmt", Some(FORMAT_CTYPE)), + arrow::datatypes::DataType::UInt32, + ), + ], + ); + + sub.on_events(&[addition_event(&store_id, chunk)]); + + let m = expect_buffer_and_format_visualizable(&sub, &store_id, &entity); + assert_eq!(m.buffer_matches.len(), 1); + assert!(matches!( + m.buffer_matches.get(&"buf".into()), + Some(DatatypeMatch::NativeSemantics { .. }) + )); + assert!(m.format_matches.contains(&ComponentIdentifier::from("fmt"))); + } + + #[test] + fn buffer_and_format_not_at_once() { + let store_id = test_store_id(); + let entity: EntityPath = "/test/entity".into(); + + let buffer_chunk = make_chunk( + &entity, + &[( + descriptor("buf", Some(BUFFER_CTYPE)), + BufferAndFormatConstraint::buffer_arrow_datatype(), + )], + ); + let format_chunk = make_chunk( + &entity, + &[( + descriptor("fmt", Some(FORMAT_CTYPE)), + arrow::datatypes::DataType::UInt32, + )], + ); + + for (first_chunk, second_chunk) in [ + (buffer_chunk.clone(), format_chunk.clone()), + (format_chunk.clone(), buffer_chunk.clone()), + ] { + let mut sub = test_subscriber_with_buffer_and_format_constraint(); + + sub.on_events(&[addition_event(&store_id, first_chunk)]); + assert_not_visualizable(&sub, &store_id, &entity); + + sub.on_events(&[addition_event(&store_id, second_chunk)]); + expect_buffer_and_format_visualizable(&sub, &store_id, &entity); + } + } + + #[test] + fn buffer_physical_only_match() { + let store_id = test_store_id(); + let entity: EntityPath = "/test/entity".into(); + let mut sub = test_subscriber_with_buffer_and_format_constraint(); + + // Buffer has the right arrow type but wrong semantic type → PhysicalDatatypeOnly. + let chunk = make_chunk( + &entity, + &[ + ( + descriptor("buf", Some("other.components.Blob")), + BufferAndFormatConstraint::buffer_arrow_datatype(), + ), + ( + descriptor("fmt", Some(FORMAT_CTYPE)), + arrow::datatypes::DataType::UInt32, + ), + ], + ); + sub.on_events(&[addition_event(&store_id, chunk)]); + + let m = expect_buffer_and_format_visualizable(&sub, &store_id, &entity); + assert!(matches!( + m.buffer_matches.get(&"buf".into()), + Some(DatatypeMatch::PhysicalDatatypeOnly { .. }) + )); + } + + #[test] + fn format_without_semantic_match_is_rejected() { + let store_id = test_store_id(); + let entity: EntityPath = "/test/entity".into(); + let mut sub = test_subscriber_with_buffer_and_format_constraint(); + + // Buffer matches, but format has wrong semantic type. + let chunk = make_chunk( + &entity, + &[ + ( + descriptor("buf", Some(BUFFER_CTYPE)), + BufferAndFormatConstraint::buffer_arrow_datatype(), + ), + ( + descriptor("fmt", Some("wrong.components.Format")), + arrow::datatypes::DataType::UInt32, + ), + ], + ); + sub.on_events(&[addition_event(&store_id, chunk)]); + assert_not_visualizable(&sub, &store_id, &entity); + } + + #[test] + fn wrong_arrow_datatype_rejected() { + let store_id = test_store_id(); + let entity: EntityPath = "/test/entity".into(); + let mut sub = test_subscriber_with_buffer_and_format_constraint(); + + // Neither buffer nor format arrow types match. + let chunk = make_chunk( + &entity, + &[ + ( + descriptor("buf", Some(BUFFER_CTYPE)), + arrow::datatypes::DataType::Float64, + ), + ( + descriptor("fmt", Some(FORMAT_CTYPE)), + arrow::datatypes::DataType::Float64, + ), + ], + ); + sub.on_events(&[addition_event(&store_id, chunk)]); + assert_not_visualizable(&sub, &store_id, &entity); + } + + #[test] + fn multiple_buffer_matches_across_events() { + let store_id = test_store_id(); + let entity: EntityPath = "/test/entity".into(); + let mut sub = test_subscriber_with_buffer_and_format_constraint(); + + // Event 1: first buffer + format. + let chunk1 = make_chunk( + &entity, + &[ + ( + descriptor("buf1", Some(BUFFER_CTYPE)), + BufferAndFormatConstraint::buffer_arrow_datatype(), + ), + ( + descriptor("fmt", Some(FORMAT_CTYPE)), + arrow::datatypes::DataType::UInt32, + ), + ], + ); + sub.on_events(&[addition_event(&store_id, chunk1)]); + expect_buffer_and_format_visualizable(&sub, &store_id, &entity); + + // Event 2: second buffer arrives. + let chunk2 = make_chunk( + &entity, + &[( + descriptor("buf2", Some("other.components.Blob")), + BufferAndFormatConstraint::buffer_arrow_datatype(), + )], + ); + sub.on_events(&[addition_event(&store_id, chunk2)]); + + // Both buffer matches should be visible. + let m = expect_buffer_and_format_visualizable(&sub, &store_id, &entity); + assert_eq!(m.buffer_matches.len(), 2); + assert!(matches!( + m.buffer_matches.get(&"buf1".into()), + Some(DatatypeMatch::NativeSemantics { .. }) + )); + assert!(matches!( + m.buffer_matches.get(&"buf2".into()), + Some(DatatypeMatch::PhysicalDatatypeOnly { .. }) + )); + } + + #[test] + fn multiple_buffers_with_multiple_formats() { + let store_id = test_store_id(); + let entity: EntityPath = "/test/entity".into(); + let mut sub = test_subscriber_with_buffer_and_format_constraint(); + + // Event 1: first buffer + first format. + let chunk1 = make_chunk( + &entity, + &[ + ( + descriptor("buf1", Some(BUFFER_CTYPE)), + BufferAndFormatConstraint::buffer_arrow_datatype(), + ), + ( + descriptor("fmt1", Some(FORMAT_CTYPE)), + arrow::datatypes::DataType::UInt32, + ), + ], + ); + sub.on_events(&[addition_event(&store_id, chunk1)]); + + let m = expect_buffer_and_format_visualizable(&sub, &store_id, &entity); + assert_eq!(m.buffer_matches.len(), 1); + assert_eq!(m.format_matches.len(), 1); + + // Event 2: second buffer + second format. + let chunk2 = make_chunk( + &entity, + &[ + ( + descriptor("buf2", Some("other.components.Blob")), + BufferAndFormatConstraint::buffer_arrow_datatype(), + ), + ( + descriptor("fmt2", Some(FORMAT_CTYPE)), + arrow::datatypes::DataType::UInt32, + ), + ], + ); + sub.on_events(&[addition_event(&store_id, chunk2)]); + + // Both buffers and both formats should be tracked in a single entry. + let m = expect_buffer_and_format_visualizable(&sub, &store_id, &entity); + assert_eq!(m.buffer_matches.len(), 2); + assert!(matches!( + m.buffer_matches.get(&"buf1".into()), + Some(DatatypeMatch::NativeSemantics { .. }) + )); + assert!(matches!( + m.buffer_matches.get(&"buf2".into()), + Some(DatatypeMatch::PhysicalDatatypeOnly { .. }) + )); + assert!( + m.format_matches + .contains(&ComponentIdentifier::from("fmt1")) + ); + assert!( + m.format_matches + .contains(&ComponentIdentifier::from("fmt2")) + ); + } + + #[test] + fn nested_struct_with_two_blob_fields() { + let store_id = test_store_id(); + let entity: EntityPath = "/test/entity".into(); + let mut sub = test_subscriber_with_buffer_and_format_constraint(); + + // A struct component containing two fields with the buffer arrow type. + let struct_dt = arrow::datatypes::DataType::Struct( + vec![ + arrow::datatypes::Field::new( + "blob_a", + BufferAndFormatConstraint::buffer_arrow_datatype(), + true, + ), + arrow::datatypes::Field::new( + "blob_b", + BufferAndFormatConstraint::buffer_arrow_datatype(), + true, + ), + ] + .into(), + ); + + let chunk = make_chunk( + &entity, + &[ + (descriptor("data", None), struct_dt), + ( + descriptor("fmt", Some(FORMAT_CTYPE)), + arrow::datatypes::DataType::UInt32, + ), + ], + ); + sub.on_events(&[addition_event(&store_id, chunk)]); + + let m = expect_buffer_and_format_visualizable(&sub, &store_id, &entity); + let data_match = m + .buffer_matches + .get(&ComponentIdentifier::from("data")) + .expect("should have buffer match for 'data'"); + match data_match { + DatatypeMatch::PhysicalDatatypeOnly { selectors, .. } => { + assert_eq!( + selectors.len(), + 2, + "should have selectors for both blob_a and blob_b, got {selectors:?}" + ); + } + other @ DatatypeMatch::NativeSemantics { .. } => { + panic!("expected PhysicalDatatypeOnly with selectors, got {other:?}") + } + } + } +} diff --git a/crates/viewer/re_viewer_context/src/view/visualizer_system.rs b/crates/viewer/re_viewer_context/src/view/visualizer_system.rs index d65e60915344..6bbae482f569 100644 --- a/crates/viewer/re_viewer_context/src/view/visualizer_system.rs +++ b/crates/viewer/re_viewer_context/src/view/visualizer_system.rs @@ -4,13 +4,14 @@ use parking_lot::Mutex; use re_chunk_store::MissingChunkReporter; use vec1::Vec1; -use re_chunk::{ArchetypeName, ComponentType}; +use re_chunk::ArchetypeName; use re_sdk_types::blueprint::components::VisualizerInstructionId; -use re_sdk_types::{Archetype, ComponentDescriptor, ComponentIdentifier, ComponentSet}; +use re_sdk_types::{ComponentDescriptor, ComponentIdentifier}; use crate::{ - IdentifiedViewSystem, ViewContext, ViewContextCollection, ViewQuery, ViewSystemExecutionError, - ViewSystemIdentifier, + BufferAndFormatConstraint, IdentifiedViewSystem, SingleRequiredComponentConstraint, + ViewContext, ViewContextCollection, ViewQuery, ViewSystemExecutionError, ViewSystemIdentifier, + VisualizabilityConstraints, }; #[derive(Debug, Clone, Default)] @@ -40,90 +41,94 @@ impl FromIterator for SortedComponentSet { } } -pub type DatatypeSet = std::collections::BTreeSet; - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct AnyPhysicalDatatypeRequirement { - /// The required component that this requirement is targeting. - pub target_component: ComponentIdentifier, - - /// The semantic type the visualizer is working with. - /// - /// Matches with the semantic type are generally preferred. - pub semantic_type: ComponentType, - - /// All supported physical Arrow data types. - /// - /// Has to contain the physical data type that is covered by the Rerun semantic type. - pub physical_types: DatatypeSet, - - /// If false, ignores all static components. - /// - /// This is useful if you rely on ranges queries as done by the time series view. - pub allow_static_data: bool, -} - -impl From for RequiredComponents { - fn from(req: AnyPhysicalDatatypeRequirement) -> Self { - Self::AnyPhysicalDatatype(req) - } -} - -/// Specifies how component requirements should be evaluated for visualizer entity matching. -#[derive(Debug, Clone, PartialEq, Eq, Default)] -pub enum RequiredComponents { - /// No component requirements - all entities are candidates. - #[default] - None, - - /// Entity must have _all_ of these components. - AllComponents(ComponentSet), - - /// Entity must have _any one_ of these components. - AnyComponent(ComponentSet), - - /// Entity must have _any one_ of these physical Arrow data types. - /// - /// For instance, we may not put views into the "recommended" section or visualizer entities proactively unless they support the native type. - AnyPhysicalDatatype(AnyPhysicalDatatypeRequirement), -} - // TODO(grtlr): Eventually we will want to hide these fields to prevent visualizers doing too much shenanigans. pub struct VisualizerQueryInfo { /// This is not required, but if it is found, it is a strong indication that this /// system should be active (if also the `required_components` are found). + /// + /// This information results in the "indicated visualizer" list. pub relevant_archetype: Option, /// Returns the minimal set of components that the system _requires_ in order to be instantiated. - pub required: RequiredComponents, + pub constraints: VisualizabilityConstraints, /// Returns the list of components that the system _queries_. /// /// Must include required components. /// Order should reflect order in archetype docs & user code as well as possible. /// - /// Note that we need full descriptors here in order to write overrides from the UI. - pub queried: SortedComponentSet, // TODO(grtlr, wumpf): This can probably be removed? + /// We use this to determine which components should be shown in the UI. + pub queried: SortedComponentSet, } impl VisualizerQueryInfo { - pub fn from_archetype() -> Self { - Self { - relevant_archetype: A::name().into(), - required: RequiredComponents::AllComponents( - A::required_components() - .iter() - .map(|c| c.component) - .collect(), - ), - queried: A::all_components().iter().cloned().collect(), - } + /// Creates a query info for a visualizer that requires both a buffer and a format component. + /// + /// Both components have to be part of the queried components. + /// See [`BufferAndFormatConstraint`] for more details. + pub fn buffer_and_format( + buffer_descriptor: &ComponentDescriptor, + format_descriptor: &ComponentDescriptor, + all_queried_components: &[ComponentDescriptor], + ) -> Self { + let query_info = Self { + relevant_archetype: format_descriptor.archetype, + constraints: BufferAndFormatConstraint::new::( + buffer_descriptor, + format_descriptor, + ) + .into(), + queried: all_queried_components.iter().cloned().collect(), + }; + + re_log::debug_assert!( + query_info + .queried + .iter() + .any(|desc| desc == buffer_descriptor), + "The buffer component must be part of the queried components." + ); + re_log::debug_assert!( + query_info + .queried + .iter() + .any(|desc| desc == format_descriptor), + "The format component must be part of the queried components." + ); + + query_info + } + + /// Creates a query info for a visualizer that requires a single component. + /// + /// The target component has to be part of the queried components. + /// See [`SingleRequiredComponentConstraint`] for more details. + pub fn single_required_component( + target_component_descriptor: &ComponentDescriptor, + all_queried_components: &[ComponentDescriptor], + ) -> Self { + let query_info = Self { + relevant_archetype: target_component_descriptor.archetype, + constraints: SingleRequiredComponentConstraint::new::(target_component_descriptor) + .into(), + queried: all_queried_components.iter().cloned().collect(), + }; + + re_log::debug_assert!( + query_info + .queried + .iter() + .any(|desc| desc == target_component_descriptor), + "The required component must be part of the queried components." + ); + + query_info } pub fn empty() -> Self { Self { relevant_archetype: Default::default(), - required: RequiredComponents::None, + constraints: VisualizabilityConstraints::None, queried: SortedComponentSet::default(), } } diff --git a/examples/rust/custom_view/src/points3d_color_visualizer.rs b/examples/rust/custom_view/src/points3d_color_visualizer.rs index 82d54b722780..c60533222283 100644 --- a/examples/rust/custom_view/src/points3d_color_visualizer.rs +++ b/examples/rust/custom_view/src/points3d_color_visualizer.rs @@ -3,9 +3,9 @@ use rerun::external::re_log_types::{EntityPath, Instance}; use rerun::external::re_sdk_types::blueprint::components::VisualizerInstructionId; use rerun::external::re_view::{DataResultQuery, VisualizerInstructionQueryResults}; use rerun::external::re_viewer_context::{ - AppOptions, IdentifiedViewSystem, RequiredComponents, ViewContext, ViewContextCollection, - ViewQuery, ViewSystemExecutionError, ViewSystemIdentifier, VisualizerExecutionOutput, - VisualizerQueryInfo, VisualizerSystem, + AppOptions, IdentifiedViewSystem, SingleRequiredComponentConstraint, ViewContext, + ViewContextCollection, ViewQuery, ViewSystemExecutionError, ViewSystemIdentifier, + VisualizerExecutionOutput, VisualizerQueryInfo, VisualizerSystem, }; /// Our view consist of single part which holds a list of egui colors for each entity path. @@ -29,18 +29,14 @@ impl VisualizerSystem for Points3DColorVisualizer { fn visualizer_query_info(&self, _app_options: &AppOptions) -> VisualizerQueryInfo { // Usually, visualizers are closely tied to archetypes. // However, here we're adding a visualizer that queries only parts of an existing archetype. - if false { - // This is what it looks like to query all the fields of an archetype. - VisualizerQueryInfo::from_archetype::() - } else { - // Instead, our custom query here is solely interested in Points3D's colors. - VisualizerQueryInfo { - relevant_archetype: Default::default(), - required: RequiredComponents::AllComponents( - std::iter::once(rerun::Points3D::descriptor_colors().component).collect(), - ), - queried: std::iter::once(rerun::Points3D::descriptor_colors()).collect(), - } + // Instead, our custom query here is solely interested in Points3D's colors. + VisualizerQueryInfo { + relevant_archetype: None, + constraints: SingleRequiredComponentConstraint::new::( + &rerun::Points3D::descriptor_colors(), + ) + .into(), + queried: std::iter::once(rerun::Points3D::descriptor_colors()).collect(), } } diff --git a/examples/rust/custom_visualizer/src/height_field_visualizer.rs b/examples/rust/custom_visualizer/src/height_field_visualizer.rs index 01757de19149..3458b541ced8 100644 --- a/examples/rust/custom_visualizer/src/height_field_visualizer.rs +++ b/examples/rust/custom_visualizer/src/height_field_visualizer.rs @@ -1,5 +1,5 @@ use rerun::Archetype as _; -use rerun::components::{Colormap, ImageFormat}; +use rerun::components::{Colormap, ImageBuffer, ImageFormat}; use rerun::external::re_view::{DataResultQuery as _, VisualizerInstructionQueryResults}; use rerun::external::re_viewer_context::{ self, IdentifiedViewSystem, ViewContext, ViewContextCollection, ViewQuery, @@ -28,7 +28,11 @@ impl VisualizerSystem for HeightFieldVisualizer { &self, _app_options: &re_viewer_context::AppOptions, ) -> VisualizerQueryInfo { - VisualizerQueryInfo::from_archetype::() + VisualizerQueryInfo::buffer_and_format::( + &HeightField::descriptor_buffer(), + &HeightField::descriptor_format(), + &HeightField::all_components(), + ) } fn execute( diff --git a/tests/rust/re_integration_test/tests/add_entity_to_view_test.rs b/tests/rust/re_integration_test/tests/add_entity_to_view_test.rs index aa74d927d82b..5af2bfd51176 100644 --- a/tests/rust/re_integration_test/tests/add_entity_to_view_test.rs +++ b/tests/rust/re_integration_test/tests/add_entity_to_view_test.rs @@ -197,7 +197,9 @@ pub async fn test_add_entity_to_view_bar_chart() { harness.right_click_label("Viewport (Grid container)"); harness.click_label("Expand all"); - harness.blueprint_tree().right_click_label("bar_chart"); + harness + .blueprint_tree() + .right_click_nth_label("bar_chart", 0); harness.snapshot_app("add_entity_to_view_bar_chart_1"); harness.hover_label_contains("Add to new view"); @@ -212,7 +214,7 @@ pub async fn test_add_entity_to_view_bar_chart() { harness .blueprint_tree() - .right_click_nth_label("bar_chart", 1); + .right_click_nth_label("bar_chart", 3); harness.snapshot_app("add_entity_to_view_bar_chart_4"); harness.click_label("Remove"); @@ -259,7 +261,7 @@ pub async fn test_add_entity_to_view_tensor() { harness.right_click_label("Viewport (Grid container)"); harness.click_label("Expand all"); - harness.blueprint_tree().right_click_label("tensor"); + harness.blueprint_tree().right_click_nth_label("tensor", 2); harness.snapshot_app("add_entity_to_view_tensor_1"); harness.hover_label_contains("Add to new view"); @@ -268,11 +270,11 @@ pub async fn test_add_entity_to_view_tensor() { harness.click_nth_label("Tensor", 2); harness.snapshot_app("add_entity_to_view_tensor_3"); - // When adding a text log, to a new view, the origin is set to the entity path + // When adding a tensor, to a new view, the origin is set to the entity path assert_eq!(get_origin(&mut harness), "/tensor"); assert_eq!(get_entity_path_filter(&mut harness), "+ /tensor/**"); - harness.blueprint_tree().right_click_nth_label("tensor", 1); + harness.blueprint_tree().right_click_nth_label("tensor", 3); harness.snapshot_app("add_entity_to_view_tensor_4"); harness.click_label("Remove"); diff --git a/tests/rust/re_integration_test/tests/snapshots/add_container_from_blueprint_panel_menu_1.png b/tests/rust/re_integration_test/tests/snapshots/add_container_from_blueprint_panel_menu_1.png index 602995f30160..091073b11295 100644 --- a/tests/rust/re_integration_test/tests/snapshots/add_container_from_blueprint_panel_menu_1.png +++ b/tests/rust/re_integration_test/tests/snapshots/add_container_from_blueprint_panel_menu_1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9803e2ddd9b1594394ead1cd1aea425dede7873b81be0e4f48a8dd5e1551d8ec -size 198330 +oid sha256:ff2a349c57e55ebde8bf6cdad496af5a5718d806a9235c6cb5e010dfc53c80cc +size 207314 diff --git a/tests/rust/re_integration_test/tests/snapshots/add_container_from_blueprint_panel_menu_2.png b/tests/rust/re_integration_test/tests/snapshots/add_container_from_blueprint_panel_menu_2.png index 0464a1f97f51..d1d0b0cca2b5 100644 --- a/tests/rust/re_integration_test/tests/snapshots/add_container_from_blueprint_panel_menu_2.png +++ b/tests/rust/re_integration_test/tests/snapshots/add_container_from_blueprint_panel_menu_2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d2e25e251f4735fbbcf2f49d663928fe037751ffe89fba345dbd30f60cd51010 -size 221273 +oid sha256:1a6896a737e5e7f879a22b4f1efbf2fe8dbcb45a9225d80655023a745b1c118c +size 230022 diff --git a/tests/rust/re_integration_test/tests/snapshots/add_container_from_blueprint_panel_menu_3.png b/tests/rust/re_integration_test/tests/snapshots/add_container_from_blueprint_panel_menu_3.png index a1ff2557d9ee..dba7989d827c 100644 --- a/tests/rust/re_integration_test/tests/snapshots/add_container_from_blueprint_panel_menu_3.png +++ b/tests/rust/re_integration_test/tests/snapshots/add_container_from_blueprint_panel_menu_3.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:75d1985993b90b176eed93e3646e34ffb5f252399403156261945fb6861a9e84 -size 171314 +oid sha256:515a0cabf15f6bce3db930ebd6f988daf3b9e23ef991785ed99100b762fdafaa +size 179879 diff --git a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_bar_chart_1.png b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_bar_chart_1.png index 5924e1bc6a05..cd7744b8ae54 100644 --- a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_bar_chart_1.png +++ b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_bar_chart_1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:55499bc390539aa0ea55dfc676572b58360d45a73fc809856e6a11cd1775e0a3 -size 181991 +oid sha256:8dac17ce91260c05fe917cfbadf254c155b1ff90c7e9fb26816c9e9e0203d9d3 +size 187921 diff --git a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_bar_chart_2.png b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_bar_chart_2.png index cf58aded8e14..41ef3673040d 100644 --- a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_bar_chart_2.png +++ b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_bar_chart_2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bcb83f57d7551ac0579524b85944fd9aa5a07f238c668b722e3bb06e8418cc3d -size 191402 +oid sha256:19cff2e13d16961b7e2a1e7b54fa6a642a820bb17fe0d5c4afabcf30265468a0 +size 202817 diff --git a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_bar_chart_3.png b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_bar_chart_3.png index a9c58d012def..ec8d82614c6d 100644 --- a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_bar_chart_3.png +++ b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_bar_chart_3.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:85ec7310550618f84b25a2ecae267d3d566d3f6b94aad74343c87f38c02c7cc3 -size 189214 +oid sha256:97b40cef915b5852a84b71586e38ae852e18bfd5fba27e1a0822ff9128302c48 +size 194855 diff --git a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_bar_chart_4.png b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_bar_chart_4.png index a451c47ea8dd..ed38ce063fd8 100644 --- a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_bar_chart_4.png +++ b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_bar_chart_4.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:78156c7119639f118f29f149442e05d9aef1c1ea9ab17fbe2655808105279227 -size 203574 +oid sha256:7ff24de9063f78182e581916d56695babb5e3dd986af66942288332da19b2961 +size 204923 diff --git a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_bar_chart_5.png b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_bar_chart_5.png index 497ac4646c7b..61cddfd79c98 100644 --- a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_bar_chart_5.png +++ b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_bar_chart_5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:aa950f66f173449c49057c6c81231c82a205e280ee9414d2c3a73e6b0c45ec5e -size 147250 +oid sha256:89265410bcfb6d6d3e7191b45e89f8cbed79667d4e591ca9e2f3569462200620 +size 152777 diff --git a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_boxes2d_1.png b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_boxes2d_1.png index 338e7f569d08..908bcefe3c63 100644 --- a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_boxes2d_1.png +++ b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_boxes2d_1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0309acef7888e29ff151095e3419f18f09bbe5184a8af39e60e1bf1036df1f0f -size 198054 +oid sha256:dea9e9a5400431fa84767e9b4db4c9ddb6fd962a7946367b7b81b8c4406a2ac6 +size 203117 diff --git a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_boxes2d_2.png b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_boxes2d_2.png index 0a1a93078b3e..2160f947de31 100644 --- a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_boxes2d_2.png +++ b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_boxes2d_2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:24f5d2d6cdb0128013bc9a79225db97f00996f3ea92427db844d3d2c1dee45c4 -size 206154 +oid sha256:03e2dd88b62406617b6f4568393fd7edf957213ad20e003f4374d10e8f76bc90 +size 211507 diff --git a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_boxes2d_3.png b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_boxes2d_3.png index 75d0be9a9246..8f6518727fae 100644 --- a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_boxes2d_3.png +++ b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_boxes2d_3.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0777d9418b0bdf44932b02725481b4b9bba5af9948bf81545539e2fb7e1e8ca3 -size 197239 +oid sha256:8d27a2b01871ac962ab717732a2bb17e9f045fe36ca9b7700fa1fb5ccf8ce279 +size 202783 diff --git a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_boxes2d_4.png b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_boxes2d_4.png index dc8599c04912..b6e749c6175c 100644 --- a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_boxes2d_4.png +++ b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_boxes2d_4.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3e782323aac44e66048fc5d3c522de15cf6776b302171004d107912aa849de31 -size 206468 +oid sha256:9de178cbac98f5c0d951856ca98421b48b69b3be25329fd4c6610f72edf3c71a +size 207967 diff --git a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_boxes2d_5.png b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_boxes2d_5.png index 497ac4646c7b..61cddfd79c98 100644 --- a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_boxes2d_5.png +++ b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_boxes2d_5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:aa950f66f173449c49057c6c81231c82a205e280ee9414d2c3a73e6b0c45ec5e -size 147250 +oid sha256:89265410bcfb6d6d3e7191b45e89f8cbed79667d4e591ca9e2f3569462200620 +size 152777 diff --git a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_boxes3d_1.png b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_boxes3d_1.png index 74f526683a38..5109e19a2544 100644 --- a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_boxes3d_1.png +++ b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_boxes3d_1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:120628ed6bb42fc17872f75441d0eab9b343d7a9622e7dcdc43cbc0b18676726 -size 208529 +oid sha256:c9fdeb1b46d14acea32743784abb0efd8c80f25d839c5d78e58346cbab45af5b +size 214125 diff --git a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_boxes3d_2.png b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_boxes3d_2.png index a4eadee14886..3b57d06e5ab8 100644 --- a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_boxes3d_2.png +++ b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_boxes3d_2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8742ea5249653d65d6015e2eb144598eba2e2688167ec2b87c2f2b32e4f96beb -size 218127 +oid sha256:5094bf587cdaffd86d510f57afc87158090bcb124b667ed7ebbaad5fc7d2afe8 +size 223703 diff --git a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_boxes3d_3.png b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_boxes3d_3.png index 073e0673aada..3cb949b8d3f2 100644 --- a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_boxes3d_3.png +++ b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_boxes3d_3.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3a766e12e25f1e785aef98d157f8dc257a425a0a150824e12e900a1b65485e5b -size 230890 +oid sha256:e71292d827d5c009ddae360fb56a50f511e48c777c11da967cdbf6efa5fba3e7 +size 236932 diff --git a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_boxes3d_4.png b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_boxes3d_4.png index 910796b214e2..693bed64a6a7 100644 --- a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_boxes3d_4.png +++ b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_boxes3d_4.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:69af9737d19faf5c2332ad437132e24480e57e035056827ee4fffb9dff7d71a3 -size 240453 +oid sha256:249282b43145ec58656fbd64e01e41101401d1c31331f7fca58c668b72c30608 +size 242902 diff --git a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_boxes3d_5.png b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_boxes3d_5.png index 497ac4646c7b..61cddfd79c98 100644 --- a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_boxes3d_5.png +++ b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_boxes3d_5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:aa950f66f173449c49057c6c81231c82a205e280ee9414d2c3a73e6b0c45ec5e -size 147250 +oid sha256:89265410bcfb6d6d3e7191b45e89f8cbed79667d4e591ca9e2f3569462200620 +size 152777 diff --git a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_tensor_1.png b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_tensor_1.png index 6285f8ef1a18..0a9423a959e9 100644 --- a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_tensor_1.png +++ b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_tensor_1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6ee45f125d352c1b98d459c0fa1936918fb58121306bc831acbb2dd67db6066c -size 176418 +oid sha256:6602d122c09e89123d7fabb29b3af56a01931045fca89f5c2fa58967f6b314e0 +size 183058 diff --git a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_tensor_2.png b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_tensor_2.png index 3474ce1bed3b..4b5a08d37b6e 100644 --- a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_tensor_2.png +++ b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_tensor_2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d47423a8b8c10f835bc0d22155a6962e269074432af77a213e6f0a45eeecbf60 -size 184462 +oid sha256:0d7e9a9bf35c8dbddcc5c2290fd9f028dc5ab93911abdade754ffa7005427bc4 +size 196407 diff --git a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_tensor_3.png b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_tensor_3.png index 1a3dba8b5b89..48bb766759b3 100644 --- a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_tensor_3.png +++ b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_tensor_3.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0cc6425e127dd51a153f25e9f22290deaff0d322660b6258fc083bea7021b527 -size 203430 +oid sha256:79264d1b43ab6082a8513e106a185823fe93adb7e81f5c4e7fc8504d26279de1 +size 214788 diff --git a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_tensor_4.png b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_tensor_4.png index e4687f1c467d..6af8eabb6e21 100644 --- a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_tensor_4.png +++ b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_tensor_4.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1e6136e0424a534fcdd03353a562e6a678b5d95e1711ec43fbd2749ce288b4a5 -size 225668 +oid sha256:a2535e8da2ec400f254ea6b711d0796bc204d5f46c7282721c5dec1534e53901 +size 227298 diff --git a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_tensor_5.png b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_tensor_5.png index 497ac4646c7b..61cddfd79c98 100644 --- a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_tensor_5.png +++ b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_tensor_5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:aa950f66f173449c49057c6c81231c82a205e280ee9414d2c3a73e6b0c45ec5e -size 147250 +oid sha256:89265410bcfb6d6d3e7191b45e89f8cbed79667d4e591ca9e2f3569462200620 +size 152777 diff --git a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_text_log_1.png b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_text_log_1.png index ceaca25ec926..80d54c0d49b2 100644 --- a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_text_log_1.png +++ b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_text_log_1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:868b454e8ea14e8545d5e1f5795c5a1ec6c1aef8d299bead68df8f545cde4d56 -size 180203 +oid sha256:6ce088c97fad90555c3693303cff9d8ceb68f5a6a555c6df0d3fb39359b6dc4f +size 184702 diff --git a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_text_log_2.png b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_text_log_2.png index c0c3d6fbbdd4..66e099b9ecfc 100644 --- a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_text_log_2.png +++ b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_text_log_2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4d194ac9df336756ec3ad7e438cb2ca355555a724584b7311b76b9c43c23edaa -size 188841 +oid sha256:c25b5b66295769da67c9bb7eda5d3f770b0314d2fdbf7cee47fe2d210ad177db +size 196861 diff --git a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_text_log_3.png b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_text_log_3.png index e7ce4c71f184..93cb02c003a5 100644 --- a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_text_log_3.png +++ b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_text_log_3.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5fd3a7613a4cd88692f173f4ce26deeb15b4259081efbeb81c038e607b016271 -size 188781 +oid sha256:220ef73cebe425eccdcd485e17f882ea541ef6c33d05ebf5a5955856fb84405f +size 194054 diff --git a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_text_log_4.png b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_text_log_4.png index f46a409bf84b..6e35afeb500d 100644 --- a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_text_log_4.png +++ b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_text_log_4.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f525510c547fd2872043aae7084a4695a8196af1a79aebde0a85efd204794f2b -size 211121 +oid sha256:f98b00956f6de7086ccf2f9358261775ca5333ba4df89281a00e71bb14370bcb +size 212620 diff --git a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_text_log_5.png b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_text_log_5.png index 497ac4646c7b..61cddfd79c98 100644 --- a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_text_log_5.png +++ b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_text_log_5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:aa950f66f173449c49057c6c81231c82a205e280ee9414d2c3a73e6b0c45ec5e -size 147250 +oid sha256:89265410bcfb6d6d3e7191b45e89f8cbed79667d4e591ca9e2f3569462200620 +size 152777 diff --git a/tests/rust/re_integration_test/tests/snapshots/add_visualizer_axes_4.png b/tests/rust/re_integration_test/tests/snapshots/add_visualizer_axes_4.png index afaa060d5eb2..945b28cedd4c 100644 --- a/tests/rust/re_integration_test/tests/snapshots/add_visualizer_axes_4.png +++ b/tests/rust/re_integration_test/tests/snapshots/add_visualizer_axes_4.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b929629ca1620fbcf107f0cad63b686501666ecfd80c3f9d638c7e415c19e137 -size 204381 +oid sha256:dfa8fa9175e2a7f15e37ab79ec703d66694b692e25e210ab1141e2f298a1cf5d +size 207039 diff --git a/tests/rust/re_integration_test/tests/snapshots/add_visualizer_axes_5.png b/tests/rust/re_integration_test/tests/snapshots/add_visualizer_axes_5.png index b64fdbe4fdfc..94a417c4ca38 100644 --- a/tests/rust/re_integration_test/tests/snapshots/add_visualizer_axes_5.png +++ b/tests/rust/re_integration_test/tests/snapshots/add_visualizer_axes_5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a6ec2bf28bc15dc8dbf7a38c7b040019619771c044c2deb9d204e7b1c197b612 -size 209070 +oid sha256:cb512e5534e58da97323bcb63cf086f6a46f40c3d1a18d0d80366258b18a1c93 +size 207900 diff --git a/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_bottom_1.png b/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_bottom_1.png index dfaf6cd8df65..b2558400ecd3 100644 --- a/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_bottom_1.png +++ b/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_bottom_1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:55860fd217492256e78b13b9ce710618abb3073706fde0ad0fe62066360e5387 -size 180008 +oid sha256:d23206dd98af668297f35f5f963d02a4b3efdc8b7f8ba8dea62b7457c2ada94f +size 188791 diff --git a/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_bottom_2.png b/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_bottom_2.png index e878e488923d..df2498422cea 100644 --- a/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_bottom_2.png +++ b/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_bottom_2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:56cb15d850ae8a11a4fb47fed3aebea7783c598f0767045d9168ee124039a4ff -size 181743 +oid sha256:328b79be6eb67a5e25614538ff2c48147acb111244fd23bfaa86dff3c3644634 +size 190775 diff --git a/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_center_1.png b/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_center_1.png index 431e530b9a49..139c194bff5e 100644 --- a/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_center_1.png +++ b/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_center_1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5d88efed52cd0694f847afa8c5c41208d56d1c06fb1268a5506624b36e2e4fc5 -size 179938 +oid sha256:1f6280ddb09841c5eff70ceb473c8c3f450c2263cad225b507bc4975a5201c96 +size 188753 diff --git a/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_center_2.png b/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_center_2.png index 81712e45d09f..58f31b5d2df2 100644 --- a/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_center_2.png +++ b/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_center_2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c43f10ba5c4a94e4d40291dd77c1b14d30e91a1643ac2fa2a5e1091a388f6719 -size 190720 +oid sha256:5185b7c556ae8c548eed1cb1f061e640e60cacf10e8386482359f614371af24d +size 199125 diff --git a/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_left_1.png b/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_left_1.png index 4f0668814bb9..04ceec14678e 100644 --- a/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_left_1.png +++ b/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_left_1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:389a159eaaeb696f2db949d05544f20de44752cd6b2815825b30056d103e5032 -size 179682 +oid sha256:71095dcf580d233c1cd1f7b138ee975c833143bfcaed53368cf76b4b98aa11a1 +size 188793 diff --git a/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_left_2.png b/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_left_2.png index f8530b460bf1..dd23c0fa72c4 100644 --- a/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_left_2.png +++ b/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_left_2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5d986c6902a78f1d1b1741d1b2a5c15ff3cfea8aec2725ae251ae33d4d7b3076 -size 179150 +oid sha256:1065d4a9cbac690e41a067ce971364361f71a7a6b86e5dbb5ed88c8bd4d66513 +size 188316 diff --git a/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_right_1.png b/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_right_1.png index ecd049e09001..d31125139d0d 100644 --- a/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_right_1.png +++ b/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_right_1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:617984003692b450232ddd2e686c0e19d2429a2b97802d317ff6bfecf1a8dfee -size 179708 +oid sha256:5a78af63da0de949a5c5d31869ae6a3302441f04397b101e74121a9787401ea2 +size 188815 diff --git a/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_right_2.png b/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_right_2.png index c11e5d2aa649..7e3c67d6a6cc 100644 --- a/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_right_2.png +++ b/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_right_2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9acf82c270b83698cb340dd135430b5992e00329eed020aca1d657202a97bd3c -size 179159 +oid sha256:442633e26647471cfc99bf721a84ada3f6d3518c678a7028dd79f4efbdb745d8 +size 188325 diff --git a/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_top_1.png b/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_top_1.png index 7b5aab206d72..ffd7c925b6cd 100644 --- a/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_top_1.png +++ b/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_top_1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:36933098848831f351f03610a0a9e86e609a82623027bc6bb4c4274f9765848b -size 179586 +oid sha256:c0ee37a908fbc640dd7e23f74772ebbecfb587854eb1164a6c146f59e024b090 +size 188709 diff --git a/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_top_2.png b/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_top_2.png index 823c26b8e7d1..44d3b6bc8896 100644 --- a/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_top_2.png +++ b/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_top_2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6502821edb40e0a33982bd09a2d89b7eaa8d73ecd9c9f9b8beb8df2085ae9939 -size 181772 +oid sha256:242e1586a8f8110bac94ba9b27aeda436c412f4876566dcc3ce051bf787a488a +size 190678 diff --git a/tests/rust/re_integration_test/tests/snapshots/heuristics_mixed_all_root.png b/tests/rust/re_integration_test/tests/snapshots/heuristics_mixed_all_root.png index 832d76fff5e5..b41c7f7721f2 100644 --- a/tests/rust/re_integration_test/tests/snapshots/heuristics_mixed_all_root.png +++ b/tests/rust/re_integration_test/tests/snapshots/heuristics_mixed_all_root.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5bcc754a03f0b1833662d9cfc58bebb510f838df20782ef391370457dfa4141e -size 123364 +oid sha256:75fa8efef0ef7bd9b69a36ce83b2c41f438bdedc7cb2a6dbacec8d42a118d86f +size 130322 diff --git a/tests/rust/re_integration_test/tests/snapshots/multi_container_drag_container_1.png b/tests/rust/re_integration_test/tests/snapshots/multi_container_drag_container_1.png index 07a711801e7e..62420fae7b75 100644 --- a/tests/rust/re_integration_test/tests/snapshots/multi_container_drag_container_1.png +++ b/tests/rust/re_integration_test/tests/snapshots/multi_container_drag_container_1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:36ae1e3abc49b4513556527f9f376a24f6437a0b18dc7b1bfd27115020ec0cb0 -size 257087 +oid sha256:6a41387982542e29b0a70740bb41426aec4766dfe74c0f4378256c80b63d8fba +size 251105 diff --git a/tests/rust/re_integration_test/tests/snapshots/multi_container_drag_container_2.png b/tests/rust/re_integration_test/tests/snapshots/multi_container_drag_container_2.png index 65d2c4a8f3dd..71c31c8dddaf 100644 --- a/tests/rust/re_integration_test/tests/snapshots/multi_container_drag_container_2.png +++ b/tests/rust/re_integration_test/tests/snapshots/multi_container_drag_container_2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:90723545a9330c4ffc5dc9cc5116c47848ff8da6991657284131cea14dcfa979 -size 257694 +oid sha256:3784c0b0b71b3eba22d6b17c3772654a753f7bf19461629143fe26fa974ab481 +size 251914 diff --git a/tests/rust/re_integration_test/tests/snapshots/multi_container_drag_container_3.png b/tests/rust/re_integration_test/tests/snapshots/multi_container_drag_container_3.png index 645b213494f2..802cd76a865a 100644 --- a/tests/rust/re_integration_test/tests/snapshots/multi_container_drag_container_3.png +++ b/tests/rust/re_integration_test/tests/snapshots/multi_container_drag_container_3.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4b015fe4e0825e8a6505ded9d4347e8db909321afd19587e1c3c17f76c2b4b57 -size 257948 +oid sha256:0b921289417aeefff9aee7f3b94dedab2c9e40433779dfc3dfd3502ca6f1568c +size 252466 diff --git a/tests/rust/re_integration_test/tests/snapshots/multi_container_drag_container_4.png b/tests/rust/re_integration_test/tests/snapshots/multi_container_drag_container_4.png index 4f33f385e4d9..07aafcdbd22f 100644 --- a/tests/rust/re_integration_test/tests/snapshots/multi_container_drag_container_4.png +++ b/tests/rust/re_integration_test/tests/snapshots/multi_container_drag_container_4.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:750e81cb61af065f58a12ee9b5144c17dea99a3d171ce5e23b6b923da183f139 -size 257204 +oid sha256:de0186ffb76599ae5509cdc8be63d008d3655a420e8ae8520b8e5961d66748cb +size 251108 diff --git a/tests/rust/re_integration_test/tests/snapshots/multi_container_drag_container_5.png b/tests/rust/re_integration_test/tests/snapshots/multi_container_drag_container_5.png index 073c1e8c78e3..ce8063a2f162 100644 --- a/tests/rust/re_integration_test/tests/snapshots/multi_container_drag_container_5.png +++ b/tests/rust/re_integration_test/tests/snapshots/multi_container_drag_container_5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d1a6db729d418f1daf6b1989ede03dc78ab2e1c5830c29a3176e406900f4fe3c -size 257248 +oid sha256:79183965bd5831a9823cabfd02d4753a1ad7d02095d7207b641e8b59a2cbccbc +size 251585 diff --git a/tests/rust/re_integration_test/tests/snapshots/multi_container_drag_container_6.png b/tests/rust/re_integration_test/tests/snapshots/multi_container_drag_container_6.png index 96d14af438ae..2d69a943276c 100644 --- a/tests/rust/re_integration_test/tests/snapshots/multi_container_drag_container_6.png +++ b/tests/rust/re_integration_test/tests/snapshots/multi_container_drag_container_6.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:96de04d649c189e69f9b0d0103cacad6518151e1700a8b475a1bf658c3444e84 -size 258815 +oid sha256:0b74e0c135b0e755f41285f98cbf19c4ba721e7b339a1bfdc546094bcb055e95 +size 252913 diff --git a/tests/rust/re_integration_test/tests/snapshots/multi_container_drag_single_view_1.png b/tests/rust/re_integration_test/tests/snapshots/multi_container_drag_single_view_1.png index 01814ac1552d..2a54ff6f98a8 100644 --- a/tests/rust/re_integration_test/tests/snapshots/multi_container_drag_single_view_1.png +++ b/tests/rust/re_integration_test/tests/snapshots/multi_container_drag_single_view_1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8e1bf480fcd6ce87acb814ba3aa41a804df665b285a4cd851390377dc8d0d295 -size 258910 +oid sha256:95ce4f4e7b982f54cfe0e6bcc6527fcb618997a51347699dce5d3076a8a0dc90 +size 252860 diff --git a/tests/rust/re_integration_test/tests/snapshots/multi_container_drag_single_view_2.png b/tests/rust/re_integration_test/tests/snapshots/multi_container_drag_single_view_2.png index 127c22e28315..5d3bf1fd49e6 100644 --- a/tests/rust/re_integration_test/tests/snapshots/multi_container_drag_single_view_2.png +++ b/tests/rust/re_integration_test/tests/snapshots/multi_container_drag_single_view_2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5923d57212ffcf83d4c324f87a414da0863784310364732fcc1fb0a7c8b25860 -size 258542 +oid sha256:9030ee0336693ce4093a305881b5299a0d526c177df16e1cbc2d350fed15ffea +size 252760 diff --git a/tests/rust/re_integration_test/tests/snapshots/multi_container_drag_single_view_3.png b/tests/rust/re_integration_test/tests/snapshots/multi_container_drag_single_view_3.png index 56e5c0cf661e..d400ccee7769 100644 --- a/tests/rust/re_integration_test/tests/snapshots/multi_container_drag_single_view_3.png +++ b/tests/rust/re_integration_test/tests/snapshots/multi_container_drag_single_view_3.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:258327241e0a55c5ebf360efb3446d1b75d12aef69ae16f214fe91b6b97439ac -size 255517 +oid sha256:f90ebe7a37192478378b139cbe023fc8f7ec95bca9524c65187cb6d69f8ceae4 +size 249431 diff --git a/tests/rust/re_integration_test/tests/snapshots/multi_container_many_views.png b/tests/rust/re_integration_test/tests/snapshots/multi_container_many_views.png index 71d0fef851b6..5ea0946aa958 100644 --- a/tests/rust/re_integration_test/tests/snapshots/multi_container_many_views.png +++ b/tests/rust/re_integration_test/tests/snapshots/multi_container_many_views.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:57008011f209806223a4655e50c71d0155d840ff9a46830e6137e5f2f496c0ad -size 499082 +oid sha256:a5e9971bcda5bf731e89f232cfc43f762c90491fb3cd5923c8f979eb7b6d204c +size 490725 diff --git a/tests/rust/re_integration_test/tests/snapshots/resize_view_horizontal_1.png b/tests/rust/re_integration_test/tests/snapshots/resize_view_horizontal_1.png index d493d9ff25a4..c6ab7190f3e8 100644 --- a/tests/rust/re_integration_test/tests/snapshots/resize_view_horizontal_1.png +++ b/tests/rust/re_integration_test/tests/snapshots/resize_view_horizontal_1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0ab364b9d99cd6248b70d3fd6cdc7bffd5a62edd4f9f047d72e39a38fa44aa99 -size 186941 +oid sha256:18910e5b5890eed2812baf5ca12c585d7b18ebebb82dba58817e883de4ac3753 +size 195728 diff --git a/tests/rust/re_integration_test/tests/snapshots/resize_view_horizontal_2.png b/tests/rust/re_integration_test/tests/snapshots/resize_view_horizontal_2.png index 803f229fda7b..2a254edf480e 100644 --- a/tests/rust/re_integration_test/tests/snapshots/resize_view_horizontal_2.png +++ b/tests/rust/re_integration_test/tests/snapshots/resize_view_horizontal_2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4068ac059b9e27380afcf9769865dec703d47dc3ede0dc728de4dd12a4e662d9 -size 188036 +oid sha256:797710c2e9d600370f5624b9048f85403e927ab4e57c986290545fba8ccf0c1a +size 196859 diff --git a/tests/rust/re_integration_test/tests/snapshots/resize_view_horizontal_3.png b/tests/rust/re_integration_test/tests/snapshots/resize_view_horizontal_3.png index 13496f0078e8..97df8af70daa 100644 --- a/tests/rust/re_integration_test/tests/snapshots/resize_view_horizontal_3.png +++ b/tests/rust/re_integration_test/tests/snapshots/resize_view_horizontal_3.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b00a4c858c80bbb9e93b30ce233cfb841fa4074b76426b767a26cf89e7525994 -size 187465 +oid sha256:b47e82db26841511f9e3fd2af35b6c4919faf7b2b54add41311fbb91dcf66c62 +size 196282 diff --git a/tests/rust/re_integration_test/tests/snapshots/resize_view_vertical_1.png b/tests/rust/re_integration_test/tests/snapshots/resize_view_vertical_1.png index 16b6209b2acb..ecd111df9961 100644 --- a/tests/rust/re_integration_test/tests/snapshots/resize_view_vertical_1.png +++ b/tests/rust/re_integration_test/tests/snapshots/resize_view_vertical_1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:be81b8ed2ac3dbb125fc209246ae9dc57d7a9a84556cbb4dba36e4d5083c16f5 -size 186680 +oid sha256:fc431f3d56714f92386554e09e9f64af37e0183f8fa119fc6636c460e4ffd73f +size 195285 diff --git a/tests/rust/re_integration_test/tests/snapshots/resize_view_vertical_2.png b/tests/rust/re_integration_test/tests/snapshots/resize_view_vertical_2.png index 0309f3442c26..76289fea492f 100644 --- a/tests/rust/re_integration_test/tests/snapshots/resize_view_vertical_2.png +++ b/tests/rust/re_integration_test/tests/snapshots/resize_view_vertical_2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b46019f2dcddaf09b518f543ebbe8c6ec8bcb3986cf6dd17ec9c3e238e1a36d9 -size 185997 +oid sha256:3b18a9eda787b5e515b9ed79ac7446b9f7e5940f42c385663e4268cfa40fb788 +size 194558 diff --git a/tests/rust/re_integration_test/tests/snapshots/resize_view_vertical_3.png b/tests/rust/re_integration_test/tests/snapshots/resize_view_vertical_3.png index 397cf6e24470..69f1590037ad 100644 --- a/tests/rust/re_integration_test/tests/snapshots/resize_view_vertical_3.png +++ b/tests/rust/re_integration_test/tests/snapshots/resize_view_vertical_3.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ab5ae879aab505ba3d83b76ec150323e8b645f112204ed526abb55448abf5cfc -size 186273 +oid sha256:084ab60a1b0a36a417b3dd99766543ae5e7692e15820aabd20020f273e8210c7 +size 194939 From 64f100332a1c971cf0d9f339dcfa796535cbfdae Mon Sep 17 00:00:00 2001 From: Isse Date: Fri, 6 Mar 2026 18:00:56 +0100 Subject: [PATCH 066/513] Share video players between views, de-duplicating video decoding work between pinholes and 2d views. ### What Using a smarter way to generate video player ids to allow players to be shared between views. Source-Ref: 71156fa9d7cd2d9bb6c1b68ace52763b39de239b --- .../re_view_spatial/src/visualizers/video/mod.rs | 15 +++++++++++---- .../visualizers/video/video_frame_reference.rs | 15 ++++++++------- .../src/visualizers/video/video_stream.rs | 10 +++++++--- 3 files changed, 26 insertions(+), 14 deletions(-) diff --git a/crates/viewer/re_view_spatial/src/visualizers/video/mod.rs b/crates/viewer/re_view_spatial/src/visualizers/video/mod.rs index 71e6859ac69e..88ecea1138f0 100644 --- a/crates/viewer/re_view_spatial/src/visualizers/video/mod.rs +++ b/crates/viewer/re_view_spatial/src/visualizers/video/mod.rs @@ -8,21 +8,28 @@ use re_renderer::resource_managers::ImageDataDesc; use re_sdk_types::ViewClassIdentifier; use re_sdk_types::blueprint::components::VisualizerInstructionId; use re_video::player::{VideoPlaybackIssueSeverity, VideoPlayerError}; -use re_viewer_context::{ViewClass as _, ViewContext, ViewId, ViewSystemIdentifier}; +use re_viewer_context::{ViewClass as _, ViewContext}; pub use video_frame_reference::VideoFrameReferenceVisualizer; pub use video_stream::VideoStreamVisualizer; use super::{LoadingIndicator, SpatialViewVisualizerData, UiLabel, UiLabelStyle, UiLabelTarget}; use crate::{PickableRectSourceData, PickableTexturedRect, SpatialView2D}; +pub const AT_TIME_CURSOR_SALT: u64 = 0x12356; + /// Identify a video stream for a given video. +/// +/// `time_track_salt` refers to a unique identifier for a certain way to play through time. +/// +/// For things following the given entity & component at the play head, use [`AT_TIME_CURSOR_SALT`] fn video_stream_id( entity_path: &EntityPath, - view_id: ViewId, - visualizer_name: ViewSystemIdentifier, + sample_component: re_sdk_types::ComponentIdentifier, + time_track_salt: u64, ) -> re_video::player::VideoPlayerStreamId { re_video::player::VideoPlayerStreamId( - re_log_types::hash::Hash64::hash((entity_path.hash(), view_id, visualizer_name)).hash64(), + re_log_types::hash::Hash64::hash((entity_path.hash(), sample_component, time_track_salt)) + .hash64(), ) } diff --git a/crates/viewer/re_view_spatial/src/visualizers/video/video_frame_reference.rs b/crates/viewer/re_view_spatial/src/visualizers/video/video_frame_reference.rs index 86a071fbfd2c..bb6e6f5b80f5 100644 --- a/crates/viewer/re_view_spatial/src/visualizers/video/video_frame_reference.rs +++ b/crates/viewer/re_view_spatial/src/visualizers/video/video_frame_reference.rs @@ -7,7 +7,7 @@ use re_sdk_types::Archetype as _; use re_sdk_types::archetypes::{AssetVideo, VideoFrameReference}; use re_sdk_types::components::{Blob, MediaType, Opacity, VideoTimestamp}; use re_viewer_context::{ - IdentifiedViewSystem, VideoAssetCache, ViewContext, ViewContextCollection, ViewId, ViewQuery, + IdentifiedViewSystem, VideoAssetCache, ViewContext, ViewContextCollection, ViewQuery, ViewSystemExecutionError, ViewerContext, VisualizerExecutionOutput, VisualizerQueryInfo, VisualizerSystem, typed_fallback_for, }; @@ -18,8 +18,8 @@ use crate::view_kind::SpatialViewKind; use crate::visualizers::SpatialViewVisualizerData; use crate::visualizers::entity_iterator::process_archetype; use crate::visualizers::video::{ - VideoFrameRenderInfo, VideoPlaybackIssue, VideoPlaybackIssueSeverity, show_video_frame, - video_stream_id, + AT_TIME_CURSOR_SALT, VideoFrameRenderInfo, VideoPlaybackIssue, VideoPlaybackIssueSeverity, + show_video_frame, video_stream_id, }; pub struct VideoFrameReferenceVisualizer { @@ -111,7 +111,6 @@ impl VisualizerSystem for VideoFrameReferenceVisualizer { ) }), entity_path, - view_query.view_id, ); } @@ -131,7 +130,6 @@ impl VisualizerSystem for VideoFrameReferenceVisualizer { } impl VideoFrameReferenceVisualizer { - #[expect(clippy::too_many_arguments)] fn process_video_frame( &mut self, ctx: &re_viewer_context::QueryContext<'_>, @@ -140,11 +138,14 @@ impl VideoFrameReferenceVisualizer { video_references: Option>, opacity: Opacity, entity_path: &EntityPath, - view_id: ViewId, ) { re_tracing::profile_function!(); - let player_stream_id = video_stream_id(entity_path, view_id, Self::identifier()); + let player_stream_id = video_stream_id( + entity_path, + VideoFrameReference::descriptor_video_reference().component, + AT_TIME_CURSOR_SALT, + ); // Follow the reference to the video asset. let video_reference: EntityPath = video_references diff --git a/crates/viewer/re_view_spatial/src/visualizers/video/video_stream.rs b/crates/viewer/re_view_spatial/src/visualizers/video/video_stream.rs index 6564a44fbddd..39337f47957a 100644 --- a/crates/viewer/re_view_spatial/src/visualizers/video/video_stream.rs +++ b/crates/viewer/re_view_spatial/src/visualizers/video/video_stream.rs @@ -16,8 +16,8 @@ use crate::visualizers::utilities::{ spatial_view_kind_from_view_class, transform_info_for_archetype_or_report_error, }; use crate::visualizers::video::{ - VideoFrameRenderInfo, VideoPlaybackIssue, VideoPlaybackIssueSeverity, show_video_frame, - video_stream_id, + AT_TIME_CURSOR_SALT, VideoFrameRenderInfo, VideoPlaybackIssue, VideoPlaybackIssueSeverity, + show_video_frame, video_stream_id, }; use crate::{PickableTexturedRect, SpatialView2D}; @@ -196,7 +196,11 @@ impl VisualizerSystem for VideoStreamVisualizer { video.video_renderer.frame_at( ctx.viewer_ctx.render_ctx(), - video_stream_id(entity_path, ctx.view_id, Self::identifier()), + video_stream_id( + entity_path, + VideoStream::descriptor_sample().component, + AT_TIME_CURSOR_SALT, + ), video_stream_time_from_query(&query_context.query), &|id| { let buffer = get_chunk_array(re_sdk_types::ChunkId::from_tuid(id)); From a81349d9eee5adee9530bb3e541ab3db2a39cdce Mon Sep 17 00:00:00 2001 From: Andrea Reale <154321632+andrea-reale@users.noreply.github.com> Date: Fri, 6 Mar 2026 18:04:43 +0100 Subject: [PATCH 067/513] fix: broken WorkOS: add back support for RS256 signatures ### Related * https://rerunio.slack.com/archives/C07T0LFT5BQ/p1772756725206719 ### What We accidentally dropped in #1024. This PR: * adds it back by implementing the corresponding provider (thanks Claude for the help). * send more information back to the client; change the SDK so that it actually returns this information to the human behind it. * Log the full error server side, but not too often. * Avoid a couple of footguns I stumbled upon that were preventing me to debug locally. The SDK would now scream: ``` PermissionError: invalid credentials: unsupported signature algorithm ``` Before it was ``` PermissionError: verifying server credentials ``` --------- Signed-off-by: Andrea Reale Source-Ref: 230acaa174b73fc0e47f1def47d49a0e612bd659 --- Cargo.lock | 1 + Cargo.toml | 1 + .../src/connection_registry.rs | 5 +- crates/utils/re_auth/Cargo.toml | 3 +- crates/utils/re_auth/src/crypto_provider.rs | 48 ++++++++++++++++++- crates/utils/re_auth/src/lib.rs | 5 -- crates/utils/re_auth/src/service/server.rs | 39 ++++++++++++++- 7 files changed, 91 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a2831c01d7d5..6cffd9a742ca 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8508,6 +8508,7 @@ dependencies = [ "rand 0.9.2", "re_analytics", "re_log", + "ring", "saturating_cast", "serde", "serde_json", diff --git a/Cargo.toml b/Cargo.toml index 418542ee7360..3b117c523e2a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -350,6 +350,7 @@ rexif = "0.7.5" rfd = { version = "0.17.2", default-features = false, features = ["xdg-portal"] } ron = { version = "0.11.0", features = ["integer128"] } roxmltree = "0.20.0" +ring = "0.17.14" rustdoc-json = "0.9.9" rustdoc-types = "0.57.1" rustls = { version = "0.23.37", default-features = false } diff --git a/crates/store/re_redap_client/src/connection_registry.rs b/crates/store/re_redap_client/src/connection_registry.rs index d5c5c17970b0..f8ac8a513659 100644 --- a/crates/store/re_redap_client/src/connection_registry.rs +++ b/crates/store/re_redap_client/src/connection_registry.rs @@ -420,18 +420,19 @@ impl ConnectionRegistryHandle { match request_result { // catch unauthenticated errors and forget the token if they happen Err(err) if err.code() == Code::Unauthenticated => { + let message = err.message().to_owned(); if let Some(credentials) = credentials { Err(ApiError::credentials_with_source( ClientCredentialsError::UnauthenticatedBadToken { status: err.into(), credentials, }, - "verifying connection to server", + message, )) } else { Err(ApiError::credentials_with_source( ClientCredentialsError::UnauthenticatedMissingToken(err.into()), - "verifying connection to server", + message, )) } } diff --git a/crates/utils/re_auth/Cargo.toml b/crates/utils/re_auth/Cargo.toml index 3fef30923100..668bad1b670f 100644 --- a/crates/utils/re_auth/Cargo.toml +++ b/crates/utils/re_auth/Cargo.toml @@ -24,7 +24,7 @@ workspace = true [features] cli = ["dep:indicatif", "dep:webbrowser", "oauth"] -oauth = ["dep:directories", "dep:ehttp", "dep:getrandom", "dep:tiny_http", "dep:uuid"] +oauth = ["dep:directories", "dep:ehttp", "dep:getrandom", "dep:ring", "dep:tiny_http", "dep:uuid"] [package.metadata.cargo-shear] ignored = [ @@ -42,6 +42,7 @@ http.workspace = true jiff = { workspace = true, features = ["serde"] } hmac.workspace = true jsonwebtoken.workspace = true +ring = { workspace = true, optional = true } parking_lot.workspace = true sha2.workspace = true signature.workspace = true diff --git a/crates/utils/re_auth/src/crypto_provider.rs b/crates/utils/re_auth/src/crypto_provider.rs index 23c752cc9bc7..6fbe86bda250 100644 --- a/crates/utils/re_auth/src/crypto_provider.rs +++ b/crates/utils/re_auth/src/crypto_provider.rs @@ -1,4 +1,4 @@ -//! Minimal [`CryptoProvider`] for `jsonwebtoken` that only supports HS256. +//! Minimal [`CryptoProvider`] for `jsonwebtoken` that supports HS256 and RS256. use hmac::{Hmac, Mac as _}; use jsonwebtoken::crypto::{CryptoProvider, JwkUtils, JwtSigner, JwtVerifier}; @@ -9,6 +9,8 @@ use signature::{Signer, Verifier}; type HmacSha256 = Hmac; +// --- HS256 --- + struct Hs256Signer(HmacSha256); impl Hs256Signer { @@ -58,6 +60,48 @@ impl JwtVerifier for Hs256Verifier { } } +// --- RS256 --- + +#[cfg(feature = "oauth")] +struct Rs256Verifier(DecodingKey); + +#[cfg(feature = "oauth")] +impl Rs256Verifier { + fn new(key: &DecodingKey) -> Self { + Self(key.clone()) + } +} + +#[cfg(feature = "oauth")] +impl Verifier> for Rs256Verifier { + fn verify(&self, msg: &[u8], sig: &Vec) -> Result<(), signature::Error> { + use jsonwebtoken::DecodingKeyKind; + use ring::signature as ring_sig; + + match self.0.kind() { + DecodingKeyKind::SecretOrDer(bytes) => { + let public_key = + ring_sig::UnparsedPublicKey::new(&ring_sig::RSA_PKCS1_2048_8192_SHA256, bytes); + public_key.verify(msg, sig) + } + DecodingKeyKind::RsaModulusExponent { n, e } => { + let components = ring_sig::RsaPublicKeyComponents { n, e }; + components.verify(&ring_sig::RSA_PKCS1_2048_8192_SHA256, msg, sig) + } + } + .map_err(|_err| signature::Error::from_source("RSA signature verification failed")) + } +} + +#[cfg(feature = "oauth")] +impl JwtVerifier for Rs256Verifier { + fn algorithm(&self) -> Algorithm { + Algorithm::RS256 + } +} + +// --- + fn unsupported_algorithm(algo: &Algorithm) -> Error { re_log::debug_panic!("DEBUG PANIC: unsupported algorithm: {algo:?}"); @@ -77,6 +121,8 @@ fn verifier_factory( ) -> Result, Error> { match algorithm { Algorithm::HS256 => Ok(Box::new(Hs256Verifier::new(key)?)), + #[cfg(feature = "oauth")] + Algorithm::RS256 => Ok(Box::new(Rs256Verifier::new(key))), other => Err(unsupported_algorithm(other)), } } diff --git a/crates/utils/re_auth/src/lib.rs b/crates/utils/re_auth/src/lib.rs index 0c4d45ac0651..ad3fd3d33d86 100644 --- a/crates/utils/re_auth/src/lib.rs +++ b/crates/utils/re_auth/src/lib.rs @@ -85,9 +85,4 @@ pub const ERROR_MESSAGE_MALFORMED_CREDENTIALS: &str = "malformed auth token"; /// The associated status code will always be `Unauthenticated`. pub const ERROR_MESSAGE_MISSING_CREDENTIALS: &str = "missing credentials"; -/// The error message in Tonic's gRPC status when a _valid token_ did not have the required permissions. -/// -/// The associated status code will always be `Unauthenticated`. -pub const ERROR_MESSAGE_INVALID_CREDENTIALS: &str = "invalid credentials"; - mod wasm_compat; diff --git a/crates/utils/re_auth/src/service/server.rs b/crates/utils/re_auth/src/service/server.rs index c7cbf6e5bda1..cf6d84e350ee 100644 --- a/crates/utils/re_auth/src/service/server.rs +++ b/crates/utils/re_auth/src/service/server.rs @@ -1,3 +1,6 @@ +use std::sync::atomic::{AtomicU64, Ordering}; +use std::time::SystemTime; + use tonic::metadata::{Ascii, MetadataValue}; use tonic::service::Interceptor; use tonic::{Request, Status}; @@ -64,8 +67,40 @@ impl Interceptor for Authenticator { let claims = self .provider .verify(&token, VerificationOptions::default()) - .map_err(|_err| { - Status::unauthenticated(crate::ERROR_MESSAGE_INVALID_CREDENTIALS) + .map_err(|err| { + // Log the full error server-side, best-effort + // rate-limited to at most once per second to avoid + // log storms. + static LAST_LOG_MS: AtomicU64 = AtomicU64::new(0); + const ONE_SECOND_MS: u64 = 1_000; + let now_ms = SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .map_or(0, |d| d.as_millis() as u64); + if now_ms.saturating_sub(LAST_LOG_MS.load(Ordering::Relaxed)) > ONE_SECOND_MS { + LAST_LOG_MS.store(now_ms, Ordering::Relaxed); + re_log::warn!("Token verification failed: {err:#}"); + } + + // Explicitly provide more detail in the error message, but do not rely + // on the error's `Display` implementation, as it may contain sensitive + // information. + let detail = match err { + Error::Jwt(ref jwt_err) => match jwt_err.kind() { + jsonwebtoken::errors::ErrorKind::ExpiredSignature => { + "token has expired" + } + jsonwebtoken::errors::ErrorKind::InvalidSignature => { + "invalid token signature" + } + jsonwebtoken::errors::ErrorKind::InvalidAlgorithm => { + "unsupported signature algorithm" + } + _ => "invalid token", + }, + Error::MalformedToken => "malformed token", + _ => "invalid credentials", + }; + Status::unauthenticated(detail) })?; req.extensions_mut().insert(UserContext { From d962bfe1b77ebd20ed4c619622505fdda98a43db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jochen=20G=C3=B6rtler?= Date: Fri, 6 Mar 2026 18:14:11 +0100 Subject: [PATCH 068/513] Redesign Lens `Op` API to be `Selector`-based MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### Related * Related to #1002. * Related to RR-3766. * Related to RR-3889. * Preparation for #665. ### What This PR removes the ad-hoc definition of `Op` in the Lenses AST and replaces it with a `Selector`-based solution. It's not all the way there, but the general structure should make it easy to add functions in a `Runtime` in the future. I've transitioned all helper functions to return the new `Result, Error>` type. In some cases I've also unwrapped the inner `MapList` from helpers, because we'd call those using the new `Selector` syntax in the future. ### Testing All tests should still pass. @MichaelGrupp might have a special interest for Lenses that live in other repositories. ### Follow-ups * Most Lenses that use `Selector::then` are a bit awkward. There is still some improvements that we can make, but this PR is big enough already 😬. * This PR also prepares the future crate split between `re_lenses_core` and `re_lenses`. Source-Ref: 5144abe5b35604a68741707b3b9ed746883f4367 --- crates/store/re_arrow_combinators/src/cast.rs | 11 +- .../store/re_arrow_combinators/src/index.rs | 26 +- crates/store/re_arrow_combinators/src/lib.rs | 2 +- crates/store/re_arrow_combinators/src/map.rs | 96 ++++--- .../store/re_arrow_combinators/src/reshape.rs | 69 ++--- .../re_arrow_combinators/src/selector/mod.rs | 29 +- .../src/selector/runtime.rs | 8 +- .../re_arrow_combinators/src/transform.rs | 48 ++-- .../tests/test_explode.rs | 30 +- .../tests/test_selector.rs | 31 +- .../tests/test_string_transforms.rs | 34 ++- .../tests/test_transform.rs | 101 ++++--- .../lenses/foxglove/camera_calibration.rs | 38 +-- .../lenses/foxglove/compressed_image.rs | 21 +- .../lenses/foxglove/compressed_video.rs | 21 +- .../lenses/foxglove/frame_transform.rs | 31 +- .../lenses/foxglove/frame_transforms.rs | 37 +-- .../src/loader_mcap/lenses/foxglove/log.rs | 51 ++-- .../lenses/foxglove/packed_element_field.rs | 40 +-- .../lenses/foxglove/point_cloud.rs | 25 +- .../lenses/foxglove/pose_in_frame.rs | 28 +- .../lenses/foxglove/poses_in_frame.rs | 28 +- .../loader_mcap/lenses/foxglove/raw_image.rs | 24 +- .../src/loader_mcap/lenses/helpers.rs | 66 ++--- .../src/loader_mcap/lenses/image_helpers.rs | 58 ++-- crates/store/re_lenses/src/ast.rs | 265 +++--------------- crates/store/re_lenses/src/builder.rs | 97 ++++--- crates/store/re_lenses/src/error.rs | 9 +- crates/store/re_lenses/src/lib.rs | 6 +- crates/store/re_lenses/src/op.rs | 56 ---- crates/store/re_lenses/src/op/basic.rs | 52 ++++ crates/store/re_lenses/src/op/mod.rs | 15 + .../store/re_lenses/src/{ => op}/semantic.rs | 161 +++++++---- crates/store/re_lenses/src/op/string.rs | 6 + crates/top/re_sdk/src/lenses/mod.rs | 6 +- crates/top/re_sdk/tests/lenses/operations.rs | 99 +++---- crates/top/re_sdk/tests/lenses/output_mode.rs | 17 +- examples/rust/lenses/src/main.rs | 35 ++- 38 files changed, 841 insertions(+), 936 deletions(-) delete mode 100644 crates/store/re_lenses/src/op.rs create mode 100644 crates/store/re_lenses/src/op/basic.rs create mode 100644 crates/store/re_lenses/src/op/mod.rs rename crates/store/re_lenses/src/{ => op}/semantic.rs (78%) create mode 100644 crates/store/re_lenses/src/op/string.rs diff --git a/crates/store/re_arrow_combinators/src/cast.rs b/crates/store/re_arrow_combinators/src/cast.rs index cc1a429838e9..8f85570f2065 100644 --- a/crates/store/re_arrow_combinators/src/cast.rs +++ b/crates/store/re_arrow_combinators/src/cast.rs @@ -43,7 +43,7 @@ where type Source = PrimitiveArray; type Target = PrimitiveArray; - fn transform(&self, source: &PrimitiveArray) -> Result, Error> { + fn transform(&self, source: &PrimitiveArray) -> Result>, Error> { let source_ref: &dyn Array = source; let target_type = T::DATA_TYPE; let casted = cast(source_ref, &target_type)?; @@ -74,7 +74,7 @@ where type Source = ArrayRef; type Target = PrimitiveArray; - fn transform(&self, source: &ArrayRef) -> Result, Error> { + fn transform(&self, source: &ArrayRef) -> Result>, Error> { source .as_any() .downcast_ref::>() @@ -84,6 +84,7 @@ where context: "downcast_ref".to_owned(), }) .cloned() + .map(Some) } } @@ -106,7 +107,7 @@ impl Transform for ListToFixedSizeList { type Source = arrow::array::ListArray; type Target = arrow::array::FixedSizeListArray; - fn transform(&self, source: &Self::Source) -> Result { + fn transform(&self, source: &Self::Source) -> Result, Error> { // Check that each list has exactly the expected length (or is null). let offsets = source.value_offsets(); let expected_length = self.value_length as usize; @@ -129,11 +130,11 @@ impl Transform for ListToFixedSizeList { source.value_type().clone(), source.is_nullable(), )); - Ok(arrow::array::FixedSizeListArray::try_new( + Ok(Some(arrow::array::FixedSizeListArray::try_new( field, self.value_length, source.values().clone(), source.nulls().cloned(), - )?) + )?)) } } diff --git a/crates/store/re_arrow_combinators/src/index.rs b/crates/store/re_arrow_combinators/src/index.rs index 065810e7c513..62045a9a6877 100644 --- a/crates/store/re_arrow_combinators/src/index.rs +++ b/crates/store/re_arrow_combinators/src/index.rs @@ -31,16 +31,16 @@ impl Transform for GetIndexList { type Source = ListArray; type Target = ArrayRef; - fn transform(&self, source: &ListArray) -> Result { + fn transform(&self, source: &ListArray) -> Result, Error> { let offsets = source.offsets(); let values = source.values(); // If values is empty, all lists are empty, so all results are null. if values.is_empty() { - return Ok(arrow::array::new_null_array( + return Ok(Some(arrow::array::new_null_array( values.data_type(), source.len(), - )); + ))); } // Collect indices to extract from the values array @@ -93,7 +93,7 @@ impl Transform for GetIndexList { let new_data = result_data.into_builder().nulls(combined_nulls).build()?; result = arrow::array::make_array(new_data); - Ok(result) + Ok(Some(result)) } } @@ -104,39 +104,43 @@ mod tests { use arrow::datatypes::Int32Type; #[test] - fn test_get_index_basic() { + fn test_get_index_basic() -> Result<(), Box> { let input = ListArray::from_iter_primitive::(vec![ Some(vec![Some(1), Some(2), Some(3)]), Some(vec![Some(4), Some(5)]), ]); - let result = GetIndexList::new(0).transform(&input).unwrap(); + let result = GetIndexList::new(0).transform(&input)?.unwrap(); let result_i32 = result.as_primitive::(); assert_eq!(result_i32.len(), 2); assert_eq!(result_i32.value(0), 1); assert_eq!(result_i32.value(1), 4); + + Ok(()) } #[test] - fn test_get_index_out_of_bounds() { + fn test_get_index_out_of_bounds() -> Result<(), Box> { let input = ListArray::from_iter_primitive::(vec![ Some(vec![Some(1), Some(2)]), Some(vec![Some(3)]), Some(vec![]), ]); - let result = GetIndexList::new(5).transform(&input).unwrap(); + let result = GetIndexList::new(5).transform(&input)?.unwrap(); let result_i32 = result.as_primitive::(); assert_eq!(result_i32.len(), 3); assert!(result_i32.is_null(0)); // Out of bounds assert!(result_i32.is_null(1)); // Out of bounds assert!(result_i32.is_null(2)); // Empty list + + Ok(()) } #[test] - fn test_get_index_with_nulls() { + fn test_get_index_with_nulls() -> Result<(), Box> { let input = ListArray::from_iter_primitive::(vec![ Some(vec![Some(1), Some(2)]), None, @@ -144,7 +148,7 @@ mod tests { Some(vec![]), ]); - let result = GetIndexList::new(1).transform(&input).unwrap(); + let result = GetIndexList::new(1).transform(&input)?.unwrap(); let result_i32 = result.as_primitive::(); assert_eq!(result_i32.len(), 4); @@ -152,5 +156,7 @@ mod tests { assert!(result_i32.is_null(1)); // Null row assert!(result_i32.is_null(2)); // Null element at index 1 assert!(result_i32.is_null(3)); // Out of bounds (empty list) + + Ok(()) } } diff --git a/crates/store/re_arrow_combinators/src/lib.rs b/crates/store/re_arrow_combinators/src/lib.rs index ac78f1d86d1a..56c39389f8ed 100644 --- a/crates/store/re_arrow_combinators/src/lib.rs +++ b/crates/store/re_arrow_combinators/src/lib.rs @@ -19,4 +19,4 @@ mod selector; pub use crate::cast::{DowncastRef, ListToFixedSizeList, PrimitiveCast}; pub use crate::error::Error; pub use crate::selector::{Error as SelectorError, Selector, extract_nested_fields}; -pub use crate::transform::{Compose, Transform}; +pub use crate::transform::Transform; diff --git a/crates/store/re_arrow_combinators/src/map.rs b/crates/store/re_arrow_combinators/src/map.rs index 7d9945cf58c8..4e30303c6545 100644 --- a/crates/store/re_arrow_combinators/src/map.rs +++ b/crates/store/re_arrow_combinators/src/map.rs @@ -34,7 +34,7 @@ where type Source = ListArray; type Target = ListArray; - fn transform(&self, source: &ListArray) -> Result { + fn transform(&self, source: &ListArray) -> Result, Error> { let (field, offsets, values, nulls) = source.clone().into_parts(); let downcast = values @@ -45,18 +45,23 @@ where actual: values.data_type().clone(), })?; - let transformed = self.transform.transform(downcast)?; - - let new_field = field - .as_ref() - .clone() - .with_data_type(transformed.data_type().clone()); - Ok(ListArray::new( - new_field.into(), - offsets, - Arc::new(transformed), - nulls, - )) + let inner = self.transform.transform(downcast)?; + + match inner { + Some(transformed) => { + let new_field = field + .as_ref() + .clone() + .with_data_type(transformed.data_type().clone()); + Ok(Some(ListArray::new( + new_field.into(), + offsets, + Arc::new(transformed), + nulls, + ))) + } + None => Ok(None), + } } } @@ -85,7 +90,7 @@ where type Source = FixedSizeListArray; type Target = FixedSizeListArray; - fn transform(&self, source: &FixedSizeListArray) -> Result { + fn transform(&self, source: &FixedSizeListArray) -> Result, Error> { let values = source.values(); let downcast = values.as_any().downcast_ref::().ok_or_else(|| { Error::UnexpectedFixedSizeListValueType { @@ -94,20 +99,25 @@ where } })?; - let transformed = self.transform.transform(downcast)?; - let field = Arc::new(Field::new_list_field( - transformed.data_type().clone(), - transformed.is_nullable(), - )); + let inner = self.transform.transform(downcast)?; let size = source.value_length(); let nulls = source.nulls().cloned(); - Ok(FixedSizeListArray::new( - field, - size, - Arc::new(transformed), - nulls, - )) + match inner { + Some(transformed) => { + let field = Arc::new(Field::new_list_field( + transformed.data_type().clone(), + transformed.is_nullable(), + )); + Ok(Some(FixedSizeListArray::new( + field, + size, + Arc::new(transformed), + nulls, + ))) + } + None => Ok(None), + } } } @@ -152,9 +162,9 @@ where type Source = PrimitiveArray; type Target = PrimitiveArray; - fn transform(&self, source: &PrimitiveArray) -> Result, Error> { + fn transform(&self, source: &PrimitiveArray) -> Result>, Error> { let result: PrimitiveArray = source.iter().map(|opt| opt.map(|v| (self.f)(v))).collect(); - Ok(result) + Ok(Some(result)) } } @@ -191,12 +201,12 @@ where type Source = PrimitiveArray; type Target = PrimitiveArray; - fn transform(&self, source: &PrimitiveArray) -> Result, Error> { + fn transform(&self, source: &PrimitiveArray) -> Result>, Error> { let result: PrimitiveArray = source .iter() .map(|opt| Some(opt.unwrap_or(self.default_value))) .collect(); - Ok(result) + Ok(Some(result)) } } @@ -232,7 +242,7 @@ impl Transform for StringPrefix { type Source = StringArray; type Target = StringArray; - fn transform(&self, source: &StringArray) -> Result { + fn transform(&self, source: &StringArray) -> Result, Error> { let result: StringArray = source .iter() .map(|opt| { @@ -246,7 +256,7 @@ impl Transform for StringPrefix { }) }) .collect(); - Ok(result) + Ok(Some(result)) } } @@ -282,7 +292,7 @@ impl Transform for StringSuffix { type Source = StringArray; type Target = StringArray; - fn transform(&self, source: &StringArray) -> Result { + fn transform(&self, source: &StringArray) -> Result, Error> { let result: StringArray = source .iter() .map(|opt| { @@ -296,6 +306,26 @@ impl Transform for StringSuffix { }) }) .collect(); - Ok(result) + Ok(Some(result)) } } + +/// Prepends a prefix to each string value, including empty strings. +pub fn string_prefix(prefix: impl Into) -> StringPrefix { + StringPrefix::new(prefix) +} + +/// Prepends a prefix to each non-empty string value, leaving empty strings unchanged. +pub fn string_prefix_nonempty(prefix: impl Into) -> StringPrefix { + StringPrefix::new(prefix).with_prefix_empty_string(false) +} + +/// Appends a suffix to each string value, including empty strings. +pub fn string_suffix(suffix: impl Into) -> StringSuffix { + StringSuffix::new(suffix) +} + +/// Appends a suffix to each non-empty string value, leaving empty strings unchanged. +pub fn string_suffix_nonempty(suffix: impl Into) -> StringSuffix { + StringSuffix::new(suffix).with_suffix_empty_string(false) +} diff --git a/crates/store/re_arrow_combinators/src/reshape.rs b/crates/store/re_arrow_combinators/src/reshape.rs index b6e7274b9f96..1a45ef9d1a55 100644 --- a/crates/store/re_arrow_combinators/src/reshape.rs +++ b/crates/store/re_arrow_combinators/src/reshape.rs @@ -16,7 +16,7 @@ use crate::{Error, Transform}; /// Extracts a field from a struct array. /// -/// Returns the field's array if it exists, otherwise returns an error. +/// Returns `None` if the field does not exist in the struct. #[derive(Clone)] pub struct GetField { field_name: String, @@ -35,17 +35,10 @@ impl Transform for GetField { type Source = StructArray; type Target = ArrayRef; - fn transform(&self, source: &StructArray) -> Result { - let field_array = source - .column_by_name(&self.field_name) - .ok_or_else(|| { - let available_fields = source.fields().iter().map(|f| f.name().clone()).collect(); - Error::FieldNotFound { - field_name: self.field_name.clone(), - available_fields, - } - })? - .clone(); + fn transform(&self, source: &StructArray) -> Result, Error> { + let Some(field_array) = source.column_by_name(&self.field_name).cloned() else { + return Ok(None); + }; // If the struct has nulls, we need to combine them with the field's nulls // because in Arrow, when a struct is null, its fields should also be null @@ -68,10 +61,10 @@ impl Transform for GetField { .into_builder() .nulls(Some(combined_nulls)) .build()?; - Ok(arrow::array::make_array(new_data)) + Ok(Some(arrow::array::make_array(new_data))) } else { // No struct nulls - just return the field as-is - Ok(field_array) + Ok(Some(field_array)) } } } @@ -117,7 +110,7 @@ impl Transform for Flatten { type Source = ListArray; type Target = ListArray; - fn transform(&self, source: &ListArray) -> Result { + fn transform(&self, source: &ListArray) -> Result, Error> { let values = source.values(); // The values should be a ListArray (or FixedSizeListArray) that we want to flatten @@ -170,12 +163,12 @@ impl Transform for Flatten { )); let offsets = arrow::buffer::OffsetBuffer::new(new_offsets.into()); - return Ok(ListArray::new( + return Ok(Some(ListArray::new( field, offsets, inner_values.clone(), source.nulls().cloned(), - )); + ))); } // General case: build new offsets and collect value ranges @@ -245,12 +238,12 @@ impl Transform for Flatten { )); let offsets = arrow::buffer::OffsetBuffer::new(new_offsets.into()); - Ok(ListArray::new( + Ok(Some(ListArray::new( field, offsets, flattened_values, source.nulls().cloned(), - )) + ))) } } @@ -282,14 +275,19 @@ impl Transform for StructToFixedList { type Source = StructArray; type Target = FixedSizeListArray; - fn transform(&self, source: &StructArray) -> Result { + fn transform(&self, source: &StructArray) -> Result, Error> { if self.field_names.is_empty() { return Err(Error::NoFieldNames); } // Get the first field to determine the element type let first_field_name = &self.field_names[0]; - let first_array = GetField::new(first_field_name).transform(source)?; + let first_array = GetField::new(first_field_name) + .transform(source)? + .ok_or_else(|| Error::FieldNotFound { + field_name: first_field_name.clone(), + available_fields: source.fields().iter().map(|f| f.name().clone()).collect(), + })?; let element_type = first_array.data_type().clone(); // Collect all field arrays, ensuring they all have the same type @@ -297,7 +295,12 @@ impl Transform for StructToFixedList { field_arrays.push(first_array); for field_name in &self.field_names[1..] { - let array = GetField::new(field_name).transform(source)?; + let array = GetField::new(field_name) + .transform(source)? + .ok_or_else(|| Error::FieldNotFound { + field_name: field_name.clone(), + available_fields: source.fields().iter().map(|f| f.name().clone()).collect(), + })?; // Verify type consistency if array.data_type() != &element_type { @@ -331,9 +334,9 @@ impl Transform for StructToFixedList { actual: list_size, err, })?; - Ok(FixedSizeListArray::new( + Ok(Some(FixedSizeListArray::new( field, list_size, values, None, // No outer nulls - )) + ))) } } @@ -344,7 +347,7 @@ impl Transform for PromoteInnerNulls { type Source = ListArray; type Target = ListArray; - fn transform(&self, source: &ListArray) -> Result { + fn transform(&self, source: &ListArray) -> Result, Error> { let (field, offsets, values, nulls) = source.clone().into_parts(); let inner_nulls = values.logical_nulls(); @@ -367,12 +370,12 @@ impl Transform for PromoteInnerNulls { null_buf.append(valid); } - Ok(ListArray::new( + Ok(Some(ListArray::new( field, offsets, values, Some(NullBuffer::from(null_buf.finish())), - )) + ))) } } @@ -392,7 +395,7 @@ impl Transform for Explode { type Source = ListArray; type Target = ListArray; - fn transform(&self, source: &Self::Source) -> Result { + fn transform(&self, source: &Self::Source) -> Result, Error> { let values_array = source.values(); let offsets = source.offsets(); @@ -467,12 +470,12 @@ impl Transform for Explode { }; let field = Arc::new(Field::new_list_field(source.value_type(), true)); - Ok(ListArray::new( + Ok(Some(ListArray::new( field, OffsetBuffer::new(new_offsets.into()), values, Some(NullBuffer::from(new_validity)), - )) + ))) } } @@ -509,7 +512,7 @@ impl Transform for RowMajorToColumnMajor { type Source = FixedSizeListArray; type Target = FixedSizeListArray; - fn transform(&self, source: &Self::Source) -> Result { + fn transform(&self, source: &Self::Source) -> Result, Error> { // First, check that the input array has the expected value length. let expected_list_size = self.output_rows * self.output_columns; let value_length = source.value_length() as usize; @@ -541,11 +544,11 @@ impl Transform for RowMajorToColumnMajor { source.value_type().clone(), source.is_nullable(), )); - Ok(FixedSizeListArray::new( + Ok(Some(FixedSizeListArray::new( field, source.value_length(), reordered_values, source.nulls().cloned(), - )) + ))) } } diff --git a/crates/store/re_arrow_combinators/src/selector/mod.rs b/crates/store/re_arrow_combinators/src/selector/mod.rs index f756d1f08b96..3ae29ae351ff 100644 --- a/crates/store/re_arrow_combinators/src/selector/mod.rs +++ b/crates/store/re_arrow_combinators/src/selector/mod.rs @@ -43,8 +43,8 @@ mod parser; mod runtime; use arrow::{ - array::{Array as _, ListArray}, - datatypes::{DataType, Field, Fields}, + array::ListArray, + datatypes::{DataType, Fields}, }; use vec1::Vec1; @@ -61,6 +61,13 @@ impl std::fmt::Display for Selector { } impl Selector { + /// Parse a selector from a query string. + /// + /// This is a convenience wrapper around [`FromStr`](std::str::FromStr). + pub fn parse(query: &str) -> Result { + query.parse() + } + /// Execute this selector against each row of a [`ListArray`]. /// /// Performs implicit iteration over the inner list array, and reconstructs the array at the end. @@ -92,9 +99,8 @@ impl crate::Transform for Selector { type Source = ListArray; type Target = ListArray; - fn transform(&self, source: &Self::Source) -> Result { - let result = self.execute_per_row(source).map_err(crate::Error::from)?; - Ok(result.unwrap_or_else(|| null_list_like(source))) + fn transform(&self, source: &Self::Source) -> Result, crate::Error> { + self.execute_per_row(source).map_err(crate::Error::from) } } @@ -102,20 +108,11 @@ impl crate::Transform for &Selector { type Source = ListArray; type Target = ListArray; - fn transform(&self, source: &Self::Source) -> Result { - let result = self.execute_per_row(source).map_err(crate::Error::from)?; - Ok(result.unwrap_or_else(|| null_list_like(source))) + fn transform(&self, source: &Self::Source) -> Result, crate::Error> { + self.execute_per_row(source).map_err(crate::Error::from) } } -/// Creates an all-null [`ListArray`] with the same type and length as `source`. -fn null_list_like(source: &ListArray) -> ListArray { - ListArray::new_null( - Field::new_list_field(source.value_type(), true).into(), - source.len(), - ) -} - /// Errors that can occur during selector parsing or execution. #[derive(Debug, thiserror::Error, Clone)] pub enum Error { diff --git a/crates/store/re_arrow_combinators/src/selector/runtime.rs b/crates/store/re_arrow_combinators/src/selector/runtime.rs index 3576fe5ae6db..dea144bbb229 100644 --- a/crates/store/re_arrow_combinators/src/selector/runtime.rs +++ b/crates/store/re_arrow_combinators/src/selector/runtime.rs @@ -37,7 +37,7 @@ fn values_downcasts_to(array: &ListArray) -> bool { } impl SegmentKind { - fn execute(&self, source: &ListArray) -> Result { + fn execute(&self, source: &ListArray) -> Result, crate::Error> { match self { Self::Field(field_name) => { MapList::new(GetField::new(field_name.clone())).transform(source) @@ -76,8 +76,12 @@ impl Segment { Err(err) => return Err(err), }; + let Some(result) = result else { + return Ok(None); + }; + if self.assert_non_null { - Ok(Some(PromoteInnerNulls.transform(&result)?)) + PromoteInnerNulls.transform(&result) } else { Ok(Some(result)) } diff --git a/crates/store/re_arrow_combinators/src/transform.rs b/crates/store/re_arrow_combinators/src/transform.rs index a27812557b73..8c9c372a2e3b 100644 --- a/crates/store/re_arrow_combinators/src/transform.rs +++ b/crates/store/re_arrow_combinators/src/transform.rs @@ -1,44 +1,54 @@ -use arrow::array::Array; +use arrow::array::{Array, ListArray}; use crate::Error; -/// A transformation that converts one Arrow array type to another. +/// A fallible transformation from one Arrow array to another. /// -/// Transformations are read-only operations that may fail (e.g., missing field, type mismatch). -/// They can be composed using the `then` method to create complex transformation pipelines. +/// Can be composed using the [`then`](Transform::then) method to create transformation pipelines. +/// +/// Some transformations may decide not to output a value (e.g. when a struct field is not found), +/// which is represented by returning `Ok(None)`. When composing transforms using [`Transform::then`] +/// and the first returned `Ok(None)`, then the second [`Transform`] will not be executed. pub trait Transform { - /// The source array type. type Source: Array; - - /// The target array type. type Target: Array; /// Apply the transformation to the source array. - fn transform(&self, source: &Self::Source) -> Result; + fn transform(&self, source: &Self::Source) -> Result, Error>; /// Chain this transformation with another transformation. - fn then(self, next: T2) -> Compose + fn then(self, next: T2) -> Then where Self: Sized, T2: Transform, { - Compose { + Then { first: self, second: next, } } } -/// Composes two transformations into a single transformation. -/// -/// This is the result of calling `.then()` on a transformation. +impl Transform for T +where + T: Fn(&ListArray) -> Result, Error>, +{ + type Source = ListArray; + type Target = ListArray; + + fn transform(&self, source: &Self::Source) -> Result, Error> { + (self)(source) + } +} + +/// Composed transformation created by calling [`.then()`](Transform::then). #[derive(Clone)] -pub struct Compose { +pub struct Then { first: T1, second: T2, } -impl Transform for Compose +impl Transform for Then where T1: Transform, T2: Transform, @@ -47,8 +57,10 @@ where type Source = T1::Source; type Target = T2::Target; - fn transform(&self, source: &Self::Source) -> Result { - let mid = self.first.transform(source)?; - self.second.transform(&mid) + fn transform(&self, source: &Self::Source) -> Result, Error> { + match self.first.transform(source)? { + Some(mid) => self.second.transform(&mid), + None => Ok(None), + } } } diff --git a/crates/store/re_arrow_combinators/tests/test_explode.rs b/crates/store/re_arrow_combinators/tests/test_explode.rs index 6933987b1c79..369a8ef4a7a8 100644 --- a/crates/store/re_arrow_combinators/tests/test_explode.rs +++ b/crates/store/re_arrow_combinators/tests/test_explode.rs @@ -10,7 +10,7 @@ use re_arrow_combinators::reshape::Explode; use util::DisplayRB; #[test] -fn test_explode_primitives() { +fn test_explode_primitives() -> Result<(), Box> { let input = ListArray::from_iter_primitive::(vec![ Some(vec![Some(1), Some(2), Some(3)]), Some(vec![Some(4), Some(5)]), @@ -32,7 +32,7 @@ fn test_explode_primitives() { "); let explode = Explode; - let result = explode.transform(&input).unwrap(); + let result = explode.transform(&input)?.unwrap(); insta::assert_snapshot!(format!("{}", DisplayRB(result)), @r" ┌───────────────────┐ @@ -53,10 +53,12 @@ fn test_explode_primitives() { │ [6] │ └───────────────────┘ "); + + Ok(()) } #[test] -fn test_explode_with_nulls_and_empty() { +fn test_explode_with_nulls_and_empty() -> Result<(), Box> { let input = ListArray::from_iter_primitive::(vec![ Some(vec![Some(1), Some(2)]), None, @@ -81,7 +83,7 @@ fn test_explode_with_nulls_and_empty() { "); let explode = Explode; - let result = explode.transform(&input).unwrap(); + let result = explode.transform(&input)?.unwrap(); insta::assert_snapshot!(format!("{}", DisplayRB(result)), @r" ┌───────────────────┐ @@ -100,10 +102,12 @@ fn test_explode_with_nulls_and_empty() { │ [3] │ └───────────────────┘ "); + + Ok(()) } #[test] -fn test_explode_nested_lists() { +fn test_explode_nested_lists() -> Result<(), Box> { let inner_values = Int32Array::from(vec![1, 2, 3, 4, 5, 6]); let inner_offsets = OffsetBuffer::new(vec![0, 2, 3, 6].into()); let inner_field = Arc::new(Field::new_list_field(DataType::Int32, true)); @@ -131,7 +135,7 @@ fn test_explode_nested_lists() { "); let explode = Explode; - let result = explode.transform(&input).unwrap(); + let result = explode.transform(&input)?.unwrap(); insta::assert_snapshot!(format!("{}", DisplayRB(result)), @r" ┌─────────────────────────┐ @@ -146,17 +150,19 @@ fn test_explode_nested_lists() { │ [[4, 5, 6]] │ └─────────────────────────┘ "); + + Ok(()) } #[test] -fn test_explode_empty_input() { +fn test_explode_empty_input() -> Result<(), Box> { // Test exploding an empty list let input = ListArray::from_iter_primitive::(Vec::< Option>>, >::new()); let explode = Explode; - let result = explode.transform(&input).unwrap(); + let result = explode.transform(&input)?.unwrap(); insta::assert_snapshot!(format!("{}", DisplayRB(result)), @r" ┌───────────────────┐ @@ -166,10 +172,12 @@ fn test_explode_empty_input() { ╞═══════════════════╡ └───────────────────┘ "); + + Ok(()) } #[test] -fn test_explode_with_skips_in_offset_buffer() { +fn test_explode_with_skips_in_offset_buffer() -> Result<(), Box> { let values = Int32Array::from(vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); let offsets = OffsetBuffer::new(vec![0, 2, 7, 10].into()); let validity = arrow::buffer::NullBuffer::from(vec![true, false, true]); @@ -192,7 +200,7 @@ fn test_explode_with_skips_in_offset_buffer() { "); let explode = Explode; - let result = explode.transform(&input).unwrap(); + let result = explode.transform(&input)?.unwrap(); insta::assert_snapshot!(format!("{}", DisplayRB(result)), @r" ┌───────────────────┐ @@ -213,4 +221,6 @@ fn test_explode_with_skips_in_offset_buffer() { │ [9] │ └───────────────────┘ "); + + Ok(()) } diff --git a/crates/store/re_arrow_combinators/tests/test_selector.rs b/crates/store/re_arrow_combinators/tests/test_selector.rs index b68b59522b7d..7fff6aabfa2e 100644 --- a/crates/store/re_arrow_combinators/tests/test_selector.rs +++ b/crates/store/re_arrow_combinators/tests/test_selector.rs @@ -243,14 +243,10 @@ fn execute_missing_field() { let result = ".nonexistent" .parse::() .unwrap() - .execute_per_row(&array); + .execute_per_row(&array) + .expect("should not error"); - assert!(matches!( - result, - Err(Error::Runtime( - re_arrow_combinators::Error::FieldNotFound { .. } - )) - )); + assert!(result.is_none(), "missing field should return None"); } #[test] @@ -349,21 +345,12 @@ fn execute_each_on_fixed_size_list() -> Result<(), Error> { fn execute_optional_field() -> Result<(), Error> { let array = fixtures::nested_struct_column(); - // Without `?`, accessing a field that doesn't exist errors. - let err = ".location.z".parse::()?.execute_per_row(&array); - assert!(matches!( - err, - Err(Error::Runtime( - re_arrow_combinators::Error::FieldNotFound { .. } - )) - )); - let err = ".foo.x".parse::()?.execute_per_row(&array); - assert!(matches!( - err, - Err(Error::Runtime( - re_arrow_combinators::Error::FieldNotFound { .. } - )) - ),); + // Accessing a field that doesn't exist returns `None`. + let result = ".location.z".parse::()?.execute_per_row(&array)?; + assert!(result.is_none(), "missing field should return None"); + + let result = ".foo.x".parse::()?.execute_per_row(&array)?; + assert!(result.is_none(), "missing field should return None"); // With `?`, the missing field is suppressed and we get `None` instead. let result = ".location.z?" diff --git a/crates/store/re_arrow_combinators/tests/test_string_transforms.rs b/crates/store/re_arrow_combinators/tests/test_string_transforms.rs index 8d6e8011fe07..483cb0a6bb2c 100644 --- a/crates/store/re_arrow_combinators/tests/test_string_transforms.rs +++ b/crates/store/re_arrow_combinators/tests/test_string_transforms.rs @@ -9,13 +9,13 @@ use crate::util::{DisplayRB, fixtures::nested_string_struct_column}; /// Tests that `StringPrefix` and `StringSuffix` work correctly when the `StringArray` /// is extracted from a nested struct where string arrays share a common values buffer. #[test] -fn test_string_transforms_from_nested_struct() { +fn test_string_transforms_from_nested_struct() -> Result<(), Box> { let list_array = nested_string_struct_column(); let names_list = MapList::new(GetField::new("data")) .then(MapList::new(GetField::new("names"))) - .transform(&list_array) - .expect("failed to extract names"); + .transform(&list_array)? + .unwrap(); insta::assert_snapshot!(DisplayRB(names_list.clone()), @r" ┌──────────────────┐ │ col │ @@ -34,8 +34,8 @@ fn test_string_transforms_from_nested_struct() { let colors_list = MapList::new(GetField::new("data")) .then(MapList::new(GetField::new("colors"))) - .transform(&list_array) - .expect("failed to extract colors"); + .transform(&list_array)? + .unwrap(); insta::assert_snapshot!(DisplayRB(colors_list.clone()), @r" ┌──────────────────┐ │ col │ @@ -54,8 +54,8 @@ fn test_string_transforms_from_nested_struct() { // Test prefix on names array using MapList. let prefix_names = MapList::new(StringPrefix::new("user:")) - .transform(&names_list) - .expect("prefix transformation failed"); + .transform(&names_list)? + .unwrap(); insta::assert_snapshot!(DisplayRB(prefix_names.clone()), @r" ┌───────────────────┐ │ col │ @@ -74,8 +74,8 @@ fn test_string_transforms_from_nested_struct() { // Test suffix on colors array using MapList. let suffix_colors = MapList::new(StringSuffix::new("_color")) - .transform(&colors_list) - .expect("suffix transformation failed"); + .transform(&colors_list)? + .unwrap(); insta::assert_snapshot!(DisplayRB(suffix_colors.clone()), @r" ┌──────────────────────┐ │ col │ @@ -92,10 +92,10 @@ fn test_string_transforms_from_nested_struct() { └──────────────────────┘ "); - // Test chaining on names array using MapList and Compose (via .then()). + // Test chaining on names array using MapList and Then (via .then()). let chained_names = MapList::new(StringPrefix::new("<").then(StringSuffix::new(">"))) - .transform(&names_list) - .expect("chained transformation failed"); + .transform(&names_list)? + .unwrap(); insta::assert_snapshot!(DisplayRB(chained_names.clone()), @r" ┌──────────────────┐ │ col │ @@ -128,18 +128,20 @@ fn test_string_transforms_from_nested_struct() { │ [{data: null}, {data: {names: dave, colors: yellow}}] │ └───────────────────────────────────────────────────────────────────┘ "#); + + Ok(()) } /// Tests that `StringPrefix` and `StringSuffix` preserve empty strings as-is when configured to do so. #[test] -fn test_string_transforms_preserve_empty_strings() { +fn test_string_transforms_preserve_empty_strings() -> Result<(), Box> { use arrow::array::StringArray; let input = StringArray::from(vec![Some("hello"), Some(""), None, Some("world")]); let prefixed = StringPrefix::new("prefix_") .with_prefix_empty_string(false) - .transform(&input) + .transform(&input)? .unwrap(); insta::assert_snapshot!(DisplayRB(prefixed), @r" ┌──────────────┐ @@ -159,7 +161,7 @@ fn test_string_transforms_preserve_empty_strings() { let suffixed = StringSuffix::new("_suffix") .with_suffix_empty_string(false) - .transform(&input) + .transform(&input)? .unwrap(); insta::assert_snapshot!(DisplayRB(suffixed), @r" ┌──────────────┐ @@ -176,4 +178,6 @@ fn test_string_transforms_preserve_empty_strings() { │ world_suffix │ └──────────────┘ "); + + Ok(()) } diff --git a/crates/store/re_arrow_combinators/tests/test_transform.rs b/crates/store/re_arrow_combinators/tests/test_transform.rs index 05653737bd7a..4d0e2bc8b2d7 100644 --- a/crates/store/re_arrow_combinators/tests/test_transform.rs +++ b/crates/store/re_arrow_combinators/tests/test_transform.rs @@ -13,15 +13,14 @@ use util::DisplayRB; use crate::util::fixtures; #[test] -fn simple() { +fn simple() -> Result<(), Box> { let array = fixtures::nested_list_struct_column(); println!("{}", DisplayRB(array.clone())); - let pipeline = Selector::from_str(".poses[]") - .unwrap() - .then(MapList::new(StructToFixedList::new(["x", "y"]))); + let pipeline = + Selector::from_str(".poses[]")?.then(MapList::new(StructToFixedList::new(["x", "y"]))); - let result: ListArray = pipeline.transform(&array).unwrap(); + let result: ListArray = pipeline.transform(&array)?.unwrap(); insta::assert_snapshot!(format!("{}", DisplayRB(result.clone())), @r" ┌────────────────────────────────────────┐ @@ -42,15 +41,16 @@ fn simple() { │ [[7.0, null], [9.0, 10.0]] │ └────────────────────────────────────────┘ "); + + Ok(()) } #[test] -fn add_one_to_leaves() { +fn add_one_to_leaves() -> Result<(), Box> { let array = fixtures::nested_list_struct_column(); println!("{}", DisplayRB(array.clone())); - let pipeline = Selector::from_str(".poses[]") - .unwrap() + let pipeline = Selector::from_str(".poses[]")? .then(MapList::new(StructToFixedList::new(["x", "y"]))) .then(MapList::new(MapFixedSizeList::new(MapPrimitive::< arrow::datatypes::Float64Type, @@ -59,7 +59,7 @@ fn add_one_to_leaves() { x + 1.0 })))); - let result = pipeline.transform(&array).unwrap(); + let result = pipeline.transform(&array)?.unwrap(); insta::assert_snapshot!( format!("{}", DisplayRB(result.clone())) @@ -83,22 +83,23 @@ fn add_one_to_leaves() { └────────────────────────────────────────┘ " ); + + Ok(()) } #[test] -fn convert_to_f32() { +fn convert_to_f32() -> Result<(), Box> { let array = fixtures::nested_list_struct_column(); println!("{}", DisplayRB(array.clone())); - let pipeline = Selector::from_str(".poses[]") - .unwrap() + let pipeline = Selector::from_str(".poses[]")? .then(MapList::new(StructToFixedList::new(["x", "y"]))) .then(MapList::new(MapFixedSizeList::new(PrimitiveCast::< Float64Array, Float32Array, >::new()))); - let result = pipeline.transform(&array).unwrap(); + let result = pipeline.transform(&array)?.unwrap(); insta::assert_snapshot!(DisplayRB(result.clone()), @r" ┌────────────────────────────────────────┐ @@ -119,21 +120,22 @@ fn convert_to_f32() { │ [[7.0, null], [9.0, 10.0]] │ └────────────────────────────────────────┘ "); + + Ok(()) } #[test] -fn replace_nulls() { +fn replace_nulls() -> Result<(), Box> { let array = fixtures::nested_list_struct_column(); println!("{}", DisplayRB(array.clone())); - let pipeline = Selector::from_str(".poses[]") - .unwrap() + let pipeline = Selector::from_str(".poses[]")? .then(MapList::new(StructToFixedList::new(["x", "y"]))) .then(MapList::new(MapFixedSizeList::new(ReplaceNull::< arrow::datatypes::Float64Type, >::new(1337.0)))); - let result = pipeline.transform(&array).unwrap(); + let result = pipeline.transform(&array)?.unwrap(); insta::assert_snapshot!(format!("{}", DisplayRB(result.clone())), @r" ┌─────────────────────────────────────────────────┐ @@ -154,16 +156,18 @@ fn replace_nulls() { │ [[7.0, 1337.0], [9.0, 10.0]] │ └─────────────────────────────────────────────────┘ "); + + Ok(()) } #[test] -fn test_flatten_single_element() { +fn test_flatten_single_element() -> Result<(), Box> { let array = fixtures::nested_list_struct_column(); println!("{}", DisplayRB(array.clone())); - let pipeline = Selector::from_str(".poses[]").unwrap(); + let pipeline = Selector::from_str(".poses[]")?; - let result = pipeline.transform(&array).unwrap(); + let result = pipeline.transform(&array)?.unwrap(); insta::assert_snapshot!( format!("{}", DisplayRB(result.clone())), @r#" @@ -186,10 +190,12 @@ fn test_flatten_single_element() { └────────────────────────────────────────────────┘ "# ); + + Ok(()) } #[test] -fn test_flatten_multiple_elements() { +fn test_flatten_multiple_elements() -> Result<(), Box> { let inner_builder = ListBuilder::new(arrow::array::Int32Builder::new()); let mut outer_builder = ListBuilder::new(inner_builder); @@ -245,9 +251,8 @@ fn test_flatten_multiple_elements() { println!("{}", DisplayRB(list_of_lists.clone())); - let result = Selector::from_str(".[]") - .unwrap() - .transform(&list_of_lists) + let result = Selector::from_str(".[]")? + .transform(&list_of_lists)? .unwrap(); insta::assert_snapshot!( @@ -273,10 +278,12 @@ fn test_flatten_multiple_elements() { └────────────────────┘ " ); + + Ok(()) } #[test] -fn test_row_major_to_col_major() { +fn test_row_major_to_col_major() -> Result<(), Box> { let inner_builder = Int32Builder::new(); let mut outer_builder = ListBuilder::new(inner_builder); @@ -330,10 +337,10 @@ fn test_row_major_to_col_major() { // Cast to `FixedSizeListArray` and convert to column-major order. let fixed_size_list_array = ListToFixedSizeList::new(12) - .transform(&input_array) + .transform(&input_array)? .unwrap(); let result = RowMajorToColumnMajor::new(4, 3) - .transform(&fixed_size_list_array) + .transform(&fixed_size_list_array)? .unwrap(); insta::assert_snapshot!( @@ -351,18 +358,19 @@ fn test_row_major_to_col_major() { └──────────────────────────────────────────────────┘ " ); + + Ok(()) } #[test] -fn test_map_list_nullability() { +fn test_map_list_nullability() -> Result<(), Box> { let array = fixtures::nested_list_struct_column(); println!("{}", DisplayRB(array.clone())); - let pipeline = Selector::from_str(".poses[]") - .unwrap() - .then(MapList::new(StructToFixedList::new(["x", "y"]))); + let pipeline = + Selector::from_str(".poses[]")?.then(MapList::new(StructToFixedList::new(["x", "y"]))); - let result: ListArray = pipeline.transform(&array).unwrap(); + let result: ListArray = pipeline.transform(&array)?.unwrap(); insta::assert_snapshot!(format!("{}", DisplayRB(result.clone())), @r" ┌────────────────────────────────────────┐ @@ -383,16 +391,18 @@ fn test_map_list_nullability() { │ [[7.0, null], [9.0, 10.0]] │ └────────────────────────────────────────┘ "); + + Ok(()) } #[test] -fn test_map_list_outer_nullability() { +fn test_map_list_outer_nullability() -> Result<(), Box> { let array = fixtures::list_not_nullable(); println!("{}", DisplayRB(array.clone())); let pipeline = MapList::new(PrimitiveCast::::new()); - let result: ListArray = pipeline.transform(&array).unwrap(); + let result: ListArray = pipeline.transform(&array)?.unwrap(); insta::assert_snapshot!(format!("{}", DisplayRB(result.clone())), @r" ┌──────────────────────────────┐ @@ -409,7 +419,7 @@ fn test_map_list_outer_nullability() { let array = fixtures::list_with_nulls(); println!("{}", DisplayRB(array.clone())); - let result: ListArray = pipeline.transform(&array).unwrap(); + let result: ListArray = pipeline.transform(&array)?.unwrap(); insta::assert_snapshot!(format!("{}", DisplayRB(result.clone())), @r" ┌─────────────────────┐ │ col │ @@ -421,16 +431,18 @@ fn test_map_list_outer_nullability() { │ null │ └─────────────────────┘ "); + + Ok(()) } #[test] -fn test_map_list_outer_nullability_identity() { +fn test_map_list_outer_nullability_identity() -> Result<(), Box> { let array = fixtures::list_not_nullable(); println!("{}", DisplayRB(array.clone())); let pipeline = MapList::new(MapPrimitive::::new(|x| x)); - let result: ListArray = pipeline.transform(&array).unwrap(); + let result: ListArray = pipeline.transform(&array)?.unwrap(); insta::assert_snapshot!(format!("{}", DisplayRB(result.clone())), @r" ┌────────────────────────────┐ @@ -443,17 +455,18 @@ fn test_map_list_outer_nullability_identity() { │ [3, 4, 5] │ └────────────────────────────┘ "); + + Ok(()) } #[test] -fn test_flatten_fixed_size_list() { +fn test_flatten_fixed_size_list() -> Result<(), Box> { let array = fixtures::nested_list_struct_column(); - // Produces List> instead of creating a new test case from scratch. - let source: ListArray = Selector::from_str(".poses[]") - .unwrap() + // Produces List(FixedSizeList(2 x Float64)) instead of creating a new test case from scratch. + let source: ListArray = Selector::from_str(".poses[]")? .then(MapList::new(StructToFixedList::new(["x", "y"]))) - .transform(&array) + .transform(&array)? .unwrap(); insta::assert_snapshot!(format!("{}", DisplayRB(source.clone())), @" @@ -476,7 +489,7 @@ fn test_flatten_fixed_size_list() { └────────────────────────────────────────┘ "); - let result = Flatten::new().transform(&source).unwrap(); + let result = Flatten::new().transform(&source)?.unwrap(); insta::assert_snapshot!(format!("{}", DisplayRB(result)), @" ┌────────────────────────┐ @@ -497,4 +510,6 @@ fn test_flatten_fixed_size_list() { │ [7.0, null, 9.0, 10.0] │ └────────────────────────┘ "); + + Ok(()) } diff --git a/crates/store/re_data_loader/src/loader_mcap/lenses/foxglove/camera_calibration.rs b/crates/store/re_data_loader/src/loader_mcap/lenses/foxglove/camera_calibration.rs index e7a25633ada1..afae073eeef6 100644 --- a/crates/store/re_data_loader/src/loader_mcap/lenses/foxglove/camera_calibration.rs +++ b/crates/store/re_data_loader/src/loader_mcap/lenses/foxglove/camera_calibration.rs @@ -1,10 +1,9 @@ -use re_lenses::{Lens, LensError, Op}; +use re_arrow_combinators::{Selector, Transform as _, map::MapList}; +use re_lenses::{Lens, LensError, op}; use re_log_types::{EntityPathFilter, TimeType}; -use re_sdk_types::archetypes::Pinhole; +use re_sdk_types::archetypes::{CoordinateFrame, Pinhole}; -use crate::loader_mcap::lenses::helpers::{ - list_3x3_row_major_to_column_major, width_height_to_resolution, -}; +use crate::loader_mcap::lenses::helpers::row_major_3x3_to_column_major; use super::{FOXGLOVE_TIMESTAMP, IMAGE_PLANE_SUFFIX}; @@ -20,29 +19,20 @@ pub fn camera_calibration() -> Result { out.time( FOXGLOVE_TIMESTAMP, TimeType::TimestampNs, - [Op::selector(".timestamp"), Op::time_spec_to_nanos()], - ) + Selector::parse(".timestamp")?.then(MapList::new(op::timespec_to_nanos())), + )? .component( - Pinhole::descriptor_child_frame(), - [ - Op::selector(".frame_id"), - Op::string_suffix_nonempty(IMAGE_PLANE_SUFFIX), - ], - ) - .component( - Pinhole::descriptor_resolution(), - [Op::func(width_height_to_resolution)], - ) + CoordinateFrame::descriptor_frame(), + Selector::parse(".frame_id")? + .then(MapList::new(op::string_suffix_nonempty(IMAGE_PLANE_SUFFIX))), + )? .component( Pinhole::descriptor_image_from_camera(), - [ - Op::selector(".K"), - Op::func(list_3x3_row_major_to_column_major), - ], - ) + Selector::parse(".K")?.then(MapList::new(row_major_3x3_to_column_major())), + )? .component( - Pinhole::descriptor_parent_frame(), - [Op::selector(".frame_id")], + CoordinateFrame::descriptor_frame(), + Selector::parse(".frame_id")?, ) })? .build()) diff --git a/crates/store/re_data_loader/src/loader_mcap/lenses/foxglove/compressed_image.rs b/crates/store/re_data_loader/src/loader_mcap/lenses/foxglove/compressed_image.rs index 17beb0465148..d0331f727151 100644 --- a/crates/store/re_data_loader/src/loader_mcap/lenses/foxglove/compressed_image.rs +++ b/crates/store/re_data_loader/src/loader_mcap/lenses/foxglove/compressed_image.rs @@ -1,4 +1,5 @@ -use re_lenses::{Lens, LensError, Op}; +use re_arrow_combinators::{Selector, Transform as _, map::MapList}; +use re_lenses::{Lens, LensError, op}; use re_log_types::{EntityPathFilter, TimeType}; use re_sdk_types::archetypes::{CoordinateFrame, EncodedImage}; @@ -14,24 +15,22 @@ pub fn compressed_image() -> Result { out.time( FOXGLOVE_TIMESTAMP, TimeType::TimestampNs, - [Op::selector(".timestamp"), Op::time_spec_to_nanos()], - ) + Selector::parse(".timestamp")?.then(MapList::new(op::timespec_to_nanos())), + )? .component( CoordinateFrame::descriptor_frame(), - [ - Op::selector(".frame_id"), - Op::string_suffix_nonempty(IMAGE_PLANE_SUFFIX), - ], - ) + Selector::parse(".frame_id")? + .then(MapList::new(op::string_suffix_nonempty(IMAGE_PLANE_SUFFIX))), + )? // The format field can be "jpeg", "png", "webp" or "avif" in the Foxglove schema. // We prefix with "image/" to get valid MIME types for Rerun. .component( EncodedImage::descriptor_media_type(), - [Op::selector(".format"), Op::string_prefix("image/")], - ) + Selector::parse(".format")?.then(MapList::new(op::string_prefix("image/"))), + )? .component( EncodedImage::descriptor_blob(), - [Op::selector(".data"), Op::binary_to_list_uint8()], + Selector::parse(".data")?.then(MapList::new(op::binary_to_list_uint8())), ) })? .build(), diff --git a/crates/store/re_data_loader/src/loader_mcap/lenses/foxglove/compressed_video.rs b/crates/store/re_data_loader/src/loader_mcap/lenses/foxglove/compressed_video.rs index 98d0d607c960..301c83ef0b2b 100644 --- a/crates/store/re_data_loader/src/loader_mcap/lenses/foxglove/compressed_video.rs +++ b/crates/store/re_data_loader/src/loader_mcap/lenses/foxglove/compressed_video.rs @@ -1,4 +1,5 @@ -use re_lenses::{Lens, LensError, Op}; +use re_arrow_combinators::{Selector, Transform as _, map::MapList}; +use re_lenses::{Lens, LensError, op}; use re_log_types::{EntityPathFilter, TimeType}; use re_sdk_types::archetypes::{CoordinateFrame, VideoStream}; @@ -14,22 +15,20 @@ pub fn compressed_video() -> Result { out.time( FOXGLOVE_TIMESTAMP, TimeType::TimestampNs, - [Op::selector(".timestamp"), Op::time_spec_to_nanos()], - ) + Selector::parse(".timestamp")?.then(MapList::new(op::timespec_to_nanos())), + )? .component( CoordinateFrame::descriptor_frame(), - [ - Op::selector(".frame_id"), - Op::string_suffix_nonempty(IMAGE_PLANE_SUFFIX), - ], - ) + Selector::parse(".frame_id")? + .then(MapList::new(op::string_suffix_nonempty(IMAGE_PLANE_SUFFIX))), + )? .component( VideoStream::descriptor_codec(), - [Op::selector(".format"), Op::string_to_video_codec()], - ) + Selector::parse(".format")?.then(MapList::new(op::string_to_video_codec())), + )? .component( VideoStream::descriptor_sample(), - [Op::selector(".data"), Op::binary_to_list_uint8()], + Selector::parse(".data")?.then(MapList::new(op::binary_to_list_uint8())), ) })? .build(), diff --git a/crates/store/re_data_loader/src/loader_mcap/lenses/foxglove/frame_transform.rs b/crates/store/re_data_loader/src/loader_mcap/lenses/foxglove/frame_transform.rs index 00b0f5a220e8..1084df42eb33 100644 --- a/crates/store/re_data_loader/src/loader_mcap/lenses/foxglove/frame_transform.rs +++ b/crates/store/re_data_loader/src/loader_mcap/lenses/foxglove/frame_transform.rs @@ -1,10 +1,9 @@ -use re_lenses::{Lens, LensError, Op}; +use re_arrow_combinators::{Selector, Transform as _, map::MapList}; +use re_lenses::{Lens, LensError, op}; use re_log_types::{EntityPathFilter, TimeType}; use re_sdk_types::archetypes::Transform3D; -use crate::loader_mcap::lenses::helpers::{ - list_xyz_struct_to_list_fixed, list_xyzw_struct_to_list_fixed, -}; +use crate::loader_mcap::lenses::helpers::{xyz_struct_to_fixed, xyzw_struct_to_fixed}; use super::FOXGLOVE_TIMESTAMP; @@ -18,29 +17,23 @@ pub fn frame_transform() -> Result { out.time( FOXGLOVE_TIMESTAMP, TimeType::TimestampNs, - [Op::selector(".timestamp"), Op::time_spec_to_nanos()], - ) + Selector::parse(".timestamp")?.then(MapList::new(op::timespec_to_nanos())), + )? .component( Transform3D::descriptor_parent_frame(), - [Op::selector(".parent_frame_id")], - ) + Selector::parse(".parent_frame_id")?, + )? .component( Transform3D::descriptor_child_frame(), - [Op::selector(".child_frame_id")], - ) + Selector::parse(".child_frame_id")?, + )? .component( Transform3D::descriptor_translation(), - [ - Op::selector(".translation"), - Op::func(list_xyz_struct_to_list_fixed), - ], - ) + Selector::parse(".translation")?.then(MapList::new(xyz_struct_to_fixed())), + )? .component( Transform3D::descriptor_quaternion(), - [ - Op::selector(".rotation"), - Op::func(list_xyzw_struct_to_list_fixed), - ], + Selector::parse(".rotation")?.then(MapList::new(xyzw_struct_to_fixed())), ) })? .build(), diff --git a/crates/store/re_data_loader/src/loader_mcap/lenses/foxglove/frame_transforms.rs b/crates/store/re_data_loader/src/loader_mcap/lenses/foxglove/frame_transforms.rs index b6e6b5bd0a72..608174186ca1 100644 --- a/crates/store/re_data_loader/src/loader_mcap/lenses/foxglove/frame_transforms.rs +++ b/crates/store/re_data_loader/src/loader_mcap/lenses/foxglove/frame_transforms.rs @@ -1,10 +1,9 @@ -use re_lenses::{Lens, LensError, Op}; +use re_arrow_combinators::{Selector, Transform as _, map::MapList}; +use re_lenses::{Lens, LensError, op}; use re_log_types::{EntityPathFilter, TimeType}; use re_sdk_types::archetypes::Transform3D; -use crate::loader_mcap::lenses::helpers::{ - list_xyz_struct_to_list_fixed, list_xyzw_struct_to_list_fixed, -}; +use crate::loader_mcap::lenses::helpers::{xyz_struct_to_fixed, xyzw_struct_to_fixed}; use super::FOXGLOVE_TIMESTAMP; @@ -18,32 +17,26 @@ pub fn frame_transforms() -> Result { out.time( FOXGLOVE_TIMESTAMP, TimeType::TimestampNs, - [ - Op::selector(".transforms[].timestamp"), - Op::time_spec_to_nanos(), - ], - ) + Selector::parse(".transforms[].timestamp")? + .then(MapList::new(op::timespec_to_nanos())), + )? .component( Transform3D::descriptor_parent_frame(), - [Op::selector(".transforms[].parent_frame_id")], - ) + Selector::parse(".transforms[].parent_frame_id")?, + )? .component( Transform3D::descriptor_child_frame(), - [Op::selector(".transforms[].child_frame_id")], - ) + Selector::parse(".transforms[].child_frame_id")?, + )? .component( Transform3D::descriptor_translation(), - [ - Op::selector(".transforms[].translation"), - Op::func(list_xyz_struct_to_list_fixed), - ], - ) + Selector::parse(".transforms[].translation")? + .then(MapList::new(xyz_struct_to_fixed())), + )? .component( Transform3D::descriptor_quaternion(), - [ - Op::selector(".transforms[].rotation"), - Op::func(list_xyzw_struct_to_list_fixed), - ], + Selector::parse(".transforms[].rotation")? + .then(MapList::new(xyzw_struct_to_fixed())), ) })? .build(), diff --git a/crates/store/re_data_loader/src/loader_mcap/lenses/foxglove/log.rs b/crates/store/re_data_loader/src/loader_mcap/lenses/foxglove/log.rs index f3ca6675473c..0c18b7833e67 100644 --- a/crates/store/re_data_loader/src/loader_mcap/lenses/foxglove/log.rs +++ b/crates/store/re_data_loader/src/loader_mcap/lenses/foxglove/log.rs @@ -1,6 +1,6 @@ -use arrow::array::{ListArray, StringArray}; -use re_arrow_combinators::{Transform, map::MapList}; -use re_lenses::{Lens, LensError, Op, OpError}; +use arrow::array::StringArray; +use re_arrow_combinators::{Selector, Transform, map::MapList}; +use re_lenses::{Lens, LensError, op}; use re_log_types::{EntityPathFilter, TimeType}; use re_sdk_types::archetypes::TextLog; @@ -16,26 +16,18 @@ pub fn log() -> Result { out.time( FOXGLOVE_TIMESTAMP, TimeType::TimestampNs, - [Op::selector(".timestamp"), Op::time_spec_to_nanos()], - ) - .component(TextLog::descriptor_text(), [Op::selector(".message")]) + Selector::parse(".timestamp")?.then(MapList::new(op::timespec_to_nanos())), + )? + .component(TextLog::descriptor_text(), Selector::parse(".message")?)? .component( TextLog::descriptor_level(), - [ - Op::selector(".level.name"), - Op::func(foxglove_level_to_rerun), - ], + Selector::parse(".level.name")?.then(MapList::new(FoxgloveToRerunLogLevel)), ) })? .build(), ) } -/// Maps Foxglove log level names to Rerun [`re_sdk_types::components::TextLogLevel`] names. -fn foxglove_level_to_rerun(list_array: &ListArray) -> Result { - Ok(MapList::new(FoxgloveToRerunLogLevel).transform(list_array)?) -} - /// Maps Foxglove log level strings to Rerun [`re_sdk_types::components::TextLogLevel`] strings. struct FoxgloveToRerunLogLevel; @@ -43,17 +35,22 @@ impl Transform for FoxgloveToRerunLogLevel { type Source = StringArray; type Target = StringArray; - fn transform(&self, source: &StringArray) -> Result { - Ok(source - .iter() - .map(|level| match level { - Some("WARNING") => Some("WARN"), - Some("FATAL") => Some("CRITICAL"), - // Rerun has no UNKNOWN level. - Some("UNKNOWN") | None => None, - // DEBUG, INFO, ERROR can be passed through as-is. - other => other, - }) - .collect()) + fn transform( + &self, + source: &StringArray, + ) -> Result, re_arrow_combinators::Error> { + Ok(Some( + source + .iter() + .map(|level| match level { + Some("WARNING") => Some("WARN"), + Some("FATAL") => Some("CRITICAL"), + // Rerun has no UNKNOWN level. + Some("UNKNOWN") | None => None, + // DEBUG, INFO, ERROR can be passed through as-is. + other => other, + }) + .collect(), + )) } } diff --git a/crates/store/re_data_loader/src/loader_mcap/lenses/foxglove/packed_element_field.rs b/crates/store/re_data_loader/src/loader_mcap/lenses/foxglove/packed_element_field.rs index 86bc3078d1a9..b41ce91b173d 100644 --- a/crates/store/re_data_loader/src/loader_mcap/lenses/foxglove/packed_element_field.rs +++ b/crates/store/re_data_loader/src/loader_mcap/lenses/foxglove/packed_element_field.rs @@ -4,31 +4,13 @@ //! [`PackedElementField`]: https://docs.foxglove.dev/docs/sdk/schemas/packed-element-field //! [`foxglove.PointCloud`]: https://docs.foxglove.dev/docs/sdk/schemas/point-cloud +use crate::loader_mcap::lenses::helpers::get_field_as; use arrow::array::builder::{FixedSizeListBuilder, Float32Builder, ListBuilder, UInt32Builder}; use arrow::array::{ Array as _, BinaryArray, Int32Array, ListArray, StringArray, StructArray, UInt32Array, }; use arrow::datatypes::{DataType, Field}; use re_arrow_combinators::Transform; -use re_arrow_combinators::map::MapList; -use re_arrow_combinators::reshape::Flatten; -use re_lenses::OpError; - -use crate::loader_mcap::lenses::helpers::get_field_as; - -/// Extracts position data from point cloud messages as a `List>`. -pub fn extract_positions(list_array: &ListArray) -> Result { - Ok(MapList::new(ExtractPositions) - .then(Flatten::new()) - .transform(list_array)?) -} - -/// Extracts RGBA color data from point cloud messages as a `List`. -pub fn extract_colors(list_array: &ListArray) -> Result { - Ok(MapList::new(ExtractColors) - .then(Flatten::new()) - .transform(list_array)?) -} /// Foxglove [`NumericType`] enum. /// @@ -173,13 +155,17 @@ fn find_field_descriptors( .collect() } -struct ExtractPositions; +/// Extracts position data from point cloud messages as a `List>`. +pub(crate) struct ExtractPositions; impl Transform for ExtractPositions { type Source = StructArray; type Target = ListArray; - fn transform(&self, source: &StructArray) -> Result { + fn transform( + &self, + source: &StructArray, + ) -> Result, re_arrow_combinators::Error> { re_tracing::profile_function!(); let point_stride_array = get_field_as::(source, "point_stride")?; @@ -244,17 +230,21 @@ impl Transform for ExtractPositions { } } - Ok(builder.finish()) + Ok(Some(builder.finish())) } } -struct ExtractColors; +/// Extracts RGBA color data from point cloud messages as a `List`. +pub(crate) struct ExtractColors; impl Transform for ExtractColors { type Source = StructArray; type Target = ListArray; - fn transform(&self, source: &StructArray) -> Result { + fn transform( + &self, + source: &StructArray, + ) -> Result, re_arrow_combinators::Error> { re_tracing::profile_function!(); let point_stride_array = get_field_as::(source, "point_stride")?; @@ -315,6 +305,6 @@ impl Transform for ExtractColors { } } - Ok(builder.finish()) + Ok(Some(builder.finish())) } } diff --git a/crates/store/re_data_loader/src/loader_mcap/lenses/foxglove/point_cloud.rs b/crates/store/re_data_loader/src/loader_mcap/lenses/foxglove/point_cloud.rs index cfc7c6592543..3e9b9b6adba1 100644 --- a/crates/store/re_data_loader/src/loader_mcap/lenses/foxglove/point_cloud.rs +++ b/crates/store/re_data_loader/src/loader_mcap/lenses/foxglove/point_cloud.rs @@ -1,9 +1,11 @@ -use re_lenses::{Lens, LensError, Op}; +use re_arrow_combinators::reshape::Flatten; +use re_arrow_combinators::{Selector, Transform as _, map::MapList}; +use re_lenses::{Lens, LensError, op}; use re_log_types::{EntityPathFilter, TimeType}; use re_sdk_types::archetypes::{CoordinateFrame, Points3D}; use super::FOXGLOVE_TIMESTAMP; -use super::packed_element_field::{extract_colors, extract_positions}; +use super::packed_element_field::{ExtractColors, ExtractPositions}; /// Creates a lens for [`foxglove.PointCloud`] messages. /// @@ -16,17 +18,24 @@ pub fn point_cloud() -> Result { out.time( FOXGLOVE_TIMESTAMP, TimeType::TimestampNs, - [Op::selector(".timestamp"), Op::time_spec_to_nanos()], - ) + Selector::parse(".timestamp")?.then(MapList::new(op::timespec_to_nanos())), + )? .component( CoordinateFrame::descriptor_frame(), - [Op::selector(".frame_id")], - ) + Selector::parse(".frame_id")?, + )? .component( Points3D::descriptor_positions(), - [Op::func(extract_positions)], + Selector::parse(".")? + .then(MapList::new(ExtractPositions)) + .then(Flatten::new()), + )? + .component( + Points3D::descriptor_colors(), + Selector::parse(".")? + .then(MapList::new(ExtractColors)) + .then(Flatten::new()), ) - .component(Points3D::descriptor_colors(), [Op::func(extract_colors)]) })? .build(), ) diff --git a/crates/store/re_data_loader/src/loader_mcap/lenses/foxglove/pose_in_frame.rs b/crates/store/re_data_loader/src/loader_mcap/lenses/foxglove/pose_in_frame.rs index 3ad5286e7de8..2724059468dd 100644 --- a/crates/store/re_data_loader/src/loader_mcap/lenses/foxglove/pose_in_frame.rs +++ b/crates/store/re_data_loader/src/loader_mcap/lenses/foxglove/pose_in_frame.rs @@ -1,10 +1,9 @@ -use re_lenses::{Lens, LensError, Op}; +use re_arrow_combinators::{Selector, Transform as _, map::MapList}; +use re_lenses::{Lens, LensError, op}; use re_log_types::{EntityPathFilter, TimeType}; use re_sdk_types::archetypes::{CoordinateFrame, InstancePoses3D}; -use crate::loader_mcap::lenses::helpers::{ - list_xyz_struct_to_list_fixed, list_xyzw_struct_to_list_fixed, -}; +use crate::loader_mcap::lenses::helpers::{xyz_struct_to_fixed, xyzw_struct_to_fixed}; use super::FOXGLOVE_TIMESTAMP; @@ -18,25 +17,20 @@ pub fn pose_in_frame() -> Result { out.time( FOXGLOVE_TIMESTAMP, TimeType::TimestampNs, - [Op::selector(".timestamp"), Op::time_spec_to_nanos()], - ) + Selector::parse(".timestamp")?.then(MapList::new(op::timespec_to_nanos())), + )? .component( CoordinateFrame::descriptor_frame(), - [Op::selector(".frame_id")], - ) + Selector::parse(".frame_id")?, + )? .component( InstancePoses3D::descriptor_translations(), - [ - Op::selector(".pose.position"), - Op::func(list_xyz_struct_to_list_fixed), - ], - ) + Selector::parse(".pose.position")?.then(MapList::new(xyz_struct_to_fixed())), + )? .component( InstancePoses3D::descriptor_quaternions(), - [ - Op::selector(".pose.orientation"), - Op::func(list_xyzw_struct_to_list_fixed), - ], + Selector::parse(".pose.orientation")? + .then(MapList::new(xyzw_struct_to_fixed())), ) })? .build(), diff --git a/crates/store/re_data_loader/src/loader_mcap/lenses/foxglove/poses_in_frame.rs b/crates/store/re_data_loader/src/loader_mcap/lenses/foxglove/poses_in_frame.rs index 57a372e661a8..1203ef6fac53 100644 --- a/crates/store/re_data_loader/src/loader_mcap/lenses/foxglove/poses_in_frame.rs +++ b/crates/store/re_data_loader/src/loader_mcap/lenses/foxglove/poses_in_frame.rs @@ -1,10 +1,9 @@ -use re_lenses::{Lens, LensError, Op}; +use re_arrow_combinators::{Selector, Transform as _, map::MapList}; +use re_lenses::{Lens, LensError, op}; use re_log_types::{EntityPathFilter, TimeType}; use re_sdk_types::archetypes::{CoordinateFrame, InstancePoses3D}; -use crate::loader_mcap::lenses::helpers::{ - list_xyz_struct_to_list_fixed, list_xyzw_struct_to_list_fixed, -}; +use crate::loader_mcap::lenses::helpers::{xyz_struct_to_fixed, xyzw_struct_to_fixed}; use super::FOXGLOVE_TIMESTAMP; @@ -18,25 +17,20 @@ pub fn poses_in_frame() -> Result { out.time( FOXGLOVE_TIMESTAMP, TimeType::TimestampNs, - [Op::selector(".timestamp"), Op::time_spec_to_nanos()], - ) + Selector::parse(".timestamp")?.then(MapList::new(op::timespec_to_nanos())), + )? .component( CoordinateFrame::descriptor_frame(), - [Op::selector(".frame_id")], - ) + Selector::parse(".frame_id")?, + )? .component( InstancePoses3D::descriptor_translations(), - [ - Op::selector(".poses[].position"), - Op::func(list_xyz_struct_to_list_fixed), - ], - ) + Selector::parse(".poses[].position")?.then(MapList::new(xyz_struct_to_fixed())), + )? .component( InstancePoses3D::descriptor_quaternions(), - [ - Op::selector(".poses[].orientation"), - Op::func(list_xyzw_struct_to_list_fixed), - ], + Selector::parse(".poses[].orientation")? + .then(MapList::new(xyzw_struct_to_fixed())), ) })? .build(), diff --git a/crates/store/re_data_loader/src/loader_mcap/lenses/foxglove/raw_image.rs b/crates/store/re_data_loader/src/loader_mcap/lenses/foxglove/raw_image.rs index 2a138ee6c1bc..6354dab0dc91 100644 --- a/crates/store/re_data_loader/src/loader_mcap/lenses/foxglove/raw_image.rs +++ b/crates/store/re_data_loader/src/loader_mcap/lenses/foxglove/raw_image.rs @@ -1,8 +1,9 @@ -use re_lenses::{Lens, LensError, Op}; +use re_arrow_combinators::{Selector, Transform as _, map::MapList}; +use re_lenses::{Lens, LensError, op}; use re_log_types::{EntityPathFilter, TimeType}; use re_sdk_types::archetypes::{CoordinateFrame, Image}; -use crate::loader_mcap::lenses::image_helpers::{encoding_to_image_format, extract_image_buffer}; +use crate::loader_mcap::lenses::image_helpers::{EncodingToImageFormat, ExtractImageBuffer}; use super::{FOXGLOVE_TIMESTAMP, IMAGE_PLANE_SUFFIX}; @@ -16,20 +17,21 @@ pub fn raw_image() -> Result { out.time( FOXGLOVE_TIMESTAMP, TimeType::TimestampNs, - [Op::selector(".timestamp"), Op::time_spec_to_nanos()], - ) + Selector::parse(".timestamp")?.then(MapList::new(op::timespec_to_nanos())), + )? .component( CoordinateFrame::descriptor_frame(), - [ - Op::selector(".frame_id"), - Op::string_suffix_nonempty(IMAGE_PLANE_SUFFIX), - ], - ) + Selector::parse(".frame_id")? + .then(MapList::new(op::string_suffix_nonempty(IMAGE_PLANE_SUFFIX))), + )? .component( Image::descriptor_format(), - [Op::func(encoding_to_image_format)], + Selector::parse(".")?.then(MapList::new(EncodingToImageFormat)), + )? + .component( + Image::descriptor_buffer(), + Selector::parse(".")?.then(MapList::new(ExtractImageBuffer)), ) - .component(Image::descriptor_buffer(), [Op::func(extract_image_buffer)]) })? .build(), ) diff --git a/crates/store/re_data_loader/src/loader_mcap/lenses/helpers.rs b/crates/store/re_data_loader/src/loader_mcap/lenses/helpers.rs index acf8d9c5da8b..ef27dd2581ec 100644 --- a/crates/store/re_data_loader/src/loader_mcap/lenses/helpers.rs +++ b/crates/store/re_data_loader/src/loader_mcap/lenses/helpers.rs @@ -1,47 +1,36 @@ //! Common helper functions for transforming Arrow data in lenses. -use arrow::array::{Array, Float32Array, Float64Array, ListArray, StructArray, UInt32Array}; -use re_arrow_combinators::Transform as _; +use arrow::array::{Array, FixedSizeListArray, Float32Array, Float64Array, ListArray, StructArray}; +use re_arrow_combinators::Transform; use re_arrow_combinators::cast::{ListToFixedSizeList, PrimitiveCast}; -use re_arrow_combinators::map::{MapFixedSizeList, MapList}; +use re_arrow_combinators::map::MapFixedSizeList; use re_arrow_combinators::reshape::{GetField, RowMajorToColumnMajor, StructToFixedList}; -use re_lenses::OpError; -/// Converts a list of structs with `x`, `y`, `z` fields to a list of fixed-size lists with 3 f32 values. -pub fn list_xyz_struct_to_list_fixed(list_array: &ListArray) -> Result { - let pipeline = MapList::new(StructToFixedList::new(["x", "y", "z"]).then( - MapFixedSizeList::new(PrimitiveCast::::new()), - )); - Ok(pipeline.transform(list_array)?) +/// Returns a transform that converts a struct with `x`, `y`, `z` fields to a fixed-size list of 3 f32 values. +pub fn xyz_struct_to_fixed() -> impl Transform { + StructToFixedList::new(["x", "y", "z"]).then(MapFixedSizeList::new(PrimitiveCast::< + Float64Array, + Float32Array, + >::new())) } -/// Converts a list of structs with `x`, `y`, `z`, `w` fields to a list of fixed-size lists with 4 f32 values (quaternions). -pub fn list_xyzw_struct_to_list_fixed(list_array: &ListArray) -> Result { - let pipeline = MapList::new(StructToFixedList::new(["x", "y", "z", "w"]).then( - MapFixedSizeList::new(PrimitiveCast::::new()), - )); - Ok(pipeline.transform(list_array)?) +/// Returns a transform that converts a struct with `x`, `y`, `z`, `w` fields to a fixed-size list of 4 f32 values (quaternions). +pub fn xyzw_struct_to_fixed() -> impl Transform { + StructToFixedList::new(["x", "y", "z", "w"]).then(MapFixedSizeList::new(PrimitiveCast::< + Float64Array, + Float32Array, + >::new())) } -/// Converts 3x3 row-major f64 matrices stored in variable-size lists to column-major f32 fixed-size lists. -pub fn list_3x3_row_major_to_column_major(list_array: &ListArray) -> Result { - let pipeline = MapList::new( - ListToFixedSizeList::new(9) - .then(RowMajorToColumnMajor::new(3, 3)) - .then(MapFixedSizeList::new(PrimitiveCast::< - Float64Array, - Float32Array, - >::new())), - ); - Ok(pipeline.transform(list_array)?) -} - -/// Converts u32 width and height fields to a `Resolution` component (fixed-size list with two f32 values). -pub fn width_height_to_resolution(list_array: &ListArray) -> Result { - let pipeline = MapList::new(StructToFixedList::new(["width", "height"]).then( - MapFixedSizeList::new(PrimitiveCast::::new()), - )); - Ok(pipeline.transform(list_array)?) +/// Returns a transform that converts 3x3 row-major f64 matrices stored in variable-size lists to column-major f32 fixed-size lists. +pub fn row_major_3x3_to_column_major() +-> impl Transform { + ListToFixedSizeList::new(9) + .then(RowMajorToColumnMajor::new(3, 3)) + .then(MapFixedSizeList::new(PrimitiveCast::< + Float64Array, + Float32Array, + >::new())) } /// Extracts a struct field by name and downcasts it to the expected array type. @@ -49,7 +38,12 @@ pub fn get_field_as( source: &StructArray, name: &str, ) -> Result { - let array_ref = GetField::new(name).transform(source)?; + let array_ref = GetField::new(name).transform(source)?.ok_or_else(|| { + re_arrow_combinators::Error::FieldNotFound { + field_name: name.to_owned(), + available_fields: source.fields().iter().map(|f| f.name().clone()).collect(), + } + })?; array_ref .as_any() .downcast_ref::() diff --git a/crates/store/re_data_loader/src/loader_mcap/lenses/image_helpers.rs b/crates/store/re_data_loader/src/loader_mcap/lenses/image_helpers.rs index b596b07cefff..6bbf6b9929ab 100644 --- a/crates/store/re_data_loader/src/loader_mcap/lenses/image_helpers.rs +++ b/crates/store/re_data_loader/src/loader_mcap/lenses/image_helpers.rs @@ -7,9 +7,7 @@ use arrow::array::{ }; use arrow::buffer::OffsetBuffer; use arrow::datatypes::{DataType, Field}; -use re_arrow_combinators::Transform; -use re_arrow_combinators::map::MapList; -use re_lenses::OpError; +use re_arrow_combinators::{Error, Transform}; use re_sdk_types::Loggable as _; use re_sdk_types::datatypes::ImageFormat; @@ -17,24 +15,13 @@ use super::helpers::get_field_as; /// Converts a struct with `width`, `height`, and `encoding` fields into a Rerun /// [`ImageFormat`] struct array, using [`re_mcap::ImageEncoding`]. -pub fn encoding_to_image_format(list_array: &ListArray) -> Result { - Ok(MapList::new(EncodingToImageFormat).transform(list_array)?) -} - -/// Extracts image buffer data from a struct with `width`, `height`, `step`, `encoding`, -/// and `data` fields. Strips row padding when the data is larger than expected for the -/// given encoding. -pub fn extract_image_buffer(list_array: &ListArray) -> Result { - Ok(MapList::new(ExtractImageBuffer).transform(list_array)?) -} - -struct EncodingToImageFormat; +pub(crate) struct EncodingToImageFormat; impl Transform for EncodingToImageFormat { type Source = StructArray; type Target = StructArray; - fn transform(&self, source: &StructArray) -> Result { + fn transform(&self, source: &StructArray) -> Result, Error> { let width_array = get_field_as::(source, "width")?; let height_array = get_field_as::(source, "height")?; let encoding_array = get_field_as::(source, "encoding")?; @@ -50,30 +37,34 @@ impl Transform for EncodingToImageFormat { height_array.value(i), ]))) }) - .collect::>()?; + .collect::>()?; let array_ref = ImageFormat::to_arrow_opt(formats.iter().map(|f| f.as_ref())) - .map_err(|err| re_arrow_combinators::Error::Other(err.to_string()))?; + .map_err(|err| Error::Other(err.to_string()))?; array_ref .as_any() .downcast_ref::() .cloned() - .ok_or_else(|| re_arrow_combinators::Error::TypeMismatch { + .ok_or_else(|| Error::TypeMismatch { expected: "StructArray".to_owned(), actual: array_ref.data_type().clone(), context: "ImageFormat serialization".to_owned(), }) + .map(Some) } } -struct ExtractImageBuffer; +/// Extracts image buffer data from a struct with `width`, `height`, `step`, `encoding`, +/// and `data` fields. Strips row padding when the data is larger than expected for the +/// given encoding. +pub(crate) struct ExtractImageBuffer; impl Transform for ExtractImageBuffer { type Source = StructArray; type Target = ListArray; - fn transform(&self, source: &StructArray) -> Result { + fn transform(&self, source: &StructArray) -> Result, Error> { re_tracing::profile_function!(); let width_array = get_field_as::(source, "width")?; @@ -139,31 +130,30 @@ impl Transform for ExtractImageBuffer { let values = UInt8Array::from(buffer); let field = Arc::new(Field::new_list_field(DataType::UInt8, false)); - Ok(ListArray::new( + Ok(Some(ListArray::new( field, OffsetBuffer::new(offsets.into()), Arc::new(values), source.nulls().cloned(), - )) + ))) } } /// Appends the current buffer length as the next offset for building a `ListArray`. -fn push_offset(buffer: &[u8], offsets: &mut Vec) -> Result<(), re_arrow_combinators::Error> { - offsets.push(i32::try_from(buffer.len()).map_err(|_err| { - re_arrow_combinators::Error::OffsetOverflow { +fn push_offset(buffer: &[u8], offsets: &mut Vec) -> Result<(), Error> { + offsets.push( + i32::try_from(buffer.len()).map_err(|_err| Error::OffsetOverflow { actual: buffer.len(), expected_type: "i32", - } - })?); + })?, + ); Ok(()) } /// Parses an encoding string into an [`re_mcap::ImageEncoding`], mapping the error for use in transforms. -fn parse_encoding(s: &str) -> Result { - s.parse() - .map_err(|_err| re_arrow_combinators::Error::UnexpectedValue { - expected: re_mcap::ImageEncoding::NAMES, - actual: s.to_owned(), - }) +fn parse_encoding(s: &str) -> Result { + s.parse().map_err(|_err| Error::UnexpectedValue { + expected: re_mcap::ImageEncoding::NAMES, + actual: s.to_owned(), + }) } diff --git a/crates/store/re_lenses/src/ast.rs b/crates/store/re_lenses/src/ast.rs index f7f637ddd75f..80153b47ef55 100644 --- a/crates/store/re_lenses/src/ast.rs +++ b/crates/store/re_lenses/src/ast.rs @@ -1,18 +1,14 @@ //! Private module with the AST-like definitions of lenses. //! -//! **Note**: Apart from high-level entry points (like [`Op`] and [`Lens`], +//! **Note**: Apart from high-level entry points (like [`Lens`]), //! we should not leak these elements into the public API. This allows us to //! evolve the definition of lenses over time, if requirements change. -use std::str::FromStr as _; - use arrow::array::{AsArray as _, Int64Array, ListArray}; use arrow::compute::take; -use arrow::datatypes::DataType; use itertools::Either; use nohash_hasher::IntMap; -use re_arrow_combinators::{Selector, Transform as _}; -use re_arrow_combinators::{map, reshape}; +use re_arrow_combinators::{Transform, reshape}; use re_chunk::{ ArrowArray as _, Chunk, ChunkId, ComponentIdentifier, EntityPath, TimeColumn, Timeline, TimelineName, @@ -21,11 +17,8 @@ use re_log_types::{EntityPathFilter, TimeType}; use re_sdk_types::{ComponentDescriptor, SerializedComponentColumn}; use vec1::Vec1; -use crate::semantic; - use crate::LensError; use crate::builder::LensBuilder; -use crate::op::{self, OpError}; pub struct InputColumn { pub entity_path_filter: EntityPathFilter, @@ -43,22 +36,43 @@ pub enum TargetEntity { Explicit(EntityPath), } +// TODO(RR-3968): Switch over to `Selector` once it is expressive enough. +/// A boxed, type-erased transform from `ListArray` to `Option`. +pub type BoxedTransform = Box + Send + Sync>; + /// A component output. /// /// Depending on the context in which this output is used, the result from -/// applying the `ops` should be a list array (1:1) or a list array of list arrays (1:N). -#[derive(Debug)] +/// applying the transform should be a list array (1:1) or a list array of list arrays (1:N). pub struct ComponentOutput { pub component_descr: ComponentDescriptor, - pub ops: Vec, + pub selector: BoxedTransform, +} + +// TODO(RR-3968): Switch over to `Selector` means we can derive this again. +impl std::fmt::Debug for ComponentOutput { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ComponentOutput") + .field("component_descr", &self.component_descr) + .finish_non_exhaustive() + } } /// A time extraction output. -#[derive(Debug)] pub struct TimeOutput { pub timeline_name: TimelineName, pub timeline_type: TimeType, - pub ops: Vec, + pub selector: BoxedTransform, +} + +// TODO(RR-3968): Switch over to `Selector` means we can derive this again. +impl std::fmt::Debug for TimeOutput { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("TimeOutput") + .field("timeline_name", &self.timeline_name) + .field("timeline_type", &self.timeline_type) + .finish_non_exhaustive() + } } #[derive(Debug)] @@ -108,195 +122,6 @@ pub enum LensKind { StaticColumns(Static), } -type CustomFn = Box Result + Sync + Send>; - -/// Provides commonly used transformations of component columns. -/// -/// Individual operations are wrapped to hide their implementation details. -#[non_exhaustive] -pub enum Op { - /// Selector operation using jq-like syntax for navigating and transforming Arrow data. - /// - /// The selector query string is parsed at execution time. - Selector(String), - - /// Converts binary arrays to list arrays of `u8`. - BinaryToListUInt8, - - /// Efficiently casts a component to a new `DataType`. - Cast(op::Cast), - - /// Converts video codec strings to Rerun `VideoCodec` enum values (as `u32`). - StringToVideoCodecUInt32, - - /// Prepends a prefix to each string value, including empty strings. - StringPrefix(String), - - /// Prepends a prefix to each non-empty string value, leaving empty strings unchanged. - StringPrefixNonEmpty(String), - - /// Appends a suffix to each string value, including empty strings. - StringSuffix(String), - - /// Appends a suffix to each non-empty string value, leaving empty strings unchanged. - StringSuffixNonEmpty(String), - - /// Converts timestamp structs with `seconds`/`nanos` or `sec`/`nsec` fields to total nanoseconds. - TimeSpecToNanos, - - /// A user-defined arbitrary function to convert a component column. - Func(CustomFn), -} - -impl std::fmt::Debug for Op { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Selector(query) => f.debug_tuple("Selector").field(query).finish(), - Self::BinaryToListUInt8 => f.debug_struct("BinaryToListUInt8").finish(), - Self::Cast(inner) => f.debug_tuple("Cast").field(inner).finish(), - Self::StringToVideoCodecUInt32 => f.debug_struct("StringToVideoCodecUInt32").finish(), - Self::StringPrefix(prefix) => f.debug_tuple("StringPrefix").field(prefix).finish(), - Self::StringPrefixNonEmpty(prefix) => { - f.debug_tuple("StringPrefixNonEmpty").field(prefix).finish() - } - Self::StringSuffix(suffix) => f.debug_tuple("StringSuffix").field(suffix).finish(), - Self::StringSuffixNonEmpty(suffix) => { - f.debug_tuple("StringSuffixNonEmpty").field(suffix).finish() - } - Self::TimeSpecToNanos => f.debug_struct("TimeSpecToNanos").finish(), - Self::Func(_) => f.debug_tuple("Func").field(&"").finish(), - } - } -} - -impl From<&str> for Op { - fn from(value: &str) -> Self { - Self::Selector(value.to_owned()) - } -} - -impl Op { - /// Creates a selector operation from a query string. - /// - /// The selector uses jq-like syntax for navigating and transforming Arrow data. - /// The query string is parsed at execution time. - /// - /// # Examples - /// - /// - `.field` - Access a field in a struct - /// - `.parent.child` - Access nested fields - /// - `.array[]` - Explode/flatten an array into multiple rows - /// - `.array[].field` - Explode array and access a field in each element - pub fn selector(query: impl Into) -> Self { - Self::Selector(query.into()) - } - - /// Converts binary arrays to list arrays of `u8`. - pub fn binary_to_list_uint8() -> Self { - Self::BinaryToListUInt8 - } - - /// Efficiently casts a component to a new `DataType`. - pub fn cast(data_type: DataType) -> Self { - Self::Cast(op::Cast { - to_inner_type: data_type, - }) - } - - /// Ignores any input and returns a constant `ListArray`. - /// - /// Commonly used with [`LensBuilder::output_static_columns`]. - /// When used in non-static columns this function will _not_ guarantee the correct amount of rows. - pub fn constant(value: ListArray) -> Self { - Self::func(move |_| Ok(value.clone())) - } - - /// Converts video codec strings to Rerun `VideoCodec` enum values (as `u32`). - pub fn string_to_video_codec() -> Self { - Self::StringToVideoCodecUInt32 - } - - /// Prepends a prefix to each string value, including empty strings. - pub fn string_prefix(prefix: impl Into) -> Self { - Self::StringPrefix(prefix.into()) - } - - /// Prepends a prefix to each non-empty string value, leaving empty strings unchanged. - pub fn string_prefix_nonempty(prefix: impl Into) -> Self { - Self::StringPrefixNonEmpty(prefix.into()) - } - - /// Appends a suffix to each string value, including empty strings. - pub fn string_suffix(suffix: impl Into) -> Self { - Self::StringSuffix(suffix.into()) - } - - /// Appends a suffix to each non-empty string value, leaving empty strings unchanged. - pub fn string_suffix_nonempty(suffix: impl Into) -> Self { - Self::StringSuffixNonEmpty(suffix.into()) - } - - /// Converts timestamp structs with `seconds`/`nanos` or `sec`/`nsec` fields to total nanoseconds. - pub fn time_spec_to_nanos() -> Self { - Self::TimeSpecToNanos - } - - /// A user-defined arbitrary function to convert a component column. - pub fn func(func: F) -> Self - where - F: for<'a> Fn(&'a ListArray) -> Result + Send + Sync + 'static, - { - Self::Func(Box::new(func)) - } -} - -impl Op { - fn call(&self, list_array: &ListArray) -> Result, OpError> { - match self { - Self::Selector(query) => { - let selector = Selector::from_str(query)?; - Ok(selector.execute_per_row(list_array)?) - } - Self::Cast(op) => op.call(list_array).map(Some), - Self::BinaryToListUInt8 => map::MapList::new(semantic::BinaryToListUInt8::::new()) - .transform(list_array) - .map_err(Into::into) - .map(Some), - Self::StringToVideoCodecUInt32 => { - map::MapList::new(semantic::StringToVideoCodecUInt32::default()) - .transform(list_array) - .map_err(Into::into) - .map(Some) - } - Self::StringPrefix(prefix) => map::MapList::new(map::StringPrefix::new(prefix.clone())) - .transform(list_array) - .map_err(Into::into) - .map(Some), - Self::StringPrefixNonEmpty(prefix) => map::MapList::new( - map::StringPrefix::new(prefix.clone()).with_prefix_empty_string(false), - ) - .transform(list_array) - .map_err(Into::into) - .map(Some), - Self::StringSuffix(suffix) => map::MapList::new(map::StringSuffix::new(suffix.clone())) - .transform(list_array) - .map_err(Into::into) - .map(Some), - Self::StringSuffixNonEmpty(suffix) => map::MapList::new( - map::StringSuffix::new(suffix.clone()).with_suffix_empty_string(false), - ) - .transform(list_array) - .map_err(Into::into) - .map(Some), - Self::TimeSpecToNanos => map::MapList::new(semantic::TimeSpecToNanos::default()) - .transform(list_array) - .map_err(Into::into) - .map(Some), - Self::Func(func) => func(list_array).map(Some), - } - } -} - /// A lens that transforms component data from one form to another. /// /// Lenses allow you to extract, transform, and restructure component data. They @@ -373,27 +198,13 @@ impl PartialChunk { } } -/// Sequentially applies a list of [`Op`]s to a list array. -/// -/// Exits early if an evaluation of an [`Op`] yields `None`, skipping subsequent ops. -fn apply_ops(initial: ListArray, ops: &[Op]) -> Result, OpError> { - let mut current = initial; - for op in ops { - match op.call(¤t)? { - Some(next) => current = next, - None => return Ok(None), - } - } - Ok(Some(current)) -} - fn collect_output_components_iter<'a>( input: &'a SerializedComponentColumn, components: &'a [ComponentOutput], entity_path: &'a EntityPath, ) -> impl Iterator> + 'a { - components.iter().filter_map(move |output| { - match apply_ops(input.list_array.clone(), &output.ops) { + components.iter().filter_map( + move |output| match output.selector.transform(&input.list_array) { Ok(Some(list_array)) => Some(Ok((output.component_descr.clone(), list_array))), Ok(None) => { re_log::debug_once!( @@ -408,8 +219,8 @@ fn collect_output_components_iter<'a>( component: output.component_descr.component, source: Box::new(source), })), - } - }) + }, + ) } fn collect_output_times_iter<'a>( @@ -418,7 +229,7 @@ fn collect_output_times_iter<'a>( entity_path: &'a EntityPath, ) -> impl Iterator> + 'a { timelines.iter().filter_map( - move |time| match apply_ops(input.list_array.clone(), &time.ops) { + move |time| match time.selector.transform(&input.list_array) { Ok(Some(list_array)) => Some(Ok((time.timeline_name, time.timeline_type, list_array))), Ok(None) => { re_log::debug_once!( @@ -699,7 +510,7 @@ impl OneToMany { match result { Ok((timeline_name, timeline_type, list_array)) => { match reshape::Explode.transform(&list_array) { - Ok(exploded) => { + Ok(Some(exploded)) => { match try_convert_time_column( timeline_name, timeline_type, @@ -712,12 +523,13 @@ impl OneToMany { } } } + Ok(None) => None, Err(err) => { errors.push(LensError::TimeOperationFailed { entity_path: entity_path.clone(), input_component: input.descriptor.component, timeline_name, - source: Box::new(err.into()), + source: Box::new(err), }); None } @@ -736,15 +548,16 @@ impl OneToMany { .filter_map(|result| match result { Ok((component_descr, list_array)) => { match reshape::Explode.transform(&list_array) { - Ok(exploded) => { + Ok(Some(exploded)) => { Some(SerializedComponentColumn::new(exploded, component_descr)) } + Ok(None) => None, Err(err) => { errors.push(LensError::ComponentOperationFailed { entity_path: entity_path.clone(), input_component: input.descriptor.component, component: component_descr.component, - source: Box::new(err.into()), + source: Box::new(err), }); None } diff --git a/crates/store/re_lenses/src/builder.rs b/crates/store/re_lenses/src/builder.rs index 4cdccace1fc0..4f016e6a7245 100644 --- a/crates/store/re_lenses/src/builder.rs +++ b/crates/store/re_lenses/src/builder.rs @@ -1,11 +1,13 @@ //! Builder API for constructing lenses. +use arrow::array::ListArray; +use re_arrow_combinators::Transform; use re_chunk::{ComponentIdentifier, EntityPath, TimelineName}; use re_log_types::{EntityPathFilter, TimeType}; use re_sdk_types::ComponentDescriptor; use crate::ast::{OneToMany, OneToOne, Static}; -use crate::{LensError, Op, ast}; +use crate::{LensError, ast}; /// Builder for lenses with support for multiple output modes. #[must_use] @@ -28,6 +30,10 @@ impl LensBuilder { } } + // TODO(RR-3962): Remove the `Result` return values from the closures again, by + // introducing a proxy object to `Selector` that holds a string internally and + // therefore is infallible. + /// Adds a temporal output with 1:1 row mapping. /// /// Each input row produces exactly one output row. Outputs inherit time columns from @@ -36,10 +42,10 @@ impl LensBuilder { /// The output will use the same entity path as the input. pub fn output_columns( mut self, - builder: impl FnOnce(ColumnsBuilder) -> ColumnsBuilder, + builder: impl FnOnce(ColumnsBuilder) -> Result, ) -> Result { let output_builder = ColumnsBuilder::new(ast::TargetEntity::SameAsInput); - let output = builder(output_builder).build(&self.input)?; + let output = builder(output_builder)?.build(&self.input)?; self.outputs.push(output); Ok(self) } @@ -51,10 +57,10 @@ impl LensBuilder { pub fn output_columns_at( mut self, entity_path: impl Into, - builder: impl FnOnce(ColumnsBuilder) -> ColumnsBuilder, + builder: impl FnOnce(ColumnsBuilder) -> Result, ) -> Result { let output_builder = ColumnsBuilder::new(ast::TargetEntity::Explicit(entity_path.into())); - let output = builder(output_builder).build(&self.input)?; + let output = builder(output_builder)?.build(&self.input)?; self.outputs.push(output); Ok(self) } @@ -66,10 +72,10 @@ impl LensBuilder { /// The output will use the same entity path as the input. pub fn output_static_columns( mut self, - builder: impl FnOnce(StaticColumnsBuilder) -> StaticColumnsBuilder, + builder: impl FnOnce(StaticColumnsBuilder) -> Result, ) -> Result { let output_builder = StaticColumnsBuilder::new(ast::TargetEntity::SameAsInput); - let output = builder(output_builder).build(&self.input)?; + let output = builder(output_builder)?.build(&self.input)?; self.outputs.push(output); Ok(self) } @@ -80,11 +86,11 @@ impl LensBuilder { pub fn output_static_columns_at( mut self, entity_path: impl Into, - builder: impl FnOnce(StaticColumnsBuilder) -> StaticColumnsBuilder, + builder: impl FnOnce(StaticColumnsBuilder) -> Result, ) -> Result { let output_builder = StaticColumnsBuilder::new(ast::TargetEntity::Explicit(entity_path.into())); - let output = builder(output_builder).build(&self.input)?; + let output = builder(output_builder)?.build(&self.input)?; self.outputs.push(output); Ok(self) } @@ -98,10 +104,10 @@ impl LensBuilder { /// The output will use the same entity path as the input. pub fn output_scatter_columns( mut self, - builder: impl FnOnce(ScatterColumnsBuilder) -> ScatterColumnsBuilder, + builder: impl FnOnce(ScatterColumnsBuilder) -> Result, ) -> Result { let output_builder = ScatterColumnsBuilder::new(ast::TargetEntity::SameAsInput); - let output = builder(output_builder).build(&self.input)?; + let output = builder(output_builder)?.build(&self.input)?; self.outputs.push(output); Ok(self) } @@ -114,11 +120,11 @@ impl LensBuilder { pub fn output_scatter_columns_at( mut self, entity_path: impl Into, - builder: impl FnOnce(ScatterColumnsBuilder) -> ScatterColumnsBuilder, + builder: impl FnOnce(ScatterColumnsBuilder) -> Result, ) -> Result { let output_builder = ScatterColumnsBuilder::new(ast::TargetEntity::Explicit(entity_path.into())); - let output = builder(output_builder).build(&self.input)?; + let output = builder(output_builder)?.build(&self.input)?; self.outputs.push(output); Ok(self) } @@ -145,6 +151,11 @@ pub struct ColumnsBuilder { time_outputs: Vec, } +// TODO(RR-3962): Get rid of the `unnecessary_wraps`. +#[expect( + clippy::unnecessary_wraps, + reason = "Result return enables `?` chaining in builder closures" +)] impl ColumnsBuilder { fn new(target_entity: ast::TargetEntity) -> Self { Self { @@ -158,17 +169,17 @@ impl ColumnsBuilder { /// /// # Arguments /// * `component_descr` - The descriptor for the output component - /// * `ops` - Sequence of operations to apply to transform the input column + /// * `selector` - Selector to apply to the input column pub fn component( mut self, component_descr: ComponentDescriptor, - ops: impl IntoIterator, - ) -> Self { + selector: impl Transform + Send + Sync + 'static, + ) -> Result { self.components.push(ast::ComponentOutput { component_descr, - ops: ops.into_iter().collect(), + selector: Box::new(selector), }); - self + Ok(self) } /// Adds a time extraction. @@ -178,19 +189,19 @@ impl ColumnsBuilder { /// # Arguments /// * `timeline_name` - Name of the timeline to create /// * `timeline_type` - Type of timeline (Sequence or Time) - /// * `ops` - Sequence of operations to extract time values (must produce [`arrow::array::Int64Array`]) + /// * `selector` - Selector to extract time values (must produce [`arrow::array::Int64Array`]) pub fn time( mut self, timeline_name: impl Into, timeline_type: TimeType, - ops: impl IntoIterator, - ) -> Self { + selector: impl Transform + Send + Sync + 'static, + ) -> Result { self.time_outputs.push(ast::TimeOutput { timeline_name: timeline_name.into(), timeline_type, - ops: ops.into_iter().collect(), + selector: Box::new(selector), }); - self + Ok(self) } /// Builds a [`ast::LensKind`], the `input` is passed for providing contextualized errors. @@ -217,6 +228,11 @@ pub struct StaticColumnsBuilder { components: Vec, } +// TODO(RR-3962): Get rid of the `unnecessary_wraps`. +#[expect( + clippy::unnecessary_wraps, + reason = "Result return enables `?` chaining in builder closures" +)] impl StaticColumnsBuilder { fn new(target_entity: ast::TargetEntity) -> Self { Self { @@ -229,17 +245,17 @@ impl StaticColumnsBuilder { /// /// # Arguments /// * `component_descr` - The descriptor for the output component - /// * `ops` - Sequence of operations to apply to transform the input column + /// * `selector` - Selector to apply to the input column pub fn component( mut self, component_descr: ComponentDescriptor, - ops: impl IntoIterator, - ) -> Self { + selector: impl Transform + Send + Sync + 'static, + ) -> Result { self.components.push(ast::ComponentOutput { component_descr, - ops: ops.into_iter().collect(), + selector: Box::new(selector), }); - self + Ok(self) } /// Builds a [`ast::LensKind`], the `input` is passed for providing contextualized errors. @@ -268,6 +284,11 @@ pub struct ScatterColumnsBuilder { time_outputs: Vec, } +// TODO(RR-3962): Get rid of the `unnecessary_wraps`. +#[expect( + clippy::unnecessary_wraps, + reason = "Result return enables `?` chaining in builder closures" +)] impl ScatterColumnsBuilder { fn new(target_entity: ast::TargetEntity) -> Self { Self { @@ -281,17 +302,17 @@ impl ScatterColumnsBuilder { /// /// # Arguments /// * `component_descr` - The descriptor for the output component - /// * `ops` - Sequence of operations to apply to transform the input column + /// * `selector` - Selector to apply to the input column pub fn component( mut self, component_descr: ComponentDescriptor, - ops: impl IntoIterator, - ) -> Self { + selector: impl Transform + Send + Sync + 'static, + ) -> Result { self.components.push(ast::ComponentOutput { component_descr, - ops: ops.into_iter().collect(), + selector: Box::new(selector), }); - self + Ok(self) } /// Adds a time extraction. @@ -301,19 +322,19 @@ impl ScatterColumnsBuilder { /// # Arguments /// * `timeline_name` - Name of the timeline to create /// * `timeline_type` - Type of timeline (Sequence or Time) - /// * `ops` - Sequence of operations to extract time values (must produce [`arrow::array::Int64Array`]) + /// * `selector` - Selector to extract time values (must produce [`arrow::array::Int64Array`]) pub fn time( mut self, timeline_name: impl Into, timeline_type: TimeType, - ops: impl IntoIterator, - ) -> Self { + selector: impl Transform + Send + Sync + 'static, + ) -> Result { self.time_outputs.push(ast::TimeOutput { timeline_name: timeline_name.into(), timeline_type, - ops: ops.into_iter().collect(), + selector: Box::new(selector), }); - self + Ok(self) } /// Builds a [`ast::LensKind`], the `input` is passed for providing contextualized errors. diff --git a/crates/store/re_lenses/src/error.rs b/crates/store/re_lenses/src/error.rs index f60a106385db..667734cb2618 100644 --- a/crates/store/re_lenses/src/error.rs +++ b/crates/store/re_lenses/src/error.rs @@ -2,8 +2,6 @@ use arrow::datatypes::DataType; use re_chunk::{ComponentIdentifier, EntityPath, TimelineName}; use re_log_types::EntityPathFilter; -use crate::op::OpError; - /// Different variants of errors that can happen when executing lenses. #[expect(missing_docs)] #[derive(Debug, thiserror::Error)] @@ -36,7 +34,7 @@ pub enum LensError { input_component: ComponentIdentifier, component: ComponentIdentifier, #[source] - source: Box, // Box because of size. + source: Box, }, #[error( @@ -47,7 +45,7 @@ pub enum LensError { input_component: ComponentIdentifier, timeline_name: TimelineName, #[source] - source: Box, // Box because of size. + source: Box, }, #[error( @@ -58,6 +56,9 @@ pub enum LensError { actual_type: DataType, }, + #[error(transparent)] + SelectorParseFailed(#[from] re_arrow_combinators::SelectorError), + #[error("Failed to scatter existing timeline '{timeline_name}' across output rows")] ScatterExistingTimeFailed { timeline_name: TimelineName, diff --git a/crates/store/re_lenses/src/lib.rs b/crates/store/re_lenses/src/lib.rs index 8ae3c07f2133..cdd814afad16 100644 --- a/crates/store/re_lenses/src/lib.rs +++ b/crates/store/re_lenses/src/lib.rs @@ -7,12 +7,10 @@ mod ast; mod builder; mod error; -mod op; -mod semantic; +pub mod op; pub use self::{ - ast::{Lens, Lenses, Op, OutputMode, PartialChunk}, + ast::{Lens, Lenses, OutputMode, PartialChunk}, builder::{ColumnsBuilder, LensBuilder, ScatterColumnsBuilder, StaticColumnsBuilder}, error::LensError, - op::OpError, }; diff --git a/crates/store/re_lenses/src/op.rs b/crates/store/re_lenses/src/op.rs deleted file mode 100644 index 7cbfc5ff80fa..000000000000 --- a/crates/store/re_lenses/src/op.rs +++ /dev/null @@ -1,56 +0,0 @@ -//! Provides commonly used transformations of Arrow arrays. -//! -//! These operations should not be exposed publicly, but instead be wrapped by the [`crate::Op`] abstraction. - -// TODO(grtlr): Eventually we will want to make the types in here compatible with Datafusion UDFs. - -use std::sync::Arc; - -use arrow::array::{Array as _, ListArray}; -use arrow::compute; -use arrow::datatypes::{DataType, Field}; - -/// Errors that occur during low-level operation execution on columns. -#[derive(Debug, thiserror::Error)] -pub enum OpError { - /// Error from Arrow combinator transformations. - #[error(transparent)] - Transform(#[from] re_arrow_combinators::Error), - - /// Error from selector parsing. - #[error(transparent)] - Selector(Box), // Box because of size. - - /// Error from Arrow operations. - #[error(transparent)] - Arrow(#[from] arrow::error::ArrowError), - - /// Other custom errors. - #[error(transparent)] - Other(Box), -} - -impl From for OpError { - fn from(value: re_arrow_combinators::SelectorError) -> Self { - Self::Selector(value.into()) - } -} - -/// Casts the `value_type` (inner array) of a `ListArray` to a different data type. -#[derive(Debug)] -pub struct Cast { - pub(crate) to_inner_type: DataType, -} - -impl Cast { - pub fn call(&self, list_array: &ListArray) -> Result { - let (_field, offsets, ref array, nulls) = list_array.clone().into_parts(); - let res = compute::cast(array, &self.to_inner_type)?; - Ok(ListArray::new( - Arc::new(Field::new_list_field(res.data_type().clone(), true)), - offsets, - res, - nulls, - )) - } -} diff --git a/crates/store/re_lenses/src/op/basic.rs b/crates/store/re_lenses/src/op/basic.rs new file mode 100644 index 000000000000..0f0fd988092d --- /dev/null +++ b/crates/store/re_lenses/src/op/basic.rs @@ -0,0 +1,52 @@ +//! Basic list-level transforms for common operations. + +use std::sync::Arc; + +use arrow::array::ListArray; +use arrow::datatypes::{DataType, Field}; + +use re_arrow_combinators::{Error, Transform}; + +/// Casts the inner values of a [`ListArray`] to a new [`DataType`]. +pub struct Cast { + to_type: DataType, +} + +impl Transform for Cast { + type Source = ListArray; + type Target = ListArray; + + fn transform(&self, source: &ListArray) -> Result, Error> { + let (field, offsets, values, nulls) = source.clone().into_parts(); + let cast_values = arrow::compute::cast(&values, &self.to_type)?; + let new_field = Arc::new(Field::new_list_field( + self.to_type.clone(), + field.is_nullable(), + )); + Ok(Some(ListArray::new(new_field, offsets, cast_values, nulls))) + } +} + +/// Creates a [`Cast`] transform that casts inner values to the given [`DataType`]. +pub fn cast(to_type: DataType) -> Cast { + Cast { to_type } +} + +/// Ignores the input and returns a fixed [`ListArray`]. +pub struct Constant { + value: ListArray, +} + +impl Transform for Constant { + type Source = ListArray; + type Target = ListArray; + + fn transform(&self, _source: &ListArray) -> Result, Error> { + Ok(Some(self.value.clone())) + } +} + +/// Creates a [`Constant`] transform that always returns the given [`ListArray`]. +pub fn constant(value: ListArray) -> Constant { + Constant { value } +} diff --git a/crates/store/re_lenses/src/op/mod.rs b/crates/store/re_lenses/src/op/mod.rs new file mode 100644 index 000000000000..96e22ea1c9ec --- /dev/null +++ b/crates/store/re_lenses/src/op/mod.rs @@ -0,0 +1,15 @@ +//! Re-exports of all available element-level transform ops. + +pub mod basic; +pub mod semantic; +pub mod string; + +// TODO(grtlr): We expose these functions here in the same format as we would call +// them in a future selector runtime. This might help with creating better convenience +// functions/macros around selector parsing in the future. + +pub use self::{ + basic::{cast, constant}, + semantic::{binary_to_list_uint8, string_to_video_codec, timespec_to_nanos}, + string::{string_prefix, string_prefix_nonempty, string_suffix, string_suffix_nonempty}, +}; diff --git a/crates/store/re_lenses/src/semantic.rs b/crates/store/re_lenses/src/op/semantic.rs similarity index 78% rename from crates/store/re_lenses/src/semantic.rs rename to crates/store/re_lenses/src/op/semantic.rs index 444113fd3a11..23b5280e75ea 100644 --- a/crates/store/re_lenses/src/semantic.rs +++ b/crates/store/re_lenses/src/op/semantic.rs @@ -1,6 +1,4 @@ //! Semantic array transforms for concrete applications. -//! -//! Note: These should not be exposed as part of the public API, but rather wrapped in [`crate::Op`]. use std::marker::PhantomData; use std::sync::Arc; @@ -40,7 +38,7 @@ impl Transform for BinaryToListUInt8; type Target = GenericListArray; - fn transform(&self, source: &GenericBinaryArray) -> Result { + fn transform(&self, source: &GenericBinaryArray) -> Result, Error> { use arrow::array::UInt8Array; use arrow::buffer::ScalarBuffer; @@ -71,7 +69,7 @@ impl Transform for BinaryToListUInt8( source: &StructArray, field_names: &[&str], - ) -> Result, Error> { + ) -> Result>, Error> { for &name in field_names { - if let Ok(array_ref) = reshape::GetField::new(name).transform(source) { + if let Ok(Some(array_ref)) = reshape::GetField::new(name).transform(source) { let casted = arrow::compute::cast(&array_ref, &TargetType::DATA_TYPE)?; - return DowncastRef::::new().transform(&casted); + let downcasted = DowncastRef::::new().transform(&casted)?; + + re_log::debug_assert!( + downcasted.is_some(), + "downcasting directly after casting should not fail" + ); + + return Ok(downcasted); } } Err(Error::FieldNotFound { @@ -104,12 +109,15 @@ impl Transform for TimeSpecToNanos { type Source = StructArray; type Target = Int64Array; - fn transform(&self, source: &StructArray) -> Result { - let seconds_array = - Self::get_field_from_variants::(source, &["seconds", "sec"])?; - let nanos_array = Self::get_field_from_variants::(source, &["nanos", "nsec"])?; + fn transform(&self, source: &StructArray) -> Result, Error> { + let (Some(seconds_array), Some(nanos_array)) = ( + Self::get_field_from_variants::(source, &["seconds", "sec"])?, + Self::get_field_from_variants::(source, &["nanos", "nsec"])?, + ) else { + return Ok(None); + }; - Ok(arrow::compute::try_binary( + Ok(Some(arrow::compute::try_binary( &seconds_array, &nanos_array, |seconds: i64, nanos: i32| -> Result { @@ -117,7 +125,7 @@ impl Transform for TimeSpecToNanos { .mul_checked(1_000_000_000)? .add_checked(nanos as i64) }, - )?) + )?)) } } @@ -130,35 +138,52 @@ impl Transform for StringToVideoCodecUInt32 { type Source = StringArray; type Target = UInt32Array; - fn transform(&self, source: &StringArray) -> Result { - Ok(source - .iter() - .try_fold( - UInt32Builder::with_capacity(source.len()), - |mut builder, maybe_str| { - if let Some(codec_str) = maybe_str { - let codec = match codec_str.to_lowercase().as_str() { - "h264" => VideoCodec::H264, - "h265" => VideoCodec::H265, - "av1" => VideoCodec::AV1, - _ => { - return Err(Error::UnexpectedValue { - expected: &["h264", "h265", "av1"], - actual: codec_str.to_owned(), - }); - } - }; - builder.append_value(codec as u32); - } else { - builder.append_null(); - } - Ok(builder) - }, - )? - .finish()) + fn transform(&self, source: &StringArray) -> Result, Error> { + Ok(Some( + source + .iter() + .try_fold( + UInt32Builder::with_capacity(source.len()), + |mut builder, maybe_str| { + if let Some(codec_str) = maybe_str { + let codec = match codec_str.to_lowercase().as_str() { + "h264" => VideoCodec::H264, + "h265" => VideoCodec::H265, + "av1" => VideoCodec::AV1, + _ => { + return Err(Error::UnexpectedValue { + expected: &["h264", "h265", "av1"], + actual: codec_str.to_owned(), + }); + } + }; + builder.append_value(codec as u32); + } else { + builder.append_null(); + } + Ok(builder) + }, + )? + .finish(), + )) } } +/// Converts binary data (i32 offsets) to a list of `u8` values. +pub fn binary_to_list_uint8() -> BinaryToListUInt8 { + BinaryToListUInt8::new() +} + +/// Converts a timestamp struct (`seconds`/`nanos`) to nanoseconds. +pub fn timespec_to_nanos() -> TimeSpecToNanos { + TimeSpecToNanos::default() +} + +/// Converts video codec name strings to `VideoCodec` enum values. +pub fn string_to_video_codec() -> StringToVideoCodecUInt32 { + StringToVideoCodecUInt32::default() +} + #[cfg(test)] mod tests { use std::sync::Arc; @@ -175,7 +200,7 @@ mod tests { use super::*; // Generic test for binary arrays where the offset is the same. - fn impl_binary_test() { + fn impl_binary_test() -> Result<(), Error> { let mut builder = GenericByteBuilder::>::new(); builder.append_value(b"hello"); builder.append_value(b"world"); @@ -185,7 +210,7 @@ mod tests { let binary_array = builder.finish(); let result = BinaryToListUInt8::::new() - .transform(&binary_array) + .transform(&binary_array)? .unwrap(); // Verify structure @@ -246,15 +271,19 @@ mod tests { assert_eq!(uint8.value(1), 0xFF); assert_eq!(uint8.value(2), 0x42); } + + Ok(()) } #[test] - fn test_binary_to_list_uint8() { + fn test_binary_to_list_uint8() -> Result<(), Error> { // We test the different offset combinations. - impl_binary_test::(); - impl_binary_test::(); - impl_binary_test::(); - impl_binary_test::(); + impl_binary_test::()?; + impl_binary_test::()?; + impl_binary_test::()?; + impl_binary_test::()?; + + Ok(()) } #[test] @@ -292,7 +321,7 @@ mod tests { /// Tests that timespec structs are correctly converted to nanoseconds, including (mixed) null handling. #[test] - fn test_timespec_to_nanos() { + fn test_timespec_to_nanos() -> Result<(), Error> { let seconds_field = Arc::new(Field::new("seconds", DataType::Int64, true)); let nanos_field = Arc::new(Field::new("nanos", DataType::Int32, true)); @@ -317,8 +346,8 @@ mod tests { None, ); let output_array = TimeSpecToNanos::default() - .transform(&struct_array) - .expect("transformation failed"); + .transform(&struct_array)? + .unwrap(); let expected_array = Int64Array::from(vec![ Some(1_500_000_000), None, @@ -327,11 +356,13 @@ mod tests { None, ]); assert_eq!(output_array, expected_array); + + Ok(()) } /// Tests that timespec structs with `sec`/`nsec` field names work too. #[test] - fn test_timespec_to_nanos_sec_nsec() { + fn test_timespec_to_nanos_sec_nsec() -> Result<(), Error> { let seconds_field = Arc::new(Field::new("sec", DataType::Int64, true)); let nanos_field = Arc::new(Field::new("nsec", DataType::Int32, true)); @@ -344,15 +375,17 @@ mod tests { None, ); let output_array = TimeSpecToNanos::default() - .transform(&struct_array) - .expect("transformation failed"); + .transform(&struct_array)? + .unwrap(); let expected_array = Int64Array::from(vec![Some(1_500_000_000), Some(2_000_000_000)]); assert_eq!(output_array, expected_array); + + Ok(()) } /// Tests that timespec with uint32 seconds and nanos fields are cast correctly. #[test] - fn test_timespec_to_nanos_uint32() { + fn test_timespec_to_nanos_uint32() -> Result<(), Error> { let seconds_field = Arc::new(Field::new("sec", DataType::UInt32, false)); let nanos_field = Arc::new(Field::new("nsec", DataType::UInt32, false)); @@ -365,15 +398,17 @@ mod tests { None, ); let output_array = TimeSpecToNanos::default() - .transform(&struct_array) - .expect("transformation failed"); + .transform(&struct_array)? + .unwrap(); let expected_array = Int64Array::from(vec![1_500_000_000i64, 2_000_000_000]); assert_eq!(output_array, expected_array); + + Ok(()) } /// Tests that supported codecs are correctly converted, and checks case-insensitivity and null handling. #[test] - fn test_string_to_codec_uint32() { + fn test_string_to_codec_uint32() -> Result<(), Error> { // Note: mixed codecs normally don't make sense, but should be fine from a pure conversion perspective. let input_array = StringArray::from(vec![ Some("H264"), @@ -384,8 +419,8 @@ mod tests { ]); assert_eq!(input_array.null_count(), 1); let output_array = StringToVideoCodecUInt32::default() - .transform(&input_array) - .expect("transformation failed"); + .transform(&input_array)? + .unwrap(); assert_eq!(output_array.null_count(), 1); let expected_array = UInt32Array::from(vec![ Some(VideoCodec::H264 as u32), @@ -395,6 +430,8 @@ mod tests { Some(VideoCodec::AV1 as u32), ]); assert_eq!(output_array, expected_array); + + Ok(()) } /// Tests that we return the correct error when an unsupported codec is in the data. @@ -414,7 +451,7 @@ mod tests { /// Tests that all codecs defined in `VideoCodec` are accepted. #[test] - fn test_string_to_codec_uint32_all_supported() { + fn test_string_to_codec_uint32_all_supported() -> Result<(), Error> { let variants = VideoCodec::variants(); let variant_names = variants .iter() @@ -427,8 +464,8 @@ mod tests { .collect::>>(), ); let output_array = StringToVideoCodecUInt32::default() - .transform(&input_array) - .expect("transformation failed - are all variants of VideoCodec supported?"); + .transform(&input_array)? + .unwrap(); let expected_array = UInt32Array::from( variants .iter() @@ -436,5 +473,7 @@ mod tests { .collect::>>(), ); assert_eq!(output_array, expected_array); + + Ok(()) } } diff --git a/crates/store/re_lenses/src/op/string.rs b/crates/store/re_lenses/src/op/string.rs new file mode 100644 index 000000000000..351e41776e31 --- /dev/null +++ b/crates/store/re_lenses/src/op/string.rs @@ -0,0 +1,6 @@ +//! Re-exports of string transforms from `re_arrow_combinators`. + +pub use re_arrow_combinators::map::{ + StringPrefix, StringSuffix, string_prefix, string_prefix_nonempty, string_suffix, + string_suffix_nonempty, +}; diff --git a/crates/top/re_sdk/src/lenses/mod.rs b/crates/top/re_sdk/src/lenses/mod.rs index 8f795f379cc5..bd9cfea3b2fb 100644 --- a/crates/top/re_sdk/src/lenses/mod.rs +++ b/crates/top/re_sdk/src/lenses/mod.rs @@ -10,11 +10,11 @@ mod sink; // Re-exports from re_lenses. // We should be careful not to expose too much implementation details here. pub use re_lenses::{ - ColumnsBuilder, Lens, LensBuilder, LensError, Lenses, Op, OpError, OutputMode, PartialChunk, - ScatterColumnsBuilder, StaticColumnsBuilder, + ColumnsBuilder, Lens, LensBuilder, LensError, Lenses, OutputMode, PartialChunk, + ScatterColumnsBuilder, StaticColumnsBuilder, op, }; -pub use re_arrow_combinators::Selector; +pub use re_arrow_combinators::{Selector, Transform}; // We keep the sink in re_sdk since it depends on LogSink. pub use self::sink::LensesSink; diff --git a/crates/top/re_sdk/tests/lenses/operations.rs b/crates/top/re_sdk/tests/lenses/operations.rs index 0533b08e234f..ec90dd0d5960 100644 --- a/crates/top/re_sdk/tests/lenses/operations.rs +++ b/crates/top/re_sdk/tests/lenses/operations.rs @@ -5,8 +5,9 @@ use std::sync::Arc; use arrow::array::{AsArray as _, Int32Builder, ListArray, ListBuilder, StringBuilder}; use arrow::datatypes::{DataType, Field}; +use re_arrow_combinators::Transform as _; use re_chunk::{ArrowArray as _, Chunk, ChunkId, TimeColumn, TimelineName}; -use re_sdk::lenses::{Lens, Lenses, Op, OutputMode}; +use re_sdk::lenses::{Lens, Lenses, OutputMode, Selector, op}; use re_sdk_types::ComponentDescriptor; use re_sdk_types::archetypes::Scalars; @@ -138,7 +139,7 @@ fn test_destructure_cast() { .output_columns_at("nullability/a", |out| { out.component( Scalars::descriptor_scalars(), - [Op::selector(".a"), Op::cast(DataType::Float64)], + Selector::parse(".a")?.then(op::cast(DataType::Float64)), ) }) .unwrap() @@ -168,7 +169,7 @@ fn test_destructure() { "structs", ) .output_columns_at("nullability/b", |out| { - out.component(Scalars::descriptor_scalars(), [Op::selector(".b")]) + out.component(Scalars::descriptor_scalars(), Selector::parse(".b")?) }) .unwrap() .build(); @@ -188,36 +189,41 @@ fn test_destructure() { #[test] fn test_inner_count() { - use re_sdk::lenses::OpError; - let original_chunk = nullability_chunk(); println!("{original_chunk}"); - let count_fn = |list_array: &ListArray| -> Result { - let mut builder = ListBuilder::new(Int32Builder::new()); - - for maybe_array in list_array.iter() { - match maybe_array { - None => builder.append_null(), - Some(component_batch_array) => { - builder - .values() - .append_value(component_batch_array.len() as i32); - builder.append(true); + let count_fn = + |list_array: &ListArray| -> Result, re_arrow_combinators::Error> { + let mut builder = ListBuilder::new(Int32Builder::new()); + + for maybe_array in list_array.iter() { + match maybe_array { + None => builder.append_null(), + Some(component_batch_array) => { + builder + .values() + .append_value(component_batch_array.len() as i32); + builder.append(true); + } } } - } - Ok(builder.finish()) - }; + Ok(Some(builder.finish())) + }; let count = Lens::for_input_column( re_log_types::EntityPathFilter::parse_forgiving("nullability"), "strings", ) .output_columns(|out| { - out.component(ComponentDescriptor::partial("counts"), [Op::func(count_fn)]) - .component(ComponentDescriptor::partial("original"), []) + out.component( + ComponentDescriptor::partial("counts"), + Selector::parse(".")?.then(count_fn), + )? + .component( + ComponentDescriptor::partial("original"), + Selector::parse(".")?, + ) }) .unwrap() .build(); @@ -258,11 +264,11 @@ fn test_static_chunk_creation() { .output_static_columns_at("nullability/static", |out| { out.component( ComponentDescriptor::partial("static_metadata_a"), - [Op::constant(metadata_builder_a.finish())], - ) + Selector::parse(".")?.then(op::constant(metadata_builder_a.finish())), + )? .component( ComponentDescriptor::partial("static_metadata_b"), - [Op::constant(metadata_builder_b.finish())], + Selector::parse(".")?.then(op::constant(metadata_builder_b.finish())), ) }) .unwrap() @@ -329,8 +335,11 @@ fn test_time_column_extraction() { "my_timestamp", ) .output_columns(|out| { - out.time("my_timeline", TimeType::Sequence, []) - .component(ComponentDescriptor::partial("extracted_time"), []) + out.time("my_timeline", TimeType::Sequence, Selector::parse(".")?)? + .component( + ComponentDescriptor::partial("extracted_time"), + Selector::parse(".")?, + ) }) .unwrap() .build(); @@ -418,10 +427,7 @@ fn create_test_struct_list() -> arrow::array::ListArray { #[test] fn test_scatter_columns() { - use re_arrow_combinators::{Selector, Transform as _}; use re_log_types::TimeType; - use re_sdk::lenses::OpError; - use std::str::FromStr as _; // Create a chunk with list of structs that should be exploded/scattered // Each element is a struct with {timestamp: i64, value: String} @@ -442,27 +448,17 @@ fn test_scatter_columns() { println!("Original chunk:"); println!("{original_chunk}"); - // Helper to extract value field from structs: List -> List - let extract_value = |list_array: &ListArray| -> Result { - Ok(Selector::from_str(".value")?.transform(list_array)?) - }; - - // Helper to extract timestamp field from structs: List -> List - let extract_timestamp = |list_array: &ListArray| -> Result { - Ok(Selector::from_str(".timestamp")?.transform(list_array)?) - }; - // Create a scatter lens that explodes the nested lists let scatter_lens = Lens::for_input_column(re_log_types::EntityPathFilter::all(), "nested_data") .output_scatter_columns_at("scatter_test/exploded", |out| { out.component( ComponentDescriptor::partial("exploded_strings"), - [Op::func(extract_value)], - ) + Selector::parse(".value")?, + )? .time( "my_timestamp", TimeType::Sequence, - [Op::func(extract_timestamp)], + Selector::parse(".timestamp")?, ) }) .unwrap() @@ -519,10 +515,7 @@ fn test_scatter_columns() { #[test] fn test_scatter_columns_static() { - use re_arrow_combinators::{Selector, Transform as _}; use re_log_types::TimeType; - use re_sdk::lenses::OpError; - use std::str::FromStr as _; // Test scatter with no existing timelines - only exploded timeline outputs let struct_list = create_test_struct_list(); @@ -541,27 +534,17 @@ fn test_scatter_columns_static() { println!("Original chunk (no timelines):"); println!("{original_chunk}"); - // Helper to extract value field from structs: List -> List - let extract_value = |list_array: &ListArray| -> Result { - Ok(Selector::from_str(".value")?.transform(list_array)?) - }; - - // Helper to extract timestamp field from structs: List -> List - let extract_timestamp = |list_array: &ListArray| -> Result { - Ok(Selector::from_str(".timestamp")?.transform(list_array)?) - }; - // Create a scatter lens that explodes the nested lists let scatter_lens = Lens::for_input_column(re_log_types::EntityPathFilter::all(), "nested_data") .output_scatter_columns_at("scatter_test/exploded", |out| { out.component( ComponentDescriptor::partial("exploded_strings"), - [Op::func(extract_value)], - ) + Selector::parse(".value")?, + )? .time( "my_timestamp", TimeType::Sequence, - [Op::func(extract_timestamp)], + Selector::parse(".timestamp")?, ) }) .unwrap() diff --git a/crates/top/re_sdk/tests/lenses/output_mode.rs b/crates/top/re_sdk/tests/lenses/output_mode.rs index 6a0ac0111a02..4b3918935234 100644 --- a/crates/top/re_sdk/tests/lenses/output_mode.rs +++ b/crates/top/re_sdk/tests/lenses/output_mode.rs @@ -2,7 +2,7 @@ use arrow::array::{ListBuilder, StringBuilder}; use re_chunk::{Chunk, ChunkId, TimeColumn, TimelineName}; -use re_sdk::lenses::{Lens, Lenses, OutputMode}; +use re_sdk::lenses::{Lens, Lenses, OutputMode, Selector}; use re_sdk_types::ComponentDescriptor; /// Helper to create a simple chunk with string data for testing @@ -39,7 +39,10 @@ fn test_output_mode_forward_all() { "test_component", ) .output_columns_at("matched/output", |out| { - out.component(ComponentDescriptor::partial("transformed"), []) + out.component( + ComponentDescriptor::partial("transformed"), + Selector::parse(".")?, + ) }) .unwrap() .build(); @@ -87,7 +90,10 @@ fn test_output_mode_forward_unmatched() { "test_component", ) .output_columns_at("matched/output", |out| { - out.component(ComponentDescriptor::partial("transformed"), []) + out.component( + ComponentDescriptor::partial("transformed"), + Selector::parse(".")?, + ) }) .unwrap() .build(); @@ -131,7 +137,10 @@ fn test_output_mode_drop_unmatched() { "test_component", ) .output_columns_at("matched/output", |out| { - out.component(ComponentDescriptor::partial("transformed"), []) + out.component( + ComponentDescriptor::partial("transformed"), + Selector::parse(".")?, + ) }) .unwrap() .build(); diff --git a/examples/rust/lenses/src/main.rs b/examples/rust/lenses/src/main.rs index 06763aaf1612..73feb7b5e902 100644 --- a/examples/rust/lenses/src/main.rs +++ b/examples/rust/lenses/src/main.rs @@ -6,7 +6,7 @@ use arrow::array::{ }; use arrow::datatypes::{DataType, Field}; use rerun::external::re_log; -use rerun::lenses::{Lens, LensesSink, Op}; +use rerun::lenses::{Lens, LensesSink, Selector, Transform as _, op}; use rerun::sink::GrpcSink; use rerun::{ ComponentDescriptor, DynamicArchetype, RecordingStream, Scalars, SerializedComponentColumn, @@ -29,7 +29,7 @@ fn lens_flag() -> anyhow::Result { }) .collect(); - Ok(ListArray::new( + Ok(Some(ListArray::new( Arc::new(Field::new_list_field( scalar_array.data_type().clone(), true, @@ -37,7 +37,7 @@ fn lens_flag() -> anyhow::Result { offsets, Arc::new(scalar_array), nulls, - )) + ))) }; let series_points = SeriesPoints::new() @@ -55,15 +55,20 @@ fn lens_flag() -> anyhow::Result { .unwrap(); let lens = Lens::for_input_column("/flag".parse()?, "example:Flag:flag") - .output_columns(|out| out.component(Scalars::descriptor_scalars(), [Op::func(step_fn)]))? + .output_columns(|out| { + out.component( + Scalars::descriptor_scalars(), + Selector::parse(".")?.then(step_fn), + ) + })? .output_static_columns_at("/flag", |out| { out.component( series_points.descriptor, - [Op::constant(series_points.list_array)], - ) + Selector::parse(".")?.then(op::constant(series_points.list_array)), + )? .component( series_lines.descriptor, - [Op::constant(series_lines.list_array)], + Selector::parse(".")?.then(op::constant(series_lines.list_array)), ) })? .build(); @@ -75,25 +80,31 @@ fn main() -> anyhow::Result<()> { re_log::setup_logging(); let instruction = Lens::for_input_column("/instructions".parse()?, "example:Instruction:text") - .output_columns(|out| out.component(TextDocument::descriptor_text(), []))? + .output_columns(|out| { + out.component(TextDocument::descriptor_text(), Selector::parse(".")?) + })? .build(); let destructure = Lens::for_input_column("/nested".parse()?, "example:Nested:payload") .output_columns_at("nested/a", |out| { out.component( Scalars::descriptor_scalars(), - [Op::selector(".a"), Op::cast(DataType::Float64)], + Selector::parse(".a")?.then(op::cast(DataType::Float64)), ) })? .output_columns_at("nested/b", |out| { - out.component(Scalars::descriptor_scalars(), [Op::selector(".b")]) + out.component(Scalars::descriptor_scalars(), Selector::parse(".b")?) })? .build(); let time = Lens::for_input_column("/timestamped".parse()?, "my_timestamp") .output_columns(|out| { - out.time("my_timeline", rerun::time::TimeType::Sequence, []) - .component(ComponentDescriptor::partial("value"), []) + out.time( + "my_timeline", + rerun::time::TimeType::Sequence, + Selector::parse(".")?, + )? + .component(ComponentDescriptor::partial("value"), Selector::parse(".")?) })? .build(); From ccdca7b0fd7fab0efca2ceeea0fe84fc1fb31d6e Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 7 Mar 2026 11:53:34 +0100 Subject: [PATCH 069/513] Allow logging out and change account in the edit server menu Especially when switching between `Rerun` and `Niko canary` etc, it is annoying that I cannot easily switch account here image Source-Ref: 79dd02fdc083fe62a4e6edfdabf17ec2b5c3c9c1 --- crates/viewer/re_redap_browser/src/server_modal.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/crates/viewer/re_redap_browser/src/server_modal.rs b/crates/viewer/re_redap_browser/src/server_modal.rs index 60b3455e752f..3de805191c58 100644 --- a/crates/viewer/re_redap_browser/src/server_modal.rs +++ b/crates/viewer/re_redap_browser/src/server_modal.rs @@ -423,6 +423,15 @@ fn auth_ui(ui: &mut egui::Ui, ctx: &AppContext<'_>, auth: &mut Authentication) { ui.horizontal(|ui| { ui.label("Continue as"); ui.label(RichText::new(&logged_in.email).strong()); + + ui.weak("or"); + + if ui + .link(RichText::new("log out").color(ui.tokens().text_subdued)) + .clicked() + { + ctx.command_sender.send_system(SystemCommand::Logout); + } }); } else { // User is not logged in - start the login flow to show buttons From 618e23527d213ca6d19b769e0307e1c7fb795cd7 Mon Sep 17 00:00:00 2001 From: Andreas Reich Date: Mon, 9 Mar 2026 09:06:04 +0100 Subject: [PATCH 070/513] Improve snippet authoring docs Improve docs around snippets and add a short note to `AGENTS.md` where to find them Source-Ref: ae4966f9085d5a5a02d2968ec21fa2eb43eb1072 --- AGENTS.md | 4 +++ docs/snippets/README.md | 49 ++++++++++++++++++++++--------------- docs/snippets/snippets.toml | 15 +++++++++++- 3 files changed, 47 insertions(+), 21 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 8ef32d1ab49e..495e2f9738a7 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -130,6 +130,10 @@ Each view type (Spatial3D, TimeSeries, etc.) has registered visualizers: The viewer uses **immediate mode**: every frame, query the store and re-render from scratch. +## Documentation snippets + +See [`docs/snippets/README.md`](docs/snippets/README.md) for how to run, build, and find snippets. Configuration for testing and indexing is in [`docs/snippets/snippets.toml`](docs/snippets/snippets.toml). + ## Python development workflow Python uses a separate uv-managed .venv (not pixi's conda env): diff --git a/docs/snippets/README.md b/docs/snippets/README.md index 32e7535d177e..7bc07e1ed3ef 100644 --- a/docs/snippets/README.md +++ b/docs/snippets/README.md @@ -1,33 +1,42 @@ -# API examples +# Documentation snippets -These examples showcase common usage of each individual Rerun `Archetype`s. +Small, self-contained examples in `all/`, organized by category (`archetypes/`, `howto/`, `tutorials/`, `views/`, etc.). Most snippets have `.py`, `.rs`, and `.cpp` versions with the same base name, and are automatically used as docstrings for the `Archetype` APIs and the [Archetypes](https://www.rerun.io/docs/reference/types) documentation. -Most of these examples are automatically used as docstrings for the `Archetype` APIs in their respective SDKs, as well as the [Archetypes](https://www.rerun.io/docs/reference/types) section of the high-level documentation. +## Running snippets -## Usage +Rust and C++ snippets compile into a single dispatcher binary that takes the snippet name (without path/extension) as first argument. -You can run each example individually using the following: +- **C++**: `pixi run -e cpp cpp-build-snippets`, then `./build/debug/docs/snippets/all/` +- **Python**: `pixi run py-build && pixi run uvpy .py` +- **Rust**: `cargo run -p snippets -- [args]` -- **C++**: - - `pixi run -e cpp cpp-build-snippets` to compile all examples - - `./build/debug/docs/snippets/all/` to run, e.g. `./build/debug/docs/snippets/all/point3d_random` -- **Python**: `pixi run py-build && pixi run uvpy .py`, e.g. `pixi run uvpy point3d_random.py`. -- **Rust**: `cargo run -p snippets -- [args]`, e.g. `cargo run -p snippets -- point3d_random`. +## Build system -## Comparison test +Both `build.rs` (Rust) and `CMakeLists.txt` (C++) auto-copy snippet sources from `all/`, rename `main` to a per-snippet function, and generate a dispatcher. Don't edit files in `src/snippets/` directly. -The script `compare_snippet_output.py` execute the same logging commands from all 3 SDKs, save the results to distinct rrd files, and finally compare these rrd files. -These tests are then automatically run by the CI, which will loudly complain if the resulting rrd files don't match. +## Finding existing snippets -These tests check that A) all of our SDKs yield the exact same data when used the same way and B) act as regression tests, relying on the fact that it is extremely unlikely that all supported languages break in the exact same way at the exact same time. +`INDEX.md` is an auto-generated index (by codegen) mapping features/archetypes to snippets with per-language links. Check it before writing new snippets. -### Usage +## Snippet configuration -To run the comparison tests, check out `pixi run uvpy docs/snippets/compare_snippet_output.py --help`. -`pixi run uvpy docs/snippets/compare_snippet_output.py` is a valid invocation that will build all 3 SDKs and run all tests for all of them. +`snippets.toml` controls snippet testing and documentation indexing. See the comments in that file for details. -### Implementing new tests +## Comparison tests + +`compare_snippet_output.py` runs the same logging commands from all 3 SDKs, saves to distinct rrd files, and compares them. CI runs these automatically. + +These tests verify: +- All SDKs yield identical data when used the same way +- Act as regression tests (extremely unlikely all languages break identically) -Just pick a name for your test, and look at existing examples to get started. The `app_id` must be `rerun_example_`. +### Running comparison tests + +- `pixi run uvpy docs/snippets/compare_snippet_output.py --help` for options +- `pixi run uvpy docs/snippets/compare_snippet_output.py` builds all 3 SDKs and runs all tests + +### Implementing new tests -The comparison process is driven by file names, so make sure all 3 tests use the same name: `.rs`, `.cpp`, `.py`. +- Pick a name, look at existing examples to get started +- Use the same name across languages: `.rs`, `.cpp`, `.py` +- Set `app_id` to `rerun_example_` diff --git a/docs/snippets/snippets.toml b/docs/snippets/snippets.toml index fcf736f3a343..56b8bce59ca8 100644 --- a/docs/snippets/snippets.toml +++ b/docs/snippets/snippets.toml @@ -1,4 +1,17 @@ -# This file is read in `compare_snippet_output.py` and the `snippets` command in `re_dev_tools`/`build_examples`. +# This file controls snippet testing and documentation indexing. +# It is read in `compare_snippet_output.py` and the `snippets` command in `re_dev_tools`/`build_examples`. +# +# Sections: +# [snippets_ref.snippets.opt_out] — snippets excluded from the documentation index +# [snippets_ref.archetypes.opt_out] — archetypes that ignore associated snippets in the index +# [snippets_ref.components.opt_out] — components that ignore associated snippets in the index +# [snippets_ref.features] — groups snippets by feature for documentation +# [opt_out.run] — snippets that won't be executed for certain SDKs +# [opt_out.compare] — snippets that run but whose output isn't compared across +# languages (e.g. different RNG implementations) +# [extra_args] — extra arguments passed to snippets when running them +# +# Items within each section are in alphabetical order. # These snippets will be excluded from the snippet index, unconditionally. [snippets_ref.snippets.opt_out] From c028f40b71c3e41b8155a916d49fae2b34f4d733 Mon Sep 17 00:00:00 2001 From: Tim Saucer Date: Mon, 9 Mar 2026 15:59:13 +0100 Subject: [PATCH 071/513] Run benchmarks via modal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### Related * Part of https://linear.app/rerun/issue/RR-3525/benchmark-query-performance ### What This PR adjusts the performance benchmarks to run a subset of the segment IDs per run. It also adds a modal runner where we distribute the segment IDs across N workers. The results are aggregated at the end. This will allow us to push performance testing in a different way than single node execution. ### Results Here are first results of the light queries: ``` ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┓ ┃ Test Name ┃ Segments ┃ Duration avg (s) ┃ Duration min (s) ┃ Duration max (s) ┃ Batch Size avg ┃ Rows/Batch avg ┃ ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━┩ │ light_aggregate_simple_no_ordering │ 1000/1000 │ 0.6857 │ 0.0884 │ 4.9750 │ 37.0 │ 1.0 │ │ light_aggregate_simple_repartitioned │ 1000/1000 │ 0.8108 │ 0.1116 │ 5.6263 │ 16.0 │ 1.0 │ │ light_aggregate_simple_with_partitioning │ 1000/1000 │ 0.9360 │ 0.1035 │ 4.1769 │ 37.0 │ 1.0 │ │ light_fetch_large_video_blob │ 1000/1000 │ 92.8216 │ 3.4345 │ 274.7561 │ 6677669.7 │ 149.9 │ │ light_fetch_single_scalar_component │ 1000/1000 │ 0.9617 │ 0.3376 │ 5.1150 │ 27627.2 │ 767.2 │ │ light_window_requires_reordering │ 1000/1000 │ 1.0307 │ 0.3341 │ 4.7379 │ 24766.3 │ 770.8 │ │ light_window_with_ordering │ 1000/1000 │ 1.2144 │ 0.3558 │ 6.0499 │ 24650.1 │ 767.2 │ └──────────────────────────────────────────┴───────────┴──────────────────┴──────────────────┴──────────────────┴────────────────┴────────────────┘ ``` While running the light queries I did see CPU of the redap server get up to about 50%. --------- Signed-off-by: Andrea Reale Source-Ref: 80c5fb5e9d09b07e60c0dfc0c975477211385be1 Co-authored-by: Andrea Reale Co-authored-by: Clement Rey --- rerun_notebook/package-lock.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rerun_notebook/package-lock.json b/rerun_notebook/package-lock.json index 16528c219314..e68ea3771a0c 100644 --- a/rerun_notebook/package-lock.json +++ b/rerun_notebook/package-lock.json @@ -15,7 +15,7 @@ }, "../rerun_js/web-viewer": { "name": "@rerun-io/web-viewer", - "version": "0.30.0-alpha.1+dev", + "version": "0.31.0-alpha.1+dev", "license": "MIT", "devDependencies": { "dts-buddy": "^0.3.0", From 0f3f548f14ae38a05d6339970b7441faaac3c1de Mon Sep 17 00:00:00 2001 From: Andreas Reich Date: Mon, 9 Mar 2026 21:01:15 +0100 Subject: [PATCH 072/513] Fix dead pydocstyle.org links ## Summary Replace dead `pydocstyle.org` URLs in `rerun_py/pyproject.toml` with working ruff documentation equivalents Source-Ref: 374ab664ce9635dd51a8fdf8d42666b635d80094 Co-authored-by: Claude Opus 4.6 --- rerun_py/pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rerun_py/pyproject.toml b/rerun_py/pyproject.toml index 26e68b1a63f2..a01d8cc5ce61 100644 --- a/rerun_py/pyproject.toml +++ b/rerun_py/pyproject.toml @@ -106,7 +106,7 @@ lint.ignore = [ # No blank lines allowed after function docstring. "D202", - # npydocstyle: http://www.pydocstyle.org/en/stable/error_codes.html + # npydocstyle: https://docs.astral.sh/ruff/rules/#pydocstyle-d # numpy convention with a few additional lints "D107", "D203", @@ -162,7 +162,7 @@ lint.select = [ "B", # flake8-bugbear lints "C4", # Comprehension-related rules "COM", # Trailing-comma-related rules - "D", # pydocstyle codes https://www.pydocstyle.org/en/latest/error_codes.html + "D", # pydocstyle codes https://docs.astral.sh/ruff/rules/#pydocstyle-d "E", # pycodestyle error codes: https://pycodestyle.pycqa.org/en/latest/intro.html#error-codes "F", # Flake8 error codes https://flake8.pycqa.org/en/latest/user/error-codes.html "FA", # Force from __future__ import annotations From deaef58e3749f9eb83951352c30b897867c3900c Mon Sep 17 00:00:00 2001 From: Andreas Reich Date: Mon, 9 Mar 2026 21:42:43 +0100 Subject: [PATCH 073/513] Fix link checker output verbosity MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary - Upgrade lychee from 0.21.0 to 0.23.0 (and lychee-action from v2.7.0 to v2.8.0) - Drop `--verbose` flag from link checker invocations - lychee 0.23 [only shows redirect details in verbose mode](https://github.com/lycheeverse/lychee/commit/91cda35665a31701b777d9f3cbc58eacb7dddaf2), so without `--verbose` the nightly output should be much cleaner — no more huge "Redirects per input" section drowning out actual errors ## Test plan - [ ] Nightly link checker runs with cleaner output 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Source-Ref: fd5fba98c3acabf3ac8d4c4ac33ba927f9f2c0b7 Co-authored-by: Claude Opus 4.6 --- .github/workflows/reusable_checks.yml | 7 +-- pixi.lock | 84 +++++++++++++-------------- pixi.toml | 4 +- 3 files changed, 47 insertions(+), 48 deletions(-) diff --git a/.github/workflows/reusable_checks.yml b/.github/workflows/reusable_checks.yml index 6c182d24ee32..776bc348f362 100644 --- a/.github/workflows/reusable_checks.yml +++ b/.github/workflows/reusable_checks.yml @@ -296,12 +296,11 @@ jobs: - name: Full Link Checker if: ${{ inputs.CHANNEL == 'nightly' }} id: lychee - uses: lycheeverse/lychee-action@v2.7.0 + uses: lycheeverse/lychee-action@v2.8.0 with: fail: true - lycheeVersion: "v0.21.0" + lycheeVersion: "v0.23.0" # When given a directory, lychee checks only markdown, html and text files, everything else we have to glob in manually. - # Pass --verbose, so that all considered links are printed, making it easier to debug. # If updating version or args update the pixi.toml to match. args: | - --verbose --cache --max-cache-age 1d . --root-dir $(pwd) "**/*.md" "**/*.rs" "**/*.toml" "**/*.hpp" "**/*.cpp" "**/CMakeLists.txt" "**/*.py" "**/*.yml" + --cache --max-cache-age 1d . --root-dir $(pwd) "**/*.md" "**/*.rs" "**/*.toml" "**/*.hpp" "**/*.cpp" "**/CMakeLists.txt" "**/*.py" "**/*.yml" diff --git a/pixi.lock b/pixi.lock index 9fae0c69a2e8..1290233fa1f1 100644 --- a/pixi.lock +++ b/pixi.lock @@ -170,7 +170,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-1.11.0-he8b52b9_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.13.8-h04c0eec_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/lychee-0.21.0-h4c46f8d_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/lychee-0.23.0-he64ecbb_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.9.4-hcb278e6_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/markdown-it-py-4.0.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.2-py311h2dc5d0c_1.conda @@ -474,7 +474,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libxkbcommon-1.11.0-h95ca766_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libxml2-2.13.8-he58860d_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libzlib-1.3.1-h86ecc28_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/lychee-0.21.0-h69fca3a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/lychee-0.23.0-hb434046_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/lz4-c-1.9.4-hd600fc2_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/markdown-it-py-4.0.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/markupsafe-3.0.2-py311ha09ea12_1.conda @@ -739,7 +739,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/libzlib-1.3.1-hd23fc13_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/llvm-openmp-21.1.0-hf4e0ed4_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/llvm-tools-16.0.6-hbedff68_3.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/lychee-0.21.0-h82c1fd0_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/lychee-0.23.0-h651e3a3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/markdown-it-py-4.0.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/markupsafe-3.0.2-py311ha3cf9ac_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mdurl-0.1.2-pyhd8ed1ab_1.conda @@ -990,7 +990,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzlib-1.3.1-h8359307_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/llvm-openmp-21.1.0-hbb9b287_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/llvm-tools-16.0.6-hc4b4ae8_4.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/lychee-0.21.0-h4639b0c_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/lychee-0.23.0-h17e24d4_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/markdown-it-py-4.0.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/markupsafe-3.0.2-py311h4921393_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mdurl-0.1.2-pyhd8ed1ab_1.conda @@ -1199,7 +1199,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/libvorbis-1.3.7-h5112557_2.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-2.13.8-h741aa76_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libzlib-1.3.1-h2466b09_2.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/lychee-0.21.0-h243827c_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/lychee-0.23.0-hb3eb754_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/m2w64-gcc-libgfortran-5.3.0-6.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/win-64/m2w64-gcc-libs-5.3.0-7.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/win-64/m2w64-gcc-libs-core-5.3.0-7.tar.bz2 @@ -1491,7 +1491,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-1.11.0-he8b52b9_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.13.8-h04c0eec_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/lychee-0.21.0-h4c46f8d_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/lychee-0.23.0-he64ecbb_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.9.4-hcb278e6_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/markdown-it-py-4.0.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.2-py311h2dc5d0c_1.conda @@ -1780,7 +1780,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libxkbcommon-1.11.0-h95ca766_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libxml2-2.13.8-he58860d_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libzlib-1.3.1-h86ecc28_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/lychee-0.21.0-h69fca3a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/lychee-0.23.0-hb434046_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/lz4-c-1.9.4-hd600fc2_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/markdown-it-py-4.0.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/markupsafe-3.0.2-py311ha09ea12_1.conda @@ -2026,7 +2026,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/libwebp-base-1.6.0-hb807250_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libxml2-2.13.8-he1bc88e_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libzlib-1.3.1-hd23fc13_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/lychee-0.21.0-h82c1fd0_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/lychee-0.23.0-h651e3a3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/markdown-it-py-4.0.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/markupsafe-3.0.2-py311ha3cf9ac_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mdurl-0.1.2-pyhd8ed1ab_1.conda @@ -2258,7 +2258,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzlib-1.3.1-h8359307_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/llvm-openmp-21.1.0-hbb9b287_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/llvm-tools-16.0.6-hc4b4ae8_4.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/lychee-0.21.0-h4639b0c_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/lychee-0.23.0-h17e24d4_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/markdown-it-py-4.0.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/markupsafe-3.0.2-py311h4921393_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mdurl-0.1.2-pyhd8ed1ab_1.conda @@ -2462,7 +2462,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/libvorbis-1.3.7-h5112557_2.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-2.13.8-h741aa76_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libzlib-1.3.1-h2466b09_2.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/lychee-0.21.0-h243827c_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/lychee-0.23.0-hb3eb754_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/m2w64-gcc-libgfortran-5.3.0-6.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/win-64/m2w64-gcc-libs-5.3.0-7.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/win-64/m2w64-gcc-libs-core-5.3.0-7.tar.bz2 @@ -10357,67 +10357,67 @@ packages: purls: [] size: 20903239 timestamp: 1739799054437 -- conda: https://conda.anaconda.org/conda-forge/linux-64/lychee-0.21.0-h4c46f8d_0.conda - sha256: 2e1e66a32691e2d90a968df5e196b249e7652fc357db6dfdbbcb876c0f473b7a - md5: dee8654fb99e14e377dac0c76a066340 +- conda: https://conda.anaconda.org/conda-forge/linux-64/lychee-0.23.0-he64ecbb_0.conda + sha256: 0b1bc4b4a8fde5bf474f5b63c64fe356b3f47034f1d485fedd630b2d17de8fb5 + md5: d89182800d61d9e4922f4f338cd28362 depends: - __glibc >=2.17,<3.0.a0 - libgcc >=14 - - openssl >=3.5.4,<4.0a0 + - openssl >=3.5.5,<4.0a0 constrains: - __glibc >=2.17 license: Apache-2.0 OR MIT purls: [] - size: 6223719 - timestamp: 1761395177369 -- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/lychee-0.21.0-h69fca3a_0.conda - sha256: 07b39abb87701aa84b715ed69b6577d78f9394e5a1c7533e8a3e2f4b62e75032 - md5: 1029e21da8a466c31dbea1e03334fa64 + size: 5582609 + timestamp: 1771270353931 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/lychee-0.23.0-hb434046_0.conda + sha256: 82ac7b7b6d1e9e8c929ac21993a0e036bfd4ee2d3db54913d4635476067901eb + md5: 347b587a5e72646fefa74431216c1c4a depends: - libgcc >=14 - - openssl >=3.5.4,<4.0a0 + - openssl >=3.5.5,<4.0a0 constrains: - __glibc >=2.17 license: Apache-2.0 OR MIT purls: [] - size: 6344571 - timestamp: 1761395223011 -- conda: https://conda.anaconda.org/conda-forge/osx-64/lychee-0.21.0-h82c1fd0_0.conda - sha256: 80fdacc9c3d53b6bc80b1e2cb28a1dce97773703fa8ed9162eea196342d40b8e - md5: 26c1688eacc1bb50a5a48a664c980f0a + size: 5422618 + timestamp: 1771270379661 +- conda: https://conda.anaconda.org/conda-forge/osx-64/lychee-0.23.0-h651e3a3_0.conda + sha256: 8037f7a2f536c5ded9353054377dd4f386a6cd52ee0fdfbd7e47911d63a8dc60 + md5: beea0174c7b90d94f2b5a7813faa7b64 depends: - - __osx >=10.13 - - openssl >=3.5.4,<4.0a0 + - __osx >=11.0 + - openssl >=3.5.5,<4.0a0 constrains: - __osx >=10.13 license: Apache-2.0 OR MIT purls: [] - size: 5795747 - timestamp: 1761395629555 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/lychee-0.21.0-h4639b0c_0.conda - sha256: 7ba2def2ddb24eced5f25216c41a89f873a184815ffdfe4269c01f7599f9bc58 - md5: bed5082ccd0153ccc955229bf7d0680e + size: 5527018 + timestamp: 1771270370972 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/lychee-0.23.0-h17e24d4_0.conda + sha256: 412ccd60e68618a3ee449f6bd4152acdf249e62073e4dfa2e7c8d405fc62b1b0 + md5: 7fa0025f406d5cf3f1100f3a90156547 depends: - __osx >=11.0 - - openssl >=3.5.4,<4.0a0 + - openssl >=3.5.5,<4.0a0 constrains: - __osx >=11.0 license: Apache-2.0 OR MIT purls: [] - size: 5678426 - timestamp: 1761396088460 -- conda: https://conda.anaconda.org/conda-forge/win-64/lychee-0.21.0-h243827c_0.conda - sha256: 69b2d5ee2cf29431330f92da82376b646d050db27e1af44fd474a3b15d967921 - md5: 589c9eb662b0de9e975558b6dea2f2b3 + size: 5174311 + timestamp: 1771270398758 +- conda: https://conda.anaconda.org/conda-forge/win-64/lychee-0.23.0-hb3eb754_0.conda + sha256: 28ee5a30bea795d00df2151fa000f32c16a605e6e5743600f674cd2234864f67 + md5: cbe8bd414d963d87749cfd47659d55c4 depends: - - openssl >=3.5.4,<4.0a0 - - ucrt >=10.0.20348.0 - vc >=14.3,<15 - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - openssl >=3.5.5,<4.0a0 license: Apache-2.0 OR MIT purls: [] - size: 5228725 - timestamp: 1761395806861 + size: 5763830 + timestamp: 1771270396939 - conda: https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.9.4-hcb278e6_0.conda sha256: 1b4c105a887f9b2041219d57036f72c4739ab9e9fe5a1486f094e58c76b31f5f md5: 318b08df404f9c9be5712aaa5a6f0bb0 diff --git a/pixi.toml b/pixi.toml index d53797ab1c40..32216cf2e8c1 100644 --- a/pixi.toml +++ b/pixi.toml @@ -219,7 +219,7 @@ fmt = { depends-on = ["format"] } link-check-pr = { cmd = "python scripts/ci/pr_link_checker.py --base-ref {{ ref }}", args = [ { arg = "ref" }, ] } -link-check = 'lychee --verbose --cache --max-cache-age 1d . --root-dir "$PIXI_PROJECT_ROOT" "**/*.md" "**/*.rs" "**/*.toml" "**/*.hpp" "**/*.cpp" "**/CMakeLists.txt" "**/*.py" "**/*.yml"' +link-check = 'lychee --cache --max-cache-age 1d . --root-dir "$PIXI_PROJECT_ROOT" "**/*.md" "**/*.rs" "**/*.toml" "**/*.hpp" "**/*.cpp" "**/CMakeLists.txt" "**/*.py" "**/*.yml"' # Assorted linting tasks fast-lint = "python scripts/fast_lint.py" @@ -536,7 +536,7 @@ flatbuffers = ">=23" gitignore-parser = ">=0.1.9" gitpython = ">=3.1.40" jinja2 = ">=3.1.3,<3.2" # For `build_screenshot_compare.py` and other utilities that build websites. -lychee = "0.21.0.*" +lychee = "0.23.0.*" mypy = "1.14.1.*" nasm = ">=2.16" # Required by https://github.com/memorysafety/rav1d for native video support pyyaml = ">=6.0" # For `check_doc_redirects.py` From 9b56a952519aefc860dd35f720f8ee68b532d762 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Tue, 10 Mar 2026 10:48:47 +0100 Subject: [PATCH 074/513] Use `tonic::Result` everywhere ## Summary - Replace `Result` with `tonic::Result` across all gRPC service code (17 files, net -67 lines) - Add a lint rule in `lint.py` to enforce using `tonic::Result<>` instead of spelling out `Result<_, tonic::Status>` Source-Ref: 1447156c7e7106c5783e8888c5c427a6d1af3009 --- .../src/dataframe_query_common.rs | 20 ++++++------------- crates/store/re_server/src/rerun_cloud.rs | 4 ++-- scripts/lint.py | 7 +++++++ 3 files changed, 15 insertions(+), 16 deletions(-) diff --git a/crates/store/re_datafusion/src/dataframe_query_common.rs b/crates/store/re_datafusion/src/dataframe_query_common.rs index 2914d7b40288..ae73fdac5166 100644 --- a/crates/store/re_datafusion/src/dataframe_query_common.rs +++ b/crates/store/re_datafusion/src/dataframe_query_common.rs @@ -75,22 +75,18 @@ pub trait DataframeClientAPI: std::fmt::Debug + Clone + Send + Sync + Unpin + 's async fn get_dataset_schema( &mut self, request: tonic::Request, - ) -> Result, tonic::Status>; + ) -> tonic::Result>; async fn query_dataset( &mut self, request: tonic::Request, - ) -> std::result::Result< - tonic::Response>, - tonic::Status, - >; + ) -> tonic::Result>>; async fn fetch_chunks( &mut self, request: tonic::Request, - ) -> std::result::Result< + ) -> tonic::Result< tonic::Response>, - tonic::Status, >; } @@ -99,26 +95,22 @@ impl DataframeClientAPI for ConnectionClient { async fn get_dataset_schema( &mut self, request: tonic::Request, - ) -> Result, tonic::Status> { + ) -> tonic::Result> { self.inner().get_dataset_schema(request).await } async fn query_dataset( &mut self, request: tonic::Request, - ) -> std::result::Result< - tonic::Response>, - tonic::Status, - > { + ) -> tonic::Result>> { self.inner().query_dataset(request).await } async fn fetch_chunks( &mut self, request: tonic::Request, - ) -> std::result::Result< + ) -> tonic::Result< tonic::Response>, - tonic::Status, > { self.inner().fetch_chunks(request).await } diff --git a/crates/store/re_server/src/rerun_cloud.rs b/crates/store/re_server/src/rerun_cloud.rs index a753de74df07..178e953e839f 100644 --- a/crates/store/re_server/src/rerun_cloud.rs +++ b/crates/store/re_server/src/rerun_cloud.rs @@ -1877,7 +1877,7 @@ impl RerunCloudService for RerunCloudHandler { /// /// Returns a deduplicated set because a single RRD can contain duplicate /// `SetStoreInfo` messages for the same store. -fn load_store_ids(rrd_path: &std::path::Path) -> Result, tonic::Status> { +fn load_store_ids(rrd_path: &std::path::Path) -> tonic::Result> { let reader = std::io::BufReader::new( std::fs::File::open(rrd_path) .map_err(|err| tonic::Status::internal(format!("Failed to open RRD file: {err:#}")))?, @@ -1898,7 +1898,7 @@ fn load_store_ids(rrd_path: &std::path::Path) -> Result, tonic } /// Parses a `memory:///store/{store_slot_id}` URL and returns the [`StoreSlotId`]. -fn parse_memory_url(url: &url::Url) -> Result { +fn parse_memory_url(url: &url::Url) -> tonic::Result { let path = url.path(); let slot_id_str = path.strip_prefix("/store/").ok_or_else(|| { tonic::Status::invalid_argument(format!( diff --git a/scripts/lint.py b/scripts/lint.py index 94f90dde6f89..898763ce29cd 100755 --- a/scripts/lint.py +++ b/scripts/lint.py @@ -49,6 +49,7 @@ ellipsis_bare = re.compile(r"^\s*\.\.\.\s*$") anyhow_result = re.compile(r"Result<.*, anyhow::Error>") +tonic_result = re.compile(r"Result<.*?,\s*tonic::Status\s*,?\s*>", re.DOTALL) pyclass_start = re.compile(r"#\[pyclass\(") pymethods_start = re.compile(r"#\[pymethods\]") @@ -1426,6 +1427,12 @@ def lint_file(filepath: str, args: Any) -> int: print(source.error("Missing `#pragma once` in C++ header file")) num_errors += 1 + if filepath.endswith(".rs"): + for match in tonic_result.finditer(source.content): + line_nr = _index_to_line_nr(source.content, match.start()) + print(source.error("Prefer using tonic::Result<>", line_nr=line_nr)) + num_errors += 1 + if filepath.endswith((".rs", ".fbs")): errors, lines_out = lint_vertical_spacing(source.lines) for error in errors: From 9e0776357d00de81f477df80a8f388f0a74c5a93 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Tue, 10 Mar 2026 11:13:35 +0100 Subject: [PATCH 075/513] Remove log spam from the re_redap_client It's so annoying. Do we really want this log level in dataplatform? If so, let's fix it some other way Source-Ref: 93d5a6ca848351812ebe9ff461e4373d41a10586 --- crates/store/re_redap_client/src/lib.rs | 12 ++++++------ crates/utils/re_auth/src/oauth.rs | 8 ++++++-- crates/utils/re_log/src/lib.rs | 1 + 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/crates/store/re_redap_client/src/lib.rs b/crates/store/re_redap_client/src/lib.rs index 87d5026ba1ac..5cd4b50c51af 100644 --- a/crates/store/re_redap_client/src/lib.rs +++ b/crates/store/re_redap_client/src/lib.rs @@ -287,7 +287,7 @@ impl std::error::Error for ApiError { } /// Helper function for executing requests or connection attempts with retries. -#[tracing::instrument(skip(f), level = "debug")] +#[tracing::instrument(skip(f), level = "trace")] pub async fn with_retry(req_name: &str, f: F) -> ApiResult where F: Fn() -> Fut, @@ -322,7 +322,7 @@ where last_retryable_err = Some(err); let backoff = backoff_gen.gen_next(); - tracing::debug!( + tracing::trace!( attempts, max_attempts = MAX_ATTEMPTS, ?backoff, @@ -332,8 +332,8 @@ where backoff.sleep().await; } Err(err) => { - // logging at the debug level to avoid having these spam in the viewer - tracing::debug!( + // logging at the trace level to avoid having these spam in debug builds of the viewer + tracing::trace!( attempts, "{req_name} failed with non-retryable error: {err}" ); @@ -341,7 +341,7 @@ where } Ok(value) => { - tracing::debug!(attempts, "{req_name} succeeded"); + tracing::trace!(attempts, "{req_name} succeeded"); return Ok(value); } } @@ -349,7 +349,7 @@ where attempts += 1; } - tracing::debug!( + tracing::trace!( attempts, max_attempts = MAX_ATTEMPTS, "{req_name} failed after max retries, giving up" diff --git a/crates/utils/re_auth/src/oauth.rs b/crates/utils/re_auth/src/oauth.rs index 75e661788e3f..64a541e24c83 100644 --- a/crates/utils/re_auth/src/oauth.rs +++ b/crates/utils/re_auth/src/oauth.rs @@ -39,10 +39,10 @@ pub struct CredentialsLoadError(#[from] storage::LoadError); /// Load credentials from storage. pub fn load_credentials() -> Result, CredentialsLoadError> { if let Some(credentials) = storage::load()? { - re_log::debug!("found credentials"); + re_log::debug_once!("Found credentials for {}", credentials.user.email); Ok(Some(credentials)) } else { - re_log::debug!("no credentials stored locally"); + re_log::debug_once!("No credentials stored locally"); Ok(None) } } @@ -373,7 +373,11 @@ impl Credentials { #[derive(Debug, Clone, serde::Deserialize, serde::Serialize)] pub struct User { + /// Opaque user identifier from the auth provider (e.g. `"user_01JZ…"`). + /// + /// This is NOT a human-readable name; use [`Self::email`] for display purposes. pub id: String, + pub email: String, } diff --git a/crates/utils/re_log/src/lib.rs b/crates/utils/re_log/src/lib.rs index aaa6a9b55a4d..5eb07b06892a 100644 --- a/crates/utils/re_log/src/lib.rs +++ b/crates/utils/re_log/src/lib.rs @@ -135,6 +135,7 @@ const CRATES_AT_INFO_LEVEL: &[&str] = &[ "datafusion", "h2", "hyper", + "opentelemetry", // Spams about NoopMeterProvider "prost_build", "reqwest", // Spams "starting new connection: …" "sqlparser", From 7314d4d82f679928bea5a4b1c9133a12fc18e6a8 Mon Sep 17 00:00:00 2001 From: Ilya Zlobintsev Date: Tue, 10 Mar 2026 09:05:03 +0100 Subject: [PATCH 076/513] Update wasm-bindgen to 0.2.108 ### Related Prerequisite for RR-3988 (updating the azure sdk brings in a new wasm-bindgen version), because indirect dependencies ### What This updates wasm-bindgen to a version needed for azure, and the sketchy wasm patcher script had to be changed for it to work. ### Testing The notebook works with multiple web viewers. --------- Source-Ref: bfe3f91721cd1472a7de8e7d89c30aab345609a8 Co-authored-by: jprochazk <1665677+jprochazk@users.noreply.github.com> --- Cargo.lock | 142 +++++------------- Cargo.toml | 4 +- crates/viewer/re_redap_browser/Cargo.toml | 2 +- crates/viewer/re_viewer/Cargo.toml | 2 +- .../notebook_viewer/notebook_viewer.ipynb | 97 ++++++++++-- rerun_js/web-viewer/build-wasm.mjs | 50 +++--- rerun_notebook/package-lock.json | 5 +- 7 files changed, 152 insertions(+), 150 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6cffd9a742ca..8337eb151d28 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4501,7 +4501,6 @@ checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" dependencies = [ "ahash", "allocator-api2", - "serde", ] [[package]] @@ -4513,6 +4512,7 @@ dependencies = [ "allocator-api2", "equivalent", "foldhash 0.1.5", + "serde", ] [[package]] @@ -5307,9 +5307,9 @@ checksum = "00810f1d8b74be64b13dbf3db89ac67740615d6c891f0e7b6179326533011a07" [[package]] name = "js-sys" -version = "0.3.77" +version = "0.3.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" dependencies = [ "once_cell", "wasm-bindgen", @@ -13357,9 +13357,9 @@ dependencies = [ [[package]] name = "walrus" -version = "0.23.3" +version = "0.24.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6481311b98508f4bc2d0abbfa5d42172e7a54b4b24d8f15e28b0dc650be0c59f" +checksum = "820a1ca30ceb782a7f4524722a7ee27bd6c4bcde97d48802ba270fd69ff1a954" dependencies = [ "anyhow", "gimli 0.26.2", @@ -13368,15 +13368,15 @@ dependencies = [ "log", "rayon", "walrus-macro", - "wasm-encoder 0.214.0", - "wasmparser 0.214.0", + "wasm-encoder 0.240.0", + "wasmparser 0.240.0", ] [[package]] name = "walrus-macro" -version = "0.22.0" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ad39ff894c43c9649fa724cdde9a6fc50b855d517ef071a93e5df82fe51d3" +checksum = "0ef06db404cbaed87cb25fd2ca3a62502af485f43383c9641ffcf1479d02fffd" dependencies = [ "heck", "proc-macro2", @@ -13419,70 +13419,43 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.100" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" dependencies = [ "cfg-if", "once_cell", "rustversion", "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" -dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn 2.0.117", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-cli-support" -version = "0.2.100" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21e1a4a49abe9cd6f762fc65fac2ef5732afeeb66be369d2f71a85b165a533cf" +checksum = "2be60cf36510aa4702ce189517229c3091f4a322a5ec2665a7737ea5956c2b3a" dependencies = [ "anyhow", "base64 0.22.1", + "leb128", "log", "rustc-demangle", "serde", "serde_json", - "tempfile", "walrus", - "wasm-bindgen-externref-xform", - "wasm-bindgen-multi-value-xform", "wasm-bindgen-shared", - "wasm-bindgen-threads-xform", - "wasm-bindgen-wasm-conventions", - "wasm-bindgen-wasm-interpreter", -] - -[[package]] -name = "wasm-bindgen-externref-xform" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "940542c5cdbe96c35f98b5da5c65fb9d18df55a0cb1d81fc5ca4acc4fda4d61c" -dependencies = [ - "anyhow", - "walrus", - "wasm-bindgen-wasm-conventions", + "wasmparser 0.240.0", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.50" +version = "0.4.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" +checksum = "70a6e77fd0ae8029c9ea0063f87c46fde723e7d887703d74ad2616d792e51e6f" dependencies = [ "cfg-if", + "futures-util", "js-sys", "once_cell", "wasm-bindgen", @@ -13491,9 +13464,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.100" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -13501,80 +13474,34 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.100" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" dependencies = [ + "bumpalo", "proc-macro2", "quote", "syn 2.0.117", - "wasm-bindgen-backend", "wasm-bindgen-shared", ] -[[package]] -name = "wasm-bindgen-multi-value-xform" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64b5ad2e97adde0c3e4369c38e0dbaee329ad8f6cc2ee8e01d1d0b13bd8b14cf" -dependencies = [ - "anyhow", - "walrus", - "wasm-bindgen-wasm-conventions", -] - [[package]] name = "wasm-bindgen-shared" -version = "0.2.100" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" dependencies = [ "unicode-ident", ] -[[package]] -name = "wasm-bindgen-threads-xform" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cbdf2d55a50f7edc9dd9aecae7a3a40e9736fda851bd8816f98a86167c8c277" -dependencies = [ - "anyhow", - "walrus", - "wasm-bindgen-wasm-conventions", -] - -[[package]] -name = "wasm-bindgen-wasm-conventions" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1c24fcaa34d2d84407122cfb1d3f37c3586756cf462be18e049b49245a16c08" -dependencies = [ - "anyhow", - "leb128", - "log", - "walrus", - "wasmparser 0.214.0", -] - -[[package]] -name = "wasm-bindgen-wasm-interpreter" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33f24921401faadd6944206f9d6837d07bbb5ff766ed51ad34528089f66550e0" -dependencies = [ - "anyhow", - "log", - "walrus", - "wasm-bindgen-wasm-conventions", -] - [[package]] name = "wasm-encoder" -version = "0.214.0" +version = "0.240.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff694f02a8d7a50b6922b197ae03883fbf18cdb2ae9fbee7b6148456f5f44041" +checksum = "06d642d8c5ecc083aafe9ceb32809276a304547a3a6eeecceb5d8152598bc71f" dependencies = [ - "leb128", + "leb128fmt", + "wasmparser 0.240.0", ] [[package]] @@ -13614,13 +13541,12 @@ dependencies = [ [[package]] name = "wasmparser" -version = "0.214.0" +version = "0.240.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5309c1090e3e84dad0d382f42064e9933fdaedb87e468cc239f0eabea73ddcb6" +checksum = "b722dcf61e0ea47440b53ff83ccb5df8efec57a69d150e4f24882e4eba7e24a4" dependencies = [ - "ahash", "bitflags 2.11.0", - "hashbrown 0.14.5", + "hashbrown 0.15.5", "indexmap 2.13.0", "semver", "serde", @@ -13749,9 +13675,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.77" +version = "0.3.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +checksum = "312e32e551d92129218ea9a2452120f4aabc03529ef03e4d0d82fb2780608598" dependencies = [ "js-sys", "wasm-bindgen", diff --git a/Cargo.toml b/Cargo.toml index 3b117c523e2a..79f4b4fcc59c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -423,8 +423,8 @@ walkdir = "2.5" # Do not make this an `=` dependency, because that may break Rust users’ builds when a newer # version is released, even if they are not building the web viewer. # For details see https://github.com/rerun-io/rerun/issues/8766 -wasm-bindgen = "=0.2.100" # ⚠️ read above notice before touching this! -wasm-bindgen-cli-support = "=0.2.100" # ⚠️ read above notice before touching this! +wasm-bindgen = "=0.2.108" # ⚠️ read above notice before touching this! +wasm-bindgen-cli-support = "=0.2.108" # ⚠️ read above notice before touching this! wasm-bindgen-futures = "0.4.50" web-sys = "0.3.77" wayland-sys = "0.31.9" diff --git a/crates/viewer/re_redap_browser/Cargo.toml b/crates/viewer/re_redap_browser/Cargo.toml index f8d9effae42e..1e3d074d66fe 100644 --- a/crates/viewer/re_redap_browser/Cargo.toml +++ b/crates/viewer/re_redap_browser/Cargo.toml @@ -56,6 +56,6 @@ js-sys.workspace = true # Try running a cell which starts a viewer, and then running it again. # There should be no errors in the browser console. # For details see https://github.com/rerun-io/rerun/issues/8766 -wasm-bindgen = "=0.2.100" # ⚠️ read above notice before touching this! +wasm-bindgen = "=0.2.108" # ⚠️ read above notice before touching this! web-sys = { workspace = true, features = ["Storage", "Window"] } uuid.workspace = true diff --git a/crates/viewer/re_viewer/Cargo.toml b/crates/viewer/re_viewer/Cargo.toml index 70678734d8b4..b859c5716eaa 100644 --- a/crates/viewer/re_viewer/Cargo.toml +++ b/crates/viewer/re_viewer/Cargo.toml @@ -169,7 +169,7 @@ wasm-bindgen-futures.workspace = true # Try running a cell which starts a viewer, and then running it again. # There should be no errors in the browser console. # For details see https://github.com/rerun-io/rerun/issues/8766 -wasm-bindgen = "=0.2.100" # ⚠️ read above notice before touching this! +wasm-bindgen = "=0.2.108" # ⚠️ read above notice before touching this! web-sys = { workspace = true, features = [ "Crypto", "History", diff --git a/examples/notebook/notebook_viewer/notebook_viewer.ipynb b/examples/notebook/notebook_viewer/notebook_viewer.ipynb index cccdab3d581c..6cf3db1fa56f 100644 --- a/examples/notebook/notebook_viewer/notebook_viewer.ipynb +++ b/examples/notebook/notebook_viewer/notebook_viewer.ipynb @@ -2,27 +2,106 @@ "cells": [ { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Starting asset server due to RERUN_NOTEBOOK_ASSET=serve-local\n", + "Loading assets into memory…\n", + "Serving rerun notebook assets at http://127.0.0.1:49643\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "70660ab254464b1db8cb3cb9d57b637d", + "version_major": 2, + "version_minor": 1 + }, + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from __future__ import annotations\n", + "\n", + "from rerun.notebook import Viewer\n", + "\n", + "viewer = Viewer(\n", + " width=\"auto\",\n", + " height=\"auto\",\n", + " url=\"https://app.rerun.io/version/nightly/examples/raw_mesh.rrd\",\n", + ")\n", + "viewer" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "5185be34c50643cf8bab1b694f2f7ab0", + "version_major": 2, + "version_minor": 1 + }, + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "from __future__ import annotations\n", "\n", "from rerun.notebook import Viewer\n", "\n", - "Viewer(\n", + "viewer = Viewer(\n", " width=\"auto\",\n", " height=\"auto\",\n", " url=\"https://app.rerun.io/version/nightly/examples/raw_mesh.rrd\",\n", - ").update_panels(\n", - " blueprint=\"hidden\",\n", - " selection=\"hidden\",\n", - " time=\"collapsed\",\n", - ")" + ")\n", + "viewer" ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] } ], - "metadata": {}, + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.13" + } + }, "nbformat": 4, "nbformat_minor": 4 } diff --git a/rerun_js/web-viewer/build-wasm.mjs b/rerun_js/web-viewer/build-wasm.mjs index fbdd02368a9c..2304323c685f 100644 --- a/rerun_js/web-viewer/build-wasm.mjs +++ b/rerun_js/web-viewer/build-wasm.mjs @@ -52,11 +52,10 @@ function re_viewer_js() { // this is HIGHLY sensitive to the exact output of `wasm-bindgen`, so if // the output changes, this will need to be updated. - const start = `let wasm_bindgen; -(function() {`; - const end = `wasm_bindgen = Object.assign(__wbg_init, { initSync }, __exports); + const start = `let wasm_bindgen = (function(exports) {`; + const end = `return Object.assign(__wbg_init, { initSync }, exports); +})({ __proto__: null });`; -})();`; if (code.indexOf(start) === -1) { throw new Error("failed to run js build script: failed to patch re_viewer.js, could not find replace start marker"); } @@ -67,19 +66,24 @@ function re_viewer_js() { code = ` export default function() { +const exports = { __proto__: null }; ${code} function deinit() { __wbg_init.__wbindgen_wasm_module = null; + wasmModule = null; wasm = null; - cachedUint8ArrayMemory0 = null; + cachedDataViewMemory0 = null; cachedFloat32ArrayMemory0 = null; + cachedInt16ArrayMemory0 = null; cachedInt32ArrayMemory0 = null; + cachedInt8ArrayMemory0 = null; + cachedUint16ArrayMemory0 = null; cachedUint32ArrayMemory0 = null; - cachedDataViewMemory0 = null; + cachedUint8ArrayMemory0 = null; } -return Object.assign(__wbg_init, { initSync, deinit }, __exports); +return Object.assign(__wbg_init, { initSync, deinit }, exports); } `; @@ -87,32 +91,24 @@ return Object.assign(__wbg_init, { initSync, deinit }, __exports); // Otherwise we end up with an exceptioon during closure destruction which prevents the references from all being // cleaned up properly. // TODO(jprochazk): Can we force these to run before we null `wasm` instead? - const closure_dtors_start_marker = "const CLOSURE_DTORS"; - const closure_dtors_end_marker = "});"; - - const closure_dtors_start = code.indexOf(closure_dtors_start_marker); - if (closure_dtors_start === -1) { - throw new Error("failed to run js build script: failed to patch re_viewer.js, could not find CLOSURE_DTORS start"); - } - const closure_dtors_end = code.indexOf(closure_dtors_end_marker, closure_dtors_start); - if (closure_dtors_end === -1) { - throw new Error("failed to run js build script: failed to patch re_viewer.js, could not find CLOSURE_DTORS end"); - } - - let m = code.substring(closure_dtors_start, closure_dtors_end).match(/__wbindgen_export_\d+/); - if (!m) { - throw new Error("failed to run js build script: failed to patch re_viewer.js, could not find __wbindgen_export within CLOSURE_DTORS"); - } - - let wbindgen_export = m[0]; + // Patch CLOSURE_DTORS to guard against null `wasm` during deinit. + // The FinalizationRegistry callback may fire after we've nulled `wasm`, + // so we need to check that wasm is still alive before calling the destructor. + const closure_dtors_original = `const CLOSURE_DTORS = (typeof FinalizationRegistry === 'undefined') + ? { register: () => {}, unregister: () => {} } + : new FinalizationRegistry(state => state.dtor(state.a, state.b));`; const closure_dtors_patch = `const CLOSURE_DTORS = (typeof FinalizationRegistry === 'undefined') ? { register: () => {}, unregister: () => {} } : new FinalizationRegistry(state => { - wasm?.${wbindgen_export}.get(state.dtor)(state.a, state.b) + if (wasm) state.dtor(state.a, state.b); });`; - code = code.substring(0, closure_dtors_start) + closure_dtors_patch + code.slice(closure_dtors_end + closure_dtors_end_marker.length); + if (code.indexOf(closure_dtors_original) === -1) { + throw new Error("failed to run js build script: failed to patch re_viewer.js, could not find CLOSURE_DTORS block"); + } + + code = code.replace(closure_dtors_original, closure_dtors_patch); fs.writeFileSync(path.join(__dirname, "re_viewer.js"), code); } diff --git a/rerun_notebook/package-lock.json b/rerun_notebook/package-lock.json index e68ea3771a0c..18f23cc99b16 100644 --- a/rerun_notebook/package-lock.json +++ b/rerun_notebook/package-lock.json @@ -902,7 +902,8 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true + "dev": true, + "peer": true }, "node_modules/json-schema-compare": { "version": "0.2.2", @@ -992,6 +993,7 @@ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", "dev": true, + "peer": true, "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, @@ -1184,7 +1186,6 @@ "resolved": "https://registry.npmjs.org/yjs/-/yjs-13.6.16.tgz", "integrity": "sha512-uEq+n/dFIecBElEdeQea8nDnltScBfuhCSyAxDw4CosveP9Ag0eW6iZi2mdpW7EgxSFT7VXK2MJl3tKaLTmhAQ==", "dev": true, - "peer": true, "dependencies": { "lib0": "^0.2.86" }, From 74a30f2f08cd9b21205886f3325a45da04d9e103 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Gyebn=C3=A1r?= Date: Tue, 10 Mar 2026 09:56:23 +0100 Subject: [PATCH 077/513] Fix overrides in blueprints created by a native viewer no showing up on the web viewer and vice versa It could sometimes happen that overrides from blueprints created by a native viewer won't show up on the web viewer and vice versa. This fixes this! --------- Source-Ref: 6cd8a0a15135e9c3b91c70a496e16336ce797212 Co-authored-by: Andreas Reich Co-authored-by: Andreas Reich Co-authored-by: Wumpf <1220815+Wumpf@users.noreply.github.com> --- Cargo.lock | 1 + Cargo.toml | 2 +- crates/store/re_log_types/Cargo.toml | 1 + .../re_log_types/src/path/entity_path.rs | 11 +++ .../visualizer_instruction_id_ext.rs | 9 +- crates/viewer/re_test_context/src/lib.rs | 3 +- .../clear_series_points_and_line.png | 4 +- ..._points_and_line_two_series_per_entity.png | 4 +- .../snapshots/explicit_component_mapping.png | 4 +- .../explicit_component_mapping_nested.png | 4 +- ...per_series_visibility_show_second_only.png | 4 +- .../per_series_visibility_splat_true.png | 4 +- .../special_characters_in_entity_path.png | 4 +- .../snapshots/transform3d_time_series.png | 4 +- .../viewer/re_viewer/tests/blueprint_test.rs | 89 ++++++++++++++++++- .../src/view_contents.rs | 46 ++++++++-- .../drop_multiple_streams_to_view_1.png | 4 +- .../drop_multiple_streams_to_view_2.png | 4 +- .../drop_multiple_streams_to_view_3.png | 4 +- .../drop_multiple_streams_to_view_4.png | 4 +- .../drop_multiple_streams_to_view_5.png | 4 +- .../drop_multiple_streams_to_view_6.png | 4 +- .../tests/snapshots/drop_stream_to_view_2.png | 4 +- .../tests/snapshots/drop_stream_to_view_3.png | 4 +- .../tests/snapshots/drop_stream_to_view_4.png | 4 +- .../tests/snapshots/drop_stream_to_view_5.png | 4 +- .../tests/snapshots/drop_stream_to_view_6.png | 4 +- .../snapshots/series_count_exceeds_max.png | 4 +- ...visualizers_multi_scalar_view_selected.png | 4 +- 29 files changed, 193 insertions(+), 53 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8337eb151d28..b501ebf112b0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9268,6 +9268,7 @@ dependencies = [ "typenum", "uuid", "web-time", + "xxhash-rust", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 79f4b4fcc59c..3817744269ee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -451,7 +451,7 @@ wgpu = { version = "27.0.1", default-features = false, features = [ "fragile-send-sync-non-atomic-wasm", ] } xshell = "0.2.7" -xxhash-rust = { version = "0.8", features = ["xxh32"] } +xxhash-rust = { version = "0.8", features = ["xxh32", "xxh64"] } zip = { version = "8.2", default-features = false, features = ["deflate"] } # --------------------------------------------------------------------------------- diff --git a/crates/store/re_log_types/Cargo.toml b/crates/store/re_log_types/Cargo.toml index ec431edc66e8..5ead290476ef 100644 --- a/crates/store/re_log_types/Cargo.toml +++ b/crates/store/re_log_types/Cargo.toml @@ -74,6 +74,7 @@ thiserror.workspace = true typenum.workspace = true uuid = { workspace = true, features = ["serde", "v4", "js"] } web-time.workspace = true +xxhash-rust.workspace = true # Optional dependencies: diff --git a/crates/store/re_log_types/src/path/entity_path.rs b/crates/store/re_log_types/src/path/entity_path.rs index 6d71cc91ac70..a8b90a77f02e 100644 --- a/crates/store/re_log_types/src/path/entity_path.rs +++ b/crates/store/re_log_types/src/path/entity_path.rs @@ -1,3 +1,4 @@ +use std::hash::{Hash as _, Hasher as _}; use std::sync::Arc; use ahash::{HashMap, HashSet}; @@ -269,6 +270,16 @@ impl EntityPath { self.hash.hash64() } + /// Calculates a deterministic hash of the entity path. + /// + /// This is useful for generating deterministic IDs for visualizer instructions. By default, + /// self.hash is generated using ahash, which works differently on web and native. + pub fn calculate_deterministic_hash(&self) -> u64 { + let mut hasher = xxhash_rust::xxh64::Xxh64::new(0); + self.parts.hash(&mut hasher); + hasher.finish() + } + /// Return [`None`] if root. #[must_use] pub fn parent(&self) -> Option { diff --git a/crates/store/re_sdk_types/src/blueprint/components/visualizer_instruction_id_ext.rs b/crates/store/re_sdk_types/src/blueprint/components/visualizer_instruction_id_ext.rs index 9f4b4c8afe35..606e955a217d 100644 --- a/crates/store/re_sdk_types/src/blueprint/components/visualizer_instruction_id_ext.rs +++ b/crates/store/re_sdk_types/src/blueprint/components/visualizer_instruction_id_ext.rs @@ -1,3 +1,5 @@ +use re_log_types::EntityPath; + use super::VisualizerInstructionId; use crate::datatypes::Uuid; @@ -19,8 +21,11 @@ impl VisualizerInstructionId { /// This is used internally for generating stable IDs for heuristically /// created visualizers. #[inline] - pub fn new_deterministic(hash: u64, index: usize) -> Self { - Self(Uuid::from(uuid::Uuid::from_u64_pair(hash, index as u64))) + pub fn new_deterministic(entity_path: &EntityPath, index: usize) -> Self { + Self(Uuid::from(uuid::Uuid::from_u64_pair( + entity_path.calculate_deterministic_hash(), + index as u64, + ))) } } diff --git a/crates/viewer/re_test_context/src/lib.rs b/crates/viewer/re_test_context/src/lib.rs index 65d00a2504bd..42991c7fe8ae 100644 --- a/crates/viewer/re_test_context/src/lib.rs +++ b/crates/viewer/re_test_context/src/lib.rs @@ -141,8 +141,7 @@ pub trait VisualizerBlueprintContext: BlueprintContext { let mut visualizer = visualizer.into(); // Generate a deterministic ID based on entity path hash and visualizer index - visualizer.id = - VisualizerInstructionId::new_deterministic(entity_path.hash().hash64(), i); + visualizer.id = VisualizerInstructionId::new_deterministic(entity_path, i); ids.push(visualizer.id); let visualizer_path = base_override_path diff --git a/crates/viewer/re_view_time_series/tests/snapshots/clear_series_points_and_line.png b/crates/viewer/re_view_time_series/tests/snapshots/clear_series_points_and_line.png index b1a567b9b05f..e8ff9bf4fa19 100644 --- a/crates/viewer/re_view_time_series/tests/snapshots/clear_series_points_and_line.png +++ b/crates/viewer/re_view_time_series/tests/snapshots/clear_series_points_and_line.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:630a9c0b35121883789bdf303b61d40ed53d992bf5a2bc5e9ed0e3f7dd3b2bb0 -size 23708 +oid sha256:3af8a3c34d843bbb3c1d64afa3f045c09ccfc24064657aeac84abedda2dc2e4b +size 23791 diff --git a/crates/viewer/re_view_time_series/tests/snapshots/clear_series_points_and_line_two_series_per_entity.png b/crates/viewer/re_view_time_series/tests/snapshots/clear_series_points_and_line_two_series_per_entity.png index 6e9c1b4fa749..ce9e8908c21f 100644 --- a/crates/viewer/re_view_time_series/tests/snapshots/clear_series_points_and_line_two_series_per_entity.png +++ b/crates/viewer/re_view_time_series/tests/snapshots/clear_series_points_and_line_two_series_per_entity.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bea21766b8760569cf3cc735546e55aa3b4e82b62c15f31e594b2f22d82c1fc0 -size 33522 +oid sha256:a420dc7866228340dc912f055cce1e4ce61e44fe3431bf93804a8623f7b3f388 +size 33614 diff --git a/crates/viewer/re_view_time_series/tests/snapshots/explicit_component_mapping.png b/crates/viewer/re_view_time_series/tests/snapshots/explicit_component_mapping.png index b1ed12d4fd2d..a363bb0b097e 100644 --- a/crates/viewer/re_view_time_series/tests/snapshots/explicit_component_mapping.png +++ b/crates/viewer/re_view_time_series/tests/snapshots/explicit_component_mapping.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ddf54a28759a043fb55194aaa652961c2da05ba14648d1b5917f23886dc861e9 -size 26546 +oid sha256:854f3b584d0cffb79404a2c86afe2cdf99b2a19164653295cb70dcf63f6ff6ed +size 26607 diff --git a/crates/viewer/re_view_time_series/tests/snapshots/explicit_component_mapping_nested.png b/crates/viewer/re_view_time_series/tests/snapshots/explicit_component_mapping_nested.png index d7e8cf9d2335..b65913e19a58 100644 --- a/crates/viewer/re_view_time_series/tests/snapshots/explicit_component_mapping_nested.png +++ b/crates/viewer/re_view_time_series/tests/snapshots/explicit_component_mapping_nested.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5212c7b576e291b1dd4625d91234d77abb0f1af314e83fc5ebe0eeaa10c041e0 -size 40684 +oid sha256:069cf6220090924743e791bcdc3df012f3a79b5adacd08c0e07c4262b54be215 +size 40750 diff --git a/crates/viewer/re_view_time_series/tests/snapshots/per_series_visibility_show_second_only.png b/crates/viewer/re_view_time_series/tests/snapshots/per_series_visibility_show_second_only.png index 3c41c8ada287..f4cd16eb6a66 100644 --- a/crates/viewer/re_view_time_series/tests/snapshots/per_series_visibility_show_second_only.png +++ b/crates/viewer/re_view_time_series/tests/snapshots/per_series_visibility_show_second_only.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1f9bc5429cccb62aacccb1f303ff5bdebbe46476f2fe912c9e7c17522aab1f25 -size 23712 +oid sha256:a70c3130e0bb078aaff1022554f61464503ac3f422d4a34f1480d95641b1fa82 +size 23756 diff --git a/crates/viewer/re_view_time_series/tests/snapshots/per_series_visibility_splat_true.png b/crates/viewer/re_view_time_series/tests/snapshots/per_series_visibility_splat_true.png index c5bb0cfbab46..af97b617dfdb 100644 --- a/crates/viewer/re_view_time_series/tests/snapshots/per_series_visibility_splat_true.png +++ b/crates/viewer/re_view_time_series/tests/snapshots/per_series_visibility_splat_true.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e8245fc26af521fc53415b367459d411ae9012f5b9ac1185ecd94f60e5e68b69 -size 30143 +oid sha256:05dc6cfab00ea49dccec560d24caf49ad0d2a7117cca9c820cd43a0f2160d764 +size 30220 diff --git a/crates/viewer/re_view_time_series/tests/snapshots/special_characters_in_entity_path.png b/crates/viewer/re_view_time_series/tests/snapshots/special_characters_in_entity_path.png index 0d232ecd63e3..0e0d105a6b2b 100644 --- a/crates/viewer/re_view_time_series/tests/snapshots/special_characters_in_entity_path.png +++ b/crates/viewer/re_view_time_series/tests/snapshots/special_characters_in_entity_path.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:432ec9b2dd042ad74b4d316ae8737d518da674652c1a744745605ee21af9f14b -size 22753 +oid sha256:e798a69e3ba7877fd76babbcbf9958365598ebf8402c23c21b23efcaab6396ec +size 22471 diff --git a/crates/viewer/re_view_time_series/tests/snapshots/transform3d_time_series.png b/crates/viewer/re_view_time_series/tests/snapshots/transform3d_time_series.png index a31571320401..8fb50bef0ff7 100644 --- a/crates/viewer/re_view_time_series/tests/snapshots/transform3d_time_series.png +++ b/crates/viewer/re_view_time_series/tests/snapshots/transform3d_time_series.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6eed4c1d082ca7b87ec71bec2ec1912f69fa3f08628e5b5e447c3164fa84f0d4 -size 51490 +oid sha256:04a9b9b86e3c95d3ad27d61df88d5c360c3cd65108d54c9999a5187d7aee4ebc +size 50820 diff --git a/crates/viewer/re_viewer/tests/blueprint_test.rs b/crates/viewer/re_viewer/tests/blueprint_test.rs index 1ca233ff0724..98f8147bdfa4 100644 --- a/crates/viewer/re_viewer/tests/blueprint_test.rs +++ b/crates/viewer/re_viewer/tests/blueprint_test.rs @@ -1,11 +1,12 @@ // Tests for saving/loading blueprints to/from a file. use std::path::Path; +use re_chunk::EntityPath; use re_chunk::{RowId, TimePoint}; use re_test_context::TestContext; use re_test_context::VisualizerBlueprintContext as _; use re_test_viewport::TestContextExt as _; -use re_viewer_context::ViewClass as _; +use re_viewer_context::{BlueprintContext as _, ViewClass as _}; use re_viewport::ViewportUi; use re_viewport_blueprint::ViewBlueprint; @@ -160,3 +161,89 @@ fn test_blueprint_load_into_new_context() { &mut snapshot_results, ); } + +/// When the entity path hash in a `VisualizerInstructionId` doesn't match the current hash +/// (e.g. because the hash algorithm changed), overrides should still be loaded. +/// The fallback path in `build_data_result_tree` recovers instruction IDs by scanning +/// blueprint children and extracting the index from the UUID's low bits. +#[test] +fn test_blueprint_override_legacy_hash() { + use re_sdk_types::AsComponents; + use re_sdk_types::blueprint::archetypes as bp_archetypes; + use re_sdk_types::blueprint::components::VisualizerInstructionId; + + let mut test_context = TestContext::new(); + test_context.register_view_class::(); + + let entity_path = re_chunk::EntityPath::from("vector"); + let vector = (0..10).map(|i| i as f32).collect::>(); + test_context.log_entity("vector", |builder| { + builder.with_archetype( + RowId::new(), + TimePoint::STATIC, + &re_sdk_types::archetypes::BarChart::new(vector), + ) + }); + + let view_id = test_context.setup_viewport_blueprint(|ctx, blueprint| { + let view = + ViewBlueprint::new_with_root_wildcard(re_view_bar_chart::BarChartView::identifier()); + let view_id = view.id; + + let base_override_path = + bp_archetypes::ViewContents::blueprint_base_visualizer_path_for_entity( + view_id.uuid(), + &entity_path, + ); + + // Use a nonsensical hash but a valid index (0). + // This simulates a legacy blueprint created with a different hash algorithm. + let legacy_id = + VisualizerInstructionId::new_deterministic(&EntityPath::from("wrong_path"), 0); + + let visualizer_path = + base_override_path + .clone() + .join(&re_chunk::EntityPath::from_single_string( + legacy_id.to_string(), + )); + + // Store the VisualizerInstruction + color override at the legacy UUID path. + let instruction = bp_archetypes::VisualizerInstruction::new("BarChart"); + let color_override = re_sdk_types::archetypes::BarChart::default().with_color([255, 0, 0]); + ctx.save_blueprint_archetypes( + visualizer_path, + [ + &instruction as &dyn AsComponents, + &color_override as &dyn AsComponents, + ], + ); + + // Intentionally do NOT store ActiveVisualizers at the base path. + // This forces the fallback recovery path that scans children by UUID index. + + blueprint.add_view_at_root(view) + }); + + // The query results are populated by setup_viewport_blueprint. + let query_result = test_context + .query_results + .get(&view_id) + .expect("query result for view should exist"); + + let data_result = query_result + .tree + .lookup_result_by_path(entity_path.hash()) + .expect("data result for 'vector' entity should exist"); + + // The override should have been picked up via the legacy UUID recovery path. + let color_component = re_sdk_types::archetypes::BarChart::descriptor_color().component; + assert!( + data_result + .visualizer_instructions + .iter() + .any(|vi| vi.component_overrides.contains(&color_component)), + "Color override should be present despite the nonsensical entity path hash in the VisualizerInstructionId. \ + The fallback recovery should match by index (low bits of UUID)." + ); +} diff --git a/crates/viewer/re_viewport_blueprint/src/view_contents.rs b/crates/viewer/re_viewport_blueprint/src/view_contents.rs index f8e767f326a1..08d45cf8a536 100644 --- a/crates/viewer/re_viewport_blueprint/src/view_contents.rs +++ b/crates/viewer/re_viewport_blueprint/src/view_contents.rs @@ -535,7 +535,41 @@ impl DataQueryPropertyResolver<'_> { }) .collect(); } else { - // Otherwise ask the `ViewClass` to choose. + // We don't have explicit visualizer instruction ids stored on the override base path. + // Recover existing ids from override children - this is important because at some + // point we changed the algorithm to generate IDs for recommendations! + // We fall back to new deterministic ids for any that don't have one. + // + // We use UUIDs created via + // `VisualizerInstructionId::new_deterministic(hash, index)` which stores + // the index in the low 64 bits of the UUID. We extract that index to + // build a map from index → existing instruction id. + let mut known_ids: IntMap = Default::default(); + if let Some(subtree) = blueprint + .tree() + .subtree(&node.data_result.override_base_path) + { + for child_tree in subtree.children.values() { + let Some(last) = child_tree.path.last() else { + continue; + }; + + let Ok(uuid) = last + .unescaped_str() + .parse::() + else { + continue; + }; + + let (_high, low) = uuid.as_u64_pair(); + let id = VisualizerInstructionId::from( + re_sdk_types::datatypes::Uuid::from(uuid), + ); + known_ids.insert(low, id); + } + } + + // Ask the `ViewClass` to choose visualizers heuristically. let recommended_visualizers = self.view_class.recommended_visualizers_for_entity( &node.data_result.entity_path, self.visualizable_entities_per_visualizer, @@ -551,10 +585,12 @@ impl DataQueryPropertyResolver<'_> { }) .enumerate() .map(|(index, (visualizer_type, component_mappings))| { - let id = VisualizerInstructionId::new_deterministic( - node.data_result.entity_path.hash64(), - index, - ); + let id = known_ids.remove(&(index as u64)).unwrap_or_else(|| { + VisualizerInstructionId::new_deterministic( + &node.data_result.entity_path, + index, + ) + }); component_mappings.into_visualizer_instruction( id, visualizer_type, diff --git a/tests/rust/re_integration_test/tests/snapshots/drop_multiple_streams_to_view_1.png b/tests/rust/re_integration_test/tests/snapshots/drop_multiple_streams_to_view_1.png index 07998844cb5d..b02613d5df4c 100644 --- a/tests/rust/re_integration_test/tests/snapshots/drop_multiple_streams_to_view_1.png +++ b/tests/rust/re_integration_test/tests/snapshots/drop_multiple_streams_to_view_1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4b323323c2f1ef60e1a7eeb6c854fc3ceee637336d4c6f9fb43dcfad3e0ba90f -size 128439 +oid sha256:ed4794b26a1917189c09d78fce463d2ad4359c1283003dbffed2648e67eb0f67 +size 128704 diff --git a/tests/rust/re_integration_test/tests/snapshots/drop_multiple_streams_to_view_2.png b/tests/rust/re_integration_test/tests/snapshots/drop_multiple_streams_to_view_2.png index cf98b9bf7135..bc6c576e6e22 100644 --- a/tests/rust/re_integration_test/tests/snapshots/drop_multiple_streams_to_view_2.png +++ b/tests/rust/re_integration_test/tests/snapshots/drop_multiple_streams_to_view_2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:80cef08ef4ad6e539114e422d943b482073712c90b4158c42aa4a74da48c84b3 -size 101783 +oid sha256:ee50ee8174f9499f415ea6e0881f6c81d2cf615ebfe0a8abd5683c6f3d677f4f +size 101826 diff --git a/tests/rust/re_integration_test/tests/snapshots/drop_multiple_streams_to_view_3.png b/tests/rust/re_integration_test/tests/snapshots/drop_multiple_streams_to_view_3.png index e3d02764fa68..6f4a413cb80a 100644 --- a/tests/rust/re_integration_test/tests/snapshots/drop_multiple_streams_to_view_3.png +++ b/tests/rust/re_integration_test/tests/snapshots/drop_multiple_streams_to_view_3.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5ddd7bf9c0a80686204aabe6a59eb916428f9634ec944faf7381888fc5c13585 -size 135400 +oid sha256:d24cd32b75c18f44d348f5a4066fe4aa35c82acb06f861e002d209357dc4afa0 +size 135600 diff --git a/tests/rust/re_integration_test/tests/snapshots/drop_multiple_streams_to_view_4.png b/tests/rust/re_integration_test/tests/snapshots/drop_multiple_streams_to_view_4.png index 7a1e51661238..9d771e570609 100644 --- a/tests/rust/re_integration_test/tests/snapshots/drop_multiple_streams_to_view_4.png +++ b/tests/rust/re_integration_test/tests/snapshots/drop_multiple_streams_to_view_4.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8bb5f0066b27126be2fb14f417108d79bbf403c55513f18c0e646475ed7308ea -size 109147 +oid sha256:c804630973a6cbee36090dfbd03dddac28208addf877df3dc77bb0f639228e71 +size 109051 diff --git a/tests/rust/re_integration_test/tests/snapshots/drop_multiple_streams_to_view_5.png b/tests/rust/re_integration_test/tests/snapshots/drop_multiple_streams_to_view_5.png index 8187f45bd16c..d3f781a1ee66 100644 --- a/tests/rust/re_integration_test/tests/snapshots/drop_multiple_streams_to_view_5.png +++ b/tests/rust/re_integration_test/tests/snapshots/drop_multiple_streams_to_view_5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:05efa8f57de8629fba987e1d11b054e3de4f2c066e49f27d843f53cfe46eaf8e -size 108923 +oid sha256:8c83ffa70e3b8a83ee99ce409175e529e5f164b0f042a965b126fceb06720e08 +size 108872 diff --git a/tests/rust/re_integration_test/tests/snapshots/drop_multiple_streams_to_view_6.png b/tests/rust/re_integration_test/tests/snapshots/drop_multiple_streams_to_view_6.png index bb7f293fcb2f..719542abbc94 100644 --- a/tests/rust/re_integration_test/tests/snapshots/drop_multiple_streams_to_view_6.png +++ b/tests/rust/re_integration_test/tests/snapshots/drop_multiple_streams_to_view_6.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:27a78830d9c29a0772c0d7209f52c8de56d7d837504e735c991a663c792f4d19 -size 151763 +oid sha256:e954dbc5edccbb46db3d39d4b1963ba9e154dad4ceec8f5efb2037464ff645c7 +size 151866 diff --git a/tests/rust/re_integration_test/tests/snapshots/drop_stream_to_view_2.png b/tests/rust/re_integration_test/tests/snapshots/drop_stream_to_view_2.png index 07998844cb5d..b02613d5df4c 100644 --- a/tests/rust/re_integration_test/tests/snapshots/drop_stream_to_view_2.png +++ b/tests/rust/re_integration_test/tests/snapshots/drop_stream_to_view_2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4b323323c2f1ef60e1a7eeb6c854fc3ceee637336d4c6f9fb43dcfad3e0ba90f -size 128439 +oid sha256:ed4794b26a1917189c09d78fce463d2ad4359c1283003dbffed2648e67eb0f67 +size 128704 diff --git a/tests/rust/re_integration_test/tests/snapshots/drop_stream_to_view_3.png b/tests/rust/re_integration_test/tests/snapshots/drop_stream_to_view_3.png index 2264020577c1..6328ebadc49e 100644 --- a/tests/rust/re_integration_test/tests/snapshots/drop_stream_to_view_3.png +++ b/tests/rust/re_integration_test/tests/snapshots/drop_stream_to_view_3.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:995611124e6fc04fa2b0def4cf617825166ac40eafff627efd355a2dbf5b7669 -size 131951 +oid sha256:efa6ada596e20d6e3b03915aadac539b6d7fa08bb26d23251fe8d12e8680f965 +size 132186 diff --git a/tests/rust/re_integration_test/tests/snapshots/drop_stream_to_view_4.png b/tests/rust/re_integration_test/tests/snapshots/drop_stream_to_view_4.png index 07998844cb5d..b02613d5df4c 100644 --- a/tests/rust/re_integration_test/tests/snapshots/drop_stream_to_view_4.png +++ b/tests/rust/re_integration_test/tests/snapshots/drop_stream_to_view_4.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4b323323c2f1ef60e1a7eeb6c854fc3ceee637336d4c6f9fb43dcfad3e0ba90f -size 128439 +oid sha256:ed4794b26a1917189c09d78fce463d2ad4359c1283003dbffed2648e67eb0f67 +size 128704 diff --git a/tests/rust/re_integration_test/tests/snapshots/drop_stream_to_view_5.png b/tests/rust/re_integration_test/tests/snapshots/drop_stream_to_view_5.png index 9ac36cf10922..3fc8aa84d666 100644 --- a/tests/rust/re_integration_test/tests/snapshots/drop_stream_to_view_5.png +++ b/tests/rust/re_integration_test/tests/snapshots/drop_stream_to_view_5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d46b7b18973254d9196bbcf4f3a42f3702f2ce0fa0abd0b8ecb8f58422a82f0f -size 129749 +oid sha256:cff3385616430b1cb4928d9a19687fe004dadf00d1f4ac5cfed070470fa598f4 +size 130023 diff --git a/tests/rust/re_integration_test/tests/snapshots/drop_stream_to_view_6.png b/tests/rust/re_integration_test/tests/snapshots/drop_stream_to_view_6.png index e3d02764fa68..6f4a413cb80a 100644 --- a/tests/rust/re_integration_test/tests/snapshots/drop_stream_to_view_6.png +++ b/tests/rust/re_integration_test/tests/snapshots/drop_stream_to_view_6.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5ddd7bf9c0a80686204aabe6a59eb916428f9634ec944faf7381888fc5c13585 -size 135400 +oid sha256:d24cd32b75c18f44d348f5a4066fe4aa35c82acb06f861e002d209357dc4afa0 +size 135600 diff --git a/tests/rust/re_integration_test/tests/snapshots/series_count_exceeds_max.png b/tests/rust/re_integration_test/tests/snapshots/series_count_exceeds_max.png index 81a74f51cfb5..b341a984e7f3 100644 --- a/tests/rust/re_integration_test/tests/snapshots/series_count_exceeds_max.png +++ b/tests/rust/re_integration_test/tests/snapshots/series_count_exceeds_max.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c0ac1b64fd920ab3633b112256010512afb06104aac2e215cf060a3a4442d842 -size 1425681 +oid sha256:bdd83d5c5eea420dd02d8d90f958282c4941d7a961e7b5b2343a290ec3f53118 +size 1432631 diff --git a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_multi_scalar_view_selected.png b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_multi_scalar_view_selected.png index 749c6a19bd26..81a34a486034 100644 --- a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_multi_scalar_view_selected.png +++ b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_multi_scalar_view_selected.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:98e6df31f27a1a533939d8e853f0f04b9241a2cb8eb909ef9ab73d17ebf230e5 -size 239860 +oid sha256:e6bd265abffcd522fcbc6a011388361fda11c60921c5f9e84e74a1a48f19d1b4 +size 240226 From 5e8bc4d6f4db6e43010e03c8c34ecdc159fe084a Mon Sep 17 00:00:00 2001 From: Antoine Beyeler <49431240+abey79@users.noreply.github.com> Date: Tue, 10 Mar 2026 10:23:38 +0100 Subject: [PATCH 078/513] Improve task-based registration error display ### Context We recently introduced a test on property size (to avoid huge manifests), which makes the task-time registration error potentially more common. Such error must be sufficiently clear to allow the user to take corrective action (e.g. do not attempt to register this URI, etc.). Currently, at the redap level, such task-time registration error is reported on a per-task-basis. Note that task typically have a batch size of up to 1000 rrd. On the client side, this was so far very poorly handled: the task error was repeated for every single rrd covered by said task. This means that for 1 failed rrd in a task, the error message would be duplicated up to 1000x. For example, in a batch registration of 3 rrds (in a single task) with two failed ones, the following error would be printed: ``` ValueError: Registration failed for the following URIs: file:///Users/hhip/src/reality/my_scripts/property_error/recording3.rrd: error while processing segments: InvalidArguments: oversized_props2 (file:///Users/hhip/src/reality/my_scripts/property_error/recording2.rrd) registration failed: segment properties (806 KiB) exceed the maximum allowed size (1000 B). Largest properties: property:large_prop2:embedding (806 KiB) (crates/redap_manifest_registry/src/dataset/api.rs:3205:56), InvalidArguments: oversized_props1 (file:///Users/hhip/src/reality/my_scripts/property_error/recording1.rrd) registration failed: segment properties (806 KiB) exceed the maximum allowed size (1000 B). Largest properties: property:large_prop1:embedding (806 KiB) (crates/redap_manifest_registry/src/dataset/api.rs:3205:56) file:///Users/hhip/src/reality/my_scripts/property_error/recording1.rrd: error while processing segments: InvalidArguments: oversized_props2 (file:///Users/hhip/src/reality/my_scripts/property_error/recording2.rrd) registration failed: segment properties (806 KiB) exceed the maximum allowed size (1000 B). Largest properties: property:large_prop2:embedding (806 KiB) (crates/redap_manifest_registry/src/dataset/api.rs:3205:56), InvalidArguments: oversized_props1 (file:///Users/hhip/src/reality/my_scripts/property_error/recording1.rrd) registration failed: segment properties (806 KiB) exceed the maximum allowed size (1000 B). Largest properties: property:large_prop1:embedding (806 KiB) (crates/redap_manifest_registry/src/dataset/api.rs:3205:56) file:///Users/hhip/src/reality/my_scripts/property_error/recording2.rrd: error while processing segments: InvalidArguments: oversized_props2 (file:///Users/hhip/src/reality/my_scripts/property_error/recording2.rrd) registration failed: segment properties (806 KiB) exceed the maximum allowed size (1000 B). Largest properties: property:large_prop2:embedding (806 KiB) (crates/redap_manifest_registry/src/dataset/api.rs:3205:56), InvalidArguments: oversized_props1 (file:///Users/hhip/src/reality/my_scripts/property_error/recording1.rrd) registration failed: segment properties (806 KiB) exceed the maximum allowed size (1000 B). Largest properties: property:large_prop1:embedding (806 KiB) (crates/redap_manifest_registry/src/dataset/api.rs:3205:56) ``` Properly addressing that requires replacing the generic task stuff at the redap level by a bespoke, typed, per-rrd error/status reporting. That's future work. ### What This PR is a low-effort fix to the current error formatting, such that the error above now becomes: ``` ValueError: Registration failed while processing the following segments: InvalidArguments: Segment 'oversized_props2' (file:///Users/hhip/src/reality/my_scripts/property_error/recording2.rrd): segment properties (806 KiB) exceed the maximum allowed size (1000 B). Largest properties: property:large_prop2:embedding (806 KiB) (crates/redap_manifest_registry/src/dataset/api.rs:3203:56) InvalidArguments: Segment 'oversized_props1' (file:///Users/hhip/src/reality/my_scripts/property_error/recording1.rrd): segment properties (806 KiB) exceed the maximum allowed size (1000 B). Largest properties: property:large_prop1:embedding (806 KiB) (crates/redap_manifest_registry/src/dataset/api.rs:3203:56) ``` Source-Ref: 7917c834d795f7ed1100ba7c37787cee4efe33f1 --- rerun_py/src/catalog/registration_handle.rs | 29 ++++++++------------- 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/rerun_py/src/catalog/registration_handle.rs b/rerun_py/src/catalog/registration_handle.rs index c6a07a941be3..06a6478084e2 100644 --- a/rerun_py/src/catalog/registration_handle.rs +++ b/rerun_py/src/catalog/registration_handle.rs @@ -165,9 +165,9 @@ impl PyRegistrationHandleInternal { .await .map_err(to_py_err)?; - // Track errors by descriptor index - let mut errors: HashMap<&RegisterWithDatasetTaskDescriptor, String> = - HashMap::new(); + // Collect unique error messages (deduplicated across descriptors + // that share the same task). + let mut unique_errors: Vec = Vec::new(); while let Some(response) = response_stream.next().await { let response = response.map_err(to_py_err)?.try_into().map_err(to_py_err)?; @@ -175,27 +175,20 @@ impl PyRegistrationHandleInternal { let results = process_task_response(response, &descriptors, &task_id_to_indices)?; - for (uri, _segment_id, error) in results { - if let Some(err) = error { - // Lookup the descriptor index for this URI - for desc in &descriptors { - if desc.storage_url.to_string() == uri { - errors.insert(desc, err.clone()); - } - } + for (_uri, _segment_id, error) in results { + if let Some(err) = error + && !unique_errors.contains(&err) + { + unique_errors.push(err); } } } // Check for any errors - if !errors.is_empty() { - let error_msgs: Vec = errors - .iter() - .map(|(desc, err)| format!("{}: {err}", desc.storage_url)) - .collect(); + if !unique_errors.is_empty() { return Err(PyValueError::new_err(format!( - "Registration failed for the following URIs:\n{}", - error_msgs.join("\n") + "Registration failed while processing the following segments:\n{}", + unique_errors.join("\n") ))); } From 40da2a433ca1469275b49604b3f72b98c3f21312 Mon Sep 17 00:00:00 2001 From: Isse Date: Tue, 10 Mar 2026 12:48:38 +0100 Subject: [PATCH 079/513] Show loader when missing codec that is still being downloaded ### Related - Closes https://linear.app/rerun/issue/RR-3992/dont-show-error-message-if-codec-isnt-loaded-yet ### What We were showing an error when the codec chunk was unloaded, but should show a loader instead. Source-Ref: 7e2120e09a42332a7cd8ad52d2e0a89f56b0698b --- .../src/visualizers/video/video_stream.rs | 10 +++++++++- .../src/cache/video_stream_cache.rs | 15 ++++++++++++++- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/crates/viewer/re_view_spatial/src/visualizers/video/video_stream.rs b/crates/viewer/re_view_spatial/src/visualizers/video/video_stream.rs index 39337f47957a..213430fe7b4b 100644 --- a/crates/viewer/re_view_spatial/src/visualizers/video/video_stream.rs +++ b/crates/viewer/re_view_spatial/src/visualizers/video/video_stream.rs @@ -140,7 +140,15 @@ impl VisualizerSystem for VideoStreamVisualizer { format!("No video samples available for {entity_path:?}"), VideoPlaybackIssueSeverity::Informational, ), - _ => ( + VideoStreamProcessingError::UnloadedCodec => ( + "Codec not loaded yet".to_owned(), + VideoPlaybackIssueSeverity::Loading, + ), + VideoStreamProcessingError::InvalidVideoSampleType(_) + | VideoStreamProcessingError::MissingCodec + | VideoStreamProcessingError::FailedReadingCodec(_) + | VideoStreamProcessingError::OutOfOrderSamples + | VideoStreamProcessingError::UnexpectedChunkChanges => ( format!("Failed to play video at {entity_path:?}: {err}"), VideoPlaybackIssueSeverity::Error, ), diff --git a/crates/viewer/re_viewer_context/src/cache/video_stream_cache.rs b/crates/viewer/re_viewer_context/src/cache/video_stream_cache.rs index ca81f32bc230..3f831a869267 100644 --- a/crates/viewer/re_viewer_context/src/cache/video_stream_cache.rs +++ b/crates/viewer/re_viewer_context/src/cache/video_stream_cache.rs @@ -101,6 +101,9 @@ pub enum VideoStreamProcessingError { #[error("No codec specified.")] MissingCodec, + #[error("Codec not loaded yet.")] + UnloadedCodec, + #[error("Failed to read codec - {0}")] FailedReadingCodec(Box), @@ -744,7 +747,17 @@ fn load_video_data_from_chunks( let sample_chunks = query_results.get_required(sample_component).unwrap_or(&[]); let codec_chunks = query_results .get_required(codec_component) - .map_err(|_err| VideoStreamProcessingError::MissingCodec)?; + .map_err(|_err| { + if store + .storage_engine() + .store() + .entity_has_component_on_timeline(&timeline, entity_path, codec_component) + { + VideoStreamProcessingError::UnloadedCodec + } else { + VideoStreamProcessingError::MissingCodec + } + })?; // Translate codec by looking at the last codec. // TODO(andreas): Should validate whether all codecs ever logged are the same, but it's a bit tedious. From 5d37a4c523ce0d4bb0fd59c75ae56d2c2308c638 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Tue, 10 Mar 2026 13:26:08 +0100 Subject: [PATCH 080/513] Make sure the item selection is compatible with the current route * Closes https://linear.app/rerun/issue/RR-3996/confusing-what-is-selected Source-Ref: e46cec36f8394496ebed276afab6cc9f58f52e89 --- crates/viewer/re_recording_panel/src/data.rs | 6 +++- crates/viewer/re_viewer/src/app_state.rs | 7 +++-- crates/viewer/re_viewer_context/src/item.rs | 30 +++++++++++++++++++ .../re_viewer_context/src/selection_state.rs | 1 + 4 files changed, 40 insertions(+), 4 deletions(-) diff --git a/crates/viewer/re_recording_panel/src/data.rs b/crates/viewer/re_recording_panel/src/data.rs index dae366917028..80f13c3ae793 100644 --- a/crates/viewer/re_recording_panel/src/data.rs +++ b/crates/viewer/re_recording_panel/src/data.rs @@ -260,9 +260,13 @@ pub struct RecordingData<'a> { #[cfg_attr(feature = "testing", derive(serde::Serialize))] pub struct ServerData<'a> { pub origin: re_uri::Origin, - pub is_active: bool, + + /// This is what is selected pub is_selected: bool, + /// What is selected is a subset of this thing + pub is_active: bool, + pub entries_data: ServerEntriesData<'a>, } diff --git a/crates/viewer/re_viewer/src/app_state.rs b/crates/viewer/re_viewer/src/app_state.rs index a66242e1dc2e..5dc325b8112d 100644 --- a/crates/viewer/re_viewer/src/app_state.rs +++ b/crates/viewer/re_viewer/src/app_state.rs @@ -251,6 +251,7 @@ impl AppState { // TODO(RR-3033): This needs to be further cleaned up and split into separately handled routes. _ => { let blueprint_query = self.blueprint_query_for_viewer(store_context.blueprint); + let route = self.navigation.current(); let Self { app_options, @@ -301,9 +302,10 @@ impl AppState { return false; } - viewport_ui.blueprint.is_item_valid(storage_context, item) + item.is_compatible_with_route(route) + && viewport_ui.blueprint.is_item_valid(storage_context, item) }, - Some(Item::StoreId(store_context.recording.store_id().clone())), + route.item(), ); if let SelectionChange::SelectionChanged(selection) = selection_change @@ -407,7 +409,6 @@ impl AppState { }; let egui_ctx = ui.ctx().clone(); - let route = self.navigation.current(); let ctx = ViewerContext { app_ctx: AppContext { is_test: app_env.is_test(), diff --git a/crates/viewer/re_viewer_context/src/item.rs b/crates/viewer/re_viewer_context/src/item.rs index 164004746e03..29270c253440 100644 --- a/crates/viewer/re_viewer_context/src/item.rs +++ b/crates/viewer/re_viewer_context/src/item.rs @@ -91,6 +91,14 @@ impl Item { Self::RedapServer(EXAMPLES_ORIGIN.clone()) } + pub fn redap_origin(&self) -> Option<&re_uri::Origin> { + match self { + Self::RedapServer(origin) => Some(origin), + Self::RedapEntry(entry_uri) => Some(&entry_uri.origin), + _ => None, + } + } + pub fn view_id(&self) -> Option> { match self { Self::AppId(_) @@ -125,6 +133,28 @@ impl Item { } } + /// Is this item compatible with the given [`crate::Route`]? + /// + /// For example, a recording or redap entry that belongs to a different + /// route than the currently active one is incompatible. + pub fn is_compatible_with_route(&self, route: &crate::Route) -> bool { + if let Self::StoreId(store_id) = self + && let Some(route_recording_id) = route.recording_id() + && store_id != route_recording_id + { + return false; + } + + if let Some(origin) = self.redap_origin() + && let Some(route_origin) = route.item().as_ref().and_then(|item| item.redap_origin()) + && origin != route_origin + { + return false; + } + + true + } + /// Converts this item to a data path if possible. pub fn to_data_path(&self) -> Option { match self { diff --git a/crates/viewer/re_viewer_context/src/selection_state.rs b/crates/viewer/re_viewer_context/src/selection_state.rs index d64a16ae237f..13d309145bcb 100644 --- a/crates/viewer/re_viewer_context/src/selection_state.rs +++ b/crates/viewer/re_viewer_context/src/selection_state.rs @@ -101,6 +101,7 @@ impl ApplicationSelectionState { if self.selection.is_empty() && let Some(fallback_selection) = fallback_selection { + re_log::trace!("Current selection invalid in this context; switching to fallback"); self.selection = ItemCollection::from(fallback_selection); } From 4108af42457f29b3ad1b5acdcbe4c898a639c243 Mon Sep 17 00:00:00 2001 From: Isse Date: Tue, 10 Mar 2026 14:03:42 +0100 Subject: [PATCH 081/513] revert change to `examples/notebook/notebook_viewer/notebook_viewer.ipynb` ### Related - Reverts a change in https://github.com/rerun-io/reality/pull/1057 ### What Unintentional change went in. Source-Ref: c56c1499ff11ac64c9b4b8758b21d24c5fbea7a7 --- .../notebook_viewer/notebook_viewer.ipynb | 98 ++----------------- 1 file changed, 10 insertions(+), 88 deletions(-) diff --git a/examples/notebook/notebook_viewer/notebook_viewer.ipynb b/examples/notebook/notebook_viewer/notebook_viewer.ipynb index 6cf3db1fa56f..fc86bab41cb1 100644 --- a/examples/notebook/notebook_viewer/notebook_viewer.ipynb +++ b/examples/notebook/notebook_viewer/notebook_viewer.ipynb @@ -2,106 +2,28 @@ "cells": [ { "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Starting asset server due to RERUN_NOTEBOOK_ASSET=serve-local\n", - "Loading assets into memory…\n", - "Serving rerun notebook assets at http://127.0.0.1:49643\n" - ] - }, - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "70660ab254464b1db8cb3cb9d57b637d", - "version_major": 2, - "version_minor": 1 - }, - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "from __future__ import annotations\n", - "\n", - "from rerun.notebook import Viewer\n", - "\n", - "viewer = Viewer(\n", - " width=\"auto\",\n", - " height=\"auto\",\n", - " url=\"https://app.rerun.io/version/nightly/examples/raw_mesh.rrd\",\n", - ")\n", - "viewer" - ] - }, - { - "cell_type": "code", - "execution_count": 2, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "5185be34c50643cf8bab1b694f2f7ab0", - "version_major": 2, - "version_minor": 1 - }, - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "from __future__ import annotations\n", "\n", "from rerun.notebook import Viewer\n", "\n", - "viewer = Viewer(\n", + "Viewer(\n", " width=\"auto\",\n", " height=\"auto\",\n", " url=\"https://app.rerun.io/version/nightly/examples/raw_mesh.rrd\",\n", - ")\n", - "viewer" + ").update_panels(\n", + " blueprint=\"hidden\",\n", + " selection=\"hidden\",\n", + " time=\"collapsed\",\n", + ")" ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] } ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.11.13" - } - }, + "metadata": {}, "nbformat": 4, "nbformat_minor": 4 } + From 17ce8c9ca1b1395e60505ceb2beea6a711c57f3a Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Tue, 10 Mar 2026 14:13:05 +0100 Subject: [PATCH 082/513] Run `lint.py` on the whole repository MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit …not just `rerun/` subfolder. Some notable fixes: * Turns on more clippy lints in a couple of crates * Improved error formatting (use `Display` instead of `Debug`) in MANY places * Avoid string injection bugs in CosmosDb * No trailing whitespace in random markdown files --------- Source-Ref: 2494d5507454f2f33184aa153d9ac266c2850cc2 Co-authored-by: Claude Opus 4.6 --- scripts/lint.py | 180 +++++++++++++++++++++++++++++------------------- 1 file changed, 109 insertions(+), 71 deletions(-) diff --git a/scripts/lint.py b/scripts/lint.py index 898763ce29cd..0ecf0f6c96fa 100755 --- a/scripts/lint.py +++ b/scripts/lint.py @@ -22,6 +22,11 @@ # ----------------------------------------------------------------------------- +# Path prefix from git root to the rerun directory. +# "./rerun/" in the monorepo (reality), "./" in the standalone rerun repo. +# Set in main(). +rerun_prefix = "./" + debug_format_of_err = re.compile(r"\{\:#?\?\}.*, err") error_match_name = re.compile(r"Err\((\w+)\)") error_map_err_name = re.compile(r"map_err\(\|(\w+)\|") @@ -124,6 +129,7 @@ def lint_url(url: str) -> str | None: "https://github.com/lycheeverse/lychee/blob/master/lychee.example.toml", "https://github.com/rerun-io/documentation/blob/main/src/utils/tokens.ts", "https://github.com/rerun-io/landing/blob/main/src/lib/lang.ts", # if this file moves we should check the linked code. + "https://github.com/rerun-io/platform-operator/blob/main/README-tech-overview.md", "https://github.com/rerun-io/rerun/blob/main/ARCHITECTURE.md", "https://github.com/rerun-io/rerun/blob/main/CODE_OF_CONDUCT.md", "https://github.com/rerun-io/rerun/blob/main/CONTRIBUTING.md", @@ -155,6 +161,7 @@ def lint_line( prev_line: str | None, file_extension: str = "rs", is_in_docstring: bool = False, + is_in_oss_rerun_repo: bool = True, ) -> str | None: if line == "": return None @@ -192,7 +199,7 @@ def lint_line( if err := lint_url(url): return err - if file_extension != "": + if file_extension != "" and is_in_oss_rerun_repo: # We lint against writing ellipsis using three dots for the sake of our UI: # * We want it consistent # * We want it beautiful (`…` looks different from `...`) @@ -248,6 +255,9 @@ def lint_line( if "{err:?}" in line or "{err:#?}" in line or debug_format_of_err.search(line): return "Format errors with re_error::format or using Display - NOT Debug formatting!" + if re.search(r"\?err\b", line): + return "Use `%err` (Display) instead of `?err` (Debug) in tracing macros" + if log_with_inline_sensitive_data.search(line): return 'URLs and paths should be passed as structured fields, not inline in log messages. Use e.g. `re_log::warn!(?url, "message: {err}")` instead of `re_log::warn!("message {url}: {err}")`' @@ -288,11 +298,12 @@ def lint_line( if "rec_stream" in line or "rr_stream" in line: return "Instantiated RecordingStreams should be named `rec`" - # Check for specific data platform phrases that should be capitalized - if re.search(r"(the\s+data\s+platform|Rerun\s+data\s+platform)", line) or re.search( - r"(? None: "The https://example.com/dataplatform/api endpoint", "We need a data platform solution", "Building data platform infrastructure", + # %err (Display) in tracing macros is good + 'tracing::warn!(%err, "something failed");', + 're_log::error!(%err, "something failed");', # Structured logging with sensitive data as fields (good pattern) 're_log::warn!(?url, "Failed to open URL: {err}");', 're_log::error!(?path, "Failed to read file: {err}");', @@ -451,6 +465,7 @@ def test_lint_line() -> None: "FIXME", "HACK", "TODO", + "# TODO make", "TODO:", "TODO(42)", "TODO(https://github.com/rerun-io/rerun/issues/42)", @@ -512,6 +527,9 @@ def test_lint_line() -> None: # thiserror with multiple unnamed fields (bad) '#[error("Failed to do {0}: {1}")]', '#[error("{0} failed with {1} at {2}")]', + # ?err (Debug) in tracing macros (bad - use %err for Display) + 'tracing::warn!(?err, "something failed");', + 're_log::error!(?err, "something failed");', ] for test in should_pass: @@ -1208,7 +1226,7 @@ def lint_markdown(filepath: str, source: SourceFile) -> tuple[list[str], list[st if in_metadata and line.startswith("-->"): in_metadata = False - if not in_code_block and not source.should_ignore(line_nr): + if not in_code_block and not source.should_ignore(line_nr) and filepath.startswith(rerun_prefix): if not in_metadata: # Check the casing on markdown headers if m := re.match(r"(\#+ )(.*)", line): @@ -1258,7 +1276,7 @@ def lint_markdown(filepath: str, source: SourceFile) -> tuple[list[str], list[st def lint_example_description(filepath: str) -> list[str]: # only applies to examples' readme - if not filepath.startswith("./examples/python") or not filepath.endswith("README.md"): + if not filepath.startswith(f"{rerun_prefix}examples/python") or not filepath.endswith("README.md"): return [] return [] @@ -1404,6 +1422,7 @@ def lint_file(filepath: str, args: Any) -> int: error: str | None is_in_docstring = False + is_in_oss_rerun_repo = filepath.startswith(rerun_prefix) prev_line = None for line_nr, line in enumerate(source.lines): @@ -1416,7 +1435,7 @@ def lint_file(filepath: str, args: Any) -> int: line = line[:-1] if line.strip() == '"""': is_in_docstring = not is_in_docstring - error = lint_line(line, prev_line, source.ext, is_in_docstring) + error = lint_line(line, prev_line, source.ext, is_in_docstring, is_in_oss_rerun_repo) prev_line = line if error is not None: num_errors += 1 @@ -1440,7 +1459,7 @@ def lint_file(filepath: str, args: Any) -> int: num_errors += len(errors) # Check for pyclass requirements (eq and module) in rerun_py Rust files - if filepath.startswith("./rerun_py/") and filepath.endswith(".rs"): + if filepath.startswith(f"{rerun_prefix}rerun_py/") and filepath.endswith(".rs"): pyclass_errors, error_lines, error_codes = lint_pyclass_requirements(source.lines) valid_errors = 0 for error, line_number, error_code in zip(pyclass_errors, error_lines, error_codes, strict=True): @@ -1481,12 +1500,18 @@ def lint_file(filepath: str, args: Any) -> int: elif 0 < num_errors: print(f"Run with --fix to automatically fix {num_errors} errors.") - if not filepath.startswith("./examples/rust") and filepath != "./Cargo.toml" and filepath.endswith("Cargo.toml"): - error = lint_workspace_lints(source.content) + if ( + filepath.endswith("Cargo.toml") + and not filepath.startswith(f"{rerun_prefix}examples/rust") + and filepath != "./dataplatform/crates/redap_protos/Cargo.toml" + ): + is_workspace = "[workspace]" in source.content + if not is_workspace: + error = lint_workspace_lints(source.content) - if error is not None: - print(source.error(error)) - num_errors += 1 + if error is not None: + print(source.error(error)) + num_errors += 1 # Markdown-specific lints if filepath.endswith(".md"): @@ -1502,8 +1527,8 @@ def lint_file(filepath: str, args: Any) -> int: def lint_crate_docs() -> int: """Make sure ARCHITECTURE.md talks about every single crate we have.""" - crates_dir = Path("crates") - architecture_md_file = Path("ARCHITECTURE.md") + crates_dir = Path(f"{rerun_prefix}crates") + architecture_md_file = Path(f"{rerun_prefix}ARCHITECTURE.md") architecture_md = architecture_md_file.read_text("utf-8") @@ -1586,57 +1611,78 @@ def main() -> None: "yml", ] + rerun_root = get_rerun_root() + + # Find the git root. In the monorepo (reality), it's the parent of rerun_root. + # In the standalone rerun repo, it IS rerun_root. + repo = git.Repo(rerun_root, search_parent_directories=True) + assert repo.working_tree_dir is not None, "Expected a non-bare git repository" + repo_root = repo.working_tree_dir + os.chdir(repo_root) + + # Path prefix from git root to the rerun directory. + # Monorepo: "./rerun/", Standalone: "./" + global rerun_prefix # noqa: PLW0603 + rerun_relative = Path(rerun_root).relative_to(repo_root) + rerun_prefix = str(rerun_relative).replace("\\", "/") + if rerun_prefix == ".": + rerun_prefix = "./" + else: + rerun_prefix = "./" + rerun_prefix + "/" + + # Helper to build a path relative to the git root, inside the rerun directory. + def rerun(path: str) -> str: + return f"{rerun_prefix}{path}" + exclude_paths = ( - "./.github/workflows/reusable_checks.yml", # zombie TODO hunting job - "./.nox", - "./.pytest_cache", - "./CODE_STYLE.md", - "./crates/build/re_types_builder/src/reflection.rs", # auto-generated - "./crates/store/re_protos/proto/schema_snapshot.yaml", # auto-generated - "./crates/store/re_protos/src/v0", # auto-generated - "./crates/store/re_protos/src/v1alpha1", # auto-generated - "./crates/viewer/re_web_viewer_server/web_viewer/re_viewer.js", # auto-generated by wasm_bindgen - "./docs/content/concepts/app-model.md", # this really needs custom letter casing - "./docs/content/reference/cli.md", # auto-generated - "./docs/snippets/all/tutorials/custom-application-id.cpp", # nuh-uh, I don't want rerun_example_ here - "./docs/snippets/all/tutorials/custom-application-id.py", # nuh-uh, I don't want rerun_example_ here - "./docs/snippets/all/tutorials/custom-application-id.rs", # nuh-uh, I don't want rerun_example_ here - "./examples/assets", - "./examples/python/detect_and_track_objects/cache/version.txt", - "./examples/python/objectron/objectron/proto/", # auto-generated - "./examples/rust/objectron/src/objectron.rs", # auto-generated - "./rerun_cpp/docs/doxygen-awesome/", # copied from an external repository - "./rerun_cpp/docs/html", - "./rerun_cpp/src/rerun/c/arrow_c_data_interface.h", # Not our code - "./rerun_cpp/src/rerun/third_party/cxxopts.hpp", # vendored - "./rerun_js/docs/", # auto-generated - "./rerun_js/node_modules", - "./rerun_js/web-viewer-react/node_modules", - "./rerun_js/web-viewer/index.js", - "./rerun_js/web-viewer/inlined.js", - "./rerun_js/web-viewer/node_modules", - "./rerun_js/web-viewer/re_viewer_bg.js", # auto-generated by wasm_bindgen - "./rerun_js/web-viewer/re_viewer.js", - "./rerun_notebook/node_modules", - "./rerun_notebook/src/rerun_notebook/static", - "./rerun_py/.pytest_cache/", - "./rerun_py/site/", # is in `.gitignore` which this script doesn't fully respect - "./run_wasm/README.md", # Has a "2d" lowercase example in a code snippet - "./scripts/lint.py", # we contain all the patterns we are linting against - "./scripts/zombie_todos.py", - "./tests/assets/lerobot/apple_storage/README.md", # not ours - "./tests/python/gil_stress/main.py", - "./tests/python/release_checklist/main.py", + rerun(".github/workflows/reusable_checks.yml"), # zombie TODO hunting job + rerun(".nox"), + rerun(".pytest_cache"), + rerun("CODE_STYLE.md"), + rerun("crates/build/re_types_builder/src/reflection.rs"), # auto-generated + rerun("crates/store/re_protos/proto/schema_snapshot.yaml"), # auto-generated + rerun("crates/store/re_protos/src/v0"), # auto-generated + rerun("crates/store/re_protos/src/v1alpha1"), # auto-generated + rerun("crates/viewer/re_web_viewer_server/web_viewer/re_viewer.js"), # auto-generated by wasm_bindgen + rerun("docs/content/concepts/app-model.md"), # this really needs custom letter casing + rerun("docs/content/reference/cli.md"), # auto-generated + rerun("docs/snippets/all/tutorials/custom-application-id.cpp"), # nuh-uh, I don't want rerun_example_ here + rerun("docs/snippets/all/tutorials/custom-application-id.py"), # nuh-uh, I don't want rerun_example_ here + rerun("docs/snippets/all/tutorials/custom-application-id.rs"), # nuh-uh, I don't want rerun_example_ here + rerun("examples/assets"), + rerun("examples/python/detect_and_track_objects/cache/version.txt"), + rerun("examples/python/objectron/objectron/proto/"), # auto-generated + rerun("examples/rust/objectron/src/objectron.rs"), # auto-generated + rerun("rerun_cpp/docs/doxygen-awesome/"), # copied from an external repository + rerun("rerun_cpp/docs/html"), + rerun("rerun_cpp/src/rerun/c/arrow_c_data_interface.h"), # Not our code + rerun("rerun_cpp/src/rerun/third_party/cxxopts.hpp"), # vendored + rerun("rerun_js/docs/"), # auto-generated + rerun("rerun_js/node_modules"), + rerun("rerun_js/web-viewer-react/node_modules"), + rerun("rerun_js/web-viewer/index.js"), + rerun("rerun_js/web-viewer/inlined.js"), + rerun("rerun_js/web-viewer/node_modules"), + rerun("rerun_js/web-viewer/re_viewer_bg.js"), # auto-generated by wasm_bindgen + rerun("rerun_js/web-viewer/re_viewer.js"), + rerun("rerun_notebook/node_modules"), + rerun("rerun_notebook/src/rerun_notebook/static"), + rerun("rerun_py/.pytest_cache/"), + rerun("rerun_py/site/"), # is in `.gitignore` which this script doesn't fully respect + rerun("run_wasm/README.md"), # Has a "2d" lowercase example in a code snippet + rerun("scripts/lint.py"), # we contain all the patterns we are linting against + rerun("scripts/zombie_todos.py"), + rerun("tests/assets/lerobot/apple_storage/README.md"), # not ours + rerun("tests/python/gil_stress/main.py"), + rerun("tests/python/release_checklist/main.py"), + "./dataplatform/crates/redap_protos/src/v1alpha1", # auto-generated protobuf files ) should_ignore = parse_gitignore(".gitignore") # TODO(#6730): parse all .gitignore files, not just top-level - rerun_root = get_rerun_root() - os.chdir(rerun_root) - if args.files: for filepath in args.files: - filepath = os.path.join(".", os.path.relpath(filepath, rerun_root)) + filepath = os.path.join(".", os.path.relpath(filepath, repo_root)) filepath = str(filepath).replace("\\", "/") extension = filepath.split(".")[-1] if extension in extensions: @@ -1644,17 +1690,9 @@ def main() -> None: continue num_errors += lint_file(filepath, args) else: - repo = git.Repo(".", search_parent_directories=True) - assert repo.working_tree_dir is not None, "Expected a non-bare git repository" - repo_root = Path(repo.working_tree_dir).resolve() - - tracked_files = [item[1].path for item in repo.index.iter_blobs()] + tracked_files = [str(item[1].path) for item in repo.index.iter_blobs()] for filepath in tracked_files: - # Filter to only files under rerun_root (for monorepo support) - full_path = repo_root / filepath - if not full_path.is_relative_to(rerun_root): - continue - filepath = "./" + str(full_path.relative_to(rerun_root)) + filepath = "./" + filepath filepath = filepath.replace("\\", "/") extension = filepath.split(".")[-1] if extension in extensions: From b5fe8f9e5c9a85b164a0f1321ef22688e02e1d6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Proch=C3=A1zka?= Date: Tue, 10 Mar 2026 15:23:45 +0100 Subject: [PATCH 083/513] Fix bad picture element in docs Not sure how this one was produced. `upload-image` doesn't add that to sources. I changed landing to ignore `full` width in `srcset`, so there's no need to push this to `docs-latest` right now Source-Ref: 11f0bb2043c3b17d4c957b89a6ad7c78ce68e389 --- docs/content/concepts/logging-and-ingestion/transforms.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/content/concepts/logging-and-ingestion/transforms.md b/docs/content/concepts/logging-and-ingestion/transforms.md index b4165d9cac78..5289f2fbfbe1 100644 --- a/docs/content/concepts/logging-and-ingestion/transforms.md +++ b/docs/content/concepts/logging-and-ingestion/transforms.md @@ -206,7 +206,6 @@ snippet: archetypes/mesh3d_instancing - From 8382f1492d14b1e47aa8e42b3c609952d9d15bc1 Mon Sep 17 00:00:00 2001 From: Andreas Reich Date: Tue, 10 Mar 2026 15:33:29 +0100 Subject: [PATCH 084/513] Fix bounding box calculation for GLB/GLTF & DAE meshes Source-Ref: d004e107e11218210fce9c77c401dd5c1569daa0 Co-authored-by: Isse --- .../re_renderer/src/importer/cpu_model.rs | 52 ++++++++++++++----- crates/viewer/re_renderer/src/importer/dae.rs | 20 +++---- .../viewer/re_renderer/src/importer/gltf.rs | 22 +++----- crates/viewer/re_renderer/src/importer/mod.rs | 2 +- crates/viewer/re_renderer/src/importer/obj.rs | 3 +- crates/viewer/re_renderer/src/lib.rs | 2 +- .../viewer/re_view_spatial/src/mesh_loader.rs | 9 +--- crates/viewer/re_view_spatial/src/ui_3d.rs | 10 ++++ crates/viewer/re_view_spatial/src/view_3d.rs | 8 ++- .../tests/snapshots/dae_mesh_import.png | 4 +- .../snapshots/dae_multi_triangle_groups.png | 4 +- .../all_view_selection_uis/Dark/3D.png | 4 +- .../all_view_selection_uis/Light/3D.png | 4 +- .../tests/snapshots/origin_camera_3d.png | 4 +- .../tests/snapshots/origin_image_3d.png | 4 +- .../tests/snapshots/origin_keypoint_3d.png | 4 +- .../tests/snapshots/origin_root_3d.png | 4 +- .../tests/snapshots/origin_world_3d.png | 4 +- 18 files changed, 93 insertions(+), 71 deletions(-) diff --git a/crates/viewer/re_renderer/src/importer/cpu_model.rs b/crates/viewer/re_renderer/src/importer/cpu_model.rs index 6572a96899a0..e7fb1c5d7394 100644 --- a/crates/viewer/re_renderer/src/importer/cpu_model.rs +++ b/crates/viewer/re_renderer/src/importer/cpu_model.rs @@ -12,10 +12,9 @@ slotmap::new_key_type! { } /// Like [`GpuMeshInstance`], but for CPU sided usage in a [`CpuModel`] only. -pub struct CpuMeshInstance { - pub mesh: CpuModelMeshKey, - pub world_from_mesh: glam::Affine3A, - // TODO(andreas): Expose other properties we have on [`GpuMeshInstance`]. +struct CpuMeshInstance { + mesh: CpuModelMeshKey, + world_from_mesh: glam::Affine3A, } /// A collection of meshes & mesh instances on the CPU. @@ -29,30 +28,55 @@ pub struct CpuMeshInstance { /// This is meant as a useful intermediate structure for doing post-processing steps on the model prior to gpu upload. #[derive(Default)] pub struct CpuModel { - pub meshes: SlotMap, - pub instances: Vec, - pub bbox: macaw::BoundingBox, + meshes: SlotMap, + instances: Vec, + bbox: macaw::BoundingBox, } impl CpuModel { /// Creates a new [`CpuModel`] from a single [`CpuMesh`], creating a single instance with identity transform. pub fn from_single_mesh(mesh: CpuMesh) -> Self { let mut model = Self::default(); - model.add_single_instance_mesh(mesh); + let key = model.add_mesh(mesh); + model.add_instance(key, glam::Affine3A::IDENTITY); model } - /// Adds a new [`CpuMesh`] to the model, creating a single instance with identity transform. - pub fn add_single_instance_mesh(&mut self, mesh: CpuMesh) { - self.bbox = self.bbox.union(mesh.bbox); - let mesh_key = self.meshes.insert(mesh); + /// Adds a [`CpuMesh`] to the model and returns its key. + /// + /// The mesh is not instantiated until [`Self::add_instance`] is called with the returned key. + pub fn add_mesh(&mut self, mesh: CpuMesh) -> CpuModelMeshKey { + self.meshes.insert(mesh) + } + + /// Adds an instance of a mesh with the given transform, updating the model's bounding box. + pub fn add_instance(&mut self, mesh_key: CpuModelMeshKey, world_from_mesh: glam::Affine3A) { + if let Some(mesh) = self.meshes.get(mesh_key) { + self.bbox = self + .bbox + .union(mesh.bbox.transform_affine3(&world_from_mesh)); + } self.instances.push(CpuMeshInstance { mesh: mesh_key, - world_from_mesh: glam::Affine3A::IDENTITY, + world_from_mesh, }); } - /// Converts the entire model into a serious of mesh instances that can be rendered. + /// The bounding box of the model, accumulated from all instances and their transforms. + pub fn bbox(&self) -> macaw::BoundingBox { + self.bbox + } + + /// Overrides the albedo factor on all materials of all meshes in the model. + pub fn override_albedo_factor(&mut self, albedo_factor: crate::Rgba) { + for (_key, mesh) in &mut self.meshes { + for material in &mut mesh.materials { + material.albedo_factor = albedo_factor; + } + } + } + + /// Converts the entire model into a series of mesh instances that can be rendered. /// /// Silently ignores: /// * instances with invalid mesh keys diff --git a/crates/viewer/re_renderer/src/importer/dae.rs b/crates/viewer/re_renderer/src/importer/dae.rs index ce1fb0642cfc..a7005856e1e6 100644 --- a/crates/viewer/re_renderer/src/importer/dae.rs +++ b/crates/viewer/re_renderer/src/importer/dae.rs @@ -19,7 +19,7 @@ use smallvec::SmallVec; use thiserror::Error; use crate::{ - CpuMeshInstance, CpuModel, CpuModelMeshKey, DebugLabel, RenderContext, Rgba32Unmul, + CpuModel, CpuModelMeshKey, DebugLabel, RenderContext, Rgba32Unmul, mesh::{self, CpuMesh}, }; @@ -132,7 +132,7 @@ fn load_dae_from_buffer_inner( } let cpu_mesh = import_geometry(geometry, mesh_element, &all_triangles, &maps, ctx)?; - let key = model.meshes.insert(cpu_mesh); + let key = model.add_mesh(cpu_mesh); let geom_id = geometry .id .as_deref() @@ -142,13 +142,11 @@ fn load_dae_from_buffer_inner( mesh_keys.insert(geom_id, key); } - let mut instances = Vec::new(); - let mut any_scene = false; for scene in document.iter::() { any_scene = true; for root in &scene.nodes { - gather_instances_recursive(&mut instances, root, &glam::Affine3A::IDENTITY, &mesh_keys); + gather_instances_recursive(&mut model, root, &glam::Affine3A::IDENTITY, &mesh_keys); } } @@ -156,7 +154,6 @@ fn load_dae_from_buffer_inner( return Err(DaeImportError::NoVisualScene); } - model.instances = instances; Ok(model) } @@ -297,7 +294,7 @@ fn extract_material_color( } fn gather_instances_recursive( - out: &mut Vec, + model: &mut CpuModel, node: &DaeNode, parent_tf: &glam::Affine3A, meshes: &HashMap, @@ -344,18 +341,15 @@ fn gather_instances_recursive( } }; - if let Some(mesh_key) = meshes.get(&id) { - out.push(CpuMeshInstance { - mesh: *mesh_key, - world_from_mesh: world_tf, - }); + if let Some(&mesh_key) = meshes.get(&id) { + model.add_instance(mesh_key, world_tf); } else { re_log::warn_once!(" references unknown geometry {id}"); } } for child in &node.children { - gather_instances_recursive(out, child, &world_tf, meshes); + gather_instances_recursive(model, child, &world_tf, meshes); } } diff --git a/crates/viewer/re_renderer/src/importer/gltf.rs b/crates/viewer/re_renderer/src/importer/gltf.rs index d9705d9cb037..67f824f38e5b 100644 --- a/crates/viewer/re_renderer/src/importer/gltf.rs +++ b/crates/viewer/re_renderer/src/importer/gltf.rs @@ -5,7 +5,7 @@ use smallvec::SmallVec; use crate::mesh::{CpuMesh, Material, MeshError}; use crate::resource_managers::{AlphaChannelUsage, GpuTexture2D, ImageDataDesc, TextureManager2D}; -use crate::{CpuMeshInstance, CpuModel, CpuModelMeshKey, RenderContext, Rgba32Unmul}; +use crate::{CpuModel, CpuModelMeshKey, RenderContext, Rgba32Unmul}; #[derive(thiserror::Error, Debug)] pub enum GltfImportError { @@ -109,18 +109,13 @@ pub fn load_gltf_from_buffer( re_tracing::profile_scope!("mesh"); let re_mesh = import_mesh(mesh, &buffers, &images_as_textures, &ctx.texture_manager_2d)?; - let re_mesh_key = re_model.meshes.insert(re_mesh); + let re_mesh_key = re_model.add_mesh(re_mesh); mesh_keys.insert(mesh.index(), re_mesh_key); } for scene in doc.scenes() { for node in scene.nodes() { - gather_instances_recursive( - &mut re_model.instances, - &node, - &glam::Affine3A::IDENTITY, - &mesh_keys, - ); + gather_instances_recursive(&mut re_model, &node, &glam::Affine3A::IDENTITY, &mesh_keys); } } @@ -298,7 +293,7 @@ fn import_mesh( } fn gather_instances_recursive( - instances: &mut Vec, + model: &mut CpuModel, node: &gltf::Node<'_>, transform: &glam::Affine3A, meshes: &HashMap, @@ -325,15 +320,12 @@ fn gather_instances_recursive( let transform = *transform * node_transform; for child in node.children() { - gather_instances_recursive(instances, &child, &transform, meshes); + gather_instances_recursive(model, &child, &transform, meshes); } if let Some(mesh) = node.mesh() - && let Some(mesh_key) = meshes.get(&mesh.index()) + && let Some(&mesh_key) = meshes.get(&mesh.index()) { - instances.push(CpuMeshInstance { - mesh: *mesh_key, - world_from_mesh: transform, - }); + model.add_instance(mesh_key, transform); } } diff --git a/crates/viewer/re_renderer/src/importer/mod.rs b/crates/viewer/re_renderer/src/importer/mod.rs index 2d1c48df9693..61a58e33bc59 100644 --- a/crates/viewer/re_renderer/src/importer/mod.rs +++ b/crates/viewer/re_renderer/src/importer/mod.rs @@ -12,4 +12,4 @@ pub mod stl; #[cfg(feature = "import-dae")] pub mod dae; -pub use cpu_model::{CpuMeshInstance, CpuModel, CpuModelMeshKey}; +pub use cpu_model::{CpuModel, CpuModelMeshKey}; diff --git a/crates/viewer/re_renderer/src/importer/obj.rs b/crates/viewer/re_renderer/src/importer/obj.rs index 0fd3c55e8637..7ea34ef1aa3a 100644 --- a/crates/viewer/re_renderer/src/importer/obj.rs +++ b/crates/viewer/re_renderer/src/importer/obj.rs @@ -98,7 +98,8 @@ pub fn load_obj_from_buffer( }; mesh.sanity_check()?; - model.add_single_instance_mesh(mesh); + let key = model.add_mesh(mesh); + model.add_instance(key, glam::Affine3A::IDENTITY); } Ok(model) diff --git a/crates/viewer/re_renderer/src/lib.rs b/crates/viewer/re_renderer/src/lib.rs index 173e7a545039..b0c8820d9387 100644 --- a/crates/viewer/re_renderer/src/lib.rs +++ b/crates/viewer/re_renderer/src/lib.rs @@ -96,7 +96,7 @@ pub use texture_readback::{TextureReadback, poll_read_texture, schedule_read_tex // Re-export used color types directly. pub use ecolor::{Color32, Hsva, Rgba}; pub use global_bindings::GlobalBindings; -pub use importer::{CpuMeshInstance, CpuModel, CpuModelMeshKey}; +pub use importer::{CpuModel, CpuModelMeshKey}; pub use line_drawable_builder::{LineBatchBuilder, LineDrawableBuilder, LineStripBuilder}; pub use point_cloud_builder::{PointCloudBatchBuilder, PointCloudBuilder}; pub use queueable_draw_data::QueueableDrawData; diff --git a/crates/viewer/re_view_spatial/src/mesh_loader.rs b/crates/viewer/re_view_spatial/src/mesh_loader.rs index ab93be7e8fd2..ff5eff9d16e4 100644 --- a/crates/viewer/re_view_spatial/src/mesh_loader.rs +++ b/crates/viewer/re_view_spatial/src/mesh_loader.rs @@ -89,16 +89,11 @@ impl LoadedMesh { _ => anyhow::bail!("{media_type} files are not supported"), }; - // Overwriting albedo_factor of CpuMesh if specified in the Asset3D if let Some(albedo_factor) = albedo_factor { - for instance in &cpu_model.instances { - for material in &mut cpu_model.meshes[instance.mesh].materials { - material.albedo_factor = albedo_factor; - } - } + cpu_model.override_albedo_factor(albedo_factor); } - let bbox = cpu_model.bbox; + let bbox = cpu_model.bbox(); let mesh_instances = cpu_model.into_gpu_meshes(render_ctx)?; Ok(Self { diff --git a/crates/viewer/re_view_spatial/src/ui_3d.rs b/crates/viewer/re_view_spatial/src/ui_3d.rs index 0a44d2cf399b..83c050a217fc 100644 --- a/crates/viewer/re_view_spatial/src/ui_3d.rs +++ b/crates/viewer/re_view_spatial/src/ui_3d.rs @@ -45,6 +45,7 @@ pub struct View3DState { eye_interact_fade_change_time: f64, pub show_smoothed_bbox: bool, + pub show_per_entity_bbox: bool, } impl Default for View3DState { @@ -55,6 +56,7 @@ impl Default for View3DState { eye_interact_fade_in: false, eye_interact_fade_change_time: f64::NEG_INFINITY, show_smoothed_bbox: false, + show_per_entity_bbox: false, } } } @@ -387,6 +389,14 @@ impl SpatialView3D { .color(ctx.tokens().frustum_color) }); } + if state.state_3d.show_per_entity_bbox { + let mut batch = line_builder.batch("per_entity_bboxes"); + for bbox in state.bounding_boxes.per_entity.values() { + batch + .add_box_outline(bbox) + .map(|lines| lines.radius(box_line_radius).color(egui::Color32::YELLOW)); + } + } show_orbit_eye_center( ui.ctx(), diff --git a/crates/viewer/re_view_spatial/src/view_3d.rs b/crates/viewer/re_view_spatial/src/view_3d.rs index f80e0a25f3b4..a4abb4d75784 100644 --- a/crates/viewer/re_view_spatial/src/view_3d.rs +++ b/crates/viewer/re_view_spatial/src/view_3d.rs @@ -523,7 +523,13 @@ impl ViewClass for SpatialView3D { state.bounding_box_ui(ui, SpatialViewKind::ThreeD); #[cfg(debug_assertions)] - ui.re_checkbox(&mut state.state_3d.show_smoothed_bbox, "Smoothed bbox"); + { + ui.re_checkbox(&mut state.state_3d.show_smoothed_bbox, "Smoothed bbox"); + ui.re_checkbox( + &mut state.state_3d.show_per_entity_bbox, + "Per-entity bboxes", + ); + } }); re_ui::list_item::list_item_scope(ui, "spatial_view3d_selection_ui", |ui| { diff --git a/crates/viewer/re_view_spatial/tests/snapshots/dae_mesh_import.png b/crates/viewer/re_view_spatial/tests/snapshots/dae_mesh_import.png index 2ef29ef39f5f..2e3bda996b5a 100644 --- a/crates/viewer/re_view_spatial/tests/snapshots/dae_mesh_import.png +++ b/crates/viewer/re_view_spatial/tests/snapshots/dae_mesh_import.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a61e82431cdc965f9efff527b81f5bd43a9f383e1a808b78f21b8b48887e6dff -size 18710 +oid sha256:8593e6eee6a344fd6a19069dc61253101640294792eaac8e8a5fcd7d72a84e76 +size 8674 diff --git a/crates/viewer/re_view_spatial/tests/snapshots/dae_multi_triangle_groups.png b/crates/viewer/re_view_spatial/tests/snapshots/dae_multi_triangle_groups.png index 41c5813cddea..2e3bda996b5a 100644 --- a/crates/viewer/re_view_spatial/tests/snapshots/dae_multi_triangle_groups.png +++ b/crates/viewer/re_view_spatial/tests/snapshots/dae_multi_triangle_groups.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0cbb3d968358bae35078634462f6a555fddd5a808558d8b0b179f3b60ddce0f6 -size 19031 +oid sha256:8593e6eee6a344fd6a19069dc61253101640294792eaac8e8a5fcd7d72a84e76 +size 8674 diff --git a/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Dark/3D.png b/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Dark/3D.png index 54914f5a3d55..0009308e6b2f 100644 --- a/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Dark/3D.png +++ b/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Dark/3D.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:995328d0e7169760df88ae2967fe13e22e37fa061e309a68960b44b99e8996b0 -size 71145 +oid sha256:47efe7cef985586038c6979dbc824f61311e80f9406564a29a1cfbbe4cae5550 +size 73695 diff --git a/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Light/3D.png b/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Light/3D.png index 0127f4ff6a39..2c9563d4b5d0 100644 --- a/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Light/3D.png +++ b/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Light/3D.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d8afdde83e598d4aec58f48cf84ac103dff010ddc6ea67151e667402232efd57 -size 71128 +oid sha256:5893ebadd0ef417484feb7c83a3aca80bbf93d3598f219ba2c2a9e00d9537e8b +size 73668 diff --git a/tests/rust/re_integration_test/tests/snapshots/origin_camera_3d.png b/tests/rust/re_integration_test/tests/snapshots/origin_camera_3d.png index 0fd541dae06a..b395c6695fc1 100644 --- a/tests/rust/re_integration_test/tests/snapshots/origin_camera_3d.png +++ b/tests/rust/re_integration_test/tests/snapshots/origin_camera_3d.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b5336bf2b058be7771fc9aa0b04fefc33e9e65a94534fade63e5aa8141c86823 -size 196480 +oid sha256:c13d6b0decc06ba3566e78ce903ede6e96884a00d5b9cea5a64c07863b8aa22e +size 199241 diff --git a/tests/rust/re_integration_test/tests/snapshots/origin_image_3d.png b/tests/rust/re_integration_test/tests/snapshots/origin_image_3d.png index 39752a10502d..9b5e3d6334bf 100644 --- a/tests/rust/re_integration_test/tests/snapshots/origin_image_3d.png +++ b/tests/rust/re_integration_test/tests/snapshots/origin_image_3d.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f8611e75e93d2fd9236413f889a3d7ccac25d59e4d60c6e5275ccc5f9078f430 -size 196447 +oid sha256:987d35866566f47a2a108f0fcb878dcf010a364921bd5f2e1e575e92007add71 +size 199208 diff --git a/tests/rust/re_integration_test/tests/snapshots/origin_keypoint_3d.png b/tests/rust/re_integration_test/tests/snapshots/origin_keypoint_3d.png index 7b4a50f1d3c7..218636109884 100644 --- a/tests/rust/re_integration_test/tests/snapshots/origin_keypoint_3d.png +++ b/tests/rust/re_integration_test/tests/snapshots/origin_keypoint_3d.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:862ab6c10a3dffb39ffdd85dd83acffcb1199d918d7ee2f40013d439c0436c5a -size 192952 +oid sha256:aba3f051bbdcca7f657966e9992e51be6364392150920c46c0664630df49eb2c +size 195495 diff --git a/tests/rust/re_integration_test/tests/snapshots/origin_root_3d.png b/tests/rust/re_integration_test/tests/snapshots/origin_root_3d.png index 1b37fc9f0c42..c44f58f3a7aa 100644 --- a/tests/rust/re_integration_test/tests/snapshots/origin_root_3d.png +++ b/tests/rust/re_integration_test/tests/snapshots/origin_root_3d.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3dbf0482eacf703381f958fc20147d1ff0695df7a24bdad30d8e379a88bfc48b -size 207398 +oid sha256:ea6d42f363c53ffcddf3e275da57a3c7bceb794b143d20057eb7dd1cb095bdbb +size 210103 diff --git a/tests/rust/re_integration_test/tests/snapshots/origin_world_3d.png b/tests/rust/re_integration_test/tests/snapshots/origin_world_3d.png index fed9ef23d96b..00739ce943b2 100644 --- a/tests/rust/re_integration_test/tests/snapshots/origin_world_3d.png +++ b/tests/rust/re_integration_test/tests/snapshots/origin_world_3d.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bd05ccc4af25e32e2b0ec97e5e82068e12bb9279734f118abb1e48188af9c849 -size 195318 +oid sha256:f0d2696108b1cebe4b76f14c0e08cb9abb8b39c2ce55eb05c1d21ead4d64fa1f +size 198079 From 5a103d3f8169ab02f15a88dbf76840401f163d4a Mon Sep 17 00:00:00 2001 From: Andreas Reich Date: Tue, 10 Mar 2026 16:18:05 +0100 Subject: [PATCH 085/513] Fix example build Source-Ref: e7ca0cb885f8a8770ad486c2240212dd820d9f6c --- pyproject.toml | 5 ++++- uv.lock | 22 ++++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 9e6a4a7d2938..0130f36a14a1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -92,6 +92,7 @@ examples = [ # Example packages from workspace "air_traffic_data", "animated_urdf", + "any_scalar", "arkit_scenes", "blueprint", "blueprint_stocks", @@ -165,6 +166,7 @@ rerun-dev-fixup = { path = "rerun_py/rerun_dev_fixup", editable = false } # Example packages air_traffic_data = { workspace = true } animated_urdf = { workspace = true } +any_scalar = { workspace = true } arkit_scenes = { workspace = true } blueprint = { workspace = true } blueprint_stocks = { workspace = true } @@ -216,9 +218,10 @@ members = [ # Examples "examples/python/air_traffic_data", "examples/python/animated_urdf", + "examples/python/any_scalar", "examples/python/arkit_scenes", - "examples/python/blueprint", "examples/python/blueprint_stocks", + "examples/python/blueprint", "examples/python/camera_video_stream", "examples/python/clock", "examples/python/controlnet", diff --git a/uv.lock b/uv.lock index 897d74114294..e6acc17d782f 100644 --- a/uv.lock +++ b/uv.lock @@ -16,6 +16,7 @@ resolution-markers = [ members = [ "air-traffic-data", "animated-urdf", + "any-scalar", "arkit-scenes", "blueprint", "blueprint-stocks", @@ -129,6 +130,25 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, ] +[[package]] +name = "any-scalar" +version = "0.1.0" +source = { editable = "examples/python/any_scalar" } +dependencies = [ + { name = "psutil" }, + { name = "pyarrow" }, + { name = "rerun-sdk" }, + { name = "yfinance" }, +] + +[package.metadata] +requires-dist = [ + { name = "psutil" }, + { name = "pyarrow" }, + { name = "rerun-sdk", editable = "rerun_py" }, + { name = "yfinance" }, +] + [[package]] name = "anyio" version = "4.12.0" @@ -4795,6 +4815,7 @@ docs = [ examples = [ { name = "air-traffic-data" }, { name = "animated-urdf" }, + { name = "any-scalar" }, { name = "arkit-scenes" }, { name = "blueprint" }, { name = "blueprint-stocks" }, @@ -4911,6 +4932,7 @@ docs = [ examples = [ { name = "air-traffic-data", editable = "examples/python/air_traffic_data" }, { name = "animated-urdf", editable = "examples/python/animated_urdf" }, + { name = "any-scalar", editable = "examples/python/any_scalar" }, { name = "arkit-scenes", editable = "examples/python/arkit_scenes" }, { name = "blueprint", editable = "examples/python/blueprint" }, { name = "blueprint-stocks", editable = "examples/python/blueprint_stocks" }, From 5dd35e3835fcceb2e8eb9715ae4cf7ff69d79627 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Proch=C3=A1zka?= Date: Tue, 10 Mar 2026 16:00:04 +0100 Subject: [PATCH 086/513] Dont clobber `py-build` in `py-build-notebook` `py-build-notebook` runs a `uv sync` which uninstalls `rerun-sdk`. This PR uses the same pattern as `sync-examples` Source-Ref: defe8283d2d1e7466666a26979b209ae396222f8 --- pixi.toml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pixi.toml b/pixi.toml index 32216cf2e8c1..7591c414b673 100644 --- a/pixi.toml +++ b/pixi.toml @@ -392,8 +392,10 @@ js-docs-serve = { cmd = "yarn --cwd rerun_js run docs:serve", depends-on = ["js- py-build = { cmd = "uv run maturin develop --uv --manifest-path rerun_py/Cargo.toml --extras=tests,datafusion", env = { RERUN_ALLOW_MISSING_BIN = "1" } } # Build the `rerun-notebook` package. -py-build-notebook = { cmd = "uv sync --package rerun-notebook", depends-on = ["js-build-base"] } -py-build-notebook-fast = { cmd = "uv sync --package rerun-notebook" } +py-build-notebook = { cmd = "uv sync --package rerun-notebook --inexact --no-install-package rerun-sdk", depends-on = [ + "js-build-base", +] } +py-build-notebook-fast = { cmd = "uv sync --package rerun-notebook --inexact --no-install-package rerun-sdk" } # Create a wheel for the `rerun-sdk` package. py-build-wheel = { cmd = "cp target/release/rerun$EXECUTABLE_EXTENSION rerun_py/rerun_sdk/rerun_cli/ && uv run maturin build --release --manifest-path rerun_py/Cargo.toml", depends-on = [ "rerun-build-native-and-web-release", From 86cae60f3b04401264384b6326cd72548dcc4836 Mon Sep 17 00:00:00 2001 From: Pablo Vela Date: Tue, 10 Mar 2026 10:57:26 -0500 Subject: [PATCH 087/513] Expose executable_name and executable_path in Python spawn() (#12685) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### What The Python spawn() and _spawn_viewer() functions were missing two parameters that were already supported in the Rust SDK, C++ SDK, and even the PyO3 bindings layer (python_bridge.rs): - executable_name — Specifies the name of the Rerun executable to search for in PATH (defaults to "rerun"). - executable_path — Enforces a specific executable path, bypassing PATH lookup entirely. This PR surfaces both parameters through the Python API with documentation matching the Rust/C++ SDKs. Additionally fixes a minor typo in the doc comments for executable_path across three files — "searching though PATH" → "searching through PATH": - crates/top/re_sdk/src/spawn.rs - rerun_cpp/src/rerun/spawn_options.hpp - rerun_cpp/src/rerun/c/rerun.h Source-Ref: e4c5286efebe9f4b84123d92fb27f20ba05e30d5 --- crates/top/re_sdk/src/spawn.rs | 2 +- rerun_cpp/src/rerun/c/rerun.h | 2 +- rerun_cpp/src/rerun/spawn_options.hpp | 2 +- rerun_py/rerun_sdk/rerun/_spawn.py | 14 ++++++++++++++ rerun_py/rerun_sdk/rerun/sinks.py | 14 ++++++++++++++ 5 files changed, 31 insertions(+), 3 deletions(-) diff --git a/crates/top/re_sdk/src/spawn.rs b/crates/top/re_sdk/src/spawn.rs index 773e51c79ef4..b77e26c6f5a2 100644 --- a/crates/top/re_sdk/src/spawn.rs +++ b/crates/top/re_sdk/src/spawn.rs @@ -44,7 +44,7 @@ pub struct SpawnOptions { /// Defaults to `rerun`. pub executable_name: String, - /// Enforce a specific executable to use instead of searching though PATH + /// Enforce a specific executable to use instead of searching through PATH /// for [`Self::executable_name`]. /// /// Unspecified by default. diff --git a/rerun_cpp/src/rerun/c/rerun.h b/rerun_cpp/src/rerun/c/rerun.h index 27bd783bf388..eb4806f7ebd2 100644 --- a/rerun_cpp/src/rerun/c/rerun.h +++ b/rerun_cpp/src/rerun/c/rerun.h @@ -150,7 +150,7 @@ typedef struct rr_spawn_options { /// Defaults to `rerun` if null. rr_string executable_name; - /// Enforce a specific executable to use instead of searching though PATH + /// Enforce a specific executable to use instead of searching through PATH /// for [`Self::executable_name`]. /// /// Unspecified by default. diff --git a/rerun_cpp/src/rerun/spawn_options.hpp b/rerun_cpp/src/rerun/spawn_options.hpp index f2ad1ce1eb9b..d2f2957bb508 100644 --- a/rerun_cpp/src/rerun/spawn_options.hpp +++ b/rerun_cpp/src/rerun/spawn_options.hpp @@ -51,7 +51,7 @@ namespace rerun { /// Defaults to `rerun` if unset. std::string_view executable_name = "rerun"; - /// Enforce a specific executable to use instead of searching though PATH + /// Enforce a specific executable to use instead of searching through PATH /// for `SpawnOptions::executable_name`. std::string_view executable_path; diff --git a/rerun_py/rerun_sdk/rerun/_spawn.py b/rerun_py/rerun_sdk/rerun/_spawn.py index 3f43daa91e50..3fbb375ea462 100644 --- a/rerun_py/rerun_sdk/rerun/_spawn.py +++ b/rerun_py/rerun_sdk/rerun/_spawn.py @@ -10,6 +10,8 @@ def _spawn_viewer( server_memory_limit: str = "1GiB", hide_welcome_screen: bool = False, detach_process: bool = True, + executable_name: str = "rerun", + executable_path: str | None = None, ) -> None: """ Internal helper to spawn a Rerun Viewer, listening on the given port. @@ -37,6 +39,16 @@ def _spawn_viewer( Hide the normal Rerun welcome screen. detach_process: Detach Rerun Viewer process from the application process. + executable_name: + Specifies the name of the Rerun executable. + You can omit the `.exe` suffix on Windows. + + Defaults to `rerun`. + executable_path: + Enforce a specific executable to use instead of searching + through PATH for `executable_name`. + + Unspecified by default. """ @@ -56,4 +68,6 @@ def _spawn_viewer( server_memory_limit=server_memory_limit, hide_welcome_screen=hide_welcome_screen, detach_process=detach_process, + executable_name=executable_name, + executable_path=executable_path, ) diff --git a/rerun_py/rerun_sdk/rerun/sinks.py b/rerun_py/rerun_sdk/rerun/sinks.py index f443e71c96b8..c3c550c6d9ba 100644 --- a/rerun_py/rerun_sdk/rerun/sinks.py +++ b/rerun_py/rerun_sdk/rerun/sinks.py @@ -462,6 +462,8 @@ def spawn( server_memory_limit: str = "1GiB", hide_welcome_screen: bool = False, detach_process: bool = True, + executable_name: str = "rerun", + executable_path: str | None = None, default_blueprint: BlueprintLike | None = None, recording: RecordingStream | None = None, ) -> None: @@ -494,6 +496,16 @@ def spawn( Hide the normal Rerun welcome screen. detach_process: Detach Rerun Viewer process from the application process. + executable_name: + Specifies the name of the Rerun executable. + You can omit the `.exe` suffix on Windows. + + Defaults to `rerun`. + executable_path: + Enforce a specific executable to use instead of searching + through PATH for `executable_name`. + + Unspecified by default. recording: Specifies the [`rerun.RecordingStream`][] to use if `connect = True`. If left unspecified, defaults to the current active data recording, if there is one. @@ -516,6 +528,8 @@ def spawn( server_memory_limit=server_memory_limit, hide_welcome_screen=hide_welcome_screen, detach_process=detach_process, + executable_name=executable_name, + executable_path=executable_path, ) if connect: From c621c52577dc586096ba7773d092b905551a3640 Mon Sep 17 00:00:00 2001 From: Michael Grupp Date: Tue, 10 Mar 2026 16:24:38 +0100 Subject: [PATCH 088/513] MCAP metadata layer: support multiple metadata records with same name An MCAP can have multiple metadata records with the same name. Hence, When converting to a Rerun component, we have to make sure that all key-value pairs from the different records end up in the same component. Fixes an error where the metadata layer would fail with such MCAPs. Source-Ref: f185f873e6f7311ac5708f0aa6f20b19ea1959e8 --- crates/store/re_mcap/src/layers/metadata.rs | 72 ++++++++++++++++++--- 1 file changed, 64 insertions(+), 8 deletions(-) diff --git a/crates/store/re_mcap/src/layers/metadata.rs b/crates/store/re_mcap/src/layers/metadata.rs index 0c046e55395e..83b3696dfd1b 100644 --- a/crates/store/re_mcap/src/layers/metadata.rs +++ b/crates/store/re_mcap/src/layers/metadata.rs @@ -1,3 +1,5 @@ +use std::collections::BTreeMap; + use re_chunk::{Chunk, EntityPath, RowId, TimePoint}; use re_sdk_types::{ Component as _, ComponentBatch as _, ComponentDescriptor, SerializedComponentBatch, components, @@ -31,7 +33,9 @@ impl Layer for McapMetadataLayer { return Ok(()); } - let mut batches: Vec = Vec::new(); + // We can encounter multiple metadata records with the same name. + // Collect all metadata records by name, merging key-value pairs from records with the same name. + let mut metadata_by_name: BTreeMap> = BTreeMap::new(); for index in &summary.metadata_indexes { let metadata = match mcap::read::metadata(mcap_bytes, index) { @@ -51,19 +55,30 @@ impl Layer for McapMetadataLayer { metadata.metadata.len(), ); - let pairs: Vec<_> = metadata - .metadata - .iter() + let entries = metadata_by_name.entry(metadata.name.clone()).or_default(); + for (key, value) in &metadata.metadata { + if entries.insert(key.clone(), value.clone()).is_some() { + re_log::warn_once!( + "Key '{key}' appears in multiple MCAP metadata records named '{}'", + metadata.name + ); + } + } + } + + let mut batches: Vec = Vec::new(); + for (name, entries) in metadata_by_name { + let pairs: Vec<_> = entries + .into_iter() .map(|(key, value)| datatypes::Utf8Pair { - first: key.clone().into(), - second: value.clone().into(), + first: key.into(), + second: value.into(), }) .collect(); let kv = components::KeyValuePairs(pairs); - batches.push(kv.try_serialized(ComponentDescriptor { archetype: Some(ARCHETYPE_NAME.into()), - component: metadata.name.into(), + component: name.into(), component_type: Some(components::KeyValuePairs::name()), })?); } @@ -137,4 +152,45 @@ mod tests { assert!(chunk.is_static()); assert_eq!(chunk.num_components(), 3); } + + /// Tests that two metadata records with the same name are merged into one component. + #[test] + fn test_duplicate_metadata_names() { + let buffer = { + const METADATA_NAME: &str = "duplicated_metadata_name"; + let cursor = io::Cursor::new(Vec::new()); + let mut writer = mcap::Writer::new(cursor).expect("failed to create writer"); + + let mut first = BTreeMap::new(); + first.insert("key_a".to_owned(), "value_a".to_owned()); + writer + .write_metadata(&mcap::records::Metadata { + name: METADATA_NAME.to_owned(), + metadata: first, + }) + .expect("failed to write metadata"); + + let mut second = BTreeMap::new(); + second.insert("key_b".to_owned(), "value_b".to_owned()); + writer + .write_metadata(&mcap::records::Metadata { + name: METADATA_NAME.to_owned(), + metadata: second, + }) + .expect("failed to write metadata"); + + writer.finish().expect("failed to finish writer"); + writer.into_inner().into_inner() + }; + + let chunks = run_metadata_layer(&buffer); + assert_eq!(chunks.len(), 1); + + let chunk = &chunks[0]; + assert_eq!( + chunk.num_components(), + 1, + "duplicates merged into one component" + ); + } } From 9a67723fe6ee4bbd2a73cb3d2402501008c7f7d2 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Tue, 10 Mar 2026 18:42:15 +0100 Subject: [PATCH 089/513] Support progressive ingest of the rrd manifest ### Related * Closes https://linear.app/rerun/issue/RR-3383/rrdmanifestindex-support-multi-manifest-setups ### What The backend already sends the RrdManifest in parts. Before we would wait until all parts Source-Ref: fb7acaf18eead75805ff675c9b62e3169a83923d --- crates/store/re_chunk_store/src/writes.rs | 11 +- .../src/data_meta_per_timeline.rs | 6 +- crates/store/re_entity_db/src/entity_db.rs | 39 ++- .../re_entity_db/src/rrd_manifest_index.rs | 77 ++++-- .../rrd_manifest_index/chunk_prioritizer.rs | 29 ++- .../sorted_temporal_chunks.rs | 13 +- .../re_entity_db/src/sorted_range_map.rs | 63 +++-- .../re_log_channel/src/data_source_message.rs | 11 +- .../src/rrd/footer/raw_rrd_manifest.rs | 233 ++++++++++-------- .../src/rrd/footer/rrd_manifest.rs | 12 +- .../re_redap_client/src/connection_client.rs | 72 +++--- crates/store/re_redap_client/src/grpc.rs | 70 ++++-- crates/top/rerun/src/commands/entrypoint.rs | 15 ++ crates/utils/re_format/src/plural.rs | 6 + crates/viewer/re_data_ui/src/entity_db_ui.rs | 12 +- crates/viewer/re_time_panel/src/time_panel.rs | 4 +- .../time_panel_partially_unloaded_chunks.png | 4 +- crates/viewer/re_viewer/src/app.rs | 11 +- .../src/cache/video_stream_cache.rs | 4 +- 19 files changed, 461 insertions(+), 231 deletions(-) diff --git a/crates/store/re_chunk_store/src/writes.rs b/crates/store/re_chunk_store/src/writes.rs index a224dc45dc45..e89882e983fc 100644 --- a/crates/store/re_chunk_store/src/writes.rs +++ b/crates/store/re_chunk_store/src/writes.rs @@ -104,7 +104,12 @@ impl ChunkStore { ) }), ); - *static_chunk_ids_per_entity = native_static_map.clone(); + for (entity_path, per_component) in native_static_map { + static_chunk_ids_per_entity + .entry(entity_path.clone()) + .or_default() + .extend(per_component.iter().map(|(&k, &v)| (k, v))); + } let native_temporal_map = rrd_manifest.temporal_map(); chunks_lineage.extend( @@ -261,7 +266,9 @@ impl ChunkStore { chunk.id() ); } else { - re_log::warn_once!("The same chunk was inserted twice (this has no effect)"); + re_log::warn_once!( + "[DEBUG] The same chunk was inserted twice (this has no effect)" + ); } } else { re_log::debug_once!("The same chunk was inserted twice (this has no effect)"); diff --git a/crates/store/re_entity_db/src/data_meta_per_timeline.rs b/crates/store/re_entity_db/src/data_meta_per_timeline.rs index 8de5748db278..a75c9443262d 100644 --- a/crates/store/re_entity_db/src/data_meta_per_timeline.rs +++ b/crates/store/re_entity_db/src/data_meta_per_timeline.rs @@ -374,7 +374,7 @@ mod tests { // Insert the manifest virtually. let event = store.insert_rrd_manifest(rrd_manifest.clone()).unwrap(); - manifest_index.append(rrd_manifest); + manifest_index.append(rrd_manifest).unwrap(); meta.on_events(&manifest_index, &store, &[event]); // Virtual rows should be counted. @@ -395,7 +395,7 @@ mod tests { // Load virtually first. let event = store.insert_rrd_manifest(rrd_manifest.clone()).unwrap(); - manifest_index.append(rrd_manifest); + manifest_index.append(rrd_manifest).unwrap(); manifest_index.on_events(&store, std::slice::from_ref(&event)); meta.on_events(&manifest_index, &store, std::slice::from_ref(&event)); @@ -456,7 +456,7 @@ mod tests { .unwrap(); let event = store.insert_rrd_manifest(rrd_manifest.clone()).unwrap(); - manifest_index.append(rrd_manifest); + manifest_index.append(rrd_manifest).unwrap(); meta.on_events(&manifest_index, &store, &[event]); assert_eq!(meta.row_count_for_timeline(tl.name()), 4); diff --git a/crates/store/re_entity_db/src/entity_db.rs b/crates/store/re_entity_db/src/entity_db.rs index 337af94824ff..ba3ed645f09d 100644 --- a/crates/store/re_entity_db/src/entity_db.rs +++ b/crates/store/re_entity_db/src/entity_db.rs @@ -72,10 +72,16 @@ pub enum RedapConnectionState { /// We are not connected to redap NotConnected, - DownloadingManifest, + /// We are downloading the manifest (no parts received yet). + DownloadingFirstManifestPart, + /// Connected but manifest is missing or failed to download. MissingManifest, + /// We have some manifest data, but more parts are still arriving. + PartialManifest, + + /// The full manifest has been received and we are ready to fetch chunks. Ready, } @@ -335,7 +341,7 @@ impl EntityDb { &mut self.rrd_manifest_index } - fn redap_connection_state(&self) -> RedapConnectionState { + pub fn redap_connection_state(&self) -> RedapConnectionState { // TODO(RR-3670): Check that connection is healthy and pick the correct icon to show the user based on that let is_connected_to_redap = self .data_source @@ -344,9 +350,13 @@ impl EntityDb { if is_connected_to_redap { if self.rrd_manifest_index.has_manifest() { - RedapConnectionState::Ready + if self.rrd_manifest_index.is_manifest_complete() { + RedapConnectionState::Ready + } else { + RedapConnectionState::PartialManifest + } } else if self.num_physical_chunks() == 0 { - RedapConnectionState::DownloadingManifest + RedapConnectionState::DownloadingFirstManifestPart } else { // This handles the case where we tried and failed to download the manifest, // but managed to download the data anyhow. @@ -361,13 +371,15 @@ impl EntityDb { pub fn can_fetch_chunks_from_redap(&self) -> bool { match self.redap_connection_state() { RedapConnectionState::NotConnected | RedapConnectionState::MissingManifest => false, - RedapConnectionState::DownloadingManifest | RedapConnectionState::Ready => true, + RedapConnectionState::DownloadingFirstManifestPart + | RedapConnectionState::PartialManifest + | RedapConnectionState::Ready => true, } } /// Are we currently in the process of downloading the RRD Manifest? - pub fn is_currently_downloading_manifest(&self) -> bool { - self.redap_connection_state() == RedapConnectionState::DownloadingManifest + pub fn is_downloading_first_part_of_manifest(&self) -> bool { + self.redap_connection_state() == RedapConnectionState::DownloadingFirstManifestPart } /// True if we're are currently waiting for necessary @@ -375,7 +387,8 @@ impl EntityDb { pub fn is_buffering(&self) -> bool { match self.redap_connection_state() { RedapConnectionState::NotConnected | RedapConnectionState::MissingManifest => false, - RedapConnectionState::DownloadingManifest => true, + RedapConnectionState::DownloadingFirstManifestPart + | RedapConnectionState::PartialManifest => true, RedapConnectionState::Ready => { if let Some(state) = self @@ -713,7 +726,6 @@ impl EntityDb { pub fn add_rrd_manifest_message(&mut self, rrd_manifest: Arc) { re_tracing::profile_function!(); - re_log::debug!("Received RrdManifest for {:?}", self.store_id()); let event = self .storage_engine @@ -730,7 +742,14 @@ impl EntityDb { } } - self.rrd_manifest_index.append(rrd_manifest); + if let Err(err) = self.rrd_manifest_index.append(rrd_manifest) { + re_log::error!("Failed to append RRD manifest: {err}"); + } + } + + /// Mark the RRD manifest as complete (all parts have been received). + pub fn mark_rrd_manifest_complete(&mut self) { + self.rrd_manifest_index.set_manifest_complete(); } /// Insert new data into the store. diff --git a/crates/store/re_entity_db/src/rrd_manifest_index.rs b/crates/store/re_entity_db/src/rrd_manifest_index.rs index eb2ff53ba671..f8ca3cf62fdc 100644 --- a/crates/store/re_entity_db/src/rrd_manifest_index.rs +++ b/crates/store/re_entity_db/src/rrd_manifest_index.rs @@ -8,7 +8,7 @@ use re_byte_size::{MemUsageTree, MemUsageTreeCapture}; use re_chunk::{ChunkId, EntityPath, Timeline, TimelineName}; use re_chunk_store::{ChunkStore, ChunkStoreDiff, ChunkStoreEvent}; use re_log::debug_assert; -use re_log_encoding::RrdManifest; +use re_log_encoding::{CodecResult, RrdManifest}; use re_log_types::{AbsoluteTimeRange, StoreKind, TimelinePoint}; use re_mutex::Mutex; @@ -137,16 +137,18 @@ impl re_byte_size::SizeBytes for LoadedRanges { /// A secondary index that keeps track of which chunks have been loaded into memory. /// /// This is constructed from an [`RrdManifest`], which is what the server sends to the client/viewer. -// -// TODO(RR-3383): support multiple manifests per index. +/// The manifest may be received in parts and concatenated together. #[derive(Default)] #[cfg_attr(feature = "testing", derive(Clone))] pub struct RrdManifestIndex { - /// The raw manifest. + /// The raw manifest (accumulated from possibly multiple parts). /// /// This is known ahead-of-time for _some_ data sources. manifest: Option>, + /// True once all parts of the manifest have been received. + manifest_complete: bool, + /// These are the chunks known to exist in the data source (e.g. remote server). /// /// The chunk store may split and/or merge root chunks, producing _derived_ chunks. @@ -181,33 +183,33 @@ impl std::fmt::Debug for RrdManifestIndex { } impl RrdManifestIndex { - pub fn append(&mut self, manifest: Arc) { + pub fn append(&mut self, delta: Arc) -> CodecResult<()> { re_tracing::profile_function!(); - if self.manifest.is_some() { - re_log::warn!( - "Received a second RRD manifest schema for the same recording. This is not yet supported." - ); - } + self.update_timeline_stats(&delta); + self.update_entity_tree(&delta); + self.update_entity_static_data(&delta); + self.chunk_prioritizer.on_rrd_manifest(&delta); - self.full_uncompressed_size = manifest.col_chunk_byte_size_uncompressed().iter().sum(); + self.full_uncompressed_size += delta.col_chunk_byte_size_uncompressed().iter().sum::(); - self.update_timeline_stats(&manifest); - self.update_entity_tree(&manifest); - self.update_entity_static_data(&manifest); - self.chunk_prioritizer.on_rrd_manifest(&manifest); + self.loaded_ranges = None; // invalidate and recompute - self.sorted_chunks - .update(&self.entity_tree, manifest.temporal_map()); + let row_offset = self + .manifest + .as_ref() + .map_or(0, |manifest| manifest.data().num_rows()); - for (row_idx, (&root_chunk_id, entity_path)) in - izip!(manifest.col_chunk_ids(), manifest.col_chunk_entity_path()).enumerate() + for (delta_row_idx, (&root_chunk_id, entity_path)) in + izip!(delta.col_chunk_ids(), delta.col_chunk_entity_path()).enumerate() { - self.root_chunks - .insert(root_chunk_id, RootChunkInfo::new(entity_path, row_idx)); + self.root_chunks.insert( + root_chunk_id, + RootChunkInfo::new(entity_path, row_offset + delta_row_idx), + ); } - for timelines in manifest.temporal_map().values() { + for timelines in delta.temporal_map().values() { for (&timeline, comps) in timelines { for chunks in comps.values() { for (&chunk_id, entry) in chunks { @@ -232,7 +234,18 @@ impl RrdManifestIndex { } } - self.manifest = Some(manifest); + let new_full_manifest = if let Some(existing) = self.manifest.take() { + Arc::new(RrdManifest::concat(&existing, &delta)?) + } else { + delta + }; + + self.sorted_chunks = + SortedTemporalChunks::new(&self.entity_tree, new_full_manifest.temporal_map()); + + self.manifest = Some(new_full_manifest); + + Ok(()) } /// Iterate over all chunks in the manifest. @@ -384,11 +397,25 @@ impl RrdManifestIndex { } /// False for recordings streamed from SDK via proxy + /// + /// This is true as soon as the first piece of the manifest is available. pub fn has_manifest(&self) -> bool { self.manifest.is_some() } - /// The full manifest, if known. + /// Have all parts of the manifest been received? + pub fn is_manifest_complete(&self) -> bool { + self.manifest_complete + } + + /// Mark the manifest as complete (all parts have been received). + pub fn set_manifest_complete(&mut self) { + self.manifest_complete = true; + } + + /// The manifest as it currently stands. + /// + /// More pieces of it may still arrive unless [`Self::is_manifest_complete`] is true. pub fn manifest(&self) -> Option<&RrdManifest> { self.manifest.as_deref() } @@ -671,6 +698,7 @@ impl re_byte_size::SizeBytes for RrdManifestIndex { chunk_prioritizer, timelines, full_uncompressed_size: _, + manifest_complete: _, } = self; entity_has_static_data.heap_size_bytes() @@ -700,6 +728,7 @@ impl MemUsageTreeCapture for RrdManifestIndex { chunk_prioritizer, timelines, full_uncompressed_size: _, + manifest_complete: _, } = self; let mut node = re_byte_size::MemUsageNode::new(); diff --git a/crates/store/re_entity_db/src/rrd_manifest_index/chunk_prioritizer.rs b/crates/store/re_entity_db/src/rrd_manifest_index/chunk_prioritizer.rs index b1127125b51b..9490307ae32d 100644 --- a/crates/store/re_entity_db/src/rrd_manifest_index/chunk_prioritizer.rs +++ b/crates/store/re_entity_db/src/rrd_manifest_index/chunk_prioritizer.rs @@ -432,13 +432,12 @@ impl re_byte_size::SizeBytes for ChunkPrioritizer { } impl ChunkPrioritizer { - pub fn on_rrd_manifest(&mut self, manifest: &RrdManifest) { - self.update_static_chunks(manifest); - self.update_chunk_intervals(manifest); - self.update_high_priority_chunks(manifest); + pub fn on_rrd_manifest(&mut self, delta: &RrdManifest) { + self.update_static_chunks(delta); + self.update_chunk_intervals(delta); + self.update_high_priority_chunks(delta); - self.component_paths_from_root_id.clear(); - for (entity, per_component) in manifest.static_map() { + for (entity, per_component) in delta.static_map() { for (component, chunk) in per_component { self.component_paths_from_root_id .entry(*chunk) @@ -450,7 +449,7 @@ impl ChunkPrioritizer { } } - for (entity, per_timeline) in manifest.temporal_map() { + for (entity, per_timeline) in delta.temporal_map() { for per_component in per_timeline.values() { for (component, chunks) in per_component { for chunk in chunks.keys() { @@ -510,10 +509,19 @@ impl ChunkPrioritizer { // parts of a hierarchy, and not all of the transform are required to be // available at each time point. // More here: https://linear.app/rerun/issue/RR-3441/required-transform-frames-arent-always-loaded - self.high_priority_chunks = Self::find_chunks_with_component_prefix( + let new_chunks = Self::find_chunks_with_component_prefix( manifest, "Transform3D:", // Hard-coding this here is VERY hacky, but I want to ship MVP ); + for (timeline, mut chunks) in new_chunks.temporal_chunks { + let existing = self + .high_priority_chunks + .temporal_chunks + .entry(timeline) + .or_default(); + existing.append(&mut chunks); + existing.sort_by_key(|chunk| chunk.time_range.min); + } } fn update_static_chunks(&mut self, manifest: &RrdManifest) { @@ -539,10 +547,11 @@ impl ChunkPrioritizer { } } - self.root_chunk_intervals.clear(); for (timeline, chunks) in per_timeline_chunks { self.root_chunk_intervals - .insert(timeline, SortedRangeMap::new(chunks)); + .entry(timeline) + .or_default() + .extend(chunks); } } diff --git a/crates/store/re_entity_db/src/rrd_manifest_index/sorted_temporal_chunks.rs b/crates/store/re_entity_db/src/rrd_manifest_index/sorted_temporal_chunks.rs index 38edd7abb0d2..7e0578ab387d 100644 --- a/crates/store/re_entity_db/src/rrd_manifest_index/sorted_temporal_chunks.rs +++ b/crates/store/re_entity_db/src/rrd_manifest_index/sorted_temporal_chunks.rs @@ -121,10 +121,21 @@ impl re_byte_size::SizeBytes for SortedTemporalChunks { } impl SortedTemporalChunks { + pub fn new( + entity_tree: &crate::EntityTree, + native_temporal_map: &re_log_encoding::RrdManifestTemporalMap, + ) -> Self { + re_tracing::profile_function!(); + let mut slf = Self::default(); + slf.update(entity_tree, native_temporal_map); + slf + } + /// Update the cache from the manifest's temporal map and entity tree. /// /// Should be called when a new rrd manifest is appended. - pub fn update( + // TODO(emilk): handle incremental ingestion + fn update( &mut self, entity_tree: &crate::EntityTree, native_temporal_map: &re_log_encoding::RrdManifestTemporalMap, diff --git a/crates/store/re_entity_db/src/sorted_range_map.rs b/crates/store/re_entity_db/src/sorted_range_map.rs index 1c09cffe07fa..a17c98bb6ea2 100644 --- a/crates/store/re_entity_db/src/sorted_range_map.rs +++ b/crates/store/re_entity_db/src/sorted_range_map.rs @@ -3,7 +3,7 @@ use std::ops::RangeInclusive; /// A sorted, immutable collection of inclusive ranges mapped to values. /// /// Supports O(log N) queries for overlapping ranges. -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone)] pub struct SortedRangeMap { /// Entries sorted by `range.start()`. entries: Vec<(RangeInclusive, V)>, @@ -13,6 +13,15 @@ pub struct SortedRangeMap { max_end: Vec, } +impl Default for SortedRangeMap { + fn default() -> Self { + Self { + entries: Vec::new(), + max_end: Vec::new(), + } + } +} + impl re_byte_size::SizeBytes for SortedRangeMap where K: re_byte_size::SizeBytes + Ord + Copy, @@ -25,24 +34,6 @@ where } impl SortedRangeMap { - pub fn new(mut entries: Vec<(RangeInclusive, V)>) -> Self { - entries.sort_by(|a, b| a.0.start().cmp(b.0.start())); - - let mut max_end = Vec::with_capacity(entries.len()); - let mut running_max = None::; - - for (range, _) in &entries { - let new_max = match running_max { - Some(m) => m.max(*range.end()), - None => *range.end(), - }; - running_max = Some(new_max); - max_end.push(new_max); - } - - Self { entries, max_end } - } - /// Returns an iterator over all (range, value) pairs that overlap with `query`. /// Results are yielded in order of `range.start()` (ascending). /// @@ -65,6 +56,27 @@ impl SortedRangeMap { self.max_end.partition_point(|max| *max < *query.start()) } + /// Append new entries and re-sort. + pub fn extend(&mut self, new_entries: Vec<(RangeInclusive, V)>) { + self.entries.extend(new_entries); + self.entries.sort_by(|a, b| a.0.start().cmp(b.0.start())); + self.update_max_end(); + } + + fn update_max_end(&mut self) { + self.max_end.clear(); + self.max_end.reserve_exact(self.entries.len()); + let mut running_max = None::; + for (range, _) in &self.entries { + let new_max = match running_max { + Some(m) => m.max(*range.end()), + None => *range.end(), + }; + running_max = Some(new_max); + self.max_end.push(new_max); + } + } + #[inline] #[cfg_attr(not(test), expect(dead_code))] // only used in tests pub fn len(&self) -> usize { @@ -122,6 +134,19 @@ impl std::iter::FusedIterator for OverlapIter<'_, K, V> {} mod tests { use super::*; + impl SortedRangeMap { + pub fn new(mut entries: Vec<(RangeInclusive, V)>) -> Self { + entries.sort_by(|a, b| a.0.start().cmp(b.0.start())); + + let mut slf = Self { + entries, + max_end: vec![], + }; + slf.update_max_end(); + slf + } + } + #[test] fn test_basic_overlap() { let map = SortedRangeMap::new(vec![ diff --git a/crates/store/re_log_channel/src/data_source_message.rs b/crates/store/re_log_channel/src/data_source_message.rs index 960b3eac9273..c05c0dbbe7a8 100644 --- a/crates/store/re_log_channel/src/data_source_message.rs +++ b/crates/store/re_log_channel/src/data_source_message.rs @@ -11,11 +11,15 @@ use re_log_types::{LogMsg, StoreId, TableMsg, impl_into_enum}; /// May contain limited UI commands for instrumenting the state of the receiving end. #[derive(Clone, Debug)] pub enum DataSourceMessage { - /// The index of all the chunks in a recording. + /// A piece of the index of all the chunks in a recording. /// /// Some sources may send this, others may not. + /// There may be one or more of these, followed by [`Self::RrdManifestComplete`]. RrdManifest(StoreId, Arc), + /// All parts of the RRD manifest have been sent. + RrdManifestComplete(StoreId), + /// See [`LogMsg`]. LogMsg(LogMsg), @@ -34,7 +38,7 @@ impl re_byte_size::SizeBytes for DataSourceMessage { Self::RrdManifest(_, manifest) => manifest.heap_size_bytes(), Self::LogMsg(log_msg) => log_msg.heap_size_bytes(), Self::TableMsg(table_msg) => table_msg.heap_size_bytes(), - Self::UiCommand(_) => 0, + Self::RrdManifestComplete(_) | Self::UiCommand(_) => 0, } } } @@ -48,6 +52,7 @@ impl DataSourceMessage { pub fn variant_name(&self) -> &'static str { match self { Self::RrdManifest { .. } => "RrdManifest", + Self::RrdManifestComplete(_) => "RrdManifestComplete", Self::LogMsg(_) => "LogMsg", Self::TableMsg(_) => "TableMsg", Self::UiCommand(_) => "UiCommand", @@ -59,7 +64,7 @@ impl DataSourceMessage { match self { Self::LogMsg(log_msg) => log_msg.insert_arrow_record_batch_metadata(key, value), Self::TableMsg(table_msg) => table_msg.insert_arrow_record_batch_metadata(key, value), - Self::RrdManifest { .. } | Self::UiCommand(_) => { + Self::RrdManifest { .. } | Self::RrdManifestComplete(_) | Self::UiCommand(_) => { // Not everything needs latency tracking } } diff --git a/crates/store/re_log_encoding/src/rrd/footer/raw_rrd_manifest.rs b/crates/store/re_log_encoding/src/rrd/footer/raw_rrd_manifest.rs index e65c41c99b79..3f8a12f6542e 100644 --- a/crates/store/re_log_encoding/src/rrd/footer/raw_rrd_manifest.rs +++ b/crates/store/re_log_encoding/src/rrd/footer/raw_rrd_manifest.rs @@ -1,8 +1,11 @@ use std::collections::{BTreeMap, HashMap}; -use arrow::array::{BinaryArray, BooleanArray, FixedSizeBinaryArray, StringArray, UInt64Array}; use arrow::buffer::NullBuffer; use arrow::datatypes::Field; +use arrow::{ + array::{BinaryArray, BooleanArray, FixedSizeBinaryArray, StringArray, UInt64Array}, + error::ArrowError, +}; use itertools::Itertools as _; use re_chunk::external::nohash_hasher::IntMap; use re_chunk::external::re_byte_size; @@ -11,7 +14,7 @@ use re_log_types::external::re_tuid::Tuid; use re_log_types::{AbsoluteTimeRange, EntityPath, StoreId, TimeType}; use re_types_core::ComponentDescriptor; -use crate::{CodecResult, Decodable as _, StreamFooterEntry, ToApplication as _}; +use crate::{CodecError, CodecResult, Decodable as _, StreamFooterEntry, ToApplication as _}; /// The payload found in [`super::RrdFooter`]s. /// @@ -233,6 +236,44 @@ impl std::fmt::Display for RrdManifestSha256 { } impl RawRrdManifest { + /// Concatenate two manifests by appending the rows of `other` onto `self`. + /// + /// Both manifests must be for the same recording (same `store_id`). + /// The sorbet schemas are merged, and the data `RecordBatch`es are concatenated. + /// + /// This is used when the server sends a manifest in multiple parts. + pub fn concat(self, other: Self) -> Result { + re_tracing::profile_function!(); + + re_log::debug_assert_eq!(self.store_id, other.store_id); + + let (sorbet_schema, sorbet_schema_sha256) = + if self.sorbet_schema_sha256 == other.sorbet_schema_sha256 { + (self.sorbet_schema, self.sorbet_schema_sha256) + } else { + return Err(ArrowError::SchemaError( + "Mismatching sorbet recording schemas in RawRrdManifest::concat".to_owned(), + )); + }; + + if self.data.schema() != other.data.schema() { + re_log::debug!( + "Different schemas in the RrdManifest ({} columns in existing, {} in the new part)", + self.data.num_columns(), + other.data.num_columns(), + ); + } + + let data = arrow::compute::concat_batches(&self.data.schema(), &[self.data, other.data])?; + + Ok(Self { + store_id: self.store_id, + sorbet_schema, + sorbet_schema_sha256, + data, + }) + } + /// High-level helper to parse [`RawRrdManifest`]s from raw RRD bytes. /// /// This does not decode all the data, but rather goes straight to the RRD footer (if any). @@ -253,7 +294,7 @@ impl RawRrdManifest { Ok(footer) => footer, // That was in fact _not_ a footer. - Err(crate::CodecError::FrameDecoding(_)) => return Ok(vec![]), + Err(CodecError::FrameDecoding(_)) => return Ok(vec![]), err @ Err(_) => err?, }; @@ -271,7 +312,7 @@ impl RawRrdManifest { let rrd_footer_byte_span = rrd_footer_byte_span .try_cast::() .ok_or_else(|| { - crate::CodecError::FrameDecoding( + CodecError::FrameDecoding( "RRD footer too large for native bit width".to_owned(), ) })? @@ -281,7 +322,7 @@ impl RawRrdManifest { let crc = crate::StreamFooter::compute_crc(rrd_footer_bytes); if crc != crc_excluding_header { - return Err(crate::CodecError::CrcMismatch { + return Err(CodecError::CrcMismatch { expected: crc_excluding_header, got: crc, }); @@ -345,7 +386,7 @@ impl RawRrdManifest { /// Computes the sha256 hash of the manifest's data, which can be used as a unique ID. // // TODO(cmc): very expensive, should be cached somewhere. - pub fn compute_sha256(&self) -> Result { + pub fn compute_sha256(&self) -> Result { re_tracing::profile_function!(); let data_ipc = { @@ -385,13 +426,11 @@ impl RawRrdManifest { .map(|(f, c)| { c.downcast_array_ref::() .ok_or_else(|| { - crate::CodecError::ArrowDeserialization( - arrow::error::ArrowError::SchemaError(format!( - "'{}' should be a BooleanArray, but it's a {} instead", - f.name(), - c.data_type(), - )), - ) + CodecError::ArrowDeserialization(ArrowError::SchemaError(format!( + "'{}' should be a BooleanArray, but it's a {} instead", + f.name(), + c.data_type(), + ))) }) .map(|c| (f, c)) }) @@ -411,7 +450,7 @@ impl RawRrdManifest { } let Some(component) = f.metadata().get("rerun:component") else { - return Err(crate::CodecError::from(ChunkError::Malformed { + return Err(CodecError::from(ChunkError::Malformed { reason: format!( "column '{}' is missing rerun:component metadata", f.name() @@ -492,7 +531,7 @@ impl RawRrdManifest { && f.name().ends_with(":start") && f.metadata().get("rerun:component") == Some(component) }) else { - return Err(crate::CodecError::from(ChunkError::Malformed { + return Err(CodecError::from(ChunkError::Malformed { reason: format!("start index is missing for {component}"), })); }; @@ -501,7 +540,7 @@ impl RawRrdManifest { && f.name().ends_with(":end") && f.metadata().get("rerun:component") == Some(component) }) else { - return Err(crate::CodecError::from(ChunkError::Malformed { + return Err(CodecError::from(ChunkError::Malformed { reason: format!("end index is missing for {component}"), })); }; @@ -512,25 +551,23 @@ impl RawRrdManifest { && f.metadata().get("rerun:component") == Some(component) }) else { - return Err(crate::CodecError::from(ChunkError::Malformed { + return Err(CodecError::from(ChunkError::Malformed { reason: format!("num_rows index is missing for {component}"), })); }; - let (time_type, col_start_raw) = TimeType::from_arrow_array(col_start) - .map_err(crate::CodecError::ArrowDeserialization)?; - let (_, col_end_raw) = TimeType::from_arrow_array(col_end) - .map_err(crate::CodecError::ArrowDeserialization)?; + let (time_type, col_start_raw) = + TimeType::from_arrow_array(col_start).map_err(CodecError::ArrowDeserialization)?; + let (_, col_end_raw) = + TimeType::from_arrow_array(col_end).map_err(CodecError::ArrowDeserialization)?; let col_num_rows_raw: &[u64] = col_num_rows .downcast_array_ref::() .ok_or_else(|| { - crate::CodecError::ArrowDeserialization(arrow::error::ArrowError::SchemaError( - format!( - "'{}' should be a BooleanArray, but it's a {} instead", - field_num_rows.name(), - col_num_rows.data_type(), - ), - )) + CodecError::ArrowDeserialization(ArrowError::SchemaError(format!( + "'{}' should be a BooleanArray, but it's a {} instead", + field_num_rows.name(), + col_num_rows.data_type(), + ))) })? .values(); @@ -639,7 +676,7 @@ impl PartialEq for RawRrdManifest { impl RawRrdManifest { pub fn compute_sorbet_schema_sha256( schema: &arrow::datatypes::Schema, - ) -> Result<[u8; 32], arrow::error::ArrowError> { + ) -> Result<[u8; 32], ArrowError> { let schema = { // Sort and remove top-level metadata before hashing. let mut fields = schema.fields().to_vec(); @@ -775,7 +812,7 @@ impl RawRrdManifest { "has_static_data" => { if field.data_type() != Self::field_chunk_is_static().data_type() { - return Err(crate::CodecError::from(ChunkError::Malformed { + return Err(CodecError::from(ChunkError::Malformed { reason: format!( "field '{}' should be {} but is actually {}", field.name(), @@ -788,7 +825,7 @@ impl RawRrdManifest { "num_rows" => { if field.data_type() != Self::field_chunk_num_rows().data_type() { - return Err(crate::CodecError::from(ChunkError::Malformed { + return Err(CodecError::from(ChunkError::Malformed { reason: format!( "field '{}' should be {} but is actually {}", field.name(), @@ -800,7 +837,7 @@ impl RawRrdManifest { } suffix => { - return Err(crate::CodecError::from(ChunkError::Malformed { + return Err(CodecError::from(ChunkError::Malformed { reason: format!( "field '{}' has invalid suffix '{suffix}'", field.name(), @@ -823,7 +860,7 @@ impl RawRrdManifest { name if Self::COMMON_IMPL_SPECIFIC_FIELDS.contains(&name) => {} name => { - return Err(crate::CodecError::from(ChunkError::Malformed { + return Err(CodecError::from(ChunkError::Malformed { reason: format!( "unexpected field '{name}' should not be present in an RRD manifest", ), @@ -850,7 +887,7 @@ impl RawRrdManifest { .schema_ref() .field_with_name(&format!("{prefix}:{counterpart}")) .map_err(|_err| { - crate::CodecError::from(ChunkError::Malformed { + CodecError::from(ChunkError::Malformed { reason: format!( "field '{}' does not have matching `:{counterpart}` field", field.name() @@ -864,7 +901,7 @@ impl RawRrdManifest { | arrow::datatypes::DataType::Duration(_) => {} datatype => { - return Err(crate::CodecError::from(ChunkError::Malformed { + return Err(CodecError::from(ChunkError::Malformed { reason: format!( "field '{}' is {datatype} which is not a supported index datatype", field.name(), @@ -874,7 +911,7 @@ impl RawRrdManifest { } if field.data_type() != field_counterpart.data_type() { - return Err(crate::CodecError::from(ChunkError::Malformed { + return Err(CodecError::from(ChunkError::Malformed { reason: format!( "field '{}' is {} but field '{}' is {}", field.name(), @@ -897,7 +934,7 @@ impl RawRrdManifest { .schema_ref() .field_with_name(&format!("{prefix}:num_rows")) .map_err(|_err| { - crate::CodecError::from(ChunkError::Malformed { + CodecError::from(ChunkError::Malformed { reason: format!( "field '{}' does not have matching `:num_rows` field", field.name() @@ -908,7 +945,7 @@ impl RawRrdManifest { match field_num_rows.data_type() { arrow::datatypes::DataType::UInt64 => {} datatype => { - return Err(crate::CodecError::from(ChunkError::Malformed { + return Err(CodecError::from(ChunkError::Malformed { reason: format!( "field '{}' is {datatype} while it should be UInt64Array", field_num_rows.name(), @@ -953,7 +990,7 @@ impl RawRrdManifest { for column in &sorbet_columns { let md = column.metadata(); let Some(component) = md.get("rerun:component") else { - return Err(crate::CodecError::from(ChunkError::Malformed { + return Err(CodecError::from(ChunkError::Malformed { reason: format!( "column '{}' is missing rerun:component metadata", column.name() @@ -979,7 +1016,7 @@ impl RawRrdManifest { .schema_ref() .field_with_name(&column_name) .map_err(|_err| { - crate::CodecError::from(ChunkError::Malformed { + CodecError::from(ChunkError::Malformed { reason: format!("static index '{column_name}' is missing"), }) })?; @@ -1004,7 +1041,7 @@ impl RawRrdManifest { for suffix in ["start", "end"] { let field = rrd_manifest_fields.remove(&format!("{sorbet_index_name_normalized}:{suffix}")) .ok_or_else(|| { - crate::CodecError::from(ChunkError::Malformed { + CodecError::from(ChunkError::Malformed { reason: format!( "global index '{sorbet_index}' does not have matching `:{suffix}` field" ), @@ -1012,7 +1049,7 @@ impl RawRrdManifest { })?; if sorbet_index.data_type() != field.data_type() { - return Err(crate::CodecError::from(ChunkError::Malformed { + return Err(CodecError::from(ChunkError::Malformed { reason: format!( "global index '{}' is {} but '{}' is {}", sorbet_index.name(), @@ -1029,7 +1066,7 @@ impl RawRrdManifest { let md = sorbet_column.metadata(); let Some(component) = md.get("rerun:component") else { - return Err(crate::CodecError::from(ChunkError::Malformed { + return Err(CodecError::from(ChunkError::Malformed { reason: format!( "column '{}' is missing rerun:component metadata", sorbet_column.name() @@ -1069,7 +1106,7 @@ impl RawRrdManifest { }; if sorbet_index.data_type() != field.data_type() { - return Err(crate::CodecError::from(ChunkError::Malformed { + return Err(CodecError::from(ChunkError::Malformed { reason: format!( "local index '{}' is {} but '{}' is {}", sorbet_index.name(), @@ -1084,7 +1121,7 @@ impl RawRrdManifest { } if !rrd_manifest_fields.is_empty() { - return Err(crate::CodecError::from(ChunkError::Malformed { + return Err(CodecError::from(ChunkError::Malformed { reason: format!( "detected dangling indexes (present in manifest but not in Sorbet schema): {:?}", rrd_manifest_fields.keys() @@ -1098,11 +1135,11 @@ impl RawRrdManifest { /// Costly. fn check_sorbet_schema_sha256_is_correct(&self) -> CodecResult<()> { let expected_sorbet_schema_sha256 = Self::compute_sorbet_schema_sha256(&self.sorbet_schema) - .map_err(crate::CodecError::ArrowDeserialization)?; + .map_err(CodecError::ArrowDeserialization)?; if self.sorbet_schema_sha256 != expected_sorbet_schema_sha256 { - return Err(crate::CodecError::ArrowDeserialization( - arrow::error::ArrowError::SchemaError(format!( + return Err(CodecError::ArrowDeserialization(ArrowError::SchemaError( + format!( "invalid schema hash: expected {} but got {}", expected_sorbet_schema_sha256 .iter() @@ -1112,8 +1149,8 @@ impl RawRrdManifest { .iter() .map(|b| format!("{b:02x}")) .collect::(), - )), - )); + ), + ))); } Ok(()) } @@ -1306,15 +1343,15 @@ impl RawRrdManifest { self.data .column_by_name(name) .ok_or_else(|| { - crate::CodecError::ArrowDeserialization(arrow::error::ArrowError::SchemaError( - format!("cannot read column: '{name}' is missing from batch",), - )) + CodecError::ArrowDeserialization(ArrowError::SchemaError(format!( + "cannot read column: '{name}' is missing from batch", + ))) })? .downcast_array_ref::() .ok_or_else(|| { - crate::CodecError::ArrowDeserialization(arrow::error::ArrowError::SchemaError( - format!("cannot downcast column: '{name}' is not a StringArray",), - )) + CodecError::ArrowDeserialization(ArrowError::SchemaError(format!( + "cannot downcast column: '{name}' is not a StringArray", + ))) }) } @@ -1334,15 +1371,15 @@ impl RawRrdManifest { self.data .column_by_name(name) .ok_or_else(|| { - crate::CodecError::ArrowDeserialization(arrow::error::ArrowError::SchemaError( - format!("cannot read column: '{name}' is missing from batch",), - )) + CodecError::ArrowDeserialization(ArrowError::SchemaError(format!( + "cannot read column: '{name}' is missing from batch", + ))) })? .downcast_array_ref::() .ok_or_else(|| { - crate::CodecError::ArrowDeserialization(arrow::error::ArrowError::SchemaError( - format!("cannot downcast column: '{name}' is not a FixedSizeBinaryArray",), - )) + CodecError::ArrowDeserialization(ArrowError::SchemaError(format!( + "cannot downcast column: '{name}' is not a FixedSizeBinaryArray", + ))) }) } @@ -1376,15 +1413,15 @@ impl RawRrdManifest { self.data .column_by_name(name) .ok_or_else(|| { - crate::CodecError::ArrowDeserialization(arrow::error::ArrowError::SchemaError( - format!("cannot read column: '{name}' is missing from batch",), - )) + CodecError::ArrowDeserialization(ArrowError::SchemaError(format!( + "cannot read column: '{name}' is missing from batch", + ))) })? .downcast_array_ref::() .ok_or_else(|| { - crate::CodecError::ArrowDeserialization(arrow::error::ArrowError::SchemaError( - format!("cannot downcast column: '{name}' is not a BooleanArray",), - )) + CodecError::ArrowDeserialization(ArrowError::SchemaError(format!( + "cannot downcast column: '{name}' is not a BooleanArray", + ))) }) } @@ -1402,15 +1439,15 @@ impl RawRrdManifest { self.data .column_by_name(name) .ok_or_else(|| { - crate::CodecError::ArrowDeserialization(arrow::error::ArrowError::SchemaError( - format!("cannot read column: '{name}' is missing from batch",), - )) + CodecError::ArrowDeserialization(ArrowError::SchemaError(format!( + "cannot read column: '{name}' is missing from batch", + ))) })? .downcast_array_ref::() .ok_or_else(|| { - crate::CodecError::ArrowDeserialization(arrow::error::ArrowError::SchemaError( - format!("cannot downcast column: '{name}' is not a UInt64Array",), - )) + CodecError::ArrowDeserialization(ArrowError::SchemaError(format!( + "cannot downcast column: '{name}' is not a UInt64Array", + ))) }) } @@ -1428,15 +1465,15 @@ impl RawRrdManifest { self.data .column_by_name(name) .ok_or_else(|| { - crate::CodecError::ArrowDeserialization(arrow::error::ArrowError::SchemaError( - format!("cannot read column: '{name}' is missing from batch",), - )) + CodecError::ArrowDeserialization(ArrowError::SchemaError(format!( + "cannot read column: '{name}' is missing from batch", + ))) })? .downcast_array_ref::() .ok_or_else(|| { - crate::CodecError::ArrowDeserialization(arrow::error::ArrowError::SchemaError( - format!("cannot downcast column: '{name}' is not a UInt64Array",), - )) + CodecError::ArrowDeserialization(ArrowError::SchemaError(format!( + "cannot downcast column: '{name}' is not a UInt64Array", + ))) }) } @@ -1456,15 +1493,15 @@ impl RawRrdManifest { self.data .column_by_name(name) .ok_or_else(|| { - crate::CodecError::ArrowDeserialization(arrow::error::ArrowError::SchemaError( - format!("cannot read column: '{name}' is missing from batch",), - )) + CodecError::ArrowDeserialization(ArrowError::SchemaError(format!( + "cannot read column: '{name}' is missing from batch", + ))) })? .downcast_array_ref::() .ok_or_else(|| { - crate::CodecError::ArrowDeserialization(arrow::error::ArrowError::SchemaError( - format!("cannot downcast column: '{name}' is not a UInt64Array",), - )) + CodecError::ArrowDeserialization(ArrowError::SchemaError(format!( + "cannot downcast column: '{name}' is not a UInt64Array", + ))) }) } @@ -1486,15 +1523,15 @@ impl RawRrdManifest { self.data .column_by_name(name) .ok_or_else(|| { - crate::CodecError::ArrowDeserialization(arrow::error::ArrowError::SchemaError( - format!("cannot read column: '{name}' is missing from batch",), - )) + CodecError::ArrowDeserialization(ArrowError::SchemaError(format!( + "cannot read column: '{name}' is missing from batch", + ))) })? .downcast_array_ref::() .ok_or_else(|| { - crate::CodecError::ArrowDeserialization(arrow::error::ArrowError::SchemaError( - format!("cannot downcast column: '{name}' is not a UInt64Array",), - )) + CodecError::ArrowDeserialization(ArrowError::SchemaError(format!( + "cannot downcast column: '{name}' is not a UInt64Array", + ))) }) } @@ -1517,15 +1554,15 @@ impl RawRrdManifest { self.data .column_by_name(name) .ok_or_else(|| { - crate::CodecError::ArrowDeserialization(arrow::error::ArrowError::SchemaError( - format!("cannot read column: '{name}' is missing from batch",), - )) + CodecError::ArrowDeserialization(ArrowError::SchemaError(format!( + "cannot read column: '{name}' is missing from batch", + ))) })? .downcast_array_ref::() .ok_or_else(|| { - crate::CodecError::ArrowDeserialization(arrow::error::ArrowError::SchemaError( - format!("cannot downcast column: '{name}' is not a BinaryArray"), - )) + CodecError::ArrowDeserialization(ArrowError::SchemaError(format!( + "cannot downcast column: '{name}' is not a BinaryArray" + ))) }) } } diff --git a/crates/store/re_log_encoding/src/rrd/footer/rrd_manifest.rs b/crates/store/re_log_encoding/src/rrd/footer/rrd_manifest.rs index f079cfdc088e..9f63dea418a2 100644 --- a/crates/store/re_log_encoding/src/rrd/footer/rrd_manifest.rs +++ b/crates/store/re_log_encoding/src/rrd/footer/rrd_manifest.rs @@ -7,7 +7,7 @@ use re_chunk::{ChunkId, EntityPath}; use re_log_types::StoreId; use super::{RawRrdManifest, RrdManifestSha256, RrdManifestStaticMap, RrdManifestTemporalMap}; -use crate::CodecResult; +use crate::{CodecError, CodecResult}; /// A pre-validated and parsed [`RawRrdManifest`]. /// @@ -166,6 +166,16 @@ impl RrdManifest { }) } + pub fn concat(a: &Self, b: &Self) -> CodecResult { + re_tracing::profile_function!(); + let a = a.raw.clone(); + let b = b.raw.clone(); + let combined = a.concat(b).map_err(|err| { + CodecError::FrameDecoding(format!("Failed to concatenate RRD manifests: {err}")) + })?; + Self::try_new(combined) + } + /// Builds an [`RrdManifest`] for in-memory chunks (useful for tests). /// /// This is a convenience wrapper around [`RawRrdManifest::build_in_memory_from_chunks`]. diff --git a/crates/store/re_redap_client/src/connection_client.rs b/crates/store/re_redap_client/src/connection_client.rs index b33c982bb97b..17d636da4f28 100644 --- a/crates/store/re_redap_client/src/connection_client.rs +++ b/crates/store/re_redap_client/src/connection_client.rs @@ -408,16 +408,15 @@ where }) } - /// Get the full [`RawRrdManifest`] of a recording. - pub async fn get_rrd_manifest( + /// Stream the [`RawRrdManifest`] parts of a recording as they arrive from the server. + /// + /// Each item in the returned stream is a manifest part (a slice of the full manifest). + /// Use [`RawRrdManifest::concat`] to combine parts if needed. + pub async fn get_rrd_manifest_stream( &mut self, dataset_id: EntryId, segment_id: SegmentId, - ) -> ApiResult { - // TODO(cmc): at some point we should probably continue the stream all the way down, but - // for now we simplify downstream's life by concatenating everything in here. - let mut rrd_manifest_parts = Vec::new(); - + ) -> ApiResult> + use> { let responses = self .inner() .get_rrd_manifest( @@ -431,32 +430,41 @@ where .map_err(|err| ApiError::tonic(err, "/GetRrdManifest failed"))? .into_inner(); - futures::pin_mut!(responses); - while let Some(resp) = responses.next().await { - let rrd_manifest_part = resp - .map_err(|err| { - ApiError::connection_with_source( - err, - "failed fetching /GetRrdManifest response part", - ) - })? - .rrd_manifest - .ok_or_else(|| { - let err = missing_field!(GetRrdManifestResponse, "rrd_manifest"); - ApiError::serialization_with_source( - err, - "missing field in /GetRrdManifest response", - ) - })? - .to_application(()) - .map_err(|err| { - ApiError::serialization_with_source( - err, - "failed parsing /GetRrdManifest response", - ) - })?; + Ok(responses.map(|resp| { + resp.map_err(|err| { + ApiError::connection_with_source( + err, + "failed fetching /GetRrdManifest response part", + ) + })? + .rrd_manifest + .ok_or_else(|| { + let err = missing_field!(GetRrdManifestResponse, "rrd_manifest"); + ApiError::serialization_with_source( + err, + "missing field in /GetRrdManifest response", + ) + })? + .to_application(()) + .map_err(|err| { + ApiError::serialization_with_source(err, "failed parsing /GetRrdManifest response") + }) + })) + } - rrd_manifest_parts.push(rrd_manifest_part); + /// Get the full [`RawRrdManifest`] of a recording, concatenated from all stream parts. + pub async fn get_rrd_manifest( + &mut self, + dataset_id: EntryId, + segment_id: SegmentId, + ) -> ApiResult { + let stream = self.get_rrd_manifest_stream(dataset_id, segment_id).await?; + + futures::pin_mut!(stream); + + let mut rrd_manifest_parts = Vec::new(); + while let Some(part) = stream.next().await { + rrd_manifest_parts.push(part?); } let Some(mut rrd_manifest) = rrd_manifest_parts.first().cloned() else { diff --git a/crates/store/re_redap_client/src/grpc.rs b/crates/store/re_redap_client/src/grpc.rs index 177d000a54ea..7a8b7f69d584 100644 --- a/crates/store/re_redap_client/src/grpc.rs +++ b/crates/store/re_redap_client/src/grpc.rs @@ -469,29 +469,61 @@ async fn stream_segment_from_server( // See the attached issues for more information. let start_time = web_time::Instant::now(); - let manifest_result = client - .get_rrd_manifest(dataset_id, segment_id.clone()) + let manifest_stream_result = client + .get_rrd_manifest_stream(dataset_id, segment_id.clone()) .await; - match manifest_result { - Ok(raw_rrd_manifest) => { - re_log::debug_once!( - "The server supports larger-than-RAM. RRD manifest ({} deflated) loaded in {:.1}s", - re_format::format_bytes(raw_rrd_manifest.total_size_bytes() as _), - start_time.elapsed().as_secs_f32(), - ); + match manifest_stream_result { + Ok(manifest_stream) => { + let mut manifest_stream = std::pin::pin!(manifest_stream); - let rrd_manifest = - re_log_encoding::RrdManifest::try_new(raw_rrd_manifest).map_err(|err| { - ApiError::invalid_arguments_with_source(err, "Invalid RRD manifest") - })?; + let mut last_rrd_manifest: Option> = None; + let mut part_nr: usize = 0; + + while let Some(part_result) = manifest_stream.next().await { + let raw_rrd_manifest_part = part_result?; + + part_nr += 1; + re_log::debug!( + "Received RRD manifest part #{part_nr}/? ({} deflated, {:.1}s elapsed)", + re_format::format_bytes(raw_rrd_manifest_part.total_size_bytes() as _), + start_time.elapsed().as_secs_f32(), + ); + + let rrd_manifest = re_log_encoding::RrdManifest::try_new(raw_rrd_manifest_part) + .map_err(|err| { + ApiError::invalid_arguments_with_source(err, "Invalid RRD manifest part") + })?; + + let rrd_manifest = Arc::new(rrd_manifest); + + if tx + .send(DataSourceMessage::RrdManifest( + store_id.clone(), + rrd_manifest.clone(), + )) + .is_err() + { + re_log::debug!("Receiver disconnected"); + return Ok(ControlFlow::Break(())); + } + + last_rrd_manifest = Some(rrd_manifest); + } + + let Some(rrd_manifest) = last_rrd_manifest else { + return Err(ApiError::serialization( + "failed to parse the response for /GetRrdManifest (no data)", + )); + }; - let rrd_manifest = Arc::new(rrd_manifest); + re_log::debug!( + "The server supports on-demand streaming. Full RRD manifest loaded in {:.1}s in {}", + start_time.elapsed().as_secs_f32(), + re_format::format_plural_s(part_nr, "part") + ); if tx - .send(DataSourceMessage::RrdManifest( - store_id.clone(), - rrd_manifest.clone(), - )) + .send(DataSourceMessage::RrdManifestComplete(store_id.clone())) .is_err() { re_log::debug!("Receiver disconnected"); @@ -514,7 +546,7 @@ async fn stream_segment_from_server( } Err(err) => { if err.kind == ApiErrorKind::Unimplemented { - re_log::debug_once!("The server does not support larger-than-RAM"); // Legacy server + re_log::debug_once!("The server does not support on-demand streaming"); // Legacy server } else { re_log::warn!("Failed to load RRD manifest: {err}"); } diff --git a/crates/top/rerun/src/commands/entrypoint.rs b/crates/top/rerun/src/commands/entrypoint.rs index f4871499ad47..2c7531c6c478 100644 --- a/crates/top/rerun/src/commands/entrypoint.rs +++ b/crates/top/rerun/src/commands/entrypoint.rs @@ -1252,6 +1252,21 @@ fn assert_receive_into_entity_db(rx: &LogReceiverSet) -> anyhow::Result { + let mut_db = match store_id.kind() { + re_log_types::StoreKind::Recording => { + rec.get_or_insert_with(|| { + re_entity_db::EntityDb::new(store_id.clone()) + }) + } + re_log_types::StoreKind::Blueprint => bp.get_or_insert_with(|| { + re_entity_db::EntityDb::new(store_id.clone()) + }), + }; + + mut_db.mark_rrd_manifest_complete(); + } + DataSourceMessage::LogMsg(msg) => { let mut_db = match msg.store_id().kind() { re_log_types::StoreKind::Recording => { diff --git a/crates/utils/re_format/src/plural.rs b/crates/utils/re_format/src/plural.rs index 9eaae3eb2eac..17b67f0f9ce1 100644 --- a/crates/utils/re_format/src/plural.rs +++ b/crates/utils/re_format/src/plural.rs @@ -1,9 +1,13 @@ use std::fmt::Display; +use re_log::debug_assert; + use crate::{UnsignedAbs, format_int, format_uint}; /// Returns either "1 $NOUN" (if `count` is one), otherwise returns `$N $NOUNs`. pub fn format_plural_s(count: impl num_traits::Unsigned + Display, noun: &'static str) -> String { + debug_assert!(!noun.ends_with('s'), "Expected singular, got {noun:?}"); + if count.is_one() { format!("1 {noun}") } else { @@ -17,6 +21,8 @@ where Int: num_traits::Signed + Display + PartialOrd + num_traits::Zero + UnsignedAbs, Int::Unsigned: Display + num_traits::Unsigned, { + debug_assert!(!noun.ends_with('s'), "Expected singular, got {noun:?}"); + if count.abs().is_one() { format!("{} {noun}", format_int(count)) } else { diff --git a/crates/viewer/re_data_ui/src/entity_db_ui.rs b/crates/viewer/re_data_ui/src/entity_db_ui.rs index 24a75e121032..ec39b49f929c 100644 --- a/crates/viewer/re_data_ui/src/entity_db_ui.rs +++ b/crates/viewer/re_data_ui/src/entity_db_ui.rs @@ -6,7 +6,7 @@ use jiff::fmt::friendly::{FractionalUnit, SpanPrinter}; use re_byte_size::SizeBytes as _; use re_chunk_store::Chunk; use re_chunk_store::ChunkStoreConfig; -use re_entity_db::{EntityDb, RrdManifestIndex}; +use re_entity_db::{EntityDb, RrdManifestIndex, entity_db::RedapConnectionState}; use re_format::{format_bytes, format_uint}; use re_log_channel::LogSource; use re_log_types::{EntityPath, StoreKind}; @@ -240,7 +240,11 @@ fn grid_content_ui(ctx: &AppContext<'_>, db: &EntityDb, ui: &mut egui::Ui, ui_la } } - if num_fully_loaded == num_root_chunks { + if db.redap_connection_state() == RedapConnectionState::PartialManifest { + ui.label(format!("{current_size} / ?")); + ui.label(format!("({} / ? chunks)", format_uint(num_fully_loaded))); + ui.end_row(); + } else if num_fully_loaded == num_root_chunks { ui.label("100%"); } else { ui.label(format!("{current_size} / {max_downloaded}")); @@ -410,6 +414,10 @@ fn debug_ui(ui: &mut egui::Ui, db: &EntityDb) { ui.label(db.is_buffering().to_string()); ui.end_row(); + ui.label("Connection"); + ui.label(format!("{:?}", db.redap_connection_state())); + ui.end_row(); + ui.label("Physical chunks"); ui.label(format_bytes(db.byte_size_of_physical_chunks() as _)); ui.end_row(); diff --git a/crates/viewer/re_time_panel/src/time_panel.rs b/crates/viewer/re_time_panel/src/time_panel.rs index 419488efaa08..181539013c18 100644 --- a/crates/viewer/re_time_panel/src/time_panel.rs +++ b/crates/viewer/re_time_panel/src/time_panel.rs @@ -349,7 +349,7 @@ impl TimePanel { let time_ctrl = store_ctx.time_ctrl; let entity_db = store_ctx.db; - let loading_text = if entity_db.is_currently_downloading_manifest() { + let loading_text = if entity_db.is_downloading_first_part_of_manifest() { Some("Downloading meta-data") } else if time_ctrl.is_pending() { Some("Waiting for timeline") @@ -427,7 +427,7 @@ impl TimePanel { ) { re_tracing::profile_function!(); - if store_ctx.db.is_currently_downloading_manifest() { + if store_ctx.db.is_downloading_first_part_of_manifest() { ui.loading_screen_ui("Downloading meta-data", |ui| { let text = "Downloading meta-data"; ui.label(egui::RichText::from(text).heading().strong()); diff --git a/crates/viewer/re_time_panel/tests/snapshots/time_panel_partially_unloaded_chunks.png b/crates/viewer/re_time_panel/tests/snapshots/time_panel_partially_unloaded_chunks.png index 180ad2982258..b7701684bc01 100644 --- a/crates/viewer/re_time_panel/tests/snapshots/time_panel_partially_unloaded_chunks.png +++ b/crates/viewer/re_time_panel/tests/snapshots/time_panel_partially_unloaded_chunks.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cd43ba9f6a6d19913aca04b471dc7e62dad56f32e90d9e87709acb12f29eea34 -size 34416 +oid sha256:5353a24c759b4fc447c066283e4e3ab6ded5ba807205696ed832c4ebdacb2c39 +size 34667 diff --git a/crates/viewer/re_viewer/src/app.rs b/crates/viewer/re_viewer/src/app.rs index e3f751b3eda8..47047e93df44 100644 --- a/crates/viewer/re_viewer/src/app.rs +++ b/crates/viewer/re_viewer/src/app.rs @@ -2508,6 +2508,11 @@ impl App { } } + DataSourceMessage::RrdManifestComplete(store_id) => { + let entity_db = store_hub.entity_db_entry(&store_id); + entity_db.mark_rrd_manifest_complete(); + } + DataSourceMessage::LogMsg(msg) => { self.receive_log_msg(&msg, store_hub, egui_ctx, &channel_source); } @@ -3386,7 +3391,9 @@ impl App { }); let mut memory_limit = if let Some(active_recording) = active_recording { - if active_recording.is_currently_downloading_manifest() { + if active_recording.is_downloading_first_part_of_manifest() { + // We need at least ONE part of the manifest, but + // as soon as we have one part we start prefetching chunks return; } @@ -3459,7 +3466,7 @@ impl App { // Subtract all already loaded physical chunks from the memory limit. for recording in store_hub.store_bundle().recordings() { - if recording.is_currently_downloading_manifest() { + if recording.is_downloading_first_part_of_manifest() { // If any are downloading manifest, don't prefetch background recordings. memory_limit = re_memory::MemoryLimit::ZERO; break; diff --git a/crates/viewer/re_viewer_context/src/cache/video_stream_cache.rs b/crates/viewer/re_viewer_context/src/cache/video_stream_cache.rs index 3f831a869267..6cac48b53030 100644 --- a/crates/viewer/re_viewer_context/src/cache/video_stream_cache.rs +++ b/crates/viewer/re_viewer_context/src/cache/video_stream_cache.rs @@ -679,7 +679,9 @@ fn handle_split_chunk_addition( idx } else { - debug_panic!("Split chunks ended up with more samples than the original chunk?"); + re_log::debug_warn_once!( + "Split chunks ended up with more samples than the original chunk?" + ); old_known_range.last_sample } From f6cd5a180be800c9d4461893e0455240afa98218 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Tue, 10 Mar 2026 20:33:22 +0100 Subject: [PATCH 090/513] Don't store full manifest in the chunk lineage tracker * Part of RR-3383 * Related to https://github.com/rerun-io/reality/pull/1082 Much simpler. Source-Ref: 9c5be3a69d16f0d0e37d1d713d108a5bb2a7a0b9 --- crates/store/re_chunk_store/src/lineage.rs | 87 ++++++++----------- ..._lineage__tests__lineage_bootstrapped.snap | 18 ++-- crates/store/re_chunk_store/src/writes.rs | 6 +- .../src/data_meta_per_timeline.rs | 6 +- 4 files changed, 53 insertions(+), 64 deletions(-) diff --git a/crates/store/re_chunk_store/src/lineage.rs b/crates/store/re_chunk_store/src/lineage.rs index a4c1c70be7bc..8f3927018d84 100644 --- a/crates/store/re_chunk_store/src/lineage.rs +++ b/crates/store/re_chunk_store/src/lineage.rs @@ -4,7 +4,6 @@ use std::sync::Arc; use itertools::Itertools as _; use re_chunk::{Chunk, ChunkId}; -use re_log_encoding::RrdManifest; use crate::ChunkStore; @@ -19,7 +18,7 @@ use crate::ChunkStore; /// This makes it usable in virtual contexts where lineage information alone should never force the /// underlying data to remain in local memory, such as the store's virtual indexes. /// Use [`ChunkDirectLineage::to_report`] to generate a [`ChunkDirectLineageReport`] instead. -#[derive(Clone, PartialEq)] +#[derive(Clone, PartialEq, Eq)] pub enum ChunkDirectLineage { /// This chunk resulted from the splitting of that other chunk. It must have siblings, somewhere. /// @@ -58,7 +57,7 @@ pub enum ChunkDirectLineage { /// /// Even if it gets garbage collected, it can be re-fetched as needed (as long as the backing /// Redap server is still available). - ReferencedFrom(Arc), + RootFromManifest { is_static: bool }, /// This chunk's data was originally logged from volatile memory. /// @@ -73,10 +72,7 @@ impl re_byte_size::SizeBytes for ChunkDirectLineage { chunk_id.heap_size_bytes() + chunk_ids.heap_size_bytes() } Self::CompactedFrom(btree_set) => btree_set.heap_size_bytes(), - Self::ReferencedFrom(_rrd_manifest) => { - 0 // calculating the size of each RrdManifest over and over again is too slow. It is also amortized, so doesn't matter much. - } - Self::Volatile => 0, + Self::RootFromManifest { .. } | Self::Volatile => 0, } } } @@ -94,9 +90,8 @@ impl std::fmt::Debug for ChunkDirectLineage { chunk_ids.iter().join(", ") )), - Self::ReferencedFrom(rrd_manifest) => { - // We don't compute the sha256 here, because it is too expensive - write!(f, "origin:{rrd_manifest:?}") + Self::RootFromManifest { is_static } => { + write!(f, "origin:(static: {is_static})") } Self::Volatile => f.write_str("origin: (cannot be re-fetched)"), @@ -122,9 +117,9 @@ impl From<&ChunkDirectLineageReport> for ChunkDirectLineage { Self::CompactedFrom(chunks.keys().copied().collect()) } - ChunkDirectLineageReport::ReferencedFrom(rrd_manifest) => { - Self::ReferencedFrom(rrd_manifest.clone()) - } + ChunkDirectLineageReport::RootFromManifest { is_static } => Self::RootFromManifest { + is_static: *is_static, + }, ChunkDirectLineageReport::Volatile => Self::Volatile, } @@ -165,9 +160,11 @@ impl ChunkDirectLineage { Some(ChunkDirectLineageReport::CompactedFrom(chunks)) } - Self::ReferencedFrom(rrd_manifest) => Some(ChunkDirectLineageReport::ReferencedFrom( - rrd_manifest.clone(), - )), + Self::RootFromManifest { is_static } => { + Some(ChunkDirectLineageReport::RootFromManifest { + is_static: *is_static, + }) + } Self::Volatile => Some(ChunkDirectLineageReport::Volatile), } @@ -223,7 +220,7 @@ pub enum ChunkDirectLineageReport { /// /// Even if it gets garbage collected, it can be re-fetched as needed (as long as the backing /// Redap server is still available). - ReferencedFrom(Arc), + RootFromManifest { is_static: bool }, /// This chunk's data was originally logged from volatile memory. /// @@ -243,8 +240,8 @@ impl std::fmt::Debug for ChunkDirectLineageReport { .debug_map() .entries(map.iter().map(|(k, v)| (k, v.id()))) .finish(), - Self::ReferencedFrom(_manifest) => { - f.debug_tuple("ReferencedFrom").finish_non_exhaustive() + Self::RootFromManifest { is_static } => { + write!(f, "RootFromManifest(static: {is_static})") } Self::Volatile => write!(f, "Volatile"), } @@ -263,15 +260,12 @@ impl ChunkStore { } // OTOH, if it has been offloaded, now we need to track down its roots and determine - // from an RRD manifest whether it is static or not, if possible. - for (_, rrd_manifest) in store.find_root_rrd_manifests(chunk_id) { - for (id, is_static) in itertools::izip!( - rrd_manifest.col_chunk_ids(), - rrd_manifest.col_chunk_is_static(), - ) { - if chunk_id == id { - return if is_static { "yes" } else { "no" }; - } + // whether it is static or not from the lineage info. + for root_id in store.find_root_manifest_chunks(chunk_id) { + if let Some(ChunkDirectLineage::RootFromManifest { is_static }) = + store.chunks_lineage.get(&root_id) + { + return if *is_static { "yes" } else { "no" }; } } @@ -349,7 +343,7 @@ impl ChunkStore { }; matches!( lineage, - ChunkDirectLineage::ReferencedFrom(_) | ChunkDirectLineage::Volatile + ChunkDirectLineage::RootFromManifest { .. } | ChunkDirectLineage::Volatile ) } @@ -359,7 +353,7 @@ impl ChunkStore { /// possible (and even common) for a chunk to have more than one root. /// /// The resulting root chunks might or might not be volatile. - /// If you only care about chunks that are still available for download, see [`Self::find_root_rrd_manifests`]. + /// If you only care about chunks that are still available for download, see [`Self::find_root_manifest_chunks`]. pub fn find_root_chunks(&self, chunk_id: &ChunkId) -> Vec { let mut roots = Vec::new(); self.collect_root_ids(chunk_id, &mut roots); @@ -380,7 +374,7 @@ impl ChunkStore { } } - Some(ChunkDirectLineage::ReferencedFrom(_) | ChunkDirectLineage::Volatile) => { + Some(ChunkDirectLineage::RootFromManifest { .. } | ChunkDirectLineage::Volatile) => { roots.push(*chunk_id); } @@ -395,34 +389,30 @@ impl ChunkStore { /// one RRD manifest. /// /// The resulting root chunks are guaranteed to be backed by an RRD manifest (non-volatile). - /// If you want to find all root chunks regardless of their origin, refer to [`Self::find_root_rrd_manifests`] + /// If you want to find all root chunks regardless of their origin, refer to [`Self::find_root_chunks`] /// instead. - pub fn find_root_rrd_manifests(&self, chunk_id: &ChunkId) -> Vec<(ChunkId, Arc)> { + pub fn find_root_manifest_chunks(&self, chunk_id: &ChunkId) -> Vec { let mut roots = Vec::new(); - self.collect_root_rrd_manifests(chunk_id, &mut roots); + self.collect_root_manifest_chunks(chunk_id, &mut roots); roots } - /// See [`Self::find_root_rrd_manifests`]. - pub fn collect_root_rrd_manifests( - &self, - chunk_id: &ChunkId, - roots: &mut Vec<(ChunkId, Arc)>, - ) { + /// See [`Self::find_root_manifest_chunks`]. + fn collect_root_manifest_chunks(&self, chunk_id: &ChunkId, roots: &mut Vec) { let lineage = self.chunks_lineage.get(chunk_id); match lineage { Some(ChunkDirectLineage::SplitFrom(chunk_id, _sibling_ids)) => { - self.collect_root_rrd_manifests(chunk_id, roots); + self.collect_root_manifest_chunks(chunk_id, roots); } Some(ChunkDirectLineage::CompactedFrom(chunk_ids)) => { for chunk_id in chunk_ids { - self.collect_root_rrd_manifests(chunk_id, roots); + self.collect_root_manifest_chunks(chunk_id, roots); } } - Some(ChunkDirectLineage::ReferencedFrom(rrd_manifest)) => { - roots.push((*chunk_id, rrd_manifest.clone())); + Some(ChunkDirectLineage::RootFromManifest { .. }) => { + roots.push(*chunk_id); } _ => {} @@ -541,6 +531,7 @@ impl ChunkStore { #[expect(clippy::bool_assert_comparison)] // I like it that way, sue me mod tests { use re_chunk::{Chunk, EntityPath, RowId, Timeline}; + use re_log_encoding::RrdManifest; use re_log_types::StoreId; use re_log_types::example_components::{MyPoint, MyPoints}; use re_log_types::external::re_tuid::Tuid; @@ -616,7 +607,7 @@ mod tests { "all these chunks' respective roots should come from the starting set" ); assert!( - store.find_root_rrd_manifests(&chunk.id()).is_empty(), + store.find_root_manifest_chunks(&chunk.id()).is_empty(), "none of these chunks should have a root RRD manifest" ); } @@ -710,12 +701,11 @@ mod tests { "all these chunks' respective roots should come from the starting set" ); - for (root_chunk_id, root_manifest) in store.find_root_rrd_manifests(&chunk.id()) { + for root_chunk_id in store.find_root_manifest_chunks(&chunk.id()) { assert!( chunks.iter().any(|c| c.id() == root_chunk_id), "all these chunks' respective roots should come from the starting manifest", ); - assert_eq!(rrd_manifest, root_manifest); } } } @@ -1043,8 +1033,8 @@ mod tests { store.descends_from_a_split(&event.chunk_after_processing.id()) ); + let lineage: ChunkDirectLineage = event.direct_lineage.clone().into(); if let Some(prev_chunk) = prev_chunk.take() { - let lineage: ChunkDirectLineage = event.direct_lineage.clone().into(); let expected = ChunkDirectLineage::CompactedFrom( [chunk.id(), prev_chunk.id()].into_iter().collect(), ); @@ -1054,7 +1044,6 @@ mod tests { store.descends_from_a_compaction(&event.chunk_after_processing.id()) ); } else { - let lineage: ChunkDirectLineage = event.direct_lineage.clone().into(); let expected = ChunkDirectLineage::Volatile; assert_eq!(expected, lineage); assert_eq!( diff --git a/crates/store/re_chunk_store/src/snapshots/re_chunk_store__lineage__tests__lineage_bootstrapped.snap b/crates/store/re_chunk_store/src/snapshots/re_chunk_store__lineage__tests__lineage_bootstrapped.snap index 445d2a1495f9..15de2e36ac98 100644 --- a/crates/store/re_chunk_store/src/snapshots/re_chunk_store__lineage__tests__lineage_bootstrapped.snap +++ b/crates/store/re_chunk_store/src/snapshots/re_chunk_store__lineage__tests__lineage_bootstrapped.snap @@ -3,22 +3,22 @@ source: crates/store/re_chunk_store/src/lineage.rs expression: generate_redacted_lineage_report(&store) --- chunk_00000000000005390000000000000004 (status:loaded static:no) - origin:RrdManifest { .. } + origin:(static: false) chunk_00000000000005390000000000000005 (status:loaded static:no) - origin:RrdManifest { .. } + origin:(static: false) chunk_00000000000005390000000000000006 (status:loaded static:no) - origin:RrdManifest { .. } + origin:(static: false) chunk_00000000000005390000000000000009 (status:loaded static:no) compacted-from: chunk_00000000000005390000000000000003 (status:offloaded static:no) - origin:RrdManifest { .. } - compacted-from: chunk_00000000000005390000000000000008 (status:offloaded static:unknown) + origin:(static: false) + compacted-from: chunk_00000000000005390000000000000008 (status:offloaded static:no) compacted-from: chunk_00000000000005390000000000000001 (status:offloaded static:no) - origin:RrdManifest { .. } + origin:(static: false) compacted-from: chunk_00000000000005390000000000000002 (status:offloaded static:no) - origin:RrdManifest { .. } + origin:(static: false) chunk_0000000000000539000000000000000a (status:loaded static:no siblings:[chunk_0000000000000539000000000000000b]) split-from: chunk_00000000000005390000000000000007 (status:offloaded static:no) - origin:RrdManifest { .. } + origin:(static: false) chunk_0000000000000539000000000000000b (status:loaded static:no siblings:[chunk_0000000000000539000000000000000a]) split-from: chunk_00000000000005390000000000000007 (status:offloaded static:no) - origin:RrdManifest { .. } + origin:(static: false) diff --git a/crates/store/re_chunk_store/src/writes.rs b/crates/store/re_chunk_store/src/writes.rs index e89882e983fc..3618084e84e6 100644 --- a/crates/store/re_chunk_store/src/writes.rs +++ b/crates/store/re_chunk_store/src/writes.rs @@ -100,7 +100,7 @@ impl ChunkStore { .map(|chunk_id| { ( *chunk_id, - ChunkDirectLineage::ReferencedFrom(rrd_manifest.clone()), + ChunkDirectLineage::RootFromManifest { is_static: true }, ) }), ); @@ -121,7 +121,7 @@ impl ChunkStore { .map(|chunk_id| { ( *chunk_id, - ChunkDirectLineage::ReferencedFrom(rrd_manifest.clone()), + ChunkDirectLineage::RootFromManifest { is_static: false }, ) }), ); @@ -301,7 +301,7 @@ impl ChunkStore { if matches!( self.direct_lineage(&chunk.id()), - Some(&ChunkDirectLineage::ReferencedFrom(_)) + Some(&ChunkDirectLineage::RootFromManifest { .. }) ) { // If we reach here, then a chunk that was previously virtually inserted using `insert_rrd_manifest` // is about to be physically inserted for real. diff --git a/crates/store/re_entity_db/src/data_meta_per_timeline.rs b/crates/store/re_entity_db/src/data_meta_per_timeline.rs index a75c9443262d..cf8bab964f43 100644 --- a/crates/store/re_entity_db/src/data_meta_per_timeline.rs +++ b/crates/store/re_entity_db/src/data_meta_per_timeline.rs @@ -76,7 +76,7 @@ impl DataMetaPerTimeline { // then don't count it since we've already counted that with a virtual // addition. if store - .find_root_rrd_manifests(&addition.delta_chunk().id()) + .find_root_manifest_chunks(&addition.delta_chunk().id()) .is_empty() { for (timeline, col) in addition.delta_chunk().timelines() { @@ -111,9 +111,9 @@ impl DataMetaPerTimeline { // So we collect the count of all root chunks in the rrd manifest // for the deleted chunk. let rrd_manifest_row_counts_iter = store - .find_root_rrd_manifests(&deletion.chunk.id()) + .find_root_manifest_chunks(&deletion.chunk.id()) .into_iter() - .filter_map(|(c, _)| manifest_index.root_chunk_info(&c)) + .filter_map(|c| manifest_index.root_chunk_info(&c)) .flat_map(|info| { info.temporals.iter().map(|(timeline, info)| { (*timeline, info.num_rows_for_all_entities_all_components) From 784c7b5df379db71e4f435262f76e0cc579d2582 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Gyebn=C3=A1r?= Date: Tue, 10 Mar 2026 20:35:06 +0100 Subject: [PATCH 091/513] Displays WorkOS organization when logged in ### Related Closes RR-3999 ### What We can't currently show which organization is expected for a cloud data source. But we can show the current one in the login popup. image Source-Ref: 06c5d7d7b0faa3b02fe9a6ef940874d589dc061d --- crates/utils/re_auth/src/oauth.rs | 10 +++++++++- crates/utils/re_auth/src/oauth/api.rs | 1 + crates/viewer/re_viewer/src/app.rs | 9 ++++++--- crates/viewer/re_viewer/src/ui/top_panel.rs | 20 +++++++++++++++---- .../re_viewer_context/src/app_context.rs | 1 + 5 files changed, 33 insertions(+), 8 deletions(-) diff --git a/crates/utils/re_auth/src/oauth.rs b/crates/utils/re_auth/src/oauth.rs index 64a541e24c83..25b80392521c 100644 --- a/crates/utils/re_auth/src/oauth.rs +++ b/crates/utils/re_auth/src/oauth.rs @@ -231,6 +231,10 @@ pub struct RerunCloudClaims { /// Subject's email address. #[serde(default, skip_serializing_if = "Option::is_none")] pub email: Option, + + /// Organization name. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub org_name: Option, } impl RerunCloudClaims { @@ -325,8 +329,10 @@ impl Credentials { let jwt = Jwt(res.access_token); let claims = RerunCloudClaims::try_from_unverified_jwt(&jwt)?; let access_token = AccessToken::try_from_unverified_jwt(jwt)?; + let mut user: User = res.user; + user.org_name = claims.org_name.clone(); Ok(InMemoryCredentials(Self { - user: res.user, + user, refresh_token: Some(RefreshToken(res.refresh_token)), access_token, claims, @@ -346,6 +352,7 @@ impl Credentials { let user = User { id: claims.sub.clone(), email, + org_name: claims.org_name.clone(), }; let access_token = AccessToken { token: access_token, @@ -379,6 +386,7 @@ pub struct User { pub id: String, pub email: String, + pub org_name: Option, } /// An access token which was valid at some point in the past. diff --git a/crates/utils/re_auth/src/oauth/api.rs b/crates/utils/re_auth/src/oauth/api.rs index 9bc9ba4e896a..fd49adfcaf84 100644 --- a/crates/utils/re_auth/src/oauth/api.rs +++ b/crates/utils/re_auth/src/oauth/api.rs @@ -341,6 +341,7 @@ impl From for crate::oauth::User { Self { id: value.id, email: value.email, + org_name: None, } } } diff --git a/crates/viewer/re_viewer/src/app.rs b/crates/viewer/re_viewer/src/app.rs index 47047e93df44..ab99a41f7d43 100644 --- a/crates/viewer/re_viewer/src/app.rs +++ b/crates/viewer/re_viewer/src/app.rs @@ -197,9 +197,12 @@ impl App { if connection_registry.should_use_stored_credentials() { let command_sender = command_channel.0.clone(); re_auth::credentials::subscribe_auth_changes(move |user| { - command_sender.send_system(SystemCommand::OnAuthChanged( - user.map(|user| AuthContext { email: user.email }), - )); + command_sender.send_system(SystemCommand::OnAuthChanged(user.map(|user| { + AuthContext { + email: user.email, + org_name: user.org_name, + } + }))); }); // Call get_token once so the auth state is initialized. diff --git a/crates/viewer/re_viewer/src/ui/top_panel.rs b/crates/viewer/re_viewer/src/ui/top_panel.rs index 8c762f234e27..37ec32adb7b1 100644 --- a/crates/viewer/re_viewer/src/ui/top_panel.rs +++ b/crates/viewer/re_viewer/src/ui/top_panel.rs @@ -1,4 +1,7 @@ -use egui::{Atom, Button, Color32, Id, Image, NumExt as _, Popup, RichText, Sense, include_image}; +use egui::{ + Align, Atom, Button, Color32, Id, Image, Layout, NumExt as _, Popup, RichText, Sense, + include_image, +}; use emath::{Rect, RectAlign, Vec2}; use re_format::format_uint; use re_renderer::WgpuResourcePoolStatistics; @@ -471,14 +474,23 @@ fn panel_buttons_r2l( ui.vertical(|ui| { ui.spacing_mut().item_spacing.y = 2.0; ui.label(RichText::new(&auth.email).color(ui.tokens().text_default)); + if let Some(org_name) = &auth.org_name { + ui.label(RichText::new(org_name).color(ui.tokens().text_subdued)); + } + }) + }); + + // Avoid increasing the width of the popup, ignore this button when egui calculates the size. + if !ui.is_sizing_pass() { + ui.with_layout(Layout::right_to_left(Align::Center), |ui| { if ui - .link(RichText::new("Log out").color(ui.tokens().text_subdued)) + .add(re_ui::ReButton::new("Log out").small().primary()) .clicked() { app.command_sender.send_system(SystemCommand::Logout); } - }) - }); + }); + } }); } } diff --git a/crates/viewer/re_viewer_context/src/app_context.rs b/crates/viewer/re_viewer_context/src/app_context.rs index 79e6cc21f4a8..5206ef9be7d0 100644 --- a/crates/viewer/re_viewer_context/src/app_context.rs +++ b/crates/viewer/re_viewer_context/src/app_context.rs @@ -84,6 +84,7 @@ pub struct AppContext<'a> { pub struct AuthContext { pub email: String, + pub org_name: Option, } impl AppContext<'_> { From 6589388d770ba08a2078e48114fdaf878e8b030a Mon Sep 17 00:00:00 2001 From: Andreas Reich Date: Wed, 11 Mar 2026 10:11:56 +0100 Subject: [PATCH 092/513] Use full recording schema in manifest ingestion Among other things this allows us to register frame ids from virtual chunks, which avoid false errors on load. --------- Source-Ref: 1b1e07c017485b3bc617b213b37b90ef414b063c Co-authored-by: Emil Ernerfeldt Co-authored-by: Claude Opus 4.6 --- crates/store/re_chunk_store/src/lineage.rs | 2 +- crates/store/re_chunk_store/src/writes.rs | 11 +--- .../src/data_meta_per_timeline.rs | 6 +- crates/store/re_entity_db/src/entity_db.rs | 13 ++-- .../re_entity_db/src/rrd_manifest_index.rs | 6 +- .../src/rrd/footer/rrd_manifest.rs | 21 +++++- .../tests/arrow_encode_roundtrip.rs | 2 + crates/store/re_query/src/latest_at.rs | 2 +- crates/store/re_sorbet/src/sorbet_schema.rs | 11 ++++ crates/store/re_tf/src/frame_id_registry.rs | 2 +- crates/store/re_tf/src/transform_forest.rs | 42 +++++++----- .../src/transform_resolution_cache/cache.rs | 54 ++++++++------- crates/viewer/re_data_ui/src/entity_db_ui.rs | 8 +++ .../src/contexts/transform_tree_context.rs | 24 ++++--- .../src/visualizers/cameras.rs | 10 ++- .../src/visualizers/transform_axes_3d.rs | 8 ++- .../utilities/transform_retrieval.rs | 66 ++++++++++++++----- crates/viewer/re_viewer/src/app.rs | 4 +- crates/viewer/re_viewer/src/app_state.rs | 1 + .../re_viewer_context/src/cache/caches.rs | 24 ------- .../src/cache/video_stream_cache.rs | 11 ++-- 21 files changed, 199 insertions(+), 129 deletions(-) diff --git a/crates/store/re_chunk_store/src/lineage.rs b/crates/store/re_chunk_store/src/lineage.rs index 8f3927018d84..c78035e4b008 100644 --- a/crates/store/re_chunk_store/src/lineage.rs +++ b/crates/store/re_chunk_store/src/lineage.rs @@ -660,7 +660,7 @@ mod tests { ); // Load it virtually. - store.insert_rrd_manifest(rrd_manifest.clone()).unwrap(); + let _ignored_events = store.insert_rrd_manifest(rrd_manifest.clone()); // Load it physically. for chunk in &chunks { diff --git a/crates/store/re_chunk_store/src/writes.rs b/crates/store/re_chunk_store/src/writes.rs index 3618084e84e6..a9e6d8a72c33 100644 --- a/crates/store/re_chunk_store/src/writes.rs +++ b/crates/store/re_chunk_store/src/writes.rs @@ -24,10 +24,7 @@ impl ChunkStore { /// /// All queries will return partial results until the missing physical data gets loaded in. #[must_use = "The chunk store events should be handled"] - pub fn insert_rrd_manifest( - &mut self, - rrd_manifest: Arc, - ) -> ChunkStoreResult { + pub fn insert_rrd_manifest(&mut self, rrd_manifest: Arc) -> ChunkStoreEvent { re_tracing::profile_function!(); let Self { @@ -52,9 +49,7 @@ impl ChunkStore { event_id: _, } = self; - let sorbet_schema = re_sorbet::SorbetSchema::try_from_raw_arrow_schema(Arc::new( - rrd_manifest.sorbet_schema().clone(), - ))?; + let sorbet_schema = &rrd_manifest.recording_schema(); time_type_registry.extend( sorbet_schema @@ -195,7 +190,7 @@ impl ChunkStore { Self::on_events(std::slice::from_ref(&event)); } - Ok(event) + event } /// Inserts a [`Chunk`] in the store. diff --git a/crates/store/re_entity_db/src/data_meta_per_timeline.rs b/crates/store/re_entity_db/src/data_meta_per_timeline.rs index cf8bab964f43..737ffd560ab8 100644 --- a/crates/store/re_entity_db/src/data_meta_per_timeline.rs +++ b/crates/store/re_entity_db/src/data_meta_per_timeline.rs @@ -373,7 +373,7 @@ mod tests { let (_, rrd_manifest) = build_manifest_chunks(&entity, tl, &[10, 20, 30], &store_id); // Insert the manifest virtually. - let event = store.insert_rrd_manifest(rrd_manifest.clone()).unwrap(); + let event = store.insert_rrd_manifest(rrd_manifest.clone()); manifest_index.append(rrd_manifest).unwrap(); meta.on_events(&manifest_index, &store, &[event]); @@ -394,7 +394,7 @@ mod tests { let (chunks, rrd_manifest) = build_manifest_chunks(&entity, tl, &[10, 20, 30], &store_id); // Load virtually first. - let event = store.insert_rrd_manifest(rrd_manifest.clone()).unwrap(); + let event = store.insert_rrd_manifest(rrd_manifest.clone()); manifest_index.append(rrd_manifest).unwrap(); manifest_index.on_events(&store, std::slice::from_ref(&event)); meta.on_events(&manifest_index, &store, std::slice::from_ref(&event)); @@ -455,7 +455,7 @@ mod tests { ) .unwrap(); - let event = store.insert_rrd_manifest(rrd_manifest.clone()).unwrap(); + let event = store.insert_rrd_manifest(rrd_manifest.clone()); manifest_index.append(rrd_manifest).unwrap(); meta.on_events(&manifest_index, &store, &[event]); diff --git a/crates/store/re_entity_db/src/entity_db.rs b/crates/store/re_entity_db/src/entity_db.rs index ba3ed645f09d..4b60ad1e1d72 100644 --- a/crates/store/re_entity_db/src/entity_db.rs +++ b/crates/store/re_entity_db/src/entity_db.rs @@ -724,7 +724,7 @@ impl EntityDb { self.entity_path_from_hash.contains_key(&entity_path.hash()) } - pub fn add_rrd_manifest_message(&mut self, rrd_manifest: Arc) { + pub fn add_rrd_manifest_message(&mut self, rrd_manifest: Arc) -> ChunkStoreEvent { re_tracing::profile_function!(); let event = self @@ -733,18 +733,13 @@ impl EntityDb { .store() .insert_rrd_manifest(rrd_manifest.clone()); - match event { - Ok(event) => { - self.on_store_events(&[event]); - } - Err(err) => { - re_log::error!("Failed to load RRD Manifest into store: {err}"); - } - } + self.on_store_events(std::slice::from_ref(&event)); if let Err(err) = self.rrd_manifest_index.append(rrd_manifest) { re_log::error!("Failed to append RRD manifest: {err}"); } + + event } /// Mark the RRD manifest as complete (all parts have been received). diff --git a/crates/store/re_entity_db/src/rrd_manifest_index.rs b/crates/store/re_entity_db/src/rrd_manifest_index.rs index f8ca3cf62fdc..5a02c3bbfa0d 100644 --- a/crates/store/re_entity_db/src/rrd_manifest_index.rs +++ b/crates/store/re_entity_db/src/rrd_manifest_index.rs @@ -281,11 +281,7 @@ impl RrdManifestIndex { } fn update_entity_tree(&mut self, manifest: &RrdManifest) { - for entity in manifest - .static_map() - .keys() - .chain(manifest.temporal_map().keys()) - { + for entity in manifest.recording_schema().all_entities() { self.entity_tree.on_new_entity(entity); } } diff --git a/crates/store/re_log_encoding/src/rrd/footer/rrd_manifest.rs b/crates/store/re_log_encoding/src/rrd/footer/rrd_manifest.rs index 9f63dea418a2..6419d9be63c3 100644 --- a/crates/store/re_log_encoding/src/rrd/footer/rrd_manifest.rs +++ b/crates/store/re_log_encoding/src/rrd/footer/rrd_manifest.rs @@ -5,6 +5,7 @@ use arrow::buffer::{BooleanBuffer, ScalarBuffer}; use re_chunk::external::re_byte_size; use re_chunk::{ChunkId, EntityPath}; use re_log_types::StoreId; +use re_sorbet::SorbetSchema; use super::{RawRrdManifest, RrdManifestSha256, RrdManifestStaticMap, RrdManifestTemporalMap}; use crate::{CodecError, CodecResult}; @@ -20,10 +21,12 @@ use crate::{CodecError, CodecResult}; /// and does not duplicate the actual data. /// /// Use [`RrdManifest::try_new`] to create an instance from a [`RawRrdManifest`]. -#[derive(Clone, PartialEq)] +#[derive(Clone)] pub struct RrdManifest { raw: RawRrdManifest, + recording_schema: SorbetSchema, + chunk_ids: FixedSizeBinaryArray, chunk_entity_paths: StringArray, chunk_is_static: BooleanBuffer, @@ -43,6 +46,13 @@ impl std::fmt::Debug for RrdManifest { } } +impl PartialEq for RrdManifest { + fn eq(&self, other: &Self) -> bool { + // All other fields are derived from `raw`, so comparing `raw` is sufficient. + self.raw == other.raw + } +} + impl re_byte_size::SizeBytes for RrdManifest { fn heap_size_bytes(&self) -> u64 { re_tracing::profile_function!(); @@ -151,8 +161,12 @@ impl RrdManifest { let static_data_map = manifest.calc_static_map()?; let temporal_data_map = manifest.calc_temporal_map()?; + let recording_schema = + SorbetSchema::try_from_raw_arrow_schema(Arc::new(manifest.sorbet_schema.clone()))?; + Ok(Self { raw: manifest, + recording_schema, chunk_ids, chunk_entity_paths, chunk_is_static, @@ -166,6 +180,11 @@ impl RrdManifest { }) } + /// The schema for the entire recording. + pub fn recording_schema(&self) -> &SorbetSchema { + &self.recording_schema + } + pub fn concat(a: &Self, b: &Self) -> CodecResult { re_tracing::profile_function!(); let a = a.raw.clone(); diff --git a/crates/store/re_log_encoding/tests/arrow_encode_roundtrip.rs b/crates/store/re_log_encoding/tests/arrow_encode_roundtrip.rs index befbaa0c2f29..14b305ac26ed 100644 --- a/crates/store/re_log_encoding/tests/arrow_encode_roundtrip.rs +++ b/crates/store/re_log_encoding/tests/arrow_encode_roundtrip.rs @@ -1,3 +1,5 @@ +#![cfg(all(feature = "encoder", feature = "decoder"))] + use re_chunk::{Chunk, RowId, TimePoint, Timeline}; use re_log_encoding::{DecoderApp, Encoder}; use re_log_types::{LogMsg, StoreId}; diff --git a/crates/store/re_query/src/latest_at.rs b/crates/store/re_query/src/latest_at.rs index beaaacd88654..6c468a254121 100644 --- a/crates/store/re_query/src/latest_at.rs +++ b/crates/store/re_query/src/latest_at.rs @@ -1205,7 +1205,7 @@ mod tests { let mut cache = QueryCache::new(store.clone()); // The store is now aware that there is a virtual tombstone pending somewhere, and so should be the cache. - cache.on_events(&[store.write().insert_rrd_manifest(rrd_manifest).unwrap()]); + cache.on_events(&[store.write().insert_rrd_manifest(rrd_manifest)]); // Load the physical data into the store, but not the tombstone. cache.on_events( diff --git a/crates/store/re_sorbet/src/sorbet_schema.rs b/crates/store/re_sorbet/src/sorbet_schema.rs index 3548b7c24cfe..727a6957fb0c 100644 --- a/crates/store/re_sorbet/src/sorbet_schema.rs +++ b/crates/store/re_sorbet/src/sorbet_schema.rs @@ -1,3 +1,5 @@ +use std::collections::BTreeSet; + use arrow::datatypes::{Schema as ArrowSchema, SchemaRef as ArrowSchemaRef}; use re_log_types::EntityPath; use re_types_core::ChunkId; @@ -85,6 +87,15 @@ impl SorbetSchema { .chain(timestamps.to_metadata()) .collect() } + + /// All the entities referenced by any column. + pub fn all_entities(&self) -> BTreeSet<&EntityPath> { + self.columns + .iter() + .filter_map(|c| c.entity_path()) + .chain(self.entity_path.iter()) + .collect() + } } impl From for SorbetColumnDescriptors { diff --git a/crates/store/re_tf/src/frame_id_registry.rs b/crates/store/re_tf/src/frame_id_registry.rs index 978e193fd7cc..e3c36960f493 100644 --- a/crates/store/re_tf/src/frame_id_registry.rs +++ b/crates/store/re_tf/src/frame_id_registry.rs @@ -115,7 +115,7 @@ impl FrameIdRegistry { } /// Registers entity path derived frame id and all its parents. - fn register_frame_id_from_entity_path(&mut self, entity_path: &EntityPath) { + pub fn register_frame_id_from_entity_path(&mut self, entity_path: &EntityPath) { // Ensure all implicit frames from this entity all the way up to the root are known. // Note that in-between entities may never be mentioned in any chunk, but we want to make sure they're known to the system. let mut entity_path = entity_path; // Have to redeclare to make borrow-checker happy. diff --git a/crates/store/re_tf/src/transform_forest.rs b/crates/store/re_tf/src/transform_forest.rs index 0bc5c3366a5b..4cc7ea08b1d0 100644 --- a/crates/store/re_tf/src/transform_forest.rs +++ b/crates/store/re_tf/src/transform_forest.rs @@ -3,7 +3,6 @@ use re_byte_size::SizeBytes; use re_chunk_store::{LatestAtQuery, MissingChunkReporter}; use re_entity_db::EntityDb; use re_log::debug_assert; -use re_sdk_types::components::TransformFrameId; use crate::frame_id_registry::FrameIdRegistry; use crate::transform_resolution_cache::ParentFromChildTransform; @@ -428,9 +427,6 @@ impl re_byte_size::MemUsageTreeCapture for TransformForest { } } -static UNKNOWN_TRANSFORM_ID: std::sync::LazyLock = - std::sync::LazyLock::new(|| TransformFrameId::new("")); - /// Starting from a `current_frame`, walks towards the parent and accumulates transforms into `transform_stack`. /// Stops until not more connection is found or an already processed `frame_id` is hit. #[expect(clippy::too_many_arguments)] @@ -802,18 +798,32 @@ fn transforms_at( if let Some(pinhole_projection) = pinhole_projection.as_ref() && pinhole_projection.parent != transform.parent { - re_log::warn_once!( - "The transform frame {:?} is connected to {:?} via a pinhole but also connected to {:?} via a transform. Any frame is only ever allowed to have a single parent at any given time.", - id_registry - .lookup_frame_id(child_frame) - .unwrap_or(&UNKNOWN_TRANSFORM_ID), - id_registry - .lookup_frame_id(pinhole_projection.parent) - .unwrap_or(&UNKNOWN_TRANSFORM_ID), - id_registry - .lookup_frame_id(transform.parent) - .unwrap_or(&UNKNOWN_TRANSFORM_ID), - ); + let transform_frame = id_registry.lookup_frame_id(child_frame); + let pinhole_parent_frame = id_registry.lookup_frame_id(pinhole_projection.parent); + let transform_parent_frame = id_registry.lookup_frame_id(transform.parent); + + // If any of the frames ids can't be resolved to a string, we're in bigger trouble and can't show a useful error + // as this implies that the registry is in an invalid state. + if let Some(transform_frame) = transform_frame + && let Some(pinhole_parent_frame) = pinhole_parent_frame + && let Some(transform_parent_frame) = transform_parent_frame + { + re_log::warn_once!( + "The transform frame {transform_frame:?} is connected to {pinhole_parent_frame:?} via a pinhole but also connected to {transform_parent_frame:?} via a transform. Any frame is only ever allowed to have a single parent at any given time.", + ); + } else { + for frame in [ + transform_frame, + pinhole_parent_frame, + transform_parent_frame, + ] { + if frame.is_none() { + re_log::debug_panic!( + "Couldn't resolve frame id for {frame:?} in the registry, even though it was present in the transforms for timeline.", + ); + } + } + } } Some(transform.parent) diff --git a/crates/store/re_tf/src/transform_resolution_cache/cache.rs b/crates/store/re_tf/src/transform_resolution_cache/cache.rs index 3d848e8c8286..1fba4dbc1931 100644 --- a/crates/store/re_tf/src/transform_resolution_cache/cache.rs +++ b/crates/store/re_tf/src/transform_resolution_cache/cache.rs @@ -199,30 +199,40 @@ impl TransformResolutionCache { re_tracing::profile_function!(); for event in events { - // This doesn't maintain a collection of chunks that needs to be kept in sync 1:1 with - // the store, rather it just keeps track of what entities have what properties, and for - // that a delta chunk is all we need. - let Some(delta_chunk) = event.delta_chunk() else { - continue; // virtual event, we don't care - }; - - // Since entity paths lead to implicit frames, we have to prime our lookup table - // with them even if this chunk doesn't have transform data. - self.frame_id_registry - .write() - .register_all_frames_in_chunk(delta_chunk); + match &**event { + re_chunk_store::ChunkStoreDiff::Addition(addition) => { + let delta_chunk = addition.delta_chunk(); + + // Since entity paths lead to implicit frames, we have to prime our lookup table + // with them even if this chunk doesn't have transform data. + self.frame_id_registry + .write() + .register_all_frames_in_chunk(delta_chunk); + + let aspects = TransformAspect::transform_aspects_of(delta_chunk); + if !aspects.is_empty() { + if delta_chunk.is_static() { + self.add_static_chunk(delta_chunk, aspects); + } else { + self.add_temporal_chunk(delta_chunk, aspects); + } + } + } - let aspects = TransformAspect::transform_aspects_of(delta_chunk); - if aspects.is_empty() { - continue; - } + re_chunk_store::ChunkStoreDiff::VirtualAddition(addition) => { + // Make all the entity paths known as potential transform paths. + let mut frame_id_registry = self.frame_id_registry.write(); + for entity_path in addition.rrd_manifest.recording_schema().all_entities() { + frame_id_registry.register_frame_id_from_entity_path(entity_path); + } + } - if event.is_deletion() { - self.remove_chunk(delta_chunk, aspects); - } else if delta_chunk.is_static() { - self.add_static_chunk(delta_chunk, aspects); - } else { - self.add_temporal_chunk(delta_chunk, aspects); + re_chunk_store::ChunkStoreDiff::Deletion(deletion) => { + let aspects = TransformAspect::transform_aspects_of(&deletion.chunk); + if !aspects.is_empty() { + self.remove_chunk(&deletion.chunk, aspects); + } + } } } } diff --git a/crates/viewer/re_data_ui/src/entity_db_ui.rs b/crates/viewer/re_data_ui/src/entity_db_ui.rs index ec39b49f929c..e2e88a3c1247 100644 --- a/crates/viewer/re_data_ui/src/entity_db_ui.rs +++ b/crates/viewer/re_data_ui/src/entity_db_ui.rs @@ -410,6 +410,14 @@ fn chunk_requests_ui(ui: &mut egui::Ui, rrd_manifest_index: &RrdManifestIndex) { fn debug_ui(ui: &mut egui::Ui, db: &EntityDb) { ui.weak("(only visible in debug builds)"); egui::Grid::new("debug-info").show(ui, |ui| { + if let Some(manifest) = db.rrd_manifest_index().manifest() { + ui.label("Entities"); + ui.label(format_uint( + manifest.recording_schema().all_entities().len(), + )); + ui.end_row(); + } + ui.label("is_buffering"); ui.label(db.is_buffering().to_string()); ui.end_row(); diff --git a/crates/viewer/re_view_spatial/src/contexts/transform_tree_context.rs b/crates/viewer/re_view_spatial/src/contexts/transform_tree_context.rs index bf1a3ff3d027..91a24388d9a5 100644 --- a/crates/viewer/re_view_spatial/src/contexts/transform_tree_context.rs +++ b/crates/viewer/re_view_spatial/src/contexts/transform_tree_context.rs @@ -539,13 +539,21 @@ impl TransformTreeContext { /// Formats a frame ID hash as a human-readable string. /// - /// Returns the frame name if known, `` otherwise. - /// (Showing the hash is practically never useful for users!) - #[inline] - pub fn format_frame(&self, frame_id_hash: TransformFrameIdHash) -> String { - self.lookup_frame_id(frame_id_hash) - .map(ToString::to_string) - .unwrap_or_else(|| "".to_owned()) + /// Returns the frame name if known, otherwise triggers a [`re_log::debug_panic`] + /// and returns `None`. + pub fn format_frame_or_debug_panic( + &self, + frame_id_hash: TransformFrameIdHash, + debug_location: &EntityPath, + ) -> Option { + if let Some(frame_id) = self.lookup_frame_id(frame_id_hash) { + Some(frame_id.to_string()) + } else { + re_log::debug_panic!( + "Failed to resolve frame id hash {frame_id_hash:?} which was referenced at {debug_location:?}" + ); + None + } } } @@ -927,7 +935,7 @@ mod tests { tree_context.target_frame(), TransformFrameIdHash::new(&expected_target), "View expected target frame {expected_target:?}, got {:?}", - tree_context.format_frame(tree_context.target_frame()) + tree_context.lookup_frame_id(tree_context.target_frame()) ); } }); diff --git a/crates/viewer/re_view_spatial/src/visualizers/cameras.rs b/crates/viewer/re_view_spatial/src/visualizers/cameras.rs index dee2dca6a060..48207d9409fd 100644 --- a/crates/viewer/re_view_spatial/src/visualizers/cameras.rs +++ b/crates/viewer/re_view_spatial/src/visualizers/cameras.rs @@ -78,9 +78,15 @@ impl CamerasVisualizer { else { // This implies that the transform context didn't see the pinhole transform. // This can happen with various frame id mismatches. TODO(andreas): When exactly does this happen? Can we add a unit test and improve the message? + let frame = if let Some(frame_id) = + transforms.format_frame_or_debug_panic(pinhole_frame_id, ctx.target_entity_path) + { + format!("child frame {frame_id:?}") + } else { + "child frame".to_owned() + }; return Err(format!( - "The pinhole's child frame {:?} does not form the root of a 2D subspace. Ensure you're transform tree is valid.", - transforms.format_frame(pinhole_frame_id) + "The pinhole's {frame} does not form the root of a 2D subspace. Ensure your transform tree is valid.", )); }; let resolved_pinhole = &pinhole_tree_root_info.pinhole_projection; diff --git a/crates/viewer/re_view_spatial/src/visualizers/transform_axes_3d.rs b/crates/viewer/re_view_spatial/src/visualizers/transform_axes_3d.rs index 2e3f68170c07..ece8a5c7608e 100644 --- a/crates/viewer/re_view_spatial/src/visualizers/transform_axes_3d.rs +++ b/crates/viewer/re_view_spatial/src/visualizers/transform_axes_3d.rs @@ -148,9 +148,11 @@ impl VisualizerSystem for TransformAxes3DVisualizer { && src.as_entity_path_hash() == entity_path.hash() => {} _ => { - if let Err(err_msg) = - format_transform_info_result(transforms, coordinate_frame_transform_result) - { + if let Err(err_msg) = format_transform_info_result( + entity_path, + transforms, + coordinate_frame_transform_result, + ) { output.report_unspecified_source( instruction.id, VisualizerReportSeverity::Error, diff --git a/crates/viewer/re_view_spatial/src/visualizers/utilities/transform_retrieval.rs b/crates/viewer/re_view_spatial/src/visualizers/utilities/transform_retrieval.rs index 992669566638..b0ba7a289bd6 100644 --- a/crates/viewer/re_view_spatial/src/visualizers/utilities/transform_retrieval.rs +++ b/crates/viewer/re_view_spatial/src/visualizers/utilities/transform_retrieval.rs @@ -30,7 +30,8 @@ pub fn transform_info_for_archetype_or_report_error<'a>( re_tracing::profile_function!(); let result = transform_context.target_from_entity_path(entity_path.hash()); - let transform_info = match format_transform_info_result(transform_context, result) { + let transform_info = match format_transform_info_result(entity_path, transform_context, result) + { Ok(transform_info) => transform_info, Err(err_msg) => { output.report_unspecified_source( @@ -43,6 +44,7 @@ pub fn transform_info_for_archetype_or_report_error<'a>( }; is_valid_space_for_content( + entity_path, instruction_id, transform_context, transform_info, @@ -55,6 +57,7 @@ pub fn transform_info_for_archetype_or_report_error<'a>( /// Formats the result of a transform retrieval into a user-friendly message. pub fn format_transform_info_result<'a>( + entity_path: &EntityPath, transform_context: &TransformTreeContext, result: Option<&'a Result>, ) -> Result<&'a TransformInfo, String> { @@ -62,25 +65,47 @@ pub fn format_transform_info_result<'a>( None => Err("No transform relation known for this entity.".to_owned()), Some(Err(re_tf::TransformFromToError::NoPathBetweenFrames { src, target, .. })) => { - let src = transform_context.format_frame(*src); - let target = transform_context.format_frame(*target); + let src = if let Some(frame_id) = + transform_context.format_frame_or_debug_panic(*src, entity_path) + { + format!("{frame_id:?}") + } else { + format!("{entity_path}:?") + }; + let target = if let Some(target) = + transform_context.format_frame_or_debug_panic(*target, entity_path) + { + format!(" ({target:?})") + } else { + String::new() + }; + Err(format!( - "No transform path from {src:?} to the view's target frame ({target:?})." + "No transform path from {src} to the view's target frame{target}." )) } Some(Err(re_tf::TransformFromToError::UnknownTargetFrame(target))) => { - // The target frame is the view's target frame. - // This means this could be hit if the view's target frame doesn't show up in any data. - let target = transform_context.format_frame(*target); - Err(format!("The view's target frame {target:?} is unknown.")) + let target = if let Some(target) = + transform_context.format_frame_or_debug_panic(*target, entity_path) + { + format!("target frame {target:?}") + } else { + "target frame".to_owned() + }; + + Err(format!("The view's {target} is unknown.")) } Some(Err(re_tf::TransformFromToError::UnknownSourceFrame(src))) => { - // Unclear how we'd hit this. This means that when processing transforms we encountered a coordinate frame that the transform cache didn't know about. - // That would imply that the cache is lagging behind. - let src = transform_context.format_frame(*src); - Err(format!("The entity's coordinate frame {src:?} is unknown.")) + let src = if let Some(frame_id) = + transform_context.format_frame_or_debug_panic(*src, entity_path) + { + format!("{frame_id:?}") + } else { + format!("{entity_path}:?") + }; + Err(format!("The entity's coordinate frame {src} is unknown.")) } Some(Ok(transform_info)) => Ok(transform_info), @@ -88,6 +113,7 @@ pub fn format_transform_info_result<'a>( } pub fn is_valid_space_for_content( + entity_path: &EntityPath, instruction_id: &VisualizerInstructionId, transform_context: &TransformTreeContext, transform: &TransformInfo, @@ -111,12 +137,16 @@ pub fn is_valid_space_for_content( if view_kind == SpatialViewKind::ThreeD && let Some(target_frame_pinhole_root) = target_frame_pinhole_root { - let origin = transform_context.format_frame(target_frame_pinhole_root); - output.report_unspecified_source( - *instruction_id, - VisualizerReportSeverity::Error, - format!("The origin of the 3D view ({origin:?}) is under pinhole projection which is not supported by most 3D visualizations."), - ); + let origin = if let Some(origin) = + transform_context.format_frame_or_debug_panic(target_frame_pinhole_root, entity_path) + { + format!("The origin of the 3D view ({origin:?})") + } else { + "The origin of the 3D view".to_owned() + }; + output.report_unspecified_source(*instruction_id, VisualizerReportSeverity::Error, format!( + "{origin} is under pinhole projection which is not supported by most 3D visualizations." + )); return false; } diff --git a/crates/viewer/re_viewer/src/app.rs b/crates/viewer/re_viewer/src/app.rs index ab99a41f7d43..ce107ef84021 100644 --- a/crates/viewer/re_viewer/src/app.rs +++ b/crates/viewer/re_viewer/src/app.rs @@ -2499,7 +2499,7 @@ impl App { match msg { DataSourceMessage::RrdManifest(store_id, rrd_manifest) => { let entity_db = store_hub.entity_db_entry(&store_id); - entity_db.add_rrd_manifest_message(rrd_manifest); + let store_event = entity_db.add_rrd_manifest_message(rrd_manifest); if let Some(caches) = store_hub.caches_for_store(&store_id) { // Downgrade to read-only, so we can access caches. @@ -2507,7 +2507,7 @@ impl App { .entity_db(&store_id) .expect("Just queried it mutable and that was fine."); - caches.on_rrd_manifest(entity_db); + caches.on_store_events(&[store_event], entity_db); } } diff --git a/crates/viewer/re_viewer/src/app_state.rs b/crates/viewer/re_viewer/src/app_state.rs index 5dc325b8112d..25fdd8be5644 100644 --- a/crates/viewer/re_viewer/src/app_state.rs +++ b/crates/viewer/re_viewer/src/app_state.rs @@ -363,6 +363,7 @@ impl AppState { let visualizable_entities = if let Some(view_class) = view_class_registry.class_entry(view.class_identifier()) { + re_tracing::profile_scope!("visualizable_entities_per_visualizer"); PerVisualizerTypeInViewClass { view_class_identifier: view.class_identifier(), per_visualizer: visualizable_entities_per_visualizer diff --git a/crates/viewer/re_viewer_context/src/cache/caches.rs b/crates/viewer/re_viewer_context/src/cache/caches.rs index 569bea606c6b..d2dc54368b21 100644 --- a/crates/viewer/re_viewer_context/src/cache/caches.rs +++ b/crates/viewer/re_viewer_context/src/cache/caches.rs @@ -112,23 +112,6 @@ impl Caches { self.memory_use_after_last_purge = self.capture_mem_usage_tree().size_bytes(); } - /// React to the chunk store's changelog, if needed. - /// - /// Useful to e.g. invalidate unreachable data. - pub fn on_rrd_manifest(&self, entity_db: &EntityDb) { - re_tracing::profile_function!(); - - if self.store_id != *entity_db.store_id() { - return; - } - - #[expect(clippy::iter_over_hash_type)] // order doesn't matter here - for cache in self.caches.lock().values() { - re_tracing::profile_scope!(cache.name); - cache.lock().on_rrd_manifest(entity_db); - } - } - /// React to the chunk store's changelog, if needed. /// /// Useful to e.g. invalidate unreachable data. @@ -209,13 +192,6 @@ pub trait Cache: std::any::Any + Send + Sync + re_byte_size::MemUsageTreeCapture _ = events; _ = entity_db; } - - /// React to receiving an rrd manifest, if needed. - /// - /// Useful for creating data that may be based on the information we get in the rrd manifest. - fn on_rrd_manifest(&mut self, entity_db: &EntityDb) { - _ = entity_db; - } } impl MemUsageTreeCapture for Caches { diff --git a/crates/viewer/re_viewer_context/src/cache/video_stream_cache.rs b/crates/viewer/re_viewer_context/src/cache/video_stream_cache.rs index 6cac48b53030..b4fb00ee89c7 100644 --- a/crates/viewer/re_viewer_context/src/cache/video_stream_cache.rs +++ b/crates/viewer/re_viewer_context/src/cache/video_stream_cache.rs @@ -1332,11 +1332,6 @@ impl Cache for VideoStreamCache { .retain(|_, entry| entry.used_this_frame.load(Ordering::Acquire)); } - fn on_rrd_manifest(&mut self, _entity_db: &EntityDb) { - // Reset everything when we receive an rrd manifest. - self.0.clear(); - } - /// Keep existing cache entries up to date with new and removed video data. fn on_store_events(&mut self, events: &[&ChunkStoreEvent], entity_db: &EntityDb) { re_tracing::profile_function!(); @@ -1344,6 +1339,12 @@ impl Cache for VideoStreamCache { let sample_component = VideoStream::descriptor_sample().component; for event in events { + if event.is_virtual_addition() { + // Reset everything when we receive an rrd manifest. + self.0.clear(); + continue; + } + let Some(delta_chunk) = event.delta_chunk() else { continue; }; From 78037c58ae4e970ec4499f1a0f6ab9b6c84a300b Mon Sep 17 00:00:00 2001 From: Katerina Gavrilo Date: Wed, 11 Mar 2026 10:16:12 +0100 Subject: [PATCH 093/513] excluded entity path is not error before it was red, which looks like an error, changing it to be simply secondary color because it is not an error before image after image Source-Ref: d05ad5b3d880fc51ce2753c39ca7538c9ae74f2d --- crates/viewer/re_selection_panel/src/selection_panel.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/viewer/re_selection_panel/src/selection_panel.rs b/crates/viewer/re_selection_panel/src/selection_panel.rs index 523ceeb0b9a8..4e392176ea73 100644 --- a/crates/viewer/re_selection_panel/src/selection_panel.rs +++ b/crates/viewer/re_selection_panel/src/selection_panel.rs @@ -994,7 +994,7 @@ fn entity_path_filter_ui( let is_exclusion = line.trim_start().starts_with('-'); let color = if is_exclusion { - style.visuals.error_fg_color + tokens.text_subdued } else { tokens.info_log_text_color }; From 29a88f9cf4b9fa68806ca2c30151e488bd4f9c03 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 11 Mar 2026 11:20:18 +0100 Subject: [PATCH 094/513] Show number of columns and rows in the recording ui ### What Show recording stats when selecting a recording. Added number of: * Entities * Timeline columns * Data columns * Rows image Source-Ref: 3bb7a675b15dfd3978f0e965380b59f988870a32 --- crates/viewer/re_data_ui/src/entity_db_ui.rs | 27 +++++++++++++++++++ .../snapshots/selection_panel_recording.png | 4 +-- ...selection_panel_recording_hover_app_id.png | 4 +-- crates/viewer/re_viewer/src/app.rs | 2 +- .../tests/snapshots/add_visualizer_axes_1.png | 4 +-- .../tests/snapshots/deselect_on_escape_3.png | 4 +-- ...zer_instruction_errors_1_warnings_only.png | 4 +-- ...struction_errors_1b_warnings_only_menu.png | 4 +-- ...tion_errors_2_warnings_and_errors_menu.png | 4 +-- .../tests/snapshots/source_component_1.png | 4 +-- 10 files changed, 44 insertions(+), 17 deletions(-) diff --git a/crates/viewer/re_data_ui/src/entity_db_ui.rs b/crates/viewer/re_data_ui/src/entity_db_ui.rs index e2e88a3c1247..5fa54c28ffb0 100644 --- a/crates/viewer/re_data_ui/src/entity_db_ui.rs +++ b/crates/viewer/re_data_ui/src/entity_db_ui.rs @@ -111,6 +111,8 @@ impl crate::AppUi for EntityDb { } fn grid_content_ui(ctx: &AppContext<'_>, db: &EntityDb, ui: &mut egui::Ui, ui_layout: UiLayout) { + re_tracing::profile_function!(); + { ui.grid_left_hand_label(&format!("{} ID", db.store_id().kind())); ui.label(db.store_id().recording_id().to_string()); @@ -272,6 +274,31 @@ fn grid_content_ui(ctx: &AppContext<'_>, db: &EntityDb, ui: &mut egui::Ui, ui_la } } + { + // Stats like number of columns, rows, etc + + let storage_engine = db.storage_engine(); + let store = storage_engine.store(); + let schema = store.schema(); + + ui.grid_left_hand_label("Entities") + .on_hover_text("In the ChunkStore"); + ui.label(re_format::format_uint(store.all_entities().len())); + ui.end_row(); + + ui.grid_left_hand_label("Timeline columns"); + ui.label(re_format::format_uint(schema.indices.len())); + ui.end_row(); + + ui.grid_left_hand_label("Data columns"); + ui.label(re_format::format_uint(schema.components.len())); + ui.end_row(); + + ui.grid_left_hand_label("Rows"); + ui.label(re_format::format_uint(store.stats().total().num_rows)); + ui.end_row(); + } + if ui_layout.is_selection_panel() { let &ChunkStoreConfig { enable_changelog: _, diff --git a/crates/viewer/re_selection_panel/tests/snapshots/selection_panel_recording.png b/crates/viewer/re_selection_panel/tests/snapshots/selection_panel_recording.png index db20f25a6032..b4943c24317d 100644 --- a/crates/viewer/re_selection_panel/tests/snapshots/selection_panel_recording.png +++ b/crates/viewer/re_selection_panel/tests/snapshots/selection_panel_recording.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7f15e3363942466414a978a70d9759d19fffb986f309f29874b39538d5406eed -size 46264 +oid sha256:6fd627a4f8bf1732338c267faf20357f99907007aed8f3064b763ae65ace7da4 +size 43876 diff --git a/crates/viewer/re_selection_panel/tests/snapshots/selection_panel_recording_hover_app_id.png b/crates/viewer/re_selection_panel/tests/snapshots/selection_panel_recording_hover_app_id.png index 91ccf4a73eed..67fb7f1fc7fb 100644 --- a/crates/viewer/re_selection_panel/tests/snapshots/selection_panel_recording_hover_app_id.png +++ b/crates/viewer/re_selection_panel/tests/snapshots/selection_panel_recording_hover_app_id.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f312a64e2ccb51c4a7c1c9b6351449031a5468322b37ecda3126bb5b7bea3163 -size 56283 +oid sha256:9d015aa4c9c2d1aa0858ef57bf420a187d298ad7cc34e0828aa0e77ed3ee51ac +size 53880 diff --git a/crates/viewer/re_viewer/src/app.rs b/crates/viewer/re_viewer/src/app.rs index ce107ef84021..a9121cb33051 100644 --- a/crates/viewer/re_viewer/src/app.rs +++ b/crates/viewer/re_viewer/src/app.rs @@ -2906,7 +2906,7 @@ impl App { } } } else { - re_log::debug_once!("Unknown archetype: {archetype_name}"); + re_log::trace_once!("Unknown archetype: {archetype_name}"); } } } diff --git a/tests/rust/re_integration_test/tests/snapshots/add_visualizer_axes_1.png b/tests/rust/re_integration_test/tests/snapshots/add_visualizer_axes_1.png index 5e128e155644..ed09b5fea57e 100644 --- a/tests/rust/re_integration_test/tests/snapshots/add_visualizer_axes_1.png +++ b/tests/rust/re_integration_test/tests/snapshots/add_visualizer_axes_1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2ccfae35f87c392ee9fb422015c0794111419b803e4573bff81bb4dff11957dd -size 154187 +oid sha256:55c8bcf89cd11c09388b2ca3b2bab4c35e5a2ba83b92083cf051c5c1681478f5 +size 160008 diff --git a/tests/rust/re_integration_test/tests/snapshots/deselect_on_escape_3.png b/tests/rust/re_integration_test/tests/snapshots/deselect_on_escape_3.png index 489461564192..05a16fce27e3 100644 --- a/tests/rust/re_integration_test/tests/snapshots/deselect_on_escape_3.png +++ b/tests/rust/re_integration_test/tests/snapshots/deselect_on_escape_3.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:db7e0b6721f5659fd9a4903ca968be66ca36fd4c2c1decc7a1e792f267a5c55c -size 146950 +oid sha256:979681aa6e8beacd8a43cfa0eb00e7ef1add804bcf00de99c2888c7c8bd503af +size 152756 diff --git a/tests/rust/re_integration_test/tests/snapshots/per_visualizer_instruction_errors_1_warnings_only.png b/tests/rust/re_integration_test/tests/snapshots/per_visualizer_instruction_errors_1_warnings_only.png index 00582fa3e2c1..0cbe222fb66a 100644 --- a/tests/rust/re_integration_test/tests/snapshots/per_visualizer_instruction_errors_1_warnings_only.png +++ b/tests/rust/re_integration_test/tests/snapshots/per_visualizer_instruction_errors_1_warnings_only.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9d1968bd128f5d5607a01cf6c852dab76ddcc760e3a9f382fb2552620522ea20 -size 82332 +oid sha256:e7607e164c5aa675b8c466f81073bd4fa01fe3df4b80c245ecf18e735cb69ba6 +size 88230 diff --git a/tests/rust/re_integration_test/tests/snapshots/per_visualizer_instruction_errors_1b_warnings_only_menu.png b/tests/rust/re_integration_test/tests/snapshots/per_visualizer_instruction_errors_1b_warnings_only_menu.png index 2f2be3b387cf..6f7c2750b06b 100644 --- a/tests/rust/re_integration_test/tests/snapshots/per_visualizer_instruction_errors_1b_warnings_only_menu.png +++ b/tests/rust/re_integration_test/tests/snapshots/per_visualizer_instruction_errors_1b_warnings_only_menu.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:201818d83d5af556680849df214860315392c05921d54384d4f5088e441b0537 -size 93004 +oid sha256:2ffbc9ce0a652d83b10c2f37ff9a1ee56cf5f9990421eb34e977931ecd77f2f7 +size 98901 diff --git a/tests/rust/re_integration_test/tests/snapshots/per_visualizer_instruction_errors_2_warnings_and_errors_menu.png b/tests/rust/re_integration_test/tests/snapshots/per_visualizer_instruction_errors_2_warnings_and_errors_menu.png index 287cb42eeecd..832e24e8ae9e 100644 --- a/tests/rust/re_integration_test/tests/snapshots/per_visualizer_instruction_errors_2_warnings_and_errors_menu.png +++ b/tests/rust/re_integration_test/tests/snapshots/per_visualizer_instruction_errors_2_warnings_and_errors_menu.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8aa9caff8daf56bb3af1bdb19858e22c2e81734c176ef056836b7e6d31332888 -size 103477 +oid sha256:a32996bddce39b388fa4a7564d2448eb14303032bd9e44cd154757d3dc515b04 +size 109391 diff --git a/tests/rust/re_integration_test/tests/snapshots/source_component_1.png b/tests/rust/re_integration_test/tests/snapshots/source_component_1.png index 23810873f066..6df88518cfcd 100644 --- a/tests/rust/re_integration_test/tests/snapshots/source_component_1.png +++ b/tests/rust/re_integration_test/tests/snapshots/source_component_1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8f129951ccde3b7a0bcf6cf3fdb7e6b6c9c8f7562b63de1e69c85cb8abb5f304 -size 180406 +oid sha256:10d32b3718159a701d074567349a40960a28773d3d29f2ea30031b668de23428 +size 186458 From a9f4ca3776e75a7280a769fefb5cf076f3de477a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jochen=20G=C3=B6rtler?= Date: Wed, 11 Mar 2026 11:54:33 +0100 Subject: [PATCH 095/513] Split `re_arrow_combinators` into `re_lenses` and `re_lenses_core` ### Related * Closes RR-3968 ### What Title. The split will be as follows: * `re_lenses_core`: Everything that is needed to execute Lenses in arbitrary environments. * `re_lenses`: A collection of predefined lenses and helpers to convert arbitrary data to Rerun semantic types. ### Compatibility * [x] Update `ARCHITECTURE.md` design diagram Source-Ref: 2895368646b0467201725b29ef4b3320db9cff94 --- ARCHITECTURE.md | 14 ++++---- Cargo.lock | 35 +++++++++---------- Cargo.toml | 2 +- crates/store/re_arrow_combinators/README.md | 14 -------- crates/store/re_arrow_combinators/src/lib.rs | 22 ------------ crates/store/re_data_loader/Cargo.toml | 2 +- .../lenses/foxglove/camera_calibration.rs | 3 +- .../lenses/foxglove/compressed_image.rs | 3 +- .../lenses/foxglove/compressed_video.rs | 3 +- .../lenses/foxglove/frame_transform.rs | 3 +- .../lenses/foxglove/frame_transforms.rs | 3 +- .../src/loader_mcap/lenses/foxglove/log.rs | 5 +-- .../lenses/foxglove/packed_element_field.rs | 16 ++++----- .../lenses/foxglove/point_cloud.rs | 4 +-- .../lenses/foxglove/pose_in_frame.rs | 3 +- .../lenses/foxglove/poses_in_frame.rs | 3 +- .../loader_mcap/lenses/foxglove/raw_image.rs | 3 +- .../src/loader_mcap/lenses/helpers.rs | 14 ++++---- .../src/loader_mcap/lenses/image_helpers.rs | 2 +- crates/store/re_lenses/Cargo.toml | 8 +---- crates/store/re_lenses/src/lib.rs | 11 +++--- crates/store/re_lenses/src/op/basic.rs | 2 +- crates/store/re_lenses/src/op/semantic.rs | 7 ++-- crates/store/re_lenses/src/op/string.rs | 4 +-- .../Cargo.toml | 9 +++-- crates/store/re_lenses_core/README.md | 15 ++++++++ .../{re_lenses => re_lenses_core}/src/ast.rs | 34 +++++++++--------- .../src/builder.rs | 2 +- .../src/combinators}/cast.rs | 2 +- .../src/combinators}/error.rs | 2 +- .../src/combinators}/index.rs | 2 +- .../src/combinators}/map.rs | 2 +- .../re_lenses_core/src/combinators/mod.rs | 21 +++++++++++ .../src/combinators}/reshape.rs | 2 +- .../src/combinators}/transform.rs | 2 +- .../src/lens_error.rs} | 6 ++-- crates/store/re_lenses_core/src/lib.rs | 25 +++++++++++++ .../src/selector/lexer.rs | 0 .../src/selector/mod.rs | 22 ++++++++---- .../src/selector/parser.rs | 0 .../src/selector/runtime.rs | 20 +++++------ .../tests/test_explode.rs | 3 +- .../tests/test_selector.rs | 12 +++---- .../tests/test_string_transforms.rs | 4 +-- .../tests/test_transform.rs | 10 +++--- .../tests/util.rs | 0 crates/top/re_sdk/Cargo.toml | 2 +- crates/top/re_sdk/src/lenses/mod.rs | 3 +- crates/top/re_sdk/tests/lenses/operations.rs | 4 +-- crates/utils/re_backoff/README.md | 4 +-- crates/viewer/re_selection_panel/Cargo.toml | 2 +- .../re_selection_panel/src/visualizer_ui.rs | 8 ++--- crates/viewer/re_view/Cargo.toml | 2 +- crates/viewer/re_view/src/lib.rs | 4 +-- crates/viewer/re_view/src/query.rs | 10 +++--- crates/viewer/re_viewer_context/Cargo.toml | 2 +- .../src/typed_entity_collections.rs | 2 +- .../src/view/visualizability_constraints.rs | 4 +-- 58 files changed, 229 insertions(+), 199 deletions(-) delete mode 100644 crates/store/re_arrow_combinators/README.md delete mode 100644 crates/store/re_arrow_combinators/src/lib.rs rename crates/store/{re_arrow_combinators => re_lenses_core}/Cargo.toml (67%) create mode 100644 crates/store/re_lenses_core/README.md rename crates/store/{re_lenses => re_lenses_core}/src/ast.rs (96%) rename crates/store/{re_lenses => re_lenses_core}/src/builder.rs (99%) rename crates/store/{re_arrow_combinators/src => re_lenses_core/src/combinators}/cast.rs (98%) rename crates/store/{re_arrow_combinators/src => re_lenses_core/src/combinators}/error.rs (98%) rename crates/store/{re_arrow_combinators/src => re_lenses_core/src/combinators}/index.rs (99%) rename crates/store/{re_arrow_combinators/src => re_lenses_core/src/combinators}/map.rs (99%) create mode 100644 crates/store/re_lenses_core/src/combinators/mod.rs rename crates/store/{re_arrow_combinators/src => re_lenses_core/src/combinators}/reshape.rs (99%) rename crates/store/{re_arrow_combinators/src => re_lenses_core/src/combinators}/transform.rs (98%) rename crates/store/{re_lenses/src/error.rs => re_lenses_core/src/lens_error.rs} (92%) create mode 100644 crates/store/re_lenses_core/src/lib.rs rename crates/store/{re_arrow_combinators => re_lenses_core}/src/selector/lexer.rs (100%) rename crates/store/{re_arrow_combinators => re_lenses_core}/src/selector/mod.rs (92%) rename crates/store/{re_arrow_combinators => re_lenses_core}/src/selector/parser.rs (100%) rename crates/store/{re_arrow_combinators => re_lenses_core}/src/selector/runtime.rs (89%) rename crates/store/{re_arrow_combinators => re_lenses_core}/tests/test_explode.rs (99%) rename crates/store/{re_arrow_combinators => re_lenses_core}/tests/test_selector.rs (98%) rename crates/store/{re_arrow_combinators => re_lenses_core}/tests/test_string_transforms.rs (98%) rename crates/store/{re_arrow_combinators => re_lenses_core}/tests/test_transform.rs (98%) rename crates/store/{re_arrow_combinators => re_lenses_core}/tests/util.rs (100%) diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index fe4d551f31db..c42c0459388c 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -99,11 +99,11 @@ Of course, this will only take us so far. In the future we plan on caching queri Here is an overview of the crates included in the project: - - - - - + + + + + - Add (and document) time range and selection support to `segment_url` [b2e7eff](https://github.com/rerun-io/rerun/commit/b2e7eff71b53e75d3a6d34c1804773ccc483f648) -- Improve `DynamicArchetype` docs with example on how to use builtin batch types [ccdfe29](https://github.com/rerun-io/rerun/commit/ccdfe2924e3e4b73379a13c4e273ca9b22d6aae8) #### 🦀 Rust API - Basic Rust & Python blueprint API for component mappings [c6d7409](https://github.com/rerun-io/rerun/commit/c6d7409bcd94402e219b4d31c682fef52eb3b340) @@ -225,7 +295,6 @@ For more details, see the [custom visualizer example](https://github.com/rerun-i #### 🪳 Bug fixes - Fix first-person camera having zero speed in zero-sized scenes [#12535](https://github.com/rerun-io/rerun/pull/12535) (thanks [@Shivam-Bhardwaj](https://github.com/Shivam-Bhardwaj)!) -- Rust `BlueprintActivation` default now matches python behavior [3f85747](https://github.com/rerun-io/rerun/commit/3f8574733c537038d6aee67c58e2b4967f1d21ea) - Fix heuristic for `target_frame` in 3D views for scenes with pinholes & named frames [3c678cc](https://github.com/rerun-io/rerun/commit/3c678cccb0e0e64bbaa2f02c6d6b0ed270bd319d) - Fix `sensor_msgs::PointCloud2` MCAP parser for small pointclouds [6491b95](https://github.com/rerun-io/rerun/commit/6491b955fb3d4d31f14ef0a6ac0d41398c3d3cf4) - Bug fix: allow copying selected text [4094a91](https://github.com/rerun-io/rerun/commit/4094a918637ad60efb27677701e0ae4d86e1b1a6) @@ -283,7 +352,6 @@ For more details, see the [custom visualizer example](https://github.com/rerun-i - Allow loading extensionless http urls via magic bytes detection [daf7a35](https://github.com/rerun-io/rerun/commit/daf7a35ebd5b25a3b53c7fda9d4b6c77dee0e705) - Fixes performance issue of too many time series plots [a74b382](https://github.com/rerun-io/rerun/commit/a74b382b9ce0b413f14d96d4a2f264e1f4b2abe8) - Support `(U)Int16` in time series plots [6bb58e4](https://github.com/rerun-io/rerun/commit/6bb58e489ba76c1744599e222180f9a358720933) -- Support custom bool types in plots [fe8d955](https://github.com/rerun-io/rerun/commit/fe8d955ab3053d5aa388455a474abf8bcb469dda) #### 🗄️ OSS server - Test handling of schema conflict and make OSS server compliant [c618910](https://github.com/rerun-io/rerun/commit/c6189106664d039a28312f1ac3007de99c979dc5) @@ -313,7 +381,6 @@ For more details, see the [custom visualizer example](https://github.com/rerun-i - Add documentation for converting custom data to rrd using log/send_column [f8cf13c](https://github.com/rerun-io/rerun/commit/f8cf13c6686b0dd06e518ec0b8ebe24017bc75ab) - Update MCAP message support documentation [a77922a](https://github.com/rerun-io/rerun/commit/a77922a46338cc1037d5c97ef9680e9879c86f2f) - Add layer identifier "foxglove" to `rerun mcap convert` [1436027](https://github.com/rerun-io/rerun/commit/14360275483603361d0c05ce522046344b226fed) -- Add generalized example (snippet + doc page) for component mappings [82da40f](https://github.com/rerun-io/rerun/commit/82da40f5959d238b1f426f802e16be1ab1782810) #### 🖼 UI improvements - Show that other timelines have data on timeline loader [47bf28f](https://github.com/rerun-io/rerun/commit/47bf28f7377bf09230ddf38151db43c58cfdc3fa) From 9c16704ff64747ee62845be03a828dc61d54780f Mon Sep 17 00:00:00 2001 From: Jash Shah <49280550+jashshah999@users.noreply.github.com> Date: Thu, 12 Mar 2026 03:26:57 -0700 Subject: [PATCH 106/513] Reuse precomputed timelines dict in send_columns instead of re-iterating indexes (#12673) `send_columns()` iterated over `indexes` to build a `timelines_args` dict with validation, but then passed a new dict comprehension to `send_arrow_chunk` that re-iterated `indexes` and called `as_arrow_array()` again. Two problems: 1. If `indexes` is a generator or single-use iterable, the second iteration produces an empty dict, silently sending data without any timeline information. 2. Even with a list, it redundantly calls `as_arrow_array()` and `timeline_name()` on every index a second time. Fix: use the already-built `timelines_args` dict. Source-Ref: 02844ea43384ab6837202520e9034b6ec8a11553 --- rerun_py/rerun_sdk/rerun/_send_columns.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rerun_py/rerun_sdk/rerun/_send_columns.py b/rerun_py/rerun_sdk/rerun/_send_columns.py index 6b98f9ae24ad..8694b5d17198 100644 --- a/rerun_py/rerun_sdk/rerun/_send_columns.py +++ b/rerun_py/rerun_sdk/rerun/_send_columns.py @@ -215,7 +215,7 @@ def send_columns( bindings.send_arrow_chunk( entity_path, - timelines={t.timeline_name(): t.as_arrow_array() for t in indexes}, + timelines=timelines_args, components=columns_args, recording=recording.to_native() if recording is not None else None, ) From 9d54afddd516aa6ca2e6768f4929bec31d049d67 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 12 Mar 2026 12:00:13 +0100 Subject: [PATCH 107/513] Change default fill mode for primitives Adds new fill mode called `TransparentFillMajorWireframe` that is the new default. Looks like this: image Screenshot 2026-03-11 at 13 56 28 Source-Ref: 82d817b046cc3e47eabbf6d09d3f19810e510c3d --- .../rerun/components/fill_mode.fbs | 7 +- .../re_sdk_types/src/components/fill_mode.rs | 21 ++- .../src/components/fill_mode_ext.rs | 34 ++++- .../FillMode_placeholder.png | 4 +- .../FillMode_placeholder.png | 4 +- .../re_view_spatial/src/shared_fallbacks.rs | 12 ++ .../src/visualizers/boxes3d.rs | 13 +- .../src/visualizers/capsules3d.rs | 7 +- .../src/visualizers/cylinders3d.rs | 7 +- .../src/visualizers/ellipsoids.rs | 7 +- .../visualizers/utilities/proc_mesh_vis.rs | 125 ++++++++++-------- ...h_base_transform_EntityHierarchy_boxes.png | 4 +- ...base_transform_EntityHierarchy_spheres.png | 4 +- ...th_base_transform_FrameHierarchy_boxes.png | 4 +- ..._base_transform_FrameHierarchy_spheres.png | 4 +- ...lamping_with_base_transform_None_boxes.png | 4 +- ...mping_with_base_transform_None_spheres.png | 4 +- ...rch_fallback_rerun.archetypes.Boxes3D.snap | 2 +- ..._fallback_rerun.archetypes.Capsules3D.snap | 2 +- ...fallback_rerun.archetypes.Cylinders3D.snap | 2 +- ...allback_rerun.archetypes.Ellipsoids3D.snap | 2 +- .../reference/types/components/fill_mode.md | 5 + rerun_cpp/src/rerun/components/fill_mode.hpp | 5 + .../rerun_sdk/rerun/components/fill_mode.py | 31 ++++- .../tests/snapshots/add_visualizer_axes_1.png | 4 +- .../tests/snapshots/add_visualizer_axes_2.png | 4 +- .../tests/snapshots/add_visualizer_axes_3.png | 4 +- .../tests/snapshots/add_visualizer_axes_4.png | 4 +- .../tests/snapshots/add_visualizer_axes_5.png | 4 +- .../tests/snapshots/deselect_on_escape_1.png | 4 +- .../tests/snapshots/deselect_on_escape_2.png | 4 +- .../tests/snapshots/deselect_on_escape_3.png | 4 +- .../tests/snapshots/deselect_on_escape_4.png | 4 +- .../tests/snapshots/deselect_on_escape_5.png | 4 +- .../tests/snapshots/deselect_on_escape_6.png | 4 +- .../tests/snapshots/deselect_on_escape_7.png | 4 +- .../tests/snapshots/deselect_on_escape_8.png | 4 +- .../snapshots/heuristics_mixed_2d_and_3d.png | 4 +- .../snapshots/heuristics_mixed_all_root.png | 4 +- .../tests/snapshots/origin_camera_2d.png | 4 +- .../tests/snapshots/origin_camera_3d.png | 4 +- .../tests/snapshots/origin_image_2d.png | 4 +- .../tests/snapshots/origin_image_3d.png | 4 +- .../tests/snapshots/origin_keypoint_2d.png | 4 +- .../tests/snapshots/origin_keypoint_3d.png | 4 +- .../tests/snapshots/origin_root_2d.png | 4 +- .../tests/snapshots/origin_root_3d.png | 4 +- .../tests/snapshots/origin_world_2d.png | 4 +- .../tests/snapshots/origin_world_3d.png | 4 +- 49 files changed, 261 insertions(+), 153 deletions(-) diff --git a/crates/store/re_sdk_types/definitions/rerun/components/fill_mode.fbs b/crates/store/re_sdk_types/definitions/rerun/components/fill_mode.fbs index caf9ec087ac8..57516fc9597f 100644 --- a/crates/store/re_sdk_types/definitions/rerun/components/fill_mode.fbs +++ b/crates/store/re_sdk_types/definitions/rerun/components/fill_mode.fbs @@ -19,7 +19,7 @@ enum FillMode: ubyte{ /// * An [archetypes.Ellipsoids3D] will draw three axis-aligned ellipses that are cross-sections /// of each ellipsoid, each of which displays two out of three of the sizes of the ellipsoid. /// * For [archetypes.Boxes3D], it is the edges of the box, identical to [components.FillMode.DenseWireframe]. - MajorWireframe (default), + MajorWireframe, /// Many lines are drawn to represent the surface of the shape in a see-through fashion. /// @@ -32,4 +32,9 @@ enum FillMode: ubyte{ /// The surface of the shape is filled in with a solid color. No lines are drawn. Solid, + + /// The surface of the shape is filled in with a transparent color, with major wireframe lines on top. + /// + /// This gives a good default appearance that shows both the shape's surface and its structure. + TransparentFillMajorWireframe (default), } diff --git a/crates/store/re_sdk_types/src/components/fill_mode.rs b/crates/store/re_sdk_types/src/components/fill_mode.rs index eff4605906a2..bfa0f2155024 100644 --- a/crates/store/re_sdk_types/src/components/fill_mode.rs +++ b/crates/store/re_sdk_types/src/components/fill_mode.rs @@ -33,7 +33,6 @@ pub enum FillMode { /// * An [`archetypes::Ellipsoids3D`][crate::archetypes::Ellipsoids3D] will draw three axis-aligned ellipses that are cross-sections /// of each ellipsoid, each of which displays two out of three of the sizes of the ellipsoid. /// * For [`archetypes::Boxes3D`][crate::archetypes::Boxes3D], it is the edges of the box, identical to [`components::FillMode::DenseWireframe`][crate::components::FillMode::DenseWireframe]. - #[default] MajorWireframe = 1, /// Many lines are drawn to represent the surface of the shape in a see-through fashion. @@ -47,6 +46,12 @@ pub enum FillMode { /// The surface of the shape is filled in with a solid color. No lines are drawn. Solid = 3, + + /// The surface of the shape is filled in with a transparent color, with major wireframe lines on top. + /// + /// This gives a good default appearance that shows both the shape's surface and its structure. + #[default] + TransparentFillMajorWireframe = 4, } impl ::re_types_core::Component for FillMode { @@ -121,6 +126,7 @@ impl ::re_types_core::Loggable for FillMode { Some(1) => Ok(Some(Self::MajorWireframe)), Some(2) => Ok(Some(Self::DenseWireframe)), Some(3) => Ok(Some(Self::Solid)), + Some(4) => Ok(Some(Self::TransparentFillMajorWireframe)), None => Ok(None), Some(invalid) => Err(DeserializationError::missing_union_arm( Self::arrow_datatype(), @@ -139,6 +145,9 @@ impl std::fmt::Display for FillMode { Self::MajorWireframe => write!(f, "MajorWireframe"), Self::DenseWireframe => write!(f, "DenseWireframe"), Self::Solid => write!(f, "Solid"), + Self::TransparentFillMajorWireframe => { + write!(f, "TransparentFillMajorWireframe") + } } } } @@ -146,7 +155,12 @@ impl std::fmt::Display for FillMode { impl ::re_types_core::reflection::Enum for FillMode { #[inline] fn variants() -> &'static [Self] { - &[Self::MajorWireframe, Self::DenseWireframe, Self::Solid] + &[ + Self::MajorWireframe, + Self::DenseWireframe, + Self::Solid, + Self::TransparentFillMajorWireframe, + ] } #[inline] @@ -161,6 +175,9 @@ impl ::re_types_core::reflection::Enum for FillMode { Self::Solid => { "The surface of the shape is filled in with a solid color. No lines are drawn." } + Self::TransparentFillMajorWireframe => { + "The surface of the shape is filled in with a transparent color, with major wireframe lines on top.\n\nThis gives a good default appearance that shows both the shape's surface and its structure." + } } } } diff --git a/crates/store/re_sdk_types/src/components/fill_mode_ext.rs b/crates/store/re_sdk_types/src/components/fill_mode_ext.rs index 9096be0cb444..6de1be278047 100644 --- a/crates/store/re_sdk_types/src/components/fill_mode_ext.rs +++ b/crates/store/re_sdk_types/src/components/fill_mode_ext.rs @@ -1,6 +1,32 @@ use super::FillMode; impl FillMode { + /// Does this fill mode include wireframe lines? + pub fn has_wireframe(self) -> bool { + match self { + Self::MajorWireframe | Self::DenseWireframe | Self::TransparentFillMajorWireframe => { + true + } + Self::Solid => false, + } + } + + /// Does this fill mode include a solid fill? + pub fn has_solid(self) -> bool { + match self { + Self::Solid | Self::TransparentFillMajorWireframe => true, + Self::MajorWireframe | Self::DenseWireframe => false, + } + } + + /// Should we only draw the major axes, or the full mesh? + pub fn axes_only(self) -> bool { + match self { + Self::MajorWireframe | Self::TransparentFillMajorWireframe => true, + Self::DenseWireframe | Self::Solid => false, + } + } + /// Instantiate a new [`FillMode`] from a u8 value. /// /// Returns `None` if the value doesn't match any of the enum's arms. @@ -8,13 +34,19 @@ impl FillMode { // NOTE: This code will be optimized out, it's only here to make sure this method fails to // compile if the enum is modified. match Self::default() { - Self::MajorWireframe | Self::DenseWireframe | Self::Solid => {} + Self::MajorWireframe + | Self::DenseWireframe + | Self::Solid + | Self::TransparentFillMajorWireframe => {} } match value { v if v == Self::MajorWireframe as u8 => Some(Self::MajorWireframe), v if v == Self::DenseWireframe as u8 => Some(Self::DenseWireframe), v if v == Self::Solid as u8 => Some(Self::Solid), + v if v == Self::TransparentFillMajorWireframe as u8 => { + Some(Self::TransparentFillMajorWireframe) + } _ => None, } } diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/FillMode_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/FillMode_placeholder.png index 1960364484d4..c6574d30dd88 100644 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/FillMode_placeholder.png +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/FillMode_placeholder.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b7c81412cf19d9d20622331b0c3cac7b8d6368fb5b3f626d843467863ab06664 -size 4182 +oid sha256:5f7b134a0e2ada57bf4ff8bbe01cf06e72ca9160f3802bd5c9b5de947bf20679 +size 4153 diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/FillMode_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/FillMode_placeholder.png index a0a256874a46..74acc9d77f15 100644 --- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/FillMode_placeholder.png +++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/FillMode_placeholder.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5cd0f23e9f605c00dbeafa05d6e15c2694ac7c9b32fef3555e5fb215c23e39b9 -size 4958 +oid sha256:30eb9a98301a990267b62aa1e82d3beb23dff08f59af5f95ce2bb824bf55f7c3 +size 6695 diff --git a/crates/viewer/re_view_spatial/src/shared_fallbacks.rs b/crates/viewer/re_view_spatial/src/shared_fallbacks.rs index e8cb4ec34252..95d1ffbaea4c 100644 --- a/crates/viewer/re_view_spatial/src/shared_fallbacks.rs +++ b/crates/viewer/re_view_spatial/src/shared_fallbacks.rs @@ -40,6 +40,18 @@ pub fn register_fallbacks(system_registry: &mut re_viewer_context::ViewSystemReg opacity_fallback(ImageKind::Segmentation), ); + // Line radii for solid primitives (thinner than the global default). + // This looks nicer with the default `FillMode::TransparentFillMajorWireframe`. + for component in [ + archetypes::Boxes3D::descriptor_radii().component, + archetypes::Ellipsoids3D::descriptor_line_radii().component, + archetypes::Capsules3D::descriptor_line_radii().component, + archetypes::Cylinders3D::descriptor_line_radii().component, + ] { + system_registry + .register_fallback_provider(component, |_ctx| components::Radius::new_ui_points(0.5)); + } + // Pinhole system_registry.register_fallback_provider( archetypes::Pinhole::descriptor_image_plane_distance().component, diff --git a/crates/viewer/re_view_spatial/src/visualizers/boxes3d.rs b/crates/viewer/re_view_spatial/src/visualizers/boxes3d.rs index f20650e72ffc..1e0b48cf0537 100644 --- a/crates/viewer/re_view_spatial/src/visualizers/boxes3d.rs +++ b/crates/viewer/re_view_spatial/src/visualizers/boxes3d.rs @@ -173,15 +173,10 @@ impl VisualizerSystem for Boxes3DVisualizer { }) .unwrap_or_default(); - match fill_mode { - FillMode::DenseWireframe | FillMode::MajorWireframe => { - // Each box consists of 4 strips with a total of 16 vertices - builder.line_builder.reserve_strips(num_boxes * 4)?; - builder.line_builder.reserve_vertices(num_boxes * 16)?; - } - FillMode::Solid => { - // No lines. - } + if fill_mode.has_wireframe() { + // Each box consists of 4 strips with a total of 16 vertices + builder.line_builder.reserve_strips(num_boxes * 4)?; + builder.line_builder.reserve_vertices(num_boxes * 16)?; } let data = re_query::range_zip_1x8( diff --git a/crates/viewer/re_view_spatial/src/visualizers/capsules3d.rs b/crates/viewer/re_view_spatial/src/visualizers/capsules3d.rs index 3d1948491bf1..d573c802e519 100644 --- a/crates/viewer/re_view_spatial/src/visualizers/capsules3d.rs +++ b/crates/viewer/re_view_spatial/src/visualizers/capsules3d.rs @@ -59,14 +59,11 @@ impl Capsules3DVisualizer { let subdivisions = match batch.fill_mode { FillMode::DenseWireframe => 3, // Don't make it too crowded - let the user see inside the mesh. - FillMode::Solid => 4, // Smooth, but not too CPU/GPU intensive + FillMode::Solid | FillMode::TransparentFillMajorWireframe => 4, // Smooth, but not too CPU/GPU intensive FillMode::MajorWireframe => 10, }; - let axes_only = match batch.fill_mode { - FillMode::MajorWireframe => true, - FillMode::DenseWireframe | FillMode::Solid => false, - }; + let axes_only = batch.fill_mode.axes_only(); let meshes = lengths_iter .zip(radii.iter()) diff --git a/crates/viewer/re_view_spatial/src/visualizers/cylinders3d.rs b/crates/viewer/re_view_spatial/src/visualizers/cylinders3d.rs index 0df40e589512..2b21e72044df 100644 --- a/crates/viewer/re_view_spatial/src/visualizers/cylinders3d.rs +++ b/crates/viewer/re_view_spatial/src/visualizers/cylinders3d.rs @@ -68,14 +68,11 @@ impl Cylinders3DVisualizer { let subdivisions = match batch.fill_mode { FillMode::DenseWireframe => 3, // Don't make it too crowded - let the user see inside the mesh. - FillMode::Solid => 4, // Smooth, but not too CPU/GPU intensive + FillMode::Solid | FillMode::TransparentFillMajorWireframe => 4, // Smooth, but not too CPU/GPU intensive FillMode::MajorWireframe => 10, }; - let axes_only = match batch.fill_mode { - FillMode::MajorWireframe => true, - FillMode::DenseWireframe | FillMode::Solid => false, - }; + let axes_only = batch.fill_mode.axes_only(); let proc_mesh_key = proc_mesh::ProcMeshKey::Cylinder { subdivisions, diff --git a/crates/viewer/re_view_spatial/src/visualizers/ellipsoids.rs b/crates/viewer/re_view_spatial/src/visualizers/ellipsoids.rs index 8cbb6cad5949..a162d0bd93e7 100644 --- a/crates/viewer/re_view_spatial/src/visualizers/ellipsoids.rs +++ b/crates/viewer/re_view_spatial/src/visualizers/ellipsoids.rs @@ -41,15 +41,12 @@ impl Ellipsoids3DVisualizer { // either world size or screen size (depending on application). let subdivisions = match batch.fill_mode { FillMode::DenseWireframe => 2, // Don't make it too crowded - let the user see inside the mesh. - FillMode::Solid => 6, // Smooth, but not too CPU/GPU intensive + FillMode::Solid | FillMode::TransparentFillMajorWireframe => 6, // Smooth, but not too CPU/GPU intensive FillMode::MajorWireframe => 12, // Three smooth ellipses }; let proc_mesh_key = proc_mesh::ProcMeshKey::Sphere { subdivisions, - axes_only: match batch.fill_mode { - FillMode::MajorWireframe => true, - FillMode::DenseWireframe | FillMode::Solid => false, - }, + axes_only: batch.fill_mode.axes_only(), }; builder.add_batch( diff --git a/crates/viewer/re_view_spatial/src/visualizers/utilities/proc_mesh_vis.rs b/crates/viewer/re_view_spatial/src/visualizers/utilities/proc_mesh_vis.rs index acd0300f42c6..39e485a69920 100644 --- a/crates/viewer/re_view_spatial/src/visualizers/utilities/proc_mesh_vis.rs +++ b/crates/viewer/re_view_spatial/src/visualizers/utilities/proc_mesh_vis.rs @@ -237,63 +237,82 @@ impl<'ctx> ProcMeshDrawableBuilder<'ctx> { .transform_affine3(&world_from_instance), ); - match fill_mode { - FillMode::MajorWireframe | FillMode::DenseWireframe => { - let Some(wireframe_mesh) = query_context.store_ctx().caches.entry( - |c: &mut proc_mesh::WireframeCache| c.entry(proc_mesh_key, self.render_ctx), - ) else { - return Err(ViewSystemExecutionError::DrawDataCreationError( - "Failed to allocate wireframe mesh".into(), - )); - }; - - for strip in &wireframe_mesh.line_strips { - let strip_builder = line_batch - .add_strip( - strip - .iter() - .map(|&point| world_from_instance.transform_point3(point)), - ) - .color(color) - .radius(radius) - .picking_instance_id(PickingLayerInstanceId(instance_index as _)) - // Looped lines should be connected with rounded corners. - .flags(LineStripFlags::FLAGS_OUTWARD_EXTENDING_ROUND_CAPS); - - if let Some(outline_mask_ids) = ent_context - .highlight - .instances - .get(&Instance::from(instance_index as u64)) - { - // Not using ent_context.highlight.index_outline_mask() because - // that's already handled when the builder was created. - strip_builder.outline_mask_ids(*outline_mask_ids); - } - } - } - FillMode::Solid => { - let store_ctx = query_context.store_ctx(); - let Some(solid_mesh) = - store_ctx.caches.entry(|c: &mut proc_mesh::SolidCache| { + let draw_wireframe = fill_mode.has_wireframe(); + let draw_solid = fill_mode.has_solid(); + + if draw_wireframe { + let Some(wireframe_mesh) = + query_context + .store_ctx() + .caches + .entry(|c: &mut proc_mesh::WireframeCache| { c.entry(proc_mesh_key, self.render_ctx) }) - else { - return Err(ViewSystemExecutionError::DrawDataCreationError( - "Failed to allocate solid mesh".into(), - )); - }; - - self.solid_instances.push(GpuMeshInstance { - gpu_mesh: solid_mesh.gpu_mesh, - world_from_mesh: world_from_instance, - outline_mask_ids: ent_context.highlight.index_outline_mask(instance), - picking_layer_id: re_view::picking_layer_id_from_instance_path_hash( - InstancePathHash::instance(entity_path, instance), - ), - additive_tint: color, - }); + else { + return Err(ViewSystemExecutionError::DrawDataCreationError( + "Failed to allocate wireframe mesh".into(), + )); + }; + + for strip in &wireframe_mesh.line_strips { + let strip_builder = line_batch + .add_strip( + strip + .iter() + .map(|&point| world_from_instance.transform_point3(point)), + ) + .color(color) + .radius(radius) + .picking_instance_id(PickingLayerInstanceId(instance_index as _)) + // Looped lines should be connected with rounded corners. + .flags(LineStripFlags::FLAGS_OUTWARD_EXTENDING_ROUND_CAPS); + + if let Some(outline_mask_ids) = ent_context + .highlight + .instances + .get(&Instance::from(instance_index as u64)) + { + // Not using ent_context.highlight.index_outline_mask() because + // that's already handled when the builder was created. + strip_builder.outline_mask_ids(*outline_mask_ids); + } } } + + if draw_solid { + let store_ctx = query_context.store_ctx(); + let Some(solid_mesh) = store_ctx + .caches + .entry(|c: &mut proc_mesh::SolidCache| c.entry(proc_mesh_key, self.render_ctx)) + else { + return Err(ViewSystemExecutionError::DrawDataCreationError( + "Failed to allocate solid mesh".into(), + )); + }; + + let tint = if fill_mode == FillMode::TransparentFillMajorWireframe { + // Make the solid fill transparent so the wireframe shows through. + #[expect(clippy::disallowed_methods)] + re_renderer::Color32::from_rgba_unmultiplied( + color.r(), + color.g(), + color.b(), + color.a() / 4, // TODO(emilk): make configurabe? + ) + } else { + color + }; + + self.solid_instances.push(GpuMeshInstance { + gpu_mesh: solid_mesh.gpu_mesh, + world_from_mesh: world_from_instance, + outline_mask_ids: ent_context.highlight.index_outline_mask(instance), + picking_layer_id: re_view::picking_layer_id_from_instance_path_hash( + InstancePathHash::instance(entity_path, instance), + ), + additive_tint: tint, + }); + } } self.data diff --git a/crates/viewer/re_view_spatial/tests/snapshots/transform_clamping_with_base_transform_EntityHierarchy_boxes.png b/crates/viewer/re_view_spatial/tests/snapshots/transform_clamping_with_base_transform_EntityHierarchy_boxes.png index 33154c4e30c1..8b013369af66 100644 --- a/crates/viewer/re_view_spatial/tests/snapshots/transform_clamping_with_base_transform_EntityHierarchy_boxes.png +++ b/crates/viewer/re_view_spatial/tests/snapshots/transform_clamping_with_base_transform_EntityHierarchy_boxes.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c8e96a40bfd70b4951c59b38d44b112e3aeaee754261384f7d79a1c361d1d85d -size 53419 +oid sha256:086b157ba3363ef21010cd0b470260da6746da9e04d1f5c6bf80bb719f0fc66e +size 52301 diff --git a/crates/viewer/re_view_spatial/tests/snapshots/transform_clamping_with_base_transform_EntityHierarchy_spheres.png b/crates/viewer/re_view_spatial/tests/snapshots/transform_clamping_with_base_transform_EntityHierarchy_spheres.png index 0ad50022dcbd..7c1fd95d225f 100644 --- a/crates/viewer/re_view_spatial/tests/snapshots/transform_clamping_with_base_transform_EntityHierarchy_spheres.png +++ b/crates/viewer/re_view_spatial/tests/snapshots/transform_clamping_with_base_transform_EntityHierarchy_spheres.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9afaaf4070a990e9c0f31a8e0f2d21869a74be29c55df084e96c6c71ad5db2a0 -size 54240 +oid sha256:4059aa0e4d55a0ae1b7ef49688b904bdc0fef4b4448bb25d32c79bc0876948c4 +size 57220 diff --git a/crates/viewer/re_view_spatial/tests/snapshots/transform_clamping_with_base_transform_FrameHierarchy_boxes.png b/crates/viewer/re_view_spatial/tests/snapshots/transform_clamping_with_base_transform_FrameHierarchy_boxes.png index 33154c4e30c1..8b013369af66 100644 --- a/crates/viewer/re_view_spatial/tests/snapshots/transform_clamping_with_base_transform_FrameHierarchy_boxes.png +++ b/crates/viewer/re_view_spatial/tests/snapshots/transform_clamping_with_base_transform_FrameHierarchy_boxes.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c8e96a40bfd70b4951c59b38d44b112e3aeaee754261384f7d79a1c361d1d85d -size 53419 +oid sha256:086b157ba3363ef21010cd0b470260da6746da9e04d1f5c6bf80bb719f0fc66e +size 52301 diff --git a/crates/viewer/re_view_spatial/tests/snapshots/transform_clamping_with_base_transform_FrameHierarchy_spheres.png b/crates/viewer/re_view_spatial/tests/snapshots/transform_clamping_with_base_transform_FrameHierarchy_spheres.png index 0ad50022dcbd..7c1fd95d225f 100644 --- a/crates/viewer/re_view_spatial/tests/snapshots/transform_clamping_with_base_transform_FrameHierarchy_spheres.png +++ b/crates/viewer/re_view_spatial/tests/snapshots/transform_clamping_with_base_transform_FrameHierarchy_spheres.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9afaaf4070a990e9c0f31a8e0f2d21869a74be29c55df084e96c6c71ad5db2a0 -size 54240 +oid sha256:4059aa0e4d55a0ae1b7ef49688b904bdc0fef4b4448bb25d32c79bc0876948c4 +size 57220 diff --git a/crates/viewer/re_view_spatial/tests/snapshots/transform_clamping_with_base_transform_None_boxes.png b/crates/viewer/re_view_spatial/tests/snapshots/transform_clamping_with_base_transform_None_boxes.png index 732a9e7922cd..771629ccd735 100644 --- a/crates/viewer/re_view_spatial/tests/snapshots/transform_clamping_with_base_transform_None_boxes.png +++ b/crates/viewer/re_view_spatial/tests/snapshots/transform_clamping_with_base_transform_None_boxes.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3e11b7b2f76e14eff1e7f903e5edccf353cdee9669bac41d1f034bee497508ea -size 54895 +oid sha256:1c2471b3a9f67ac65643685a9e3261acb57ae91c9618e1391081341741d5c4e6 +size 52984 diff --git a/crates/viewer/re_view_spatial/tests/snapshots/transform_clamping_with_base_transform_None_spheres.png b/crates/viewer/re_view_spatial/tests/snapshots/transform_clamping_with_base_transform_None_spheres.png index 1f3ce8ae88ca..cddcf588b035 100644 --- a/crates/viewer/re_view_spatial/tests/snapshots/transform_clamping_with_base_transform_None_spheres.png +++ b/crates/viewer/re_view_spatial/tests/snapshots/transform_clamping_with_base_transform_None_spheres.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0ca6194d3555fc768382a408251fff4d375980b53610e3570163be5b681e24e0 -size 55391 +oid sha256:34ece0ed62ef152f0c00002005eab12c99b9144276f84ea9b6c645eab76e17cc +size 59471 diff --git a/crates/viewer/re_viewer/tests/snapshots/all_component_fallbacks__arch_fallback_rerun.archetypes.Boxes3D.snap b/crates/viewer/re_viewer/tests/snapshots/all_component_fallbacks__arch_fallback_rerun.archetypes.Boxes3D.snap index 4b01dfa88f72..34ec4e718788 100644 --- a/crates/viewer/re_viewer/tests/snapshots/all_component_fallbacks__arch_fallback_rerun.archetypes.Boxes3D.snap +++ b/crates/viewer/re_viewer/tests/snapshots/all_component_fallbacks__arch_fallback_rerun.archetypes.Boxes3D.snap @@ -8,7 +8,7 @@ rotation_axis_angles: [{axis: [1.0, 0.0, 0.0], angle: 0.0}] quaternions: [[0.0, 0.0, 0.0, 1.0]] colors: [3161148927] radii: [-1.5] -fill_mode: [1] +fill_mode: [4] labels: [] show_labels: [true] class_ids: [0] diff --git a/crates/viewer/re_viewer/tests/snapshots/all_component_fallbacks__arch_fallback_rerun.archetypes.Capsules3D.snap b/crates/viewer/re_viewer/tests/snapshots/all_component_fallbacks__arch_fallback_rerun.archetypes.Capsules3D.snap index bae1cee1054c..c54264b0d341 100644 --- a/crates/viewer/re_viewer/tests/snapshots/all_component_fallbacks__arch_fallback_rerun.archetypes.Capsules3D.snap +++ b/crates/viewer/re_viewer/tests/snapshots/all_component_fallbacks__arch_fallback_rerun.archetypes.Capsules3D.snap @@ -9,7 +9,7 @@ rotation_axis_angles: [{axis: [1.0, 0.0, 0.0], angle: 0.0}] quaternions: [[0.0, 0.0, 0.0, 1.0]] colors: [3161148927] line_radii: [-1.5] -fill_mode: [1] +fill_mode: [4] labels: [] show_labels: [true] class_ids: [0] diff --git a/crates/viewer/re_viewer/tests/snapshots/all_component_fallbacks__arch_fallback_rerun.archetypes.Cylinders3D.snap b/crates/viewer/re_viewer/tests/snapshots/all_component_fallbacks__arch_fallback_rerun.archetypes.Cylinders3D.snap index c82b1edb3073..66f19fd682d8 100644 --- a/crates/viewer/re_viewer/tests/snapshots/all_component_fallbacks__arch_fallback_rerun.archetypes.Cylinders3D.snap +++ b/crates/viewer/re_viewer/tests/snapshots/all_component_fallbacks__arch_fallback_rerun.archetypes.Cylinders3D.snap @@ -9,7 +9,7 @@ rotation_axis_angles: [{axis: [1.0, 0.0, 0.0], angle: 0.0}] quaternions: [[0.0, 0.0, 0.0, 1.0]] colors: [3161148927] line_radii: [-1.5] -fill_mode: [1] +fill_mode: [4] labels: [] show_labels: [true] class_ids: [0] diff --git a/crates/viewer/re_viewer/tests/snapshots/all_component_fallbacks__arch_fallback_rerun.archetypes.Ellipsoids3D.snap b/crates/viewer/re_viewer/tests/snapshots/all_component_fallbacks__arch_fallback_rerun.archetypes.Ellipsoids3D.snap index 480445ee030a..375f96befcaf 100644 --- a/crates/viewer/re_viewer/tests/snapshots/all_component_fallbacks__arch_fallback_rerun.archetypes.Ellipsoids3D.snap +++ b/crates/viewer/re_viewer/tests/snapshots/all_component_fallbacks__arch_fallback_rerun.archetypes.Ellipsoids3D.snap @@ -8,7 +8,7 @@ rotation_axis_angles: [{axis: [1.0, 0.0, 0.0], angle: 0.0}] quaternions: [[0.0, 0.0, 0.0, 1.0]] colors: [3161148927] line_radii: [-1.5] -fill_mode: [1] +fill_mode: [4] labels: [] show_labels: [true] class_ids: [0] diff --git a/docs/content/reference/types/components/fill_mode.md b/docs/content/reference/types/components/fill_mode.md index 851b57f05187..d1f1a63b6649 100644 --- a/docs/content/reference/types/components/fill_mode.md +++ b/docs/content/reference/types/components/fill_mode.md @@ -27,6 +27,11 @@ Examples of what this means: #### `Solid` = 3 The surface of the shape is filled in with a solid color. No lines are drawn. +#### `TransparentFillMajorWireframe` = 4 +The surface of the shape is filled in with a transparent color, with major wireframe lines on top. + +This gives a good default appearance that shows both the shape's surface and its structure. + ## Arrow datatype ``` diff --git a/rerun_cpp/src/rerun/components/fill_mode.hpp b/rerun_cpp/src/rerun/components/fill_mode.hpp index fc0a0e75f568..c3f3e00b6b3f 100644 --- a/rerun_cpp/src/rerun/components/fill_mode.hpp +++ b/rerun_cpp/src/rerun/components/fill_mode.hpp @@ -43,6 +43,11 @@ namespace rerun::components { /// The surface of the shape is filled in with a solid color. No lines are drawn. Solid = 3, + + /// The surface of the shape is filled in with a transparent color, with major wireframe lines on top. + /// + /// This gives a good default appearance that shows both the shape's surface and its structure. + TransparentFillMajorWireframe = 4, }; } // namespace rerun::components diff --git a/rerun_py/rerun_sdk/rerun/components/fill_mode.py b/rerun_py/rerun_sdk/rerun/components/fill_mode.py index fa702c823a85..c8d33df9aba7 100644 --- a/rerun_py/rerun_sdk/rerun/components/fill_mode.py +++ b/rerun_py/rerun_sdk/rerun/components/fill_mode.py @@ -49,6 +49,13 @@ class FillMode(Enum): Solid = 3 """The surface of the shape is filled in with a solid color. No lines are drawn.""" + TransparentFillMajorWireframe = 4 + """ + The surface of the shape is filled in with a transparent color, with major wireframe lines on top. + + This gives a good default appearance that shows both the shape's surface and its structure. + """ + @classmethod def auto(cls, val: str | int | FillMode) -> FillMode: """Best-effort converter, including a case-insensitive string matcher.""" @@ -71,13 +78,33 @@ def __str__(self) -> str: FillModeLike = ( - FillMode | Literal["DenseWireframe", "MajorWireframe", "Solid", "densewireframe", "majorwireframe", "solid"] | int + FillMode + | Literal[ + "DenseWireframe", + "MajorWireframe", + "Solid", + "TransparentFillMajorWireframe", + "densewireframe", + "majorwireframe", + "solid", + "transparentfillmajorwireframe", + ] + | int ) """A type alias for any FillMode-like object.""" FillModeArrayLike = ( FillMode - | Literal["DenseWireframe", "MajorWireframe", "Solid", "densewireframe", "majorwireframe", "solid"] + | Literal[ + "DenseWireframe", + "MajorWireframe", + "Solid", + "TransparentFillMajorWireframe", + "densewireframe", + "majorwireframe", + "solid", + "transparentfillmajorwireframe", + ] | int | Sequence[FillModeLike] ) diff --git a/tests/rust/re_integration_test/tests/snapshots/add_visualizer_axes_1.png b/tests/rust/re_integration_test/tests/snapshots/add_visualizer_axes_1.png index ed09b5fea57e..d36387d2c378 100644 --- a/tests/rust/re_integration_test/tests/snapshots/add_visualizer_axes_1.png +++ b/tests/rust/re_integration_test/tests/snapshots/add_visualizer_axes_1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:55c8bcf89cd11c09388b2ca3b2bab4c35e5a2ba83b92083cf051c5c1681478f5 -size 160008 +oid sha256:e4134de6f345e76e1ea198fdbe143d5d8b7ae1cae4467a567fbe91cba66a33ac +size 157817 diff --git a/tests/rust/re_integration_test/tests/snapshots/add_visualizer_axes_2.png b/tests/rust/re_integration_test/tests/snapshots/add_visualizer_axes_2.png index ba77ee93a02e..7c68eeb7f973 100644 --- a/tests/rust/re_integration_test/tests/snapshots/add_visualizer_axes_2.png +++ b/tests/rust/re_integration_test/tests/snapshots/add_visualizer_axes_2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9a89e32cd58e9e462a8e09c21ae6735ba84849a9c28baac5839d65a327e040e2 -size 187144 +oid sha256:4e096bd35573d266ecec8f66edae7e8cebc5c66a1ab242ed3047da1abed0d233 +size 187414 diff --git a/tests/rust/re_integration_test/tests/snapshots/add_visualizer_axes_3.png b/tests/rust/re_integration_test/tests/snapshots/add_visualizer_axes_3.png index 708e5a9dbcbb..6405b87e7411 100644 --- a/tests/rust/re_integration_test/tests/snapshots/add_visualizer_axes_3.png +++ b/tests/rust/re_integration_test/tests/snapshots/add_visualizer_axes_3.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:de432b931da1583916a57b1895acdd592f8ec743cbf8b8f0d4a9b18cb2f02045 -size 196306 +oid sha256:7b35be6f4bdd149e8c550f7314af83cd5ad5c7c23d5aea7f566ceac652559796 +size 184029 diff --git a/tests/rust/re_integration_test/tests/snapshots/add_visualizer_axes_4.png b/tests/rust/re_integration_test/tests/snapshots/add_visualizer_axes_4.png index 945b28cedd4c..c25e62d95f41 100644 --- a/tests/rust/re_integration_test/tests/snapshots/add_visualizer_axes_4.png +++ b/tests/rust/re_integration_test/tests/snapshots/add_visualizer_axes_4.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:dfa8fa9175e2a7f15e37ab79ec703d66694b692e25e210ab1141e2f298a1cf5d -size 207039 +oid sha256:b3d69b31375ac29658eaabb9744fa8944ccad92e59f892bd81424efa7c41518d +size 191470 diff --git a/tests/rust/re_integration_test/tests/snapshots/add_visualizer_axes_5.png b/tests/rust/re_integration_test/tests/snapshots/add_visualizer_axes_5.png index 94a417c4ca38..2a806f8ba04e 100644 --- a/tests/rust/re_integration_test/tests/snapshots/add_visualizer_axes_5.png +++ b/tests/rust/re_integration_test/tests/snapshots/add_visualizer_axes_5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cb512e5534e58da97323bcb63cf086f6a46f40c3d1a18d0d80366258b18a1c93 -size 207900 +oid sha256:00926de4051a6dbf0423d942cb63d896f1da17807272b5ecb381ef34175e6adb +size 192365 diff --git a/tests/rust/re_integration_test/tests/snapshots/deselect_on_escape_1.png b/tests/rust/re_integration_test/tests/snapshots/deselect_on_escape_1.png index 5cf8592d5a77..049afba5e918 100644 --- a/tests/rust/re_integration_test/tests/snapshots/deselect_on_escape_1.png +++ b/tests/rust/re_integration_test/tests/snapshots/deselect_on_escape_1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5031fbc3bc15c1bd21f7c8e33a0e9193d40407cc8b49be9143e8e16874b2e3bc -size 139276 +oid sha256:fbaab2cde0271845d2e173e70ead96b5ad0ebd5bc2be9184a86aa676b9191e91 +size 129250 diff --git a/tests/rust/re_integration_test/tests/snapshots/deselect_on_escape_2.png b/tests/rust/re_integration_test/tests/snapshots/deselect_on_escape_2.png index 8930f6f86442..3d2115a6acd3 100644 --- a/tests/rust/re_integration_test/tests/snapshots/deselect_on_escape_2.png +++ b/tests/rust/re_integration_test/tests/snapshots/deselect_on_escape_2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a3b2fd93cfc6e88552bbc48c7060b0f6f96fe7fdb89ee1f1cd649ba1f2adf74a -size 175505 +oid sha256:a5c620c33a82943f20eb9e33540f45d05ca96670c235fb31fa9b0f00d76bd239 +size 172889 diff --git a/tests/rust/re_integration_test/tests/snapshots/deselect_on_escape_3.png b/tests/rust/re_integration_test/tests/snapshots/deselect_on_escape_3.png index 05a16fce27e3..a104bb81d3d7 100644 --- a/tests/rust/re_integration_test/tests/snapshots/deselect_on_escape_3.png +++ b/tests/rust/re_integration_test/tests/snapshots/deselect_on_escape_3.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:979681aa6e8beacd8a43cfa0eb00e7ef1add804bcf00de99c2888c7c8bd503af -size 152756 +oid sha256:0bf411ae07b88dc8c18f6988009e13e3ee722d8866d86ac34abc364c3ab38b33 +size 147625 diff --git a/tests/rust/re_integration_test/tests/snapshots/deselect_on_escape_4.png b/tests/rust/re_integration_test/tests/snapshots/deselect_on_escape_4.png index e51d85e749dd..4c0a24e039dc 100644 --- a/tests/rust/re_integration_test/tests/snapshots/deselect_on_escape_4.png +++ b/tests/rust/re_integration_test/tests/snapshots/deselect_on_escape_4.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d47802f8b293622cf4239304c0e65bc6056ed186e2f5d7fb259885f77962ea49 -size 215364 +oid sha256:d37d92be62caa75405142ab147f9a61a0beeef375d4b779fd721a0ea112d43e8 +size 217205 diff --git a/tests/rust/re_integration_test/tests/snapshots/deselect_on_escape_5.png b/tests/rust/re_integration_test/tests/snapshots/deselect_on_escape_5.png index fb5a97dca850..4020929a2671 100644 --- a/tests/rust/re_integration_test/tests/snapshots/deselect_on_escape_5.png +++ b/tests/rust/re_integration_test/tests/snapshots/deselect_on_escape_5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:54f99e243544cf496a31d29d16cd79da987c7f9926a5dc18c57640780c707a7d -size 175616 +oid sha256:a03bf72c3b713fb9fa951aaa736f2e6e72750418739e7e848d9fafcf9194122b +size 173000 diff --git a/tests/rust/re_integration_test/tests/snapshots/deselect_on_escape_6.png b/tests/rust/re_integration_test/tests/snapshots/deselect_on_escape_6.png index dbbc822db939..5111ec6bd5b3 100644 --- a/tests/rust/re_integration_test/tests/snapshots/deselect_on_escape_6.png +++ b/tests/rust/re_integration_test/tests/snapshots/deselect_on_escape_6.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:458ddef61351904be2b797764843c9c27cb12811ade8619f434df5ed61bd62ec -size 192393 +oid sha256:383ddd956a6bc70a829c07e5b9ca93ca06f30400f9a7c697360b50b6fdf3a598 +size 190344 diff --git a/tests/rust/re_integration_test/tests/snapshots/deselect_on_escape_7.png b/tests/rust/re_integration_test/tests/snapshots/deselect_on_escape_7.png index ac5c75a5ae6d..153e0573aef0 100644 --- a/tests/rust/re_integration_test/tests/snapshots/deselect_on_escape_7.png +++ b/tests/rust/re_integration_test/tests/snapshots/deselect_on_escape_7.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9defe8a72840292e51db9f44bdb2ea71256534eb4137e31b3504b58c90415e59 -size 175551 +oid sha256:a630d86f24eb82e5abe831968b24b7d03b3a16790f4dccc10e4baaa59eb1645b +size 172935 diff --git a/tests/rust/re_integration_test/tests/snapshots/deselect_on_escape_8.png b/tests/rust/re_integration_test/tests/snapshots/deselect_on_escape_8.png index 399e8f371498..5e0ec492d3bb 100644 --- a/tests/rust/re_integration_test/tests/snapshots/deselect_on_escape_8.png +++ b/tests/rust/re_integration_test/tests/snapshots/deselect_on_escape_8.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a56cba183b01a528cd798ab0b85880ee8f8ab3a547b4f9874ec6ff283081c322 -size 138371 +oid sha256:e726d7d85f9db0c47b388ad6d91eb7d090d8baa10acc3b290fca169f8c4193ae +size 128348 diff --git a/tests/rust/re_integration_test/tests/snapshots/heuristics_mixed_2d_and_3d.png b/tests/rust/re_integration_test/tests/snapshots/heuristics_mixed_2d_and_3d.png index ee62d97f293c..e8fa41bc639a 100644 --- a/tests/rust/re_integration_test/tests/snapshots/heuristics_mixed_2d_and_3d.png +++ b/tests/rust/re_integration_test/tests/snapshots/heuristics_mixed_2d_and_3d.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:55cdd7e85b779cfc32ac74395e7dc25e389d6c3c62d7835474ab4e9ee91c7f25 -size 99911 +oid sha256:9cb9eb0ef81339200d042c7c0364913fff5000f0681a0940c78194dc3e0f2956 +size 95005 diff --git a/tests/rust/re_integration_test/tests/snapshots/heuristics_mixed_all_root.png b/tests/rust/re_integration_test/tests/snapshots/heuristics_mixed_all_root.png index b41c7f7721f2..1eb31a8417cd 100644 --- a/tests/rust/re_integration_test/tests/snapshots/heuristics_mixed_all_root.png +++ b/tests/rust/re_integration_test/tests/snapshots/heuristics_mixed_all_root.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:75fa8efef0ef7bd9b69a36ce83b2c41f438bdedc7cb2a6dbacec8d42a118d86f -size 130322 +oid sha256:d42987f9e720a0c8ac88a7bdeef9c78af3032f13be8a504602eba70742b75c1c +size 129323 diff --git a/tests/rust/re_integration_test/tests/snapshots/origin_camera_2d.png b/tests/rust/re_integration_test/tests/snapshots/origin_camera_2d.png index 619840326a79..4b083ff383e7 100644 --- a/tests/rust/re_integration_test/tests/snapshots/origin_camera_2d.png +++ b/tests/rust/re_integration_test/tests/snapshots/origin_camera_2d.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:af9e8deb42e213e77ccbc1e6e9b799995de1d29d43f30cea05abc38a060fdc85 -size 154285 +oid sha256:3e3345bf1f7876a14f48fd11cae41944dfb735858f93c9bb8b9bfc4e90cd1607 +size 150082 diff --git a/tests/rust/re_integration_test/tests/snapshots/origin_camera_3d.png b/tests/rust/re_integration_test/tests/snapshots/origin_camera_3d.png index b395c6695fc1..99204babadeb 100644 --- a/tests/rust/re_integration_test/tests/snapshots/origin_camera_3d.png +++ b/tests/rust/re_integration_test/tests/snapshots/origin_camera_3d.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c13d6b0decc06ba3566e78ce903ede6e96884a00d5b9cea5a64c07863b8aa22e -size 199241 +oid sha256:151a1409330801d36319a8595093810178e483da104c527546b5318e4eba6798 +size 194958 diff --git a/tests/rust/re_integration_test/tests/snapshots/origin_image_2d.png b/tests/rust/re_integration_test/tests/snapshots/origin_image_2d.png index 80fb5dc80d89..0198a8e6a8a9 100644 --- a/tests/rust/re_integration_test/tests/snapshots/origin_image_2d.png +++ b/tests/rust/re_integration_test/tests/snapshots/origin_image_2d.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:abd0db69a3899e7f902e3af87a748c0b826b3d97acb776a946a94d86acdcaaf7 -size 180860 +oid sha256:805b852e2c4542f4c73f832a1819d9a69594fd253fb4947e41ca84178e9d9f23 +size 176545 diff --git a/tests/rust/re_integration_test/tests/snapshots/origin_image_3d.png b/tests/rust/re_integration_test/tests/snapshots/origin_image_3d.png index 9b5e3d6334bf..781b3ff781b4 100644 --- a/tests/rust/re_integration_test/tests/snapshots/origin_image_3d.png +++ b/tests/rust/re_integration_test/tests/snapshots/origin_image_3d.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:987d35866566f47a2a108f0fcb878dcf010a364921bd5f2e1e575e92007add71 -size 199208 +oid sha256:eb879e7f199b94cd97fd85edca18dc4c9283f276aaf0b270e13b340aa7fe1ef3 +size 194943 diff --git a/tests/rust/re_integration_test/tests/snapshots/origin_keypoint_2d.png b/tests/rust/re_integration_test/tests/snapshots/origin_keypoint_2d.png index 7042dfed2ea3..07318c061d08 100644 --- a/tests/rust/re_integration_test/tests/snapshots/origin_keypoint_2d.png +++ b/tests/rust/re_integration_test/tests/snapshots/origin_keypoint_2d.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7e46b81118835fe9af4cf9e412e075ee1c6507ed6433d949e428abb253837c7a -size 167610 +oid sha256:34e9d948f9115a89899cf4bff9af12b38c22c1337f312b226326c293e4a4586e +size 163284 diff --git a/tests/rust/re_integration_test/tests/snapshots/origin_keypoint_3d.png b/tests/rust/re_integration_test/tests/snapshots/origin_keypoint_3d.png index 218636109884..992ef3c4a33c 100644 --- a/tests/rust/re_integration_test/tests/snapshots/origin_keypoint_3d.png +++ b/tests/rust/re_integration_test/tests/snapshots/origin_keypoint_3d.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:aba3f051bbdcca7f657966e9992e51be6364392150920c46c0664630df49eb2c -size 195495 +oid sha256:22a669e5bfa9b5226d2d479eeb80826aae686d8bbd74708274cd897fb96dec63 +size 191220 diff --git a/tests/rust/re_integration_test/tests/snapshots/origin_root_2d.png b/tests/rust/re_integration_test/tests/snapshots/origin_root_2d.png index ca154cb76c02..999f7fa4b5b7 100644 --- a/tests/rust/re_integration_test/tests/snapshots/origin_root_2d.png +++ b/tests/rust/re_integration_test/tests/snapshots/origin_root_2d.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8d1d4f1cf69dee935dd2c31c429f80008e3f17e5c7e35dee86e432be38e837b0 -size 152634 +oid sha256:9cfe4045fd60be73287912014e6aeb428ceceb8d21b36f6645864eea3618f861 +size 148430 diff --git a/tests/rust/re_integration_test/tests/snapshots/origin_root_3d.png b/tests/rust/re_integration_test/tests/snapshots/origin_root_3d.png index c44f58f3a7aa..defb37b42a9d 100644 --- a/tests/rust/re_integration_test/tests/snapshots/origin_root_3d.png +++ b/tests/rust/re_integration_test/tests/snapshots/origin_root_3d.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ea6d42f363c53ffcddf3e275da57a3c7bceb794b143d20057eb7dd1cb095bdbb -size 210103 +oid sha256:a14d5dd61ded6d948001888cf47d525d0afd81545ba5bcc5e19d7d26ceaff515 +size 201108 diff --git a/tests/rust/re_integration_test/tests/snapshots/origin_world_2d.png b/tests/rust/re_integration_test/tests/snapshots/origin_world_2d.png index 0a4949bdef2d..b8ae95619657 100644 --- a/tests/rust/re_integration_test/tests/snapshots/origin_world_2d.png +++ b/tests/rust/re_integration_test/tests/snapshots/origin_world_2d.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:01f4b0a39243dc1c64857f232ec99557f05fe9cd9fb4914d5f70eaa9eafd1fa0 -size 153192 +oid sha256:0588446a654315bff40c357a59b659b66875bcbbc196dad322775890756cb301 +size 148998 diff --git a/tests/rust/re_integration_test/tests/snapshots/origin_world_3d.png b/tests/rust/re_integration_test/tests/snapshots/origin_world_3d.png index 00739ce943b2..362f656389d3 100644 --- a/tests/rust/re_integration_test/tests/snapshots/origin_world_3d.png +++ b/tests/rust/re_integration_test/tests/snapshots/origin_world_3d.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f0d2696108b1cebe4b76f14c0e08cb9abb8b39c2ce55eb05c1d21ead4d64fa1f -size 198079 +oid sha256:1b55903bb516e1a9ca16b1b745d22ca3fdafe279ed73028cf5588b8efb04fb65 +size 193805 From 1ba7daa8b51d97dc54ce00ce04ca96feac0f9ab2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Proch=C3=A1zka?= Date: Thu, 12 Mar 2026 12:06:34 +0100 Subject: [PATCH 108/513] Import `landing` * Part of RR-3385 and RR-3330 This is a straightforward import of https://github.com/rerun-io/landing After this PR, anyone can run `pixi run landing-serve` to serve docs locally. Follow-ups planned: - Make docs/examples live-reload - Depend on local `web-viewer` version - Should be a debug version, to ensure `wasm-opt` doesn't run constantly and the rebuild is relatively fast - Upon `web-viewer` rebuild, `vite` should reload the page - This may cause weirdness if the file has only been partially written, so it may require ensuring files are updated atomically - Optionally depend on locally-built example and snippet `.rrd`s - Build `rerun.io` on CI in the monorepo: - PR previews as before, but without the redeploy hacks, instead using the `vercel` CLI on CI - Production builds off of `main` - Builds happen on our CI runners, not on Vercel. Vercel only receives prebuilt artifacts. This means we can also show build failures as CI failures more easily. - Versioned docs: - First, split `landing` into landing (search, examples, blog, team) and docs page (search, docs) - Ensure docs can be built using `adapter-static`, it must not use any server-side code - Upload docs for each commit/pr/version, like we do for other assets (e.g. web viewer) - Keep a version index up-to-date with dev and each release - Show a version selector dropdown on each version of the docs Source-Ref: 773097044d992dfd62b457767edb7d5982d06088 --- .gitattributes | 5 +++++ docs/content/_redirects.yaml | 2 +- .../logging-and-ingestion/data-loaders/overview.md | 2 +- .../logging-and-ingestion/mcap/decoders-explained.md | 2 +- docs/content/concepts/logging-and-ingestion/recordings.md | 6 ++++-- docs/content/concepts/visualization/blueprints.md | 4 ++-- docs/content/getting-started/configure-the-viewer.md | 4 ++-- .../configure-the-viewer/navigating-the-viewer.md | 8 ++++++++ docs/content/reference/migration/migration-0-22.md | 2 +- docs/content/reference/viewer/blueprints.md | 2 +- lychee.toml | 3 ++- 11 files changed, 28 insertions(+), 12 deletions(-) diff --git a/.gitattributes b/.gitattributes index f6126fea948a..1599073b1093 100644 --- a/.gitattributes +++ b/.gitattributes @@ -10,4 +10,9 @@ pixi.lock linguist-generated=true **/snapshots/**/*.png filter=lfs diff=lfs merge=lfs -text **/*.h264 filter=lfs diff=lfs merge=lfs -text **/*.mp4 filter=lfs diff=lfs merge=lfs -text +landing/**/*.jpg filter=lfs diff=lfs merge=lfs -text +landing/**/*.jpeg filter=lfs diff=lfs merge=lfs -text +landing/**/*.png filter=lfs diff=lfs merge=lfs -text +landing/**/*.gif filter=lfs diff=lfs merge=lfs -text +landing/**/*.webp filter=lfs diff=lfs merge=lfs -text examples/assets/example.rrd filter=lfs diff=lfs merge=lfs -text diff --git a/docs/content/_redirects.yaml b/docs/content/_redirects.yaml index 19f988c79694..e54ad9e45afa 100644 --- a/docs/content/_redirects.yaml +++ b/docs/content/_redirects.yaml @@ -76,7 +76,7 @@ reference/viewer: reference/viewer/overview # Howto section howto/build-a-blueprint-programmatically: howto/visualization/build-a-blueprint-programmatically howto/callbacks: howto/visualization/callbacks -howto/configure-viewer-through-code: getting-started/configure-the-viewer#programmatic-blueprints +howto/configure-viewer-through-code: getting-started/configure-the-viewer/navigating-the-viewer#programmatic-blueprints howto/dataframe-api: howto/query-and-transform/get-data-out howto/embed-rerun-viewer: howto/integrations/embed-web howto/fixed-window-plot: howto/visualization/fixed-window-plot diff --git a/docs/content/concepts/logging-and-ingestion/data-loaders/overview.md b/docs/content/concepts/logging-and-ingestion/data-loaders/overview.md index 40c4b23fe33e..24ae52c90611 100644 --- a/docs/content/concepts/logging-and-ingestion/data-loaders/overview.md +++ b/docs/content/concepts/logging-and-ingestion/data-loaders/overview.md @@ -22,7 +22,7 @@ The easiest way to create your own `DataLoader` is by implementing what we call This executable takes a file path as a command line argument and outputs Rerun logs on `stdout`. It will be called by the Rerun Viewer/SDK when the user opens a file, and be passed the path to that file. -From there, it can log data as usual, using the [`stdout` logging sink](../../../reference/sdk/operating-modes.md#standard-inputoutput). +From there, it can log data as usual, using the [`stdout` logging sink](../../../reference/sdk/operating-modes.md#standard-inputoutput-stdout). The Rerun Viewer/SDK will then automatically load the data streamed to the external loader's standard output. diff --git a/docs/content/concepts/logging-and-ingestion/mcap/decoders-explained.md b/docs/content/concepts/logging-and-ingestion/mcap/decoders-explained.md index af3343879e60..8b85b621abe1 100644 --- a/docs/content/concepts/logging-and-ingestion/mcap/decoders-explained.md +++ b/docs/content/concepts/logging-and-ingestion/mcap/decoders-explained.md @@ -64,7 +64,7 @@ rerun mcap convert input.mcap -d ros2msg -d raw -d recording_info -o output.rrd Each decoder creates different types of components on entity paths (derived from MCAP channel topics) that can be accessed through Rerun's SDK: - Data from the `ros2msg` decoder and supported Foxglove messages appears as native Rerun visualization archetypes (see [here](message-formats.md#overview) for an overview) -- Other data from the `protobuf` or `ros2_reflection` decoders appears as structured components that can be queried by field name or manually added to certain views ([example](message-formats.md#example-time-series-plot-for-custom-message-scalars)) +- Other data from the `protobuf` or `ros2_reflection` decoders appears as structured components that can be queried by field name or manually added to certain views ([example](message-formats.md#example-timeseries-plot-for-custom-message-scalars)) - Data from the `raw` decoder appears as blob components containing the original message bytes - Metadata from `schema`, `stats`, and `recording_info` decoders appears as dedicated metadata entities diff --git a/docs/content/concepts/logging-and-ingestion/recordings.md b/docs/content/concepts/logging-and-ingestion/recordings.md index 9b406eda9dcc..96c78bf402ec 100644 --- a/docs/content/concepts/logging-and-ingestion/recordings.md +++ b/docs/content/concepts/logging-and-ingestion/recordings.md @@ -24,7 +24,8 @@ In its UI, the Viewer presents (logical) recordings sharing the same application In particular, they share the same [blueprint](../visualization/blueprints.md). -### Recordings on the Data Platform + +### Recordings on the Data Platform The Data Platform has a slightly different object model, which you can read more about in [Catalog object model](../query-and-transform/catalog-object-model.md). @@ -60,7 +61,8 @@ Application IDs are arbitrary user-defined strings set when initializing the SDK snippet: tutorials/custom-application-id -### When application IDs matter + +### When application IDs matter Application IDs are used by the Viewer when loading recordings directly (not via the Data Platform): diff --git a/docs/content/concepts/visualization/blueprints.md b/docs/content/concepts/visualization/blueprints.md index 90882ff7294f..6e97821bf67e 100644 --- a/docs/content/concepts/visualization/blueprints.md +++ b/docs/content/concepts/visualization/blueprints.md @@ -115,7 +115,7 @@ if robot_error: rr.send_blueprint(blueprint, make_active=True) ``` -See [Configure the Viewer](../../getting-started/configure-the-viewer.md#programmatic-blueprints) for detailed examples and our guide on how to [build a blueprint programmatically](../../howto/visualization/build-a-blueprint-programmatically.md). +See [Configure the Viewer](../../getting-started/configure-the-viewer/navigating-the-viewer.md#programmatic-blueprints) for detailed examples and our guide on how to [build a blueprint programmatically](../../howto/visualization/build-a-blueprint-programmatically.md). ## Common use cases @@ -144,7 +144,7 @@ Under the hood, blueprints are just data. They are structured using the same [En - **Anything you modify in the Viewer can be saved and shared** as a blueprint file - **Blueprints can be produced programmatically** using just the Rerun SDK without depending on the Viewer -- **Blueprint data is fully expressive**, enabling [blueprint overrides](customize-views.md#per-entity-component-override) that are as powerful as logged data +- **Blueprint data is fully expressive**, enabling [blueprint overrides](customize-views.md#component-mappings) that are as powerful as logged data - **The full time-series nature** simplifies future features like snapshots and undo/redo - **Debugging tools for Rerun data** can inspect blueprint state just like recording data diff --git a/docs/content/getting-started/configure-the-viewer.md b/docs/content/getting-started/configure-the-viewer.md index f131c2ed452c..4ab7d8dbf091 100644 --- a/docs/content/getting-started/configure-the-viewer.md +++ b/docs/content/getting-started/configure-the-viewer.md @@ -100,8 +100,8 @@ Each of the three sides has a button in the upper-right corner. Click these to s -There are several ways to rearrange the viewer layout to your liking: through the Viewer [user interface](#interactive-configuration), -via the [Blueprint API](#programmatic-blueprints), or by [loading an .rbl file](#save-and-load-blueprint-files). +There are several ways to rearrange the viewer layout to your liking: through the Viewer [user interface](configure-the-viewer/navigating-the-viewer.md#interactive-configuration), +via the [Blueprint API](configure-the-viewer/navigating-the-viewer.md#programmatic-blueprints), or by [loading an .rbl file](configure-the-viewer/navigating-the-viewer.md#save-and-load-blueprint-files). ## Exploring data diff --git a/docs/content/getting-started/configure-the-viewer/navigating-the-viewer.md b/docs/content/getting-started/configure-the-viewer/navigating-the-viewer.md index ce35aa5dbcb4..a046f65628c8 100644 --- a/docs/content/getting-started/configure-the-viewer/navigating-the-viewer.md +++ b/docs/content/getting-started/configure-the-viewer/navigating-the-viewer.md @@ -208,6 +208,14 @@ This is particularly valuable for: --- +## Programmatic blueprints + +You can also define blueprints entirely from code using the Blueprint API. This is ideal for creating reproducible layouts, generating views dynamically based on your data, or integrating blueprint configuration into your logging pipeline. + +For a complete guide, see [Build a blueprint programmatically](../../howto/visualization/build-a-blueprint-programmatically.md) and the [Blueprints concept page](../../concepts/visualization/blueprints.md). + +--- + ## Next steps - **Explore view types**: Check the [View Type Reference](../../reference/types/views/) to see all available views and their configuration options diff --git a/docs/content/reference/migration/migration-0-22.md b/docs/content/reference/migration/migration-0-22.md index 29663842723a..8a2a7c9ac301 100644 --- a/docs/content/reference/migration/migration-0-22.md +++ b/docs/content/reference/migration/migration-0-22.md @@ -498,4 +498,4 @@ To achieve the same effect, you can log any of the following "invalid" transform Previously, the `DisconnectedSpace` archetype played a double role by governing view spawn heuristics & being used as a transform placeholder. This led to a lot of complexity and often broke or caused confusion (see https://github.com/rerun-io/rerun/issues/6817, https://github.com/rerun-io/rerun/issues/4465, https://github.com/rerun-io/rerun/issues/4221). By now, explicit blueprints offer a better way to express which views should be spawned and what content they should query. -(you can learn more about blueprints [here](../../getting-started/configure-the-viewer#programmatic-blueprints)). +(you can learn more about blueprints [here](../../getting-started/configure-the-viewer/navigating-the-viewer.md#programmatic-blueprints)). diff --git a/docs/content/reference/viewer/blueprints.md b/docs/content/reference/viewer/blueprints.md index 03dd79f10208..373830d50dbd 100644 --- a/docs/content/reference/viewer/blueprints.md +++ b/docs/content/reference/viewer/blueprints.md @@ -40,7 +40,7 @@ Hovering over any item reveals controls for: - **Eye icon**: Show or hide the item - **"-" button**: Remove the item from the blueprint -Right-click any item for a context menu with additional operations. See [Configure the Viewer](../../getting-started/configure-the-viewer.md#interactive-configuration) for details on all interactive operations. +Right-click any item for a context menu with additional operations. See [Configure the Viewer](../../getting-started/configure-the-viewer/navigating-the-viewer.md#interactive-configuration) for details on all interactive operations. ### Data blueprints diff --git a/lychee.toml b/lychee.toml index bdadf4fc0d20..4180a3e372e2 100644 --- a/lychee.toml +++ b/lychee.toml @@ -21,7 +21,7 @@ # Maximum number of concurrent link checks. # Workaround for "too many open files" error on MacOS, see https://github.com/lycheeverse/lychee/issues/1248 -max_concurrency = 32 +max_concurrency = 96 # Check links inside `` and `

` blocks as well as Markdown code blocks.
 include_verbatim = true
@@ -53,6 +53,7 @@ exclude_path = [
   ".pixi",
   "build",
   "docs/python/",
+  "landing",
   "rerun_cpp/_deps",
   "rerun_cpp/docs/html",
   "rerun_cpp/docs/xml",

From 15d18e7bd230a9ac7029ca278d940c20f2b0dd51 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?G=C3=A1bor=20Gyebn=C3=A1r?= 
Date: Thu, 12 Mar 2026 12:15:39 +0100
Subject: [PATCH 109/513] Fixes logout + login into a different account

### Related

Closes RR-3998

### What

Clears credential cache when the user logs out.
Previously, the cache still had the credentials after logout. When the
user logged in, it did not refresh the cache and the old credentials
were used. So the user always had to restart the cli after logout to
switch accounts. :(

### Testing

Manual.

Source-Ref: e80cf7f8ca5d8c907b4da2872b150ed23347558d
---
 crates/utils/re_auth/src/credentials.rs | 5 +++++
 crates/utils/re_auth/src/oauth.rs       | 4 ++--
 2 files changed, 7 insertions(+), 2 deletions(-)

diff --git a/crates/utils/re_auth/src/credentials.rs b/crates/utils/re_auth/src/credentials.rs
index 5fce84a2c301..f0d3ec5130b9 100644
--- a/crates/utils/re_auth/src/credentials.rs
+++ b/crates/utils/re_auth/src/credentials.rs
@@ -73,6 +73,11 @@ pub(crate) mod oauth {
         subscribers.push(Box::new(callback));
     }
 
+    /// Clear the credentials cache
+    pub(crate) fn clear_cache() {
+        *CACHE.blocking_write() = None;
+    }
+
     /// Provider which uses `OAuth` credentials stored on the user's machine.
     #[derive(Debug, Default)]
     pub struct CliCredentialsProvider {
diff --git a/crates/utils/re_auth/src/oauth.rs b/crates/utils/re_auth/src/oauth.rs
index 25b80392521c..90bf3ff38b07 100644
--- a/crates/utils/re_auth/src/oauth.rs
+++ b/crates/utils/re_auth/src/oauth.rs
@@ -106,9 +106,9 @@ pub fn clear_credentials() -> Result, CredentialsClearErro
         }
     });
 
-    storage::clear()?;
-
+    crate::credentials::oauth::clear_cache();
     crate::credentials::oauth::auth_update(None);
+    storage::clear()?;
 
     Ok(outcome)
 }

From 8f297fec7324feca12be5c3b8eae05cf6c74b84e Mon Sep 17 00:00:00 2001
From: Andreas Reich 
Date: Thu, 12 Mar 2026 13:12:32 +0100
Subject: [PATCH 110/513] Performance improvements for many views + many
 entities

Removes `collect_visualizable_entities_for_view_class` and copied impls
of it - we now just always take the full list and filter on the fly.
This is fine because we _already_ did very similar filtering. These
subfiltered list can just reference the heavy objects in the list of all
visualizable entities!
A very nice outcome of this also made it clear that we can make
`recommended_visualizers_for_entity` implementations simpler by
providing data that's closer to what we actually need!

Unfortunately doesn't _yet_ make `VisualizableReason` non-clone since
right now it's a bit tricky to pull it out of the store subscriber
(should we do locking for that? or better make it a viewer cache so we
can just take long lived references!)

On a select user provided scene:

Before:
image

After:
image

So 4 ms alone in the `view` scope in `app_state.rs` (which still takes
too long of course in general) for said scene

---
* Fixes RR-3973

Source-Ref: c788c341bcfd2c04221a0ac4fb736608292107af
---
 .../re_selection_panel/src/selection_panel.rs |  69 +----------
 .../re_selection_panel/src/visualizer_ui.rs   |  62 ++++++----
 crates/viewer/re_test_viewport/src/lib.rs     |   4 +-
 .../re_view_bar_chart/src/view_class.rs       |  18 +--
 crates/viewer/re_view_spatial/src/view_3d.rs  |  28 ++---
 .../viewer/re_view_tensor/src/view_class.rs   |  18 +--
 .../re_view_time_series/src/view_class.rs     |  71 +++++------
 crates/viewer/re_viewer/src/app_state.rs      |  31 +----
 .../re_viewer_context/src/view/view_class.rs  |  43 ++-----
 .../src/view/visualizer_system.rs             |   4 +
 .../re_viewer_context/src/viewer_context.rs   |  20 +---
 .../benches/data_query.rs                     |  15 ++-
 .../viewer/re_viewport_blueprint/src/lib.rs   |   3 +
 .../src/test_view_class.rs                    |  95 +++++++++++++++
 .../viewer/re_viewport_blueprint/src/view.rs  |  10 +-
 .../src/view_contents.rs                      | 112 +++++++++++-------
 .../custom_view/src/points3d_color_view.rs    |  19 ++-
 17 files changed, 308 insertions(+), 314 deletions(-)
 create mode 100644 crates/viewer/re_viewport_blueprint/src/test_view_class.rs

diff --git a/crates/viewer/re_selection_panel/src/selection_panel.rs b/crates/viewer/re_selection_panel/src/selection_panel.rs
index 7c657de30ca0..b62113aea8d4 100644
--- a/crates/viewer/re_selection_panel/src/selection_panel.rs
+++ b/crates/viewer/re_selection_panel/src/selection_panel.rs
@@ -486,36 +486,12 @@ The last rule matching `/world/house` is `+ /world/**`, so it is included.
             let view_class = view.class(ctx.view_class_registry());
             let view_state = view_states.get_mut_or_create(ctx.store_id(), view.id, view_class);
 
-            // Try the new `visualizers_section` first, fall back to deprecated `visualizers_ui`.
-            // These live in separate scopes because `visualizers_section` borrows `view_state`
-            // immutably (via `ViewContext`), while the legacy `visualizers_ui` borrows it mutably.
-            let showed_visualizers_section = {
-                let view_ctx = view.bundle_context_with_state(ctx, view_state);
-                if let Some(section) = view_class.visualizers_section(&view_ctx) {
-                    show_visualizers_section(ctx, ui, *view_id, section.add_options, &|ui| {
-                        let view_ctx = view.bundle_context_with_state(ctx, view_state);
-                        (section.ui)(ui, &view_ctx);
-                    });
-                    true
-                } else {
-                    false
-                }
-            };
-            if !showed_visualizers_section {
-                #[expect(deprecated)]
-                let legacy_ui =
-                    view_class.visualizers_ui(ctx, *view_id, view_state, &view.space_origin);
-                if let Some(legacy_ui) = legacy_ui {
-                    let add_options = legacy_collect_add_visualizer_options_from_recommended(
-                        ctx,
-                        *view_id,
-                        view_class,
-                        view.class_identifier(),
-                    );
-                    show_visualizers_section(ctx, ui, *view_id, add_options, &|ui| {
-                        legacy_ui(ui);
-                    });
-                }
+            let view_ctx = view.bundle_context_with_state(ctx, view_state);
+            if let Some(section) = view_class.visualizers_section(&view_ctx) {
+                show_visualizers_section(ctx, ui, *view_id, section.add_options, &|ui| {
+                    let view_ctx = view.bundle_context_with_state(ctx, view_state);
+                    (section.ui)(ui, &view_ctx);
+                });
             }
 
             ui.section_collapsing_header("View properties")
@@ -573,39 +549,6 @@ fn show_visualizers_section(
         });
 }
 
-/// Collect add-visualizer options via [`re_viewer_context::ViewClass::recommended_visualizers_for_entity`].
-fn legacy_collect_add_visualizer_options_from_recommended(
-    viewer_ctx: &ViewerContext<'_>,
-    view_id: ViewId,
-    view_class: &dyn re_viewer_context::ViewClass,
-    view_class_identifier: re_sdk_types::ViewClassIdentifier,
-) -> Vec<(EntityPath, RecommendedVisualizers)> {
-    profile_function!();
-
-    let query_result = viewer_ctx.lookup_query_result(view_id);
-    let visualizable_entities_per_visualizer =
-        viewer_ctx.collect_visualizable_entities_for_view_class(view_class_identifier);
-
-    query_result
-        .tree
-        .iter_data_results()
-        .filter(|data_result| !data_result.tree_prefix_only)
-        .filter_map(|data_result| {
-            let entity_path = &data_result.entity_path;
-            let recommended = view_class.recommended_visualizers_for_entity(
-                entity_path,
-                &visualizable_entities_per_visualizer,
-                viewer_ctx.indicated_entities_per_visualizer,
-            );
-            if recommended.0.is_empty() {
-                None
-            } else {
-                Some((entity_path.clone(), recommended))
-            }
-        })
-        .collect()
-}
-
 fn visualizer_section_plus_button(
     viewer_ctx: &ViewerContext<'_>,
     view_id: ViewId,
diff --git a/crates/viewer/re_selection_panel/src/visualizer_ui.rs b/crates/viewer/re_selection_panel/src/visualizer_ui.rs
index ee99c21746fe..86bc39fb48ab 100644
--- a/crates/viewer/re_selection_panel/src/visualizer_ui.rs
+++ b/crates/viewer/re_selection_panel/src/visualizer_ui.rs
@@ -7,6 +7,7 @@ use re_chunk::ComponentIdentifier;
 use re_data_ui::{DataUi as _, sorted_component_list_by_archetype_for_ui};
 use re_log_types::EntityPath;
 use re_sdk_types::Archetype as _;
+use re_sdk_types::ViewClassIdentifier;
 use re_sdk_types::blueprint::archetypes::ActiveVisualizers;
 use re_sdk_types::blueprint::components::VisualizerInstructionId;
 use re_sdk_types::blueprint::datatypes::ComponentSourceKind;
@@ -20,14 +21,26 @@ use re_view::{
     BlueprintResolvedResultsExt as _, ChunksWithComponent, latest_at_with_blueprint_resolved_data,
 };
 use re_viewer_context::{
-    BlueprintContext as _, DataResult, DatatypeMatch, PerVisualizerTypeInViewClass,
-    TryShowEditUiResult, UiLayout, ViewContext, ViewSystemIdentifier, VisualizableEntities,
-    VisualizableReason, VisualizerCollection, VisualizerComponentMappings,
+    BlueprintContext as _, DataResult, DatatypeMatch, TryShowEditUiResult, UiLayout, ViewContext,
+    ViewSystemIdentifier, VisualizableReason, VisualizerCollection, VisualizerComponentMappings,
     VisualizerComponentSource, VisualizerInstruction, VisualizerQueryInfo,
     VisualizerReportSeverity, VisualizerSystem, VisualizerViewReport,
 };
 use re_viewport_blueprint::ViewBlueprint;
 
+/// Extracts the list of visualizers (with their reasons) for a specific entity
+/// from the viewer context, without cloning.
+fn visualizers_for_entity<'a>(
+    viewer_ctx: &'a re_viewer_context::ViewerContext<'a>,
+    view_class_identifier: ViewClassIdentifier,
+    entity_path: &EntityPath,
+) -> Vec<(ViewSystemIdentifier, &'a VisualizableReason)> {
+    viewer_ctx
+        .iter_visualizable_entities_for_view_class(view_class_identifier)
+        .filter_map(|(visualizer, ents)| ents.get(entity_path).map(|reason| (visualizer, reason)))
+        .collect()
+}
+
 pub fn visualizer_ui(
     ctx: &ViewContext<'_>,
     view: &ViewBlueprint,
@@ -753,11 +766,14 @@ fn extract_recommended_source_options(
     // Rule 2: View-recommended mappings are recommended.
     let view_ctx = mapping_ctx.view_ctx();
     let viewer_ctx = mapping_ctx.viewer_ctx();
-    let visualizable_entities_per_visualizer =
-        viewer_ctx.collect_visualizable_entities_for_view_class(view_ctx.view_class_identifier);
+    let visualizers_with_reason = visualizers_for_entity(
+        viewer_ctx,
+        view_ctx.view_class_identifier,
+        &mapping_ctx.data_result.entity_path,
+    );
     let recommended_visualizers = view_ctx.view_class().recommended_visualizers_for_entity(
         &mapping_ctx.data_result.entity_path,
-        &visualizable_entities_per_visualizer,
+        &visualizers_with_reason,
         viewer_ctx.indicated_entities_per_visualizer,
     );
     if let Some(recommended_mappings) = recommended_visualizers
@@ -998,12 +1014,14 @@ fn menu_add_new_visualizer(
     ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend);
 
     // Determine which visualizers are recommended.
-    let visualizable_entities_per_visualizer = ctx
-        .viewer_ctx
-        .collect_visualizable_entities_for_view_class(ctx.view_class_identifier);
+    let visualizers_with_reason = visualizers_for_entity(
+        ctx.viewer_ctx,
+        ctx.view_class_identifier,
+        &data_result.entity_path,
+    );
     let recommended_visualizers = ctx.view_class().recommended_visualizers_for_entity(
         &data_result.entity_path,
-        &visualizable_entities_per_visualizer,
+        &visualizers_with_reason,
         ctx.viewer_ctx.indicated_entities_per_visualizer,
     );
 
@@ -1117,26 +1135,29 @@ fn component_mappings_for_new_visualizer(
 ) -> VisualizerComponentMappings {
     // Get recommended visualizers with their component mappings so we can use them
     // when the user adds a new visualizer.
-    let visualizable_entities_per_visualizer = ctx
-        .viewer_ctx
-        .collect_visualizable_entities_for_view_class(ctx.view_class_identifier);
+    let entity_visualizers =
+        visualizers_for_entity(ctx.viewer_ctx, ctx.view_class_identifier, entity_path);
     let recommended_visualizers = ctx.view_class().recommended_visualizers_for_entity(
         entity_path,
-        &visualizable_entities_per_visualizer,
+        &entity_visualizers,
         ctx.viewer_ctx.indicated_entities_per_visualizer,
     );
     let component_mapping_recommendations = recommended_visualizers.0.get(visualizer_type).cloned();
 
     // Chain in all possible mappings.
+    let visualizable_reason = entity_visualizers
+        .iter()
+        .find(|(viz, _)| viz == visualizer_type)
+        .map(|(_, reason)| *reason);
     let all_mapping_candidates = component_mapping_recommendations
         .into_iter()
         .flatten()
         .map(re_viewer_context::RecommendedMappings::into_mappings)
         .chain(
             component_mappings_for_required_components_from_visualizability(
-                *visualizer_type,
                 entity_path,
-                &visualizable_entities_per_visualizer,
+                visualizer_type,
+                visualizable_reason,
             ),
         );
 
@@ -1160,15 +1181,10 @@ fn component_mappings_for_new_visualizer(
 
 /// Derives component mappings from the visualizability reason when no explicit recommendation exists.
 fn component_mappings_for_required_components_from_visualizability(
-    visualizer_type: ViewSystemIdentifier,
     entity_path: &EntityPath,
-    visualizable_entities_per_visualizer: &PerVisualizerTypeInViewClass,
+    visualizer_type: &ViewSystemIdentifier,
+    reason: Option<&VisualizableReason>,
 ) -> Vec {
-    // Look up why this entity is visualizable for this visualizer type.
-    let reason = visualizable_entities_per_visualizer
-        .get(&visualizer_type)
-        .and_then(|entities| entities.get(entity_path));
-
     match reason {
         Some(VisualizableReason::SingleRequiredComponentMatch(matches)) => matches
             .matches
diff --git a/crates/viewer/re_test_viewport/src/lib.rs b/crates/viewer/re_test_viewport/src/lib.rs
index d441519b5776..f75d29ec0095 100644
--- a/crates/viewer/re_test_viewport/src/lib.rs
+++ b/crates/viewer/re_test_viewport/src/lib.rs
@@ -93,8 +93,6 @@ impl TestContextExt for TestContext {
                             let class_identifier = view_blueprint.class_identifier();
                             let class = class_registry.class(class_identifier).unwrap_or_else(|| panic!("The class '{class_identifier}' must be registered beforehand"));
 
-                            let visualizable_entities_for_view = ctx.collect_visualizable_entities_for_view_class(class_identifier);
-
                             let query_range = view_blueprint.query_range(
                                 ctx.blueprint_db(),
                                 ctx.blueprint_query,
@@ -109,7 +107,7 @@ impl TestContextExt for TestContext {
                                 class_registry,
                                 ctx.blueprint_query,
                                 &query_range,
-                                &visualizable_entities_for_view,
+                                ctx.visualizable_entities_per_visualizer,
                                 ctx.indicated_entities_per_visualizer,
                                 ctx.app_options(),
                             );
diff --git a/crates/viewer/re_view_bar_chart/src/view_class.rs b/crates/viewer/re_view_bar_chart/src/view_class.rs
index 9bf7f14fd878..d1fae94a2d2f 100644
--- a/crates/viewer/re_view_bar_chart/src/view_class.rs
+++ b/crates/viewer/re_view_bar_chart/src/view_class.rs
@@ -10,10 +10,10 @@ use re_ui::{Help, IconText, MouseButtonText, icons, list_item};
 use re_view::controls::SELECTION_RECT_ZOOM_BUTTON;
 use re_view::view_property_ui;
 use re_viewer_context::{
-    IdentifiedViewSystem as _, IndicatedEntities, PerVisualizerType, PerVisualizerTypeInViewClass,
-    RecommendedVisualizers, ViewClass, ViewClassExt as _, ViewClassRegistryError, ViewId,
-    ViewQuery, ViewState, ViewStateExt as _, ViewSystemExecutionError, ViewerContext,
-    VisualizableEntities, suggest_view_for_each_entity,
+    IdentifiedViewSystem as _, IndicatedEntities, PerVisualizerType, RecommendedVisualizers,
+    ViewClass, ViewClassExt as _, ViewClassRegistryError, ViewId, ViewQuery, ViewState,
+    ViewStateExt as _, ViewSystemExecutionError, ViewSystemIdentifier, ViewerContext,
+    VisualizableReason, suggest_view_for_each_entity,
 };
 use re_viewport_blueprint::ViewProperty;
 
@@ -103,8 +103,8 @@ impl ViewClass for BarChartView {
 
     fn recommended_visualizers_for_entity(
         &self,
-        entity_path: &EntityPath,
-        visualizable_entities_per_visualizer: &PerVisualizerTypeInViewClass,
+        _entity_path: &EntityPath,
+        visualizers_with_reason: &[(ViewSystemIdentifier, &VisualizableReason)],
         _indicated_entities_per_visualizer: &PerVisualizerType,
     ) -> RecommendedVisualizers {
         // Default implementation would not suggest the BarChart visualizer for tensors and 1D images,
@@ -112,9 +112,9 @@ impl ViewClass for BarChartView {
         // (and as of writing, something needs to be both visualizable and indicated to be shown in a visualizer)
 
         // Keeping this implementation simple: We know there's only a single visualizer here.
-        if visualizable_entities_per_visualizer
-            .get(&BarChartVisualizerSystem::identifier())
-            .is_some_and(|entities| entities.contains_key(entity_path))
+        if visualizers_with_reason
+            .iter()
+            .any(|(viz, _)| *viz == BarChartVisualizerSystem::identifier())
         {
             RecommendedVisualizers::default(BarChartVisualizerSystem::identifier())
         } else {
diff --git a/crates/viewer/re_view_spatial/src/view_3d.rs b/crates/viewer/re_view_spatial/src/view_3d.rs
index a4abb4d75784..f10db46b80df 100644
--- a/crates/viewer/re_view_spatial/src/view_3d.rs
+++ b/crates/viewer/re_view_spatial/src/view_3d.rs
@@ -17,14 +17,12 @@ use re_tf::query_view_coordinates;
 use re_ui::{Help, UiExt as _, list_item};
 use re_view::view_property_ui;
 use re_viewer_context::{
-    IdentifiedViewSystem as _, IndicatedEntities, PerVisualizerType, PerVisualizerTypeInViewClass,
-    QueryContext, RecommendedView, RecommendedVisualizers, ViewClass, ViewClassExt as _,
-    ViewClassRegistryError, ViewContext, ViewId, ViewQuery, ViewSpawnHeuristics, ViewState,
-    ViewStateExt as _, ViewSystemExecutionError, ViewSystemIdentifier, ViewerContext,
-    VisualizableEntities,
+    IdentifiedViewSystem as _, IndicatedEntities, PerVisualizerType, QueryContext, RecommendedView,
+    RecommendedVisualizers, ViewClass, ViewClassExt as _, ViewClassRegistryError, ViewContext,
+    ViewId, ViewQuery, ViewSpawnHeuristics, ViewState, ViewStateExt as _, ViewSystemExecutionError,
+    ViewSystemIdentifier, ViewerContext, VisualizableReason,
 };
 use re_viewport_blueprint::ViewProperty;
-use smallvec::SmallVec;
 
 use crate::contexts::register_spatial_contexts;
 use crate::heuristics::IndicatedVisualizableEntities;
@@ -325,17 +323,12 @@ impl ViewClass for SpatialView3D {
     fn recommended_visualizers_for_entity(
         &self,
         entity_path: &EntityPath,
-        visualizable_entities_per_visualizer: &PerVisualizerTypeInViewClass,
+        visualizers_with_reason: &[(ViewSystemIdentifier, &VisualizableReason)],
         indicated_entities_per_visualizer: &PerVisualizerType,
     ) -> RecommendedVisualizers {
         let axes_viz = TransformAxes3DVisualizer::identifier();
         let camera_viz = CamerasVisualizer::identifier();
 
-        let visualizable: HashSet<&ViewSystemIdentifier> = visualizable_entities_per_visualizer
-            .iter()
-            .filter_map(|(visualizer, ents)| ents.contains_key(entity_path).then_some(visualizer))
-            .collect();
-
         let indicated: HashSet<&ViewSystemIdentifier> = indicated_entities_per_visualizer
             .iter()
             .filter_map(|(visualizer, ents)| {
@@ -348,14 +341,15 @@ impl ViewClass for SpatialView3D {
             .collect();
 
         // Start with all the entities which are both indicated and visualizable.
-        let mut enabled_visualizers: SmallVec<[ViewSystemIdentifier; 4]> = indicated
-            .intersection(&visualizable)
-            .copied()
-            .copied()
+        let mut enabled_visualizers: Vec<_> = visualizers_with_reason
+            .iter()
+            .filter_map(|(viz, _reason)| indicated.contains(viz).then_some(*viz))
             .collect();
 
         // Arrow visualizer is not enabled yet but we could…
-        if !enabled_visualizers.contains(&axes_viz) && visualizable.contains(&axes_viz) {
+        if !enabled_visualizers.contains(&axes_viz)
+            && visualizers_with_reason.iter().any(|(v, _)| *v == axes_viz)
+        {
             // …if we already have the [`CamerasVisualizer`] active.
             if enabled_visualizers.contains(&camera_viz) {
                 enabled_visualizers.push(axes_viz);
diff --git a/crates/viewer/re_view_tensor/src/view_class.rs b/crates/viewer/re_view_tensor/src/view_class.rs
index 144df9967ce7..2fc18b0b7fcf 100644
--- a/crates/viewer/re_view_tensor/src/view_class.rs
+++ b/crates/viewer/re_view_tensor/src/view_class.rs
@@ -15,10 +15,10 @@ use re_ui::{Help, UiExt as _, list_item};
 use re_view::view_property_ui;
 use re_viewer_context::{
     ColormapWithRange, IdentifiedViewSystem as _, IndicatedEntities, Item, PerVisualizerType,
-    PerVisualizerTypeInViewClass, RecommendedVisualizers, SystemCommand, SystemCommandSender as _,
-    TensorStatsCache, ViewClass, ViewClassExt as _, ViewClassRegistryError, ViewContext, ViewId,
-    ViewQuery, ViewState, ViewStateExt as _, ViewSystemExecutionError, ViewerContext,
-    VisualizableEntities, gpu_bridge, suggest_view_for_each_entity,
+    RecommendedVisualizers, SystemCommand, SystemCommandSender as _, TensorStatsCache, ViewClass,
+    ViewClassExt as _, ViewClassRegistryError, ViewContext, ViewId, ViewQuery, ViewState,
+    ViewStateExt as _, ViewSystemExecutionError, ViewSystemIdentifier, ViewerContext,
+    VisualizableReason, gpu_bridge, suggest_view_for_each_entity,
 };
 use re_viewport_blueprint::ViewProperty;
 
@@ -98,8 +98,8 @@ Set the displayed dimensions in a selection panel.",
 
     fn recommended_visualizers_for_entity(
         &self,
-        entity_path: &EntityPath,
-        visualizable_entities_per_visualizer: &PerVisualizerTypeInViewClass,
+        _entity_path: &EntityPath,
+        visualizers_with_reason: &[(ViewSystemIdentifier, &VisualizableReason)],
         _indicated_entities_per_visualizer: &PerVisualizerType,
     ) -> RecommendedVisualizers {
         // Default implementation would not suggest the Tensor visualizer for images,
@@ -107,9 +107,9 @@ Set the displayed dimensions in a selection panel.",
         // (and as of writing, something needs to be both visualizable and indicated to be shown in a visualizer)
 
         // Keeping this implementation simple: We know there's only a single visualizer here.
-        if visualizable_entities_per_visualizer
-            .get(&TensorSystem::identifier())
-            .is_some_and(|entities| entities.contains_key(entity_path))
+        if visualizers_with_reason
+            .iter()
+            .any(|(viz, _)| *viz == TensorSystem::identifier())
         {
             RecommendedVisualizers::default(TensorSystem::identifier())
         } else {
diff --git a/crates/viewer/re_view_time_series/src/view_class.rs b/crates/viewer/re_view_time_series/src/view_class.rs
index 2d0fc0a2f06f..c1545d15fa4f 100644
--- a/crates/viewer/re_view_time_series/src/view_class.rs
+++ b/crates/viewer/re_view_time_series/src/view_class.rs
@@ -20,12 +20,11 @@ use re_view::controls::{MOVE_TIME_CURSOR_BUTTON, SELECTION_RECT_ZOOM_BUTTON};
 use re_view::view_property_ui;
 use re_viewer_context::{
     BlueprintContext as _, DataResultInteractionAddress, DatatypeMatch, IdentifiedViewSystem as _,
-    IndicatedEntities, PerVisualizerType, PerVisualizerTypeInViewClass, QueryRange,
-    RecommendedMappings, RecommendedView, RecommendedVisualizers, SingleRequiredComponentMatch,
-    SystemExecutionOutput, TimeControlCommand, ViewClass, ViewClassExt as _,
-    ViewClassRegistryError, ViewHighlights, ViewId, ViewQuery, ViewSpawnHeuristics, ViewState,
-    ViewStateExt as _, ViewSystemExecutionError, ViewSystemIdentifier, ViewerContext,
-    VisualizableEntities, VisualizableReason, VisualizerComponentSource,
+    IndicatedEntities, PerVisualizerType, QueryRange, RecommendedMappings, RecommendedView,
+    RecommendedVisualizers, SingleRequiredComponentMatch, SystemExecutionOutput,
+    TimeControlCommand, ViewClass, ViewClassExt as _, ViewClassRegistryError, ViewHighlights,
+    ViewId, ViewQuery, ViewSpawnHeuristics, ViewState, ViewStateExt as _, ViewSystemExecutionError,
+    ViewSystemIdentifier, ViewerContext, VisualizableReason, VisualizerComponentSource,
 };
 use re_viewport_blueprint::ViewProperty;
 use smallvec::SmallVec;
@@ -276,15 +275,13 @@ impl ViewClass for TimeSeriesView {
     fn recommended_visualizers_for_entity(
         &self,
         entity_path: &EntityPath,
-        visualizable_entities_per_visualizer: &PerVisualizerTypeInViewClass,
+        visualizers_with_reason: &[(ViewSystemIdentifier, &VisualizableReason)],
         indicated_entities_per_visualizer: &PerVisualizerType,
     ) -> RecommendedVisualizers {
         let available_visualizers: HashMap =
-            visualizable_entities_per_visualizer
+            visualizers_with_reason
                 .iter()
-                .filter_map(|(visualizer, ents)| {
-                    ents.get(entity_path).map(|reason| (*visualizer, reason))
-                })
+                .map(|(visualizer, reason)| (*visualizer, *reason))
                 .collect();
 
         let mut recommended = RecommendedVisualizers(
@@ -333,12 +330,12 @@ impl ViewClass for TimeSeriesView {
         &'a self,
         ctx: &'a re_viewer_context::ViewContext<'a>,
     ) -> Option> {
-        let visualizable_entities_per_visualizer = ctx
-            .viewer_ctx
-            .collect_visualizable_entities_for_view_class(Self::identifier());
-
         let series_line_id = SeriesLinesSystem::identifier();
-        let visualizable_entities = visualizable_entities_per_visualizer.get(&series_line_id);
+        let visualizable_entities = ctx
+            .viewer_ctx
+            .iter_visualizable_entities_for_view_class(Self::identifier())
+            .find(|(viz, _)| *viz == series_line_id)
+            .map(|(_, ents)| ents);
 
         let data_results = ctx.query_result.tree.iter_data_results();
         let add_options = data_results
@@ -1483,27 +1480,12 @@ mod tests {
         re_test_context::TestContext::test_help_view(|ctx| TimeSeriesView.help(ctx));
     }
 
-    fn build_visualizable_entities(
-        entity_path: &EntityPath,
-        reason: VisualizableReason,
-    ) -> PerVisualizerTypeInViewClass {
-        PerVisualizerTypeInViewClass {
-            view_class_identifier: TimeSeriesView::identifier(),
-            per_visualizer: std::iter::once((
-                SeriesLinesSystem::identifier(),
-                VisualizableEntities(std::iter::once((entity_path.clone(), reason)).collect()),
-            ))
-            .collect(),
-        }
-    }
-
     /// Regression: non-recommended physical datatype (`Int32`) must not cause
     /// `SeriesLinesSystem` to be recommended with an empty mapping.
     #[test]
     fn test_no_recommendation_for_non_recommended_datatype() {
         let entity_path = EntityPath::from("sensor/data");
-        let viz = build_visualizable_entities(
-            &entity_path,
+        let reason =
             VisualizableReason::SingleRequiredComponentMatch(SingleRequiredComponentMatch {
                 target_component: Scalars::descriptor_scalars().component,
                 matches: std::iter::once((
@@ -1515,11 +1497,14 @@ mod tests {
                     },
                 ))
                 .collect(),
-            }),
-        );
+            });
+        let visualizers = [(SeriesLinesSystem::identifier(), &reason)];
         let indicated = PerVisualizerType::default();
-        let result =
-            TimeSeriesView.recommended_visualizers_for_entity(&entity_path, &viz, &indicated);
+        let result = TimeSeriesView.recommended_visualizers_for_entity(
+            &entity_path,
+            &visualizers,
+            &indicated,
+        );
 
         assert!(result.0.is_empty());
     }
@@ -1528,8 +1513,7 @@ mod tests {
     #[test]
     fn test_recommendation_for_recommended_datatype() {
         let entity_path = EntityPath::from("sensor/data");
-        let viz = build_visualizable_entities(
-            &entity_path,
+        let reason =
             VisualizableReason::SingleRequiredComponentMatch(SingleRequiredComponentMatch {
                 target_component: Scalars::descriptor_scalars().component,
                 matches: std::iter::once((
@@ -1540,11 +1524,14 @@ mod tests {
                     },
                 ))
                 .collect(),
-            }),
-        );
+            });
+        let visualizers = [(SeriesLinesSystem::identifier(), &reason)];
         let indicated = PerVisualizerType::default();
-        let result =
-            TimeSeriesView.recommended_visualizers_for_entity(&entity_path, &viz, &indicated);
+        let result = TimeSeriesView.recommended_visualizers_for_entity(
+            &entity_path,
+            &visualizers,
+            &indicated,
+        );
 
         assert!(result.0.contains_key(&SeriesLinesSystem::identifier()));
         let mappings = &result.0[&SeriesLinesSystem::identifier()];
diff --git a/crates/viewer/re_viewer/src/app_state.rs b/crates/viewer/re_viewer/src/app_state.rs
index 47dd7900ca01..1ba66058e7d2 100644
--- a/crates/viewer/re_viewer/src/app_state.rs
+++ b/crates/viewer/re_viewer/src/app_state.rs
@@ -17,10 +17,9 @@ use re_viewer_context::open_url::{self, ViewerOpenUrl};
 use re_viewer_context::{
     ActiveStoreContext, AppContext, AppOptions, ApplicationSelectionState, AsyncRuntimeHandle,
     AuthContext, BlueprintContext, BlueprintUndoState, CommandSender, ComponentUiRegistry,
-    DragAndDropManager, FallbackProviderRegistry, Item, PerVisualizerTypeInViewClass, Route,
-    SelectionChange, StorageContext, StoreHub, StoreViewContext, SystemCommand,
-    SystemCommandSender as _, TableStore, TimeControl, TimeControlCommand, ViewClassRegistry,
-    ViewStates, ViewerContext, blueprint_timeline,
+    DragAndDropManager, FallbackProviderRegistry, Item, Route, SelectionChange, StorageContext,
+    StoreHub, StoreViewContext, SystemCommand, SystemCommandSender as _, TableStore, TimeControl,
+    TimeControlCommand, ViewClassRegistry, ViewStates, ViewerContext, blueprint_timeline,
 };
 use re_viewport::ViewportUi;
 use re_viewport_blueprint::ViewportBlueprint;
@@ -362,28 +361,6 @@ impl AppState {
                         .map(|view| {
                             re_tracing::profile_scope!("view", view.display_name_or_default().to_string().as_str());
 
-                            // Same logic as in `ViewerContext::collect_visualizable_entities_for_view_class`,
-                            // but we don't have access to `ViewerContext` just yet.
-                            let visualizable_entities = if let Some(view_class) =
-                                view_class_registry.class_entry(view.class_identifier())
-                            {
-                                re_tracing::profile_scope!("visualizable_entities_per_visualizer");
-                                PerVisualizerTypeInViewClass {
-                                    view_class_identifier: view.class_identifier(),
-                                    per_visualizer: visualizable_entities_per_visualizer
-                                        .iter()
-                                        .filter_map(|(vis, ents)| {
-                                            view_class
-                                                .visualizer_system_ids
-                                                .contains(vis)
-                                                .then_some((*vis, ents.clone()))
-                                        })
-                                        .collect(),
-                                }
-                            } else {
-                                PerVisualizerTypeInViewClass::empty(view.class_identifier())
-                            };
-
                             let view_state = view_states
                                 .get(recording.store_id(), view.id)
                                 .expect("View state should exist, we just called ensure_state_exists on it.");
@@ -404,7 +381,7 @@ impl AppState {
                                     view_class_registry,
                                     app_blueprint_ctx.blueprint_query(),
                                     &query_range,
-                                    &visualizable_entities,
+                                    &visualizable_entities_per_visualizer,
                                     &indicated_entities_per_visualizer,
                                     app_options,
                                 ),
diff --git a/crates/viewer/re_viewer_context/src/view/view_class.rs b/crates/viewer/re_viewer_context/src/view/view_class.rs
index 0f25774c7484..2b6448a7f8ca 100644
--- a/crates/viewer/re_viewer_context/src/view/view_class.rs
+++ b/crates/viewer/re_viewer_context/src/view/view_class.rs
@@ -8,10 +8,9 @@ use vec1::Vec1;
 
 use super::ViewContext;
 use crate::{
-    IndicatedEntities, PerVisualizerType, PerVisualizerTypeInViewClass, QueryRange,
-    RecommendedMappings, SystemExecutionOutput, ViewClassRegistryError, ViewId, ViewQuery,
-    ViewSpawnHeuristics, ViewSystemExecutionError, ViewSystemIdentifier, ViewSystemRegistrator,
-    ViewerContext, VisualizableEntities,
+    IndicatedEntities, PerVisualizerType, QueryRange, RecommendedMappings, SystemExecutionOutput,
+    ViewClassRegistryError, ViewId, ViewQuery, ViewSpawnHeuristics, ViewSystemExecutionError,
+    ViewSystemIdentifier, ViewSystemRegistrator, ViewerContext, VisualizableReason,
 };
 
 #[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd, Ord, Eq)]
@@ -155,27 +154,20 @@ pub trait ViewClass: Send + Sync {
     /// Will only be called for entities where the selected visualizers have not
     /// been overridden by the blueprint.
     ///
+    /// `visualizers_with_reason` contains the visualizers that are visualizable for this entity,
+    /// along with the reason why they are visualizable.
+    ///
     /// This interface provides a default implementation which will return all visualizers
     /// which are both visualizable and indicated for the given entity.
     fn recommended_visualizers_for_entity(
         &self,
         entity_path: &EntityPath,
-        visualizable_entities_per_visualizer: &PerVisualizerTypeInViewClass,
+        visualizers_with_reason: &[(ViewSystemIdentifier, &VisualizableReason)],
         indicated_entities_per_visualizer: &PerVisualizerType,
     ) -> RecommendedVisualizers {
-        let available_visualizers =
-            visualizable_entities_per_visualizer
-                .iter()
-                .filter_map(|(visualizer, ents)| {
-                    if ents.contains_key(entity_path) {
-                        Some(visualizer)
-                    } else {
-                        None
-                    }
-                });
-
-        let recommended = available_visualizers
-            .filter_map(|visualizer| {
+        let recommended = visualizers_with_reason
+            .iter()
+            .filter_map(|(visualizer, _reason)| {
                 if indicated_entities_per_visualizer
                     .get(visualizer)
                     .is_some_and(|matching_list| matching_list.contains(entity_path))
@@ -200,21 +192,6 @@ pub trait ViewClass: Send + Sync {
         None
     }
 
-    /// Ui for quickly navigating the (active) visualizers for a view.
-    ///
-    /// Each view can opt-in to using this feature.
-    #[deprecated(note = "Implement `visualizers_section` instead.", since = "0.30.1")]
-    #[expect(clippy::type_complexity)]
-    fn visualizers_ui<'a>(
-        &'a self,
-        _ctx: &'a ViewerContext<'a>,
-        _view_id: ViewId,
-        _state: &'a mut dyn ViewState,
-        _space_origin: &'a EntityPath,
-    ) -> Option> {
-        None
-    }
-
     /// Determines which views should be spawned by default for this class.
     ///
     /// Only entities matching `include_entity` should be considered,
diff --git a/crates/viewer/re_viewer_context/src/view/visualizer_system.rs b/crates/viewer/re_viewer_context/src/view/visualizer_system.rs
index 6bbae482f569..f8bf9a7b9d82 100644
--- a/crates/viewer/re_viewer_context/src/view/visualizer_system.rs
+++ b/crates/viewer/re_viewer_context/src/view/visualizer_system.rs
@@ -380,4 +380,8 @@ impl VisualizerCollection {
         self.iter()
             .filter_map(|visualizer| visualizer.data()?.downcast_ref::())
     }
+
+    pub fn contains_visualizer_type(&self, name: ViewSystemIdentifier) -> bool {
+        self.systems.contains_key(&name)
+    }
 }
diff --git a/crates/viewer/re_viewer_context/src/viewer_context.rs b/crates/viewer/re_viewer_context/src/viewer_context.rs
index 41be70048b5e..fc8b02704584 100644
--- a/crates/viewer/re_viewer_context/src/viewer_context.rs
+++ b/crates/viewer/re_viewer_context/src/viewer_context.rs
@@ -14,8 +14,8 @@ use crate::time_control::TimeControlCommand;
 use crate::{
     ActiveStoreContext, AppContext, AppOptions, ApplicationSelectionState, CommandSender,
     ComponentUiRegistry, DragAndDropManager, IndicatedEntities, Item, ItemCollection,
-    PerVisualizerType, PerVisualizerTypeInViewClass, StoreHub, StoreViewContext, SystemCommand,
-    SystemCommandSender as _, TimeControl, ViewClassRegistry, ViewId, VisualizableEntities,
+    PerVisualizerType, StoreHub, StoreViewContext, SystemCommand, SystemCommandSender as _,
+    TimeControl, ViewClassRegistry, ViewId, VisualizableEntities,
 };
 
 /// The most powerful context, when you need to know the active blueprint and views.
@@ -367,20 +367,4 @@ impl<'a> ViewerContext<'a> {
                 .map(|(viz_id, entities)| (*viz_id, entities)),
         )
     }
-
-    /// Like [`Self::iter_visualizable_entities_for_view_class`], but collects into a [`PerVisualizerTypeInViewClass`].
-    pub fn collect_visualizable_entities_for_view_class(
-        &self,
-        view_class_identifier: ViewClassIdentifier,
-    ) -> PerVisualizerTypeInViewClass {
-        re_tracing::profile_function!();
-
-        PerVisualizerTypeInViewClass {
-            view_class_identifier,
-            per_visualizer: self
-                .iter_visualizable_entities_for_view_class(view_class_identifier)
-                .map(|(viz_id, entities)| (viz_id, entities.clone()))
-                .collect(),
-        }
-    }
 }
diff --git a/crates/viewer/re_viewport_blueprint/benches/data_query.rs b/crates/viewer/re_viewport_blueprint/benches/data_query.rs
index a6dd87fc71b1..a5cb206756d7 100644
--- a/crates/viewer/re_viewport_blueprint/benches/data_query.rs
+++ b/crates/viewer/re_viewport_blueprint/benches/data_query.rs
@@ -12,8 +12,8 @@ use re_sdk_types::archetypes::Points2D;
 use re_sdk_types::components::Position2D;
 use re_types_core::ViewClassIdentifier;
 use re_viewer_context::{
-    ActiveStoreContext, Caches, PerVisualizerType, PerVisualizerTypeInViewClass, QueryRange,
-    ViewClassRegistry, VisualizableEntities, VisualizableReason, blueprint_timeline,
+    ActiveStoreContext, Caches, PerVisualizerType, QueryRange, ViewClassRegistry,
+    VisualizableEntities, VisualizableReason, blueprint_timeline,
 };
 use re_viewport_blueprint::ViewContents;
 
@@ -58,7 +58,7 @@ fn query_tree_many_entities(c: &mut Criterion) {
     let indicated_entities = PerVisualizerType(
         // Indicate everything.
         visualizable_entities
-            .per_visualizer
+            .0
             .iter()
             .map(|(v, e)| {
                 (
@@ -151,8 +151,8 @@ fn query_tree_many_entities(c: &mut Criterion) {
 // --- Helpers ---
 
 fn build_entity_tree(
-    view_class_id: ViewClassIdentifier,
-) -> (EntityDb, PerVisualizerTypeInViewClass) {
+    _view_class_id: ViewClassIdentifier,
+) -> (EntityDb, PerVisualizerType) {
     use rand::{Rng as _, SeedableRng as _};
     // Use a fixed seed for deterministic, reproducible benchmarks
     let mut rng = rand::rngs::StdRng::seed_from_u64(42);
@@ -203,8 +203,7 @@ fn build_entity_tree(
     }
 
     // Set up visualizable entities - make most entities visualizable
-    let mut visualizable_entities =
-        PerVisualizerTypeInViewClass::::empty(view_class_id);
+    let mut visualizable_entities = PerVisualizerType::::default();
     let visualizable_set = all_entities
         .iter()
         .filter(|_| rng.random_bool(0.7)) // 70% of entities are visualizable
@@ -213,7 +212,7 @@ fn build_entity_tree(
         .collect();
 
     visualizable_entities
-        .per_visualizer
+        .0
         .insert("Points3D".into(), VisualizableEntities(visualizable_set));
 
     (recording, visualizable_entities)
diff --git a/crates/viewer/re_viewport_blueprint/src/lib.rs b/crates/viewer/re_viewport_blueprint/src/lib.rs
index a7a9d750fda6..79636f23a24c 100644
--- a/crates/viewer/re_viewport_blueprint/src/lib.rs
+++ b/crates/viewer/re_viewport_blueprint/src/lib.rs
@@ -12,6 +12,9 @@ mod view_properties;
 mod viewport_blueprint;
 mod viewport_command;
 
+#[cfg(test)]
+mod test_view_class;
+
 pub use container::ContainerBlueprint;
 pub use entity_add_info::{CanAddToView, EntityAddInfo, create_entity_add_info};
 use re_chunk::EntityPath;
diff --git a/crates/viewer/re_viewport_blueprint/src/test_view_class.rs b/crates/viewer/re_viewport_blueprint/src/test_view_class.rs
new file mode 100644
index 000000000000..afca36a108f1
--- /dev/null
+++ b/crates/viewer/re_viewport_blueprint/src/test_view_class.rs
@@ -0,0 +1,95 @@
+//! A minimal view class for testing.
+
+use re_chunk::EntityPath;
+use re_sdk_types::ViewClassIdentifier;
+use re_viewer_context::{
+    IdentifiedViewSystem, SystemExecutionOutput, ViewClass, ViewClassLayoutPriority,
+    ViewClassRegistryError, ViewContext, ViewContextCollection, ViewQuery, ViewSpawnHeuristics,
+    ViewState, ViewSystemExecutionError, ViewSystemIdentifier, ViewSystemRegistrator,
+    ViewerContext, VisualizerExecutionOutput, VisualizerQueryInfo, VisualizerSystem,
+};
+
+/// A minimal visualizer for testing.
+#[derive(Default)]
+pub struct TestVisualizer;
+
+impl IdentifiedViewSystem for TestVisualizer {
+    fn identifier() -> ViewSystemIdentifier {
+        "TestVisualizer".into()
+    }
+}
+
+impl VisualizerSystem for TestVisualizer {
+    fn visualizer_query_info(
+        &self,
+        _app_options: &re_viewer_context::AppOptions,
+    ) -> VisualizerQueryInfo {
+        VisualizerQueryInfo::empty()
+    }
+
+    fn execute(
+        &mut self,
+        _ctx: &ViewContext<'_>,
+        _query: &ViewQuery<'_>,
+        _context_systems: &ViewContextCollection,
+    ) -> Result {
+        Ok(VisualizerExecutionOutput::default())
+    }
+}
+
+/// A minimal view class for testing.
+#[derive(Default)]
+pub struct TestViewClass;
+
+impl ViewClass for TestViewClass {
+    fn identifier() -> ViewClassIdentifier {
+        "TestView".into()
+    }
+
+    fn display_name(&self) -> &'static str {
+        "Test"
+    }
+
+    fn icon(&self) -> &'static re_ui::Icon {
+        &re_ui::icons::VIEW_UNKNOWN
+    }
+
+    fn help(&self, _os: egui::os::OperatingSystem) -> re_ui::Help {
+        re_ui::Help::new("Test view class")
+    }
+
+    fn on_register(
+        &self,
+        system_registry: &mut ViewSystemRegistrator<'_>,
+    ) -> Result<(), ViewClassRegistryError> {
+        system_registry.register_visualizer::()
+    }
+
+    fn new_state(&self) -> Box {
+        Box::<()>::default()
+    }
+
+    fn layout_priority(&self) -> ViewClassLayoutPriority {
+        ViewClassLayoutPriority::Low
+    }
+
+    fn spawn_heuristics(
+        &self,
+        _ctx: &ViewerContext<'_>,
+        _include_entity: &dyn Fn(&EntityPath) -> bool,
+    ) -> ViewSpawnHeuristics {
+        ViewSpawnHeuristics::root()
+    }
+
+    fn ui(
+        &self,
+        _ctx: &ViewerContext<'_>,
+        _missing_chunk_reporter: &re_chunk_store::MissingChunkReporter,
+        _ui: &mut egui::Ui,
+        _state: &mut dyn ViewState,
+        _query: &ViewQuery<'_>,
+        _system_output: SystemExecutionOutput,
+    ) -> Result<(), ViewSystemExecutionError> {
+        Ok(())
+    }
+}
diff --git a/crates/viewer/re_viewport_blueprint/src/view.rs b/crates/viewer/re_viewport_blueprint/src/view.rs
index 4cc33067e4d9..ce38e4f8a97d 100644
--- a/crates/viewer/re_viewport_blueprint/src/view.rs
+++ b/crates/viewer/re_viewport_blueprint/src/view.rs
@@ -461,8 +461,7 @@ mod tests {
     use re_sdk_types::blueprint::archetypes::EntityBehavior;
     use re_test_context::TestContext;
     use re_viewer_context::{
-        PerVisualizerType, PerVisualizerTypeInViewClass, ViewClassPlaceholder,
-        VisualizableEntities, VisualizableReason,
+        PerVisualizerType, ViewClassPlaceholder, VisualizableEntities, VisualizableReason,
     };
 
     use super::*;
@@ -506,11 +505,6 @@ mod tests {
                 });
         }
 
-        let visualizable_entities = PerVisualizerTypeInViewClass:: {
-            view_class_identifier: ViewClassPlaceholder::identifier(),
-            per_visualizer: visualizable_entities.0.clone(),
-        };
-
         // Basic blueprint - a single view that queries everything.
         test_ctx.register_view_class::();
         let view = ViewBlueprint::new_with_root_wildcard(ViewClassPlaceholder::identifier());
@@ -672,7 +666,7 @@ mod tests {
     fn update_overrides(
         test_ctx: &TestContext,
         view: &ViewBlueprint,
-        visualizable_entities: &PerVisualizerTypeInViewClass,
+        visualizable_entities: &PerVisualizerType,
     ) -> re_viewer_context::DataQueryResult {
         let mut result = None;
 
diff --git a/crates/viewer/re_viewport_blueprint/src/view_contents.rs b/crates/viewer/re_viewport_blueprint/src/view_contents.rs
index 08d45cf8a536..cbf78b3a085d 100644
--- a/crates/viewer/re_viewport_blueprint/src/view_contents.rs
+++ b/crates/viewer/re_viewport_blueprint/src/view_contents.rs
@@ -17,15 +17,18 @@ use re_sdk_types::blueprint::{
 use re_sdk_types::{Loggable as _, ViewClassIdentifier};
 use re_viewer_context::{
     DataQueryResult, DataResult, DataResultHandle, DataResultNode, DataResultTree,
-    IndicatedEntities, PerVisualizerType, PerVisualizerTypeInViewClass, QueryRange, ViewId,
-    ViewSystemIdentifier, ViewerContext, VisualizableEntities, VisualizerComponentMappings,
-    VisualizerInstruction,
+    IndicatedEntities, PerVisualizerType, QueryRange, ViewId, ViewSystemIdentifier, ViewerContext,
+    VisualizableEntities, VisualizableReason, VisualizerComponentMappings, VisualizerInstruction,
 };
 use slotmap::SlotMap;
 use smallvec::SmallVec;
 
 use crate::{ViewBlueprint, ViewProperty};
 
+/// All the visualizable entities in a view mapped to visualizer+reason.
+type VisualizableEntitiesPerVisualizerInView<'a> =
+    IntMap>;
+
 /// Data to be added to a view, built from a [`blueprint_archetypes::ViewContents`].
 ///
 /// During execution, it will walk an [`EntityTree`] and return a [`DataResultTree`]
@@ -265,7 +268,7 @@ impl ViewContents {
         view_class_registry: &re_viewer_context::ViewClassRegistry,
         blueprint_query: &LatestAtQuery,
         query_range: &QueryRange,
-        visualizable_entities_per_visualizer: &PerVisualizerTypeInViewClass,
+        visualizable_entities_per_visualizer: &PerVisualizerType,
         indicated_entities_per_visualizer: &PerVisualizerType,
         app_options: &re_viewer_context::AppOptions,
     ) -> DataQueryResult {
@@ -273,11 +276,36 @@ impl ViewContents {
 
         let mut data_results = SlotMap::::default();
 
-        let visualizers_per_entity =
-            Self::visualizers_per_entity(visualizable_entities_per_visualizer);
+        let view_class = view_class_registry.get_class_or_log_error(self.view_class_identifier);
+        let visualizer_collection =
+            view_class_registry.new_visualizer_collection(self.view_class_identifier);
+
+        // TODO(andreas): Build this only once for every type of view.
+        let visualizable_entities_per_visualizer_in_view: VisualizableEntitiesPerVisualizerInView<
+            '_,
+        > = {
+            re_tracing::profile_scope!("visualizable_entities_per_visualizer_in_view");
+
+            let mut visualizable_entities_per_visualizer_in_view = IntMap::default();
+            for (visualizer, visualizable_entities) in visualizable_entities_per_visualizer.iter() {
+                // Skip over visualizers that aren't used in this view.
+                if !visualizer_collection.contains_visualizer_type(*visualizer) {
+                    continue;
+                }
+
+                for (entity_path, reason) in visualizable_entities.iter() {
+                    visualizable_entities_per_visualizer_in_view
+                        .entry(entity_path.hash())
+                        .or_insert_with(Vec::new)
+                        .push((*visualizer, reason));
+                }
+            }
+            visualizable_entities_per_visualizer_in_view
+        };
 
         let executor = QueryExpressionEvaluator {
-            visualizers_per_entity: &visualizers_per_entity,
+            visualizable_entities_per_visualizer_in_view:
+                &visualizable_entities_per_visualizer_in_view,
             entity_path_filter: &self.entity_path_filter,
             view_id: self.view_id,
         };
@@ -298,9 +326,6 @@ impl ViewContents {
         let component_defaults = {
             re_tracing::profile_scope!("component_defaults");
 
-            let visualizer_collection =
-                view_class_registry.new_visualizer_collection(self.view_class_identifier);
-
             // Figure out which components are relevant.
             let mut components_for_defaults = IntSet::default();
             for (visualizer, entities) in visualizable_entities_per_visualizer.iter() {
@@ -335,8 +360,9 @@ impl ViewContents {
         // TODO(andreas): integrate with `add_entity_tree_to_data_results_recursive` above.
         let resolver = DataQueryPropertyResolver {
             view_query_range: query_range,
-            view_class: view_class_registry.get_class_or_log_error(self.view_class_identifier),
-            visualizable_entities_per_visualizer,
+            view_class,
+            visualizable_entities_per_visualizer_in_view:
+                &visualizable_entities_per_visualizer_in_view,
             indicated_entities_per_visualizer,
         };
 
@@ -349,27 +375,6 @@ impl ViewContents {
 
         query_result
     }
-
-    fn visualizers_per_entity(
-        visualizable_entities_for_visualizer_systems: &PerVisualizerTypeInViewClass<
-            VisualizableEntities,
-        >,
-    ) -> IntMap> {
-        re_tracing::profile_function!();
-
-        let mut visualizers_per_entity = IntMap::default();
-        for (visualizer, visualizable_entities) in
-            visualizable_entities_for_visualizer_systems.iter()
-        {
-            for entity_path in visualizable_entities.keys() {
-                visualizers_per_entity
-                    .entry(entity_path.hash())
-                    .or_insert_with(SmallVec::new)
-                    .push(*visualizer);
-            }
-        }
-        visualizers_per_entity
-    }
 }
 
 /// Helper struct for executing the query from [`ViewContents`]
@@ -378,7 +383,7 @@ impl ViewContents {
 /// used to efficiently determine if we should continue the walk or switch
 /// to a pure recursive evaluation.
 struct QueryExpressionEvaluator<'a> {
-    visualizers_per_entity: &'a IntMap>,
+    visualizable_entities_per_visualizer_in_view: &'a VisualizableEntitiesPerVisualizerInView<'a>,
     entity_path_filter: &'a ResolvedEntityPathFilter,
     view_id: ViewId,
 }
@@ -407,7 +412,7 @@ impl QueryExpressionEvaluator<'_> {
         // Also, it's nice to inform the UI about how many entities we could show.
         let any_visualizers_available = matches_filter
             && self
-                .visualizers_per_entity
+                .visualizable_entities_per_visualizer_in_view
                 .get(&entity_path.hash())
                 .is_some_and(|visualizer| !visualizer.is_empty());
         *num_visualized_entities += any_visualizers_available as usize;
@@ -455,7 +460,7 @@ impl QueryExpressionEvaluator<'_> {
 struct DataQueryPropertyResolver<'a> {
     view_query_range: &'a QueryRange,
     view_class: &'a dyn re_viewer_context::ViewClass,
-    visualizable_entities_per_visualizer: &'a PerVisualizerTypeInViewClass,
+    visualizable_entities_per_visualizer_in_view: &'a VisualizableEntitiesPerVisualizerInView<'a>,
     indicated_entities_per_visualizer: &'a PerVisualizerType,
 }
 
@@ -570,9 +575,16 @@ impl DataQueryPropertyResolver<'_> {
                 }
 
                 // Ask the `ViewClass` to choose visualizers heuristically.
+                let visualizers_with_reason: &[(ViewSystemIdentifier, &VisualizableReason)] = self
+                    .visualizable_entities_per_visualizer_in_view
+                    .get(&node.data_result.entity_path.hash())
+                    .map_or(&[], |visualizers_with_reason| {
+                        visualizers_with_reason.as_slice()
+                    });
+
                 let recommended_visualizers = self.view_class.recommended_visualizers_for_entity(
                     &node.data_result.entity_path,
-                    self.visualizable_entities_per_visualizer,
+                    visualizers_with_reason,
                     self.indicated_entities_per_visualizer,
                 );
                 node.data_result.visualizer_instructions = recommended_visualizers
@@ -760,15 +772,18 @@ impl DataQueryPropertyResolver<'_> {
 mod tests {
     use std::sync::Arc;
 
-    use re_chunk::{Chunk, RowId};
+    use re_chunk::{Chunk, EntityPath, RowId};
     use re_entity_db::EntityDb;
     use re_log_types::example_components::{MyPoint, MyPoints};
     use re_log_types::{StoreId, TimePoint, Timeline};
+    use re_types_core::reflection::Reflection;
     use re_viewer_context::{
-        ActiveStoreContext, Caches, ViewClassRegistry, VisualizableReason, blueprint_timeline,
+        ActiveStoreContext, Caches, FallbackProviderRegistry, IdentifiedViewSystem as _,
+        ViewClass as _, ViewClassRegistry, VisualizableReason, blueprint_timeline,
     };
 
     use super::*;
+    use crate::test_view_class::{TestViewClass, TestVisualizer};
 
     #[test]
     fn test_query_results() {
@@ -782,11 +797,20 @@ mod tests {
             re_log_types::StoreKind::Blueprint,
             "test_app",
         ));
-        let view_class_identifier = "3D".into();
+        let view_class_identifier = TestViewClass::identifier();
 
         let timeline_frame = Timeline::new_sequence("frame");
         let timepoint = TimePoint::from_iter([(timeline_frame, 10)]);
-        let view_class_registry = ViewClassRegistry::default();
+
+        let mut view_class_registry = ViewClassRegistry::default();
+        let mut fallback_registry = FallbackProviderRegistry::default();
+        view_class_registry
+            .add_class::(
+                &Reflection::default(),
+                &re_viewer_context::AppOptions::default(),
+                &mut fallback_registry,
+            )
+            .expect("registering test view class should succeed");
 
         // Set up a store DB with some entities
         for entity_path in ["parent", "parent/skipped/child1", "parent/skipped/child2"] {
@@ -805,11 +829,11 @@ mod tests {
         }
 
         let mut visualizable_entities_for_visualizer_systems =
-            PerVisualizerTypeInViewClass::::empty(view_class_identifier);
+            PerVisualizerType::::default();
 
         visualizable_entities_for_visualizer_systems
-            .per_visualizer
-            .entry("Points3D".into())
+            .0
+            .entry(TestVisualizer::identifier())
             .or_insert_with(|| {
                 VisualizableEntities(
                     [
diff --git a/examples/rust/custom_view/src/points3d_color_view.rs b/examples/rust/custom_view/src/points3d_color_view.rs
index 63c558136857..d284b33e4742 100644
--- a/examples/rust/custom_view/src/points3d_color_view.rs
+++ b/examples/rust/custom_view/src/points3d_color_view.rs
@@ -8,11 +8,10 @@ use rerun::external::re_sdk_types::ViewClassIdentifier;
 use rerun::external::re_ui::{self, Help};
 use rerun::external::re_viewer_context::{
     DataResultInteractionAddress, HoverHighlight, IdentifiedViewSystem as _, IndicatedEntities,
-    Item, MissingChunkReporter, PerVisualizerType, PerVisualizerTypeInViewClass,
-    RecommendedVisualizers, SelectionHighlight, SystemExecutionOutput, UiLayout, ViewClass,
-    ViewClassLayoutPriority, ViewClassRegistryError, ViewId, ViewQuery, ViewSpawnHeuristics,
-    ViewState, ViewStateExt as _, ViewSystemExecutionError, ViewSystemRegistrator, ViewerContext,
-    VisualizableEntities,
+    Item, MissingChunkReporter, PerVisualizerType, RecommendedVisualizers, SelectionHighlight,
+    SystemExecutionOutput, UiLayout, ViewClass, ViewClassLayoutPriority, ViewClassRegistryError,
+    ViewId, ViewQuery, ViewSpawnHeuristics, ViewState, ViewStateExt as _, ViewSystemExecutionError,
+    ViewSystemIdentifier, ViewSystemRegistrator, ViewerContext, VisualizableReason,
 };
 
 use crate::points3d_color_visualizer::{ColorWithInstance, Points3DColorVisualizer};
@@ -132,13 +131,13 @@ impl ViewClass for ColorCoordinatesView {
     /// We want to enable the visualizer here though for any visualizable entity instead!
     fn recommended_visualizers_for_entity(
         &self,
-        entity_path: &EntityPath,
-        visualizable_entities_per_visualizer: &PerVisualizerTypeInViewClass,
+        _entity_path: &EntityPath,
+        visualizers: &[(ViewSystemIdentifier, &VisualizableReason)],
         _indicated_entities_per_visualizer: &PerVisualizerType,
     ) -> RecommendedVisualizers {
-        if visualizable_entities_per_visualizer
-            .get(&Points3DColorVisualizer::identifier())
-            .is_some_and(|entities| entities.contains_key(entity_path))
+        if visualizers
+            .iter()
+            .any(|(viz, _)| *viz == Points3DColorVisualizer::identifier())
         {
             RecommendedVisualizers::default(Points3DColorVisualizer::identifier())
         } else {

From d4d4d1c819c046c823fd13f8ee23728eff0d22be Mon Sep 17 00:00:00 2001
From: Emil Ernerfeldt 
Date: Thu, 12 Mar 2026 13:43:29 +0100
Subject: [PATCH 111/513] Slightly better UI for recording with no timelines

Source-Ref: 696f5e307fde37165a62029d2a40ba598f32f03e
---
 .../re_time_panel/src/time_control_ui.rs      |  9 ++-
 crates/viewer/re_time_panel/src/time_panel.rs | 68 ++++++++++++-------
 .../re_viewer_context/src/time_control.rs     | 27 +++++++-
 3 files changed, 77 insertions(+), 27 deletions(-)

diff --git a/crates/viewer/re_time_panel/src/time_control_ui.rs b/crates/viewer/re_time_panel/src/time_control_ui.rs
index aaaaf603c2b9..66559449371c 100644
--- a/crates/viewer/re_time_panel/src/time_control_ui.rs
+++ b/crates/viewer/re_time_panel/src/time_control_ui.rs
@@ -26,7 +26,14 @@ impl TimeControlUi {
             let response = egui::ComboBox::from_id_salt("timeline")
                 .selected_text(time_ctrl.timeline_name().as_str())
                 .show_ui(ui, |ui| {
-                    for timeline in entity_db.timelines().values() {
+                    let timelines = entity_db.timelines();
+
+                    if timelines.is_empty() {
+                        ui.weak("The recording has no timelines");
+                        return;
+                    }
+
+                    for timeline in timelines.values() {
                         let num_rows = entity_db.num_temporal_rows_on_timeline(timeline.name());
                         if ui
                             .selectable_label(
diff --git a/crates/viewer/re_time_panel/src/time_panel.rs b/crates/viewer/re_time_panel/src/time_panel.rs
index 181539013c18..c3a1aa3c6d26 100644
--- a/crates/viewer/re_time_panel/src/time_panel.rs
+++ b/crates/viewer/re_time_panel/src/time_panel.rs
@@ -9,6 +9,7 @@ use egui::{
 use re_context_menu::{SelectionUpdateBehavior, context_menu_ui_for_item_with_context};
 use re_data_ui::DataUi as _;
 use re_entity_db::InstancePath;
+use re_entity_db::entity_db::RedapConnectionState;
 use re_log_types::{
     AbsoluteTimeRange, ApplicationId, ComponentPath, EntityPath, TimeInt, TimeReal,
 };
@@ -352,7 +353,16 @@ impl TimePanel {
         let loading_text = if entity_db.is_downloading_first_part_of_manifest() {
             Some("Downloading meta-data")
         } else if time_ctrl.is_pending() {
-            Some("Waiting for timeline")
+            if entity_db.redap_connection_state() == RedapConnectionState::PartialManifest {
+                Some("Waiting for timeline")
+            } else {
+                ui.horizontal(|ui| {
+                    ui.centered_and_justified(|ui| {
+                        ui.label("The recording has no timelines");
+                    });
+                });
+                return;
+            }
         } else {
             None
         };
@@ -437,32 +447,40 @@ impl TimePanel {
         }
 
         if store_ctx.time_ctrl.is_pending() {
-            ui.loading_screen_ui("Waiting for timeline", |ui| {
-                let text = format!("Waiting for timeline: {}", store_ctx.timeline_name());
-                ui.label(egui::RichText::from(text).heading().strong());
-
-                let timeline_count = store_ctx.db.timelines().len();
-
-                match timeline_count {
-                    0 => {}
-                    1 => {
-                        ui.label("One other timeline has data");
-                    }
-                    c => {
-                        ui.label(format!("{c} other timelines have data"));
+            if store_ctx.db.redap_connection_state() == RedapConnectionState::PartialManifest {
+                ui.loading_screen_ui("Waiting for timeline", |ui| {
+                    let text = format!("Waiting for timeline: {}", store_ctx.timeline_name());
+                    ui.label(egui::RichText::from(text).heading().strong());
+
+                    let timeline_count = store_ctx.db.timelines().len();
+
+                    match timeline_count {
+                        0 => {}
+                        1 => {
+                            ui.label("One other timeline has data");
+                        }
+                        c => {
+                            ui.label(format!("{c} other timelines have data"));
+                        }
                     }
-                }
 
-                if ui
-                    .button(
-                        egui::RichText::new("Go to default timeline")
-                            .color(ui.style().visuals.weak_text_color()),
-                    )
-                    .clicked()
-                {
-                    time_commands.push(TimeControlCommand::ResetActiveTimeline);
-                }
-            });
+                    if ui
+                        .button(
+                            egui::RichText::new("Go to default timeline")
+                                .color(ui.style().visuals.weak_text_color()),
+                        )
+                        .clicked()
+                    {
+                        time_commands.push(TimeControlCommand::ResetActiveTimeline);
+                    }
+                });
+            } else {
+                ui.horizontal(|ui| {
+                    ui.centered_and_justified(|ui| {
+                        ui.label("The recording has no timelines");
+                    });
+                });
+            }
 
             return;
         }
diff --git a/crates/viewer/re_viewer_context/src/time_control.rs b/crates/viewer/re_viewer_context/src/time_control.rs
index 98575eefcf84..e970a3966c7a 100644
--- a/crates/viewer/re_viewer_context/src/time_control.rs
+++ b/crates/viewer/re_viewer_context/src/time_control.rs
@@ -320,15 +320,33 @@ impl TimeState {
     }
 }
 
+/// Which timeline is currently active in the time panel.
+///
+/// The active timeline can be in one of three states:
+/// - Automatically chosen based on heuristics (e.g. the timeline with most data),
+/// - Explicitly selected by the user,
+/// - Or "pending": requested by name (via blueprint or user action) but not yet
+///   present in the entity database. A pending timeline is promoted to `UserEdited`
+///   once data containing that timeline arrives (see [`TimeControl::select_valid_timeline`]).
 // TODO(andreas): This should be a blueprint property and follow the usual rules of how we determine fallbacks.
 #[derive(serde::Deserialize, serde::Serialize, Clone, PartialEq, Debug)]
 enum ActiveTimeline {
+    /// Automatically selected based on heuristics. Re-evaluated every frame.
     Auto(Timeline),
+
+    /// Explicitly selected by the user or resolved from blueprint.
     UserEdited(Timeline),
+
+    /// A timeline was requested by name but hasn't been seen in the data yet.
+    ///
+    /// This happens when the blueprint or a [`TimeControlCommand::SetActiveTimeline`] references
+    /// a timeline that doesn't exist in the current [`EntityDb`]. We store only the name
+    /// and wait for matching data to arrive, at which point this becomes `UserEdited`.
     Pending(TimelineName),
 }
 
 impl ActiveTimeline {
+    /// The name of the active timeline, regardless of its state.
     pub fn name(&self) -> &TimelineName {
         match self {
             Self::Auto(timeline) | Self::UserEdited(timeline) => timeline.name(),
@@ -336,6 +354,10 @@ impl ActiveTimeline {
         }
     }
 
+    /// The full [`Timeline`], if available.
+    ///
+    /// Returns `None` for [`Self::Pending`] since the timeline hasn't been
+    /// resolved against the entity database yet.
     pub fn timeline(&self) -> Option<&Timeline> {
         match self {
             Self::Auto(timeline) | Self::UserEdited(timeline) => Some(timeline),
@@ -1451,7 +1473,10 @@ impl TimeControl {
         }
     }
 
-    /// Is the active timeline pending?
+    /// Is the active timeline pending resolution?
+    ///
+    /// When `true`, the requested timeline name hasn't been found in the data yet,
+    /// so [`Self::timeline()`] returns `None` and time-dependent queries may not work.
     pub fn is_pending(&self) -> bool {
         matches!(self.timeline, ActiveTimeline::Pending(_))
     }

From 11c3df756ca0a38bb6de9f751bad8fa7533de8a5 Mon Sep 17 00:00:00 2001
From: Isse 
Date: Thu, 12 Mar 2026 14:47:33 +0100
Subject: [PATCH 112/513] Fix repeating count on hovering ui

### Related

- Closes RR-4031

### What

Fixes repeating the count in UI when hovering over the view report
button.

Source-Ref: 35263c5abcfdf577f5ac809303634fd9c5aada7f
---
 crates/viewer/re_viewport/src/viewport_ui.rs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/crates/viewer/re_viewport/src/viewport_ui.rs b/crates/viewer/re_viewport/src/viewport_ui.rs
index 6e3b295f7dd5..aa238ad55291 100644
--- a/crates/viewer/re_viewport/src/viewport_ui.rs
+++ b/crates/viewer/re_viewport/src/viewport_ui.rs
@@ -805,7 +805,7 @@ impl TilesDelegate<'_, '_> {
             let response = ui
                 .add(egui::Button::image(report_image))
                 .on_hover_text(format!(
-                    "Show {report_count} {}",
+                    "Show {}",
                     re_format::format_plural_s(report_count, "visualizer report")
                 ));
 

From 7aed140a1526c6ed29909a41bc605fa75abc271a Mon Sep 17 00:00:00 2001
From: Emil Ernerfeldt 
Date: Thu, 12 Mar 2026 15:17:55 +0100
Subject: [PATCH 113/513] Speed up ChunkStore schema lookups

This adds a `StoreSchema`, owned and managed by `ChunkStore`.

Instead of computing it from scratch in the `.schema()` call, keep track
of it with an internal store subscriber.

Additionally, all queries on the `ChunkStore` that is only about the
schema now go via the `.schema()` accessor. That makes it clear that
"This question can be answered without any information about chunks".

---------

Source-Ref: 1f6b88d7b97f5c84f197aac50ef655afd7519ac2
Co-authored-by: Claude Opus 4.6 
---
 AGENTS.md                                     |   2 +-
 Cargo.lock                                    |   2 +-
 crates/store/re_chunk_store/Cargo.toml        |   1 -
 crates/store/re_chunk_store/src/dataframe.rs  |  96 +----
 crates/store/re_chunk_store/src/gc.rs         |   6 +-
 crates/store/re_chunk_store/src/lib.rs        |   2 +
 crates/store/re_chunk_store/src/query.rs      | 110 +----
 crates/store/re_chunk_store/src/stats.rs      |  27 +-
 crates/store/re_chunk_store/src/store.rs      |  91 +---
 .../store/re_chunk_store/src/store_schema.rs  | 393 ++++++++++++++++++
 crates/store/re_chunk_store/src/writes.rs     | 105 +----
 .../store/re_chunk_store/tests/dataframe.rs   |   2 +-
 .../re_data_loader/tests/test_mcap_loader.rs  |   2 +-
 crates/store/re_dataframe/src/engine.rs       |   3 +-
 crates/store/re_entity_db/src/entity_db.rs    |  10 +-
 crates/store/re_query/src/cache.rs            |   6 +-
 crates/store/re_query/src/storage_engine.rs   |  10 +
 crates/store/re_server/src/store/dataset.rs   |   6 +-
 crates/store/re_server/src/store/layer.rs     |   7 +-
 crates/store/re_sorbet/Cargo.toml             |   1 +
 .../src/component_column_descriptor.rs        |  21 +
 .../re_sorbet/src/index_column_descriptor.rs  |  11 +
 crates/top/rerun/src/commands/entrypoint.rs   |   7 +-
 crates/top/rerun/src/commands/rrd/split.rs    |   3 +-
 .../re_chunk_store_ui/src/chunk_list_mode.rs  |   2 +-
 .../re_chunk_store_ui/src/chunk_store_ui.rs   |   2 +-
 .../re_data_ui/src/annotation_context_ui.rs   |   8 +-
 crates/viewer/re_data_ui/src/entity_db_ui.rs  |   2 +-
 .../viewer/re_data_ui/src/instance_path_ui.rs |   2 +-
 .../re_data_ui/src/latest_at_instance_ui.rs   |   2 +-
 .../re_selection_panel/src/defaults_ui.rs     |   2 +-
 .../re_selection_panel/src/selection_panel.rs |   2 +-
 .../re_selection_panel/src/visualizer_ui.rs   |  18 +-
 .../re_time_panel/src/streams_tree_data.rs    |  10 +-
 crates/viewer/re_view/src/query.rs            |   2 +-
 crates/viewer/re_view_spatial/src/view_3d.rs  |   4 +-
 .../re_viewer/src/blueprint/validation.rs     |   2 +-
 .../src/component_fallbacks.rs                |   5 +-
 .../re_viewer_context/src/image_info.rs       |   2 +-
 .../viewer/re_viewport_blueprint/src/view.rs  |   2 +-
 .../src/view_contents.rs                      |  24 +-
 .../src/view_properties.rs                    |   5 +-
 rerun_py/src/recording/rrd.rs                 |   2 +-
 43 files changed, 588 insertions(+), 434 deletions(-)
 create mode 100644 crates/store/re_chunk_store/src/store_schema.rs

diff --git a/AGENTS.md b/AGENTS.md
index 495e2f9738a7..d81d01fb8f7c 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -27,7 +27,7 @@ We use `pixi` for task management and dependency installation. Check `pixi.toml`
 - `pixi run codegen` - Generate Rust/Python/C++ code from .fbs type definitions
 
 **Formatting:**
-- `pixi run rs-fmt` - Format Rust files
+- `pixi run rs-fmt` - Format Rust files. **Always run this after editing Rust files, before committing.**
 - `pixi run py-fmt` - Format Python files
 - `pixi run cpp-fmt` - Format C++ files
 - `pixi run toml-fmt` - Format TOML files
diff --git a/Cargo.lock b/Cargo.lock
index 4a25659e5dcc..69e4934f1fdc 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -8666,7 +8666,6 @@ dependencies = [
  "re_types_core",
  "saturating_cast",
  "similar-asserts",
- "tap",
  "thiserror 2.0.18",
  "web-time",
 ]
@@ -9894,6 +9893,7 @@ dependencies = [
  "itertools 0.14.0",
  "nohash-hasher",
  "re_arrow_util",
+ "re_byte_size",
  "re_log",
  "re_log_types",
  "re_tracing",
diff --git a/crates/store/re_chunk_store/Cargo.toml b/crates/store/re_chunk_store/Cargo.toml
index b2025d7e27f2..e7fe45f4f2a4 100644
--- a/crates/store/re_chunk_store/Cargo.toml
+++ b/crates/store/re_chunk_store/Cargo.toml
@@ -50,7 +50,6 @@ itertools.workspace = true
 nohash-hasher.workspace = true
 parking_lot = { workspace = true, features = ["arc_lock"] }
 saturating_cast.workspace = true
-tap.workspace = true
 thiserror.workspace = true
 web-time.workspace = true
 
diff --git a/crates/store/re_chunk_store/src/dataframe.rs b/crates/store/re_chunk_store/src/dataframe.rs
index 2e74dee65ab6..ac49ceda175d 100644
--- a/crates/store/re_chunk_store/src/dataframe.rs
+++ b/crates/store/re_chunk_store/src/dataframe.rs
@@ -3,16 +3,13 @@
 use std::collections::{BTreeMap, BTreeSet};
 use std::ops::{Deref, DerefMut};
 
-use arrow::array::ListArray as ArrowListArray;
-use arrow::datatypes::{DataType as ArrowDatatype, Field as ArrowField};
-use itertools::Itertools as _;
+use arrow::datatypes::DataType as ArrowDatatype;
 use re_chunk::{ComponentIdentifier, LatestAtQuery, RangeQuery, TimelineName};
 use re_log_types::{AbsoluteTimeRange, EntityPath, TimeInt, Timeline};
 use re_sorbet::{
     ChunkColumnDescriptors, ColumnSelector, ComponentColumnDescriptor, ComponentColumnSelector,
     IndexColumnDescriptor, TimeColumnSelector,
 };
-use tap::Tap as _;
 
 use crate::{ChunkStore, ColumnMetadata};
 
@@ -308,76 +305,6 @@ impl QueryExpression {
 // ---
 
 impl ChunkStore {
-    /// Returns the full schema of the store.
-    ///
-    /// This will include a column descriptor for every timeline and every component on every
-    /// entity that has been written to the store so far.
-    ///
-    /// The order of the columns is guaranteed to be in a specific order:
-    /// * first, the time columns in lexical order (`frame_nr`, `log_time`, ...);
-    /// * second, the component columns in lexical order (`Color`, `Radius, ...`).
-    pub fn schema(&self) -> ChunkColumnDescriptors {
-        re_tracing::profile_function!();
-
-        let indices = self
-            .timelines()
-            .values()
-            .map(|timeline| IndexColumnDescriptor::from(*timeline))
-            .collect();
-
-        let components = self
-            .per_column_metadata
-            .iter()
-            .flat_map(|(entity_path, per_identifier)| {
-                per_identifier
-                    .values()
-                    .map(move |(descr, _, datatype)| (entity_path, descr, datatype))
-            })
-            .filter_map(|(entity_path, component_descr, datatype)| {
-                let metadata =
-                    self.lookup_column_metadata(entity_path, component_descr.component)?;
-
-                Some(((entity_path, component_descr), (metadata, datatype)))
-            })
-            .map(|((entity_path, component_descr), (metadata, datatype))| {
-                let ColumnMetadata {
-                    is_static,
-                    is_tombstone,
-                    is_semantically_empty,
-                } = metadata;
-
-                if let Some(c) = component_descr.component_type {
-                    c.sanity_check();
-                }
-
-                ComponentColumnDescriptor {
-                    // NOTE: The data is always a at least a list, whether it's latest-at or range.
-                    // It might be wrapped further in e.g. a dict, but at the very least
-                    // it's a list.
-                    store_datatype: ArrowListArray::DATA_TYPE_CONSTRUCTOR(
-                        ArrowField::new("item", datatype.clone(), true).into(),
-                    ),
-
-                    entity_path: entity_path.clone(),
-                    archetype: component_descr.archetype,
-                    component: component_descr.component,
-                    component_type: component_descr.component_type,
-                    is_static,
-                    is_tombstone,
-                    is_semantically_empty,
-                }
-            })
-            .collect_vec()
-            .tap_mut(|components| components.sort());
-
-        ChunkColumnDescriptors {
-            row_id: self.row_id_descriptor(),
-            indices,
-            components,
-        }
-        .tap(|schema| schema.sanity_check())
-    }
-
     #[expect(clippy::unused_self)]
     pub fn row_id_descriptor(&self) -> re_sorbet::RowIdColumnDescriptor {
         re_sorbet::RowIdColumnDescriptor::from_sorted(false)
@@ -385,7 +312,7 @@ impl ChunkStore {
 
     /// Given a [`TimeColumnSelector`], returns the corresponding [`IndexColumnDescriptor`].
     pub fn resolve_time_selector(&self, selector: &TimeColumnSelector) -> IndexColumnDescriptor {
-        let timelines = self.timelines();
+        let timelines = self.schema.timelines();
 
         let timeline = timelines
             .get(&selector.timeline)
@@ -421,21 +348,22 @@ impl ChunkStore {
             is_semantically_empty: false,
         };
 
-        let per_identifier = self.per_column_metadata.get(&selector.entity_path)?;
+        let per_identifier = self
+            .schema
+            .per_column_metadata_for_entity(&selector.entity_path)?;
 
         // We perform a scan over all component descriptors in the queried entity path.
-        let (component_descr, _, datatype) =
-            per_identifier.get(&selector.component.as_str().into())?;
+        let entry = per_identifier.get(&selector.component.as_str().into())?;
 
-        result.store_datatype = datatype.clone();
-        result.archetype = component_descr.archetype;
-        result.component_type = component_descr.component_type;
+        result.store_datatype = entry.datatype.clone();
+        result.archetype = entry.descriptor.archetype;
+        result.component_type = entry.descriptor.component_type;
 
         if let Some(ColumnMetadata {
             is_static,
             is_tombstone,
             is_semantically_empty,
-        }) = self.lookup_column_metadata(&selector.entity_path, component_descr.component)
+        }) = self.lookup_column_metadata(&selector.entity_path, entry.descriptor.component)
         {
             result.is_static = is_static;
             result.is_tombstone = is_tombstone;
@@ -455,7 +383,9 @@ impl ChunkStore {
 
         let filter = Self::create_component_filter_from_query(query);
 
-        self.schema().filter_components(filter)
+        self.schema
+            .chunk_column_descriptors()
+            .filter_components(filter)
     }
 
     pub fn create_component_filter_from_query(
diff --git a/crates/store/re_chunk_store/src/gc.rs b/crates/store/re_chunk_store/src/gc.rs
index 96dc7fd77c5d..35ab5b15f9f1 100644
--- a/crates/store/re_chunk_store/src/gc.rs
+++ b/crates/store/re_chunk_store/src/gc.rs
@@ -515,8 +515,7 @@ impl ChunkStore {
         let Self {
             id: _,
             config: _,
-            time_type_registry: _,                // purely additive
-            per_column_metadata: _,               // purely additive
+            schema: _,                            // purely additive
             physical_chunks_per_chunk_id: _,      // handled by shallow impl
             physical_chunk_ids_per_min_row_id: _, // handled by shallow impl
             chunks_lineage,                       // purely additive
@@ -679,8 +678,7 @@ impl ChunkStore {
         let Self {
             id: _,
             config: _,
-            time_type_registry: _,  // purely additive
-            per_column_metadata: _, // purely additive
+            schema: _, // purely additive
             physical_chunks_per_chunk_id,
             physical_chunk_ids_per_min_row_id,
             chunks_lineage: _,                              // virtual
diff --git a/crates/store/re_chunk_store/src/lib.rs b/crates/store/re_chunk_store/src/lib.rs
index 499791e8d18b..c86821670d3a 100644
--- a/crates/store/re_chunk_store/src/lib.rs
+++ b/crates/store/re_chunk_store/src/lib.rs
@@ -24,6 +24,7 @@ mod properties;
 mod query;
 mod stats;
 mod store;
+mod store_schema;
 mod subscribers;
 mod writes;
 
@@ -56,6 +57,7 @@ pub use self::store::{
     ChunkStore, ChunkStoreConfig, ChunkStoreGeneration, ChunkStoreHandle, ChunkStoreHandleWeak,
     ColumnMetadata, QueriedChunkIdTracker,
 };
+pub use self::store_schema::StoreSchema;
 pub use self::subscribers::{
     ChunkStoreSubscriber, ChunkStoreSubscriberHandle, PerStoreChunkSubscriber,
 };
diff --git a/crates/store/re_chunk_store/src/query.rs b/crates/store/re_chunk_store/src/query.rs
index 4cc7559fd821..b3735b6c23db 100644
--- a/crates/store/re_chunk_store/src/query.rs
+++ b/crates/store/re_chunk_store/src/query.rs
@@ -1,4 +1,4 @@
-use std::collections::{BTreeMap, BTreeSet};
+use std::collections::BTreeSet;
 use std::sync::Arc;
 
 use itertools::{Either, Itertools as _};
@@ -9,8 +9,8 @@ use saturating_cast::SaturatingCast as _;
 use re_chunk::{
     Chunk, ChunkId, ComponentIdentifier, LatestAtQuery, RangeQuery, TimeColumn, TimelineName,
 };
-use re_log_types::{AbsoluteTimeRange, EntityPath, TimeInt, Timeline};
-use re_types_core::{ComponentDescriptor, ComponentSet, UnorderedComponentSet};
+use re_log_types::{AbsoluteTimeRange, EntityPath, TimeInt};
+use re_types_core::{ComponentSet, UnorderedComponentSet};
 
 use crate::{ChunkStore, ChunkTrackingMode};
 // Used all over in docstrings.
@@ -27,15 +27,6 @@ use crate::store::ChunkIdSetPerTime;
 
 // Meta queries
 impl ChunkStore {
-    /// Retrieve all [`Timeline`]s in the store.
-    #[inline]
-    pub fn timelines(&self) -> BTreeMap {
-        self.time_type_registry
-            .iter()
-            .map(|(name, typ)| (*name, Timeline::new(*name, *typ)))
-            .collect()
-    }
-
     /// Retrieve all [`EntityPath`]s in the store.
     #[inline]
     pub fn all_entities(&self) -> IntSet {
@@ -171,7 +162,7 @@ impl ChunkStore {
     }
 
     /// Retrieve all the [`ComponentIdentifier`]s that have been written to for a given [`EntityPath`] on
-    /// the specified [`Timeline`].
+    /// the specified [`re_chunk::Timeline`].
     ///
     /// Static components are always included in the results.
     ///
@@ -220,7 +211,7 @@ impl ChunkStore {
     }
 
     /// Retrieve all the [`ComponentIdentifier`]s that have been written to for a given [`EntityPath`] on
-    /// the specified [`Timeline`].
+    /// the specified [`re_chunk::Timeline`].
     ///
     /// Static components are always included in the results.
     ///
@@ -268,97 +259,6 @@ impl ChunkStore {
         }
     }
 
-    /// Retrieve all the [`ComponentIdentifier`]s that have been written to for a given [`EntityPath`].
-    ///
-    /// Static components are always included in the results.
-    ///
-    /// Returns `None` if the entity has never had any data logged to it.
-    pub fn all_components_for_entity(
-        &self,
-        entity_path: &EntityPath,
-    ) -> Option {
-        re_tracing::profile_function!();
-
-        let static_components: Option = self
-            .static_chunk_ids_per_entity
-            .get(entity_path)
-            .map(|static_chunks_per_component| {
-                static_chunks_per_component.keys().copied().collect()
-            });
-
-        let temporal_components: Option = self
-            .temporal_chunk_ids_per_entity_per_component
-            .get(entity_path)
-            .map(|temporal_chunk_ids_per_timeline| {
-                temporal_chunk_ids_per_timeline
-                    .values()
-                    .flat_map(|temporal_chunk_ids_per_component| {
-                        temporal_chunk_ids_per_component.keys().copied()
-                    })
-                    .collect()
-            });
-
-        match (static_components, temporal_components) {
-            (None, None) => None,
-            (None, comps @ Some(_)) | (comps @ Some(_), None) => comps,
-            (Some(static_comps), Some(temporal_comps)) => {
-                Some(static_comps.into_iter().chain(temporal_comps).collect())
-            }
-        }
-    }
-
-    /// Retrieve all the [`ComponentIdentifier`]s that have been written to for a given [`EntityPath`].
-    ///
-    /// Static components are always included in the results.
-    ///
-    /// Returns `None` if the entity has never had any data logged to it.
-    pub fn all_components_for_entity_sorted(
-        &self,
-        entity_path: &EntityPath,
-    ) -> Option {
-        re_tracing::profile_function!();
-
-        let static_components: Option = self
-            .static_chunk_ids_per_entity
-            .get(entity_path)
-            .map(|static_chunks_per_component| {
-                static_chunks_per_component.keys().copied().collect()
-            });
-
-        let temporal_components: Option = self
-            .temporal_chunk_ids_per_entity_per_component
-            .get(entity_path)
-            .map(|temporal_chunk_ids_per_timeline| {
-                temporal_chunk_ids_per_timeline
-                    .values()
-                    .flat_map(|temporal_chunk_ids_per_component| {
-                        temporal_chunk_ids_per_component.keys().copied()
-                    })
-                    .collect()
-            });
-
-        match (static_components, temporal_components) {
-            (None, None) => None,
-            (None, comps @ Some(_)) | (comps @ Some(_), None) => comps,
-            (Some(static_comps), Some(temporal_comps)) => {
-                Some(static_comps.into_iter().chain(temporal_comps).collect())
-            }
-        }
-    }
-
-    /// Retrieves the [`ComponentDescriptor`] at a given [`EntityPath`] that has a certain [`ComponentIdentifier`].
-    // TODO(andreas): The descriptor for a given identifier should never change within a recording.
-    pub fn entity_component_descriptor(
-        &self,
-        entity_path: &EntityPath,
-        component: ComponentIdentifier,
-    ) -> Option {
-        self.per_column_metadata
-            .get(entity_path)
-            .and_then(|per_identifier| per_identifier.get(&component))
-            .map(|(component_descr, _, _)| component_descr.clone())
-    }
-
     /// Check whether an entity has a static component or a temporal component on the specified timeline.
     ///
     /// This does _not_ check if the entity actually currently holds any data for that component.
diff --git a/crates/store/re_chunk_store/src/stats.rs b/crates/store/re_chunk_store/src/stats.rs
index 9c1925e779f5..5d5fdf05e01b 100644
--- a/crates/store/re_chunk_store/src/stats.rs
+++ b/crates/store/re_chunk_store/src/stats.rs
@@ -347,7 +347,8 @@ impl ChunkStore {
         entity_path: &EntityPath,
         component: ComponentIdentifier,
     ) -> u64 {
-        self.timelines()
+        self.schema
+            .timelines()
             .keys()
             .map(|timeline| {
                 self.num_physical_temporal_events_for_component_on_timeline(
@@ -371,8 +372,7 @@ impl SizeBytes for ChunkStore {
             temporal_chunk_ids_per_entity_per_component,
             id,
             config,
-            time_type_registry,
-            per_column_metadata,
+            schema,
             physical_chunk_ids_per_min_row_id,
             chunks_lineage,
             dangling_splits,
@@ -425,12 +425,8 @@ impl SizeBytes for ChunkStore {
             + id.heap_size_bytes()
             + config.heap_size_bytes()
             + {
-                profile_scope!("time_type_registry");
-                time_type_registry.heap_size_bytes()
-            }
-            + {
-                profile_scope!("per_column_metadata");
-                per_column_metadata.heap_size_bytes()
+                profile_scope!("schema");
+                schema.heap_size_bytes()
             }
             + {
                 profile_scope!("physical_chunk_ids_per_min_row_id");
@@ -484,12 +480,17 @@ impl MemUsageTreeCapture for ChunkStore {
             }
         }
 
-        let mut node = MemUsageNode::new();
-
+        let mut entities_node = MemUsageNode::new();
         for (entity_path, size) in memory_per_entity {
-            node.add(entity_path.to_string(), MemUsageTree::Bytes(size));
+            entities_node.add(entity_path.to_string(), MemUsageTree::Bytes(size));
         }
 
-        node.with_total_size_bytes(self.total_size_bytes())
+        MemUsageNode::new()
+            .with_child(
+                "schema",
+                MemUsageTree::Bytes(self.schema.total_size_bytes()),
+            )
+            .with_child("entities", entities_node.into_tree())
+            .with_total_size_bytes(self.total_size_bytes())
     }
 }
diff --git a/crates/store/re_chunk_store/src/store.rs b/crates/store/re_chunk_store/src/store.rs
index 000183233085..06bd0f888aee 100644
--- a/crates/store/re_chunk_store/src/store.rs
+++ b/crates/store/re_chunk_store/src/store.rs
@@ -3,15 +3,13 @@ use std::sync::Arc;
 use std::sync::atomic::AtomicU64;
 
 use ahash::{HashMap, HashSet};
-use arrow::datatypes::DataType as ArrowDataType;
 use itertools::Itertools as _;
 use nohash_hasher::IntMap;
 use parking_lot::RwLock;
 use re_log::debug_assert;
 
 use re_chunk::{Chunk, ChunkId, ComponentIdentifier, RowId, TimelineName};
-use re_log_types::{EntityPath, StoreId, TimeInt, TimeType};
-use re_types_core::{ComponentDescriptor, ComponentType};
+use re_log_types::{EntityPath, StoreId, TimeInt};
 
 use crate::{ChunkDirectLineage, ChunkStoreChunkStats, ChunkStoreError, ChunkStoreResult};
 
@@ -495,18 +493,11 @@ pub struct ChunkStore {
     /// The configuration of the chunk store (e.g. compaction settings).
     pub(crate) config: ChunkStoreConfig,
 
-    /// Keeps track of the _latest_ datatype for each time column.
+    /// Incrementally maintained store schema.
     ///
-    /// This index is purely additive: it is never affected by garbage collection in any way.
-    ///
-    /// See also [`Self::time_column_type`].
-    pub(crate) time_type_registry: IntMap,
-
-    // TODO(grtlr): Can we slim this map down by getting rid of `ColumnIdentifier`-level here?
-    pub(crate) per_column_metadata: IntMap<
-        EntityPath,
-        IntMap,
-    >,
+    /// Contains all column descriptors and per-entity component sets.
+    /// Purely additive: never affected by garbage collection.
+    pub(crate) schema: crate::StoreSchema,
 
     /// All the *physical* chunks currently loaded in the store, mapped by their respective IDs.
     ///
@@ -609,7 +600,7 @@ pub struct ChunkStore {
         ChunkIdSetPerTimePerComponentPerTimelinePerEntity,
 
     /// All *physical & virtual* temporal [`ChunkId`]s for all entities on all timelines, without the
-    /// [`ComponentType`] index.
+    /// [`re_types_core::ComponentType`] index.
     ///
     /// This index is purely additive: it is never affected by garbage collection in any way.
     /// This implies that the chunk IDs present in this set might be either physical/loaded or
@@ -677,8 +668,7 @@ impl Clone for ChunkStore {
         Self {
             id: self.id.clone(),
             config: self.config.clone(),
-            time_type_registry: self.time_type_registry.clone(),
-            per_column_metadata: self.per_column_metadata.clone(),
+            schema: self.schema.clone(),
             physical_chunks_per_chunk_id: self.physical_chunks_per_chunk_id.clone(),
             chunks_lineage: self.chunks_lineage.clone(),
             dangling_splits: self.dangling_splits.clone(),
@@ -705,9 +695,8 @@ impl std::fmt::Display for ChunkStore {
         let Self {
             id,
             config,
-            time_type_registry: _,
-            per_column_metadata: _,
-            physical_chunks_per_chunk_id: chunks_per_chunk_id,
+            schema: _,
+            physical_chunks_per_chunk_id,
             physical_chunk_ids_per_min_row_id: chunk_ids_per_min_row_id,
             chunks_lineage,
             dangling_splits: _,
@@ -738,7 +727,7 @@ impl std::fmt::Display for ChunkStore {
 
         f.write_str(&indent::indent_all_by(4, "physical chunks: [\n"))?;
         for chunk_id in chunk_ids_per_min_row_id.values() {
-            if let Some(chunk) = chunks_per_chunk_id.get(chunk_id) {
+            if let Some(chunk) = physical_chunks_per_chunk_id.get(chunk_id) {
                 f.write_str(&indent::indent_all_by(
                     8,
                     format!("{}\n", self.format_lineage(chunk_id)),
@@ -758,7 +747,7 @@ impl std::fmt::Display for ChunkStore {
 
         f.write_str(&indent::indent_all_by(4, "virtual chunks: [\n"))?;
         for chunk_id in chunks_lineage.keys().sorted() {
-            if chunks_per_chunk_id.contains_key(chunk_id) {
+            if physical_chunks_per_chunk_id.contains_key(chunk_id) {
                 continue;
             }
 
@@ -788,8 +777,7 @@ impl ChunkStore {
         Self {
             id,
             config,
-            time_type_registry: Default::default(),
-            per_column_metadata: Default::default(),
+            schema: Default::default(),
             physical_chunk_ids_per_min_row_id: Default::default(),
             chunks_lineage: Default::default(),
             dangling_splits: Default::default(),
@@ -878,17 +866,20 @@ impl ChunkStore {
         self.physical_chunks_per_chunk_id.len()
     }
 
+    /// The incrementally maintained store schema.
+    ///
+    /// Contains all column descriptors, per-entity component sets,
+    /// timeline types, and per-column metadata.
+    #[inline]
+    pub fn schema(&self) -> &crate::StoreSchema {
+        &self.schema
+    }
+
     /// All the currently loaded chunks
     pub fn physical_chunks(&self) -> impl Iterator> + '_ {
         self.physical_chunks_per_chunk_id.values()
     }
 
-    /// Lookup the _latest_ [`TimeType`] used by a specific [`TimelineName`].
-    #[inline]
-    pub fn time_column_type(&self, timeline_name: &TimelineName) -> Option {
-        self.time_type_registry.get(timeline_name).copied()
-    }
-
     /// Lookup the [`ColumnMetadata`] for a specific [`EntityPath`] and [`re_types_core::Component`].
     pub fn lookup_column_metadata(
         &self,
@@ -898,10 +889,8 @@ impl ChunkStore {
         let ColumnMetadataState {
             is_semantically_empty,
         } = self
-            .per_column_metadata
-            .get(entity_path)
-            .and_then(|per_identifier| per_identifier.get(&component))
-            .map(|(_, metadata_state, _)| metadata_state)?;
+            .schema
+            .lookup_column_metadata_state(entity_path, component)?;
 
         let is_static = self
             .static_chunk_ids_per_entity
@@ -920,40 +909,6 @@ impl ChunkStore {
         })
     }
 
-    /// Get the [`ComponentType`] and [`ArrowDataType`] for a specific [`EntityPath`] and [`ComponentIdentifier`].
-    pub fn lookup_component_type(
-        &self,
-        entity_path: &EntityPath,
-        component: ComponentIdentifier,
-    ) -> Option<(Option, ArrowDataType)> {
-        let (component_descr, _, datatype) = self
-            .per_column_metadata
-            .get(entity_path)
-            .and_then(|per_identifier| per_identifier.get(&component))?;
-        Some((component_descr.component_type, datatype.clone()))
-    }
-
-    /// Checks whether any column in the store with the given [`ComponentType`] has a datatype
-    /// that differs from `expected_datatype`.
-    ///
-    /// This iterates over all entities, so it should not be called in a hot path.
-    pub fn has_mismatched_datatype_for_component_type(
-        &self,
-        component_type: &ComponentType,
-        expected_datatype: &ArrowDataType,
-    ) -> Option<&ArrowDataType> {
-        for per_component in self.per_column_metadata.values() {
-            for (descr, _, datatype) in per_component.values() {
-                if descr.component_type.as_ref() == Some(component_type)
-                    && datatype != expected_datatype
-                {
-                    return Some(datatype);
-                }
-            }
-        }
-        None
-    }
-
     /// Returns and iterator over [`ChunkId`]s that were detected as
     /// used or missing since the last time since method was called.
     ///
diff --git a/crates/store/re_chunk_store/src/store_schema.rs b/crates/store/re_chunk_store/src/store_schema.rs
new file mode 100644
index 000000000000..4c5704a01537
--- /dev/null
+++ b/crates/store/re_chunk_store/src/store_schema.rs
@@ -0,0 +1,393 @@
+//! Incrementally maintained store schema.
+//!
+//! Tracks all column descriptors and per-entity component sets.
+//!
+//! Never affected by garbage collection.
+
+use std::collections::BTreeMap;
+
+use arrow::array::ListArray as ArrowListArray;
+use arrow::datatypes::{DataType as ArrowDataType, Field as ArrowField};
+use nohash_hasher::IntMap;
+
+use re_byte_size::SizeBytes;
+use re_chunk::ComponentIdentifier;
+use re_log_types::{EntityPath, TimeType, Timeline, TimelineName};
+use re_sdk_types::ComponentDescriptor;
+use re_sorbet::{
+    ChunkColumnDescriptors, ComponentColumnDescriptor, IndexColumnDescriptor, RowIdColumnDescriptor,
+};
+use re_types_core::{ArchetypeName, ComponentSet, ComponentType};
+
+use crate::ColumnMetadataState;
+
+/// Per-column metadata for a single component on a single entity.
+#[derive(Debug, Clone)]
+pub struct ColumnMetadataEntry {
+    pub descriptor: ComponentDescriptor,
+    pub metadata_state: ColumnMetadataState,
+    pub datatype: ArrowDataType,
+}
+
+impl re_byte_size::SizeBytes for ColumnMetadataEntry {
+    fn heap_size_bytes(&self) -> u64 {
+        let Self {
+            descriptor,
+            metadata_state,
+            datatype,
+        } = self;
+        descriptor.heap_size_bytes() + metadata_state.heap_size_bytes() + datatype.heap_size_bytes()
+    }
+}
+
+use crate::ChunkStoreEvent;
+
+// ---
+
+/// Key for looking up a [`ComponentColumnDescriptor`] in the schema.
+///
+/// Matches the fields used by the `Ord` implementation of [`ComponentColumnDescriptor`].
+type SchemaComponentKey = (
+    EntityPath,
+    Option,
+    ComponentIdentifier,
+    Option,
+);
+
+fn schema_component_key(descr: &ComponentColumnDescriptor) -> SchemaComponentKey {
+    (
+        descr.entity_path.clone(),
+        descr.archetype,
+        descr.component,
+        descr.component_type,
+    )
+}
+
+// ---
+
+/// Incrementally maintained store schema.
+///
+/// Contains [`ChunkColumnDescriptors`] and per-entity component sets.
+/// Updated via [`Self::on_events`] when chunks are inserted or RRD manifests are ingested.
+/// Never affected by garbage collection.
+#[derive(Debug, Clone, Default)]
+pub struct StoreSchema {
+    /// The _latest_ [`TimeType`] for each timeline name.
+    time_type_registry: IntMap,
+
+    /// All component column descriptors ever seen, keyed for fast lookup/update.
+    components: BTreeMap,
+
+    /// Per-entity set of all components ever seen (sorted).
+    components_per_entity: IntMap,
+
+    // TODO(grtlr): Can we slim this map down by getting rid of `ComponentIdentifier`-level here?
+    per_column_metadata: IntMap>,
+}
+
+impl StoreSchema {
+    /// Retrieve all timelines in the store.
+    #[inline]
+    pub fn timelines(&self) -> BTreeMap {
+        self.time_type_registry
+            .iter()
+            .map(|(name, typ)| (*name, Timeline::new(*name, *typ)))
+            .collect()
+    }
+
+    /// Lookup the _latest_ [`TimeType`] used by a specific [`TimelineName`].
+    #[inline]
+    pub fn time_column_type(&self, timeline_name: &TimelineName) -> Option {
+        self.time_type_registry.get(timeline_name).copied()
+    }
+
+    /// Returns all [`ComponentIdentifier`]s that have ever been written to the given entity, sorted.
+    ///
+    /// Returns `None` if the entity has never had any data logged to it.
+    #[inline]
+    pub fn all_components_for_entity(&self, entity_path: &EntityPath) -> Option<&ComponentSet> {
+        self.components_per_entity.get(entity_path)
+    }
+
+    /// Retrieves the [`ComponentDescriptor`] at a given [`EntityPath`] that has a certain [`ComponentIdentifier`].
+    pub fn entity_component_descriptor(
+        &self,
+        entity_path: &EntityPath,
+        component: ComponentIdentifier,
+    ) -> Option {
+        self.per_column_metadata
+            .get(entity_path)
+            .and_then(|per_identifier| per_identifier.get(&component))
+            .map(|entry| entry.descriptor.clone())
+    }
+
+    /// Get the [`re_types_core::ComponentType`] and [`ArrowDataType`] for a specific [`EntityPath`] and [`ComponentIdentifier`].
+    pub fn lookup_component_type(
+        &self,
+        entity_path: &EntityPath,
+        component: ComponentIdentifier,
+    ) -> Option<(Option, ArrowDataType)> {
+        let entry = self
+            .per_column_metadata
+            .get(entity_path)
+            .and_then(|per_identifier| per_identifier.get(&component))?;
+        Some((entry.descriptor.component_type, entry.datatype.clone()))
+    }
+
+    /// Lookup the `ColumnMetadataState` for a specific [`EntityPath`] and [`ComponentIdentifier`].
+    pub fn lookup_column_metadata_state(
+        &self,
+        entity_path: &EntityPath,
+        component: ComponentIdentifier,
+    ) -> Option<&ColumnMetadataState> {
+        self.per_column_metadata
+            .get(entity_path)
+            .and_then(|per_identifier| per_identifier.get(&component))
+            .map(|entry| &entry.metadata_state)
+    }
+
+    /// Checks whether any column in the store with the given [`re_types_core::ComponentType`] has a datatype
+    /// that differs from `expected_datatype`.
+    ///
+    /// This iterates over all entities, so it should not be called in a hot path.
+    pub fn has_mismatched_datatype_for_component_type(
+        &self,
+        component_type: &ComponentType,
+        expected_datatype: &ArrowDataType,
+    ) -> Option<&ArrowDataType> {
+        re_tracing::profile_function!();
+        for per_component in self.per_column_metadata.values() {
+            for entry in per_component.values() {
+                if entry.descriptor.component_type.as_ref() == Some(component_type)
+                    && entry.datatype != *expected_datatype
+                {
+                    return Some(&entry.datatype);
+                }
+            }
+        }
+        None
+    }
+
+    /// Access the per-column metadata for a given entity.
+    pub fn per_column_metadata_for_entity(
+        &self,
+        entity_path: &EntityPath,
+    ) -> Option<&IntMap> {
+        self.per_column_metadata.get(entity_path)
+    }
+
+    /// Returns the full schema of the store.
+    ///
+    /// This will include a column descriptor for every timeline and every component on every
+    /// entity that has been written to the store so far.
+    ///
+    /// The order of the columns is guaranteed to be in a specific order:
+    /// * first, the time columns in lexical order (`frame_nr`, `log_time`, ...);
+    /// * second, the component columns in lexical order (`Color`, `Radius, ...`).
+    pub fn chunk_column_descriptors(&self) -> ChunkColumnDescriptors {
+        let mut indices: Vec = self
+            .time_type_registry
+            .iter()
+            .map(|(name, typ)| IndexColumnDescriptor::from(Timeline::new(*name, *typ)))
+            .collect();
+        indices.sort();
+
+        ChunkColumnDescriptors {
+            row_id: RowIdColumnDescriptor::from_sorted(false),
+            indices,
+            components: self.components.values().cloned().collect(),
+        }
+    }
+
+    // --- Updating via events ---
+
+    /// Update the schema from store events.
+    ///
+    /// This processes addition events (both physical chunk additions and virtual
+    /// manifest additions). Deletion events are ignored since the schema is purely additive.
+    pub fn on_events(&mut self, events: &[ChunkStoreEvent]) {
+        re_tracing::profile_function!();
+
+        for event in events {
+            match &event.diff {
+                crate::ChunkStoreDiff::Addition(add) => {
+                    self.on_chunk_addition(&add.chunk_after_processing);
+                }
+                crate::ChunkStoreDiff::VirtualAddition(vadd) => {
+                    self.on_rrd_manifest(&vadd.rrd_manifest);
+                }
+                crate::ChunkStoreDiff::Deletion(_) => {
+                    // Schema is purely additive — deletions are ignored.
+                }
+            }
+        }
+    }
+
+    fn on_chunk_addition(&mut self, chunk: &re_chunk::Chunk) {
+        let is_static = chunk.is_static();
+
+        // Update time type registry
+        for (name, time_column) in chunk.timelines() {
+            let new_typ = time_column.timeline().typ();
+            if let Some(old_typ) = self.time_type_registry.insert(*name, new_typ)
+                && old_typ != new_typ
+            {
+                re_log::warn_once!(
+                    "Timeline '{name}' changed type from {old_typ:?} to {new_typ:?}. \
+                        Rerun does not support using different types for the same timeline.",
+                );
+            }
+        }
+
+        let entity_path = chunk.entity_path();
+
+        // Update component columns and per-entity component sets
+        for column in chunk.components().values() {
+            let descriptor = &column.descriptor;
+            let component = descriptor.component;
+
+            self.components_per_entity
+                .entry(entity_path.clone())
+                .or_default()
+                .insert(component);
+
+            let is_semantically_empty =
+                re_arrow_util::is_list_array_semantically_empty(&column.list_array);
+
+            use re_types_core::Archetype as _;
+            let is_tombstone = re_types_core::archetypes::Clear::all_components()
+                .iter()
+                .any(|descr| descr.component == component);
+
+            let col_descr = ComponentColumnDescriptor {
+                store_datatype: ArrowListArray::DATA_TYPE_CONSTRUCTOR(
+                    ArrowField::new("item", column.list_array.value_type().clone(), true).into(),
+                ),
+                entity_path: entity_path.clone(),
+                archetype: descriptor.archetype,
+                component: descriptor.component,
+                component_type: descriptor.component_type,
+                is_static,
+                is_tombstone,
+                is_semantically_empty,
+            };
+
+            let key = schema_component_key(&col_descr);
+            self.components
+                .entry(key)
+                .and_modify(|existing| {
+                    // Additive updates: once static, always static; once non-empty, always non-empty
+                    existing.is_static |= is_static;
+                    existing.is_semantically_empty &= is_semantically_empty;
+                })
+                .or_insert(col_descr);
+
+            // Update per-column metadata
+            let entry = self
+                .per_column_metadata
+                .entry(entity_path.clone())
+                .or_default()
+                .entry(component)
+                .or_insert_with(|| ColumnMetadataEntry {
+                    descriptor: descriptor.clone(),
+                    metadata_state: ColumnMetadataState {
+                        is_semantically_empty: true,
+                    },
+                    datatype: column.list_array.value_type().clone(),
+                });
+            if entry.datatype != column.list_array.value_type() {
+                // TODO(grtlr): If we encounter two different data types, we should split the chunk.
+                // More information: https://github.com/rerun-io/rerun/pull/10082#discussion_r2140549340
+                re_log::warn!(
+                    "Datatype of column {} in {entity_path} has changed from {} to {}",
+                    entry.descriptor,
+                    entry.datatype,
+                    column.list_array.value_type()
+                );
+                entry.datatype = column.list_array.value_type().clone();
+            }
+            entry.metadata_state.is_semantically_empty &= is_semantically_empty;
+        }
+    }
+
+    fn on_rrd_manifest(&mut self, rrd_manifest: &re_log_encoding::RrdManifest) {
+        let sorbet_schema = rrd_manifest.recording_schema();
+
+        // Update time type registry
+        for descr in sorbet_schema.columns.index_columns() {
+            self.time_type_registry
+                .insert(descr.timeline_name(), descr.timeline().typ());
+        }
+
+        // Update component columns and per-entity component sets
+        for descr in sorbet_schema.columns.component_columns() {
+            let component = descr.component;
+            let entity_path = &descr.entity_path;
+
+            self.components_per_entity
+                .entry(entity_path.clone())
+                .or_default()
+                .insert(component);
+
+            let key = schema_component_key(descr);
+            self.components
+                .entry(key)
+                .and_modify(|existing| {
+                    existing.is_static |= descr.is_static;
+                    existing.is_semantically_empty &= descr.is_semantically_empty;
+                })
+                .or_insert_with(|| descr.clone());
+
+            // Update per-column metadata
+            let inner_datatype = descr.inner_datatype();
+            let previous = self
+                .per_column_metadata
+                .entry(entity_path.clone())
+                .or_default()
+                .insert(
+                    component,
+                    ColumnMetadataEntry {
+                        descriptor: descr.component_descriptor(),
+                        metadata_state: ColumnMetadataState {
+                            is_semantically_empty: descr.is_semantically_empty,
+                        },
+                        datatype: inner_datatype.clone(),
+                    },
+                );
+
+            if let Some(previous) = previous
+                && previous.datatype != inner_datatype
+            {
+                re_log::warn_once!(
+                    "Component '{component}' on entity '{entity_path}' changed type from {} to {inner_datatype}",
+                    previous.datatype,
+                );
+            }
+        }
+    }
+
+    /// Remove all data for a given entity path.
+    ///
+    /// Called from `ChunkStore::drop_entity_path`.
+    pub fn drop_entity(&mut self, entity_path: &EntityPath) {
+        self.components.retain(|key, _| key.0 != *entity_path);
+        self.components_per_entity.remove(entity_path);
+        self.per_column_metadata.remove(entity_path);
+    }
+}
+
+impl SizeBytes for StoreSchema {
+    fn heap_size_bytes(&self) -> u64 {
+        let Self {
+            time_type_registry,
+            components,
+            components_per_entity,
+            per_column_metadata,
+        } = self;
+
+        time_type_registry.heap_size_bytes()
+            + components.heap_size_bytes()
+            + components_per_entity.heap_size_bytes()
+            + per_column_metadata.heap_size_bytes()
+    }
+}
diff --git a/crates/store/re_chunk_store/src/writes.rs b/crates/store/re_chunk_store/src/writes.rs
index a9e6d8a72c33..e26e7213d4de 100644
--- a/crates/store/re_chunk_store/src/writes.rs
+++ b/crates/store/re_chunk_store/src/writes.rs
@@ -14,7 +14,7 @@ use crate::store::ChunkIdSetPerTime;
 use crate::{
     ChunkDirectLineage, ChunkDirectLineageReport, ChunkId, ChunkStore, ChunkStoreChunkStats,
     ChunkStoreConfig, ChunkStoreDiff, ChunkStoreDiffAddition, ChunkStoreError, ChunkStoreEvent,
-    ChunkStoreResult, ColumnMetadataState,
+    ChunkStoreResult,
 };
 
 // ---
@@ -30,9 +30,8 @@ impl ChunkStore {
         let Self {
             id: _,
             config: _,
-            time_type_registry,
-            per_column_metadata,
-            physical_chunks_per_chunk_id: _, // physical data only
+            schema: _,                            // handled below
+            physical_chunks_per_chunk_id: _,      // physical data only
             physical_chunk_ids_per_min_row_id: _, // physical data only
             chunks_lineage,
             dangling_splits: _, // cannot split during virtual insert
@@ -49,44 +48,6 @@ impl ChunkStore {
             event_id: _,
         } = self;
 
-        let sorbet_schema = &rrd_manifest.recording_schema();
-
-        time_type_registry.extend(
-            sorbet_schema
-                .columns
-                .index_columns()
-                .map(|descr| (descr.timeline_name(), descr.timeline().typ())),
-        );
-
-        for descr in sorbet_schema.columns.component_columns() {
-            let inner_datatype = descr.inner_datatype();
-            let previous = per_column_metadata
-                .entry(descr.entity_path.clone())
-                .or_default()
-                .insert(
-                    descr.component,
-                    (
-                        descr.component_descriptor(),
-                        ColumnMetadataState {
-                            is_semantically_empty: descr.is_semantically_empty,
-                        },
-                        inner_datatype.clone(),
-                    ),
-                );
-
-            if let Some(previous) = previous
-                && previous.2 != inner_datatype
-            {
-                re_log::warn_once!(
-                    "Component '{}' on entity '{}' changed type from {} to {}",
-                    descr.component,
-                    descr.entity_path,
-                    previous.2,
-                    inner_datatype
-                );
-            }
-        }
-
         let native_static_map = rrd_manifest.static_map();
         chunks_lineage.extend(
             native_static_map
@@ -186,6 +147,8 @@ impl ChunkStore {
             diff: ChunkStoreDiff::virtual_addition(rrd_manifest),
         };
 
+        self.schema.on_events(std::slice::from_ref(&event));
+
         if self.config.enable_changelog {
             Self::on_events(std::slice::from_ref(&event));
         }
@@ -221,6 +184,8 @@ impl ChunkStore {
             })
             .collect();
 
+        self.schema.on_events(&events);
+
         if self.config.enable_changelog {
             Self::on_events(&events);
         }
@@ -769,57 +734,6 @@ impl ChunkStore {
             );
         }
 
-        for (name, columns) in chunk_after_processing.timelines() {
-            let new_typ = columns.timeline().typ();
-            if let Some(old_typ) = self.time_type_registry.insert(*name, new_typ)
-                && old_typ != new_typ
-            {
-                re_log::warn_once!(
-                    "Timeline '{name}' changed type from {old_typ:?} to {new_typ:?}. \
-                        Rerun does not support using different types for the same timeline.",
-                );
-            }
-        }
-
-        for column in chunk_after_processing.components().values() {
-            let re_types_core::SerializedComponentColumn {
-                list_array,
-                descriptor,
-            } = column;
-
-            let (descr, column_metadata_state, datatype) = self
-                .per_column_metadata
-                .entry(chunk_after_processing.entity_path().clone())
-                .or_default()
-                .entry(descriptor.component)
-                .or_insert_with(|| {
-                    (
-                        descriptor.clone(),
-                        ColumnMetadataState {
-                            is_semantically_empty: true,
-                        },
-                        list_array.value_type().clone(),
-                    )
-                });
-            {
-                if *datatype != list_array.value_type() {
-                    // TODO(grtlr): If we encounter two different data types, we should split the chunk.
-                    // More information: https://github.com/rerun-io/rerun/pull/10082#discussion_r2140549340
-                    re_log::warn!(
-                        "Datatype of column {descr} in {} has changed from {datatype} to {}",
-                        chunk_after_processing.entity_path(),
-                        list_array.value_type()
-                    );
-                    *datatype = list_array.value_type().clone();
-                }
-
-                let is_semantically_empty =
-                    re_arrow_util::is_list_array_semantically_empty(list_array);
-
-                column_metadata_state.is_semantically_empty &= is_semantically_empty;
-            }
-        }
-
         Ok(all_diffs)
     }
 
@@ -1012,8 +926,7 @@ impl ChunkStore {
         let Self {
             id,
             config: _,
-            time_type_registry: _,
-            per_column_metadata,
+            schema,
             physical_chunks_per_chunk_id: chunks_per_chunk_id,
             chunks_lineage: _, // lineage metadata must never be dropped, regardless
             dangling_splits: _, // this counts as lineage metadata too
@@ -1031,7 +944,7 @@ impl ChunkStore {
             event_id,
         } = self;
 
-        per_column_metadata.remove(entity_path);
+        schema.drop_entity(entity_path);
 
         let dropped_static_chunks = {
             let dropped_static_chunk_ids: BTreeSet<_> = static_chunk_ids_per_entity
diff --git a/crates/store/re_chunk_store/tests/dataframe.rs b/crates/store/re_chunk_store/tests/dataframe.rs
index 2320c7af2c30..cae4888f4f7b 100644
--- a/crates/store/re_chunk_store/tests/dataframe.rs
+++ b/crates/store/re_chunk_store/tests/dataframe.rs
@@ -43,7 +43,7 @@ fn schema() -> anyhow::Result<()> {
     let chunk1 = Arc::new(chunk1);
     store.insert_chunk(&chunk1)?;
 
-    let ChunkColumnDescriptors { components, .. } = store.schema();
+    let ChunkColumnDescriptors { components, .. } = store.schema().chunk_column_descriptors();
 
     assert_eq!(
         components
diff --git a/crates/store/re_data_loader/tests/test_mcap_loader.rs b/crates/store/re_data_loader/tests/test_mcap_loader.rs
index c202b21eee80..01e04010aaca 100644
--- a/crates/store/re_data_loader/tests/test_mcap_loader.rs
+++ b/crates/store/re_data_loader/tests/test_mcap_loader.rs
@@ -62,7 +62,7 @@ mod tests {
         }
 
         // Extract and snapshot the schema
-        let schema = store_handle.read().schema();
+        let schema = store_handle.read().schema().chunk_column_descriptors();
         insta::assert_debug_snapshot!("ros2", schema);
     }
 }
diff --git a/crates/store/re_dataframe/src/engine.rs b/crates/store/re_dataframe/src/engine.rs
index 290721af7f21..57e93bf1b5df 100644
--- a/crates/store/re_dataframe/src/engine.rs
+++ b/crates/store/re_dataframe/src/engine.rs
@@ -68,7 +68,8 @@ impl QueryEngine {
     /// * second, the component columns in lexical order (`Color`, `Radius, ...`).
     #[inline]
     pub fn schema(&self) -> ChunkColumnDescriptors {
-        self.engine.with(|store, _cache| store.schema())
+        self.engine
+            .with(|store, _cache| store.schema().chunk_column_descriptors())
     }
 
     /// Returns the filtered schema for the given [`QueryExpression`].
diff --git a/crates/store/re_entity_db/src/entity_db.rs b/crates/store/re_entity_db/src/entity_db.rs
index 4b60ad1e1d72..17346aa2495a 100644
--- a/crates/store/re_entity_db/src/entity_db.rs
+++ b/crates/store/re_entity_db/src/entity_db.rs
@@ -252,13 +252,13 @@ impl EntityDb {
             let depth = entity_path.len() - 1;
             let indent = "  ".repeat(depth);
             text.push_str(&format!("{indent}{entity_path}\n"));
-            let Some(components) = store.all_components_for_entity_sorted(entity_path) else {
+            let Some(components) = store.schema().all_components_for_entity(entity_path) else {
                 return;
             };
-            for component in components {
+            for &component in components {
                 let component_indent = "  ".repeat(depth + 1);
                 if let Some((component_type, datatype)) =
-                    store.lookup_component_type(entity_path, component)
+                    store.schema().lookup_component_type(entity_path, component)
                 {
                     let name = component_type
                         .map_or_else(|| component.to_string(), |ct| ct.short_name().to_owned());
@@ -510,7 +510,7 @@ impl EntityDb {
 
     pub fn timeline_type(&self, timeline_name: &TimelineName) -> TimeType {
         self.storage_engine()
-            .store()
+            .schema()
             .time_column_type(timeline_name)
             .unwrap_or_else(|| {
                 if timeline_name == &TimelineName::log_time() {
@@ -641,7 +641,7 @@ impl EntityDb {
     }
 
     pub fn timelines(&self) -> std::collections::BTreeMap {
-        self.storage_engine().store().timelines()
+        self.storage_engine().schema().timelines()
     }
 
     /// Returns the time range of data on the given timeline, ignoring any static times.
diff --git a/crates/store/re_query/src/cache.rs b/crates/store/re_query/src/cache.rs
index ef44391525e6..f5df4c66b050 100644
--- a/crates/store/re_query/src/cache.rs
+++ b/crates/store/re_query/src/cache.rs
@@ -246,8 +246,10 @@ impl std::fmt::Debug for QueryCache {
                     "  [{cache_key:?} (pending_invalidation_min={:?})]",
                     cache.pending_invalidations.first().map(|&t| {
                         let range = AbsoluteTimeRange::new(t, TimeInt::MAX);
-                        if let Some(time_type) =
-                            store.read().time_column_type(&cache_key.timeline_name)
+                        if let Some(time_type) = store
+                            .read()
+                            .schema()
+                            .time_column_type(&cache_key.timeline_name)
                         {
                             time_type.format_range_utc(range)
                         } else {
diff --git a/crates/store/re_query/src/storage_engine.rs b/crates/store/re_query/src/storage_engine.rs
index 12f342b7cff7..88927eed238a 100644
--- a/crates/store/re_query/src/storage_engine.rs
+++ b/crates/store/re_query/src/storage_engine.rs
@@ -157,6 +157,11 @@ impl StorageEngineReadGuard<'_> {
         &self.store
     }
 
+    #[inline]
+    pub fn schema(&self) -> &re_chunk_store::StoreSchema {
+        self.store.schema()
+    }
+
     #[inline]
     pub fn cache(&self) -> &QueryCache {
         &self.cache
@@ -204,6 +209,11 @@ impl StorageEngineArcReadGuard {
         &self.store
     }
 
+    #[inline]
+    pub fn schema(&self) -> &re_chunk_store::StoreSchema {
+        self.store.schema()
+    }
+
     #[inline]
     pub fn cache(&self) -> &QueryCache {
         &self.cache
diff --git a/crates/store/re_server/src/store/dataset.rs b/crates/store/re_server/src/store/dataset.rs
index d894521fdd49..04a4dd665bf8 100644
--- a/crates/store/re_server/src/store/dataset.rs
+++ b/crates/store/re_server/src/store/dataset.rs
@@ -574,7 +574,11 @@ impl Dataset {
         // Validate schema compatibility before inserting
         let current_schema = self.schema()?;
         let new_layer_schema = {
-            let fields = store_handle.read().schema().arrow_fields();
+            let fields = store_handle
+                .read()
+                .schema()
+                .chunk_column_descriptors()
+                .arrow_fields();
             Schema::new_with_metadata(fields, HashMap::default())
         };
         Schema::try_merge([current_schema, new_layer_schema]).map_err(|err| {
diff --git a/crates/store/re_server/src/store/layer.rs b/crates/store/re_server/src/store/layer.rs
index 9d61db7a3258..0a1f8a47f044 100644
--- a/crates/store/re_server/src/store/layer.rs
+++ b/crates/store/re_server/src/store/layer.rs
@@ -60,7 +60,12 @@ impl Layer {
     }
 
     pub fn schema(&self) -> Schema {
-        let fields = self.store_handle.read().schema().arrow_fields();
+        let fields = self
+            .store_handle
+            .read()
+            .schema()
+            .chunk_column_descriptors()
+            .arrow_fields();
         Schema::new_with_metadata(fields, HashMap::default())
     }
 
diff --git a/crates/store/re_sorbet/Cargo.toml b/crates/store/re_sorbet/Cargo.toml
index 7bcf5213b8b0..f34ca0def570 100644
--- a/crates/store/re_sorbet/Cargo.toml
+++ b/crates/store/re_sorbet/Cargo.toml
@@ -21,6 +21,7 @@ all-features = true
 
 [dependencies]
 re_arrow_util.workspace = true
+re_byte_size.workspace = true
 re_log_types.workspace = true
 re_log.workspace = true
 re_tracing.workspace = true
diff --git a/crates/store/re_sorbet/src/component_column_descriptor.rs b/crates/store/re_sorbet/src/component_column_descriptor.rs
index e51440eb1189..e1d7fb969e8b 100644
--- a/crates/store/re_sorbet/src/component_column_descriptor.rs
+++ b/crates/store/re_sorbet/src/component_column_descriptor.rs
@@ -66,6 +66,27 @@ pub struct ComponentColumnDescriptor {
     pub is_semantically_empty: bool,
 }
 
+impl re_byte_size::SizeBytes for ComponentColumnDescriptor {
+    #[inline]
+    fn heap_size_bytes(&self) -> u64 {
+        let Self {
+            entity_path,
+            archetype,
+            component,
+            component_type,
+            store_datatype,
+            is_static: _,
+            is_tombstone: _,
+            is_semantically_empty: _,
+        } = self;
+        entity_path.heap_size_bytes()
+            + archetype.heap_size_bytes()
+            + component.heap_size_bytes()
+            + component_type.heap_size_bytes()
+            + store_datatype.heap_size_bytes()
+    }
+}
+
 impl PartialOrd for ComponentColumnDescriptor {
     #[inline]
     fn partial_cmp(&self, other: &Self) -> Option {
diff --git a/crates/store/re_sorbet/src/index_column_descriptor.rs b/crates/store/re_sorbet/src/index_column_descriptor.rs
index 399e06f7d0ff..3973c37156b8 100644
--- a/crates/store/re_sorbet/src/index_column_descriptor.rs
+++ b/crates/store/re_sorbet/src/index_column_descriptor.rs
@@ -24,6 +24,17 @@ pub struct IndexColumnDescriptor {
     pub is_sorted: bool,
 }
 
+impl re_byte_size::SizeBytes for IndexColumnDescriptor {
+    fn heap_size_bytes(&self) -> u64 {
+        let Self {
+            timeline,
+            datatype,
+            is_sorted: _,
+        } = self;
+        timeline.heap_size_bytes() + datatype.heap_size_bytes()
+    }
+}
+
 impl PartialOrd for IndexColumnDescriptor {
     #[inline]
     fn partial_cmp(&self, other: &Self) -> Option {
diff --git a/crates/top/rerun/src/commands/entrypoint.rs b/crates/top/rerun/src/commands/entrypoint.rs
index 642a216a7aeb..51215716fb98 100644
--- a/crates/top/rerun/src/commands/entrypoint.rs
+++ b/crates/top/rerun/src/commands/entrypoint.rs
@@ -1425,9 +1425,10 @@ fn initialize_thread_pool(threads_args: i32) {
         }
     } else if threads_args == 1 {
         // 1 means "single-threaded".
-        // Put all jobs on the caller thread, just to simplify
-        // the flamegraph and make it more similar to a browser.
-        builder = builder.num_threads(1).use_current_thread();
+        // NOTE: we intentionally do NOT use `.use_current_thread()` here,
+        // because that causes deadlocks when code does `rayon::spawn()`
+        // followed by blocking on the result (e.g. in `load_file.rs`).
+        builder = builder.num_threads(1);
         re_log::info!("Running in single-threaded mode.");
     } else {
         // 0 means "use all cores", and rayon understands that
diff --git a/crates/top/rerun/src/commands/rrd/split.rs b/crates/top/rerun/src/commands/rrd/split.rs
index 8ed172ae031c..f5dec6bbfc76 100644
--- a/crates/top/rerun/src/commands/rrd/split.rs
+++ b/crates/top/rerun/src/commands/rrd/split.rs
@@ -437,7 +437,7 @@ impl SplitCommand {
             //
             // Note that this is across *all recordings* in the file/stream.
             let mut known_timelines: BTreeMap = Default::default();
-            for (name, timeline) in stores.values().flat_map(|store| store.timelines()) {
+            for (name, timeline) in stores.values().flat_map(|store| store.schema().timelines()) {
                 if let Some(existing) = known_timelines.insert(name, timeline) {
                     anyhow::ensure!(
                         existing == timeline,
@@ -570,6 +570,7 @@ impl SplitCommand {
             .into_iter()
             .filter_map(|entity| {
                 store
+                    .schema()
                     .all_components_for_entity(&entity)
                     .map(|components| (entity, components))
             })
diff --git a/crates/viewer/re_chunk_store_ui/src/chunk_list_mode.rs b/crates/viewer/re_chunk_store_ui/src/chunk_list_mode.rs
index 9cfc2b7b9378..6c770600c18a 100644
--- a/crates/viewer/re_chunk_store_ui/src/chunk_list_mode.rs
+++ b/crates/viewer/re_chunk_store_ui/src/chunk_list_mode.rs
@@ -33,7 +33,7 @@ impl ChunkListMode {
         chunk_store: &ChunkStore,
         format: TimestampFormat,
     ) -> Option<()> {
-        let all_timelines = chunk_store.timelines();
+        let all_timelines = chunk_store.schema().timelines();
         let all_entities = chunk_store.all_entities_sorted();
         let all_components = chunk_store.all_components_sorted();
 
diff --git a/crates/viewer/re_chunk_store_ui/src/chunk_store_ui.rs b/crates/viewer/re_chunk_store_ui/src/chunk_store_ui.rs
index 639f0278660d..ec00d606fd77 100644
--- a/crates/viewer/re_chunk_store_ui/src/chunk_store_ui.rs
+++ b/crates/viewer/re_chunk_store_ui/src/chunk_store_ui.rs
@@ -144,7 +144,7 @@ impl DatastoreUi {
             self.chunk_store_info_ui(ui, chunk_store, storage_context, datastore_ui_active);
 
         // Each of these must be a column that contains the corresponding time range.
-        let all_timelines = chunk_store.timelines();
+        let all_timelines = chunk_store.schema().timelines();
 
         self.chunk_list_mode.ui(ui, chunk_store, timestamp_format);
 
diff --git a/crates/viewer/re_data_ui/src/annotation_context_ui.rs b/crates/viewer/re_data_ui/src/annotation_context_ui.rs
index 455ca46d4578..9bfefa522717 100644
--- a/crates/viewer/re_data_ui/src/annotation_context_ui.rs
+++ b/crates/viewer/re_data_ui/src/annotation_context_ui.rs
@@ -102,10 +102,14 @@ fn annotation_info(
     let storage_engine = ctx.db.storage_engine();
     let store = storage_engine.store();
     let mut possible_class_id_components = store
+        .schema()
         .all_components_for_entity(entity_path)?
-        .into_iter()
+        .iter()
+        .copied()
         .filter(|component| {
-            let descriptor = store.entity_component_descriptor(entity_path, *component);
+            let descriptor = store
+                .schema()
+                .entity_component_descriptor(entity_path, *component);
             descriptor.is_some_and(|d| d.component_type == Some(components::ClassId::name()))
         });
     let picked_class_id_component = possible_class_id_components.next()?;
diff --git a/crates/viewer/re_data_ui/src/entity_db_ui.rs b/crates/viewer/re_data_ui/src/entity_db_ui.rs
index 5fa54c28ffb0..c07b1a34ac2d 100644
--- a/crates/viewer/re_data_ui/src/entity_db_ui.rs
+++ b/crates/viewer/re_data_ui/src/entity_db_ui.rs
@@ -279,7 +279,7 @@ fn grid_content_ui(ctx: &AppContext<'_>, db: &EntityDb, ui: &mut egui::Ui, ui_la
 
         let storage_engine = db.storage_engine();
         let store = storage_engine.store();
-        let schema = store.schema();
+        let schema = store.schema().chunk_column_descriptors();
 
         ui.grid_left_hand_label("Entities")
             .on_hover_text("In the ChunkStore");
diff --git a/crates/viewer/re_data_ui/src/instance_path_ui.rs b/crates/viewer/re_data_ui/src/instance_path_ui.rs
index f27561eb9634..20205343d939 100644
--- a/crates/viewer/re_data_ui/src/instance_path_ui.rs
+++ b/crates/viewer/re_data_ui/src/instance_path_ui.rs
@@ -53,7 +53,7 @@ impl DataUi for InstancePath {
             let store = storage_engine.store();
             unordered_components
                 .iter()
-                .filter_map(|c| store.entity_component_descriptor(entity_path, *c))
+                .filter_map(|c| store.schema().entity_component_descriptor(entity_path, *c))
                 .sorted()
                 .collect_vec()
         };
diff --git a/crates/viewer/re_data_ui/src/latest_at_instance_ui.rs b/crates/viewer/re_data_ui/src/latest_at_instance_ui.rs
index e5f2dc621e15..91bff1971212 100644
--- a/crates/viewer/re_data_ui/src/latest_at_instance_ui.rs
+++ b/crates/viewer/re_data_ui/src/latest_at_instance_ui.rs
@@ -44,7 +44,7 @@ impl DataUi for LatestAtInstanceResult<'_> {
         let engine = ctx.db.storage_engine();
 
         let Some(component_descriptor) = engine
-            .store()
+            .schema()
             .entity_component_descriptor(&entity_path, component)
         else {
             ui.label(format!(
diff --git a/crates/viewer/re_selection_panel/src/defaults_ui.rs b/crates/viewer/re_selection_panel/src/defaults_ui.rs
index 2b7834a73d6c..463b6624c70c 100644
--- a/crates/viewer/re_selection_panel/src/defaults_ui.rs
+++ b/crates/viewer/re_selection_panel/src/defaults_ui.rs
@@ -120,7 +120,7 @@ fn active_default_ui(
             let Some(component_descr) = blueprint_store_view_ctx
                 .db
                 .storage_engine()
-                .store()
+                .schema()
                 .entity_component_descriptor(&view.defaults_path, *component)
             else {
                 // Must mean the default wasn't active after all.
diff --git a/crates/viewer/re_selection_panel/src/selection_panel.rs b/crates/viewer/re_selection_panel/src/selection_panel.rs
index b62113aea8d4..755450bfcd9e 100644
--- a/crates/viewer/re_selection_panel/src/selection_panel.rs
+++ b/crates/viewer/re_selection_panel/src/selection_panel.rs
@@ -190,7 +190,7 @@ impl SelectionPanel {
                 let store_view_ctx = ctx.guess_store_view_context_for_entity(entity_path);
                 let engine = store_view_ctx.db.storage_engine();
                 let component_descriptor = engine
-                    .store()
+                    .schema()
                     .entity_component_descriptor(entity_path, *component)
                     .unwrap_or_else(|| ComponentDescriptor::partial(*component));
 
diff --git a/crates/viewer/re_selection_panel/src/visualizer_ui.rs b/crates/viewer/re_selection_panel/src/visualizer_ui.rs
index 86bc39fb48ab..621a83d877d9 100644
--- a/crates/viewer/re_selection_panel/src/visualizer_ui.rs
+++ b/crates/viewer/re_selection_panel/src/visualizer_ui.rs
@@ -240,17 +240,17 @@ fn visualizer_components(
 
     // Query all components of the entity so we can show them in the source component mapping UI.
     let entity_components_with_datatype = {
-        let components = viewer_ctx
-            .recording_engine()
-            .store()
-            .all_components_for_entity_sorted(&data_result.entity_path)
-            .unwrap_or_default();
+        let engine = viewer_ctx.recording_engine();
+        let store = engine.store();
+        let components = store
+            .schema()
+            .all_components_for_entity(&data_result.entity_path);
         components
             .into_iter()
-            .filter_map(|component_id| {
-                let component_type = viewer_ctx
-                    .recording_engine()
-                    .store()
+            .flatten()
+            .filter_map(|&component_id| {
+                let component_type = store
+                    .schema()
                     .lookup_component_type(&data_result.entity_path, component_id);
                 component_type.map(|(_, arrow_datatype)| (component_id, arrow_datatype))
             })
diff --git a/crates/viewer/re_time_panel/src/streams_tree_data.rs b/crates/viewer/re_time_panel/src/streams_tree_data.rs
index 0b5d34e6a2bb..fb5c44275515 100644
--- a/crates/viewer/re_time_panel/src/streams_tree_data.rs
+++ b/crates/viewer/re_time_panel/src/streams_tree_data.rs
@@ -254,12 +254,14 @@ pub fn components_for_entity(
     store: &ChunkStore,
     entity_path: &EntityPath,
 ) -> ArchetypeComponentMap {
-    if let Some(components) = store.all_components_for_entity(entity_path) {
+    if let Some(components) = store.schema().all_components_for_entity(entity_path) {
         sorted_component_list_by_archetype_for_ui(
             ctx.reflection,
-            components
-                .iter()
-                .filter_map(|component| store.entity_component_descriptor(entity_path, *component)),
+            components.iter().filter_map(|component| {
+                store
+                    .schema()
+                    .entity_component_descriptor(entity_path, *component)
+            }),
         )
     } else {
         ArchetypeComponentMap::default()
diff --git a/crates/viewer/re_view/src/query.rs b/crates/viewer/re_view/src/query.rs
index c1a740d15d16..a0d389e565c6 100644
--- a/crates/viewer/re_view/src/query.rs
+++ b/crates/viewer/re_view/src/query.rs
@@ -124,7 +124,7 @@ fn component_not_found_error(
         {
             let store = store_engine.store();
 
-            let timeline = store.timelines().get(&timeline_name).copied();
+            let timeline = store.schema().timelines().get(&timeline_name).copied();
 
             for missing_root_chunk_id in missing_virtual_chunks
                 .iter()
diff --git a/crates/viewer/re_view_spatial/src/view_3d.rs b/crates/viewer/re_view_spatial/src/view_3d.rs
index f10db46b80df..d76c79ad5576 100644
--- a/crates/viewer/re_view_spatial/src/view_3d.rs
+++ b/crates/viewer/re_view_spatial/src/view_3d.rs
@@ -385,8 +385,8 @@ impl ViewClass for SpatialView3D {
                 // There's also a strong argument to be made that ViewCoordinates implies a 3D space, thus changing the SpacialTopology accordingly!
                 let engine = ctx.recording_engine();
                 ctx.recording().tree().visit_children_recursively(|path| {
-                    if let Some(components) = engine.store().all_components_for_entity(path)
-                        && components.into_iter().any(|component| {
+                    if let Some(components) = engine.schema().all_components_for_entity(path)
+                        && components.iter().any(|&component| {
                             archetypes::Pinhole::all_components().iter().any(|c| c.component == component)
                             // TODO(#2663): Note that the view coordinates component may be logged by different archetypes.
                                 || component
diff --git a/crates/viewer/re_viewer/src/blueprint/validation.rs b/crates/viewer/re_viewer/src/blueprint/validation.rs
index 9095a2a7a084..b91384529bb3 100644
--- a/crates/viewer/re_viewer/src/blueprint/validation.rs
+++ b/crates/viewer/re_viewer/src/blueprint/validation.rs
@@ -4,7 +4,7 @@ use re_types_core::Component;
 pub(crate) fn validate_component(blueprint: &EntityDb) -> bool {
     let engine = blueprint.storage_engine();
     if let Some(actual_datatype) = engine
-        .store()
+        .schema()
         .has_mismatched_datatype_for_component_type(&C::name(), &C::arrow_datatype())
     {
         // If the schemas don't match, we definitely have a problem
diff --git a/crates/viewer/re_viewer_context/src/component_fallbacks.rs b/crates/viewer/re_viewer_context/src/component_fallbacks.rs
index 9d070d3ddd54..92de09907b87 100644
--- a/crates/viewer/re_viewer_context/src/component_fallbacks.rs
+++ b/crates/viewer/re_viewer_context/src/component_fallbacks.rs
@@ -300,9 +300,8 @@ fn placeholder_for(
     } else {
         let entity_path = ctx.target_entity_path;
         viewer_ctx.recording_engine()
-                .store()
-                .lookup_component_type(entity_path, component_identifier)
-                .or_else(|| viewer_ctx.blueprint_engine().store().lookup_component_type(entity_path, component_identifier))
+                .schema().lookup_component_type(entity_path, component_identifier)
+                .or_else(|| viewer_ctx.blueprint_engine().schema().lookup_component_type(entity_path, component_identifier))
                 .map(|(_component_type, datatype)| datatype)
                 .unwrap_or_else(|| {
                          re_log::error_once!("Could not find datatype for component {component}. Using null array as placeholder.");
diff --git a/crates/viewer/re_viewer_context/src/image_info.rs b/crates/viewer/re_viewer_context/src/image_info.rs
index 3285b8c746a9..f1a890756bdc 100644
--- a/crates/viewer/re_viewer_context/src/image_info.rs
+++ b/crates/viewer/re_viewer_context/src/image_info.rs
@@ -24,7 +24,7 @@ pub fn resolution_of_image_at(
     // TODO(andreas): can we do this more efficiently?
     // TODO(andreas): doesn't take blueprint into account!
     let all_components = storage_engine
-        .store()
+        .schema()
         .all_components_for_entity(entity_path)?;
     let image_format_descr = all_components
         .get(&archetypes::Image::descriptor_format().component)
diff --git a/crates/viewer/re_viewport_blueprint/src/view.rs b/crates/viewer/re_viewport_blueprint/src/view.rs
index ce38e4f8a97d..dff4b0aa3585 100644
--- a/crates/viewer/re_viewport_blueprint/src/view.rs
+++ b/crates/viewer/re_viewport_blueprint/src/view.rs
@@ -277,7 +277,7 @@ impl ViewBlueprint {
                                     .cache()
                                     .latest_at(query, path, [component])
                                     .component_batch_raw(component)?;
-                                let descriptor = blueprint_engine.store().entity_component_descriptor(path, component)?;
+                                let descriptor = blueprint_engine.schema().entity_component_descriptor(path, component)?;
                                 Some((descriptor, array))
                             }),
                     )
diff --git a/crates/viewer/re_viewport_blueprint/src/view_contents.rs b/crates/viewer/re_viewport_blueprint/src/view_contents.rs
index cbf78b3a085d..043437c4a01b 100644
--- a/crates/viewer/re_viewport_blueprint/src/view_contents.rs
+++ b/crates/viewer/re_viewport_blueprint/src/view_contents.rs
@@ -624,15 +624,16 @@ impl DataQueryPropertyResolver<'_> {
                     .map(|vi| (vi.id, handle)),
             );
 
+        let blueprint_engine = blueprint.storage_engine();
+
         // Gather "special" overrides directly on the base path (per entity).
-        for component in blueprint
-            .storage_engine()
-            .store()
+        for &component in blueprint_engine
+            .schema()
             .all_components_for_entity(override_base_path)
-            .unwrap_or_default()
+            .into_iter()
+            .flatten()
         {
-            if let Some(component_data) = blueprint
-                    .storage_engine()
+            if let Some(component_data) = blueprint_engine
                     .cache()
                     .latest_at(blueprint_query, override_base_path, [component])
                     .component_batch_raw(component)
@@ -679,14 +680,13 @@ impl DataQueryPropertyResolver<'_> {
         for instruction in &mut node.data_result.visualizer_instructions {
             // Gather "real" overrides on visualizer instruction specific path.
             // TODO(andreas): Why not keep the component data while we're here? Could speed up things a lot down the line.
-            for component in blueprint
-                .storage_engine()
-                .store()
+            for &component in blueprint_engine
+                .schema()
                 .all_components_for_entity(&instruction.override_path)
-                .unwrap_or_default()
+                .into_iter()
+                .flatten()
             {
-                if let Some(component_data) = blueprint
-                        .storage_engine()
+                if let Some(component_data) = blueprint_engine
                         .cache()
                         .latest_at(blueprint_query, &instruction.override_path, [component])
                         .component_batch_raw(component) &&
diff --git a/crates/viewer/re_viewport_blueprint/src/view_properties.rs b/crates/viewer/re_viewport_blueprint/src/view_properties.rs
index 62982bc31122..f30c7f52f739 100644
--- a/crates/viewer/re_viewport_blueprint/src/view_properties.rs
+++ b/crates/viewer/re_viewport_blueprint/src/view_properties.rs
@@ -241,8 +241,9 @@ impl ViewProperty {
         let blueprint_storage_engine = ctx.blueprint_db().storage_engine();
         let blueprint_store = blueprint_storage_engine.store();
         for component in self.query_results.components.keys().copied() {
-            if let Some(component_descr) =
-                blueprint_store.entity_component_descriptor(&self.blueprint_store_path, component)
+            if let Some(component_descr) = blueprint_store
+                .schema()
+                .entity_component_descriptor(&self.blueprint_store_path, component)
             {
                 ctx.clear_blueprint_component(self.blueprint_store_path.clone(), component_descr);
             }
diff --git a/rerun_py/src/recording/rrd.rs b/rerun_py/src/recording/rrd.rs
index 7ee2a765f117..0faf102054f9 100644
--- a/rerun_py/src/recording/rrd.rs
+++ b/rerun_py/src/recording/rrd.rs
@@ -61,7 +61,7 @@ impl PyRecording {
     /// The schema describing all the columns available in the recording.
     fn schema(&self, py: Python<'_>) -> PyResult> {
         let schema_internal = PySchemaInternal {
-            columns: self.store.read().schema().into(),
+            columns: self.store.read().schema().chunk_column_descriptors().into(),
             metadata: Default::default(),
         };
 

From f2e54a7577c9ec811778e879993d00af0acc87fa Mon Sep 17 00:00:00 2001
From: Andreas Reich 
Date: Thu, 12 Mar 2026 15:22:02 +0100
Subject: [PATCH 114/513] Degrade `format_frame_or_debug_panic` to
 `format_frame_or_debug_warn`

debug panic blocked people from working on things

---------

Source-Ref: 247e9dcf48c05c4f422a798925bb25f46bf34cd1
Co-authored-by: Emil Ernerfeldt 
---
 .../src/contexts/transform_tree_context.rs             |  7 +++----
 .../viewer/re_view_spatial/src/visualizers/cameras.rs  |  2 +-
 .../src/visualizers/utilities/transform_retrieval.rs   | 10 +++++-----
 3 files changed, 9 insertions(+), 10 deletions(-)

diff --git a/crates/viewer/re_view_spatial/src/contexts/transform_tree_context.rs b/crates/viewer/re_view_spatial/src/contexts/transform_tree_context.rs
index 42ad0168614e..b3a8d0c6b710 100644
--- a/crates/viewer/re_view_spatial/src/contexts/transform_tree_context.rs
+++ b/crates/viewer/re_view_spatial/src/contexts/transform_tree_context.rs
@@ -539,9 +539,8 @@ impl TransformTreeContext {
 
     /// Formats a frame ID hash as a human-readable string.
     ///
-    /// Returns the frame name if known, otherwise triggers a [`re_log::debug_panic`]
-    /// and returns `None`.
-    pub fn format_frame_or_debug_panic(
+    /// Returns the frame name if known, otherwise logs a warning in debug builds and returns `None`.
+    pub fn format_frame_or_debug_warn(
         &self,
         frame_id_hash: TransformFrameIdHash,
         debug_location: &EntityPath,
@@ -549,7 +548,7 @@ impl TransformTreeContext {
         if let Some(frame_id) = self.lookup_frame_id(frame_id_hash) {
             Some(frame_id.to_string())
         } else {
-            re_log::debug_panic!(
+            re_log::debug_warn!(
                 "Failed to resolve frame id hash {frame_id_hash:?} which was referenced at {debug_location:?}"
             );
             None
diff --git a/crates/viewer/re_view_spatial/src/visualizers/cameras.rs b/crates/viewer/re_view_spatial/src/visualizers/cameras.rs
index 48207d9409fd..f3eb1ba44a32 100644
--- a/crates/viewer/re_view_spatial/src/visualizers/cameras.rs
+++ b/crates/viewer/re_view_spatial/src/visualizers/cameras.rs
@@ -79,7 +79,7 @@ impl CamerasVisualizer {
             // This implies that the transform context didn't see the pinhole transform.
             // This can happen with various frame id mismatches. TODO(andreas): When exactly does this happen? Can we add a unit test and improve the message?
             let frame = if let Some(frame_id) =
-                transforms.format_frame_or_debug_panic(pinhole_frame_id, ctx.target_entity_path)
+                transforms.format_frame_or_debug_warn(pinhole_frame_id, ctx.target_entity_path)
             {
                 format!("child frame {frame_id:?}")
             } else {
diff --git a/crates/viewer/re_view_spatial/src/visualizers/utilities/transform_retrieval.rs b/crates/viewer/re_view_spatial/src/visualizers/utilities/transform_retrieval.rs
index b0ba7a289bd6..04acd35e0a3f 100644
--- a/crates/viewer/re_view_spatial/src/visualizers/utilities/transform_retrieval.rs
+++ b/crates/viewer/re_view_spatial/src/visualizers/utilities/transform_retrieval.rs
@@ -66,14 +66,14 @@ pub fn format_transform_info_result<'a>(
 
         Some(Err(re_tf::TransformFromToError::NoPathBetweenFrames { src, target, .. })) => {
             let src = if let Some(frame_id) =
-                transform_context.format_frame_or_debug_panic(*src, entity_path)
+                transform_context.format_frame_or_debug_warn(*src, entity_path)
             {
                 format!("{frame_id:?}")
             } else {
                 format!("{entity_path}:?")
             };
             let target = if let Some(target) =
-                transform_context.format_frame_or_debug_panic(*target, entity_path)
+                transform_context.format_frame_or_debug_warn(*target, entity_path)
             {
                 format!(" ({target:?})")
             } else {
@@ -87,7 +87,7 @@ pub fn format_transform_info_result<'a>(
 
         Some(Err(re_tf::TransformFromToError::UnknownTargetFrame(target))) => {
             let target = if let Some(target) =
-                transform_context.format_frame_or_debug_panic(*target, entity_path)
+                transform_context.format_frame_or_debug_warn(*target, entity_path)
             {
                 format!("target frame {target:?}")
             } else {
@@ -99,7 +99,7 @@ pub fn format_transform_info_result<'a>(
 
         Some(Err(re_tf::TransformFromToError::UnknownSourceFrame(src))) => {
             let src = if let Some(frame_id) =
-                transform_context.format_frame_or_debug_panic(*src, entity_path)
+                transform_context.format_frame_or_debug_warn(*src, entity_path)
             {
                 format!("{frame_id:?}")
             } else {
@@ -138,7 +138,7 @@ pub fn is_valid_space_for_content(
         && let Some(target_frame_pinhole_root) = target_frame_pinhole_root
     {
         let origin = if let Some(origin) =
-            transform_context.format_frame_or_debug_panic(target_frame_pinhole_root, entity_path)
+            transform_context.format_frame_or_debug_warn(target_frame_pinhole_root, entity_path)
         {
             format!("The origin of the 3D view ({origin:?})")
         } else {

From a0a97e7151d44e6c32c795ad2b781316326a55f3 Mon Sep 17 00:00:00 2001
From: Isse 
Date: Thu, 12 Mar 2026 15:32:20 +0100
Subject: [PATCH 115/513] Don't show last successful frame on some video errors

### What

On some errors, like missing data (not unloaded data), it makes more
sense to not show the last successful frame.

Source-Ref: 2e7c3385d98b954a9bd2291c0bc24e265976d868
---
 .../src/visualizers/video/mod.rs              | 22 ++++++++++++++++++-
 1 file changed, 21 insertions(+), 1 deletion(-)

diff --git a/crates/viewer/re_view_spatial/src/visualizers/video/mod.rs b/crates/viewer/re_view_spatial/src/visualizers/video/mod.rs
index 88ecea1138f0..53f76ab068ae 100644
--- a/crates/viewer/re_view_spatial/src/visualizers/video/mod.rs
+++ b/crates/viewer/re_view_spatial/src/visualizers/video/mod.rs
@@ -45,6 +45,9 @@ struct VideoPlaybackIssue {
     message: String,
     severity: VideoPlaybackIssueSeverity,
     should_request_more_frames: bool,
+
+    /// Should we show the last successful video frame behind this issue?
+    show_frame: bool,
 }
 
 impl VideoPlaybackIssue {
@@ -53,6 +56,7 @@ impl VideoPlaybackIssue {
             message,
             severity,
             should_request_more_frames: false,
+            show_frame: false,
         }
     }
 }
@@ -63,6 +67,19 @@ impl From for VideoPlaybackIssue {
             message: error.to_string(),
             severity: error.severity(),
             should_request_more_frames: error.should_request_more_frames(),
+            show_frame: match error {
+                VideoPlayerError::NegativeTimestamp
+                | VideoPlayerError::InsufficientSampleData(_) => false,
+
+                VideoPlayerError::EmptyBuffer
+                | VideoPlayerError::UnloadedSampleData(_)
+                | VideoPlayerError::CreateChunk(_)
+                | VideoPlayerError::DecodeChunk(_)
+                | VideoPlayerError::Decoding(_)
+                | VideoPlayerError::BadData
+                | VideoPlayerError::TextureUploadError(_)
+                | VideoPlayerError::DecoderUnexpectedlyExited => true,
+            },
         }
     }
 }
@@ -85,6 +102,7 @@ fn show_video_frame(
     frame: Option,
     issue: Option,
 ) {
+    let show_frame = issue.as_ref().map(|issue| issue.show_frame).unwrap_or(true);
     // Use the texture dimensions if available, otherwise the provided fallback.
     let video_size = frame
         .as_ref()
@@ -123,7 +141,9 @@ fn show_video_frame(
         });
     }
 
-    if let Some(frame) = frame {
+    if let Some(frame) = frame
+        && show_frame
+    {
         let re_renderer::video::VideoFrameTexture {
             texture,
             decoder_delay_state,

From 3942d44f678b694624c7116b5b480efa27366a8e Mon Sep 17 00:00:00 2001
From: Andreas Reich 
Date: Thu, 12 Mar 2026 15:33:32 +0100
Subject: [PATCH 116/513] Take manifest into account when creating transform
 resolution cache

we only did incremental updates, but forgot to ingest the manifest into
the transform cache on fresh cache creation

Source-Ref: 44e893adbb7c608196bc8890f52c9e1a26aa332e
---
 Cargo.lock                                    |  1 +
 crates/store/re_tf/Cargo.toml                 |  5 +++--
 .../src/transform_resolution_cache/cache.rs   | 21 ++++++++++++++-----
 .../utilities/transform_retrieval.rs          |  2 +-
 4 files changed, 21 insertions(+), 8 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index 69e4934f1fdc..5582767a6a37 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -9994,6 +9994,7 @@ dependencies = [
  "re_chunk_store",
  "re_entity_db",
  "re_log",
+ "re_log_encoding",
  "re_log_types",
  "re_mutex",
  "re_sdk_types",
diff --git a/crates/store/re_tf/Cargo.toml b/crates/store/re_tf/Cargo.toml
index 98cccee7b47c..7699899ecf1a 100644
--- a/crates/store/re_tf/Cargo.toml
+++ b/crates/store/re_tf/Cargo.toml
@@ -24,11 +24,12 @@ re_arrow_util.workspace = true
 re_byte_size = { workspace = true, features = ["glam"] }
 re_chunk_store.workspace = true
 re_entity_db.workspace = true # It would be nice not to depend on this, but we need this in order to do queries right now.
-re_log.workspace = true
+re_log_encoding.workspace = true
 re_log_types.workspace = true
+re_log.workspace = true
 re_mutex.workspace = true
-re_tracing.workspace = true
 re_sdk_types = { workspace = true, features = ["glam"] }
+re_tracing.workspace = true
 
 ahash.workspace = true
 arrow.workspace = true
diff --git a/crates/store/re_tf/src/transform_resolution_cache/cache.rs b/crates/store/re_tf/src/transform_resolution_cache/cache.rs
index 1fba4dbc1931..5ee35927775e 100644
--- a/crates/store/re_tf/src/transform_resolution_cache/cache.rs
+++ b/crates/store/re_tf/src/transform_resolution_cache/cache.rs
@@ -6,6 +6,7 @@ use re_byte_size::SizeBytes;
 use re_chunk_store::ChunkStore;
 use re_entity_db::EntityDb;
 use re_log::{debug_assert, debug_assert_eq};
+use re_log_encoding::RrdManifest;
 use re_log_types::{TimeInt, TimelineName};
 use re_sdk_types::archetypes;
 
@@ -65,6 +66,10 @@ impl TransformResolutionCache {
 
         let mut cache = Self::default();
 
+        if let Some(manifest) = entity_db.rrd_manifest_index().manifest() {
+            cache.register_manifest(manifest);
+        }
+
         for chunk in entity_db.storage_engine().store().iter_physical_chunks() {
             // Register all frames even if this chunk doesn't have transform data.
             cache
@@ -220,11 +225,7 @@ impl TransformResolutionCache {
                 }
 
                 re_chunk_store::ChunkStoreDiff::VirtualAddition(addition) => {
-                    // Make all the entity paths known as potential transform paths.
-                    let mut frame_id_registry = self.frame_id_registry.write();
-                    for entity_path in addition.rrd_manifest.recording_schema().all_entities() {
-                        frame_id_registry.register_frame_id_from_entity_path(entity_path);
-                    }
+                    self.register_manifest(&addition.rrd_manifest);
                 }
 
                 re_chunk_store::ChunkStoreDiff::Deletion(deletion) => {
@@ -237,6 +238,16 @@ impl TransformResolutionCache {
         }
     }
 
+    fn register_manifest(&self, manifest: &RrdManifest) {
+        re_tracing::profile_function!();
+
+        // Make all the entity paths known as potential transform paths.
+        let mut frame_id_registry = self.frame_id_registry.write();
+        for entity_path in manifest.recording_schema().all_entities() {
+            frame_id_registry.register_frame_id_from_entity_path(entity_path);
+        }
+    }
+
     fn add_temporal_chunk(&self, chunk: &re_chunk_store::Chunk, aspects: TransformAspect) {
         re_tracing::profile_function!(format!(
             "{} rows, {}",
diff --git a/crates/viewer/re_view_spatial/src/visualizers/utilities/transform_retrieval.rs b/crates/viewer/re_view_spatial/src/visualizers/utilities/transform_retrieval.rs
index 04acd35e0a3f..cc22b2018676 100644
--- a/crates/viewer/re_view_spatial/src/visualizers/utilities/transform_retrieval.rs
+++ b/crates/viewer/re_view_spatial/src/visualizers/utilities/transform_retrieval.rs
@@ -103,7 +103,7 @@ pub fn format_transform_info_result<'a>(
             {
                 format!("{frame_id:?}")
             } else {
-                format!("{entity_path}:?")
+                format!("{entity_path:?}")
             };
             Err(format!("The entity's coordinate frame {src} is unknown."))
         }

From dfddbff646f4a7b3b1675840ac6d31ba9fe30f55 Mon Sep 17 00:00:00 2001
From: Isse 
Date: Thu, 12 Mar 2026 16:07:36 +0100
Subject: [PATCH 117/513] Use text color for video error image

### Related

- Closes RR-4030

### What

Changes tint color for video error image to depend on text color. Makes
it better for light mode and dark mode.

Also fixes a bug where we didn't use draw order for the error image
sometimes causing it to be drawn behind the frame.

Before:
Screenshot 2026-03-12 at 14 50 29

After:
Screenshot 2026-03-12 at 14 48 10

Source-Ref: 2b7a0a308549719758e85df4f1638d4e7bf6654f
---
 .../viewer/re_view_spatial/src/visualizers/video/mod.rs   | 8 +++++---
 .../tests/snapshots/video_asset_VP9_beyond_end.png        | 4 ++--
 .../snapshots/video_asset_VP9_not_on_frame_boundary.png   | 4 ++--
 .../tests/snapshots/video_asset_VP9_start.png             | 4 ++--
 4 files changed, 11 insertions(+), 9 deletions(-)

diff --git a/crates/viewer/re_view_spatial/src/visualizers/video/mod.rs b/crates/viewer/re_view_spatial/src/visualizers/video/mod.rs
index 53f76ab068ae..7900038fcfc4 100644
--- a/crates/viewer/re_view_spatial/src/visualizers/video/mod.rs
+++ b/crates/viewer/re_view_spatial/src/visualizers/video/mod.rs
@@ -7,6 +7,7 @@ use re_renderer::renderer;
 use re_renderer::resource_managers::ImageDataDesc;
 use re_sdk_types::ViewClassIdentifier;
 use re_sdk_types::blueprint::components::VisualizerInstructionId;
+use re_ui::ContextExt as _;
 use re_video::player::{VideoPlaybackIssueSeverity, VideoPlayerError};
 use re_viewer_context::{ViewClass as _, ViewContext};
 pub use video_frame_reference::VideoFrameReferenceVisualizer;
@@ -117,6 +118,7 @@ fn show_video_frame(
     let extent_v = world_from_entity.transform_vector3(glam::Vec3::Y * video_size.y);
 
     let mut has_rendered_texture = false;
+    let mut depth_offset = 0;
 
     let loading_indicator_reason = if let Some(issue) = &issue {
         if matches!(issue.severity, VideoPlaybackIssueSeverity::Loading) {
@@ -163,6 +165,7 @@ fn show_video_frame(
                     .with(visualizer_instruction),
                 issue.is_none() && !show_loading_indicator,
             );
+            depth_offset = frame.depth_offset;
             let textured_rect = renderer::TexturedRect {
                 top_left_corner_position,
                 extent_u,
@@ -313,9 +316,8 @@ fn show_video_frame(
             texture_filter_magnification: renderer::TextureFilterMag::Linear,
             texture_filter_minification: renderer::TextureFilterMin::Linear,
             outline_mask: highlight.overall,
-            #[expect(clippy::disallowed_methods)] // Ok to just dim it
-            multiplicative_tint: egui::Rgba::from_gray(0.5),
-            ..Default::default()
+            multiplicative_tint: egui::Rgba::from(ctx.egui_ctx().tokens().text_default).to_opaque(),
+            depth_offset,
         },
     };
 
diff --git a/crates/viewer/re_view_spatial/tests/snapshots/video_asset_VP9_beyond_end.png b/crates/viewer/re_view_spatial/tests/snapshots/video_asset_VP9_beyond_end.png
index bc5b090f3c0d..72d07053bd52 100644
--- a/crates/viewer/re_view_spatial/tests/snapshots/video_asset_VP9_beyond_end.png
+++ b/crates/viewer/re_view_spatial/tests/snapshots/video_asset_VP9_beyond_end.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:657139949ac09a64273594e030cdee3490a5e3389d9a48947b5736904176e3ed
-size 7788
+oid sha256:7f01bc64627ff0021c1bde09eb62ded9b91ff0a89cf659f3764632a27f505e6e
+size 7784
diff --git a/crates/viewer/re_view_spatial/tests/snapshots/video_asset_VP9_not_on_frame_boundary.png b/crates/viewer/re_view_spatial/tests/snapshots/video_asset_VP9_not_on_frame_boundary.png
index bc5b090f3c0d..72d07053bd52 100644
--- a/crates/viewer/re_view_spatial/tests/snapshots/video_asset_VP9_not_on_frame_boundary.png
+++ b/crates/viewer/re_view_spatial/tests/snapshots/video_asset_VP9_not_on_frame_boundary.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:657139949ac09a64273594e030cdee3490a5e3389d9a48947b5736904176e3ed
-size 7788
+oid sha256:7f01bc64627ff0021c1bde09eb62ded9b91ff0a89cf659f3764632a27f505e6e
+size 7784
diff --git a/crates/viewer/re_view_spatial/tests/snapshots/video_asset_VP9_start.png b/crates/viewer/re_view_spatial/tests/snapshots/video_asset_VP9_start.png
index bc5b090f3c0d..72d07053bd52 100644
--- a/crates/viewer/re_view_spatial/tests/snapshots/video_asset_VP9_start.png
+++ b/crates/viewer/re_view_spatial/tests/snapshots/video_asset_VP9_start.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:657139949ac09a64273594e030cdee3490a5e3389d9a48947b5736904176e3ed
-size 7788
+oid sha256:7f01bc64627ff0021c1bde09eb62ded9b91ff0a89cf659f3764632a27f505e6e
+size 7784

From 5ba2817aec62a42c8504cd4774d4f483d55c227d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jochen=20G=C3=B6rtler?= 
Date: Thu, 12 Mar 2026 16:31:55 +0100
Subject: [PATCH 118/513] Support protobuf messages with `Map` and arbitrary
 `oneof` fields

### Related

* Closes RR-2297
* Closes RR-3931
* Closes https://github.com/rerun-io/rerun/issues/11221

### What

This allows ingesting (MCAP) Protobuf messages with `Map` and arbitrary
`oneof` fields. For `Map` we use an Arrow `MapArray`. `oneof`s are
modelled as nullable Arrow structs with additional Arrow metadata
(extension type) that signals a `oneof` field.

Source-Ref: 852205e13fc83a800df2815657cd68c8791e2580
---
 Cargo.lock                                    |   2 +-
 crates/store/re_mcap/Cargo.toml               |   2 +-
 crates/store/re_mcap/src/decoders/protobuf.rs | 541 +++++++++++-------
 ...tion_tests__decode_failure_resilience.snap |  23 +-
 ...d_combinations_with_presence_tracking.snap |  26 +-
 ...ombinations_without_presence_tracking.snap |  23 +-
 ...buf__integration_tests__oneof_fields.snap} |  52 +-
 ...gration_tests__oneof_message_variants.snap |  31 +
 ...obuf__integration_tests__oneof_nested.snap |  20 +-
 9 files changed, 458 insertions(+), 262 deletions(-)
 rename crates/store/re_mcap/src/decoders/snapshots/{re_mcap__decoders__protobuf__integration_tests__synthetic_vs_real_oneof.snap => re_mcap__decoders__protobuf__integration_tests__oneof_fields.snap} (75%)
 create mode 100644 crates/store/re_mcap/src/decoders/snapshots/re_mcap__decoders__protobuf__integration_tests__oneof_message_variants.snap

diff --git a/Cargo.lock b/Cargo.lock
index 5582767a6a37..dcd28c280e13 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -9278,10 +9278,10 @@ dependencies = [
  "arrow",
  "byteorder",
  "cdr-encoding",
- "crossbeam",
  "insta",
  "mcap",
  "prost-reflect",
+ "re_arrow_util",
  "re_chunk",
  "re_log",
  "re_log_types",
diff --git a/crates/store/re_mcap/Cargo.toml b/crates/store/re_mcap/Cargo.toml
index 51a1860667f8..2090edc4d3be 100644
--- a/crates/store/re_mcap/Cargo.toml
+++ b/crates/store/re_mcap/Cargo.toml
@@ -36,7 +36,7 @@ strum = { workspace = true, features = ["derive"] }
 thiserror.workspace = true
 
 [dev-dependencies]
-crossbeam.workspace = true
 insta = { workspace = true, features = ["filters", "redactions"] }
 prost-reflect = { workspace = true, features = ["text-format"] }
+re_arrow_util.workspace = true
 re_log = { workspace = true, features = ["setup"] }
diff --git a/crates/store/re_mcap/src/decoders/protobuf.rs b/crates/store/re_mcap/src/decoders/protobuf.rs
index 8107708981d7..44e4f8a0a806 100644
--- a/crates/store/re_mcap/src/decoders/protobuf.rs
+++ b/crates/store/re_mcap/src/decoders/protobuf.rs
@@ -1,12 +1,14 @@
+use std::sync::Arc;
+
 use arrow::array::{
     ArrayBuilder, BinaryBuilder, BooleanBuilder, FixedSizeListBuilder, Float32Builder,
-    Float64Builder, Int32Builder, Int64Builder, ListBuilder, StringBuilder, StructBuilder,
-    UInt32Builder, UInt64Builder,
+    Float64Builder, Int32Builder, Int64Builder, ListBuilder, MapBuilder, MapFieldNames,
+    StringBuilder, StructBuilder, UInt32Builder, UInt64Builder,
 };
 use arrow::datatypes::{DataType, Field, Fields};
 use prost_reflect::{
-    DescriptorPool, DynamicMessage, FieldDescriptor, Kind, MessageDescriptor, ReflectMessage as _,
-    Value,
+    DescriptorPool, DynamicMessage, FieldDescriptor, Kind, MapKey, MessageDescriptor,
+    OneofDescriptor, ReflectMessage as _, Value,
 };
 use re_chunk::{Chunk, ChunkId};
 use re_sdk_types::ComponentDescriptor;
@@ -15,21 +17,6 @@ use re_sdk_types::reflection::ComponentDescriptorExt as _;
 use crate::parsers::{MessageParser, ParserContext};
 use crate::{DecoderIdentifier, Error, MessageDecoder};
 
-/// Returns `true` if the field belongs to a real (non-synthetic) `oneof`.
-///
-/// Proto3 `optional` fields use synthetic `oneof`s for presence tracking,
-/// which should be treated as regular fields. A real oneof always has 2+
-/// variants, while proto3 optional creates exactly one synthetic oneof per
-/// field, so checking `o.fields().len() > 1` is sufficient.
-///
-/// Note: `o.is_synthetic()` alone is NOT reliable because some protobuf
-/// compilers/tools don't emit the `proto3_optional` flag it relies on.
-fn is_real_oneof_field(field: &FieldDescriptor) -> bool {
-    field
-        .containing_oneof()
-        .is_some_and(|o| o.fields().len() > 1)
-}
-
 struct ProtobufMessageParser {
     message_descriptor: MessageDescriptor,
     builder: FixedSizeListBuilder,
@@ -56,6 +43,9 @@ enum ProtobufError {
         actual: prost_reflect::Kind,
     },
 
+    #[error("appending to array failed: {0}")]
+    AppendFailed(#[from] arrow::error::ArrowError),
+
     #[error("unknown enum number {0}")]
     UnknownEnumNumber(i32),
 
@@ -95,30 +85,57 @@ fn append_message_fields(
         .map(|(field_desc, value)| (field_desc.number(), value))
         .collect();
 
-    // TODO(#11221): Support real `oneof` values in protobuf MCAP files.
-    // Synthetic oneofs (proto3 optional) are treated as regular fields.
-    let non_oneof_fields = descriptor.fields().filter(|f| !is_real_oneof_field(f));
+    let grouped = grouped_fields(&descriptor);
 
-    for (field_builder, field_desc) in struct_builder
+    for (field_builder, grouped_field) in struct_builder
         .field_builders_mut()
         .iter_mut()
-        .zip(non_oneof_fields)
+        .zip(grouped.iter())
     {
-        // Use the actual field number from the schema, not index-based numbering.
-        // Protobuf schemas can have gaps (e.g., fields 1, 2, 5, 8 after deprecating 3, 4).
-        let protobuf_number = field_desc.number();
+        match grouped_field {
+            GroupedField::Regular(field_desc) => {
+                // Use the actual field number from the schema, not index-based numbering.
+                // Protobuf schemas can have gaps (e.g., fields 1, 2, 5, 8 after deprecating 3, 4).
+                let protobuf_number = field_desc.number();
+
+                if let Some(val) = set_fields.get(&protobuf_number) {
+                    append_value(field_builder, field_desc, val)?;
+                } else {
+                    // For proto3 optional fields, maps, and lists: append null for unset fields.
+                    // For regular proto3 fields: append default values.
+                    if field_desc.supports_presence() || field_desc.is_map() || field_desc.is_list()
+                    {
+                        append_null_to_builder(field_builder)?;
+                    } else {
+                        // Use the default value for this field type.
+                        let default_value = field_desc.default_value();
+                        append_value(field_builder, field_desc, &default_value)?;
+                    }
+                }
+            }
+            GroupedField::OneOf(oneof) => {
+                let oneof_builder = field_builder
+                    .as_any_mut()
+                    .downcast_mut::()
+                    .expect("oneof wrapper builder should be a StructBuilder");
+
+                // Find which variant (if any) is set in this message.
+                let mut any_set = false;
+                for (variant_builder, variant_field) in oneof_builder
+                    .field_builders_mut()
+                    .iter_mut()
+                    .zip(oneof.fields())
+                {
+                    if let Some(val) = set_fields.get(&variant_field.number()) {
+                        append_value(variant_builder, &variant_field, val)?;
+                        any_set = true;
+                    } else {
+                        append_null_to_builder(variant_builder)?;
+                    }
+                }
 
-        if let Some(val) = set_fields.get(&protobuf_number) {
-            append_value(field_builder, &field_desc, val)?;
-        } else {
-            // For proto3 optional fields, append null for unset fields.
-            // For regular proto3 fields, append default values.
-            if field_desc.supports_presence() {
-                append_null_to_builder(field_builder)?;
-            } else {
-                // Use the default value for this field type.
-                let default_value = field_desc.default_value();
-                append_value(field_builder, &field_desc, &default_value)?;
+                // Append the wrapper struct: true if any variant is set, false (null) otherwise.
+                oneof_builder.append(any_set);
             }
         }
     }
@@ -219,6 +236,11 @@ fn append_null_to_builder(builder: &mut dyn ArrayBuilder) -> Result<(), Protobuf
         .downcast_mut::>>()
     {
         b.append_null();
+    } else if let Some(b) = builder
+        .as_any_mut()
+        .downcast_mut::, Box>>()
+    {
+        b.append(false)?;
     } else {
         return Err(ProtobufError::UnsupportedType(
             "Unknown builder type for append_null",
@@ -260,9 +282,30 @@ fn append_value(
             list_builder.append(true);
             re_log::trace!("Finished append on list with elements {val}");
         }
-        Value::Map(_hash_map) => {
-            // We should not encounter hash maps in protobufs.
-            return Err(ProtobufError::UnsupportedType("HashMap"));
+        Value::Map(hash_map) => {
+            let Kind::Message(entry_msg) = field.kind() else {
+                return Err(ProtobufError::UnexpectedType {
+                    expected: "message (map entry)",
+                    actual: field.kind(),
+                });
+            };
+            let key_field = entry_msg.map_entry_key_field();
+            let value_field = entry_msg.map_entry_value_field();
+
+            let map_builder = downcast_err::<
+                MapBuilder, Box>,
+            >(builder, val)?;
+
+            let mut sorted_entries: Vec<_> = hash_map.iter().collect();
+            sorted_entries.sort_by_key(|(k, _)| (*k).clone());
+            for (map_key, map_value) in sorted_entries {
+                let key_value = map_key_to_value(map_key);
+                append_value(map_builder.keys(), &key_field, &key_value)?;
+                append_value(map_builder.values(), &value_field, map_value)?;
+            }
+            map_builder
+                .append(true)
+                .map_err(|_err| ProtobufError::UnsupportedType("MapBuilder append failed"))?;
         }
         Value::EnumNumber(x) => {
             let kind = field.kind();
@@ -293,31 +336,85 @@ fn append_value(
     Ok(())
 }
 
-fn struct_builder_from_message(message_descriptor: &MessageDescriptor) -> StructBuilder {
-    // TODO(#11221): Support real `oneof` values in protobuf MCAP files.
-    // Log about real oneof fields (not synthetic ones from proto3 optional).
-    let skipped_fields: Vec<_> = message_descriptor
-        .fields()
-        .filter(is_real_oneof_field)
-        .map(|f| f.name().to_owned())
-        .collect();
-    if !skipped_fields.is_empty() {
-        re_log::debug_once!(
-            "Skipping `oneof` fields {skipped_fields:?} in `{}`.",
-            message_descriptor.full_name()
-        );
+/// Converts a [`MapKey`] to a [`Value`] so it can be appended via `append_value`.
+fn map_key_to_value(key: &MapKey) -> Value {
+    match key {
+        MapKey::Bool(b) => Value::Bool(*b),
+        MapKey::I32(i) => Value::I32(*i),
+        MapKey::I64(i) => Value::I64(*i),
+        MapKey::U32(u) => Value::U32(*u),
+        MapKey::U64(u) => Value::U64(*u),
+        MapKey::String(s) => Value::String(s.clone()),
     }
+}
 
-    let fields = message_descriptor
-        .fields()
-        .filter(|f| !is_real_oneof_field(f))
-        .map(|f| arrow_field_from(&f))
-        .collect::();
-    let field_builders = message_descriptor
-        .fields()
-        .filter(|f| !is_real_oneof_field(f))
-        .map(|f| arrow_builder_from_field(&f))
-        .collect::>();
+enum GroupedField {
+    Regular(FieldDescriptor),
+    OneOf(OneofDescriptor),
+}
+
+/// Iterates the fields of a message descriptor, collapsing real oneof variants
+/// (those with more than one field) into a single [`GroupedField::OneOf`] entry
+/// at the position of the first variant.
+fn grouped_fields(descriptor: &MessageDescriptor) -> Vec {
+    let mut result = Vec::new();
+    let mut seen = ahash::HashSet::default();
+    for field in descriptor.fields() {
+        // Synthetic oneofs (single-field, proto3 optional) remain as [`GroupedField::Regular`].
+        if let Some(oneof) = field.containing_oneof()
+            && oneof.fields().len() > 1
+        {
+            if seen.insert(oneof.full_name().to_owned()) {
+                result.push(GroupedField::OneOf(oneof));
+            }
+            continue;
+        }
+        result.push(GroupedField::Regular(field));
+    }
+    result
+}
+
+/// Creates Arrow [`Fields`] from a message descriptor, wrapping real oneof
+/// variants in a single struct field named after the oneof with
+/// `ProtobufOneOf` metadata on the wrapper.
+fn fields_from_message(descriptor: &MessageDescriptor) -> Fields {
+    grouped_fields(descriptor)
+        .into_iter()
+        .map(|gf| match gf {
+            GroupedField::Regular(f) => arrow_field_from(&f),
+            GroupedField::OneOf(oneof) => {
+                let inner: Fields = oneof.fields().map(|f| arrow_field_from(&f)).collect();
+                Field::new(oneof.name(), DataType::Struct(inner), true).with_metadata(
+                    std::iter::once((
+                        "ARROW:extension:name".to_owned(),
+                        "rerun.datatypes.ProtobufOneOf".to_owned(),
+                    ))
+                    .collect(),
+                )
+            }
+        })
+        .collect()
+}
+
+fn struct_builder_from_message(message_descriptor: &MessageDescriptor) -> StructBuilder {
+    let fields = fields_from_message(message_descriptor);
+    let field_builders: Vec> = grouped_fields(message_descriptor)
+        .into_iter()
+        .map(|gf| -> Box {
+            match gf {
+                GroupedField::Regular(f) => arrow_builder_from_field(&f),
+                GroupedField::OneOf(oneof) => {
+                    let inner_fields: Fields =
+                        oneof.fields().map(|f| arrow_field_from(&f)).collect();
+                    let inner_builders: Vec> = oneof
+                        .fields()
+                        .map(|f| arrow_builder_from_field(&f))
+                        .collect();
+                    Box::new(StructBuilder::new(inner_fields, inner_builders))
+                }
+            }
+        })
+        .collect();
 
     re_log::debug_assert_eq!(fields.len(), field_builders.len());
 
@@ -340,6 +437,18 @@ fn arrow_builder_from_field(descr: &FieldDescriptor) -> Box {
         Kind::Bool => Box::new(BooleanBuilder::new()),
         Kind::String => Box::new(StringBuilder::new()),
         Kind::Bytes => Box::new(BinaryBuilder::new()),
+        Kind::Message(message_descriptor) if descr.is_map() => {
+            let key_field = message_descriptor.map_entry_key_field();
+            let val_field = message_descriptor.map_entry_value_field();
+            let field_names = MapFieldNames {
+                entry: "entries".to_owned(),
+                key: key_field.name().to_owned(),
+                value: val_field.name().to_owned(),
+            };
+            let key_builder = arrow_builder_from_field(&key_field);
+            let val_builder = arrow_builder_from_field(&val_field);
+            return Box::new(MapBuilder::new(Some(field_names), key_builder, val_builder));
+        }
         Kind::Message(message_descriptor) => {
             Box::new(struct_builder_from_message(&message_descriptor)) as Box
         }
@@ -395,14 +504,29 @@ fn datatype_from(descr: &FieldDescriptor) -> DataType {
         Kind::Bool => DataType::Boolean,
         Kind::String => DataType::Utf8,
         Kind::Bytes => DataType::Binary,
+        Kind::Message(message_descriptor) if descr.is_map() => {
+            let proto_key_field = message_descriptor.map_entry_key_field();
+            let proto_val_field = message_descriptor.map_entry_value_field();
+            let key_field = Field::new(
+                proto_key_field.name(),
+                datatype_from(&proto_key_field),
+                false,
+            );
+            let val_field = Field::new(
+                proto_val_field.name(),
+                datatype_from(&proto_val_field),
+                true,
+            );
+            let entry_field = Field::new(
+                "entries",
+                DataType::Struct(Fields::from(vec![key_field, val_field])),
+                false,
+            );
+            // TODO(grtlr): We actually store the data sorted, but `MapBuilder` does not allow that.
+            DataType::Map(Arc::new(entry_field), false)
+        }
         Kind::Message(message_descriptor) => {
-            // TODO(#11221): Support real `oneof` values in protobuf MCAP files.
-            let fields = message_descriptor
-                .fields()
-                .filter(|f| !is_real_oneof_field(f))
-                .map(|f| arrow_field_from(&f))
-                .collect::();
-            DataType::Struct(fields)
+            DataType::Struct(fields_from_message(&message_descriptor))
         }
         Kind::Enum(_) => {
             // Struct with "name" (String) and "value" (Int32) fields.
@@ -423,14 +547,6 @@ fn datatype_from(descr: &FieldDescriptor) -> DataType {
     inner
 }
 
-/// Returns `true` if the message or any of its nested messages contain real (non-synthetic) oneofs.
-fn has_real_oneofs(descriptor: &MessageDescriptor) -> bool {
-    descriptor.fields().any(|f| {
-        is_real_oneof_field(&f)
-            || matches!(f.kind(), Kind::Message(nested) if has_real_oneofs(&nested))
-    })
-}
-
 /// Provides reflection-based conversion of protobuf-encoded MCAP messages.
 ///
 /// Applying this decoder will result in a direct Arrow representation of the fields.
@@ -468,15 +584,6 @@ impl MessageDecoder for McapProtobufDecoder {
                 .get_message_by_name(schema.name.as_str())
                 .ok_or_else(|| Error::NoSchema(schema.name.clone()))?;
 
-            // TODO(#11221): Support real oneof values in protobuf MCAP files.
-            if has_real_oneofs(&message_descriptor) {
-                re_log::warn_once!(
-                    "Protobuf schema `{}` on topic `{}` contains `oneof` fields which are not yet supported and will be ignored.",
-                    schema.name,
-                    channel.topic,
-                );
-            }
-
             let found = self
                 .descrs_per_topic
                 .insert(channel.topic.clone(), message_descriptor);
@@ -548,22 +655,32 @@ mod unit_tests {
 mod integration_tests {
     use std::io;
 
-    use crossbeam::channel::Receiver;
-
     use prost_reflect::prost::Message as _;
     use prost_reflect::prost_types::{
         DescriptorProto, EnumDescriptorProto, EnumValueDescriptorProto, FieldDescriptorProto,
-        FileDescriptorProto, FileDescriptorSet, OneofDescriptorProto, field_descriptor_proto,
+        FileDescriptorProto, FileDescriptorSet, MessageOptions, OneofDescriptorProto,
+        field_descriptor_proto,
     };
     use prost_reflect::{DescriptorPool, DynamicMessage, MessageDescriptor};
     use re_chunk::Chunk;
-    use re_log::LogMsg;
 
     use re_log_types::TimeType;
 
     use crate::DecoderRegistry;
     use crate::decoders::McapProtobufDecoder;
 
+    fn format_chunk(chunk: &Chunk) -> String {
+        let batch = chunk.to_record_batch().expect("failed to convert chunk");
+        re_arrow_util::RecordBatchFormatOpts {
+            width: Some(240),
+            max_cell_content_width: usize::MAX,
+            redact_non_deterministic: true,
+            ..Default::default()
+        }
+        .format(&batch)
+        .to_string()
+    }
+
     /// Helper to mark fields as proto3 optional with proper synthetic oneof declarations.
     ///
     /// Returns the modified fields and the synthetic `OneofDescriptorProto` entries that
@@ -618,6 +735,32 @@ mod integration_tests {
             .expect("failed to get message descriptor")
     }
 
+    /// Creates a `Person` message descriptor equivalent to:
+    ///
+    /// ```protobuf
+    /// message Person {
+    ///   enum Status {
+    ///     UNKNOWN = 0;
+    ///     ACTIVE = 1;
+    ///     INACTIVE = 2;
+    ///   }
+    ///
+    ///   message Address {
+    ///     optional string street = 1;
+    ///     optional string city = 2;
+    ///   }
+    ///
+    ///   optional string name = 1;
+    ///   reserved 2 to 4;
+    ///   optional int32 id = 5;
+    ///   optional Status status = 8;
+    ///   optional Address address = 9;
+    ///   map tags = 10;
+    /// }
+    /// ```
+    ///
+    /// If `use_proto3_optional` is `false`, the `optional` keywords are removed and
+    /// fields will not have presence tracking (unset fields show default values).
     fn create_person_descriptor(use_proto3_optional: bool) -> (&'static str, DescriptorProto) {
         let status = EnumDescriptorProto {
             name: Some("Status".into()),
@@ -704,17 +847,52 @@ mod integration_tests {
             },
         ];
 
-        let (fields, person_oneof_decls) = if use_proto3_optional {
+        let (mut fields, person_oneof_decls) = if use_proto3_optional {
             make_fields_optional(fields, 0)
         } else {
             (fields, vec![])
         };
 
+        // Map field added *after* `make_fields_optional` so it is not wrapped in a synthetic oneof.
+        let tags_entry = DescriptorProto {
+            name: Some("TagsEntry".into()),
+            field: vec![
+                FieldDescriptorProto {
+                    name: Some("key".into()),
+                    number: Some(1),
+                    label: Some(field_descriptor_proto::Label::Optional as i32),
+                    r#type: Some(field_descriptor_proto::Type::String as i32),
+                    ..Default::default()
+                },
+                FieldDescriptorProto {
+                    name: Some("value".into()),
+                    number: Some(2),
+                    label: Some(field_descriptor_proto::Label::Optional as i32),
+                    r#type: Some(field_descriptor_proto::Type::String as i32),
+                    ..Default::default()
+                },
+            ],
+            options: Some(MessageOptions {
+                map_entry: Some(true),
+                ..Default::default()
+            }),
+            ..Default::default()
+        };
+
+        fields.push(FieldDescriptorProto {
+            name: Some("tags".into()),
+            number: Some(10),
+            label: Some(field_descriptor_proto::Label::Repeated as i32),
+            r#type: Some(field_descriptor_proto::Type::Message as i32),
+            type_name: Some("TagsEntry".into()),
+            ..Default::default()
+        });
+
         // Create a message descriptor with reserved field numbers (2, 3, 4) between actual fields.
         let person_proto = DescriptorProto {
             name: Some("Person".into()),
             field: fields,
-            nested_type: vec![address_message],
+            nested_type: vec![address_message, tags_entry],
             enum_type: vec![status],
             oneof_decl: person_oneof_decls,
             reserved_range: vec![
@@ -784,13 +962,22 @@ mod integration_tests {
 
     /// Helper to create test messages with various field combinations.
     fn create_test_messages(person_message: &MessageDescriptor) -> Vec {
+        use prost_reflect::{MapKey, Value};
+
         vec![
-            // Message 1: has all fields including nested address.
-            DynamicMessage::parse_text_format(
-                person_message.clone(),
-                "name: \"Alice\" id: 123 status: 1 address: { street: \"Main St\" city: \"NYC\" }",
-            )
-            .expect("failed to parse text format"),
+            // Message 1: has all fields including nested address and tags.
+            {
+                let mut msg = DynamicMessage::parse_text_format(
+                    person_message.clone(),
+                    "name: \"Alice\" id: 123 status: 1 address: { street: \"Main St\" city: \"NYC\" }",
+                )
+                .expect("failed to parse text format");
+                let mut tags = std::collections::HashMap::new();
+                tags.insert(MapKey::String("role".into()), Value::String("admin".into()));
+                tags.insert(MapKey::String("org".into()), Value::String("rerun".into()));
+                msg.set_field_by_name("tags", Value::Map(tags));
+                msg
+            },
             // Message 2: has name and status, with partial address (only street).
             DynamicMessage::parse_text_format(
                 person_message.clone(),
@@ -800,22 +987,28 @@ mod integration_tests {
             // Message 3: has name and id, no address.
             DynamicMessage::parse_text_format(person_message.clone(), "name: \"Charlie\" id: 456")
                 .expect("failed to parse text format"),
-            // Message 4: has only name and nested address.
-            DynamicMessage::parse_text_format(
-                person_message.clone(),
-                "name: \"Dave\" address: { city: \"LA\" }",
-            )
-            .expect("failed to parse text format"),
+            // Message 4: has only name, nested address, and tags.
+            {
+                let mut msg = DynamicMessage::parse_text_format(
+                    person_message.clone(),
+                    "name: \"Dave\" address: { city: \"LA\" }",
+                )
+                .expect("failed to parse text format");
+                let mut tags = std::collections::HashMap::new();
+                tags.insert(MapKey::String("role".into()), Value::String("admin".into()));
+                msg.set_field_by_name("tags", Value::Map(tags));
+                msg
+            },
             // Message 5: has only id (name, status, and address missing).
             {
                 let mut msg = DynamicMessage::new(person_message.clone());
-                msg.set_field_by_name("id", prost_reflect::Value::I32(789));
+                msg.set_field_by_name("id", Value::I32(789));
                 msg
             },
             // Message 6: has only status (name, id, and address missing).
             {
                 let mut msg = DynamicMessage::new(person_message.clone());
-                msg.set_field_by_name("status", prost_reflect::Value::EnumNumber(1));
+                msg.set_field_by_name("status", Value::EnumNumber(1));
                 msg
             },
             // Message 7: empty message (all fields missing).
@@ -853,7 +1046,7 @@ mod integration_tests {
         let chunks = run_decoder(&summary, buffer.as_slice());
         assert_eq!(chunks.len(), 1);
 
-        insta::assert_snapshot!(snapshot_name, format!("{:-240}", &chunks[0]));
+        insta::assert_snapshot!(snapshot_name, format_chunk(&chunks[0]));
     }
 
     /// Test various field combinations with proto3 optional (presence tracking).
@@ -925,9 +1118,23 @@ mod integration_tests {
         // We wrote 10 messages (5 valid, 5 invalid), so we should get 5 rows.
         assert_eq!(chunks[0].num_rows(), 5);
 
-        insta::assert_snapshot!("decode_failure_resilience", format!("{:-240}", &chunks[0]));
+        insta::assert_snapshot!("decode_failure_resilience", format_chunk(&chunks[0]));
     }
 
+    /// Creates a `Color` message descriptor equivalent to:
+    ///
+    /// ```protobuf
+    /// message Color {
+    ///   string object = 1;
+    ///   oneof color {
+    ///     string rgb = 2;
+    ///     string hsv = 3;
+    ///     string bgr = 4;
+    ///   }
+    ///   optional float gamma = 5;
+    /// }
+    /// ```
+    ///
     /// If `set_proto3_optional_flag` is `false`, the `gamma` field will still be in a
     /// single-field oneof but without the `proto3_optional` flag. This simulates protobuf
     /// compilers/tools that don't emit the flag.
@@ -996,6 +1203,13 @@ mod integration_tests {
         ("com.example.Color", color_proto)
     }
 
+    /// Creates a `Scene` message descriptor equivalent to:
+    ///
+    /// ```protobuf
+    /// message Scene {
+    ///   Color object = 1;
+    /// }
+    /// ```
     fn create_scene_descriptor() -> (&'static str, DescriptorProto) {
         let scene_proto = DescriptorProto {
             name: Some("Scene".into()),
@@ -1033,30 +1247,6 @@ mod integration_tests {
         ]
     }
 
-    fn check_single_warning(log_rx: &Receiver, topic: &str) {
-        // Verify warning was emitted
-        let warning = log_rx
-            .try_recv()
-            .expect("Expected warning for oneof field in message");
-        assert_eq!(warning.level, re_log::Level::Warn);
-        assert!(
-            warning.msg.contains("oneof"),
-            "Expected warning to mention 'oneof', but got: {}",
-            warning.msg
-        );
-        assert!(
-            warning.msg.contains(topic),
-            "Expected warning to mention topic '{topic}', but got: {}",
-            warning.msg
-        );
-
-        // Verify no additional warnings
-        assert!(
-            log_rx.try_recv().is_err(),
-            "Expected exactly one warning, but found additional warnings"
-        );
-    }
-
     fn create_color_test_messages(color_message: &MessageDescriptor) -> Vec {
         vec![
             // Message 1: object, gamma, and rgb color.
@@ -1077,20 +1267,11 @@ mod integration_tests {
         ]
     }
 
-    // TODO(#11221): Support `oneof` values in protobuf MCAP files.
-    /// This test verifies that proto3 optional fields (synthetic oneofs) are preserved
-    /// while real oneof fields are properly filtered out.
-    #[test]
-    fn test_synthetic_vs_real_oneof() {
-        // Setup logging to capture warnings
-        re_log::setup_logging();
-        let (logger, log_rx) = re_log::ChannelLogger::new(re_log::LevelFilter::Warn);
-        re_log::add_boxed_logger(Box::new(logger)).expect("failed to add logger");
-
-        let (color_name, color_proto) = create_color_descriptor(true);
+    /// Helper to test oneof fields with or without the `proto3_optional` flag on `gamma`.
+    fn test_oneof_fields_helper(set_proto3_optional_flag: bool) {
+        let (color_name, color_proto) = create_color_descriptor(set_proto3_optional_flag);
         let color_message = create_message_descriptor(vec![color_proto], color_name);
 
-        // Create MCAP with test messages.
         let buffer = Vec::new();
         let cursor = io::Cursor::new(buffer);
         let mut writer = mcap::Writer::new(cursor).expect("failed to create writer");
@@ -1109,71 +1290,33 @@ mod integration_tests {
 
         let chunks = run_decoder(&summary, buffer.as_slice());
 
-        check_single_warning(&log_rx, "color_topic");
-
         assert_eq!(chunks.len(), 1);
         assert_eq!(chunks[0].num_rows(), 3);
 
-        // The chunk should have columns for "object" and "gamma" (regular + optional),
-        // but NOT for "rgb", "hsv", or "bgr" (real oneof fields, filtered out).
-        insta::assert_snapshot!("synthetic_vs_real_oneof", format!("{:-240}", &chunks[0]));
+        insta::assert_snapshot!("oneof_fields", format_chunk(&chunks[0]));
     }
 
-    /// Same as `test_synthetic_vs_real_oneof` but without the `proto3_optional` flag on
-    /// `gamma`. Verifies that single-field oneofs are treated as regular optional fields
-    /// even when `is_synthetic()` returns `false` (which happens when some protobuf
-    /// compilers/tools don't emit the `proto3_optional` flag).
+    /// This test verifies that all oneof fields (both real and synthetic) are included
+    /// in the Arrow output.
     #[test]
-    fn test_single_field_oneof_without_proto3_optional_flag() {
-        re_log::setup_logging();
-        let (logger, log_rx) = re_log::ChannelLogger::new(re_log::LevelFilter::Warn);
-        re_log::add_boxed_logger(Box::new(logger)).expect("failed to add logger");
-
-        let (color_name, color_proto) = create_color_descriptor(false);
-        let color_message = create_message_descriptor(vec![color_proto], color_name);
-
-        let buffer = Vec::new();
-        let cursor = io::Cursor::new(buffer);
-        let mut writer = mcap::Writer::new(cursor).expect("failed to create writer");
-
-        let channel_id = add_schema_and_channel(&mut writer, &color_message, "color_topic")
-            .expect("failed to add schema and channel");
-
-        let messages = create_color_test_messages(&color_message);
-        for (i, msg) in messages.iter().enumerate() {
-            write_message(&mut writer, channel_id, msg, (i + 1) as u64)
-                .expect("failed to write message");
-        }
-
-        let summary = writer.finish().expect("finishing writer failed");
-        let buffer = writer.into_inner().into_inner();
-
-        let chunks = run_decoder(&summary, buffer.as_slice());
-
-        check_single_warning(&log_rx, "color_topic");
-
-        assert_eq!(chunks.len(), 1);
-        assert_eq!(chunks[0].num_rows(), 3);
+    fn test_oneof_fields() {
+        test_oneof_fields_helper(true);
+    }
 
-        // Output should be identical to `synthetic_vs_real_oneof` — gamma is preserved
-        // despite the missing `proto3_optional` flag, thanks to the `fields().len() > 1` check.
-        insta::assert_snapshot!("synthetic_vs_real_oneof", format!("{:-240}", &chunks[0]));
+    /// Same as `test_oneof_fields` but without the `proto3_optional` flag on `gamma`.
+    /// Verifies that the output is identical regardless of whether the flag is set.
+    #[test]
+    fn test_oneof_fields_without_proto3_optional_flag() {
+        test_oneof_fields_helper(false);
     }
 
-    // TODO(#11221): Support `oneof` values in protobuf MCAP files.
-    /// This test verifies that nested `oneof` fields triggers a warning and are filtered out.
+    /// This test verifies that nested oneof fields are included in the Arrow output.
     #[test]
     fn test_oneof_nested() {
-        // Setup logging to capture warnings
-        re_log::setup_logging();
-        let (logger, log_rx) = re_log::ChannelLogger::new(re_log::LevelFilter::Warn);
-        re_log::add_boxed_logger(Box::new(logger)).expect("failed to add logger");
-
         let (_, color_proto) = create_color_descriptor(true);
         let (scene_name, scene_proto) = create_scene_descriptor();
         let scene_message = create_message_descriptor(vec![color_proto, scene_proto], scene_name);
 
-        // Create MCAP with test messages.
         let buffer = Vec::new();
         let cursor = io::Cursor::new(buffer);
         let mut writer = mcap::Writer::new(cursor).expect("failed to create writer");
@@ -1192,11 +1335,9 @@ mod integration_tests {
 
         let chunks = run_decoder(&summary, buffer.as_slice());
 
-        check_single_warning(&log_rx, "scene_topic");
-
-        assert_eq!(chunks.len(), 1); // One chunk for scene_topic
+        assert_eq!(chunks.len(), 1);
         assert_eq!(chunks[0].num_rows(), 2);
 
-        insta::assert_snapshot!("oneof_nested", format!("{:-240}", &chunks[0]));
+        insta::assert_snapshot!("oneof_nested", format_chunk(&chunks[0]));
     }
 }
diff --git a/crates/store/re_mcap/src/decoders/snapshots/re_mcap__decoders__protobuf__integration_tests__decode_failure_resilience.snap b/crates/store/re_mcap/src/decoders/snapshots/re_mcap__decoders__protobuf__integration_tests__decode_failure_resilience.snap
index b68e06a2d495..3b3d303672bd 100644
--- a/crates/store/re_mcap/src/decoders/snapshots/re_mcap__decoders__protobuf__integration_tests__decode_failure_resilience.snap
+++ b/crates/store/re_mcap/src/decoders/snapshots/re_mcap__decoders__protobuf__integration_tests__decode_failure_resilience.snap
@@ -1,6 +1,6 @@
 ---
-source: crates/store/re_mcap/src/layers/protobuf.rs
-expression: "format!(\"{:-240}\", &chunks[0])"
+source: crates/store/re_mcap/src/decoders/protobuf.rs
+expression: "format_chunk(&chunks[0])"
 ---
 ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
 │ METADATA:                                                                                                                                                                       │
@@ -15,19 +15,26 @@ expression: "format!(\"{:-240}\", &chunks[0])"
 │ │ ARROW:extension:metadata: {"namespace":"row"} ┆ index_name: message_log_time  ┆ index_name: message_publish_time ┆ Struct("name": Utf8, "value": Int32), metadata:          │ │
 │ │ ARROW:extension:name: TUID                    ┆ is_sorted: true               ┆ is_sorted: true                  ┆ {"ARROW:extension:name":                                 │ │
 │ │ is_sorted: true                               ┆ kind: index                   ┆ kind: index                      ┆ "rerun.datatypes.ProtobufEnum"}, "address":              │ │
-│ │ kind: control                                 ┆                               ┆                                  ┆ Struct("street": Utf8, "city": Utf8)))                   │ │
+│ │ kind: control                                 ┆                               ┆                                  ┆ Struct("street": Utf8, "city": Utf8), "tags":            │ │
+│ │                                               ┆                               ┆                                  ┆ Map("entries": non-null Struct("key": non-null Utf8,     │ │
+│ │                                               ┆                               ┆                                  ┆ "value": Utf8), unsorted)))                              │ │
 │ │                                               ┆                               ┆                                  ┆ archetype: com.example.Person                            │ │
 │ │                                               ┆                               ┆                                  ┆ component: com.example.Person:message                    │ │
 │ │                                               ┆                               ┆                                  ┆ kind: data                                               │ │
 │ ╞═══════════════════════════════════════════════╪═══════════════════════════════╪══════════════════════════════════╪══════════════════════════════════════════════════════════╡ │
-│ │ row_[**REDACTED**]                            ┆ 1970-01-01T00:00:00.000000101 ┆ 1970-01-01T00:00:00.000000101    ┆ [{name: Person1, id: 1, status: null, address: null}]    │ │
+│ │ row_[**REDACTED**]                            ┆ 1970-01-01T00:00:00.000000101 ┆ 1970-01-01T00:00:00.000000101    ┆ [{name: Person1, id: 1, status: null, address: null,     │ │
+│ │                                               ┆                               ┆                                  ┆ tags: null}]                                             │ │
 │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │
-│ │ row_[**REDACTED**]                            ┆ 1970-01-01T00:00:00.000000103 ┆ 1970-01-01T00:00:00.000000103    ┆ [{name: Person3, id: 3, status: null, address: null}]    │ │
+│ │ row_[**REDACTED**]                            ┆ 1970-01-01T00:00:00.000000103 ┆ 1970-01-01T00:00:00.000000103    ┆ [{name: Person3, id: 3, status: null, address: null,     │ │
+│ │                                               ┆                               ┆                                  ┆ tags: null}]                                             │ │
 │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │
-│ │ row_[**REDACTED**]                            ┆ 1970-01-01T00:00:00.000000105 ┆ 1970-01-01T00:00:00.000000105    ┆ [{name: Person5, id: 5, status: null, address: null}]    │ │
+│ │ row_[**REDACTED**]                            ┆ 1970-01-01T00:00:00.000000105 ┆ 1970-01-01T00:00:00.000000105    ┆ [{name: Person5, id: 5, status: null, address: null,     │ │
+│ │                                               ┆                               ┆                                  ┆ tags: null}]                                             │ │
 │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │
-│ │ row_[**REDACTED**]                            ┆ 1970-01-01T00:00:00.000000107 ┆ 1970-01-01T00:00:00.000000107    ┆ [{name: Person7, id: 7, status: null, address: null}]    │ │
+│ │ row_[**REDACTED**]                            ┆ 1970-01-01T00:00:00.000000107 ┆ 1970-01-01T00:00:00.000000107    ┆ [{name: Person7, id: 7, status: null, address: null,     │ │
+│ │                                               ┆                               ┆                                  ┆ tags: null}]                                             │ │
 │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │
-│ │ row_[**REDACTED**]                            ┆ 1970-01-01T00:00:00.000000109 ┆ 1970-01-01T00:00:00.000000109    ┆ [{name: Person9, id: 9, status: null, address: null}]    │ │
+│ │ row_[**REDACTED**]                            ┆ 1970-01-01T00:00:00.000000109 ┆ 1970-01-01T00:00:00.000000109    ┆ [{name: Person9, id: 9, status: null, address: null,     │ │
+│ │                                               ┆                               ┆                                  ┆ tags: null}]                                             │ │
 │ └───────────────────────────────────────────────┴───────────────────────────────┴──────────────────────────────────┴──────────────────────────────────────────────────────────┘ │
 └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
diff --git a/crates/store/re_mcap/src/decoders/snapshots/re_mcap__decoders__protobuf__integration_tests__field_combinations_with_presence_tracking.snap b/crates/store/re_mcap/src/decoders/snapshots/re_mcap__decoders__protobuf__integration_tests__field_combinations_with_presence_tracking.snap
index 8758b5a8bfe4..b43033d7e5c9 100644
--- a/crates/store/re_mcap/src/decoders/snapshots/re_mcap__decoders__protobuf__integration_tests__field_combinations_with_presence_tracking.snap
+++ b/crates/store/re_mcap/src/decoders/snapshots/re_mcap__decoders__protobuf__integration_tests__field_combinations_with_presence_tracking.snap
@@ -1,6 +1,6 @@
 ---
-source: crates/store/re_mcap/src/layers/protobuf.rs
-expression: "format!(\"{:-240}\", &chunks[0])"
+source: crates/store/re_mcap/src/decoders/protobuf.rs
+expression: "format_chunk(&chunks[0])"
 ---
 ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
 │ METADATA:                                                                                                                                                                       │
@@ -15,27 +15,33 @@ expression: "format!(\"{:-240}\", &chunks[0])"
 │ │ ARROW:extension:metadata: {"namespace":"row"} ┆ index_name: message_log_time  ┆ index_name: message_publish_time ┆ Struct("name": Utf8, "value": Int32), metadata:          │ │
 │ │ ARROW:extension:name: TUID                    ┆ is_sorted: true               ┆ is_sorted: true                  ┆ {"ARROW:extension:name":                                 │ │
 │ │ is_sorted: true                               ┆ kind: index                   ┆ kind: index                      ┆ "rerun.datatypes.ProtobufEnum"}, "address":              │ │
-│ │ kind: control                                 ┆                               ┆                                  ┆ Struct("street": Utf8, "city": Utf8)))                   │ │
+│ │ kind: control                                 ┆                               ┆                                  ┆ Struct("street": Utf8, "city": Utf8), "tags":            │ │
+│ │                                               ┆                               ┆                                  ┆ Map("entries": non-null Struct("key": non-null Utf8,     │ │
+│ │                                               ┆                               ┆                                  ┆ "value": Utf8), unsorted)))                              │ │
 │ │                                               ┆                               ┆                                  ┆ archetype: com.example.Person                            │ │
 │ │                                               ┆                               ┆                                  ┆ component: com.example.Person:message                    │ │
 │ │                                               ┆                               ┆                                  ┆ kind: data                                               │ │
 │ ╞═══════════════════════════════════════════════╪═══════════════════════════════╪══════════════════════════════════╪══════════════════════════════════════════════════════════╡ │
 │ │ row_[**REDACTED**]                            ┆ 1970-01-01T00:00:00.000000001 ┆ 1970-01-01T00:00:00.000000001    ┆ [{name: Alice, id: 123, status: {name: ACTIVE, value:    │ │
-│ │                                               ┆                               ┆                                  ┆ 1}, address: {street: Main St, city: NYC}}]              │ │
+│ │                                               ┆                               ┆                                  ┆ 1}, address: {street: Main St, city: NYC}, tags: {org:   │ │
+│ │                                               ┆                               ┆                                  ┆ rerun, role: admin}}]                                    │ │
 │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │
 │ │ row_[**REDACTED**]                            ┆ 1970-01-01T00:00:00.000000002 ┆ 1970-01-01T00:00:00.000000002    ┆ [{name: Bob, id: null, status: {name: INACTIVE, value:   │ │
-│ │                                               ┆                               ┆                                  ┆ 2}, address: {street: Oak Ave, city: null}}]             │ │
+│ │                                               ┆                               ┆                                  ┆ 2}, address: {street: Oak Ave, city: null}, tags: null}] │ │
 │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │
-│ │ row_[**REDACTED**]                            ┆ 1970-01-01T00:00:00.000000003 ┆ 1970-01-01T00:00:00.000000003    ┆ [{name: Charlie, id: 456, status: null, address: null}]  │ │
+│ │ row_[**REDACTED**]                            ┆ 1970-01-01T00:00:00.000000003 ┆ 1970-01-01T00:00:00.000000003    ┆ [{name: Charlie, id: 456, status: null, address: null,   │ │
+│ │                                               ┆                               ┆                                  ┆ tags: null}]                                             │ │
 │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │
 │ │ row_[**REDACTED**]                            ┆ 1970-01-01T00:00:00.000000004 ┆ 1970-01-01T00:00:00.000000004    ┆ [{name: Dave, id: null, status: null, address: {street:  │ │
-│ │                                               ┆                               ┆                                  ┆ null, city: LA}}]                                        │ │
+│ │                                               ┆                               ┆                                  ┆ null, city: LA}, tags: {role: admin}}]                   │ │
 │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │
-│ │ row_[**REDACTED**]                            ┆ 1970-01-01T00:00:00.000000005 ┆ 1970-01-01T00:00:00.000000005    ┆ [{name: null, id: 789, status: null, address: null}]     │ │
+│ │ row_[**REDACTED**]                            ┆ 1970-01-01T00:00:00.000000005 ┆ 1970-01-01T00:00:00.000000005    ┆ [{name: null, id: 789, status: null, address: null,      │ │
+│ │                                               ┆                               ┆                                  ┆ tags: null}]                                             │ │
 │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │
 │ │ row_[**REDACTED**]                            ┆ 1970-01-01T00:00:00.000000006 ┆ 1970-01-01T00:00:00.000000006    ┆ [{name: null, id: null, status: {name: ACTIVE, value:    │ │
-│ │                                               ┆                               ┆                                  ┆ 1}, address: null}]                                      │ │
+│ │                                               ┆                               ┆                                  ┆ 1}, address: null, tags: null}]                          │ │
 │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │
-│ │ row_[**REDACTED**]                            ┆ 1970-01-01T00:00:00.000000007 ┆ 1970-01-01T00:00:00.000000007    ┆ [{name: null, id: null, status: null, address: null}]    │ │
+│ │ row_[**REDACTED**]                            ┆ 1970-01-01T00:00:00.000000007 ┆ 1970-01-01T00:00:00.000000007    ┆ [{name: null, id: null, status: null, address: null,     │ │
+│ │                                               ┆                               ┆                                  ┆ tags: null}]                                             │ │
 │ └───────────────────────────────────────────────┴───────────────────────────────┴──────────────────────────────────┴──────────────────────────────────────────────────────────┘ │
 └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
diff --git a/crates/store/re_mcap/src/decoders/snapshots/re_mcap__decoders__protobuf__integration_tests__field_combinations_without_presence_tracking.snap b/crates/store/re_mcap/src/decoders/snapshots/re_mcap__decoders__protobuf__integration_tests__field_combinations_without_presence_tracking.snap
index c38bbb62c07e..96668b4b34d2 100644
--- a/crates/store/re_mcap/src/decoders/snapshots/re_mcap__decoders__protobuf__integration_tests__field_combinations_without_presence_tracking.snap
+++ b/crates/store/re_mcap/src/decoders/snapshots/re_mcap__decoders__protobuf__integration_tests__field_combinations_without_presence_tracking.snap
@@ -1,6 +1,6 @@
 ---
-source: crates/store/re_mcap/src/layers/protobuf.rs
-expression: "format!(\"{:-240}\", &chunks[0])"
+source: crates/store/re_mcap/src/decoders/protobuf.rs
+expression: "format_chunk(&chunks[0])"
 ---
 ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
 │ METADATA:                                                                                                                                                                       │
@@ -15,30 +15,33 @@ expression: "format!(\"{:-240}\", &chunks[0])"
 │ │ ARROW:extension:metadata: {"namespace":"row"} ┆ index_name: message_log_time  ┆ index_name: message_publish_time ┆ Struct("name": Utf8, "value": Int32), metadata:          │ │
 │ │ ARROW:extension:name: TUID                    ┆ is_sorted: true               ┆ is_sorted: true                  ┆ {"ARROW:extension:name":                                 │ │
 │ │ is_sorted: true                               ┆ kind: index                   ┆ kind: index                      ┆ "rerun.datatypes.ProtobufEnum"}, "address":              │ │
-│ │ kind: control                                 ┆                               ┆                                  ┆ Struct("street": Utf8, "city": Utf8)))                   │ │
+│ │ kind: control                                 ┆                               ┆                                  ┆ Struct("street": Utf8, "city": Utf8), "tags":            │ │
+│ │                                               ┆                               ┆                                  ┆ Map("entries": non-null Struct("key": non-null Utf8,     │ │
+│ │                                               ┆                               ┆                                  ┆ "value": Utf8), unsorted)))                              │ │
 │ │                                               ┆                               ┆                                  ┆ archetype: com.example.Person                            │ │
 │ │                                               ┆                               ┆                                  ┆ component: com.example.Person:message                    │ │
 │ │                                               ┆                               ┆                                  ┆ kind: data                                               │ │
 │ ╞═══════════════════════════════════════════════╪═══════════════════════════════╪══════════════════════════════════╪══════════════════════════════════════════════════════════╡ │
 │ │ row_[**REDACTED**]                            ┆ 1970-01-01T00:00:00.000000001 ┆ 1970-01-01T00:00:00.000000001    ┆ [{name: Alice, id: 123, status: {name: ACTIVE, value:    │ │
-│ │                                               ┆                               ┆                                  ┆ 1}, address: {street: Main St, city: NYC}}]              │ │
+│ │                                               ┆                               ┆                                  ┆ 1}, address: {street: Main St, city: NYC}, tags: {org:   │ │
+│ │                                               ┆                               ┆                                  ┆ rerun, role: admin}}]                                    │ │
 │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │
 │ │ row_[**REDACTED**]                            ┆ 1970-01-01T00:00:00.000000002 ┆ 1970-01-01T00:00:00.000000002    ┆ [{name: Bob, id: 0, status: {name: INACTIVE, value: 2},  │ │
-│ │                                               ┆                               ┆                                  ┆ address: {street: Oak Ave, city: }}]                     │ │
+│ │                                               ┆                               ┆                                  ┆ address: {street: Oak Ave, city: }, tags: null}]         │ │
 │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │
 │ │ row_[**REDACTED**]                            ┆ 1970-01-01T00:00:00.000000003 ┆ 1970-01-01T00:00:00.000000003    ┆ [{name: Charlie, id: 456, status: {name: UNKNOWN, value: │ │
-│ │                                               ┆                               ┆                                  ┆ 0}, address: null}]                                      │ │
+│ │                                               ┆                               ┆                                  ┆ 0}, address: null, tags: null}]                          │ │
 │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │
 │ │ row_[**REDACTED**]                            ┆ 1970-01-01T00:00:00.000000004 ┆ 1970-01-01T00:00:00.000000004    ┆ [{name: Dave, id: 0, status: {name: UNKNOWN, value: 0},  │ │
-│ │                                               ┆                               ┆                                  ┆ address: {street: , city: LA}}]                          │ │
+│ │                                               ┆                               ┆                                  ┆ address: {street: , city: LA}, tags: {role: admin}}]     │ │
 │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │
 │ │ row_[**REDACTED**]                            ┆ 1970-01-01T00:00:00.000000005 ┆ 1970-01-01T00:00:00.000000005    ┆ [{name: , id: 789, status: {name: UNKNOWN, value: 0},    │ │
-│ │                                               ┆                               ┆                                  ┆ address: null}]                                          │ │
+│ │                                               ┆                               ┆                                  ┆ address: null, tags: null}]                              │ │
 │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │
 │ │ row_[**REDACTED**]                            ┆ 1970-01-01T00:00:00.000000006 ┆ 1970-01-01T00:00:00.000000006    ┆ [{name: , id: 0, status: {name: ACTIVE, value: 1},       │ │
-│ │                                               ┆                               ┆                                  ┆ address: null}]                                          │ │
+│ │                                               ┆                               ┆                                  ┆ address: null, tags: null}]                              │ │
 │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │
 │ │ row_[**REDACTED**]                            ┆ 1970-01-01T00:00:00.000000007 ┆ 1970-01-01T00:00:00.000000007    ┆ [{name: , id: 0, status: {name: UNKNOWN, value: 0},      │ │
-│ │                                               ┆                               ┆                                  ┆ address: null}]                                          │ │
+│ │                                               ┆                               ┆                                  ┆ address: null, tags: null}]                              │ │
 │ └───────────────────────────────────────────────┴───────────────────────────────┴──────────────────────────────────┴──────────────────────────────────────────────────────────┘ │
 └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
diff --git a/crates/store/re_mcap/src/decoders/snapshots/re_mcap__decoders__protobuf__integration_tests__synthetic_vs_real_oneof.snap b/crates/store/re_mcap/src/decoders/snapshots/re_mcap__decoders__protobuf__integration_tests__oneof_fields.snap
similarity index 75%
rename from crates/store/re_mcap/src/decoders/snapshots/re_mcap__decoders__protobuf__integration_tests__synthetic_vs_real_oneof.snap
rename to crates/store/re_mcap/src/decoders/snapshots/re_mcap__decoders__protobuf__integration_tests__oneof_fields.snap
index a7a50176ad58..849bbbcf4777 100644
--- a/crates/store/re_mcap/src/decoders/snapshots/re_mcap__decoders__protobuf__integration_tests__synthetic_vs_real_oneof.snap
+++ b/crates/store/re_mcap/src/decoders/snapshots/re_mcap__decoders__protobuf__integration_tests__oneof_fields.snap
@@ -1,26 +1,30 @@
 ---
-source: crates/store/re_mcap/src/layers/protobuf.rs
-expression: "format!(\"{:-240}\", &chunks[0])"
+source: crates/store/re_mcap/src/decoders/protobuf.rs
+expression: "format_chunk(&chunks[0])"
 ---
-┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
-│ METADATA:                                                                                                                                                                   │
-│ * entity_path: /color_topic                                                                                                                                                 │
-│ * id: [**REDACTED**]                                                                                                                                                        │
-│ * version: [**REDACTED**]                                                                                                                                                   │
-├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
-│ ┌───────────────────────────────────────────────┬───────────────────────────────┬──────────────────────────────────┬──────────────────────────────────────────────────────┐ │
-│ │ RowId                                         ┆ message_log_time              ┆ message_publish_time             ┆ com.example.Color:message                            │ │
-│ │ ---                                           ┆ ---                           ┆ ---                              ┆ ---                                                  │ │
-│ │ type: non-null FixedSizeBinary(16)            ┆ type: Timestamp(ns)           ┆ type: Timestamp(ns)              ┆ type: List(Struct("object": Utf8, "gamma": Float32)) │ │
-│ │ ARROW:extension:metadata: {"namespace":"row"} ┆ index_name: message_log_time  ┆ index_name: message_publish_time ┆ archetype: com.example.Color                         │ │
-│ │ ARROW:extension:name: TUID                    ┆ is_sorted: true               ┆ is_sorted: true                  ┆ component: com.example.Color:message                 │ │
-│ │ is_sorted: true                               ┆ kind: index                   ┆ kind: index                      ┆ kind: data                                           │ │
-│ │ kind: control                                 ┆                               ┆                                  ┆                                                      │ │
-│ ╞═══════════════════════════════════════════════╪═══════════════════════════════╪══════════════════════════════════╪══════════════════════════════════════════════════════╡ │
-│ │ row_[**REDACTED**]                            ┆ 1970-01-01T00:00:00.000000001 ┆ 1970-01-01T00:00:00.000000001    ┆ [{object: box, gamma: 2.2}]                          │ │
-│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │
-│ │ row_[**REDACTED**]                            ┆ 1970-01-01T00:00:00.000000002 ┆ 1970-01-01T00:00:00.000000002    ┆ [{object: sphere, gamma: null}]                      │ │
-│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │
-│ │ row_[**REDACTED**]                            ┆ 1970-01-01T00:00:00.000000003 ┆ 1970-01-01T00:00:00.000000003    ┆ [{object: cone, gamma: null}]                        │ │
-│ └───────────────────────────────────────────────┴───────────────────────────────┴──────────────────────────────────┴──────────────────────────────────────────────────────┘ │
-└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
+┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
+│ METADATA:                                                                                                                                                                       │
+│ * entity_path: /color_topic                                                                                                                                                     │
+│ * id: [**REDACTED**]                                                                                                                                                            │
+│ * version: [**REDACTED**]                                                                                                                                                       │
+├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
+│ ┌───────────────────────────────────────────────┬───────────────────────────────┬──────────────────────────────────┬──────────────────────────────────────────────────────────┐ │
+│ │ RowId                                         ┆ message_log_time              ┆ message_publish_time             ┆ com.example.Color:message                                │ │
+│ │ ---                                           ┆ ---                           ┆ ---                              ┆ ---                                                      │ │
+│ │ type: non-null FixedSizeBinary(16)            ┆ type: Timestamp(ns)           ┆ type: Timestamp(ns)              ┆ type: List(Struct("object": Utf8, "color": Struct("rgb": │ │
+│ │ ARROW:extension:metadata: {"namespace":"row"} ┆ index_name: message_log_time  ┆ index_name: message_publish_time ┆ Utf8, "hsv": Utf8, "bgr": Utf8), metadata:               │ │
+│ │ ARROW:extension:name: TUID                    ┆ is_sorted: true               ┆ is_sorted: true                  ┆ {"ARROW:extension:name":                                 │ │
+│ │ is_sorted: true                               ┆ kind: index                   ┆ kind: index                      ┆ "rerun.datatypes.ProtobufOneOf"}, "gamma": Float32))     │ │
+│ │ kind: control                                 ┆                               ┆                                  ┆ archetype: com.example.Color                             │ │
+│ │                                               ┆                               ┆                                  ┆ component: com.example.Color:message                     │ │
+│ │                                               ┆                               ┆                                  ┆ kind: data                                               │ │
+│ ╞═══════════════════════════════════════════════╪═══════════════════════════════╪══════════════════════════════════╪══════════════════════════════════════════════════════════╡ │
+│ │ row_[**REDACTED**]                            ┆ 1970-01-01T00:00:00.000000001 ┆ 1970-01-01T00:00:00.000000001    ┆ [{object: box, color: {rgb: 255,0,0, hsv: null, bgr:     │ │
+│ │                                               ┆                               ┆                                  ┆ null}, gamma: 2.2}]                                      │ │
+│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │
+│ │ row_[**REDACTED**]                            ┆ 1970-01-01T00:00:00.000000002 ┆ 1970-01-01T00:00:00.000000002    ┆ [{object: sphere, color: {rgb: null, hsv: 120,1.0,1.0,   │ │
+│ │                                               ┆                               ┆                                  ┆ bgr: null}, gamma: null}]                                │ │
+│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │
+│ │ row_[**REDACTED**]                            ┆ 1970-01-01T00:00:00.000000003 ┆ 1970-01-01T00:00:00.000000003    ┆ [{object: cone, color: null, gamma: null}]               │ │
+│ └───────────────────────────────────────────────┴───────────────────────────────┴──────────────────────────────────┴──────────────────────────────────────────────────────────┘ │
+└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
diff --git a/crates/store/re_mcap/src/decoders/snapshots/re_mcap__decoders__protobuf__integration_tests__oneof_message_variants.snap b/crates/store/re_mcap/src/decoders/snapshots/re_mcap__decoders__protobuf__integration_tests__oneof_message_variants.snap
new file mode 100644
index 000000000000..e545788444a3
--- /dev/null
+++ b/crates/store/re_mcap/src/decoders/snapshots/re_mcap__decoders__protobuf__integration_tests__oneof_message_variants.snap
@@ -0,0 +1,31 @@
+---
+source: crates/store/re_mcap/src/decoders/protobuf.rs
+expression: "format_chunk(&chunks[0])"
+---
+┌────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
+│ METADATA:                                                                                                                                                                      │
+│ * entity_path: /shape_topic                                                                                                                                                    │
+│ * id: [**REDACTED**]                                                                                                                                                           │
+│ * version: [**REDACTED**]                                                                                                                                                      │
+├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
+│ ┌───────────────────────────────────────────────┬──────────────────────────────┬──────────────────────────────────┬──────────────────────────────────────────────────────────┐ │
+│ │ RowId                                         ┆ message_log_time             ┆ message_publish_time             ┆ com.example.Shape:message                                │ │
+│ │ ---                                           ┆ ---                          ┆ ---                              ┆ ---                                                      │ │
+│ │ type: non-null FixedSizeBinary(16)            ┆ type: Duration(ns)           ┆ type: Duration(ns)               ┆ type: List(Struct("name": Utf8, "geometry":              │ │
+│ │ ARROW:extension:metadata: {"namespace":"row"} ┆ index_name: message_log_time ┆ index_name: message_publish_time ┆ Struct("circle": Struct("radius": Float32), "rectangle": │ │
+│ │ ARROW:extension:name: TUID                    ┆ is_sorted: true              ┆ is_sorted: true                  ┆ Struct("width": Float32, "height": Float32)), metadata:  │ │
+│ │ is_sorted: true                               ┆ kind: index                  ┆ kind: index                      ┆ {"ARROW:extension:name":                                 │ │
+│ │ kind: control                                 ┆                              ┆                                  ┆ "rerun.datatypes.ProtobufOneOf"}))                       │ │
+│ │                                               ┆                              ┆                                  ┆ archetype: com.example.Shape                             │ │
+│ │                                               ┆                              ┆                                  ┆ component: com.example.Shape:message                     │ │
+│ │                                               ┆                              ┆                                  ┆ kind: data                                               │ │
+│ ╞═══════════════════════════════════════════════╪══════════════════════════════╪══════════════════════════════════╪══════════════════════════════════════════════════════════╡ │
+│ │ row_[**REDACTED**]                            ┆ PT0.000000001S               ┆ PT0.000000001S                   ┆ [{name: wheel, geometry: {circle: {radius: 5.0},         │ │
+│ │                                               ┆                              ┆                                  ┆ rectangle: null}}]                                       │ │
+│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │
+│ │ row_[**REDACTED**]                            ┆ PT0.000000002S               ┆ PT0.000000002S                   ┆ [{name: door, geometry: {circle: null, rectangle:        │ │
+│ │                                               ┆                              ┆                                  ┆ {width: 3.0, height: 7.0}}}]                             │ │
+│ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │
+│ │ row_[**REDACTED**]                            ┆ PT0.000000003S               ┆ PT0.000000003S                   ┆ [{name: unknown, geometry: null}]                        │ │
+│ └───────────────────────────────────────────────┴──────────────────────────────┴──────────────────────────────────┴──────────────────────────────────────────────────────────┘ │
+└────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
diff --git a/crates/store/re_mcap/src/decoders/snapshots/re_mcap__decoders__protobuf__integration_tests__oneof_nested.snap b/crates/store/re_mcap/src/decoders/snapshots/re_mcap__decoders__protobuf__integration_tests__oneof_nested.snap
index 16625c8299af..b5f042b38548 100644
--- a/crates/store/re_mcap/src/decoders/snapshots/re_mcap__decoders__protobuf__integration_tests__oneof_nested.snap
+++ b/crates/store/re_mcap/src/decoders/snapshots/re_mcap__decoders__protobuf__integration_tests__oneof_nested.snap
@@ -1,6 +1,6 @@
 ---
-source: crates/store/re_mcap/src/layers/protobuf.rs
-expression: "format!(\"{:-240}\", &chunks[0])"
+source: crates/store/re_mcap/src/decoders/protobuf.rs
+expression: "format_chunk(&chunks[0])"
 ---
 ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
 │ METADATA:                                                                                                                                                                       │
@@ -12,13 +12,17 @@ expression: "format!(\"{:-240}\", &chunks[0])"
 │ │ RowId                                         ┆ message_log_time              ┆ message_publish_time             ┆ com.example.Scene:message                                │ │
 │ │ ---                                           ┆ ---                           ┆ ---                              ┆ ---                                                      │ │
 │ │ type: non-null FixedSizeBinary(16)            ┆ type: Timestamp(ns)           ┆ type: Timestamp(ns)              ┆ type: List(Struct("object": Struct("object": Utf8,       │ │
-│ │ ARROW:extension:metadata: {"namespace":"row"} ┆ index_name: message_log_time  ┆ index_name: message_publish_time ┆ "gamma": Float32)))                                      │ │
-│ │ ARROW:extension:name: TUID                    ┆ is_sorted: true               ┆ is_sorted: true                  ┆ archetype: com.example.Scene                             │ │
-│ │ is_sorted: true                               ┆ kind: index                   ┆ kind: index                      ┆ component: com.example.Scene:message                     │ │
-│ │ kind: control                                 ┆                               ┆                                  ┆ kind: data                                               │ │
+│ │ ARROW:extension:metadata: {"namespace":"row"} ┆ index_name: message_log_time  ┆ index_name: message_publish_time ┆ "color": Struct("rgb": Utf8, "hsv": Utf8, "bgr": Utf8),  │ │
+│ │ ARROW:extension:name: TUID                    ┆ is_sorted: true               ┆ is_sorted: true                  ┆ metadata: {"ARROW:extension:name":                       │ │
+│ │ is_sorted: true                               ┆ kind: index                   ┆ kind: index                      ┆ "rerun.datatypes.ProtobufOneOf"}, "gamma": Float32)))    │ │
+│ │ kind: control                                 ┆                               ┆                                  ┆ archetype: com.example.Scene                             │ │
+│ │                                               ┆                               ┆                                  ┆ component: com.example.Scene:message                     │ │
+│ │                                               ┆                               ┆                                  ┆ kind: data                                               │ │
 │ ╞═══════════════════════════════════════════════╪═══════════════════════════════╪══════════════════════════════════╪══════════════════════════════════════════════════════════╡ │
-│ │ row_[**REDACTED**]                            ┆ 1970-01-01T00:00:00.000000001 ┆ 1970-01-01T00:00:00.000000001    ┆ [{object: {object: cube, gamma: null}}]                  │ │
+│ │ row_[**REDACTED**]                            ┆ 1970-01-01T00:00:00.000000001 ┆ 1970-01-01T00:00:00.000000001    ┆ [{object: {object: cube, color: {rgb: 128,64,32, hsv:    │ │
+│ │                                               ┆                               ┆                                  ┆ null, bgr: null}, gamma: null}}]                         │ │
 │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ │
-│ │ row_[**REDACTED**]                            ┆ 1970-01-01T00:00:00.000000002 ┆ 1970-01-01T00:00:00.000000002    ┆ [{object: {object: pyramid, gamma: null}}]               │ │
+│ │ row_[**REDACTED**]                            ┆ 1970-01-01T00:00:00.000000002 ┆ 1970-01-01T00:00:00.000000002    ┆ [{object: {object: pyramid, color: {rgb: null, hsv:      │ │
+│ │                                               ┆                               ┆                                  ┆ null, bgr: 0,255,0}, gamma: null}}]                      │ │
 │ └───────────────────────────────────────────────┴───────────────────────────────┴──────────────────────────────────┴──────────────────────────────────────────────────────────┘ │
 └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘

From 2fce708c9638b76c5a9841b16d7f51dc41408075 Mon Sep 17 00:00:00 2001
From: Emil Ernerfeldt 
Date: Thu, 12 Mar 2026 22:29:35 +0100
Subject: [PATCH 119/513] Update to latest egui/eframe

This removes the one second delay when switching to the native viewer on
Mac - BUT ONLY if the viewer has been occluded for more than one second.

 ## Changes:

https://github.com/emilk/egui/compare/9276778181619be4065715ab03d36ca0b318667d...9bc062c8eee17f154743a35145983f0e6f02b1c3

Source-Ref: 099e9d6d6b9ad1901d059a7a3fe6d5437c421a39
---
 Cargo.lock                                    | 25 +++++++++++--------
 .../re_arrow_ui/tests/snapshots/arrow_ui.png  |  4 +--
 ...print_panel_filter_active_above_origin.png |  4 +--
 ...rint_panel_filter_active_inside_origin.png |  4 +--
 ...int_panel_filter_active_outside_origin.png |  4 +--
 .../query-_path_to,_rig/empty.png             |  4 +--
 .../query-_path_to,_rig/multiple_proj.png     |  4 +--
 .../query-_path_to,_rig/non_root_origin.png   |  4 +--
 .../proj_with_placeholder.png                 |  4 +--
 .../query-_path_to,_rig/root_origin.png       |  4 +--
 .../query-_path_to,_rig/single_proj.png       |  4 +--
 .../query-_path_to,_rig/unknown_origin.png    |  4 +--
 .../query-_path_to_th/empty.png               |  4 +--
 .../query-_path_to_th/multiple_proj.png       |  4 +--
 .../query-_path_to_th/non_root_origin.png     |  4 +--
 .../proj_with_placeholder.png                 |  4 +--
 .../query-_path_to_th/root_origin.png         |  4 +--
 .../query-_path_to_th/single_proj.png         |  4 +--
 .../query-_path_to_th/unknown_origin.png      |  4 +--
 .../query-_to_the_/empty.png                  |  4 +--
 .../query-_to_the_/multiple_proj.png          |  4 +--
 .../query-_to_the_/non_root_origin.png        |  4 +--
 .../query-_to_the_/proj_with_placeholder.png  |  4 +--
 .../query-_to_the_/root_origin.png            |  4 +--
 .../query-_to_the_/single_proj.png            |  4 +--
 .../query-_to_the_/unknown_origin.png         |  4 +--
 .../query-ath,left/empty.png                  |  4 +--
 .../query-ath,left/multiple_proj.png          |  4 +--
 .../query-ath,left/non_root_origin.png        |  4 +--
 .../query-ath,left/proj_with_placeholder.png  |  4 +--
 .../query-ath,left/root_origin.png            |  4 +--
 .../query-ath,left/single_proj.png            |  4 +--
 .../query-ath,left/unknown_origin.png         |  4 +--
 .../view_structure_test/query-ath,t/empty.png |  4 +--
 .../query-ath,t/multiple_proj.png             |  4 +--
 .../query-ath,t/non_root_origin.png           |  4 +--
 .../query-ath,t/proj_with_placeholder.png     |  4 +--
 .../query-ath,t/root_origin.png               |  4 +--
 .../query-ath,t/single_proj.png               |  4 +--
 .../query-ath,t/unknown_origin.png            |  4 +--
 .../view_structure_test/query-path/empty.png  |  4 +--
 .../query-path/multiple_proj.png              |  4 +--
 .../query-path/non_root_origin.png            |  4 +--
 .../query-path/proj_with_placeholder.png      |  4 +--
 .../query-path/root_origin.png                |  4 +--
 .../query-path/single_proj.png                |  4 +--
 .../query-path/unknown_origin.png             |  4 +--
 .../query-to_the/empty.png                    |  4 +--
 .../query-to_the/multiple_proj.png            |  4 +--
 .../query-to_the/non_root_origin.png          |  4 +--
 .../query-to_the/proj_with_placeholder.png    |  4 +--
 .../query-to_the/root_origin.png              |  4 +--
 .../query-to_the/single_proj.png              |  4 +--
 .../query-to_the/unknown_origin.png           |  4 +--
 .../Plane3D_placeholder.png                   |  4 +--
 .../filter_ui_boolean_equals_false.png        |  2 +-
 .../filter_ui_boolean_equals_true.png         |  4 +--
 .../filter_ui_float_compares_none.png         |  2 +-
 .../tests/snapshots/filter_ui_int_compare.png |  4 +--
 .../snapshots/filter_ui_int_compare_none.png  |  4 +--
 ...ilter_ui_nullable_boolean_equals_false.png |  2 +-
 ...filter_ui_nullable_boolean_equals_null.png |  2 +-
 ...filter_ui_nullable_boolean_equals_true.png |  4 +--
 ...er_ui_nullable_boolean_not_equals_true.png |  2 +-
 .../snapshots/filter_ui_string_contains.png   |  2 +-
 .../filter_ui_string_contains_empty.png       |  4 +--
 .../filter_ui_string_starts_with.png          |  4 +--
 .../snapshots/filter_ui_timestamp_after.png   |  4 +--
 .../snapshots/filter_ui_timestamp_between.png |  4 +--
 .../filter_ui_timestamp_not_after.png         |  2 +-
 .../tests/snapshots/filter_wrapping.png       |  4 +--
 .../snapshots/header_tooltip_index_column.png |  4 +--
 ...eader_tooltip_index_column_with_extras.png |  4 +--
 ...tip_raw_field_user_and_sorbet_metadata.png |  4 +--
 ...d_user_and_sorbet_metadata_with_extras.png |  4 +--
 ...header_tooltip_raw_field_user_metadata.png |  4 +--
 ...ip_raw_field_user_metadata_with_extras.png |  4 +--
 .../popup_ui_boolean_equals_false.png         |  4 +--
 .../popup_ui_boolean_equals_true.png          |  4 +--
 .../snapshots/popup_ui_float_compares.png     |  4 +--
 ...popup_ui_nullable_boolean_equals_false.png |  4 +--
 .../popup_ui_nullable_boolean_equals_null.png |  4 +--
 .../popup_ui_nullable_boolean_equals_true.png |  4 +--
 ...up_ui_nullable_boolean_not_equals_true.png |  4 +--
 .../snapshots/popup_ui_string_starts_with.png |  4 +--
 .../tests/snapshots/test_ascending.png        |  4 +--
 .../snapshots/test_column_menu_button.png     |  4 +--
 .../tests/snapshots/test_descending.png       |  4 +--
 .../tests/snapshots/test_no_sort.png          |  4 +--
 .../snapshots/timestamp_filter_on_commit.png  |  4 +--
 ...ction_panel_component_hybrid_overwrite.png |  2 +-
 ...ction_panel_component_static_overwrite.png |  2 +-
 .../snapshots/selection_panel_recording.png   |  4 +--
 ...selection_panel_recording_hover_app_id.png |  4 +--
 .../selection_panel_view_entity_no_match.png  |  4 +--
 .../snapshots/focused_item_is_focused.png     |  4 +--
 .../snapshots/help_view_timeline_mac.png      |  4 +--
 .../snapshots/help_view_timeline_windows.png  |  4 +--
 .../tests/snapshots/time_panel_dense_data.png |  4 +--
 ...time_panel_filter_test_active_no_query.png |  4 +--
 .../time_panel_filter_test_active_query.png   |  4 +--
 .../time_panel_filter_test_inactive.png       |  4 +--
 .../time_panel_loading_unloaded_chunks.png    |  4 +--
 .../time_panel_only_unloaded_chunks.png       |  4 +--
 .../time_panel_partially_unloaded_chunks.png  |  4 +--
 .../various_entity_kinds_timeline_a_0.png     |  4 +--
 .../various_entity_kinds_timeline_a_5.png     |  4 +--
 ...y_kinds_timeline_a_9223372036854775807.png |  4 +--
 .../various_entity_kinds_timeline_b_0.png     |  4 +--
 .../various_entity_kinds_timeline_b_5.png     |  4 +--
 ...y_kinds_timeline_b_9223372036854775807.png |  4 +--
 .../various_filters-_path_to,_rig.png         |  4 +--
 .../snapshots/various_filters-_to_the.png     |  4 +--
 .../snapshots/various_filters-_to_the_.png    |  4 +--
 .../snapshots/various_filters-ath,left.png    |  4 +--
 .../tests/snapshots/various_filters-ath,t.png |  4 +--
 .../tests/snapshots/various_filters-none.png  |  4 +--
 .../tests/snapshots/various_filters-path.png  |  4 +--
 .../tests/snapshots/various_filters-t.png     |  4 +--
 .../snapshots/various_filters-to_the,oid.png  |  4 +--
 .../snapshots/various_filters-to_the.png      |  4 +--
 .../snapshots/various_filters-to_the_.png     |  4 +--
 .../tests/snapshots/various_filters-void.png  |  4 +--
 .../re_ui/tests/snapshots/alert_dark.png      |  4 +--
 .../re_ui/tests/snapshots/alert_light.png     |  4 +--
 .../re_ui/tests/snapshots/combo_item.png      |  4 +--
 .../re_ui/tests/snapshots/filter_widget.png   |  4 +--
 .../viewer/re_ui/tests/snapshots/help_Mac.png |  4 +--
 .../re_ui/tests/snapshots/help_Windows.png    |  4 +--
 .../relative_time_range_absolute_duration.png |  4 +--
 ...relative_time_range_absolute_timestamp.png |  4 +--
 ...ve_time_range_absolute_to_end_duration.png |  4 +--
 ...e_time_range_absolute_to_end_timestamp.png |  4 +--
 ...tive_time_range_around_cursor_duration.png |  4 +--
 ...ive_time_range_around_cursor_timestamp.png |  4 +--
 ...relative_time_range_at_cursor_duration.png |  4 +--
 ...elative_time_range_at_cursor_timestamp.png |  4 +--
 ...tive_time_range_cursor_to_end_duration.png |  4 +--
 ...ive_time_range_cursor_to_end_timestamp.png |  4 +--
 ..._time_range_start_to_absolute_duration.png |  4 +--
 ...time_range_start_to_absolute_timestamp.png |  4 +--
 ...ve_time_range_start_to_cursor_duration.png |  4 +--
 ...e_time_range_start_to_cursor_timestamp.png |  4 +--
 .../tests/snapshots/bar_chart_1d.png          |  4 +--
 .../help_view_bar_chart_view_mac.png          |  4 +--
 .../help_view_bar_chart_view_windows.png      |  4 +--
 .../help_view_dataframe_view_mac.png          |  4 +--
 .../help_view_dataframe_view_windows.png      |  4 +--
 .../tests/snapshots/null_timeline.png         |  4 +--
 .../unknown_timeline_selection_panel_ui.png   |  4 +--
 .../snapshots/help_view_graph_view_mac.png    |  4 +--
 .../help_view_graph_view_windows.png          |  4 +--
 .../snapshots/help_view_map_view_mac.png      |  4 +--
 .../snapshots/help_view_map_view_windows.png  |  4 +--
 .../annotations_hover_background.png          |  2 +-
 .../annotations_hover_rect_green.png          |  4 +--
 .../snapshots/annotations_hover_rect_red.png  |  4 +--
 .../annotations_hover_region_green.png        |  4 +--
 .../annotations_hover_region_red.png          |  4 +--
 .../tests/snapshots/help_view_2d_view_mac.png |  4 +--
 .../snapshots/help_view_2d_view_windows.png   |  4 +--
 .../tests/snapshots/help_view_3d_view_mac.png |  4 +--
 .../snapshots/help_view_3d_view_windows.png   |  4 +--
 .../snapshots/help_view_tensor_view_mac.png   |  4 +--
 .../help_view_tensor_view_windows.png         |  4 +--
 .../tests/snapshots/tensor_2d_t2.png          |  2 +-
 .../help_view_text_document_view_mac.png      |  4 +--
 .../help_view_text_document_view_windows.png  |  4 +--
 .../snapshots/help_view_text_log_view_mac.png |  4 +--
 .../help_view_text_log_view_windows.png       |  4 +--
 ...efaults_with_time_series_around_cursor.png |  4 +--
 ...nd_defaults_with_time_series_at_cursor.png |  4 +--
 ..._with_time_series_start_until_absolute.png |  4 +--
 .../bootstrapped_secondaries_full.png         |  4 +--
 .../bootstrapped_secondaries_partial.png      |  4 +--
 .../snapshots/explicit_component_mapping.png  |  4 +--
 .../explicit_component_mapping_nested.png     |  4 +--
 .../help_view_time_series_view_mac.png        |  4 +--
 .../help_view_time_series_view_windows.png    |  4 +--
 ...per_series_visibility_show_second_only.png |  2 +-
 .../per_series_visibility_splat_true.png      |  2 +-
 .../snapshots/transform3d_time_series.png     |  4 +--
 ...ble_time_range_around_cursor_view_data.png |  4 +--
 ...time_range_around_cursor_view_timeline.png |  4 +--
 ...e_range_start_until_absolute_view_data.png |  4 +--
 ...nge_start_until_absolute_view_timeline.png |  4 +--
 .../all_view_selection_uis/Dark/2D.png        |  4 +--
 .../all_view_selection_uis/Dark/3D.png        |  4 +--
 .../all_view_selection_uis/Dark/Dataframe.png |  2 +-
 .../all_view_selection_uis/Dark/Graph.png     |  4 +--
 .../all_view_selection_uis/Dark/Map.png       |  4 +--
 .../all_view_selection_uis/Dark/Tensor.png    |  4 +--
 .../Dark/TextDocument.png                     |  4 +--
 .../all_view_selection_uis/Dark/TextLog.png   |  2 +-
 .../Dark/TimeSeries.png                       |  4 +--
 .../all_view_selection_uis/Light/2D.png       |  4 +--
 .../all_view_selection_uis/Light/3D.png       |  4 +--
 .../Light/Dataframe.png                       |  2 +-
 .../all_view_selection_uis/Light/Graph.png    |  4 +--
 .../all_view_selection_uis/Light/Map.png      |  4 +--
 .../all_view_selection_uis/Light/Tensor.png   |  4 +--
 .../Light/TextDocument.png                    |  4 +--
 .../all_view_selection_uis/Light/TextLog.png  |  4 +--
 .../Light/TimeSeries.png                      |  4 +--
 .../blueprint_change_and_restore.png          |  4 +--
 .../blueprint_load_into_new_context_1.png     |  4 +--
 .../blueprint_load_into_new_context_2.png     |  4 +--
 .../snapshots/colormap_selector_closed.png    |  4 +--
 .../snapshots/colormap_selector_open.png      |  4 +--
 .../snapshots/menu_without_recording.png      |  4 +--
 .../snapshots/open_url_modal__invalid_url.png |  2 +-
 .../snapshots/open_url_modal__no_url.png      |  4 +--
 .../snapshots/open_url_modal__valid_url.png   |  2 +-
 .../tests/snapshots/settings_screen.png       |  4 +--
 .../share_modal__dataset_segment_url.png      |  4 +--
 ...l__dataset_segment_url_with_time_range.png |  4 +--
 .../snapshots/share_modal__server_url.png     |  2 +-
 ..._container_from_blueprint_panel_menu_1.png |  4 +--
 ..._container_from_blueprint_panel_menu_2.png |  4 +--
 ..._container_from_blueprint_panel_menu_3.png |  4 +--
 .../add_entity_to_view_bar_chart_1.png        |  4 +--
 .../add_entity_to_view_bar_chart_2.png        |  4 +--
 .../add_entity_to_view_bar_chart_3.png        |  4 +--
 .../add_entity_to_view_bar_chart_4.png        |  4 +--
 .../add_entity_to_view_bar_chart_5.png        |  4 +--
 .../add_entity_to_view_boxes2d_1.png          |  4 +--
 .../add_entity_to_view_boxes2d_2.png          |  4 +--
 .../add_entity_to_view_boxes2d_3.png          |  4 +--
 .../add_entity_to_view_boxes2d_4.png          |  4 +--
 .../add_entity_to_view_boxes2d_5.png          |  4 +--
 .../add_entity_to_view_boxes3d_1.png          |  4 +--
 .../add_entity_to_view_boxes3d_2.png          |  4 +--
 .../add_entity_to_view_boxes3d_3.png          |  4 +--
 .../add_entity_to_view_boxes3d_4.png          |  4 +--
 .../add_entity_to_view_boxes3d_5.png          |  4 +--
 .../snapshots/add_entity_to_view_tensor_1.png |  4 +--
 .../snapshots/add_entity_to_view_tensor_2.png |  4 +--
 .../snapshots/add_entity_to_view_tensor_3.png |  4 +--
 .../snapshots/add_entity_to_view_tensor_4.png |  4 +--
 .../snapshots/add_entity_to_view_tensor_5.png |  4 +--
 .../add_entity_to_view_text_log_1.png         |  4 +--
 .../add_entity_to_view_text_log_2.png         |  4 +--
 .../add_entity_to_view_text_log_3.png         |  4 +--
 .../add_entity_to_view_text_log_4.png         |  4 +--
 .../add_entity_to_view_text_log_5.png         |  4 +--
 .../tests/snapshots/add_visualizer_axes_1.png |  4 +--
 .../blueprint_tree_context_menu_02.png        |  4 +--
 .../snapshots/change_container_type_2.png     |  4 +--
 .../tests/snapshots/dataset_ui_empty_form.png |  4 +--
 .../tests/snapshots/dataset_ui_table.png      |  2 +-
 .../tests/snapshots/deselect_on_escape_3.png  |  4 +--
 .../drag_view_to_other_view_bottom_1.png      |  4 +--
 .../drag_view_to_other_view_bottom_2.png      |  4 +--
 .../drag_view_to_other_view_center_1.png      |  4 +--
 .../drag_view_to_other_view_center_2.png      |  4 +--
 .../drag_view_to_other_view_left_1.png        |  4 +--
 .../drag_view_to_other_view_left_2.png        |  4 +--
 .../drag_view_to_other_view_right_1.png       |  4 +--
 .../drag_view_to_other_view_right_2.png       |  4 +--
 .../drag_view_to_other_view_top_1.png         |  4 +--
 .../drag_view_to_other_view_top_2.png         |  4 +--
 .../drop_multiple_streams_to_view_1.png       |  4 +--
 .../drop_multiple_streams_to_view_2.png       |  4 +--
 .../drop_multiple_streams_to_view_3.png       |  4 +--
 .../drop_multiple_streams_to_view_4.png       |  4 +--
 .../drop_multiple_streams_to_view_5.png       |  4 +--
 .../drop_multiple_streams_to_view_6.png       |  4 +--
 .../tests/snapshots/drop_stream_to_view_1.png |  4 +--
 .../tests/snapshots/drop_stream_to_view_2.png |  4 +--
 .../tests/snapshots/drop_stream_to_view_3.png |  4 +--
 .../tests/snapshots/drop_stream_to_view_4.png |  4 +--
 .../tests/snapshots/drop_stream_to_view_5.png |  4 +--
 .../tests/snapshots/drop_stream_to_view_6.png |  4 +--
 .../snapshots/heuristics_mixed_all_root.png   |  4 +--
 .../snapshots/multi_container_deep_nested.png |  4 +--
 .../multi_container_drag_container_1.png      |  4 +--
 .../multi_container_drag_container_2.png      |  4 +--
 .../multi_container_drag_container_3.png      |  4 +--
 .../multi_container_drag_container_4.png      |  4 +--
 .../multi_container_drag_container_5.png      |  4 +--
 .../multi_container_drag_container_6.png      |  4 +--
 .../multi_container_drag_single_view_1.png    |  4 +--
 .../multi_container_drag_single_view_2.png    |  4 +--
 .../multi_container_drag_single_view_3.png    |  4 +--
 .../snapshots/multi_container_many_views.png  |  4 +--
 ...zer_instruction_errors_1_warnings_only.png |  4 +--
 ...struction_errors_1b_warnings_only_menu.png |  4 +--
 ...tion_errors_2_warnings_and_errors_menu.png |  4 +--
 ...rors_2b_warnings_and_errors_dataresult.png |  4 +--
 ...lizer_instruction_errors_3_errors_only.png |  4 +--
 ...instruction_errors_3b_errors_only_menu.png |  4 +--
 .../snapshots/resize_view_horizontal_1.png    |  4 +--
 .../snapshots/resize_view_horizontal_2.png    |  4 +--
 .../snapshots/resize_view_horizontal_3.png    |  4 +--
 .../snapshots/resize_view_vertical_1.png      |  4 +--
 .../snapshots/resize_view_vertical_2.png      |  4 +--
 .../snapshots/resize_view_vertical_3.png      |  4 +--
 .../snapshots/series_count_exceeds_max.png    |  4 +--
 .../tests/snapshots/source_component_1.png    |  4 +--
 .../tests/snapshots/source_component_2.png    |  4 +--
 .../tests/snapshots/source_component_3.png    |  4 +--
 .../tests/snapshots/source_component_4.png    |  4 +--
 .../tests/snapshots/source_component_5.png    |  4 +--
 .../tests/snapshots/source_component_6.png    |  4 +--
 .../tests/snapshots/source_component_7.png    |  4 +--
 .../tests/snapshots/source_component_8.png    |  4 +--
 .../snapshots/start_with_dataset_url.png      |  4 +--
 .../streams_context_single_select_3.png       |  4 +--
 .../snapshots/view_visualizers_1_initial.png  |  4 +--
 ...view_visualizers_2_plots_view_selected.png |  4 +--
 ...view_visualizers_3_other_view_selected.png |  4 +--
 .../view_visualizers_4_hover_sin_line.png     |  4 +--
 .../view_visualizers_5_after_hide.png         |  4 +--
 ...sualizers_6_hover_different_after_hide.png |  4 +--
 .../view_visualizers_add_1_view_selected.png  |  4 +--
 .../view_visualizers_add_2_popup_open.png     |  4 +--
 ...visualizers_add_multi_1_starting_state.png |  4 +--
 ...lizers_add_multi_2_selected_visualizer.png |  4 +--
 ...alizers_add_multi_3_removed_visualizer.png |  4 +--
 ...lizers_add_multi_4_view_selected_again.png |  4 +--
 ...iew_visualizers_add_multi_5_popup_open.png |  4 +--
 ...ew_visualizers_add_multi_6_first_added.png |  4 +--
 ...w_visualizers_add_multi_7_second_added.png |  4 +--
 .../view_visualizers_ctx_menu_1_open.png      |  4 +--
 ...view_visualizers_ctx_menu_2_after_hide.png |  4 +--
 ...iew_visualizers_ctx_menu_3_show_option.png |  4 +--
 ...view_visualizers_ctx_menu_4_after_show.png |  4 +--
 ...ew_visualizers_ctx_menu_5_after_remove.png |  4 +--
 ...visualizers_multi_scalar_view_selected.png |  4 +--
 329 files changed, 650 insertions(+), 647 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index dcd28c280e13..9910772bd12b 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3145,7 +3145,7 @@ dependencies = [
 [[package]]
 name = "ecolor"
 version = "0.33.3"
-source = "git+https://github.com/emilk/egui.git?branch=main#9276778181619be4065715ab03d36ca0b318667d"
+source = "git+https://github.com/emilk/egui.git?branch=main#14afefa2521d1baaf4fd02105eec2d3727a7ac36"
 dependencies = [
  "bytemuck",
  "color-hex",
@@ -3162,7 +3162,7 @@ checksum = "18aade80d5e09429040243ce1143ddc08a92d7a22820ac512610410a4dd5214f"
 [[package]]
 name = "eframe"
 version = "0.33.3"
-source = "git+https://github.com/emilk/egui.git?branch=main#9276778181619be4065715ab03d36ca0b318667d"
+source = "git+https://github.com/emilk/egui.git?branch=main#14afefa2521d1baaf4fd02105eec2d3727a7ac36"
 dependencies = [
  "ahash",
  "bytemuck",
@@ -3200,7 +3200,7 @@ dependencies = [
 [[package]]
 name = "egui"
 version = "0.33.3"
-source = "git+https://github.com/emilk/egui.git?branch=main#9276778181619be4065715ab03d36ca0b318667d"
+source = "git+https://github.com/emilk/egui.git?branch=main#14afefa2521d1baaf4fd02105eec2d3727a7ac36"
 dependencies = [
  "accesskit",
  "ahash",
@@ -3220,7 +3220,7 @@ dependencies = [
 [[package]]
 name = "egui-wgpu"
 version = "0.33.3"
-source = "git+https://github.com/emilk/egui.git?branch=main#9276778181619be4065715ab03d36ca0b318667d"
+source = "git+https://github.com/emilk/egui.git?branch=main#14afefa2521d1baaf4fd02105eec2d3727a7ac36"
 dependencies = [
  "ahash",
  "bytemuck",
@@ -3239,7 +3239,7 @@ dependencies = [
 [[package]]
 name = "egui-winit"
 version = "0.33.3"
-source = "git+https://github.com/emilk/egui.git?branch=main#9276778181619be4065715ab03d36ca0b318667d"
+source = "git+https://github.com/emilk/egui.git?branch=main#14afefa2521d1baaf4fd02105eec2d3727a7ac36"
 dependencies = [
  "accesskit_winit",
  "arboard",
@@ -3307,7 +3307,7 @@ dependencies = [
 [[package]]
 name = "egui_extras"
 version = "0.33.3"
-source = "git+https://github.com/emilk/egui.git?branch=main#9276778181619be4065715ab03d36ca0b318667d"
+source = "git+https://github.com/emilk/egui.git?branch=main#14afefa2521d1baaf4fd02105eec2d3727a7ac36"
 dependencies = [
  "ahash",
  "egui",
@@ -3324,7 +3324,7 @@ dependencies = [
 [[package]]
 name = "egui_glow"
 version = "0.33.3"
-source = "git+https://github.com/emilk/egui.git?branch=main#9276778181619be4065715ab03d36ca0b318667d"
+source = "git+https://github.com/emilk/egui.git?branch=main#14afefa2521d1baaf4fd02105eec2d3727a7ac36"
 dependencies = [
  "bytemuck",
  "egui",
@@ -3340,7 +3340,7 @@ dependencies = [
 [[package]]
 name = "egui_kittest"
 version = "0.33.3"
-source = "git+https://github.com/emilk/egui.git?branch=main#9276778181619be4065715ab03d36ca0b318667d"
+source = "git+https://github.com/emilk/egui.git?branch=main#14afefa2521d1baaf4fd02105eec2d3727a7ac36"
 dependencies = [
  "dify",
  "eframe",
@@ -3419,7 +3419,7 @@ checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
 [[package]]
 name = "emath"
 version = "0.33.3"
-source = "git+https://github.com/emilk/egui.git?branch=main#9276778181619be4065715ab03d36ca0b318667d"
+source = "git+https://github.com/emilk/egui.git?branch=main#14afefa2521d1baaf4fd02105eec2d3727a7ac36"
 dependencies = [
  "bytemuck",
  "serde",
@@ -3545,13 +3545,14 @@ dependencies = [
 [[package]]
 name = "epaint"
 version = "0.33.3"
-source = "git+https://github.com/emilk/egui.git?branch=main#9276778181619be4065715ab03d36ca0b318667d"
+source = "git+https://github.com/emilk/egui.git?branch=main#14afefa2521d1baaf4fd02105eec2d3727a7ac36"
 dependencies = [
  "ahash",
  "bytemuck",
  "ecolor",
  "emath",
  "epaint_default_fonts",
+ "font-types",
  "log",
  "nohash-hasher",
  "parking_lot",
@@ -3560,13 +3561,14 @@ dependencies = [
  "self_cell",
  "serde",
  "skrifa",
+ "smallvec",
  "vello_cpu",
 ]
 
 [[package]]
 name = "epaint_default_fonts"
 version = "0.33.3"
-source = "git+https://github.com/emilk/egui.git?branch=main#9276778181619be4065715ab03d36ca0b318667d"
+source = "git+https://github.com/emilk/egui.git?branch=main#14afefa2521d1baaf4fd02105eec2d3727a7ac36"
 
 [[package]]
 name = "equivalent"
@@ -3789,6 +3791,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b1e4d2d0cf79d38430cc9dc9aadec84774bff2e1ba30ae2bf6c16cfce9385a23"
 dependencies = [
  "bytemuck",
+ "serde",
 ]
 
 [[package]]
diff --git a/crates/viewer/re_arrow_ui/tests/snapshots/arrow_ui.png b/crates/viewer/re_arrow_ui/tests/snapshots/arrow_ui.png
index fcf8242aafe9..33e1fa7241f5 100644
--- a/crates/viewer/re_arrow_ui/tests/snapshots/arrow_ui.png
+++ b/crates/viewer/re_arrow_ui/tests/snapshots/arrow_ui.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:d835ac5a083622cacc5539f0b5c35ab9f4dff565a96ed71304fd65a7fbff7795
-size 52564
+oid sha256:dfe3b23639ac3892f4bcb98bf489a42bfc4ff29695928751a5efce2aa0fbdc41
+size 52265
diff --git a/crates/viewer/re_blueprint_tree/tests/snapshots/blueprint_panel_filter_active_above_origin.png b/crates/viewer/re_blueprint_tree/tests/snapshots/blueprint_panel_filter_active_above_origin.png
index 417ded330426..83ed1592cd7b 100644
--- a/crates/viewer/re_blueprint_tree/tests/snapshots/blueprint_panel_filter_active_above_origin.png
+++ b/crates/viewer/re_blueprint_tree/tests/snapshots/blueprint_panel_filter_active_above_origin.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:eb655df70bc196a3cd90c9abe533e75a55e697bdadb0a05680e5f8009778ef46
-size 24442
+oid sha256:b562e48510701c4463c3894ada75f761a5aa3fadbae01e8e0afd1a2d238d033f
+size 24456
diff --git a/crates/viewer/re_blueprint_tree/tests/snapshots/blueprint_panel_filter_active_inside_origin.png b/crates/viewer/re_blueprint_tree/tests/snapshots/blueprint_panel_filter_active_inside_origin.png
index 7e62bfa6d01e..b5fb023fbc74 100644
--- a/crates/viewer/re_blueprint_tree/tests/snapshots/blueprint_panel_filter_active_inside_origin.png
+++ b/crates/viewer/re_blueprint_tree/tests/snapshots/blueprint_panel_filter_active_inside_origin.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:1753760c24f74bc3e777ac51a2b0664017ff57d7cc4ce757b58f7e7094dcc7d9
-size 17704
+oid sha256:b027e3c4eda5090f565c6bfd8e381482f17753e15d0907ad5017c3297cabb6b2
+size 17743
diff --git a/crates/viewer/re_blueprint_tree/tests/snapshots/blueprint_panel_filter_active_outside_origin.png b/crates/viewer/re_blueprint_tree/tests/snapshots/blueprint_panel_filter_active_outside_origin.png
index a3146b886c2f..c9778793a7b6 100644
--- a/crates/viewer/re_blueprint_tree/tests/snapshots/blueprint_panel_filter_active_outside_origin.png
+++ b/crates/viewer/re_blueprint_tree/tests/snapshots/blueprint_panel_filter_active_outside_origin.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:f49f53d3e4218a698b0811cfafb2a0b1729c2c009d04498f58f2a7205cd04c5d
-size 22714
+oid sha256:6dae701b68d3c5d01dbfb18617600cf20170a2d1813300660da29805d41a39ed
+size 22745
diff --git a/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-_path_to,_rig/empty.png b/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-_path_to,_rig/empty.png
index 1b48dd989263..2c4740310a12 100644
--- a/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-_path_to,_rig/empty.png
+++ b/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-_path_to,_rig/empty.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:a797634a630e2a5e4a8743a1ac0618f919b14cdec1788e90d82fd51fbc51717a
-size 11095
+oid sha256:1744382bebd667bf019ac8277aae50bd40ebf367a735d42e9080579aedd4d4a0
+size 11087
diff --git a/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-_path_to,_rig/multiple_proj.png b/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-_path_to,_rig/multiple_proj.png
index 1b48dd989263..2c4740310a12 100644
--- a/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-_path_to,_rig/multiple_proj.png
+++ b/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-_path_to,_rig/multiple_proj.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:a797634a630e2a5e4a8743a1ac0618f919b14cdec1788e90d82fd51fbc51717a
-size 11095
+oid sha256:1744382bebd667bf019ac8277aae50bd40ebf367a735d42e9080579aedd4d4a0
+size 11087
diff --git a/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-_path_to,_rig/non_root_origin.png b/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-_path_to,_rig/non_root_origin.png
index 1b48dd989263..2c4740310a12 100644
--- a/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-_path_to,_rig/non_root_origin.png
+++ b/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-_path_to,_rig/non_root_origin.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:a797634a630e2a5e4a8743a1ac0618f919b14cdec1788e90d82fd51fbc51717a
-size 11095
+oid sha256:1744382bebd667bf019ac8277aae50bd40ebf367a735d42e9080579aedd4d4a0
+size 11087
diff --git a/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-_path_to,_rig/proj_with_placeholder.png b/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-_path_to,_rig/proj_with_placeholder.png
index 1b48dd989263..2c4740310a12 100644
--- a/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-_path_to,_rig/proj_with_placeholder.png
+++ b/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-_path_to,_rig/proj_with_placeholder.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:a797634a630e2a5e4a8743a1ac0618f919b14cdec1788e90d82fd51fbc51717a
-size 11095
+oid sha256:1744382bebd667bf019ac8277aae50bd40ebf367a735d42e9080579aedd4d4a0
+size 11087
diff --git a/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-_path_to,_rig/root_origin.png b/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-_path_to,_rig/root_origin.png
index 3a72da62a4a7..4aef43c8dba0 100644
--- a/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-_path_to,_rig/root_origin.png
+++ b/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-_path_to,_rig/root_origin.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:f4dea924f11e5f1f19c622cff46d611cebcc25a3bd0b3d6d74f62f0d6c32f000
-size 20364
+oid sha256:451b098aeda56b546ba6cb0e08050e1b5c31f080950a61baf1fac6095a02c9f4
+size 20356
diff --git a/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-_path_to,_rig/single_proj.png b/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-_path_to,_rig/single_proj.png
index 1b48dd989263..2c4740310a12 100644
--- a/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-_path_to,_rig/single_proj.png
+++ b/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-_path_to,_rig/single_proj.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:a797634a630e2a5e4a8743a1ac0618f919b14cdec1788e90d82fd51fbc51717a
-size 11095
+oid sha256:1744382bebd667bf019ac8277aae50bd40ebf367a735d42e9080579aedd4d4a0
+size 11087
diff --git a/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-_path_to,_rig/unknown_origin.png b/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-_path_to,_rig/unknown_origin.png
index 1b48dd989263..2c4740310a12 100644
--- a/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-_path_to,_rig/unknown_origin.png
+++ b/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-_path_to,_rig/unknown_origin.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:a797634a630e2a5e4a8743a1ac0618f919b14cdec1788e90d82fd51fbc51717a
-size 11095
+oid sha256:1744382bebd667bf019ac8277aae50bd40ebf367a735d42e9080579aedd4d4a0
+size 11087
diff --git a/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-_path_to_th/empty.png b/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-_path_to_th/empty.png
index ccbdaa94ca8f..acb7c13ba8de 100644
--- a/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-_path_to_th/empty.png
+++ b/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-_path_to_th/empty.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:7c2f41e4b69cb698fa2e6aa75aef4578324e48a781d8ed320fac61b095f1cdb3
-size 10935
+oid sha256:06bde302c796fbf3d9075da1254ed5f44a853f45057024ffee27d63076052660
+size 10953
diff --git a/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-_path_to_th/multiple_proj.png b/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-_path_to_th/multiple_proj.png
index ccbdaa94ca8f..acb7c13ba8de 100644
--- a/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-_path_to_th/multiple_proj.png
+++ b/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-_path_to_th/multiple_proj.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:7c2f41e4b69cb698fa2e6aa75aef4578324e48a781d8ed320fac61b095f1cdb3
-size 10935
+oid sha256:06bde302c796fbf3d9075da1254ed5f44a853f45057024ffee27d63076052660
+size 10953
diff --git a/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-_path_to_th/non_root_origin.png b/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-_path_to_th/non_root_origin.png
index ccbdaa94ca8f..acb7c13ba8de 100644
--- a/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-_path_to_th/non_root_origin.png
+++ b/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-_path_to_th/non_root_origin.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:7c2f41e4b69cb698fa2e6aa75aef4578324e48a781d8ed320fac61b095f1cdb3
-size 10935
+oid sha256:06bde302c796fbf3d9075da1254ed5f44a853f45057024ffee27d63076052660
+size 10953
diff --git a/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-_path_to_th/proj_with_placeholder.png b/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-_path_to_th/proj_with_placeholder.png
index ccbdaa94ca8f..acb7c13ba8de 100644
--- a/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-_path_to_th/proj_with_placeholder.png
+++ b/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-_path_to_th/proj_with_placeholder.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:7c2f41e4b69cb698fa2e6aa75aef4578324e48a781d8ed320fac61b095f1cdb3
-size 10935
+oid sha256:06bde302c796fbf3d9075da1254ed5f44a853f45057024ffee27d63076052660
+size 10953
diff --git a/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-_path_to_th/root_origin.png b/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-_path_to_th/root_origin.png
index e7b58da68593..e19a78099aaf 100644
--- a/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-_path_to_th/root_origin.png
+++ b/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-_path_to_th/root_origin.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:233026689dd215dfffa5b18451e5ecc11fad571a32fb8a3dee4f58c858b528d8
-size 21023
+oid sha256:8c892a2b8e490a1f33e5220582ce278c320f410b00eec042fa57303159eafcce
+size 21041
diff --git a/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-_path_to_th/single_proj.png b/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-_path_to_th/single_proj.png
index ccbdaa94ca8f..acb7c13ba8de 100644
--- a/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-_path_to_th/single_proj.png
+++ b/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-_path_to_th/single_proj.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:7c2f41e4b69cb698fa2e6aa75aef4578324e48a781d8ed320fac61b095f1cdb3
-size 10935
+oid sha256:06bde302c796fbf3d9075da1254ed5f44a853f45057024ffee27d63076052660
+size 10953
diff --git a/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-_path_to_th/unknown_origin.png b/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-_path_to_th/unknown_origin.png
index ccbdaa94ca8f..acb7c13ba8de 100644
--- a/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-_path_to_th/unknown_origin.png
+++ b/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-_path_to_th/unknown_origin.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:7c2f41e4b69cb698fa2e6aa75aef4578324e48a781d8ed320fac61b095f1cdb3
-size 10935
+oid sha256:06bde302c796fbf3d9075da1254ed5f44a853f45057024ffee27d63076052660
+size 10953
diff --git a/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-_to_the_/empty.png b/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-_to_the_/empty.png
index ef5b1f3e965f..939ff8428e81 100644
--- a/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-_to_the_/empty.png
+++ b/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-_to_the_/empty.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:34a810b22ff49525a0180ad0e03eb2e27bca9f1e796163390fe063be3baf31ac
-size 10519
+oid sha256:3e225837cb937a4be97d8eb3404ad1bf54a108cc60dd73cc7d5df1f500bdae59
+size 10550
diff --git a/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-_to_the_/multiple_proj.png b/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-_to_the_/multiple_proj.png
index ef5b1f3e965f..939ff8428e81 100644
--- a/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-_to_the_/multiple_proj.png
+++ b/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-_to_the_/multiple_proj.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:34a810b22ff49525a0180ad0e03eb2e27bca9f1e796163390fe063be3baf31ac
-size 10519
+oid sha256:3e225837cb937a4be97d8eb3404ad1bf54a108cc60dd73cc7d5df1f500bdae59
+size 10550
diff --git a/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-_to_the_/non_root_origin.png b/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-_to_the_/non_root_origin.png
index 579ff9f7e91f..4a57df74f65e 100644
--- a/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-_to_the_/non_root_origin.png
+++ b/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-_to_the_/non_root_origin.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:5eed564745f5e5da6daa09f66cef773e4788b0a542d01ea983b288c6b55567dd
-size 19456
+oid sha256:23196a79fde519a7f71f9ae5c10772ee2843b358b1bcb395a1a5f0299827571b
+size 19487
diff --git a/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-_to_the_/proj_with_placeholder.png b/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-_to_the_/proj_with_placeholder.png
index 579ff9f7e91f..4a57df74f65e 100644
--- a/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-_to_the_/proj_with_placeholder.png
+++ b/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-_to_the_/proj_with_placeholder.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:5eed564745f5e5da6daa09f66cef773e4788b0a542d01ea983b288c6b55567dd
-size 19456
+oid sha256:23196a79fde519a7f71f9ae5c10772ee2843b358b1bcb395a1a5f0299827571b
+size 19487
diff --git a/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-_to_the_/root_origin.png b/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-_to_the_/root_origin.png
index cc8f23056498..4ad953478c44 100644
--- a/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-_to_the_/root_origin.png
+++ b/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-_to_the_/root_origin.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:4883965f5229ebd065a4890ea3b3ab0636958f51d465e6b7c3f3c09a2a7b531d
-size 20659
+oid sha256:618aa1c13aa63de63ea83ac1e854bec4ca383237c79355e6aa005e7c2e20881c
+size 20691
diff --git a/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-_to_the_/single_proj.png b/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-_to_the_/single_proj.png
index e56b58bb438e..95a0c53eefc7 100644
--- a/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-_to_the_/single_proj.png
+++ b/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-_to_the_/single_proj.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:be134610d0f1473fbef8a4a067e31268e59db5d009b8f18735fd4d2d57710e97
-size 21905
+oid sha256:cf9f1e123ed07410d010742293f646ac651ef5b2db3c49c820edeb4a45b458df
+size 21936
diff --git a/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-_to_the_/unknown_origin.png b/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-_to_the_/unknown_origin.png
index ef5b1f3e965f..939ff8428e81 100644
--- a/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-_to_the_/unknown_origin.png
+++ b/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-_to_the_/unknown_origin.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:34a810b22ff49525a0180ad0e03eb2e27bca9f1e796163390fe063be3baf31ac
-size 10519
+oid sha256:3e225837cb937a4be97d8eb3404ad1bf54a108cc60dd73cc7d5df1f500bdae59
+size 10550
diff --git a/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-ath,left/empty.png b/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-ath,left/empty.png
index 599925f8cf88..58f9e36ffad3 100644
--- a/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-ath,left/empty.png
+++ b/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-ath,left/empty.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:125cd07b54554d57d5deefc5a0dcf619ddaf4d8ab6118a0d9c26a8902bb30733
-size 10166
+oid sha256:85b13d8cab353a2be4c3d9626439d0e9125681f3881b8425738e4ba2b181a708
+size 10196
diff --git a/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-ath,left/multiple_proj.png b/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-ath,left/multiple_proj.png
index 599925f8cf88..58f9e36ffad3 100644
--- a/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-ath,left/multiple_proj.png
+++ b/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-ath,left/multiple_proj.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:125cd07b54554d57d5deefc5a0dcf619ddaf4d8ab6118a0d9c26a8902bb30733
-size 10166
+oid sha256:85b13d8cab353a2be4c3d9626439d0e9125681f3881b8425738e4ba2b181a708
+size 10196
diff --git a/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-ath,left/non_root_origin.png b/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-ath,left/non_root_origin.png
index 599925f8cf88..58f9e36ffad3 100644
--- a/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-ath,left/non_root_origin.png
+++ b/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-ath,left/non_root_origin.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:125cd07b54554d57d5deefc5a0dcf619ddaf4d8ab6118a0d9c26a8902bb30733
-size 10166
+oid sha256:85b13d8cab353a2be4c3d9626439d0e9125681f3881b8425738e4ba2b181a708
+size 10196
diff --git a/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-ath,left/proj_with_placeholder.png b/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-ath,left/proj_with_placeholder.png
index 599925f8cf88..58f9e36ffad3 100644
--- a/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-ath,left/proj_with_placeholder.png
+++ b/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-ath,left/proj_with_placeholder.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:125cd07b54554d57d5deefc5a0dcf619ddaf4d8ab6118a0d9c26a8902bb30733
-size 10166
+oid sha256:85b13d8cab353a2be4c3d9626439d0e9125681f3881b8425738e4ba2b181a708
+size 10196
diff --git a/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-ath,left/root_origin.png b/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-ath,left/root_origin.png
index 2af6d03262d4..b27e6bc46753 100644
--- a/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-ath,left/root_origin.png
+++ b/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-ath,left/root_origin.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:0b653087088eddf798e6b64437a3e4a097b6b57bcd8622f658be1e2ff008c076
-size 19319
+oid sha256:5aed8ae2e229f00da11be1b066f7c005c81f5b8f134f2c472413c0ddf4540150
+size 19349
diff --git a/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-ath,left/single_proj.png b/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-ath,left/single_proj.png
index 599925f8cf88..58f9e36ffad3 100644
--- a/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-ath,left/single_proj.png
+++ b/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-ath,left/single_proj.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:125cd07b54554d57d5deefc5a0dcf619ddaf4d8ab6118a0d9c26a8902bb30733
-size 10166
+oid sha256:85b13d8cab353a2be4c3d9626439d0e9125681f3881b8425738e4ba2b181a708
+size 10196
diff --git a/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-ath,left/unknown_origin.png b/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-ath,left/unknown_origin.png
index 599925f8cf88..58f9e36ffad3 100644
--- a/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-ath,left/unknown_origin.png
+++ b/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-ath,left/unknown_origin.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:125cd07b54554d57d5deefc5a0dcf619ddaf4d8ab6118a0d9c26a8902bb30733
-size 10166
+oid sha256:85b13d8cab353a2be4c3d9626439d0e9125681f3881b8425738e4ba2b181a708
+size 10196
diff --git a/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-ath,t/empty.png b/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-ath,t/empty.png
index 332f666a8734..d8bba5b73bbf 100644
--- a/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-ath,t/empty.png
+++ b/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-ath,t/empty.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:3c4abc1e74c8c3f458d5a1a10b9b6e95e50470e057153d6400ce823967361f42
-size 9816
+oid sha256:fab400e5c0114db8bac8af295e12839bda5e8f41a68cff653dad6138bef4213d
+size 9845
diff --git a/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-ath,t/multiple_proj.png b/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-ath,t/multiple_proj.png
index 332f666a8734..d8bba5b73bbf 100644
--- a/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-ath,t/multiple_proj.png
+++ b/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-ath,t/multiple_proj.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:3c4abc1e74c8c3f458d5a1a10b9b6e95e50470e057153d6400ce823967361f42
-size 9816
+oid sha256:fab400e5c0114db8bac8af295e12839bda5e8f41a68cff653dad6138bef4213d
+size 9845
diff --git a/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-ath,t/non_root_origin.png b/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-ath,t/non_root_origin.png
index 332f666a8734..d8bba5b73bbf 100644
--- a/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-ath,t/non_root_origin.png
+++ b/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-ath,t/non_root_origin.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:3c4abc1e74c8c3f458d5a1a10b9b6e95e50470e057153d6400ce823967361f42
-size 9816
+oid sha256:fab400e5c0114db8bac8af295e12839bda5e8f41a68cff653dad6138bef4213d
+size 9845
diff --git a/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-ath,t/proj_with_placeholder.png b/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-ath,t/proj_with_placeholder.png
index f6e6489f1908..a4e328c780ad 100644
--- a/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-ath,t/proj_with_placeholder.png
+++ b/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-ath,t/proj_with_placeholder.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:c2c09b315a3bee6b7303b62b8e4b6f95549bf609872c44d308fbee15b1209ca2
-size 25261
+oid sha256:db21692baef255300f2d6ea2a428ab26fc0161b0d71fdfde7e197011aaf2856b
+size 25301
diff --git a/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-ath,t/root_origin.png b/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-ath,t/root_origin.png
index f267b771a0ce..9c14c0f3300c 100644
--- a/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-ath,t/root_origin.png
+++ b/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-ath,t/root_origin.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:919cd4cf9d724360ffef7205c63e20d2bccf538d9cd0a625b0e3448cc08c05e2
-size 24793
+oid sha256:5a351620a0d6c3aefd3e307f968140ef0d451127cf54db6af266222bbb98ea86
+size 24832
diff --git a/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-ath,t/single_proj.png b/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-ath,t/single_proj.png
index 332f666a8734..d8bba5b73bbf 100644
--- a/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-ath,t/single_proj.png
+++ b/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-ath,t/single_proj.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:3c4abc1e74c8c3f458d5a1a10b9b6e95e50470e057153d6400ce823967361f42
-size 9816
+oid sha256:fab400e5c0114db8bac8af295e12839bda5e8f41a68cff653dad6138bef4213d
+size 9845
diff --git a/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-ath,t/unknown_origin.png b/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-ath,t/unknown_origin.png
index c3d6850f2a41..6a7d758e5216 100644
--- a/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-ath,t/unknown_origin.png
+++ b/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-ath,t/unknown_origin.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:1f45c25a6c4baf1bc979a5a70494ebc6e96f14afd121ecb71b04f08c83217e52
-size 17754
+oid sha256:944d7dfc690789571f332d5f2cbcf8592d8021ef02978b10af468639348aa009
+size 17794
diff --git a/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-path/empty.png b/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-path/empty.png
index 95108aa69cb7..aedf859b60c1 100644
--- a/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-path/empty.png
+++ b/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-path/empty.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:a179e9255a8700efaa096b9fde383d8ffa053660df3eec34fa23fcbda02af025
-size 9983
+oid sha256:23b36d630ff773356113f02734c34a4db1f99146eb9ca773198a7280e4cb73d0
+size 9997
diff --git a/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-path/multiple_proj.png b/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-path/multiple_proj.png
index 95108aa69cb7..aedf859b60c1 100644
--- a/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-path/multiple_proj.png
+++ b/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-path/multiple_proj.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:a179e9255a8700efaa096b9fde383d8ffa053660df3eec34fa23fcbda02af025
-size 9983
+oid sha256:23b36d630ff773356113f02734c34a4db1f99146eb9ca773198a7280e4cb73d0
+size 9997
diff --git a/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-path/non_root_origin.png b/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-path/non_root_origin.png
index 95108aa69cb7..aedf859b60c1 100644
--- a/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-path/non_root_origin.png
+++ b/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-path/non_root_origin.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:a179e9255a8700efaa096b9fde383d8ffa053660df3eec34fa23fcbda02af025
-size 9983
+oid sha256:23b36d630ff773356113f02734c34a4db1f99146eb9ca773198a7280e4cb73d0
+size 9997
diff --git a/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-path/proj_with_placeholder.png b/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-path/proj_with_placeholder.png
index 148d37ea52ba..71280ab910a6 100644
--- a/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-path/proj_with_placeholder.png
+++ b/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-path/proj_with_placeholder.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:f5848a467bc3c071dd57e4bd1cb7d0aef51e8a37a6116a45f11d0c2d51494c9f
-size 25314
+oid sha256:f4ac67c2bf3f302ee6ff206d4905a2f8d1f4894eddd47503fc8a3522aaa9a114
+size 25328
diff --git a/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-path/root_origin.png b/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-path/root_origin.png
index 8e6e9f3142e1..315bec9569f6 100644
--- a/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-path/root_origin.png
+++ b/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-path/root_origin.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:915d3179b296747e24c807df50d61cd0448b3fd133572bb0f097054559dc16d1
-size 24787
+oid sha256:4ad3c42adbf2844e1a3d1968c9f101db11e793813a7f5b985fceac9a5e820e5f
+size 24801
diff --git a/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-path/single_proj.png b/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-path/single_proj.png
index 95108aa69cb7..aedf859b60c1 100644
--- a/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-path/single_proj.png
+++ b/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-path/single_proj.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:a179e9255a8700efaa096b9fde383d8ffa053660df3eec34fa23fcbda02af025
-size 9983
+oid sha256:23b36d630ff773356113f02734c34a4db1f99146eb9ca773198a7280e4cb73d0
+size 9997
diff --git a/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-path/unknown_origin.png b/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-path/unknown_origin.png
index c7879efaec28..79e43e7d46b8 100644
--- a/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-path/unknown_origin.png
+++ b/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-path/unknown_origin.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:fe3bb7be80a2268d0197d15970c7e2d455aef9c571079df147371b349684f5f9
-size 17959
+oid sha256:b0a13ea36d57eeac7870f9b319f9db91d6c9175c5a8933da7a2bc23dc7eb9ab6
+size 17973
diff --git a/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-to_the/empty.png b/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-to_the/empty.png
index 399ca83ae89b..33229fda2747 100644
--- a/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-to_the/empty.png
+++ b/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-to_the/empty.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:9883dc80066c75e61ee50100a7c539b18cb9a58ef1ba6cdcf2ece3847e2d05e5
-size 10194
+oid sha256:69d0f76fcaba00d82f1ddc6568a3005454c22a3b39e19d1470f0c371124c1710
+size 10213
diff --git a/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-to_the/multiple_proj.png b/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-to_the/multiple_proj.png
index 399ca83ae89b..33229fda2747 100644
--- a/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-to_the/multiple_proj.png
+++ b/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-to_the/multiple_proj.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:9883dc80066c75e61ee50100a7c539b18cb9a58ef1ba6cdcf2ece3847e2d05e5
-size 10194
+oid sha256:69d0f76fcaba00d82f1ddc6568a3005454c22a3b39e19d1470f0c371124c1710
+size 10213
diff --git a/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-to_the/non_root_origin.png b/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-to_the/non_root_origin.png
index 9c2c33eb1e17..ca805f7acec6 100644
--- a/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-to_the/non_root_origin.png
+++ b/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-to_the/non_root_origin.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:42061c8a51c9e54e643b13a3fcbdee1b1d29265156d5a1e79c766ce6c4f86a50
-size 19130
+oid sha256:9b4272bdc56c4ec44077d6d66f4a4908f508efced424aa9133a96437e1557425
+size 19149
diff --git a/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-to_the/proj_with_placeholder.png b/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-to_the/proj_with_placeholder.png
index fc06fa22aa11..1c962e86136d 100644
--- a/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-to_the/proj_with_placeholder.png
+++ b/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-to_the/proj_with_placeholder.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:419f37180630319f567948931f7de2ed92957dc860a009792fa6162d301b0230
-size 26493
+oid sha256:8205e6b51ecc7ca945e6333b4587dcfd7b439c414b3c9f51b0abf10b80e653b5
+size 26512
diff --git a/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-to_the/root_origin.png b/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-to_the/root_origin.png
index bde804a58e6a..cac01264536d 100644
--- a/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-to_the/root_origin.png
+++ b/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-to_the/root_origin.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:a8a57f31ba1fd56e86554c592fa7a834112ea710ff038ca7c8341031a9fd6692
-size 23314
+oid sha256:a7b5a95642dcf7167c0fd8b42a0c3f25c010bffd7a7110b38d3a56bc84773d7f
+size 23333
diff --git a/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-to_the/single_proj.png b/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-to_the/single_proj.png
index 5e14bf3b826b..c613ed03f33d 100644
--- a/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-to_the/single_proj.png
+++ b/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-to_the/single_proj.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:5cc29f6c07cab8971f4c5d01aa41f67290f705f3150a4c8fa81425c8d4260500
-size 21579
+oid sha256:619b4db43fd4e56008dfc582315122ec53dbdb9f7ace8a7a19a55301dd4e9d24
+size 21598
diff --git a/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-to_the/unknown_origin.png b/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-to_the/unknown_origin.png
index 399ca83ae89b..33229fda2747 100644
--- a/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-to_the/unknown_origin.png
+++ b/crates/viewer/re_blueprint_tree/tests/snapshots/view_structure_test/query-to_the/unknown_origin.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:9883dc80066c75e61ee50100a7c539b18cb9a58ef1ba6cdcf2ece3847e2d05e5
-size 10194
+oid sha256:69d0f76fcaba00d82f1ddc6568a3005454c22a3b39e19d1470f0c371124c1710
+size 10213
diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Plane3D_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Plane3D_placeholder.png
index 1c0cfdbbba41..147af624c53b 100644
--- a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Plane3D_placeholder.png
+++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/Plane3D_placeholder.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:6aa0aec453fc7f9b3ae6e5f08520be88ef5b537ed356bf11147b5ef99a5bc3a8
-size 4351
+oid sha256:4c8afe5fbb5a228bf665a708391c066b52bbc1594d00765f943bb962dce3f9f7
+size 4358
diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_ui_boolean_equals_false.png b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_ui_boolean_equals_false.png
index e27b33461a56..b0d7f4500e73 100644
--- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_ui_boolean_equals_false.png
+++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_ui_boolean_equals_false.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:3720d51e79a170aa67e2302b1e8e1a226c00a74bcc6f35b54e3a0a1a9d7c7389
+oid sha256:f1bcb413caf4d4c723488d5d3ee52fd61747793447de05626f12770a779cbc0e
 size 4519
diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_ui_boolean_equals_true.png b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_ui_boolean_equals_true.png
index 21fede462f3b..df2a881ca16a 100644
--- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_ui_boolean_equals_true.png
+++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_ui_boolean_equals_true.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:4b9d3918022f6261d0ebf36aef2a9731f6d3acb6c1ba5be799d8c12815ac65f3
-size 4300
+oid sha256:7e10757c71a5418279a3bbcc8b7a156f7d584e25a7005cd60da29c7bd6a74dea
+size 4301
diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_ui_float_compares_none.png b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_ui_float_compares_none.png
index 719ec5f85308..978d06731d4d 100644
--- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_ui_float_compares_none.png
+++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_ui_float_compares_none.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:f5bf78d4d9642881c85093c26b466f07eb9666b9b3f7b8ed911850304f54d1ae
+oid sha256:33f5519c46dfd900abe3d1db8486ca5d10bfb4afabb58ce17ba8315d382bb1fb
 size 3924
diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_ui_int_compare.png b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_ui_int_compare.png
index 084e01d1121f..fbcce66330fd 100644
--- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_ui_int_compare.png
+++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_ui_int_compare.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:04bf8826f10c03e3c2775249dd7e7d285696f0cb946bba57b2de43033156f7e8
-size 4276
+oid sha256:505c92699adafab6dabc9987d6dd06103b2696e5db2c31eb21ffe9cfa3560ad4
+size 4277
diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_ui_int_compare_none.png b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_ui_int_compare_none.png
index 06a15fd4054c..074df42d09c1 100644
--- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_ui_int_compare_none.png
+++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_ui_int_compare_none.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:55b94e1acb6e4363092befa50ca75cc1dee94b207abe7f3c3b0adeabf1115d02
-size 3864
+oid sha256:0f9d7c191898d3ea3bb720da557b91dd54c48f446938d77a66c5fc58f5adcbf4
+size 3865
diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_ui_nullable_boolean_equals_false.png b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_ui_nullable_boolean_equals_false.png
index e27b33461a56..b0d7f4500e73 100644
--- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_ui_nullable_boolean_equals_false.png
+++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_ui_nullable_boolean_equals_false.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:3720d51e79a170aa67e2302b1e8e1a226c00a74bcc6f35b54e3a0a1a9d7c7389
+oid sha256:f1bcb413caf4d4c723488d5d3ee52fd61747793447de05626f12770a779cbc0e
 size 4519
diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_ui_nullable_boolean_equals_null.png b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_ui_nullable_boolean_equals_null.png
index 6e763d5997d1..5ceb4182b8ee 100644
--- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_ui_nullable_boolean_equals_null.png
+++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_ui_nullable_boolean_equals_null.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:85e237269b785726a8735c3fb0f8fc59e0d47e379caea34376ebceaa42c32f8e
+oid sha256:d53f24d5618043c02a9a7c94b3707fe1166bb53d08f458599a5d403c932c4b75
 size 4131
diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_ui_nullable_boolean_equals_true.png b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_ui_nullable_boolean_equals_true.png
index 21fede462f3b..df2a881ca16a 100644
--- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_ui_nullable_boolean_equals_true.png
+++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_ui_nullable_boolean_equals_true.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:4b9d3918022f6261d0ebf36aef2a9731f6d3acb6c1ba5be799d8c12815ac65f3
-size 4300
+oid sha256:7e10757c71a5418279a3bbcc8b7a156f7d584e25a7005cd60da29c7bd6a74dea
+size 4301
diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_ui_nullable_boolean_not_equals_true.png b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_ui_nullable_boolean_not_equals_true.png
index 5e21bb51bd45..5ce214fdf36c 100644
--- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_ui_nullable_boolean_not_equals_true.png
+++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_ui_nullable_boolean_not_equals_true.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:869063c1984ba967867de532f6dfa9346e651550dc7d3de776e7243bdda8e586
+oid sha256:9d66977223ee3396cf7aedd32e384c4154a8bfc5e0d965e18421310640a0c2d1
 size 4728
diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_ui_string_contains.png b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_ui_string_contains.png
index 3df89248dd06..2acaf1625a4e 100644
--- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_ui_string_contains.png
+++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_ui_string_contains.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:bb9b04d0ab9c107a718e9a25ab603971b38d34f58155165b348fdad31a9e9b39
+oid sha256:6e5a71d2cedbfe8485b5d046d4a33becd228870584a20b5f1822ebface51bfd8
 size 5434
diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_ui_string_contains_empty.png b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_ui_string_contains_empty.png
index ee3b6268a0cd..bfb18c91f1de 100644
--- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_ui_string_contains_empty.png
+++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_ui_string_contains_empty.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:0044c142a44ed107bf20809b2d4b25a4b8dcd2ad3129477e9b4aca8874725ebd
-size 4753
+oid sha256:2a93e292c56a88f37bf3a6d96df127b30d35c34da9a0a4e9d057d6462b884646
+size 4752
diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_ui_string_starts_with.png b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_ui_string_starts_with.png
index d56f43f8083e..f53c947aaa36 100644
--- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_ui_string_starts_with.png
+++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_ui_string_starts_with.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:7221c58bf4e772da35d1e075c9bc7a86a7503ad6ccfcbcef375a3fc913608d47
-size 5720
+oid sha256:3f74c1f1a97fff0f333ca5601fa4ba96ad0609e4f5a571802f00dcc904e8e65b
+size 5719
diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_ui_timestamp_after.png b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_ui_timestamp_after.png
index e84e9cd70f0c..a4a5c018b876 100644
--- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_ui_timestamp_after.png
+++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_ui_timestamp_after.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:b3b129d8d029d70a59ed3a13faf9b74faf13cb2f52af07a21bf415aedaded276
-size 7333
+oid sha256:7a3905bd7a53d226a908e8661655e22ae99c3ad809a95bb2fcbd97e26380e59c
+size 7334
diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_ui_timestamp_between.png b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_ui_timestamp_between.png
index d2900b47dfe0..1ac9496b0082 100644
--- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_ui_timestamp_between.png
+++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_ui_timestamp_between.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:685c84f1be05dd10e36fccbef27ea16a438113d1390595a61c5d53e8dddd7246
-size 11531
+oid sha256:e5f64c533b49f85a52eb01d5ee608bfee1681fa2344bb01e2a2433406849b81a
+size 11532
diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_ui_timestamp_not_after.png b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_ui_timestamp_not_after.png
index 979b70286941..068e3b9d9845 100644
--- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_ui_timestamp_not_after.png
+++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_ui_timestamp_not_after.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:3b20fed5d8bc500fdc89173df9d5a82af04fd4bff879a4179fd0812a54cedd7c
+oid sha256:211643e4e76eed494da73e4d6b67758e44c4871d7405bdc213cbd18ca3f6d14e
 size 7783
diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_wrapping.png b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_wrapping.png
index e8ec80a217bd..4656000997ad 100644
--- a/crates/viewer/re_dataframe_ui/tests/snapshots/filter_wrapping.png
+++ b/crates/viewer/re_dataframe_ui/tests/snapshots/filter_wrapping.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:619bfd4d5403e245a9deba61c925e5a670aca00ac004c6176179c3e3714f7d9c
-size 32446
+oid sha256:f4320fb2c5219b62cb1e1d0023850c77ec7e6c3bc57db43dcc4ccb09b1cc0d61
+size 32447
diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/header_tooltip_index_column.png b/crates/viewer/re_dataframe_ui/tests/snapshots/header_tooltip_index_column.png
index 5ddd542cda83..bc520185472c 100644
--- a/crates/viewer/re_dataframe_ui/tests/snapshots/header_tooltip_index_column.png
+++ b/crates/viewer/re_dataframe_ui/tests/snapshots/header_tooltip_index_column.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:bd9d58e6ca6e319ada0d27c6101b0038dabe0722e90034523d57d9063e6e1afe
-size 24023
+oid sha256:2adafeb07e28f0e89fe7dcdc37fc55e735b3d66180215701ebdc3710c80e0b9d
+size 24024
diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/header_tooltip_index_column_with_extras.png b/crates/viewer/re_dataframe_ui/tests/snapshots/header_tooltip_index_column_with_extras.png
index fe8c1fd1ec3e..dfe4dcbcc560 100644
--- a/crates/viewer/re_dataframe_ui/tests/snapshots/header_tooltip_index_column_with_extras.png
+++ b/crates/viewer/re_dataframe_ui/tests/snapshots/header_tooltip_index_column_with_extras.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:bf15837bbb2705e6816ae44aa0f0f3db6141d7fa636fcdd820862176e0be2b16
-size 53723
+oid sha256:056a470790f1cbde0d0377c96ecee45c38f2d3af8e567f6e33d57dd55e418d63
+size 53724
diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/header_tooltip_raw_field_user_and_sorbet_metadata.png b/crates/viewer/re_dataframe_ui/tests/snapshots/header_tooltip_raw_field_user_and_sorbet_metadata.png
index e91a613b5522..7dbf6c641358 100644
--- a/crates/viewer/re_dataframe_ui/tests/snapshots/header_tooltip_raw_field_user_and_sorbet_metadata.png
+++ b/crates/viewer/re_dataframe_ui/tests/snapshots/header_tooltip_raw_field_user_and_sorbet_metadata.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:7f186e084b5bd450c641f0bbfa01cefdc88b2c7e6e797f0b818d7c7f1c793ff9
-size 33559
+oid sha256:e962830c709251032dbd402c08644bc3a10128135856e448cd0c3db4eb6a45cb
+size 33557
diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/header_tooltip_raw_field_user_and_sorbet_metadata_with_extras.png b/crates/viewer/re_dataframe_ui/tests/snapshots/header_tooltip_raw_field_user_and_sorbet_metadata_with_extras.png
index 9f0380524b39..8f881e262239 100644
--- a/crates/viewer/re_dataframe_ui/tests/snapshots/header_tooltip_raw_field_user_and_sorbet_metadata_with_extras.png
+++ b/crates/viewer/re_dataframe_ui/tests/snapshots/header_tooltip_raw_field_user_and_sorbet_metadata_with_extras.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:163dfca4b1d0a1bd895c359dd1d5d671cc43683cfd3a662074c9aeaf01c8ffbe
-size 70073
+oid sha256:2d934ea68fcf94d1eded7dfed9964c5e7131347505a36f1a517673f323e43b11
+size 70066
diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/header_tooltip_raw_field_user_metadata.png b/crates/viewer/re_dataframe_ui/tests/snapshots/header_tooltip_raw_field_user_metadata.png
index d9d21d6f51cf..35997c76fc98 100644
--- a/crates/viewer/re_dataframe_ui/tests/snapshots/header_tooltip_raw_field_user_metadata.png
+++ b/crates/viewer/re_dataframe_ui/tests/snapshots/header_tooltip_raw_field_user_metadata.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:5bfb6bbce5a66ca47d7f149e711b221259c527dfbf004c8b51f67bd658a6be78
-size 32105
+oid sha256:6704409ecfb6840ea6319193d99e5e7481a8e4d7c230d45d9cec12a47a6299b7
+size 32102
diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/header_tooltip_raw_field_user_metadata_with_extras.png b/crates/viewer/re_dataframe_ui/tests/snapshots/header_tooltip_raw_field_user_metadata_with_extras.png
index 7a7715c51c32..d073f163f703 100644
--- a/crates/viewer/re_dataframe_ui/tests/snapshots/header_tooltip_raw_field_user_metadata_with_extras.png
+++ b/crates/viewer/re_dataframe_ui/tests/snapshots/header_tooltip_raw_field_user_metadata_with_extras.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:200a50daebe825306922343ce16475c79995ef4d4efd3982608eca02fb7f5e26
-size 59974
+oid sha256:bdd1b12c9f78ba6f8dc6469f87e2ef2116df454f41377422c03a32ed93aad824
+size 59964
diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/popup_ui_boolean_equals_false.png b/crates/viewer/re_dataframe_ui/tests/snapshots/popup_ui_boolean_equals_false.png
index 5917a3411ac2..b12d19d94cd8 100644
--- a/crates/viewer/re_dataframe_ui/tests/snapshots/popup_ui_boolean_equals_false.png
+++ b/crates/viewer/re_dataframe_ui/tests/snapshots/popup_ui_boolean_equals_false.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:4438ea356d262224e008d79a80d239631a8687bde87c61137c403a10f254c929
-size 13005
+oid sha256:b6c1edb843f132d09eb1d5da98084b60f64c1c78acf40ad18be926c8b22b9064
+size 13003
diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/popup_ui_boolean_equals_true.png b/crates/viewer/re_dataframe_ui/tests/snapshots/popup_ui_boolean_equals_true.png
index 5d090879dbad..c82e2bb428c2 100644
--- a/crates/viewer/re_dataframe_ui/tests/snapshots/popup_ui_boolean_equals_true.png
+++ b/crates/viewer/re_dataframe_ui/tests/snapshots/popup_ui_boolean_equals_true.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:20ff41eebff554205feb72814aef3c1aefda09d4438fb479c082932865bffed6
-size 13002
+oid sha256:96aab659ce7401fac7399558be2dca90d1f714e7fff0e93542971af614f8054b
+size 12999
diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/popup_ui_float_compares.png b/crates/viewer/re_dataframe_ui/tests/snapshots/popup_ui_float_compares.png
index eb480e66ecee..6e831dd27fae 100644
--- a/crates/viewer/re_dataframe_ui/tests/snapshots/popup_ui_float_compares.png
+++ b/crates/viewer/re_dataframe_ui/tests/snapshots/popup_ui_float_compares.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:b497546c1dccb724a2487bfae0afa28a9c4f2d2cf1cbeed98a5753554bd0d17b
-size 12365
+oid sha256:cfd227bdeb4e0d2def6fe8483dbb9725767a3484a2274c3ababc9452ae90164c
+size 12400
diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/popup_ui_nullable_boolean_equals_false.png b/crates/viewer/re_dataframe_ui/tests/snapshots/popup_ui_nullable_boolean_equals_false.png
index 1cff8b51df3c..5e18b2cdd530 100644
--- a/crates/viewer/re_dataframe_ui/tests/snapshots/popup_ui_nullable_boolean_equals_false.png
+++ b/crates/viewer/re_dataframe_ui/tests/snapshots/popup_ui_nullable_boolean_equals_false.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:67ef4c93b7f79ff0f7aeb26cb53d0cb6143bd376b4f27d98bac64af51dbf3af2
-size 13820
+oid sha256:e7070b02ea77e47c123fcc38b0f593c6d75ff7b7c129becad9e83ae1a54a7c8a
+size 13819
diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/popup_ui_nullable_boolean_equals_null.png b/crates/viewer/re_dataframe_ui/tests/snapshots/popup_ui_nullable_boolean_equals_null.png
index 73004564e6bb..ec75609e9dd9 100644
--- a/crates/viewer/re_dataframe_ui/tests/snapshots/popup_ui_nullable_boolean_equals_null.png
+++ b/crates/viewer/re_dataframe_ui/tests/snapshots/popup_ui_nullable_boolean_equals_null.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:a8796e1e9f563740598706638b1ede9330fc5a8ee78eb26b2d3080c5cdb38785
-size 13805
+oid sha256:9da0647539ddfa7494206634d74dee67205b4ff99f428a5d4a687dd8e7e28242
+size 13804
diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/popup_ui_nullable_boolean_equals_true.png b/crates/viewer/re_dataframe_ui/tests/snapshots/popup_ui_nullable_boolean_equals_true.png
index 06f0ec71dc8a..f43768d85993 100644
--- a/crates/viewer/re_dataframe_ui/tests/snapshots/popup_ui_nullable_boolean_equals_true.png
+++ b/crates/viewer/re_dataframe_ui/tests/snapshots/popup_ui_nullable_boolean_equals_true.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:7492f0aeb30fe9632ab1a52e48eed23a9888ac06a89dc59e1f1603b84f6c2236
-size 13817
+oid sha256:9846b2d99e7a52ed815dbd4117a86a89cb9492d4a523423d5f65d65facdb7cf6
+size 13816
diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/popup_ui_nullable_boolean_not_equals_true.png b/crates/viewer/re_dataframe_ui/tests/snapshots/popup_ui_nullable_boolean_not_equals_true.png
index f08148789aa7..89bb0cb827ec 100644
--- a/crates/viewer/re_dataframe_ui/tests/snapshots/popup_ui_nullable_boolean_not_equals_true.png
+++ b/crates/viewer/re_dataframe_ui/tests/snapshots/popup_ui_nullable_boolean_not_equals_true.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:154af6029dd6bd971583b8c66e5e77d5fdf7cb1dc303d2ac1f023b486c54bfd0
-size 14187
+oid sha256:f64b6729fb3419749397635c8ad854f7fbc8b93ff1e92c343ba9cba749723c47
+size 14194
diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/popup_ui_string_starts_with.png b/crates/viewer/re_dataframe_ui/tests/snapshots/popup_ui_string_starts_with.png
index d934d68613b0..849ffcd5fe84 100644
--- a/crates/viewer/re_dataframe_ui/tests/snapshots/popup_ui_string_starts_with.png
+++ b/crates/viewer/re_dataframe_ui/tests/snapshots/popup_ui_string_starts_with.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:d58f8f6c55ef58db3ccb5fa4f3dce97cddc218220a43fbf1c153cce53352223e
-size 13656
+oid sha256:bbce412012ff754bfe097daec331b76f48eb2a8a84ff74f3f07c1028c35d27f1
+size 13648
diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/test_ascending.png b/crates/viewer/re_dataframe_ui/tests/snapshots/test_ascending.png
index 9bb2a61532a8..4785948ef238 100644
--- a/crates/viewer/re_dataframe_ui/tests/snapshots/test_ascending.png
+++ b/crates/viewer/re_dataframe_ui/tests/snapshots/test_ascending.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:330c9e710e35873f06740cf3a696ec2f7e951d03db8e739b624194ebc4fd58f7
-size 18262
+oid sha256:e8ebac8d4e8a55555faa363a7765825ffe289b8e797f9f0a9768252a822992b1
+size 18261
diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/test_column_menu_button.png b/crates/viewer/re_dataframe_ui/tests/snapshots/test_column_menu_button.png
index c9eac52fef10..140a01b55aeb 100644
--- a/crates/viewer/re_dataframe_ui/tests/snapshots/test_column_menu_button.png
+++ b/crates/viewer/re_dataframe_ui/tests/snapshots/test_column_menu_button.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:c73ad99b33f45f6d536ba27719d4bb9dcddfd9150f812bf6228fb5e8560c0a81
-size 26917
+oid sha256:d90a98b4f426ecbd880a739645639e9c72288672f06021d4efc757ee65281619
+size 26794
diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/test_descending.png b/crates/viewer/re_dataframe_ui/tests/snapshots/test_descending.png
index 9ee44ba79b77..ab162c560cca 100644
--- a/crates/viewer/re_dataframe_ui/tests/snapshots/test_descending.png
+++ b/crates/viewer/re_dataframe_ui/tests/snapshots/test_descending.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:f387b2dc59a20afcd5f86dff8367da9ae11b4b08e42b72a3aa22f82314882c18
-size 18551
+oid sha256:4c3e79af15eaaf564b9a135814df43936d6cd69297b8e51623041b57ad684fe0
+size 18550
diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/test_no_sort.png b/crates/viewer/re_dataframe_ui/tests/snapshots/test_no_sort.png
index 1f0b2b43d882..1932b4c034a2 100644
--- a/crates/viewer/re_dataframe_ui/tests/snapshots/test_no_sort.png
+++ b/crates/viewer/re_dataframe_ui/tests/snapshots/test_no_sort.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:ede2a8c126d63f8795ec90e74e9baa965fbfe824bf7d76d8971cc74791ad1ffb
-size 17193
+oid sha256:dfd190326b3fa74cb42f560480e47e5808e3b66f97e82361f508ccecb7dae538
+size 17194
diff --git a/crates/viewer/re_dataframe_ui/tests/snapshots/timestamp_filter_on_commit.png b/crates/viewer/re_dataframe_ui/tests/snapshots/timestamp_filter_on_commit.png
index 7b8a01491c7e..4aa8013a82d6 100644
--- a/crates/viewer/re_dataframe_ui/tests/snapshots/timestamp_filter_on_commit.png
+++ b/crates/viewer/re_dataframe_ui/tests/snapshots/timestamp_filter_on_commit.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:47bdf92289a01798e63ce27010394e7143070d828b6022908205c3f5b0115326
-size 39927
+oid sha256:b0f8cf23146d468254a852973eb52945993edd8292fad4423906d7c43a601fa5
+size 39716
diff --git a/crates/viewer/re_selection_panel/tests/snapshots/selection_panel_component_hybrid_overwrite.png b/crates/viewer/re_selection_panel/tests/snapshots/selection_panel_component_hybrid_overwrite.png
index 428915205d83..b05d165e7629 100644
--- a/crates/viewer/re_selection_panel/tests/snapshots/selection_panel_component_hybrid_overwrite.png
+++ b/crates/viewer/re_selection_panel/tests/snapshots/selection_panel_component_hybrid_overwrite.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:6f9f280446c05ec277f8c03f04e58eb0dee73044e04141e8a3ae47f87f250178
+oid sha256:8de1ef8393995c0ddd1663ba28ed88eb50db1eb95db3cf4fe0a572bbae9ea45f
 size 39064
diff --git a/crates/viewer/re_selection_panel/tests/snapshots/selection_panel_component_static_overwrite.png b/crates/viewer/re_selection_panel/tests/snapshots/selection_panel_component_static_overwrite.png
index f89df6045465..1c9dab84aed0 100644
--- a/crates/viewer/re_selection_panel/tests/snapshots/selection_panel_component_static_overwrite.png
+++ b/crates/viewer/re_selection_panel/tests/snapshots/selection_panel_component_static_overwrite.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:117c55beb98742ed9ea4cfc1a8473ef0ea45c77893146abb54f361e3f30f5c48
+oid sha256:52123f212d160445d4f773d95f50c8fb3ffc3a6fcb448134c53da14e2db1632c
 size 36743
diff --git a/crates/viewer/re_selection_panel/tests/snapshots/selection_panel_recording.png b/crates/viewer/re_selection_panel/tests/snapshots/selection_panel_recording.png
index b4943c24317d..26b1d879deba 100644
--- a/crates/viewer/re_selection_panel/tests/snapshots/selection_panel_recording.png
+++ b/crates/viewer/re_selection_panel/tests/snapshots/selection_panel_recording.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:6fd627a4f8bf1732338c267faf20357f99907007aed8f3064b763ae65ace7da4
-size 43876
+oid sha256:586483f9c6a4920575d4b16861e858154490e3c9fc12a641c2d9d417bcd6a1a3
+size 43872
diff --git a/crates/viewer/re_selection_panel/tests/snapshots/selection_panel_recording_hover_app_id.png b/crates/viewer/re_selection_panel/tests/snapshots/selection_panel_recording_hover_app_id.png
index 67fb7f1fc7fb..9bb9784d4764 100644
--- a/crates/viewer/re_selection_panel/tests/snapshots/selection_panel_recording_hover_app_id.png
+++ b/crates/viewer/re_selection_panel/tests/snapshots/selection_panel_recording_hover_app_id.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:9d015aa4c9c2d1aa0858ef57bf420a187d298ad7cc34e0828aa0e77ed3ee51ac
-size 53880
+oid sha256:17b755397bd88670e400760d1dd98d202b072afae19463759a2a69e9e82ee556
+size 53853
diff --git a/crates/viewer/re_selection_panel/tests/snapshots/selection_panel_view_entity_no_match.png b/crates/viewer/re_selection_panel/tests/snapshots/selection_panel_view_entity_no_match.png
index e667a2aa4d20..5af5070dbc91 100644
--- a/crates/viewer/re_selection_panel/tests/snapshots/selection_panel_view_entity_no_match.png
+++ b/crates/viewer/re_selection_panel/tests/snapshots/selection_panel_view_entity_no_match.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:2de6871a5c98eb56e2a0711c91ac5f6817ad2858f1081307411750afd860851f
-size 33065
+oid sha256:c69721616dd9313f08c0989fcc305174596598fa73f5d75f480c84fb638604bf
+size 33068
diff --git a/crates/viewer/re_time_panel/tests/snapshots/focused_item_is_focused.png b/crates/viewer/re_time_panel/tests/snapshots/focused_item_is_focused.png
index 10e1fa27a1fd..05211e29b713 100644
--- a/crates/viewer/re_time_panel/tests/snapshots/focused_item_is_focused.png
+++ b/crates/viewer/re_time_panel/tests/snapshots/focused_item_is_focused.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:f768d41ba62bc22c0d799b0208b83d3fd409e879b94f714b871d43a40f3baf59
-size 24777
+oid sha256:44a19a7d64d263ceb1312d94799bd5ed5953963acf69b884f78d71590a40a298
+size 24787
diff --git a/crates/viewer/re_time_panel/tests/snapshots/help_view_timeline_mac.png b/crates/viewer/re_time_panel/tests/snapshots/help_view_timeline_mac.png
index 17f07509e282..b15ad6526622 100644
--- a/crates/viewer/re_time_panel/tests/snapshots/help_view_timeline_mac.png
+++ b/crates/viewer/re_time_panel/tests/snapshots/help_view_timeline_mac.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:bf7fca2936ff2ee17da1829c2a4782a30cfebcfc99d306ca0f03bbb914ec94fb
-size 25482
+oid sha256:b1ea46a0405c4f9a5c8ec5fa51e9a11e4df0d5d7df4f066bba7096a26b40a1c1
+size 25255
diff --git a/crates/viewer/re_time_panel/tests/snapshots/help_view_timeline_windows.png b/crates/viewer/re_time_panel/tests/snapshots/help_view_timeline_windows.png
index 2e6d94274f38..91896652a1b4 100644
--- a/crates/viewer/re_time_panel/tests/snapshots/help_view_timeline_windows.png
+++ b/crates/viewer/re_time_panel/tests/snapshots/help_view_timeline_windows.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:a683de66193abd8c6e79e102d52f36c0274e107c67e0db9f5d1744360ea9312b
-size 25844
+oid sha256:0ac8b69dfceb91a4e49c468196b06654ec6618ee19a25fd8da196ce1fd5925e5
+size 25628
diff --git a/crates/viewer/re_time_panel/tests/snapshots/time_panel_dense_data.png b/crates/viewer/re_time_panel/tests/snapshots/time_panel_dense_data.png
index d100ffe98308..5f2b80aada81 100644
--- a/crates/viewer/re_time_panel/tests/snapshots/time_panel_dense_data.png
+++ b/crates/viewer/re_time_panel/tests/snapshots/time_panel_dense_data.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:617d84d0f28a551cc12f336ef686bf2be32db1120d8b4238a6a759e5665ee6d9
-size 30307
+oid sha256:694b8c6c3b022de75c82b31036a82dea534ac6ba949a02aff95224c98dae75ae
+size 30320
diff --git a/crates/viewer/re_time_panel/tests/snapshots/time_panel_filter_test_active_no_query.png b/crates/viewer/re_time_panel/tests/snapshots/time_panel_filter_test_active_no_query.png
index fd796751c17e..633e05749463 100644
--- a/crates/viewer/re_time_panel/tests/snapshots/time_panel_filter_test_active_no_query.png
+++ b/crates/viewer/re_time_panel/tests/snapshots/time_panel_filter_test_active_no_query.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:1d43cdba58075837c5b63f9f2b0797da8c0eb1553d37a62d3e8851479dba043d
-size 31531
+oid sha256:b69508fe9d580e1fde504a85fc01474c6550268c7407710d0cd0df59d4f2e441
+size 31541
diff --git a/crates/viewer/re_time_panel/tests/snapshots/time_panel_filter_test_active_query.png b/crates/viewer/re_time_panel/tests/snapshots/time_panel_filter_test_active_query.png
index d18d482fa6b0..2fed29706edd 100644
--- a/crates/viewer/re_time_panel/tests/snapshots/time_panel_filter_test_active_query.png
+++ b/crates/viewer/re_time_panel/tests/snapshots/time_panel_filter_test_active_query.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:d8b365cd1abfc080936235304ee534639f0981e901896fd1fe62065de917839e
-size 23539
+oid sha256:a4c79fa4719f1a46864b8da03dd81edf575894f11b791bf932e19946fa611099
+size 23560
diff --git a/crates/viewer/re_time_panel/tests/snapshots/time_panel_filter_test_inactive.png b/crates/viewer/re_time_panel/tests/snapshots/time_panel_filter_test_inactive.png
index db1bb2d0cfa5..1add17957c93 100644
--- a/crates/viewer/re_time_panel/tests/snapshots/time_panel_filter_test_inactive.png
+++ b/crates/viewer/re_time_panel/tests/snapshots/time_panel_filter_test_inactive.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:abed993aec2529929410bf0faccefef701c689059715d97ee704b4d06ae1ce3c
-size 29359
+oid sha256:05470c4e2ffa38fc77e2cd0c0b98bf42b95af6e19df35434bdefe59863b6aef2
+size 29369
diff --git a/crates/viewer/re_time_panel/tests/snapshots/time_panel_loading_unloaded_chunks.png b/crates/viewer/re_time_panel/tests/snapshots/time_panel_loading_unloaded_chunks.png
index e3e6d57bf4b3..d61d670b3a72 100644
--- a/crates/viewer/re_time_panel/tests/snapshots/time_panel_loading_unloaded_chunks.png
+++ b/crates/viewer/re_time_panel/tests/snapshots/time_panel_loading_unloaded_chunks.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:01ba7dab40da7ff1d12cf321466258d9f8bb6ac9a36eeed27995067ecce6c3a9
-size 34721
+oid sha256:155b8462ed6a0ce549175bae8103626b9ad9f79044c3ed51f752a19b1426dc97
+size 34555
diff --git a/crates/viewer/re_time_panel/tests/snapshots/time_panel_only_unloaded_chunks.png b/crates/viewer/re_time_panel/tests/snapshots/time_panel_only_unloaded_chunks.png
index f4a7edb2223b..9282326ea97b 100644
--- a/crates/viewer/re_time_panel/tests/snapshots/time_panel_only_unloaded_chunks.png
+++ b/crates/viewer/re_time_panel/tests/snapshots/time_panel_only_unloaded_chunks.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:008403a2800a96968d8fb45f52012caede998075e197f3b6b7c0413639746fcd
-size 33506
+oid sha256:a4c8a5d1f2b020373dc64fb1a62781492ec224ff23630212c601d6ab1ad50e86
+size 33551
diff --git a/crates/viewer/re_time_panel/tests/snapshots/time_panel_partially_unloaded_chunks.png b/crates/viewer/re_time_panel/tests/snapshots/time_panel_partially_unloaded_chunks.png
index b7701684bc01..468f4fd1f30c 100644
--- a/crates/viewer/re_time_panel/tests/snapshots/time_panel_partially_unloaded_chunks.png
+++ b/crates/viewer/re_time_panel/tests/snapshots/time_panel_partially_unloaded_chunks.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:5353a24c759b4fc447c066283e4e3ab6ded5ba807205696ed832c4ebdacb2c39
-size 34667
+oid sha256:7ded2817a215499e16d0bf1a2109a06f1e10047bbb29ca771497c4fc9910ff38
+size 34605
diff --git a/crates/viewer/re_time_panel/tests/snapshots/various_entity_kinds_timeline_a_0.png b/crates/viewer/re_time_panel/tests/snapshots/various_entity_kinds_timeline_a_0.png
index 9fb41d7f6ad0..a3abc0868380 100644
--- a/crates/viewer/re_time_panel/tests/snapshots/various_entity_kinds_timeline_a_0.png
+++ b/crates/viewer/re_time_panel/tests/snapshots/various_entity_kinds_timeline_a_0.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:4fa6b5ee6d7cff02d80b35e05aefebb01a956944602c0a3c378343e1887ef477
-size 129318
+oid sha256:d24afc3a101bc39b5ce409df1fcf89b01f31177290ccc6229404368d792ee030
+size 130191
diff --git a/crates/viewer/re_time_panel/tests/snapshots/various_entity_kinds_timeline_a_5.png b/crates/viewer/re_time_panel/tests/snapshots/various_entity_kinds_timeline_a_5.png
index c4ebc07a41f0..76d9fa643ba1 100644
--- a/crates/viewer/re_time_panel/tests/snapshots/various_entity_kinds_timeline_a_5.png
+++ b/crates/viewer/re_time_panel/tests/snapshots/various_entity_kinds_timeline_a_5.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:dd57303c3a0415f4107ea6c50bc9120c692f23ad1549525a799f3bf7be67e94b
-size 130944
+oid sha256:7223c2443703860f45b576ea753eaebf1fd76ff641df1b4d48dd9da8b131dd56
+size 131673
diff --git a/crates/viewer/re_time_panel/tests/snapshots/various_entity_kinds_timeline_a_9223372036854775807.png b/crates/viewer/re_time_panel/tests/snapshots/various_entity_kinds_timeline_a_9223372036854775807.png
index c658d350f9f1..72a8abd6e494 100644
--- a/crates/viewer/re_time_panel/tests/snapshots/various_entity_kinds_timeline_a_9223372036854775807.png
+++ b/crates/viewer/re_time_panel/tests/snapshots/various_entity_kinds_timeline_a_9223372036854775807.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:2c9f26e11451d537eb516d1407af52a4dcef8311bb05c82de9bee0ec7255b380
-size 132908
+oid sha256:076d06cccf79fe4b6693941eb59c6b9a71c3a762b12d549b52568216fb6ceba3
+size 133624
diff --git a/crates/viewer/re_time_panel/tests/snapshots/various_entity_kinds_timeline_b_0.png b/crates/viewer/re_time_panel/tests/snapshots/various_entity_kinds_timeline_b_0.png
index 2272268fa1ea..e94027e6da84 100644
--- a/crates/viewer/re_time_panel/tests/snapshots/various_entity_kinds_timeline_b_0.png
+++ b/crates/viewer/re_time_panel/tests/snapshots/various_entity_kinds_timeline_b_0.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:7081b844dc82ed8a8440b2901350b449d57d6863ed418fc1882c7327ea31885c
-size 128040
+oid sha256:f5fa68989ffe84e6cff6274d6d7e8d4d227cf85bd764decd6cd0e9ff66828cd7
+size 132900
diff --git a/crates/viewer/re_time_panel/tests/snapshots/various_entity_kinds_timeline_b_5.png b/crates/viewer/re_time_panel/tests/snapshots/various_entity_kinds_timeline_b_5.png
index bf5cc7fb47dc..2d784efd2e6f 100644
--- a/crates/viewer/re_time_panel/tests/snapshots/various_entity_kinds_timeline_b_5.png
+++ b/crates/viewer/re_time_panel/tests/snapshots/various_entity_kinds_timeline_b_5.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:1a50f4b438063f57236334077ccced84bfdb23cc659da9e0a8497511b1c30180
-size 129696
+oid sha256:13a71b52f11ac7d6c434c4e12c09fe5d51ef3593b0ee675b1f6501a4cb1fd9bf
+size 134245
diff --git a/crates/viewer/re_time_panel/tests/snapshots/various_entity_kinds_timeline_b_9223372036854775807.png b/crates/viewer/re_time_panel/tests/snapshots/various_entity_kinds_timeline_b_9223372036854775807.png
index fd49de288c35..bfab4344b1f7 100644
--- a/crates/viewer/re_time_panel/tests/snapshots/various_entity_kinds_timeline_b_9223372036854775807.png
+++ b/crates/viewer/re_time_panel/tests/snapshots/various_entity_kinds_timeline_b_9223372036854775807.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:a2f078548758e6362fd47f89a21024a16b64a1f68dce0e541ee3ebbac5ce25c5
-size 131725
+oid sha256:2c801ea4c052e9410414bc835424918ff7d8f6d4c96e7891ce4942ef30f0dfd4
+size 136419
diff --git a/crates/viewer/re_time_panel/tests/snapshots/various_filters-_path_to,_rig.png b/crates/viewer/re_time_panel/tests/snapshots/various_filters-_path_to,_rig.png
index 82c947e857a3..d1a89b81ac7d 100644
--- a/crates/viewer/re_time_panel/tests/snapshots/various_filters-_path_to,_rig.png
+++ b/crates/viewer/re_time_panel/tests/snapshots/various_filters-_path_to,_rig.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:ebbba2f59fd178ca074b73c9167beae1ae4b647deeb6b7ff10a763e9599a5e5f
-size 27745
+oid sha256:11fbf96a067cbae56c1d41a40cad14b4ae133585f0f8d5062bf638b2f77911f7
+size 27729
diff --git a/crates/viewer/re_time_panel/tests/snapshots/various_filters-_to_the.png b/crates/viewer/re_time_panel/tests/snapshots/various_filters-_to_the.png
index 45ed33eed102..dbc16c6fffce 100644
--- a/crates/viewer/re_time_panel/tests/snapshots/various_filters-_to_the.png
+++ b/crates/viewer/re_time_panel/tests/snapshots/various_filters-_to_the.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:f928a0bee0e3a49b23cf9c60eeedf61c07f90a7ac876f3be2a8600be07b665ab
-size 28031
+oid sha256:34d48f6a591e732169363371502ee9fb54dfb0a8e6c6512f334349be5967e403
+size 28017
diff --git a/crates/viewer/re_time_panel/tests/snapshots/various_filters-_to_the_.png b/crates/viewer/re_time_panel/tests/snapshots/various_filters-_to_the_.png
index 693c53fe108e..ebb49c3a68bd 100644
--- a/crates/viewer/re_time_panel/tests/snapshots/various_filters-_to_the_.png
+++ b/crates/viewer/re_time_panel/tests/snapshots/various_filters-_to_the_.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:8b7d6b72267d5b9e29d2745c7f5f231190bb3d790770f5bd9513b74366bfd011
-size 28133
+oid sha256:7e0be8212e6e96e0cdb5ad82b081d7211989db22a8e50a2a0559243127ed1eae
+size 28150
diff --git a/crates/viewer/re_time_panel/tests/snapshots/various_filters-ath,left.png b/crates/viewer/re_time_panel/tests/snapshots/various_filters-ath,left.png
index 91a311dce39e..bc4b8b2b3df0 100644
--- a/crates/viewer/re_time_panel/tests/snapshots/various_filters-ath,left.png
+++ b/crates/viewer/re_time_panel/tests/snapshots/various_filters-ath,left.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:e23119044b6e3057f3b803514cac27a1efcdf74e661b9fe987e8bd3bb4c8127e
-size 26628
+oid sha256:84333d73526476d83ebd79906f9894570eb6ca963d9ac8da60673faa4e5d2181
+size 26640
diff --git a/crates/viewer/re_time_panel/tests/snapshots/various_filters-ath,t.png b/crates/viewer/re_time_panel/tests/snapshots/various_filters-ath,t.png
index 7ac9df2f789f..c9291a6ab56b 100644
--- a/crates/viewer/re_time_panel/tests/snapshots/various_filters-ath,t.png
+++ b/crates/viewer/re_time_panel/tests/snapshots/various_filters-ath,t.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:bafb6dc4594d5705efb59ce5f2582a015cd9aac39ff43b6e7adf8bb6875e99a2
-size 33194
+oid sha256:471c77d03ad5b0130a71068092194ee5b8c0092685066c6a04e74c2712e140de
+size 33219
diff --git a/crates/viewer/re_time_panel/tests/snapshots/various_filters-none.png b/crates/viewer/re_time_panel/tests/snapshots/various_filters-none.png
index f42109f4bf57..27259e4488a5 100644
--- a/crates/viewer/re_time_panel/tests/snapshots/various_filters-none.png
+++ b/crates/viewer/re_time_panel/tests/snapshots/various_filters-none.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:423e1ad5755457256e2951434e96e8a6e5eb8f864ad48984ea3262bc3a0bb8fe
-size 29524
+oid sha256:87af0ebefeb2a0503f412843aaaf6368f9ccb83640fb1c87902c128ac019af09
+size 29510
diff --git a/crates/viewer/re_time_panel/tests/snapshots/various_filters-path.png b/crates/viewer/re_time_panel/tests/snapshots/various_filters-path.png
index 8d3b87815338..a77b45ea1a4e 100644
--- a/crates/viewer/re_time_panel/tests/snapshots/various_filters-path.png
+++ b/crates/viewer/re_time_panel/tests/snapshots/various_filters-path.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:e40e0d8cda19f53b10db5c25846dec8458ae6befbb9a2e36eb007ef529ba250d
-size 33377
+oid sha256:9b95a94aa8425ba5a78684536065e06a04f24c9c7139bcd10cfc650497cbff75
+size 33372
diff --git a/crates/viewer/re_time_panel/tests/snapshots/various_filters-t.png b/crates/viewer/re_time_panel/tests/snapshots/various_filters-t.png
index ce7411ce8492..e0ac57c44cb8 100644
--- a/crates/viewer/re_time_panel/tests/snapshots/various_filters-t.png
+++ b/crates/viewer/re_time_panel/tests/snapshots/various_filters-t.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:2df3b06f5680a156a4ed8ca90e19fca31b3ffcec247079665504cf338a5c87bf
-size 43713
+oid sha256:d7c1ebe6bfe1f6177306c0dfb3ea01307be0105eb959eaaab694f657aea5c0f4
+size 43699
diff --git a/crates/viewer/re_time_panel/tests/snapshots/various_filters-to_the,oid.png b/crates/viewer/re_time_panel/tests/snapshots/various_filters-to_the,oid.png
index 64aeb9b715d7..b6916be32bcb 100644
--- a/crates/viewer/re_time_panel/tests/snapshots/various_filters-to_the,oid.png
+++ b/crates/viewer/re_time_panel/tests/snapshots/various_filters-to_the,oid.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:fdea5ea7c0d05c59f46862c5e8beafef3833881322587f936274b5c4d087adda
-size 28287
+oid sha256:6d713e99b8d3cf341d13a7aaf1a38504196a0094453ba63066438cf95a31ce3c
+size 28272
diff --git a/crates/viewer/re_time_panel/tests/snapshots/various_filters-to_the.png b/crates/viewer/re_time_panel/tests/snapshots/various_filters-to_the.png
index fde700c8e6df..8c2427b413c5 100644
--- a/crates/viewer/re_time_panel/tests/snapshots/various_filters-to_the.png
+++ b/crates/viewer/re_time_panel/tests/snapshots/various_filters-to_the.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:e0645e3e1ad469d678c489ffa6adb5c58da001f547ab3dfe642e3ef437e8c3b6
-size 31357
+oid sha256:cd9da947f243c987b03fee1897922f7c308b7cb2cf0189fe231f57d4e3fae0da
+size 31360
diff --git a/crates/viewer/re_time_panel/tests/snapshots/various_filters-to_the_.png b/crates/viewer/re_time_panel/tests/snapshots/various_filters-to_the_.png
index 71204d53d6c9..91855e4e8aa0 100644
--- a/crates/viewer/re_time_panel/tests/snapshots/various_filters-to_the_.png
+++ b/crates/viewer/re_time_panel/tests/snapshots/various_filters-to_the_.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:b3de0b3819f6d8cbe418e893769a12e51fd6cc8cdfaddd934e962898403f7ab3
-size 27918
+oid sha256:a450df37022a2b56a7b14b09cbbe8327a2c3ae0f7e099950300d693cf5462916
+size 27952
diff --git a/crates/viewer/re_time_panel/tests/snapshots/various_filters-void.png b/crates/viewer/re_time_panel/tests/snapshots/various_filters-void.png
index 846e0d45d804..70e88f199849 100644
--- a/crates/viewer/re_time_panel/tests/snapshots/various_filters-void.png
+++ b/crates/viewer/re_time_panel/tests/snapshots/various_filters-void.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:23f242aa64904bd1b4f53b1fed3d25b6eb4bdecb46569206f6fa801c324221a2
-size 27633
+oid sha256:540a351226450435f0aee9a2481dcfbf369ba44eb70efcbae198f0e252111e36
+size 27619
diff --git a/crates/viewer/re_ui/tests/snapshots/alert_dark.png b/crates/viewer/re_ui/tests/snapshots/alert_dark.png
index 94fc4c4b0b93..f60ee6ae5f36 100644
--- a/crates/viewer/re_ui/tests/snapshots/alert_dark.png
+++ b/crates/viewer/re_ui/tests/snapshots/alert_dark.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:15ffadfe39320039aaf1da5a057ddb5bfeb1628d7cdce6f7626e034e825db9cc
-size 12804
+oid sha256:03b9eb2ddef39e83d7295ce5f2e74ee0d417d6ffdd6ae03a4b9ca4146ffd9aa9
+size 12649
diff --git a/crates/viewer/re_ui/tests/snapshots/alert_light.png b/crates/viewer/re_ui/tests/snapshots/alert_light.png
index 17748fd3a210..2d4411b2f820 100644
--- a/crates/viewer/re_ui/tests/snapshots/alert_light.png
+++ b/crates/viewer/re_ui/tests/snapshots/alert_light.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:1fc8938507fb9444e44ef3e8ea211b85182525f7c4fcd67c3b3485efe27d3a9d
-size 13021
+oid sha256:58d8c96783d8fd4bf7027c5902c8789b101120e857dbf1fe69d3a11f0be2e06c
+size 12866
diff --git a/crates/viewer/re_ui/tests/snapshots/combo_item.png b/crates/viewer/re_ui/tests/snapshots/combo_item.png
index 412a5368a561..ceebb830df10 100644
--- a/crates/viewer/re_ui/tests/snapshots/combo_item.png
+++ b/crates/viewer/re_ui/tests/snapshots/combo_item.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:375f4f24e424907f48c58e026c766c697d250402de0d9e67ddd918eea3571104
-size 16047
+oid sha256:f4b66d239aa148e282712e35896b50012a795b78d57d96de6638fd711238bf3e
+size 16107
diff --git a/crates/viewer/re_ui/tests/snapshots/filter_widget.png b/crates/viewer/re_ui/tests/snapshots/filter_widget.png
index f65f17c1fee0..63c9f680068b 100644
--- a/crates/viewer/re_ui/tests/snapshots/filter_widget.png
+++ b/crates/viewer/re_ui/tests/snapshots/filter_widget.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:a446b3bc43996e66dae3c40ea13183b58d9ac00fd73f2c95ca8f8eff5f2ed139
-size 9692
+oid sha256:d334c89b58dbe779a1ec3004998aa7251935c6baba79dea9023a0f32a3e9e350
+size 9694
diff --git a/crates/viewer/re_ui/tests/snapshots/help_Mac.png b/crates/viewer/re_ui/tests/snapshots/help_Mac.png
index c9d88a1a7d46..67451bf326ad 100644
--- a/crates/viewer/re_ui/tests/snapshots/help_Mac.png
+++ b/crates/viewer/re_ui/tests/snapshots/help_Mac.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:a1eb3661c43ae0ed170b87654d38db494cba282dfcb35e3872e534601fbf415b
-size 44675
+oid sha256:ef9b2fa53248f6479202335143124d4ef97ad00901af8a0d373605994c60af15
+size 44586
diff --git a/crates/viewer/re_ui/tests/snapshots/help_Windows.png b/crates/viewer/re_ui/tests/snapshots/help_Windows.png
index 098f908397fe..ce278d01a3d2 100644
--- a/crates/viewer/re_ui/tests/snapshots/help_Windows.png
+++ b/crates/viewer/re_ui/tests/snapshots/help_Windows.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:e3c560fce8e83c9b5419e31021594fcd843efa39af1c94ace9ec4fad35ab44fa
-size 44947
+oid sha256:a19f1df8ed522815cff52c42f1939f975cbea9df2e5f128c4ed1e1f3125d9cda
+size 44861
diff --git a/crates/viewer/re_ui/tests/snapshots/relative_time_range_absolute_duration.png b/crates/viewer/re_ui/tests/snapshots/relative_time_range_absolute_duration.png
index 16b98013b675..936cc4fccdf9 100644
--- a/crates/viewer/re_ui/tests/snapshots/relative_time_range_absolute_duration.png
+++ b/crates/viewer/re_ui/tests/snapshots/relative_time_range_absolute_duration.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:6c131fb95b211c161be5513444192568d469f98d67470512c42ee21ce4eba383
-size 11775
+oid sha256:4b1c3d6adafcfd36b39386079a1512898f0c696229284fff15a41290a040a3e3
+size 11766
diff --git a/crates/viewer/re_ui/tests/snapshots/relative_time_range_absolute_timestamp.png b/crates/viewer/re_ui/tests/snapshots/relative_time_range_absolute_timestamp.png
index 4c835fd97f68..02e2aa327738 100644
--- a/crates/viewer/re_ui/tests/snapshots/relative_time_range_absolute_timestamp.png
+++ b/crates/viewer/re_ui/tests/snapshots/relative_time_range_absolute_timestamp.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:35b55a2cdb735b3e8574d4f85943b98ac7ffe0b677f7f0189babf11f49d1d40e
-size 22870
+oid sha256:293c7dbc5a046a96c2afcf3a7f05be0cbce31ae8ef41f0a26082999f0f02bc60
+size 22757
diff --git a/crates/viewer/re_ui/tests/snapshots/relative_time_range_absolute_to_end_duration.png b/crates/viewer/re_ui/tests/snapshots/relative_time_range_absolute_to_end_duration.png
index 6e9ddb7ae816..b501fb0ae205 100644
--- a/crates/viewer/re_ui/tests/snapshots/relative_time_range_absolute_to_end_duration.png
+++ b/crates/viewer/re_ui/tests/snapshots/relative_time_range_absolute_to_end_duration.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:890925cd668110c3eb1b65b2090018d4a0c99c6160637283f475764300ca318d
-size 9263
+oid sha256:89c61f184a98fb11b93489f01993d45e573b08f5e9b8bb5b130ec6437dd6f850
+size 9260
diff --git a/crates/viewer/re_ui/tests/snapshots/relative_time_range_absolute_to_end_timestamp.png b/crates/viewer/re_ui/tests/snapshots/relative_time_range_absolute_to_end_timestamp.png
index d04b9cec203a..b44a749d6109 100644
--- a/crates/viewer/re_ui/tests/snapshots/relative_time_range_absolute_to_end_timestamp.png
+++ b/crates/viewer/re_ui/tests/snapshots/relative_time_range_absolute_to_end_timestamp.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:4846cd643deceb90ed7d98f38022c19bd968ad8cc52af3494cb11f79480761ef
-size 14968
+oid sha256:296ac8f0dfc666b6d57c149dea4edaf0a857573e757bdc8b6b000a15f70d4b64
+size 14863
diff --git a/crates/viewer/re_ui/tests/snapshots/relative_time_range_around_cursor_duration.png b/crates/viewer/re_ui/tests/snapshots/relative_time_range_around_cursor_duration.png
index 9adf19e0442f..ff6ed4064481 100644
--- a/crates/viewer/re_ui/tests/snapshots/relative_time_range_around_cursor_duration.png
+++ b/crates/viewer/re_ui/tests/snapshots/relative_time_range_around_cursor_duration.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:c2f6de476daf1881ccc3bbf39620110427b6a3ada29b78a9d91dfb72ad592bbd
-size 13926
+oid sha256:c377d594ef3ffb1ec18ace461713493f5c42c1eed33574eea9fe1bc24e0b4558
+size 13991
diff --git a/crates/viewer/re_ui/tests/snapshots/relative_time_range_around_cursor_timestamp.png b/crates/viewer/re_ui/tests/snapshots/relative_time_range_around_cursor_timestamp.png
index 77660178e8b5..d156b9a9cc62 100644
--- a/crates/viewer/re_ui/tests/snapshots/relative_time_range_around_cursor_timestamp.png
+++ b/crates/viewer/re_ui/tests/snapshots/relative_time_range_around_cursor_timestamp.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:bbec0ea17f62c64d49763cbf31d0f06403f9e54a278c98aecf224d5b296b2f24
-size 19685
+oid sha256:97c167cecfd30ddbf7daf460d654329460906f5c4084915cbfbb2d4da52b7c83
+size 19714
diff --git a/crates/viewer/re_ui/tests/snapshots/relative_time_range_at_cursor_duration.png b/crates/viewer/re_ui/tests/snapshots/relative_time_range_at_cursor_duration.png
index c81e89c9fd3f..597e1afc1ff4 100644
--- a/crates/viewer/re_ui/tests/snapshots/relative_time_range_at_cursor_duration.png
+++ b/crates/viewer/re_ui/tests/snapshots/relative_time_range_at_cursor_duration.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:a9c1726d2549380a8842b11eed58dcb0852c9a36ed46a729296539df17295501
-size 10571
+oid sha256:0ac3b76380c3964b02f4b44aa6233d332da389c96056b26b11a05b20a14ecbf6
+size 10645
diff --git a/crates/viewer/re_ui/tests/snapshots/relative_time_range_at_cursor_timestamp.png b/crates/viewer/re_ui/tests/snapshots/relative_time_range_at_cursor_timestamp.png
index ad54a36cb509..a83a423cdf93 100644
--- a/crates/viewer/re_ui/tests/snapshots/relative_time_range_at_cursor_timestamp.png
+++ b/crates/viewer/re_ui/tests/snapshots/relative_time_range_at_cursor_timestamp.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:2d691d948e27887411031ab204975f793a2b54c08a1236e827f3e1c29a3b2fde
-size 12218
+oid sha256:620233ec78761c1fc04035fef74815bf8e43c51a0370e641412db63309eed77e
+size 12290
diff --git a/crates/viewer/re_ui/tests/snapshots/relative_time_range_cursor_to_end_duration.png b/crates/viewer/re_ui/tests/snapshots/relative_time_range_cursor_to_end_duration.png
index 207d53c58068..498177c7ba5d 100644
--- a/crates/viewer/re_ui/tests/snapshots/relative_time_range_cursor_to_end_duration.png
+++ b/crates/viewer/re_ui/tests/snapshots/relative_time_range_cursor_to_end_duration.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:6586acab5dcd99503c2f159f6c4a7556d5b3e9586709923e19509d15fcf126a7
-size 10843
+oid sha256:93fa68363d2d23d422821b08b372ac5ecc33b769a09ec73a2303cb4975618b45
+size 10877
diff --git a/crates/viewer/re_ui/tests/snapshots/relative_time_range_cursor_to_end_timestamp.png b/crates/viewer/re_ui/tests/snapshots/relative_time_range_cursor_to_end_timestamp.png
index 88a16cfc17d6..c3a4a497e42f 100644
--- a/crates/viewer/re_ui/tests/snapshots/relative_time_range_cursor_to_end_timestamp.png
+++ b/crates/viewer/re_ui/tests/snapshots/relative_time_range_cursor_to_end_timestamp.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:f128217c33c28e40d29ad87bb0f8820cfeb57981db36b07a1448398b1f59c6de
-size 13538
+oid sha256:dd0fe8ecfb45359945fd08052808b9e16bc3d6653a1ebe56daa079215e90b474
+size 13618
diff --git a/crates/viewer/re_ui/tests/snapshots/relative_time_range_start_to_absolute_duration.png b/crates/viewer/re_ui/tests/snapshots/relative_time_range_start_to_absolute_duration.png
index 4f2f073b24b4..bbace0b6b2db 100644
--- a/crates/viewer/re_ui/tests/snapshots/relative_time_range_start_to_absolute_duration.png
+++ b/crates/viewer/re_ui/tests/snapshots/relative_time_range_start_to_absolute_duration.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:3d38a559f6f55fb15e7d2cde69f24f5fad4c03489c7311cb039d567451f398f2
-size 11384
+oid sha256:66590974e60d84eaac11b967e286e74f786fee61daec8b5d173ecd99ed5f28d1
+size 11382
diff --git a/crates/viewer/re_ui/tests/snapshots/relative_time_range_start_to_absolute_timestamp.png b/crates/viewer/re_ui/tests/snapshots/relative_time_range_start_to_absolute_timestamp.png
index acb489030786..7105a4d627b7 100644
--- a/crates/viewer/re_ui/tests/snapshots/relative_time_range_start_to_absolute_timestamp.png
+++ b/crates/viewer/re_ui/tests/snapshots/relative_time_range_start_to_absolute_timestamp.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:8d629156be2316ee4f2676393365cab16ad3b4a13bf155022465ed607fcde854
-size 16830
+oid sha256:1c72206c24388ae30d90acab2c0fdd76a0a1fd3668f4fba844d91cdc9c9af8b7
+size 16732
diff --git a/crates/viewer/re_ui/tests/snapshots/relative_time_range_start_to_cursor_duration.png b/crates/viewer/re_ui/tests/snapshots/relative_time_range_start_to_cursor_duration.png
index dc1747a2b7ca..1c98acc92f23 100644
--- a/crates/viewer/re_ui/tests/snapshots/relative_time_range_start_to_cursor_duration.png
+++ b/crates/viewer/re_ui/tests/snapshots/relative_time_range_start_to_cursor_duration.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:08b1e15d156ba06a79eba1cefcddbeb8d32819f89c6fa2c9747ba735b7cf789b
-size 12365
+oid sha256:34a316e62e79209f29f906f5de9fcf0ac10d07bed2af44a761bdbd1cc913c02b
+size 12440
diff --git a/crates/viewer/re_ui/tests/snapshots/relative_time_range_start_to_cursor_timestamp.png b/crates/viewer/re_ui/tests/snapshots/relative_time_range_start_to_cursor_timestamp.png
index 4d1f01a739fa..5a8aa83c09b4 100644
--- a/crates/viewer/re_ui/tests/snapshots/relative_time_range_start_to_cursor_timestamp.png
+++ b/crates/viewer/re_ui/tests/snapshots/relative_time_range_start_to_cursor_timestamp.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:04ae2e470e929c1845456ed305c0d59b5e4a24e0db68e916989f254056a49408
-size 15129
+oid sha256:ccfe075746b934b1fa336f441ac0f74445eaf4867efef16176682e3a6a89acc6
+size 15213
diff --git a/crates/viewer/re_view_bar_chart/tests/snapshots/bar_chart_1d.png b/crates/viewer/re_view_bar_chart/tests/snapshots/bar_chart_1d.png
index 0b4b15d23db3..a5ac87f3b33e 100644
--- a/crates/viewer/re_view_bar_chart/tests/snapshots/bar_chart_1d.png
+++ b/crates/viewer/re_view_bar_chart/tests/snapshots/bar_chart_1d.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:fff21575dd9236ddf07e163b700d1a6f6b3aa689970a3bc03136892c9965ee80
-size 20339
+oid sha256:1f51a7c81cdaa7abca93b9fce76ef139e064d0f3c16f2cffcb199ea91f47e335
+size 20352
diff --git a/crates/viewer/re_view_bar_chart/tests/snapshots/help_view_bar_chart_view_mac.png b/crates/viewer/re_view_bar_chart/tests/snapshots/help_view_bar_chart_view_mac.png
index f223216ed5f6..14d051183211 100644
--- a/crates/viewer/re_view_bar_chart/tests/snapshots/help_view_bar_chart_view_mac.png
+++ b/crates/viewer/re_view_bar_chart/tests/snapshots/help_view_bar_chart_view_mac.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:8413d587a534570f39972628b621d406c3323313453901b632364cbdd07a8fc1
-size 19405
+oid sha256:71b22244534deed5c08815349b4ee02ad91f0fe4aa84e77dc8a82afc8542477c
+size 19419
diff --git a/crates/viewer/re_view_bar_chart/tests/snapshots/help_view_bar_chart_view_windows.png b/crates/viewer/re_view_bar_chart/tests/snapshots/help_view_bar_chart_view_windows.png
index 6f65f5165222..a9dd196bfb6f 100644
--- a/crates/viewer/re_view_bar_chart/tests/snapshots/help_view_bar_chart_view_windows.png
+++ b/crates/viewer/re_view_bar_chart/tests/snapshots/help_view_bar_chart_view_windows.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:99da0abfdfd9c1e0e1b596e15ad9c47c0d01cfafad2ec0dbf004e3c869ddda05
-size 20877
+oid sha256:4b58c04a1fbf1a71c44a2cc429ada6c1c872de0183a33d88f5b17760125063e0
+size 20608
diff --git a/crates/viewer/re_view_dataframe/tests/snapshots/help_view_dataframe_view_mac.png b/crates/viewer/re_view_dataframe/tests/snapshots/help_view_dataframe_view_mac.png
index cd63eea89242..8603a11e6db1 100644
--- a/crates/viewer/re_view_dataframe/tests/snapshots/help_view_dataframe_view_mac.png
+++ b/crates/viewer/re_view_dataframe/tests/snapshots/help_view_dataframe_view_mac.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:9bd6eb7e84b2cb85506ff55bdaaed581831ad312c654f9130db8342791c57ae7
-size 22182
+oid sha256:8600884077d2e85f0f68a83214af0e8a5f5ef676a5987efcdfc65926ea1456bd
+size 22328
diff --git a/crates/viewer/re_view_dataframe/tests/snapshots/help_view_dataframe_view_windows.png b/crates/viewer/re_view_dataframe/tests/snapshots/help_view_dataframe_view_windows.png
index cd63eea89242..8603a11e6db1 100644
--- a/crates/viewer/re_view_dataframe/tests/snapshots/help_view_dataframe_view_windows.png
+++ b/crates/viewer/re_view_dataframe/tests/snapshots/help_view_dataframe_view_windows.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:9bd6eb7e84b2cb85506ff55bdaaed581831ad312c654f9130db8342791c57ae7
-size 22182
+oid sha256:8600884077d2e85f0f68a83214af0e8a5f5ef676a5987efcdfc65926ea1456bd
+size 22328
diff --git a/crates/viewer/re_view_dataframe/tests/snapshots/null_timeline.png b/crates/viewer/re_view_dataframe/tests/snapshots/null_timeline.png
index 184538728c0e..391a295e8235 100644
--- a/crates/viewer/re_view_dataframe/tests/snapshots/null_timeline.png
+++ b/crates/viewer/re_view_dataframe/tests/snapshots/null_timeline.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:1c92f3d9e230b592f40196f2594131ff14ae188d726396f66aaa41f5742b580d
-size 12598
+oid sha256:feacf68edbe850c545b700c3fda2f288b829fb63a07bc18a5a578163ef834dd6
+size 12597
diff --git a/crates/viewer/re_view_dataframe/tests/snapshots/unknown_timeline_selection_panel_ui.png b/crates/viewer/re_view_dataframe/tests/snapshots/unknown_timeline_selection_panel_ui.png
index 9f61edd0d677..ea5aa5b84d37 100644
--- a/crates/viewer/re_view_dataframe/tests/snapshots/unknown_timeline_selection_panel_ui.png
+++ b/crates/viewer/re_view_dataframe/tests/snapshots/unknown_timeline_selection_panel_ui.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:15b734a81b9143c20b161d10940fd18ec2668033b640c91f22dbbc47f79d8c00
-size 29618
+oid sha256:03f3b34a9849bb1b137dcb9c65ebf1aacb35be9fe0d53eb0653af271535a2341
+size 29617
diff --git a/crates/viewer/re_view_graph/tests/snapshots/help_view_graph_view_mac.png b/crates/viewer/re_view_graph/tests/snapshots/help_view_graph_view_mac.png
index ad7279b88004..e5c5ffd46e6b 100644
--- a/crates/viewer/re_view_graph/tests/snapshots/help_view_graph_view_mac.png
+++ b/crates/viewer/re_view_graph/tests/snapshots/help_view_graph_view_mac.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:d2c5bb9141b5cb32321c7a4d01024484631e41161daeeb0ce788f545cb8267e7
-size 8357
+oid sha256:9fd27804fb0a3108d2a6266812994591f93a4f67b37eed8c59d1717c55bae01e
+size 8466
diff --git a/crates/viewer/re_view_graph/tests/snapshots/help_view_graph_view_windows.png b/crates/viewer/re_view_graph/tests/snapshots/help_view_graph_view_windows.png
index 0c9267341551..4ac086f204bb 100644
--- a/crates/viewer/re_view_graph/tests/snapshots/help_view_graph_view_windows.png
+++ b/crates/viewer/re_view_graph/tests/snapshots/help_view_graph_view_windows.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:fd1d856a1f6be5bad1584ba32cd941d9648e1bff27e0853d5186002756b97356
-size 8398
+oid sha256:45a6c1c6743174380d6a2c6e0599a09cbd2a4bc46da87c2a9bf27196131ebd7c
+size 8506
diff --git a/crates/viewer/re_view_map/tests/snapshots/help_view_map_view_mac.png b/crates/viewer/re_view_map/tests/snapshots/help_view_map_view_mac.png
index 430c9b10f813..4c7dddaee927 100644
--- a/crates/viewer/re_view_map/tests/snapshots/help_view_map_view_mac.png
+++ b/crates/viewer/re_view_map/tests/snapshots/help_view_map_view_mac.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:915ea67a77b2ff70016dcce320139831c13483dc7fe3ecd0b491c045bce441ec
-size 8111
+oid sha256:edb2c04c80c866ef072dc08eec6c4641ed7e57971a2c1240196041c3e9c5fe38
+size 8219
diff --git a/crates/viewer/re_view_map/tests/snapshots/help_view_map_view_windows.png b/crates/viewer/re_view_map/tests/snapshots/help_view_map_view_windows.png
index 288de13c2b85..57e811a87ff3 100644
--- a/crates/viewer/re_view_map/tests/snapshots/help_view_map_view_windows.png
+++ b/crates/viewer/re_view_map/tests/snapshots/help_view_map_view_windows.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:7b7957023543f87d43203af6e5e5c6966659b308895a6c9aede00262b5d35fb6
-size 8151
+oid sha256:c6504c98826fde1b40367024fc37e8ac8f1b83058c9a8797dcd72f19c96a137e
+size 8259
diff --git a/crates/viewer/re_view_spatial/tests/snapshots/annotations_hover_background.png b/crates/viewer/re_view_spatial/tests/snapshots/annotations_hover_background.png
index ebd0bc09470f..a94a22434133 100644
--- a/crates/viewer/re_view_spatial/tests/snapshots/annotations_hover_background.png
+++ b/crates/viewer/re_view_spatial/tests/snapshots/annotations_hover_background.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:1f7e4ce22ea721ee52d19b772a9ea3ddb85246e2b3f850ede8c9355a23ef5698
+oid sha256:29092999acc7b4d4f30fe109d46addb952f2f3dd4e8c85e47b659d32e2d966ad
 size 20733
diff --git a/crates/viewer/re_view_spatial/tests/snapshots/annotations_hover_rect_green.png b/crates/viewer/re_view_spatial/tests/snapshots/annotations_hover_rect_green.png
index 02f87fbf66da..5abae63e9363 100644
--- a/crates/viewer/re_view_spatial/tests/snapshots/annotations_hover_rect_green.png
+++ b/crates/viewer/re_view_spatial/tests/snapshots/annotations_hover_rect_green.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:b859745de5ee4a3ee897fb4c168e7e1d3a50f22b9dc15cdef5dd464b46e2f2b4
-size 20965
+oid sha256:35dd2960372580bf697d831d97034d9930cacd9b88d6134949524eb434ae45da
+size 20966
diff --git a/crates/viewer/re_view_spatial/tests/snapshots/annotations_hover_rect_red.png b/crates/viewer/re_view_spatial/tests/snapshots/annotations_hover_rect_red.png
index 346455cbbd94..08f5e8249d13 100644
--- a/crates/viewer/re_view_spatial/tests/snapshots/annotations_hover_rect_red.png
+++ b/crates/viewer/re_view_spatial/tests/snapshots/annotations_hover_rect_red.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:b60e29e0544ed3ba2d8d6096a5d1e160631d7ac378b44cda117d669fbefdc339
-size 21321
+oid sha256:414b56b4f86713e4c30446e7a9b293ec1fd03485a8fd23fb4d975fcbbec9a55f
+size 21318
diff --git a/crates/viewer/re_view_spatial/tests/snapshots/annotations_hover_region_green.png b/crates/viewer/re_view_spatial/tests/snapshots/annotations_hover_region_green.png
index 60ca39ade161..8cf0909e402f 100644
--- a/crates/viewer/re_view_spatial/tests/snapshots/annotations_hover_region_green.png
+++ b/crates/viewer/re_view_spatial/tests/snapshots/annotations_hover_region_green.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:f841e16cc3bf3cca7794533cb1f70b4c8328d11b9cb97c51f0ca4bbd5caa4b08
-size 34436
+oid sha256:0b0e8bdc25d0d3cbeed8a237ae80fc1722c3800b629d6be3a72aba9ba6246b70
+size 34441
diff --git a/crates/viewer/re_view_spatial/tests/snapshots/annotations_hover_region_red.png b/crates/viewer/re_view_spatial/tests/snapshots/annotations_hover_region_red.png
index 40df44c0d4f1..decef9deac8e 100644
--- a/crates/viewer/re_view_spatial/tests/snapshots/annotations_hover_region_red.png
+++ b/crates/viewer/re_view_spatial/tests/snapshots/annotations_hover_region_red.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:f0f1928733058df76f8c076b9d177bfa6546f556e75754dc7d9e751808841aaf
-size 32491
+oid sha256:50f3190611d196d9a7c74a37564c63420ca8395718c45669b8211ba218b6c016
+size 32473
diff --git a/crates/viewer/re_view_spatial/tests/snapshots/help_view_2d_view_mac.png b/crates/viewer/re_view_spatial/tests/snapshots/help_view_2d_view_mac.png
index 0ec7cfef8aea..d89e4cb09dc1 100644
--- a/crates/viewer/re_view_spatial/tests/snapshots/help_view_2d_view_mac.png
+++ b/crates/viewer/re_view_spatial/tests/snapshots/help_view_2d_view_mac.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:5f93bb0a675ee27e67b845bb9b629fcf0a83cb1b1f34259c97a03f2501c05bc9
-size 7923
+oid sha256:5c4fc28f958bf7b015bb7437cb1605f605ec287c2f59eb942f472df38ff0a668
+size 8032
diff --git a/crates/viewer/re_view_spatial/tests/snapshots/help_view_2d_view_windows.png b/crates/viewer/re_view_spatial/tests/snapshots/help_view_2d_view_windows.png
index df3272ca9156..ed1d12d53928 100644
--- a/crates/viewer/re_view_spatial/tests/snapshots/help_view_2d_view_windows.png
+++ b/crates/viewer/re_view_spatial/tests/snapshots/help_view_2d_view_windows.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:a37d098794fd888e6ae5cce9012b9bdf37b6e5ce66d0bcea33d1cba73548fbcf
-size 7963
+oid sha256:48ded7c933e1c9ad308dad46c3fd7c73f0aa0094414126d21a825e8aa47c3a5c
+size 8072
diff --git a/crates/viewer/re_view_spatial/tests/snapshots/help_view_3d_view_mac.png b/crates/viewer/re_view_spatial/tests/snapshots/help_view_3d_view_mac.png
index b4108e22507e..006cec65c271 100644
--- a/crates/viewer/re_view_spatial/tests/snapshots/help_view_3d_view_mac.png
+++ b/crates/viewer/re_view_spatial/tests/snapshots/help_view_3d_view_mac.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:b2eb1a7b448d314a110601f8945e18454e1573ab01fc9306d714a7a27b1bfc79
-size 24143
+oid sha256:66154932e846a938d5ae5fbf54121245df027e6a1b06911c30dca7bb4115fb60
+size 24139
diff --git a/crates/viewer/re_view_spatial/tests/snapshots/help_view_3d_view_windows.png b/crates/viewer/re_view_spatial/tests/snapshots/help_view_3d_view_windows.png
index 975e70973038..d2c8e018c51b 100644
--- a/crates/viewer/re_view_spatial/tests/snapshots/help_view_3d_view_windows.png
+++ b/crates/viewer/re_view_spatial/tests/snapshots/help_view_3d_view_windows.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:cf11454289431072965106e1d1a1817de4fb6302600beae3c052994017c06240
-size 25158
+oid sha256:e20694b956094449494675fac5af71aca2bef087e15f4939a874a40c5ba09d6f
+size 25153
diff --git a/crates/viewer/re_view_tensor/tests/snapshots/help_view_tensor_view_mac.png b/crates/viewer/re_view_tensor/tests/snapshots/help_view_tensor_view_mac.png
index 844c6d57b8d8..22729265d6e9 100644
--- a/crates/viewer/re_view_tensor/tests/snapshots/help_view_tensor_view_mac.png
+++ b/crates/viewer/re_view_tensor/tests/snapshots/help_view_tensor_view_mac.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:f329095400d8b3f85d276205bef2c04bb9c1ebd8fa2f137b08362de5a9210c34
-size 18237
+oid sha256:de5d837695953543ba190b17d8e94068db3a2db6a3385531a01a3557c6cc5238
+size 18127
diff --git a/crates/viewer/re_view_tensor/tests/snapshots/help_view_tensor_view_windows.png b/crates/viewer/re_view_tensor/tests/snapshots/help_view_tensor_view_windows.png
index 844c6d57b8d8..22729265d6e9 100644
--- a/crates/viewer/re_view_tensor/tests/snapshots/help_view_tensor_view_windows.png
+++ b/crates/viewer/re_view_tensor/tests/snapshots/help_view_tensor_view_windows.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:f329095400d8b3f85d276205bef2c04bb9c1ebd8fa2f137b08362de5a9210c34
-size 18237
+oid sha256:de5d837695953543ba190b17d8e94068db3a2db6a3385531a01a3557c6cc5238
+size 18127
diff --git a/crates/viewer/re_view_tensor/tests/snapshots/tensor_2d_t2.png b/crates/viewer/re_view_tensor/tests/snapshots/tensor_2d_t2.png
index 56e129a6266f..e167d57cb210 100644
--- a/crates/viewer/re_view_tensor/tests/snapshots/tensor_2d_t2.png
+++ b/crates/viewer/re_view_tensor/tests/snapshots/tensor_2d_t2.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:0add8fcb8bc970b42257fe85834cfa0208319d83313735050ee9d6f4ef29f4dc
+oid sha256:972470e3ecf49d1aeddcd5849a79cbae514a6e234aee976ee2828cc134c063fb
 size 8517
diff --git a/crates/viewer/re_view_text_document/tests/snapshots/help_view_text_document_view_mac.png b/crates/viewer/re_view_text_document/tests/snapshots/help_view_text_document_view_mac.png
index 0b99a0a58972..11d958758806 100644
--- a/crates/viewer/re_view_text_document/tests/snapshots/help_view_text_document_view_mac.png
+++ b/crates/viewer/re_view_text_document/tests/snapshots/help_view_text_document_view_mac.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:34000e153266a9279edc7f4e77d4d02785221d44908519d779bc08f8bee07304
-size 7585
+oid sha256:da71a3f7c55f87b4967048b0846b312e251af6d401d3c06f90ceeef6f5aeb8df
+size 7579
diff --git a/crates/viewer/re_view_text_document/tests/snapshots/help_view_text_document_view_windows.png b/crates/viewer/re_view_text_document/tests/snapshots/help_view_text_document_view_windows.png
index 0b99a0a58972..11d958758806 100644
--- a/crates/viewer/re_view_text_document/tests/snapshots/help_view_text_document_view_windows.png
+++ b/crates/viewer/re_view_text_document/tests/snapshots/help_view_text_document_view_windows.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:34000e153266a9279edc7f4e77d4d02785221d44908519d779bc08f8bee07304
-size 7585
+oid sha256:da71a3f7c55f87b4967048b0846b312e251af6d401d3c06f90ceeef6f5aeb8df
+size 7579
diff --git a/crates/viewer/re_view_text_log/tests/snapshots/help_view_text_log_view_mac.png b/crates/viewer/re_view_text_log/tests/snapshots/help_view_text_log_view_mac.png
index 0604f12bbab0..c51ce9d77f7f 100644
--- a/crates/viewer/re_view_text_log/tests/snapshots/help_view_text_log_view_mac.png
+++ b/crates/viewer/re_view_text_log/tests/snapshots/help_view_text_log_view_mac.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:0cba627854cdeba2a7d3281b84ed35f8ac196ebb4a46e2094fc74d764d62cca0
-size 14860
+oid sha256:713fe47ede572eb333ced410f3c0ec65878005b5aa684ee9a2dd0a6aa9629f7f
+size 14750
diff --git a/crates/viewer/re_view_text_log/tests/snapshots/help_view_text_log_view_windows.png b/crates/viewer/re_view_text_log/tests/snapshots/help_view_text_log_view_windows.png
index 0604f12bbab0..c51ce9d77f7f 100644
--- a/crates/viewer/re_view_text_log/tests/snapshots/help_view_text_log_view_windows.png
+++ b/crates/viewer/re_view_text_log/tests/snapshots/help_view_text_log_view_windows.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:0cba627854cdeba2a7d3281b84ed35f8ac196ebb4a46e2094fc74d764d62cca0
-size 14860
+oid sha256:713fe47ede572eb333ced410f3c0ec65878005b5aa684ee9a2dd0a6aa9629f7f
+size 14750
diff --git a/crates/viewer/re_view_time_series/tests/snapshots/blueprint_overrides_and_defaults_with_time_series_around_cursor.png b/crates/viewer/re_view_time_series/tests/snapshots/blueprint_overrides_and_defaults_with_time_series_around_cursor.png
index 3fc4e39341d8..9bd47de42a24 100644
--- a/crates/viewer/re_view_time_series/tests/snapshots/blueprint_overrides_and_defaults_with_time_series_around_cursor.png
+++ b/crates/viewer/re_view_time_series/tests/snapshots/blueprint_overrides_and_defaults_with_time_series_around_cursor.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:20747c6e8ab0e386f040a177833634794098c1a06a4477716f1c564122a0077a
-size 16587
+oid sha256:365da76c48a65bd90d50839061806b58c370faf88ac438e9d9d73c23c4fcf8ce
+size 16500
diff --git a/crates/viewer/re_view_time_series/tests/snapshots/blueprint_overrides_and_defaults_with_time_series_at_cursor.png b/crates/viewer/re_view_time_series/tests/snapshots/blueprint_overrides_and_defaults_with_time_series_at_cursor.png
index 5be377c6b807..5b048f348d88 100644
--- a/crates/viewer/re_view_time_series/tests/snapshots/blueprint_overrides_and_defaults_with_time_series_at_cursor.png
+++ b/crates/viewer/re_view_time_series/tests/snapshots/blueprint_overrides_and_defaults_with_time_series_at_cursor.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:159b3b9a85283f0920da919be3d31414495549585e339eddbb4353418ca97521
-size 14702
+oid sha256:0fc5e96175f9e458c4debb80444b87ec74f54eb7d7690ad147553a28dfca6c31
+size 14574
diff --git a/crates/viewer/re_view_time_series/tests/snapshots/blueprint_overrides_and_defaults_with_time_series_start_until_absolute.png b/crates/viewer/re_view_time_series/tests/snapshots/blueprint_overrides_and_defaults_with_time_series_start_until_absolute.png
index 0e360ff4fecc..d028a4743a78 100644
--- a/crates/viewer/re_view_time_series/tests/snapshots/blueprint_overrides_and_defaults_with_time_series_start_until_absolute.png
+++ b/crates/viewer/re_view_time_series/tests/snapshots/blueprint_overrides_and_defaults_with_time_series_start_until_absolute.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:254497aa8fd52e6c237c754769bf6494e63c269f431da7fb13f3a9fae2f8d42a
-size 16353
+oid sha256:781789729fec8d75603545809d05f4c81fc1696eb8284fd3ab1516ec39230890
+size 16348
diff --git a/crates/viewer/re_view_time_series/tests/snapshots/bootstrapped_secondaries_full.png b/crates/viewer/re_view_time_series/tests/snapshots/bootstrapped_secondaries_full.png
index dd3142e43f4a..ca4fb8cf7bbb 100644
--- a/crates/viewer/re_view_time_series/tests/snapshots/bootstrapped_secondaries_full.png
+++ b/crates/viewer/re_view_time_series/tests/snapshots/bootstrapped_secondaries_full.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:b6a4b348eeea9f66cbaa20bfeaac2769986e5a489716ae47ae75e01cee278489
-size 16082
+oid sha256:8893c6c3d216456c19c0122723041445ba35a124cc75e8a35ec64b80d83ea031
+size 16075
diff --git a/crates/viewer/re_view_time_series/tests/snapshots/bootstrapped_secondaries_partial.png b/crates/viewer/re_view_time_series/tests/snapshots/bootstrapped_secondaries_partial.png
index dd3142e43f4a..ca4fb8cf7bbb 100644
--- a/crates/viewer/re_view_time_series/tests/snapshots/bootstrapped_secondaries_partial.png
+++ b/crates/viewer/re_view_time_series/tests/snapshots/bootstrapped_secondaries_partial.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:b6a4b348eeea9f66cbaa20bfeaac2769986e5a489716ae47ae75e01cee278489
-size 16082
+oid sha256:8893c6c3d216456c19c0122723041445ba35a124cc75e8a35ec64b80d83ea031
+size 16075
diff --git a/crates/viewer/re_view_time_series/tests/snapshots/explicit_component_mapping.png b/crates/viewer/re_view_time_series/tests/snapshots/explicit_component_mapping.png
index a363bb0b097e..c67a50e90f7e 100644
--- a/crates/viewer/re_view_time_series/tests/snapshots/explicit_component_mapping.png
+++ b/crates/viewer/re_view_time_series/tests/snapshots/explicit_component_mapping.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:854f3b584d0cffb79404a2c86afe2cdf99b2a19164653295cb70dcf63f6ff6ed
-size 26607
+oid sha256:8431ac56ede3ea822dab389635bc1865e366f5a5b809b2e83fe6813b03c77831
+size 26652
diff --git a/crates/viewer/re_view_time_series/tests/snapshots/explicit_component_mapping_nested.png b/crates/viewer/re_view_time_series/tests/snapshots/explicit_component_mapping_nested.png
index b65913e19a58..d31163cd4e29 100644
--- a/crates/viewer/re_view_time_series/tests/snapshots/explicit_component_mapping_nested.png
+++ b/crates/viewer/re_view_time_series/tests/snapshots/explicit_component_mapping_nested.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:069cf6220090924743e791bcdc3df012f3a79b5adacd08c0e07c4262b54be215
-size 40750
+oid sha256:92331b1049bfede88cf8754ad538a6b6418b47f22b3267cfda26a7e0160c4ef2
+size 40756
diff --git a/crates/viewer/re_view_time_series/tests/snapshots/help_view_time_series_view_mac.png b/crates/viewer/re_view_time_series/tests/snapshots/help_view_time_series_view_mac.png
index 56d08ee679bb..0ec40dd952ed 100644
--- a/crates/viewer/re_view_time_series/tests/snapshots/help_view_time_series_view_mac.png
+++ b/crates/viewer/re_view_time_series/tests/snapshots/help_view_time_series_view_mac.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:afee979c636afea5b58060eb28fb3603819aca5fa05efc9a412a0ca62088ca82
-size 29667
+oid sha256:dc68ec5be9bd9a2f299fb4ba9c43295b186d9ef107ea1874eac9c5fccc0e662e
+size 29689
diff --git a/crates/viewer/re_view_time_series/tests/snapshots/help_view_time_series_view_windows.png b/crates/viewer/re_view_time_series/tests/snapshots/help_view_time_series_view_windows.png
index 512230f90ca4..d0752d96cfe1 100644
--- a/crates/viewer/re_view_time_series/tests/snapshots/help_view_time_series_view_windows.png
+++ b/crates/viewer/re_view_time_series/tests/snapshots/help_view_time_series_view_windows.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:4911a614ada69bf62c304c06535a32208afb2bf37bbab61359294ad789c4e43a
-size 31102
+oid sha256:0cd5a8fc71c29fd43731952ada7e965d0f13443c195892f8f68264e45d0ccc59
+size 31111
diff --git a/crates/viewer/re_view_time_series/tests/snapshots/per_series_visibility_show_second_only.png b/crates/viewer/re_view_time_series/tests/snapshots/per_series_visibility_show_second_only.png
index f4cd16eb6a66..8f85509a92e4 100644
--- a/crates/viewer/re_view_time_series/tests/snapshots/per_series_visibility_show_second_only.png
+++ b/crates/viewer/re_view_time_series/tests/snapshots/per_series_visibility_show_second_only.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:a70c3130e0bb078aaff1022554f61464503ac3f422d4a34f1480d95641b1fa82
+oid sha256:bfc21a10a12260e603801e49c5fdee33d321e71c03496443ad440c46e4fb5a83
 size 23756
diff --git a/crates/viewer/re_view_time_series/tests/snapshots/per_series_visibility_splat_true.png b/crates/viewer/re_view_time_series/tests/snapshots/per_series_visibility_splat_true.png
index af97b617dfdb..04c237955a86 100644
--- a/crates/viewer/re_view_time_series/tests/snapshots/per_series_visibility_splat_true.png
+++ b/crates/viewer/re_view_time_series/tests/snapshots/per_series_visibility_splat_true.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:05dc6cfab00ea49dccec560d24caf49ad0d2a7117cca9c820cd43a0f2160d764
+oid sha256:77e60eacbd3dce19afc24708ff66b056d7a5fdd7b5d1c99986351d9f404288f9
 size 30220
diff --git a/crates/viewer/re_view_time_series/tests/snapshots/transform3d_time_series.png b/crates/viewer/re_view_time_series/tests/snapshots/transform3d_time_series.png
index 8fb50bef0ff7..1205281523ae 100644
--- a/crates/viewer/re_view_time_series/tests/snapshots/transform3d_time_series.png
+++ b/crates/viewer/re_view_time_series/tests/snapshots/transform3d_time_series.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:04a9b9b86e3c95d3ad27d61df88d5c360c3cd65108d54c9999a5187d7aee4ebc
-size 50820
+oid sha256:75da9e4a61790ff222dc22ef0a5fb0f73f68ade250b998bfc0ddd279598f980f
+size 50861
diff --git a/crates/viewer/re_view_time_series/tests/snapshots/visible_time_range_around_cursor_view_data.png b/crates/viewer/re_view_time_series/tests/snapshots/visible_time_range_around_cursor_view_data.png
index a03c11f2a077..315f4bf421ec 100644
--- a/crates/viewer/re_view_time_series/tests/snapshots/visible_time_range_around_cursor_view_data.png
+++ b/crates/viewer/re_view_time_series/tests/snapshots/visible_time_range_around_cursor_view_data.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:cea54cf310d15db2f12e4fea0d93b6ebd50f05b206ed0906c8778466e55515a7
-size 14478
+oid sha256:cbab6b4db34634af90cc1498e7fb1c34f20ca1347e63983b8280f4920ac30cd4
+size 14423
diff --git a/crates/viewer/re_view_time_series/tests/snapshots/visible_time_range_around_cursor_view_timeline.png b/crates/viewer/re_view_time_series/tests/snapshots/visible_time_range_around_cursor_view_timeline.png
index 8ba2d5f20494..1c57807a9686 100644
--- a/crates/viewer/re_view_time_series/tests/snapshots/visible_time_range_around_cursor_view_timeline.png
+++ b/crates/viewer/re_view_time_series/tests/snapshots/visible_time_range_around_cursor_view_timeline.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:e4fb5099ce07a4e1c220b6daaed04dd76c7f3b236f036c1dbe748b6c699fa762
-size 18353
+oid sha256:344633d8f72678b1b5f620fb04c5c5e1616759a4f3962d5e796b128d938853ec
+size 18286
diff --git a/crates/viewer/re_view_time_series/tests/snapshots/visible_time_range_start_until_absolute_view_data.png b/crates/viewer/re_view_time_series/tests/snapshots/visible_time_range_start_until_absolute_view_data.png
index 0e360ff4fecc..d028a4743a78 100644
--- a/crates/viewer/re_view_time_series/tests/snapshots/visible_time_range_start_until_absolute_view_data.png
+++ b/crates/viewer/re_view_time_series/tests/snapshots/visible_time_range_start_until_absolute_view_data.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:254497aa8fd52e6c237c754769bf6494e63c269f431da7fb13f3a9fae2f8d42a
-size 16353
+oid sha256:781789729fec8d75603545809d05f4c81fc1696eb8284fd3ab1516ec39230890
+size 16348
diff --git a/crates/viewer/re_view_time_series/tests/snapshots/visible_time_range_start_until_absolute_view_timeline.png b/crates/viewer/re_view_time_series/tests/snapshots/visible_time_range_start_until_absolute_view_timeline.png
index 411fe566c6e9..73390bd2cf90 100644
--- a/crates/viewer/re_view_time_series/tests/snapshots/visible_time_range_start_until_absolute_view_timeline.png
+++ b/crates/viewer/re_view_time_series/tests/snapshots/visible_time_range_start_until_absolute_view_timeline.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:460663d1d698d24d66d6948928c6a979e6c400a71991b0bff51aa606f30d2014
-size 18620
+oid sha256:40f0db0237f0957069aa97eec3c2e7904d4bd6fbaa908972bb9a8b0646f588b2
+size 18539
diff --git a/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Dark/2D.png b/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Dark/2D.png
index 5f132d68aa2c..78c04891bed2 100644
--- a/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Dark/2D.png
+++ b/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Dark/2D.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:dee0f4fa463eb55b826145c16ab3a360cbd421832d186cfbadab2a46db069327
-size 23358
+oid sha256:9bdbe857608f1ea693acf430cefdae2dbe0c9f1614b008cf9bb05f1c76062db2
+size 23373
diff --git a/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Dark/3D.png b/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Dark/3D.png
index 0009308e6b2f..4e2995ecd2e3 100644
--- a/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Dark/3D.png
+++ b/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Dark/3D.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:47efe7cef985586038c6979dbc824f61311e80f9406564a29a1cfbbe4cae5550
-size 73695
+oid sha256:d8e7391c4afbfcbbd7da1574f92017ec26b1e903682987778ec67a0e0aaa5097
+size 73669
diff --git a/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Dark/Dataframe.png b/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Dark/Dataframe.png
index 737e886054a6..9980929708f7 100644
--- a/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Dark/Dataframe.png
+++ b/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Dark/Dataframe.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:e6632bea334e28bbdd87e5473090334a9be7f8ee728d48383e0b6a392304fb6c
+oid sha256:ec68c359a878e24773c88f09b1db7dfab88eba128853cb55bbddcde43fe531ce
 size 31870
diff --git a/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Dark/Graph.png b/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Dark/Graph.png
index e13e0731f5b0..5d473be25d2b 100644
--- a/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Dark/Graph.png
+++ b/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Dark/Graph.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:3afb99cdece052d19912386c6c40b984f053703962e55378561ddc8340fa63bd
-size 29031
+oid sha256:17eea4c77cd8c12f6472f8869e1bc8358fb1c1115f5bff8a25a499e1b2541171
+size 29032
diff --git a/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Dark/Map.png b/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Dark/Map.png
index b38e147125e5..5ead54cbabcc 100644
--- a/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Dark/Map.png
+++ b/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Dark/Map.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:370230b35e3abe9ac4e80c599e2d638029092568c7299bce8fd3cc02a3c119d9
-size 16182
+oid sha256:080462fbb0a12e6e868233a29860bd1b846fa93efa3a204b84944448f89f0307
+size 16179
diff --git a/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Dark/Tensor.png b/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Dark/Tensor.png
index 26f2a08d1552..5955f911ca6a 100644
--- a/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Dark/Tensor.png
+++ b/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Dark/Tensor.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:5da28d0a952f469d698a919ed06f11b4e5526c1b566c470d2677a654b84b7e0a
-size 24132
+oid sha256:11bfc5fc1f831290785e6b9c862b6c80f9238dec3823f51285b28d8b9f4071df
+size 24121
diff --git a/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Dark/TextDocument.png b/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Dark/TextDocument.png
index 069c7620a647..a931a6475205 100644
--- a/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Dark/TextDocument.png
+++ b/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Dark/TextDocument.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:4c91cd423bfa232a7ed02904a2764b65a1680237e2ea4355a05364424beddabf
-size 14681
+oid sha256:9446581293741073066f36e8d173f7e2b9af91ba08961b712dc4f3f40bb6d5b4
+size 14708
diff --git a/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Dark/TextLog.png b/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Dark/TextLog.png
index 913cf9828d97..3601c7444b96 100644
--- a/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Dark/TextLog.png
+++ b/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Dark/TextLog.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:96b09ed9f184418bef573e7324491d999e59380893c12063812f2f1e8be8789a
+oid sha256:d267579fe4a5d2e74a7d12e008dfd66f477b789f0370640376b330f30a760084
 size 23340
diff --git a/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Dark/TimeSeries.png b/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Dark/TimeSeries.png
index a3fad67d1612..d6e6e15ebb17 100644
--- a/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Dark/TimeSeries.png
+++ b/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Dark/TimeSeries.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:58b3f60db4f5ebc4585f31779e55b980948d7385b53996a4b93cde930497bcdb
-size 32904
+oid sha256:7eeb106aa38c5011184e0de763b8c9c26a414fba19a180298344abf97b7e922d
+size 32902
diff --git a/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Light/2D.png b/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Light/2D.png
index 09a8130f065b..ea6bcf94b18c 100644
--- a/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Light/2D.png
+++ b/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Light/2D.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:42545818cf84b29c2fe1b61de17a52b62f170d2fc786fa6a3ad613b4a0b185ac
-size 23301
+oid sha256:dee31017d73d5782247989451629a3fe8ed0b3f7bbf985aec7eba09eeadd8d69
+size 23296
diff --git a/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Light/3D.png b/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Light/3D.png
index 2c9563d4b5d0..ff8f3c283bd2 100644
--- a/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Light/3D.png
+++ b/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Light/3D.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:5893ebadd0ef417484feb7c83a3aca80bbf93d3598f219ba2c2a9e00d9537e8b
-size 73668
+oid sha256:992c9dcdbfe240e0ef4efd1059b8d5ebdf37e47f9b1009dbccda0e14fac6eb36
+size 73721
diff --git a/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Light/Dataframe.png b/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Light/Dataframe.png
index b76873824eb8..e5d2095e3e5f 100644
--- a/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Light/Dataframe.png
+++ b/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Light/Dataframe.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:522e820b5d7a092af4bb771b22c1618e2aa1b72619ad786fc3f72494208dea7d
+oid sha256:f8e20463b80918eec2bd8e18e5ccaa8bb740b642c1675a978cdf63cda88e8e37
 size 31685
diff --git a/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Light/Graph.png b/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Light/Graph.png
index 270135299391..32f3bc2f2800 100644
--- a/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Light/Graph.png
+++ b/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Light/Graph.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:d76b62ddbde32e317004ec48c48cfd6f20cb17ff29726c2b77dd0afe75324e52
-size 28470
+oid sha256:7632320937973b94d297d30c1b39b96b4c1bebf586471763758cb0dd9d04dda9
+size 28471
diff --git a/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Light/Map.png b/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Light/Map.png
index 4b320a653ac7..d515aa3d1a02 100644
--- a/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Light/Map.png
+++ b/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Light/Map.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:f08fda414541a28b11fa4eb3d7931873b33aaa0c3f1371b010c498319bfc5650
-size 16224
+oid sha256:aa167aca88f562b1a508260d7c6bcbdeaaae886382f8ecce44eaf1cc2faecc22
+size 16222
diff --git a/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Light/Tensor.png b/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Light/Tensor.png
index 1ab11dadaad4..0cc3371df0ed 100644
--- a/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Light/Tensor.png
+++ b/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Light/Tensor.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:4c595ab151f80ab8bbe4f24b53848c2963b4b00085fef63ce496157961489d93
-size 24011
+oid sha256:42a54f04cc7f031395bb07bdea5662bacf6f038d86341ade6756f2522b0ee207
+size 24000
diff --git a/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Light/TextDocument.png b/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Light/TextDocument.png
index 844fd8502fca..7ad9235ab2f7 100644
--- a/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Light/TextDocument.png
+++ b/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Light/TextDocument.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:208b8c7bef5913877989ffc99843a9c07f2f9fddeb7d957c09d7cfa1f817d5d0
-size 14729
+oid sha256:d50f65039c72cb77587c03178b83ff491da791d912c0914d45bb44d800b3ce09
+size 14749
diff --git a/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Light/TextLog.png b/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Light/TextLog.png
index ae8d5da431fb..0297b00801b5 100644
--- a/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Light/TextLog.png
+++ b/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Light/TextLog.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:748926722d590718966c492be69841ae3972d583b667666be4b09fc0e17d8b97
-size 23069
+oid sha256:9391c9455371dbfa760ac1d3800497a8629a77e2c3d0ab40c54116d8cadc5d5a
+size 23070
diff --git a/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Light/TimeSeries.png b/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Light/TimeSeries.png
index ecd06c3d2e00..4dd5e8fb9aa8 100644
--- a/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Light/TimeSeries.png
+++ b/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Light/TimeSeries.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:6dd3e0581a2465662312b554cfcbebb0c1816b9034417985639b10feb30f4485
-size 32675
+oid sha256:f109ce3c6400117ba0c5aac1bf741487ffc73301ab6cca2fb070909d5568fe45
+size 32672
diff --git a/crates/viewer/re_viewer/tests/snapshots/blueprint_change_and_restore.png b/crates/viewer/re_viewer/tests/snapshots/blueprint_change_and_restore.png
index 045e5902a2c2..147e1da49aa1 100644
--- a/crates/viewer/re_viewer/tests/snapshots/blueprint_change_and_restore.png
+++ b/crates/viewer/re_viewer/tests/snapshots/blueprint_change_and_restore.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:50961a6b313016e93151d377fd5a8857a5bd503e2a8df4780fc784eb960f561c
-size 29286
+oid sha256:c1b36b549bb6c8a86146662da218c2379cc2eb4de4dc1fc64d8e2b757f993fbf
+size 29247
diff --git a/crates/viewer/re_viewer/tests/snapshots/blueprint_load_into_new_context_1.png b/crates/viewer/re_viewer/tests/snapshots/blueprint_load_into_new_context_1.png
index 1a0c61b68ab8..13ecde7c7544 100644
--- a/crates/viewer/re_viewer/tests/snapshots/blueprint_load_into_new_context_1.png
+++ b/crates/viewer/re_viewer/tests/snapshots/blueprint_load_into_new_context_1.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:4f8d8744fa48c72b4c4f82734e5f081efa7502b1370b1db30257507df6315bc5
-size 25166
+oid sha256:31e29b55f3abb4b23aeae5acdefe2219af0adc422f2c1368315b6e938c13b465
+size 25165
diff --git a/crates/viewer/re_viewer/tests/snapshots/blueprint_load_into_new_context_2.png b/crates/viewer/re_viewer/tests/snapshots/blueprint_load_into_new_context_2.png
index 55c97abcfa15..e40ad025a6de 100644
--- a/crates/viewer/re_viewer/tests/snapshots/blueprint_load_into_new_context_2.png
+++ b/crates/viewer/re_viewer/tests/snapshots/blueprint_load_into_new_context_2.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:65090e5497a3aac7a9aaf00e9fa995a50add4a8c8c931707c1bd0c3ae79ff36d
-size 29563
+oid sha256:02f951f98841775336b9cc7ae5df514f2c4f12e3b764899320ad01be47751458
+size 29513
diff --git a/crates/viewer/re_viewer/tests/snapshots/colormap_selector_closed.png b/crates/viewer/re_viewer/tests/snapshots/colormap_selector_closed.png
index ea78ab20414a..13227a451a1e 100644
--- a/crates/viewer/re_viewer/tests/snapshots/colormap_selector_closed.png
+++ b/crates/viewer/re_viewer/tests/snapshots/colormap_selector_closed.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:e224e3ddd75a84d1faac95ae8fa5f682d35c13f83a523e227665e2b7a54df953
-size 3112
+oid sha256:92e1aade23f33e1cbcf1a5977de4aa879fc00276f76bb0c9feb71a243f5b2bd6
+size 3134
diff --git a/crates/viewer/re_viewer/tests/snapshots/colormap_selector_open.png b/crates/viewer/re_viewer/tests/snapshots/colormap_selector_open.png
index 87b8a58b60c4..5ac49734581b 100644
--- a/crates/viewer/re_viewer/tests/snapshots/colormap_selector_open.png
+++ b/crates/viewer/re_viewer/tests/snapshots/colormap_selector_open.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:1cf5ca95eb6da8568a221f3c436457b0a6f323393a70cb02bae4c624fbaf663c
-size 23876
+oid sha256:a73675cd5d270998468095d67dc3e68a8f0a2fdc7856ca6d6f56236e7be24c0d
+size 23697
diff --git a/crates/viewer/re_viewer/tests/snapshots/menu_without_recording.png b/crates/viewer/re_viewer/tests/snapshots/menu_without_recording.png
index 67ab56b5fbf0..ba421e01b07a 100644
--- a/crates/viewer/re_viewer/tests/snapshots/menu_without_recording.png
+++ b/crates/viewer/re_viewer/tests/snapshots/menu_without_recording.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:cdedc5eb909da2844462c6dc531bb12b0216c000452c75687e56d6f4019e8b74
-size 122525
+oid sha256:a85a3ac0621c1df0e24eb6ff67228cf64f6e3712c0e472d55ebb587589df867b
+size 122528
diff --git a/crates/viewer/re_viewer/tests/snapshots/open_url_modal__invalid_url.png b/crates/viewer/re_viewer/tests/snapshots/open_url_modal__invalid_url.png
index fdc98e542bb4..a399f6e844a3 100644
--- a/crates/viewer/re_viewer/tests/snapshots/open_url_modal__invalid_url.png
+++ b/crates/viewer/re_viewer/tests/snapshots/open_url_modal__invalid_url.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:ca5f5620b7f7a7f5644b2ef7a5af742d5a811a0c81b19e61e56a0869cfbd5da4
+oid sha256:19eeb7485364d8a4b2e3b9919e7db0e77d89258a32ed65f778bd8f32b938e512
 size 21200
diff --git a/crates/viewer/re_viewer/tests/snapshots/open_url_modal__no_url.png b/crates/viewer/re_viewer/tests/snapshots/open_url_modal__no_url.png
index d3815f7d0860..235bed65935e 100644
--- a/crates/viewer/re_viewer/tests/snapshots/open_url_modal__no_url.png
+++ b/crates/viewer/re_viewer/tests/snapshots/open_url_modal__no_url.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:33a889972ff642a87f6d961318e7a0a0a413c5098a32090f720dbc663045a9c3
-size 12413
+oid sha256:fc848c48b272ab41648a453e7a7fdd3f820034bed66c28fc0c295cbb20725953
+size 12414
diff --git a/crates/viewer/re_viewer/tests/snapshots/open_url_modal__valid_url.png b/crates/viewer/re_viewer/tests/snapshots/open_url_modal__valid_url.png
index 5b016c4129c8..a20589362766 100644
--- a/crates/viewer/re_viewer/tests/snapshots/open_url_modal__valid_url.png
+++ b/crates/viewer/re_viewer/tests/snapshots/open_url_modal__valid_url.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:92a25e29ca83eedbb8b45f539a8cc7a940ae4c9d1ab82fe0d88f4097dab44dc2
+oid sha256:db0e27978ce3f56057720a4d4dea5ce4884882242486a820180cf953916df26b
 size 25484
diff --git a/crates/viewer/re_viewer/tests/snapshots/settings_screen.png b/crates/viewer/re_viewer/tests/snapshots/settings_screen.png
index 0076569ff8a4..1b2f2cffd2e1 100644
--- a/crates/viewer/re_viewer/tests/snapshots/settings_screen.png
+++ b/crates/viewer/re_viewer/tests/snapshots/settings_screen.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:edca8d351fd6397bb4f6344872b9952bc8f233facfa7791dfa4722505aa8df90
-size 76152
+oid sha256:655448e7303d8fd058fb2da13ab9e51df114b68f04d90981fe36f37c747f4f97
+size 76148
diff --git a/crates/viewer/re_viewer/tests/snapshots/share_modal__dataset_segment_url.png b/crates/viewer/re_viewer/tests/snapshots/share_modal__dataset_segment_url.png
index 71644f0189f9..3f1ca00be224 100644
--- a/crates/viewer/re_viewer/tests/snapshots/share_modal__dataset_segment_url.png
+++ b/crates/viewer/re_viewer/tests/snapshots/share_modal__dataset_segment_url.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:643961d01116072858751823c4d5ef7b4a491290d9c05d81a421883471e06132
-size 30988
+oid sha256:9bb7578b80c0ac7247d98b0fd6069ed2f8b73a50396de70be6c6bde672f70816
+size 30989
diff --git a/crates/viewer/re_viewer/tests/snapshots/share_modal__dataset_segment_url_with_time_range.png b/crates/viewer/re_viewer/tests/snapshots/share_modal__dataset_segment_url_with_time_range.png
index 22d4e053d1e9..5e0f87dc728d 100644
--- a/crates/viewer/re_viewer/tests/snapshots/share_modal__dataset_segment_url_with_time_range.png
+++ b/crates/viewer/re_viewer/tests/snapshots/share_modal__dataset_segment_url_with_time_range.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:f57a3d1ba9fc18397775fc74775fb9603fc389d07c73c771cbb0d8cb8e156c7c
-size 34299
+oid sha256:0ab6a529c4900339c8a2334ded493603abdc6546643acba9a67dbdbb549e6794
+size 34300
diff --git a/crates/viewer/re_viewer/tests/snapshots/share_modal__server_url.png b/crates/viewer/re_viewer/tests/snapshots/share_modal__server_url.png
index b12f04cb29cd..4fd1682a34a3 100644
--- a/crates/viewer/re_viewer/tests/snapshots/share_modal__server_url.png
+++ b/crates/viewer/re_viewer/tests/snapshots/share_modal__server_url.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:ae337e3dd4982d59caa72fcfb74316319c6aaeb50f11fbea397e2213f3684431
+oid sha256:9014813fe1f0994bb7a62ddb733b29be1c2f0a49c8e913927d705deb02b2740b
 size 17358
diff --git a/tests/rust/re_integration_test/tests/snapshots/add_container_from_blueprint_panel_menu_1.png b/tests/rust/re_integration_test/tests/snapshots/add_container_from_blueprint_panel_menu_1.png
index 091073b11295..847dd20d0816 100644
--- a/tests/rust/re_integration_test/tests/snapshots/add_container_from_blueprint_panel_menu_1.png
+++ b/tests/rust/re_integration_test/tests/snapshots/add_container_from_blueprint_panel_menu_1.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:ff2a349c57e55ebde8bf6cdad496af5a5718d806a9235c6cb5e010dfc53c80cc
-size 207314
+oid sha256:b65bc7d445fbc923fd310b01ff779e5380e4c550c7a9718ebd8730bef5906e38
+size 207490
diff --git a/tests/rust/re_integration_test/tests/snapshots/add_container_from_blueprint_panel_menu_2.png b/tests/rust/re_integration_test/tests/snapshots/add_container_from_blueprint_panel_menu_2.png
index d1d0b0cca2b5..8a464a3336a5 100644
--- a/tests/rust/re_integration_test/tests/snapshots/add_container_from_blueprint_panel_menu_2.png
+++ b/tests/rust/re_integration_test/tests/snapshots/add_container_from_blueprint_panel_menu_2.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:1a6896a737e5e7f879a22b4f1efbf2fe8dbcb45a9225d80655023a745b1c118c
-size 230022
+oid sha256:b3e044f562e8a26db3f963fc3fb66486e7c263da38231e0d96e69d04bdf67120
+size 230069
diff --git a/tests/rust/re_integration_test/tests/snapshots/add_container_from_blueprint_panel_menu_3.png b/tests/rust/re_integration_test/tests/snapshots/add_container_from_blueprint_panel_menu_3.png
index dba7989d827c..e48917feaa13 100644
--- a/tests/rust/re_integration_test/tests/snapshots/add_container_from_blueprint_panel_menu_3.png
+++ b/tests/rust/re_integration_test/tests/snapshots/add_container_from_blueprint_panel_menu_3.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:515a0cabf15f6bce3db930ebd6f988daf3b9e23ef991785ed99100b762fdafaa
-size 179879
+oid sha256:99c7ed5f8ee39a5510f210db03ec7e60afbd22518bd44bc6b69e10a2b58a31c5
+size 180060
diff --git a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_bar_chart_1.png b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_bar_chart_1.png
index cd7744b8ae54..1308210c23f3 100644
--- a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_bar_chart_1.png
+++ b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_bar_chart_1.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:8dac17ce91260c05fe917cfbadf254c155b1ff90c7e9fb26816c9e9e0203d9d3
-size 187921
+oid sha256:7f22f6cb9e16c2e82f6195f2092f2a32f1d1a8a05554338a2b8712a743283fcf
+size 187674
diff --git a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_bar_chart_2.png b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_bar_chart_2.png
index 41ef3673040d..2b14f1519fb7 100644
--- a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_bar_chart_2.png
+++ b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_bar_chart_2.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:19cff2e13d16961b7e2a1e7b54fa6a642a820bb17fe0d5c4afabcf30265468a0
-size 202817
+oid sha256:a4465c6c9aedbcb2f4d4c09654c5b61763f75fc909bbf60198b947b2cbc34006
+size 202400
diff --git a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_bar_chart_3.png b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_bar_chart_3.png
index ec8d82614c6d..e47893453cbc 100644
--- a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_bar_chart_3.png
+++ b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_bar_chart_3.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:97b40cef915b5852a84b71586e38ae852e18bfd5fba27e1a0822ff9128302c48
-size 194855
+oid sha256:78987ace67db529e75c235b4a5f5f3dc0fb0ebe3005ecc7cd3be5c7dcf0f4ed3
+size 194583
diff --git a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_bar_chart_4.png b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_bar_chart_4.png
index ed38ce063fd8..97c58c76c8b9 100644
--- a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_bar_chart_4.png
+++ b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_bar_chart_4.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:7ff24de9063f78182e581916d56695babb5e3dd986af66942288332da19b2961
-size 204923
+oid sha256:6e7039d71e3c166b88d62db272c9f51809ec425d20fd36c56850142cd0ec9b3f
+size 204720
diff --git a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_bar_chart_5.png b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_bar_chart_5.png
index 61cddfd79c98..0b275b4ebbe0 100644
--- a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_bar_chart_5.png
+++ b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_bar_chart_5.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:89265410bcfb6d6d3e7191b45e89f8cbed79667d4e591ca9e2f3569462200620
-size 152777
+oid sha256:d2ba9f5298f6d43a0bf9ba6a6561b2b014f9c2d3b98a58aa14adf8b4d3a5591c
+size 152682
diff --git a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_boxes2d_1.png b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_boxes2d_1.png
index 908bcefe3c63..20211463baa7 100644
--- a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_boxes2d_1.png
+++ b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_boxes2d_1.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:dea9e9a5400431fa84767e9b4db4c9ddb6fd962a7946367b7b81b8c4406a2ac6
-size 203117
+oid sha256:34c9fd81bcb8706afd500f77fa685f1ed36cceb0d4f6b9740c2e9ac402c0e889
+size 203042
diff --git a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_boxes2d_2.png b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_boxes2d_2.png
index 2160f947de31..57345d301f05 100644
--- a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_boxes2d_2.png
+++ b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_boxes2d_2.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:03e2dd88b62406617b6f4568393fd7edf957213ad20e003f4374d10e8f76bc90
-size 211507
+oid sha256:bb69b67ec1854ffa80f8edd76061b579dd7814b41e609f2df797527809dcbe25
+size 211348
diff --git a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_boxes2d_3.png b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_boxes2d_3.png
index 8f6518727fae..6dce56801c48 100644
--- a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_boxes2d_3.png
+++ b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_boxes2d_3.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:8d27a2b01871ac962ab717732a2bb17e9f045fe36ca9b7700fa1fb5ccf8ce279
-size 202783
+oid sha256:aee97022974b531c6d6c2203def29f8b1e1664c93033321f941aa6dda280f647
+size 202702
diff --git a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_boxes2d_4.png b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_boxes2d_4.png
index b6e749c6175c..d81debcc0d42 100644
--- a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_boxes2d_4.png
+++ b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_boxes2d_4.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:9de178cbac98f5c0d951856ca98421b48b69b3be25329fd4c6610f72edf3c71a
-size 207967
+oid sha256:62e8fdd2e3c4e3a29b5627a3c13b3d3d00e6560ee0cabe2e6b55de96c8e327dd
+size 207832
diff --git a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_boxes2d_5.png b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_boxes2d_5.png
index 61cddfd79c98..0b275b4ebbe0 100644
--- a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_boxes2d_5.png
+++ b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_boxes2d_5.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:89265410bcfb6d6d3e7191b45e89f8cbed79667d4e591ca9e2f3569462200620
-size 152777
+oid sha256:d2ba9f5298f6d43a0bf9ba6a6561b2b014f9c2d3b98a58aa14adf8b4d3a5591c
+size 152682
diff --git a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_boxes3d_1.png b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_boxes3d_1.png
index 5109e19a2544..ef1ce3dbcd80 100644
--- a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_boxes3d_1.png
+++ b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_boxes3d_1.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:c9fdeb1b46d14acea32743784abb0efd8c80f25d839c5d78e58346cbab45af5b
-size 214125
+oid sha256:94e2f488ea61df7762d8dfd363dc26ad90dfbbb81ca80b36bdabb03744656355
+size 214193
diff --git a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_boxes3d_2.png b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_boxes3d_2.png
index 3b57d06e5ab8..b4c4e471a62b 100644
--- a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_boxes3d_2.png
+++ b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_boxes3d_2.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:5094bf587cdaffd86d510f57afc87158090bcb124b667ed7ebbaad5fc7d2afe8
-size 223703
+oid sha256:6c93a98c60479065fe24262cbea0077412357faad5ad598f3ce2f2ed3a8b6b01
+size 223591
diff --git a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_boxes3d_3.png b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_boxes3d_3.png
index 3cb949b8d3f2..bd9ea16882b4 100644
--- a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_boxes3d_3.png
+++ b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_boxes3d_3.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:e71292d827d5c009ddae360fb56a50f511e48c777c11da967cdbf6efa5fba3e7
-size 236932
+oid sha256:aadd41a8fb7efc215a364f8bdb88f48acc774a4a9452760103883c1ebeb82200
+size 239528
diff --git a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_boxes3d_4.png b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_boxes3d_4.png
index 693bed64a6a7..747109808cf6 100644
--- a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_boxes3d_4.png
+++ b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_boxes3d_4.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:249282b43145ec58656fbd64e01e41101401d1c31331f7fca58c668b72c30608
-size 242902
+oid sha256:3aa3030040493007ca56f865ec1cc6ac9e5557c0dfa389c1d5d8db335657ddf1
+size 245501
diff --git a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_boxes3d_5.png b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_boxes3d_5.png
index 61cddfd79c98..0b275b4ebbe0 100644
--- a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_boxes3d_5.png
+++ b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_boxes3d_5.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:89265410bcfb6d6d3e7191b45e89f8cbed79667d4e591ca9e2f3569462200620
-size 152777
+oid sha256:d2ba9f5298f6d43a0bf9ba6a6561b2b014f9c2d3b98a58aa14adf8b4d3a5591c
+size 152682
diff --git a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_tensor_1.png b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_tensor_1.png
index 0a9423a959e9..ca7e2763534b 100644
--- a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_tensor_1.png
+++ b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_tensor_1.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:6602d122c09e89123d7fabb29b3af56a01931045fca89f5c2fa58967f6b314e0
-size 183058
+oid sha256:58e0246d444e86f3a3cf4a74803ba647cc428115460e0c25f18630b4a912fc3a
+size 182774
diff --git a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_tensor_2.png b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_tensor_2.png
index 4b5a08d37b6e..f2bb5c2a98a8 100644
--- a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_tensor_2.png
+++ b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_tensor_2.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:0d7e9a9bf35c8dbddcc5c2290fd9f028dc5ab93911abdade754ffa7005427bc4
-size 196407
+oid sha256:ff10ffc5f046aef9f603f4d467c7c4d0f0cea2cef1ab5ec197f02b87bbc70f90
+size 195936
diff --git a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_tensor_3.png b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_tensor_3.png
index 48bb766759b3..43da4082f75f 100644
--- a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_tensor_3.png
+++ b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_tensor_3.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:79264d1b43ab6082a8513e106a185823fe93adb7e81f5c4e7fc8504d26279de1
-size 214788
+oid sha256:3e5915466be78910fa52f8932125beaec0095bd97b6c12df1f95e815104cdb24
+size 214752
diff --git a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_tensor_4.png b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_tensor_4.png
index 6af8eabb6e21..c4955d17cd52 100644
--- a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_tensor_4.png
+++ b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_tensor_4.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:a2535e8da2ec400f254ea6b711d0796bc204d5f46c7282721c5dec1534e53901
-size 227298
+oid sha256:6646a52bf940aee1318560585325ecec55529f6887726f12ca31baae850b00a6
+size 227213
diff --git a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_tensor_5.png b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_tensor_5.png
index 61cddfd79c98..0b275b4ebbe0 100644
--- a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_tensor_5.png
+++ b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_tensor_5.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:89265410bcfb6d6d3e7191b45e89f8cbed79667d4e591ca9e2f3569462200620
-size 152777
+oid sha256:d2ba9f5298f6d43a0bf9ba6a6561b2b014f9c2d3b98a58aa14adf8b4d3a5591c
+size 152682
diff --git a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_text_log_1.png b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_text_log_1.png
index 80d54c0d49b2..92ba148f88d7 100644
--- a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_text_log_1.png
+++ b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_text_log_1.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:6ce088c97fad90555c3693303cff9d8ceb68f5a6a555c6df0d3fb39359b6dc4f
-size 184702
+oid sha256:3924b35ad12b3a8a59fa16fc99b2235f376f788e07721c4d4949a50c04af16ed
+size 184511
diff --git a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_text_log_2.png b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_text_log_2.png
index 66e099b9ecfc..4c6dbf7a84a4 100644
--- a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_text_log_2.png
+++ b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_text_log_2.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:c25b5b66295769da67c9bb7eda5d3f770b0314d2fdbf7cee47fe2d210ad177db
-size 196861
+oid sha256:29bde6cc9993ee59868ad9c939b18b0fa3930e3b8082d838b2206db9eb1dd845
+size 196513
diff --git a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_text_log_3.png b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_text_log_3.png
index 93cb02c003a5..339661028143 100644
--- a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_text_log_3.png
+++ b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_text_log_3.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:220ef73cebe425eccdcd485e17f882ea541ef6c33d05ebf5a5955856fb84405f
-size 194054
+oid sha256:abaa3ca0344f109c7af3b5f110caa71435c9710782ad201c94f606be37d1cdd8
+size 193897
diff --git a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_text_log_4.png b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_text_log_4.png
index 6e35afeb500d..07bca5917659 100644
--- a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_text_log_4.png
+++ b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_text_log_4.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:f98b00956f6de7086ccf2f9358261775ca5333ba4df89281a00e71bb14370bcb
-size 212620
+oid sha256:db096d93bba263bfc4615b180d776bf89a45b740c16cb1cb48d690e0c94a9359
+size 212422
diff --git a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_text_log_5.png b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_text_log_5.png
index 61cddfd79c98..0b275b4ebbe0 100644
--- a/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_text_log_5.png
+++ b/tests/rust/re_integration_test/tests/snapshots/add_entity_to_view_text_log_5.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:89265410bcfb6d6d3e7191b45e89f8cbed79667d4e591ca9e2f3569462200620
-size 152777
+oid sha256:d2ba9f5298f6d43a0bf9ba6a6561b2b014f9c2d3b98a58aa14adf8b4d3a5591c
+size 152682
diff --git a/tests/rust/re_integration_test/tests/snapshots/add_visualizer_axes_1.png b/tests/rust/re_integration_test/tests/snapshots/add_visualizer_axes_1.png
index d36387d2c378..a18c8e2c3534 100644
--- a/tests/rust/re_integration_test/tests/snapshots/add_visualizer_axes_1.png
+++ b/tests/rust/re_integration_test/tests/snapshots/add_visualizer_axes_1.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:e4134de6f345e76e1ea198fdbe143d5d8b7ae1cae4467a567fbe91cba66a33ac
-size 157817
+oid sha256:37498f081bd98dc037253947c212f05114d02fc1d2410d869c512091baafce13
+size 157772
diff --git a/tests/rust/re_integration_test/tests/snapshots/blueprint_tree_context_menu_02.png b/tests/rust/re_integration_test/tests/snapshots/blueprint_tree_context_menu_02.png
index 3c96416f71fb..a2cce5fd629d 100644
--- a/tests/rust/re_integration_test/tests/snapshots/blueprint_tree_context_menu_02.png
+++ b/tests/rust/re_integration_test/tests/snapshots/blueprint_tree_context_menu_02.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:aaab3df2c81f9913a33952aed5a7ddc519d49515cf61a0db4e7e78582debdd7b
-size 135983
+oid sha256:b4f50dc25ad22862771daf9b200f908c79457b139813c7290b0da328646f5c10
+size 136150
diff --git a/tests/rust/re_integration_test/tests/snapshots/change_container_type_2.png b/tests/rust/re_integration_test/tests/snapshots/change_container_type_2.png
index 89abfcf9f1b5..55db56f73709 100644
--- a/tests/rust/re_integration_test/tests/snapshots/change_container_type_2.png
+++ b/tests/rust/re_integration_test/tests/snapshots/change_container_type_2.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:83278e9e02d928aa50d6999cdeea703b325d98ed06c35d056aed3b4a2a87fad0
-size 150252
+oid sha256:35633b4d1193dca383f0093e4f2a77f6f362f4c32a453f58d32bb02a2a703ad9
+size 150283
diff --git a/tests/rust/re_integration_test/tests/snapshots/dataset_ui_empty_form.png b/tests/rust/re_integration_test/tests/snapshots/dataset_ui_empty_form.png
index 50fafc49ce7b..a4e3c11f62fb 100644
--- a/tests/rust/re_integration_test/tests/snapshots/dataset_ui_empty_form.png
+++ b/tests/rust/re_integration_test/tests/snapshots/dataset_ui_empty_form.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:1a0ae742319b4c6da77bbc6cbae485b57bea5a79e90ef6a65e7015a28df764f8
-size 66870
+oid sha256:d909366c8cbf4f881e64cf4e0c194f961c83816c848288e99102c6a5e9c3a001
+size 66806
diff --git a/tests/rust/re_integration_test/tests/snapshots/dataset_ui_table.png b/tests/rust/re_integration_test/tests/snapshots/dataset_ui_table.png
index 3c3e48c654e1..1ddcf95bfb80 100644
--- a/tests/rust/re_integration_test/tests/snapshots/dataset_ui_table.png
+++ b/tests/rust/re_integration_test/tests/snapshots/dataset_ui_table.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:4ed9ea2bc62ed207a02170e00b728c757e1bb7afbcc0ac188a1457a08534aeed
+oid sha256:47ef51b9cfc9aace25dcfc5b75294f4c57bf2d11fbda376d8d69f00c4b62bf5a
 size 43380
diff --git a/tests/rust/re_integration_test/tests/snapshots/deselect_on_escape_3.png b/tests/rust/re_integration_test/tests/snapshots/deselect_on_escape_3.png
index a104bb81d3d7..d4f73ebf51fc 100644
--- a/tests/rust/re_integration_test/tests/snapshots/deselect_on_escape_3.png
+++ b/tests/rust/re_integration_test/tests/snapshots/deselect_on_escape_3.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:0bf411ae07b88dc8c18f6988009e13e3ee722d8866d86ac34abc364c3ab38b33
-size 147625
+oid sha256:f230ed492ece4d9009e20c51adfda4d97cb2c693128e84a0d9d34ff4c1c509d8
+size 147614
diff --git a/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_bottom_1.png b/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_bottom_1.png
index b2558400ecd3..507af6634348 100644
--- a/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_bottom_1.png
+++ b/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_bottom_1.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:d23206dd98af668297f35f5f963d02a4b3efdc8b7f8ba8dea62b7457c2ada94f
-size 188791
+oid sha256:ac578242323ef0a6a3d91ece2ab0fb8b9dcee70202a7f921731d333a1c2f3b3c
+size 189131
diff --git a/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_bottom_2.png b/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_bottom_2.png
index df2498422cea..1a19e3bc6cc9 100644
--- a/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_bottom_2.png
+++ b/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_bottom_2.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:328b79be6eb67a5e25614538ff2c48147acb111244fd23bfaa86dff3c3644634
-size 190775
+oid sha256:8566c17b8dafd2a35f019ea99e25538d323ab0132166e4058aeed3376be9b119
+size 190763
diff --git a/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_center_1.png b/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_center_1.png
index 139c194bff5e..9f00bf916d2d 100644
--- a/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_center_1.png
+++ b/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_center_1.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:1f6280ddb09841c5eff70ceb473c8c3f450c2263cad225b507bc4975a5201c96
-size 188753
+oid sha256:59b556f9c9983a0fa5ebd0719e27398d77ed08abcef418c2716f1a3f4e5984ad
+size 189088
diff --git a/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_center_2.png b/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_center_2.png
index 58f31b5d2df2..a65893e6e536 100644
--- a/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_center_2.png
+++ b/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_center_2.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:5185b7c556ae8c548eed1cb1f061e640e60cacf10e8386482359f614371af24d
-size 199125
+oid sha256:226dc98d7985b077753ecf7a935cbaab711f04785c416804451c0735a3074f96
+size 199728
diff --git a/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_left_1.png b/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_left_1.png
index 04ceec14678e..3f4e517283d7 100644
--- a/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_left_1.png
+++ b/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_left_1.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:71095dcf580d233c1cd1f7b138ee975c833143bfcaed53368cf76b4b98aa11a1
-size 188793
+oid sha256:1638b6152edfcbbf93db6e34618e139ee243672a9bf6e830e0c679fa90c9e643
+size 189124
diff --git a/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_left_2.png b/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_left_2.png
index dd23c0fa72c4..01832d1539a0 100644
--- a/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_left_2.png
+++ b/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_left_2.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:1065d4a9cbac690e41a067ce971364361f71a7a6b86e5dbb5ed88c8bd4d66513
-size 188316
+oid sha256:37a799ea917390cfdcc91bac1719ebd08a29668e75fe0a43011928022a75cfe1
+size 188669
diff --git a/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_right_1.png b/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_right_1.png
index d31125139d0d..4eb020e06040 100644
--- a/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_right_1.png
+++ b/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_right_1.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:5a78af63da0de949a5c5d31869ae6a3302441f04397b101e74121a9787401ea2
-size 188815
+oid sha256:00c4d67f3f28d4f7319e8d1af47ac3b6d1352e82b96bad5a8948117592e755c9
+size 189153
diff --git a/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_right_2.png b/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_right_2.png
index 7e3c67d6a6cc..6193214df4a4 100644
--- a/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_right_2.png
+++ b/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_right_2.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:442633e26647471cfc99bf721a84ada3f6d3518c678a7028dd79f4efbdb745d8
-size 188325
+oid sha256:5a821ef94d81bb1f53013c7a77d7841f65b20cb9eb805b70ca6b212dd0ec8b9f
+size 188785
diff --git a/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_top_1.png b/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_top_1.png
index ffd7c925b6cd..2dbc19ec62e5 100644
--- a/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_top_1.png
+++ b/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_top_1.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:c0ee37a908fbc640dd7e23f74772ebbecfb587854eb1164a6c146f59e024b090
-size 188709
+oid sha256:56fa173216120923336931da2de5d221fd9812855aa0aa3fc3e9661bfc93ca97
+size 189047
diff --git a/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_top_2.png b/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_top_2.png
index 44d3b6bc8896..4d70c4ec730e 100644
--- a/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_top_2.png
+++ b/tests/rust/re_integration_test/tests/snapshots/drag_view_to_other_view_top_2.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:242e1586a8f8110bac94ba9b27aeda436c412f4876566dcc3ce051bf787a488a
-size 190678
+oid sha256:9c29663cd2ce2d394fc065ed151e8c3aec1a6ed37e7779b4f86088e5562926e4
+size 190872
diff --git a/tests/rust/re_integration_test/tests/snapshots/drop_multiple_streams_to_view_1.png b/tests/rust/re_integration_test/tests/snapshots/drop_multiple_streams_to_view_1.png
index b02613d5df4c..c9e68703e356 100644
--- a/tests/rust/re_integration_test/tests/snapshots/drop_multiple_streams_to_view_1.png
+++ b/tests/rust/re_integration_test/tests/snapshots/drop_multiple_streams_to_view_1.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:ed4794b26a1917189c09d78fce463d2ad4359c1283003dbffed2648e67eb0f67
-size 128704
+oid sha256:3434da1d2949e696a889493a5cf44794d17e0711dcb00b0f232e360059c16ad3
+size 128711
diff --git a/tests/rust/re_integration_test/tests/snapshots/drop_multiple_streams_to_view_2.png b/tests/rust/re_integration_test/tests/snapshots/drop_multiple_streams_to_view_2.png
index bc6c576e6e22..f38ea26d3c2c 100644
--- a/tests/rust/re_integration_test/tests/snapshots/drop_multiple_streams_to_view_2.png
+++ b/tests/rust/re_integration_test/tests/snapshots/drop_multiple_streams_to_view_2.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:ee50ee8174f9499f415ea6e0881f6c81d2cf615ebfe0a8abd5683c6f3d677f4f
-size 101826
+oid sha256:f0c29dc8c3687680ca7dc27c67933253cfd1d8412a89d6152a2fb9956f14615b
+size 101831
diff --git a/tests/rust/re_integration_test/tests/snapshots/drop_multiple_streams_to_view_3.png b/tests/rust/re_integration_test/tests/snapshots/drop_multiple_streams_to_view_3.png
index 6f4a413cb80a..6b37a88c535e 100644
--- a/tests/rust/re_integration_test/tests/snapshots/drop_multiple_streams_to_view_3.png
+++ b/tests/rust/re_integration_test/tests/snapshots/drop_multiple_streams_to_view_3.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:d24cd32b75c18f44d348f5a4066fe4aa35c82acb06f861e002d209357dc4afa0
-size 135600
+oid sha256:14c90c6be3b37825126421037b8d673d4b07865e716e7855fc92e5496615806c
+size 135605
diff --git a/tests/rust/re_integration_test/tests/snapshots/drop_multiple_streams_to_view_4.png b/tests/rust/re_integration_test/tests/snapshots/drop_multiple_streams_to_view_4.png
index 9d771e570609..e73f38c351b7 100644
--- a/tests/rust/re_integration_test/tests/snapshots/drop_multiple_streams_to_view_4.png
+++ b/tests/rust/re_integration_test/tests/snapshots/drop_multiple_streams_to_view_4.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:c804630973a6cbee36090dfbd03dddac28208addf877df3dc77bb0f639228e71
-size 109051
+oid sha256:57489a0bc3a9261ffef3af6eb3288bce2a4ed2a3ce7d535124efd6674c7b6bf4
+size 109041
diff --git a/tests/rust/re_integration_test/tests/snapshots/drop_multiple_streams_to_view_5.png b/tests/rust/re_integration_test/tests/snapshots/drop_multiple_streams_to_view_5.png
index d3f781a1ee66..2f320164c935 100644
--- a/tests/rust/re_integration_test/tests/snapshots/drop_multiple_streams_to_view_5.png
+++ b/tests/rust/re_integration_test/tests/snapshots/drop_multiple_streams_to_view_5.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:8c83ffa70e3b8a83ee99ce409175e529e5f164b0f042a965b126fceb06720e08
-size 108872
+oid sha256:2c83dde2f3ed83edb8d114543b5e208025dbd11626841d1c04b79e54968b5acb
+size 108858
diff --git a/tests/rust/re_integration_test/tests/snapshots/drop_multiple_streams_to_view_6.png b/tests/rust/re_integration_test/tests/snapshots/drop_multiple_streams_to_view_6.png
index 719542abbc94..ccb4dfbbcf50 100644
--- a/tests/rust/re_integration_test/tests/snapshots/drop_multiple_streams_to_view_6.png
+++ b/tests/rust/re_integration_test/tests/snapshots/drop_multiple_streams_to_view_6.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:e954dbc5edccbb46db3d39d4b1963ba9e154dad4ceec8f5efb2037464ff645c7
-size 151866
+oid sha256:32df3a58b5d341bb326866fb42a02ee2d3b3129bdbca74505db7335752498ab2
+size 151865
diff --git a/tests/rust/re_integration_test/tests/snapshots/drop_stream_to_view_1.png b/tests/rust/re_integration_test/tests/snapshots/drop_stream_to_view_1.png
index b1a6045cd6b8..c136a24369e9 100644
--- a/tests/rust/re_integration_test/tests/snapshots/drop_stream_to_view_1.png
+++ b/tests/rust/re_integration_test/tests/snapshots/drop_stream_to_view_1.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:27fb7fe91562d0b2ae7b595dc1a70815c46df69fb832d077c576181b1d06e37d
-size 101130
+oid sha256:03cde5481138dd77898ce424b3a8537b1173dc2493836d90dfde4187b14a14c6
+size 101450
diff --git a/tests/rust/re_integration_test/tests/snapshots/drop_stream_to_view_2.png b/tests/rust/re_integration_test/tests/snapshots/drop_stream_to_view_2.png
index b02613d5df4c..c9e68703e356 100644
--- a/tests/rust/re_integration_test/tests/snapshots/drop_stream_to_view_2.png
+++ b/tests/rust/re_integration_test/tests/snapshots/drop_stream_to_view_2.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:ed4794b26a1917189c09d78fce463d2ad4359c1283003dbffed2648e67eb0f67
-size 128704
+oid sha256:3434da1d2949e696a889493a5cf44794d17e0711dcb00b0f232e360059c16ad3
+size 128711
diff --git a/tests/rust/re_integration_test/tests/snapshots/drop_stream_to_view_3.png b/tests/rust/re_integration_test/tests/snapshots/drop_stream_to_view_3.png
index 6328ebadc49e..108c7fff304f 100644
--- a/tests/rust/re_integration_test/tests/snapshots/drop_stream_to_view_3.png
+++ b/tests/rust/re_integration_test/tests/snapshots/drop_stream_to_view_3.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:efa6ada596e20d6e3b03915aadac539b6d7fa08bb26d23251fe8d12e8680f965
-size 132186
+oid sha256:b96a10360f656a8a01f44820330c5d747593a0f2483f78310550a010f28d2db4
+size 132192
diff --git a/tests/rust/re_integration_test/tests/snapshots/drop_stream_to_view_4.png b/tests/rust/re_integration_test/tests/snapshots/drop_stream_to_view_4.png
index b02613d5df4c..c9e68703e356 100644
--- a/tests/rust/re_integration_test/tests/snapshots/drop_stream_to_view_4.png
+++ b/tests/rust/re_integration_test/tests/snapshots/drop_stream_to_view_4.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:ed4794b26a1917189c09d78fce463d2ad4359c1283003dbffed2648e67eb0f67
-size 128704
+oid sha256:3434da1d2949e696a889493a5cf44794d17e0711dcb00b0f232e360059c16ad3
+size 128711
diff --git a/tests/rust/re_integration_test/tests/snapshots/drop_stream_to_view_5.png b/tests/rust/re_integration_test/tests/snapshots/drop_stream_to_view_5.png
index 3fc8aa84d666..f357ed1e77b5 100644
--- a/tests/rust/re_integration_test/tests/snapshots/drop_stream_to_view_5.png
+++ b/tests/rust/re_integration_test/tests/snapshots/drop_stream_to_view_5.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:cff3385616430b1cb4928d9a19687fe004dadf00d1f4ac5cfed070470fa598f4
-size 130023
+oid sha256:8ce7c97a657989e2d628c1cd70e08eaa53303ee33a4a1a5b3ccc14b6589702dc
+size 130019
diff --git a/tests/rust/re_integration_test/tests/snapshots/drop_stream_to_view_6.png b/tests/rust/re_integration_test/tests/snapshots/drop_stream_to_view_6.png
index 6f4a413cb80a..6b37a88c535e 100644
--- a/tests/rust/re_integration_test/tests/snapshots/drop_stream_to_view_6.png
+++ b/tests/rust/re_integration_test/tests/snapshots/drop_stream_to_view_6.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:d24cd32b75c18f44d348f5a4066fe4aa35c82acb06f861e002d209357dc4afa0
-size 135600
+oid sha256:14c90c6be3b37825126421037b8d673d4b07865e716e7855fc92e5496615806c
+size 135605
diff --git a/tests/rust/re_integration_test/tests/snapshots/heuristics_mixed_all_root.png b/tests/rust/re_integration_test/tests/snapshots/heuristics_mixed_all_root.png
index 1eb31a8417cd..51c7c0f6719d 100644
--- a/tests/rust/re_integration_test/tests/snapshots/heuristics_mixed_all_root.png
+++ b/tests/rust/re_integration_test/tests/snapshots/heuristics_mixed_all_root.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:d42987f9e720a0c8ac88a7bdeef9c78af3032f13be8a504602eba70742b75c1c
-size 129323
+oid sha256:7ac4154224e88c1c92f8e1cf9c8052959ffc840fee08f66cb3865c063901afaf
+size 129337
diff --git a/tests/rust/re_integration_test/tests/snapshots/multi_container_deep_nested.png b/tests/rust/re_integration_test/tests/snapshots/multi_container_deep_nested.png
index 2efcfdbc3443..d027fdaca619 100644
--- a/tests/rust/re_integration_test/tests/snapshots/multi_container_deep_nested.png
+++ b/tests/rust/re_integration_test/tests/snapshots/multi_container_deep_nested.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:9fc7bc4b19f1c8c3c64f2c71951a99fb5acf1943f6a564cce42ad20f5088ba1a
-size 242652
+oid sha256:43135ce0fe8073bf2dc7989f62dcfc23480c7785292a7cf1bc6c2e6a6317a1b4
+size 242622
diff --git a/tests/rust/re_integration_test/tests/snapshots/multi_container_drag_container_1.png b/tests/rust/re_integration_test/tests/snapshots/multi_container_drag_container_1.png
index 62420fae7b75..e25b690bbf24 100644
--- a/tests/rust/re_integration_test/tests/snapshots/multi_container_drag_container_1.png
+++ b/tests/rust/re_integration_test/tests/snapshots/multi_container_drag_container_1.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:6a41387982542e29b0a70740bb41426aec4766dfe74c0f4378256c80b63d8fba
-size 251105
+oid sha256:c65134a4c7860803cb8a8efd8542473a64162aa42b30fd1b0ddbe2ec5922cc0c
+size 251088
diff --git a/tests/rust/re_integration_test/tests/snapshots/multi_container_drag_container_2.png b/tests/rust/re_integration_test/tests/snapshots/multi_container_drag_container_2.png
index 71c31c8dddaf..95188fa7657a 100644
--- a/tests/rust/re_integration_test/tests/snapshots/multi_container_drag_container_2.png
+++ b/tests/rust/re_integration_test/tests/snapshots/multi_container_drag_container_2.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:3784c0b0b71b3eba22d6b17c3772654a753f7bf19461629143fe26fa974ab481
-size 251914
+oid sha256:d4a15d1671c389cf8f06fbb3066d1a7fb5c247a341652c53c98bff524d7b7a5b
+size 251869
diff --git a/tests/rust/re_integration_test/tests/snapshots/multi_container_drag_container_3.png b/tests/rust/re_integration_test/tests/snapshots/multi_container_drag_container_3.png
index 802cd76a865a..bed9e4d5e41d 100644
--- a/tests/rust/re_integration_test/tests/snapshots/multi_container_drag_container_3.png
+++ b/tests/rust/re_integration_test/tests/snapshots/multi_container_drag_container_3.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:0b921289417aeefff9aee7f3b94dedab2c9e40433779dfc3dfd3502ca6f1568c
-size 252466
+oid sha256:ed18c4806ef9390b08b458169f18aa450baaed7723988ff900929a5f3edbc293
+size 252431
diff --git a/tests/rust/re_integration_test/tests/snapshots/multi_container_drag_container_4.png b/tests/rust/re_integration_test/tests/snapshots/multi_container_drag_container_4.png
index 07aafcdbd22f..4413b1776322 100644
--- a/tests/rust/re_integration_test/tests/snapshots/multi_container_drag_container_4.png
+++ b/tests/rust/re_integration_test/tests/snapshots/multi_container_drag_container_4.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:de0186ffb76599ae5509cdc8be63d008d3655a420e8ae8520b8e5961d66748cb
-size 251108
+oid sha256:21fe0589034cbf9247e1a5738d64eedca790f1fdf9fb5d41845d1ecb7dcda644
+size 251206
diff --git a/tests/rust/re_integration_test/tests/snapshots/multi_container_drag_container_5.png b/tests/rust/re_integration_test/tests/snapshots/multi_container_drag_container_5.png
index ce8063a2f162..c11ddd3df363 100644
--- a/tests/rust/re_integration_test/tests/snapshots/multi_container_drag_container_5.png
+++ b/tests/rust/re_integration_test/tests/snapshots/multi_container_drag_container_5.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:79183965bd5831a9823cabfd02d4753a1ad7d02095d7207b641e8b59a2cbccbc
-size 251585
+oid sha256:3f4db6150e08aa22dab165ece52abb3d8225877531cbe626f4271062ee65782c
+size 251251
diff --git a/tests/rust/re_integration_test/tests/snapshots/multi_container_drag_container_6.png b/tests/rust/re_integration_test/tests/snapshots/multi_container_drag_container_6.png
index 2d69a943276c..2df71065bdd4 100644
--- a/tests/rust/re_integration_test/tests/snapshots/multi_container_drag_container_6.png
+++ b/tests/rust/re_integration_test/tests/snapshots/multi_container_drag_container_6.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:0b74e0c135b0e755f41285f98cbf19c4ba721e7b339a1bfdc546094bcb055e95
-size 252913
+oid sha256:8c32d42417b6a6ecf80bb42d47ec7d3cc76c5e3bd7a95834e99b7fbbf6ed8488
+size 252858
diff --git a/tests/rust/re_integration_test/tests/snapshots/multi_container_drag_single_view_1.png b/tests/rust/re_integration_test/tests/snapshots/multi_container_drag_single_view_1.png
index 2a54ff6f98a8..180c646806aa 100644
--- a/tests/rust/re_integration_test/tests/snapshots/multi_container_drag_single_view_1.png
+++ b/tests/rust/re_integration_test/tests/snapshots/multi_container_drag_single_view_1.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:95ce4f4e7b982f54cfe0e6bcc6527fcb618997a51347699dce5d3076a8a0dc90
-size 252860
+oid sha256:fb8fead10c6d95d6a375d1bdfc4664a1e1f8faa1ce2cd08e6de2d3d4bb59b00d
+size 252904
diff --git a/tests/rust/re_integration_test/tests/snapshots/multi_container_drag_single_view_2.png b/tests/rust/re_integration_test/tests/snapshots/multi_container_drag_single_view_2.png
index 5d3bf1fd49e6..cee55b8c3203 100644
--- a/tests/rust/re_integration_test/tests/snapshots/multi_container_drag_single_view_2.png
+++ b/tests/rust/re_integration_test/tests/snapshots/multi_container_drag_single_view_2.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:9030ee0336693ce4093a305881b5299a0d526c177df16e1cbc2d350fed15ffea
-size 252760
+oid sha256:893e7ba22fbebf74c42d01b22cdf133f76bbc5aa21c244652a95b52e8c1e50ac
+size 252828
diff --git a/tests/rust/re_integration_test/tests/snapshots/multi_container_drag_single_view_3.png b/tests/rust/re_integration_test/tests/snapshots/multi_container_drag_single_view_3.png
index d400ccee7769..cd7ef425cfbb 100644
--- a/tests/rust/re_integration_test/tests/snapshots/multi_container_drag_single_view_3.png
+++ b/tests/rust/re_integration_test/tests/snapshots/multi_container_drag_single_view_3.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:f90ebe7a37192478378b139cbe023fc8f7ec95bca9524c65187cb6d69f8ceae4
-size 249431
+oid sha256:8e5e2782b942bdad2476344e59efa57dc2837bbe685bdf6faceb59d6ee60860b
+size 249262
diff --git a/tests/rust/re_integration_test/tests/snapshots/multi_container_many_views.png b/tests/rust/re_integration_test/tests/snapshots/multi_container_many_views.png
index 5ea0946aa958..8f64ac9739e3 100644
--- a/tests/rust/re_integration_test/tests/snapshots/multi_container_many_views.png
+++ b/tests/rust/re_integration_test/tests/snapshots/multi_container_many_views.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:a5e9971bcda5bf731e89f232cfc43f762c90491fb3cd5923c8f979eb7b6d204c
-size 490725
+oid sha256:da651ecf274e6570109ae588ec8064ce664134ea18203fb9c761c7b98d52893a
+size 490556
diff --git a/tests/rust/re_integration_test/tests/snapshots/per_visualizer_instruction_errors_1_warnings_only.png b/tests/rust/re_integration_test/tests/snapshots/per_visualizer_instruction_errors_1_warnings_only.png
index 0cbe222fb66a..6510e1df48d6 100644
--- a/tests/rust/re_integration_test/tests/snapshots/per_visualizer_instruction_errors_1_warnings_only.png
+++ b/tests/rust/re_integration_test/tests/snapshots/per_visualizer_instruction_errors_1_warnings_only.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:e7607e164c5aa675b8c466f81073bd4fa01fe3df4b80c245ecf18e735cb69ba6
-size 88230
+oid sha256:5a5d0bf3eab9567c6ac37dbb75c4d426c0ee713d9e7981746c0fcc6b7ed9cc69
+size 88272
diff --git a/tests/rust/re_integration_test/tests/snapshots/per_visualizer_instruction_errors_1b_warnings_only_menu.png b/tests/rust/re_integration_test/tests/snapshots/per_visualizer_instruction_errors_1b_warnings_only_menu.png
index 6f7c2750b06b..fa8d48513e7d 100644
--- a/tests/rust/re_integration_test/tests/snapshots/per_visualizer_instruction_errors_1b_warnings_only_menu.png
+++ b/tests/rust/re_integration_test/tests/snapshots/per_visualizer_instruction_errors_1b_warnings_only_menu.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:2ffbc9ce0a652d83b10c2f37ff9a1ee56cf5f9990421eb34e977931ecd77f2f7
-size 98901
+oid sha256:b119f26d308154d30193acf046db73cfce1bb2ba7200fdf3d715f396c710dcde
+size 98943
diff --git a/tests/rust/re_integration_test/tests/snapshots/per_visualizer_instruction_errors_2_warnings_and_errors_menu.png b/tests/rust/re_integration_test/tests/snapshots/per_visualizer_instruction_errors_2_warnings_and_errors_menu.png
index 832e24e8ae9e..10dcd7d1260c 100644
--- a/tests/rust/re_integration_test/tests/snapshots/per_visualizer_instruction_errors_2_warnings_and_errors_menu.png
+++ b/tests/rust/re_integration_test/tests/snapshots/per_visualizer_instruction_errors_2_warnings_and_errors_menu.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:a32996bddce39b388fa4a7564d2448eb14303032bd9e44cd154757d3dc515b04
-size 109391
+oid sha256:08e210368f8259b9ddcd2f58fd19aa597470fe9d022afaf4f95ee384bb7c9f4c
+size 109392
diff --git a/tests/rust/re_integration_test/tests/snapshots/per_visualizer_instruction_errors_2b_warnings_and_errors_dataresult.png b/tests/rust/re_integration_test/tests/snapshots/per_visualizer_instruction_errors_2b_warnings_and_errors_dataresult.png
index daa95747904a..5493c0837f76 100644
--- a/tests/rust/re_integration_test/tests/snapshots/per_visualizer_instruction_errors_2b_warnings_and_errors_dataresult.png
+++ b/tests/rust/re_integration_test/tests/snapshots/per_visualizer_instruction_errors_2b_warnings_and_errors_dataresult.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:7abeb8c1f78110b3b28b0afa1d34596ea93c5bbd298bbe4e4ef1d58eabf0a5e7
-size 106991
+oid sha256:6768062ff424107d085be8099d28a332495be680f1605926675b3aaa6893323d
+size 106926
diff --git a/tests/rust/re_integration_test/tests/snapshots/per_visualizer_instruction_errors_3_errors_only.png b/tests/rust/re_integration_test/tests/snapshots/per_visualizer_instruction_errors_3_errors_only.png
index ed900b1ada87..f98fcd4f47ff 100644
--- a/tests/rust/re_integration_test/tests/snapshots/per_visualizer_instruction_errors_3_errors_only.png
+++ b/tests/rust/re_integration_test/tests/snapshots/per_visualizer_instruction_errors_3_errors_only.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:b7ef8b6831426fffeb6691f6e0d44de91213233e56219d1901cf58cdd964c2ce
-size 103590
+oid sha256:9b718809ca05753fdd2bebaf8fff41ede19a10ce9ee2186a9b4b1ff21c4a2475
+size 103525
diff --git a/tests/rust/re_integration_test/tests/snapshots/per_visualizer_instruction_errors_3b_errors_only_menu.png b/tests/rust/re_integration_test/tests/snapshots/per_visualizer_instruction_errors_3b_errors_only_menu.png
index 75714a47b6aa..43157f6b7d72 100644
--- a/tests/rust/re_integration_test/tests/snapshots/per_visualizer_instruction_errors_3b_errors_only_menu.png
+++ b/tests/rust/re_integration_test/tests/snapshots/per_visualizer_instruction_errors_3b_errors_only_menu.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:6ae63981d9ffccd6e9bbd00753e44efe3585e9ddc7f28b21a89cdc09999d43b5
-size 108162
+oid sha256:45ec51453b4a5eb8a6ca344a3e0f0f5bc9b19777d014b2231e817b57dbecbd90
+size 108153
diff --git a/tests/rust/re_integration_test/tests/snapshots/resize_view_horizontal_1.png b/tests/rust/re_integration_test/tests/snapshots/resize_view_horizontal_1.png
index c6ab7190f3e8..777eb37e9fdf 100644
--- a/tests/rust/re_integration_test/tests/snapshots/resize_view_horizontal_1.png
+++ b/tests/rust/re_integration_test/tests/snapshots/resize_view_horizontal_1.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:18910e5b5890eed2812baf5ca12c585d7b18ebebb82dba58817e883de4ac3753
-size 195728
+oid sha256:f1a3e3d17324f18e5eb41c9980b0e9e5627344f58e27562f5ea224a312a8b288
+size 196101
diff --git a/tests/rust/re_integration_test/tests/snapshots/resize_view_horizontal_2.png b/tests/rust/re_integration_test/tests/snapshots/resize_view_horizontal_2.png
index 2a254edf480e..f0e0d3a38913 100644
--- a/tests/rust/re_integration_test/tests/snapshots/resize_view_horizontal_2.png
+++ b/tests/rust/re_integration_test/tests/snapshots/resize_view_horizontal_2.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:797710c2e9d600370f5624b9048f85403e927ab4e57c986290545fba8ccf0c1a
-size 196859
+oid sha256:bdb4afecabb85053599c1eb52b0c70426a530eaac7926d21e6bda648905fdca5
+size 197271
diff --git a/tests/rust/re_integration_test/tests/snapshots/resize_view_horizontal_3.png b/tests/rust/re_integration_test/tests/snapshots/resize_view_horizontal_3.png
index 97df8af70daa..a96cab3b4148 100644
--- a/tests/rust/re_integration_test/tests/snapshots/resize_view_horizontal_3.png
+++ b/tests/rust/re_integration_test/tests/snapshots/resize_view_horizontal_3.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:b47e82db26841511f9e3fd2af35b6c4919faf7b2b54add41311fbb91dcf66c62
-size 196282
+oid sha256:c03c9ac92f7a47e3de387c2255647b4cf5fb77b4b496517811069337acde5fb1
+size 196700
diff --git a/tests/rust/re_integration_test/tests/snapshots/resize_view_vertical_1.png b/tests/rust/re_integration_test/tests/snapshots/resize_view_vertical_1.png
index ecd111df9961..eb935787951d 100644
--- a/tests/rust/re_integration_test/tests/snapshots/resize_view_vertical_1.png
+++ b/tests/rust/re_integration_test/tests/snapshots/resize_view_vertical_1.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:fc431f3d56714f92386554e09e9f64af37e0183f8fa119fc6636c460e4ffd73f
-size 195285
+oid sha256:9c16ad686db9f33db57f796ca1edab1707ab2b7cc9f9312ac9cb6d03818627b7
+size 195778
diff --git a/tests/rust/re_integration_test/tests/snapshots/resize_view_vertical_2.png b/tests/rust/re_integration_test/tests/snapshots/resize_view_vertical_2.png
index 76289fea492f..8a0db7bf8838 100644
--- a/tests/rust/re_integration_test/tests/snapshots/resize_view_vertical_2.png
+++ b/tests/rust/re_integration_test/tests/snapshots/resize_view_vertical_2.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:3b18a9eda787b5e515b9ed79ac7446b9f7e5940f42c385663e4268cfa40fb788
-size 194558
+oid sha256:65246fb6b5e35a4e224f2eb252581621544b0e289e95adda6964acb2654b2844
+size 195135
diff --git a/tests/rust/re_integration_test/tests/snapshots/resize_view_vertical_3.png b/tests/rust/re_integration_test/tests/snapshots/resize_view_vertical_3.png
index 69f1590037ad..12949e94e69f 100644
--- a/tests/rust/re_integration_test/tests/snapshots/resize_view_vertical_3.png
+++ b/tests/rust/re_integration_test/tests/snapshots/resize_view_vertical_3.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:084ab60a1b0a36a417b3dd99766543ae5e7692e15820aabd20020f273e8210c7
-size 194939
+oid sha256:cf402083466737d156e524a957dd7bbc6dcbd953df7aa5a66276a266da049711
+size 195375
diff --git a/tests/rust/re_integration_test/tests/snapshots/series_count_exceeds_max.png b/tests/rust/re_integration_test/tests/snapshots/series_count_exceeds_max.png
index b341a984e7f3..1693aa6928ca 100644
--- a/tests/rust/re_integration_test/tests/snapshots/series_count_exceeds_max.png
+++ b/tests/rust/re_integration_test/tests/snapshots/series_count_exceeds_max.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:bdd83d5c5eea420dd02d8d90f958282c4941d7a961e7b5b2343a290ec3f53118
-size 1432631
+oid sha256:f4f6e8966b1d6890fa3ed40c3691f25aa99ddb9a17bb3505896c46220c165879
+size 1431584
diff --git a/tests/rust/re_integration_test/tests/snapshots/source_component_1.png b/tests/rust/re_integration_test/tests/snapshots/source_component_1.png
index 6df88518cfcd..afb8b1c37341 100644
--- a/tests/rust/re_integration_test/tests/snapshots/source_component_1.png
+++ b/tests/rust/re_integration_test/tests/snapshots/source_component_1.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:10d32b3718159a701d074567349a40960a28773d3d29f2ea30031b668de23428
-size 186458
+oid sha256:7767f98c34f6bce5f16dd7da369ba3367d15e17e6c10a9110f6bc60ca4a5094a
+size 186418
diff --git a/tests/rust/re_integration_test/tests/snapshots/source_component_2.png b/tests/rust/re_integration_test/tests/snapshots/source_component_2.png
index 43ec11b81a01..a2b3f235e536 100644
--- a/tests/rust/re_integration_test/tests/snapshots/source_component_2.png
+++ b/tests/rust/re_integration_test/tests/snapshots/source_component_2.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:4492f507144aa4204f759aaadb872860b4d9243fc1973c195d7b02fd0ef9d4de
-size 187964
+oid sha256:02ba178c500cab7aa322a8960dbc5a7510edad0f9cd401267bfe7e067742c6fa
+size 190684
diff --git a/tests/rust/re_integration_test/tests/snapshots/source_component_3.png b/tests/rust/re_integration_test/tests/snapshots/source_component_3.png
index f68b90bdfb27..e208597f19b3 100644
--- a/tests/rust/re_integration_test/tests/snapshots/source_component_3.png
+++ b/tests/rust/re_integration_test/tests/snapshots/source_component_3.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:9513b35a5a5c51690aafde116fa8f1e802e35ba24294ba09e54c4e600066e3da
-size 210083
+oid sha256:088b223edcbeb67176dafe18a9ff2cd439cf291868c2e463bbb542214ca9506f
+size 210131
diff --git a/tests/rust/re_integration_test/tests/snapshots/source_component_4.png b/tests/rust/re_integration_test/tests/snapshots/source_component_4.png
index a0aa38a4afb2..90ad3f920e11 100644
--- a/tests/rust/re_integration_test/tests/snapshots/source_component_4.png
+++ b/tests/rust/re_integration_test/tests/snapshots/source_component_4.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:1567a6cf92ff25901635848471cecc0bb864a768b282e28f41ed1500986de43e
-size 208702
+oid sha256:507b1ccae905499f6018403fad8dc84703eac0ee182a3cae053e357b52d677b5
+size 208685
diff --git a/tests/rust/re_integration_test/tests/snapshots/source_component_5.png b/tests/rust/re_integration_test/tests/snapshots/source_component_5.png
index ee6a3fe35abb..896233436dbd 100644
--- a/tests/rust/re_integration_test/tests/snapshots/source_component_5.png
+++ b/tests/rust/re_integration_test/tests/snapshots/source_component_5.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:e1e5f443625080420fa0f7d5e94d270dd609d0b80cac13383a74a5aa35eecd7d
-size 215633
+oid sha256:fac513d19b16696f2a872002ff870c86cfb71bd7495ee49e1ca7156319bc1378
+size 216542
diff --git a/tests/rust/re_integration_test/tests/snapshots/source_component_6.png b/tests/rust/re_integration_test/tests/snapshots/source_component_6.png
index 036b2b664038..e99025de3aa0 100644
--- a/tests/rust/re_integration_test/tests/snapshots/source_component_6.png
+++ b/tests/rust/re_integration_test/tests/snapshots/source_component_6.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:555a481e5c2a8f2fa349e5af3d9fff34e9b9831a98b4998a8cf2d0596fccc90a
-size 152321
+oid sha256:988035594fb1a161b3d336be840af2e9d0d10d9f26b7d0aafa15f95a8c635a20
+size 152054
diff --git a/tests/rust/re_integration_test/tests/snapshots/source_component_7.png b/tests/rust/re_integration_test/tests/snapshots/source_component_7.png
index 04463eb16ee1..4ffcb52bf033 100644
--- a/tests/rust/re_integration_test/tests/snapshots/source_component_7.png
+++ b/tests/rust/re_integration_test/tests/snapshots/source_component_7.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:15cb9b29a2f5d79f2be0ac3b556d1bc5fb01500aabcea93a2c6b609bf2737f34
-size 159775
+oid sha256:d703feb53fd1078f24102525308192c78742b12ef1e104ae18a3ba6192684c7b
+size 160022
diff --git a/tests/rust/re_integration_test/tests/snapshots/source_component_8.png b/tests/rust/re_integration_test/tests/snapshots/source_component_8.png
index 91938295ed39..65df226cd428 100644
--- a/tests/rust/re_integration_test/tests/snapshots/source_component_8.png
+++ b/tests/rust/re_integration_test/tests/snapshots/source_component_8.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:e7f6dec083f074da9fcaf64900cb36c4efa0e7bf2739cd32faa813df4f8a0139
-size 170462
+oid sha256:55d36697d114363d3491a15b975cbf4782b4139bf950b5836980497c16348f7d
+size 170148
diff --git a/tests/rust/re_integration_test/tests/snapshots/start_with_dataset_url.png b/tests/rust/re_integration_test/tests/snapshots/start_with_dataset_url.png
index efeb5ad9bcf1..f7f737534c1e 100644
--- a/tests/rust/re_integration_test/tests/snapshots/start_with_dataset_url.png
+++ b/tests/rust/re_integration_test/tests/snapshots/start_with_dataset_url.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:efb3346b0d5769cc6fb72026ea28972ae0c5f716b0b6bfc803f62d630d60bb4f
-size 43004
+oid sha256:0ec49132c789ae078927c78223939d7e476205f789a0e79b8e0c6f124601f778
+size 43005
diff --git a/tests/rust/re_integration_test/tests/snapshots/streams_context_single_select_3.png b/tests/rust/re_integration_test/tests/snapshots/streams_context_single_select_3.png
index 0974345dae8e..51b3c89f907b 100644
--- a/tests/rust/re_integration_test/tests/snapshots/streams_context_single_select_3.png
+++ b/tests/rust/re_integration_test/tests/snapshots/streams_context_single_select_3.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:2fd535bbfc63d59dec6311ee917da38264ec30dfd80578daa4f09352b7a79b80
-size 62810
+oid sha256:06803e74ec03f0aa0c6015b88ed130a57aed08602e8193b07ac48d2e74af6ef2
+size 63041
diff --git a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_1_initial.png b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_1_initial.png
index b3250bbcc937..357c9898b86e 100644
--- a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_1_initial.png
+++ b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_1_initial.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:fd4065e0a331f0c2a80c78902b8f54e2d9f68969924f94b2c4f5d3b939af18de
-size 176491
+oid sha256:05a20199c100f00f6c7bf3af90d821870c51510ca06750a4b59042b0e1012eab
+size 176401
diff --git a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_2_plots_view_selected.png b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_2_plots_view_selected.png
index 07fc0de7e104..fa64ae145d41 100644
--- a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_2_plots_view_selected.png
+++ b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_2_plots_view_selected.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:3f18926d749e5659d8b5e397721e6ed031e15eb15b9bcb690a4469ef5a1bc56a
-size 239257
+oid sha256:9c45f8e5dbe1324f8a51be5c69a3a10f05c9fecca55928ca245342f71ba9c74e
+size 239338
diff --git a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_3_other_view_selected.png b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_3_other_view_selected.png
index a4ecadc1f372..3475812f84d7 100644
--- a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_3_other_view_selected.png
+++ b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_3_other_view_selected.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:d54e0840579720d48573800747a21bfa7fe612f88a2074f596cc5c37c2e05853
-size 224296
+oid sha256:7b255a642e4d432f40bdb415827896491c0c5e6d45ccb0597c1ca091e60128b4
+size 224508
diff --git a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_4_hover_sin_line.png b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_4_hover_sin_line.png
index 5688f40f33c9..0b7e4bbf7309 100644
--- a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_4_hover_sin_line.png
+++ b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_4_hover_sin_line.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:1ad7e78ba0b6acee97a09b2231cce1f637b7c00fac301c3716e8a7dc5cfcac7f
-size 239226
+oid sha256:b576291191ec029ec6a2f590a05c352a4f38dd73bcdc6d7a10a0fc366e1339ba
+size 239044
diff --git a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_5_after_hide.png b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_5_after_hide.png
index 54f9bde63dda..b3f8e0bfb66b 100644
--- a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_5_after_hide.png
+++ b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_5_after_hide.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:54f80ef26989dcd7d0b5649730f502d7f8d6676bd321ae0d95342e6cd7c2b786
-size 233045
+oid sha256:507153b974bdf2299a930a3185c3448439850c47d6334fc5feeebdcb10990c6c
+size 232888
diff --git a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_6_hover_different_after_hide.png b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_6_hover_different_after_hide.png
index 54a4f81d7239..a492995d75f9 100644
--- a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_6_hover_different_after_hide.png
+++ b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_6_hover_different_after_hide.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:a82fb31e87860d40b9b1d26001cdcefd046cf8a69ca834cefadbc4ea92565a98
-size 236058
+oid sha256:e01cd38845bbaab7a7e693103823ee1ddc354bb15abd2d8435ed68691ed9fbef
+size 235913
diff --git a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_1_view_selected.png b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_1_view_selected.png
index ab41d01bac85..ce00d3619934 100644
--- a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_1_view_selected.png
+++ b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_1_view_selected.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:da3bf690e42ad320c03e316abe78c6e574a92680196f18856dd1be90ad2a9ffe
-size 173649
+oid sha256:ed172863ece32457608b53140d65146e210d3b7f8b493a06185af01a6cb923a7
+size 173529
diff --git a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_2_popup_open.png b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_2_popup_open.png
index cee55d70a2eb..2436802c1786 100644
--- a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_2_popup_open.png
+++ b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_2_popup_open.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:64d3bcbc044e5f67225abc60b5797b45b1291b3a09a2ebebe04959cf2bc7a61b
-size 172595
+oid sha256:e798b3aec15d7010b1347b64f3ebd88b1a741e500d996dfa59121a3111b38252
+size 172364
diff --git a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_1_starting_state.png b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_1_starting_state.png
index 558af32035bb..54b5bd9adf56 100644
--- a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_1_starting_state.png
+++ b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_1_starting_state.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:274bdbeb2e08fe2c582b0d91dc6e0472af7bb278e600bf5ce26ee65468ec474f
-size 198459
+oid sha256:72befdae015e006cb6b00f4f5493f573add88cda0783ae9e3224883d73f84ce4
+size 198557
diff --git a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_2_selected_visualizer.png b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_2_selected_visualizer.png
index 0831927e3aec..2c952690bc81 100644
--- a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_2_selected_visualizer.png
+++ b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_2_selected_visualizer.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:b90b53ae6388ddf16fb4a1af783995e16dc1685b67766e761c1370fb90369e69
-size 202791
+oid sha256:8e1f919ba28e7312c1b287c671458f806b4afa438ecd534aa7318740f85300a8
+size 202917
diff --git a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_3_removed_visualizer.png b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_3_removed_visualizer.png
index 695ff789ab2f..e5db7fbf9264 100644
--- a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_3_removed_visualizer.png
+++ b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_3_removed_visualizer.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:e9f7c7526d650f631dc45656cd90cb92932faab4dd451ee4b496da646c499644
-size 141944
+oid sha256:65b0ecce495c697504b918d3651a7d2add11f63c2ad87a545c66467faad9bece
+size 141809
diff --git a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_4_view_selected_again.png b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_4_view_selected_again.png
index 6d814efa2e85..d4f7a1ad7bf5 100644
--- a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_4_view_selected_again.png
+++ b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_4_view_selected_again.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:28088e31382cc45a2c9c3f79773b80a4fb23211fb9c367298810f5956cac01e9
-size 184443
+oid sha256:73adf5a295da95d9b47fb7b02029ec28c278f4b3137ec5260b0ea077178bd193
+size 184568
diff --git a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_5_popup_open.png b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_5_popup_open.png
index ed4db0825e07..251571b8c468 100644
--- a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_5_popup_open.png
+++ b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_5_popup_open.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:3bcf9b099fed14b0360872210a2602fd20d2f9342cdc0d70f8f6493f6a0c9966
-size 209439
+oid sha256:4ce6b6f4a1d53d95e2ca51b476601df10240d6d7d22c757c12b2a576fb99748f
+size 209511
diff --git a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_6_first_added.png b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_6_first_added.png
index bc8be0a31b99..44b51240b6b5 100644
--- a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_6_first_added.png
+++ b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_6_first_added.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:d90d49ccf11fd7d35fbe8bc4d0c7343397556904ac7ae4f57859eb87c4f5e6ec
-size 196453
+oid sha256:270c883d6fbe47cbd8e0e65e93147510dc65534968642e0f15c3cd6b2fc2ff4d
+size 196471
diff --git a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_7_second_added.png b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_7_second_added.png
index bbc274376f02..78836c24e307 100644
--- a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_7_second_added.png
+++ b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_7_second_added.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:2a428592b3252c3cfc68347d54c6986b77ae7fe0a629d61536a6ad5e138d4419
-size 198689
+oid sha256:3659639c6c6142656f750aa2d53ce8b61c92315a1bd535e5625afc2c0e30e05f
+size 198705
diff --git a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_ctx_menu_1_open.png b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_ctx_menu_1_open.png
index 6ea480ecd5d7..c96c7371e547 100644
--- a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_ctx_menu_1_open.png
+++ b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_ctx_menu_1_open.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:bd7273dc7206bda3da6e3a99ce7c7f1fd966af7e08ac144f16c0f5010b772297
-size 179910
+oid sha256:9e54f3a0b02a1f4b4a891479e5cbb4683245a0eb6f818c253b445fad6d4b798f
+size 179866
diff --git a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_ctx_menu_2_after_hide.png b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_ctx_menu_2_after_hide.png
index 585cc8d9e2aa..9be6af46aa62 100644
--- a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_ctx_menu_2_after_hide.png
+++ b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_ctx_menu_2_after_hide.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:6aa5684b67ce70d3f84eca4b82e0acdd276b06addcd823e3e84524ae9ef0708b
-size 161711
+oid sha256:9b150fc1e08ebf9a3e9b0234a52645738a005eedb5e006284e4005b18e5cfa58
+size 161550
diff --git a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_ctx_menu_3_show_option.png b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_ctx_menu_3_show_option.png
index 09a36e6b2df8..97520e9c7c1c 100644
--- a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_ctx_menu_3_show_option.png
+++ b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_ctx_menu_3_show_option.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:15cf121517187d0f145ff2f084cbe9fd65991650f2fbf692e54c652627c05341
-size 166285
+oid sha256:bdb3b59fd39423e1643b60b8d64684fd5057c102dca1a2dd5717a4663ebfd613
+size 166129
diff --git a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_ctx_menu_4_after_show.png b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_ctx_menu_4_after_show.png
index e084955e27f7..cc35deee0625 100644
--- a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_ctx_menu_4_after_show.png
+++ b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_ctx_menu_4_after_show.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:3126dfb7a0c6631faca6d5b05e66e03556f0c6249ada70a77b362c169fd83a79
-size 173616
+oid sha256:df70c4b9972c767e78b7d29cc4c5e32056a2316e9f6de46843b05713f9d37052
+size 173504
diff --git a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_ctx_menu_5_after_remove.png b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_ctx_menu_5_after_remove.png
index aeb471c6f406..399c3359cf05 100644
--- a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_ctx_menu_5_after_remove.png
+++ b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_ctx_menu_5_after_remove.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:dd4b3481d06a8211b398323759cff5a7e566a3d96665c7a7ee7c4ee8ee4d7fa1
-size 158119
+oid sha256:9254753994908ab05e93be4f0ae158680dfc3028d26c67df7a1744c3f7e3f42e
+size 157941
diff --git a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_multi_scalar_view_selected.png b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_multi_scalar_view_selected.png
index 81a34a486034..8ddb96382242 100644
--- a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_multi_scalar_view_selected.png
+++ b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_multi_scalar_view_selected.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:e6bd265abffcd522fcbc6a011388361fda11c60921c5f9e84e74a1a48f19d1b4
-size 240226
+oid sha256:aef308f4db2a927b30e407eb00b9a846a486efb493570db58a3af027629781c0
+size 240254

From ac9509840b9024ac1538270a7b82d19da649fa11 Mon Sep 17 00:00:00 2001
From: Emil Ernerfeldt 
Date: Thu, 12 Mar 2026 22:29:42 +0100
Subject: [PATCH 120/513] Add `rerun download` to download full recording from
 server

### What
`rerun download $url` downloads the URL from either Rerun Cloud, the OSS
server, or just off of http.

Source-Ref: ced460056c9840f7de524db25336a70cbe5b03d4
---
 .../store/re_data_source/src/data_source.rs   |  23 ++-
 crates/store/re_redap_client/src/grpc.rs      |  78 +++++++-
 crates/store/re_redap_client/src/lib.rs       |   2 +-
 crates/top/rerun/src/commands/download.rs     | 173 ++++++++++++++++++
 crates/top/rerun/src/commands/entrypoint.rs   |  10 +
 crates/top/rerun/src/commands/mod.rs          |   2 +
 docs/content/reference/cli.md                 |  21 +++
 7 files changed, 296 insertions(+), 13 deletions(-)
 create mode 100644 crates/top/rerun/src/commands/download.rs

diff --git a/crates/store/re_data_source/src/data_source.rs b/crates/store/re_data_source/src/data_source.rs
index 0c07e9b7efd6..ba018764d448 100644
--- a/crates/store/re_data_source/src/data_source.rs
+++ b/crates/store/re_data_source/src/data_source.rs
@@ -221,6 +221,20 @@ impl LogDataSource {
         self,
         on_auth_err: AuthErrorHandler,
         connection_registry: &ConnectionRegistryHandle,
+    ) -> anyhow::Result {
+        self.stream_with_options(
+            on_auth_err,
+            connection_registry,
+            re_redap_client::StreamingOptions::default(),
+        )
+    }
+
+    /// Like [`Self::stream`], but with additional options controlling streaming behavior.
+    pub fn stream_with_options(
+        self,
+        on_auth_err: AuthErrorHandler,
+        connection_registry: &ConnectionRegistryHandle,
+        streaming_options: re_redap_client::StreamingOptions,
     ) -> anyhow::Result {
         re_tracing::profile_function!();
 
@@ -313,8 +327,13 @@ impl LogDataSource {
                 let uri_clone = uri.clone();
                 let stream_segment = async move {
                     let client = connection_registry.client(uri_clone.origin.clone()).await?;
-                    re_redap_client::stream_blueprint_and_segment_from_server(client, tx, uri_clone)
-                        .await
+                    re_redap_client::stream_blueprint_and_segment_from_server(
+                        client,
+                        tx,
+                        uri_clone,
+                        streaming_options,
+                    )
+                    .await
                 };
 
                 spawn_future(async move {
diff --git a/crates/store/re_redap_client/src/grpc.rs b/crates/store/re_redap_client/src/grpc.rs
index 7a8b7f69d584..b4f475a84c2c 100644
--- a/crates/store/re_redap_client/src/grpc.rs
+++ b/crates/store/re_redap_client/src/grpc.rs
@@ -317,6 +317,34 @@ where
     })
 }
 
+/// Callback invoked as chunks are downloaded.
+///
+/// Arguments: `(total_bytes_downloaded, total_bytes_expected)`.
+/// `total_bytes_expected` may be `None` if the total size is not known.
+pub type ProgressCallback = std::sync::Arc) + Send + Sync>;
+
+/// Options that control how segment data is streamed from the server.
+#[derive(Clone, Default)]
+pub struct StreamingOptions {
+    /// If `true`, download all chunks eagerly instead of relying on
+    /// on-demand streaming via the RRD manifest.
+    ///
+    /// This is useful for downloading a full recording to disk.
+    pub force_full_download: bool,
+
+    /// Optional callback invoked as chunks are downloaded.
+    pub on_progress: Option,
+}
+
+impl std::fmt::Debug for StreamingOptions {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        f.debug_struct("StreamingOptions")
+            .field("force_full_download", &self.force_full_download)
+            .field("on_progress", &self.on_progress.as_ref().map(|_| "…"))
+            .finish()
+    }
+}
+
 /// Canonical way to ingest segment data from a Rerun Data Platform server, dealing with
 /// server-stored blueprints if any.
 ///
@@ -330,6 +358,7 @@ pub async fn stream_blueprint_and_segment_from_server(
     mut client: ConnectionClient,
     tx: re_log_channel::LogSender,
     uri: re_uri::DatasetSegmentUri,
+    options: StreamingOptions,
 ) -> ApiResult {
     re_log::debug!("Loading {uri}…");
 
@@ -355,6 +384,7 @@ pub async fn stream_blueprint_and_segment_from_server(
             store_version: None,
         };
 
+        // Blueprints are always fully downloaded regardless of streaming options.
         if stream_segment_from_server(
             &mut client,
             blueprint_store_info,
@@ -362,6 +392,7 @@ pub async fn stream_blueprint_and_segment_from_server(
             blueprint_dataset,
             blueprint_segment,
             re_uri::Fragment::default(),
+            &StreamingOptions::default(),
         )
         .await?
         .is_break()
@@ -408,6 +439,7 @@ pub async fn stream_blueprint_and_segment_from_server(
         dataset_id.into(),
         segment_id.into(),
         fragment,
+        &options,
     )
     .await?
     .is_break()
@@ -426,6 +458,7 @@ async fn stream_segment_from_server(
     dataset_id: EntryId,
     segment_id: SegmentId,
     fragment: re_uri::Fragment,
+    options: &StreamingOptions,
 ) -> ApiResult> {
     let store_id = store_info.store_id.clone();
 
@@ -531,16 +564,16 @@ async fn stream_segment_from_server(
             }
 
             match store_id.kind() {
-                StoreKind::Recording => {
+                StoreKind::Recording if !options.force_full_download => {
                     re_log::debug!("Letting the viewer load chunks on-demand");
                     return Ok(ControlFlow::Continue(()));
                 }
-                StoreKind::Blueprint => {
+                StoreKind::Recording | StoreKind::Blueprint => {
                     // Load all of the chunks in one go; most important first:
                     let batch = sort_batch(rrd_manifest.data()).map_err(|err| {
                         ApiError::invalid_arguments_with_source(err, "Failed to sort chunk index")
                     })?;
-                    return load_chunks(client, tx, &store_id, batch).await;
+                    return load_chunks(client, tx, &store_id, batch, options).await;
                 }
             }
         }
@@ -604,7 +637,10 @@ async fn stream_segment_from_server(
                 );
             }
 
-            if load_chunks(client, tx, &store_id, batch).await?.is_break() {
+            if load_chunks(client, tx, &store_id, batch, options)
+                .await?
+                .is_break()
+            {
                 return Ok(ControlFlow::Break(()));
             }
         }
@@ -655,9 +691,9 @@ async fn stream_segment_from_server(
         let filtered_batch = re_arrow_util::take_record_batch(&batch, &filtered_indices)
             .map_err(|err| ApiError::invalid_arguments_with_source(err, "take_record_batch"))?;
 
-        load_chunks(client, tx, &store_id, filtered_batch).await
+        load_chunks(client, tx, &store_id, filtered_batch, options).await
     } else {
-        load_chunks(client, tx, &store_id, batch).await
+        load_chunks(client, tx, &store_id, batch, options).await
     }
 }
 
@@ -674,6 +710,7 @@ async fn load_chunks(
     tx: &re_log_channel::LogSender,
     store_id: &StoreId,
     full_batch: RecordBatch,
+    options: &StreamingOptions,
 ) -> ApiResult> {
     re_log::trace!("Requesting {} chunks from server…", full_batch.num_rows());
 
@@ -682,6 +719,7 @@ async fn load_chunks(
     // Batch requests in groups of N=32 rows.
     const BATCH_SIZE: usize = 32;
     let num_rows = full_batch.num_rows();
+    let total_size_bytes = total_size_bytes_from_batch(&full_batch);
     let mut futures = FuturesUnordered::new();
 
     for start in (0..num_rows).step_by(BATCH_SIZE) {
@@ -697,8 +735,16 @@ async fn load_chunks(
         });
     }
 
+    let mut downloaded_bytes: u64 = 0;
+
     while let Some(res) = futures::stream::StreamExt::next(&mut futures).await {
-        let result = res?;
+        let (result, batch_bytes) = res?;
+
+        downloaded_bytes += batch_bytes;
+        if let Some(on_progress) = &options.on_progress {
+            on_progress(downloaded_bytes, total_size_bytes);
+        }
+
         if result.is_break() {
             return Ok(ControlFlow::Break(()));
         }
@@ -709,18 +755,30 @@ async fn load_chunks(
     Ok(ControlFlow::Continue(()))
 }
 
+/// Try to extract total deflated size from the batch's `chunk_byte_size` column.
+fn total_size_bytes_from_batch(batch: &RecordBatch) -> Option {
+    let col = batch.column_by_name("chunk_byte_size")?;
+    let array = col.as_primitive_opt::()?;
+    Some(array.iter().map(|v| v.unwrap_or(0)).sum())
+}
+
+/// Returns `(control_flow, bytes_downloaded)`.
 async fn load_small_chunk_batch(
     client: &mut ConnectionClient,
     tx: &re_log_channel::LogSender,
     store_id: &StoreId,
     batch: &RecordBatch,
-) -> ApiResult> {
+) -> ApiResult<(ControlFlow<()>, u64)> {
     // TODO(RR-3323): FetchChunks should expose a proper bidirectional streaming path on native.
     let chunk_stream = client.fetch_segment_chunks_by_id(batch).await?;
     let mut chunk_stream = fetch_chunks_response_to_chunk_and_segment_id(chunk_stream);
 
+    let mut batch_bytes: u64 = 0;
+
     while let Some(chunks) = chunk_stream.next().await {
         for (chunk, _partition_id) in chunks? {
+            batch_bytes += chunk.heap_size_bytes();
+
             if tx
                 .send(
                     LogMsg::ArrowMsg(
@@ -738,12 +796,12 @@ async fn load_small_chunk_batch(
                 .is_err()
             {
                 re_log::debug!("Receiver disconnected");
-                return Ok(ControlFlow::Break(()));
+                return Ok((ControlFlow::Break(()), batch_bytes));
             }
         }
     }
 
-    Ok(ControlFlow::Continue(()))
+    Ok((ControlFlow::Continue(()), batch_bytes))
 }
 
 fn sort_batch(batch: &RecordBatch) -> Result {
diff --git a/crates/store/re_redap_client/src/lib.rs b/crates/store/re_redap_client/src/lib.rs
index 5cd4b50c51af..7ae9aa3e9b60 100644
--- a/crates/store/re_redap_client/src/lib.rs
+++ b/crates/store/re_redap_client/src/lib.rs
@@ -10,7 +10,7 @@ pub use self::connection_registry::{
     CredentialSource, Credentials, SourcedCredentials,
 };
 pub use self::grpc::{
-    RedapClient, channel, fetch_chunks_response_to_chunk_and_segment_id,
+    RedapClient, StreamingOptions, channel, fetch_chunks_response_to_chunk_and_segment_id,
     stream_blueprint_and_segment_from_server,
 };
 
diff --git a/crates/top/rerun/src/commands/download.rs b/crates/top/rerun/src/commands/download.rs
new file mode 100644
index 000000000000..84119fdbdd66
--- /dev/null
+++ b/crates/top/rerun/src/commands/download.rs
@@ -0,0 +1,173 @@
+use std::sync::Arc;
+use std::sync::atomic::{AtomicU64, Ordering};
+
+use re_data_source::{FromUriOptions, LogDataSource};
+use re_log_channel::DataSourceMessage;
+
+/// Download recordings and save them as `.rrd` files.
+///
+/// Supports any URI that Rerun can load: gRPC dataset segment URLs,
+/// HTTP URLs to `.rrd` files, local file paths, etc.
+#[derive(Debug, Clone, clap::Parser)]
+pub struct DownloadCommand {
+    /// One or more URIs to download.
+    #[clap(required = true)]
+    urls: Vec,
+
+    /// Override the output directory for the downloaded `.rrd` files.
+    ///
+    /// Defaults to the current working directory.
+    #[clap(short, long)]
+    output_dir: Option,
+}
+
+impl DownloadCommand {
+    pub fn run(self, _tokio_runtime: &tokio::runtime::Handle) -> anyhow::Result<()> {
+        let output_dir = self
+            .output_dir
+            .unwrap_or_else(|| std::path::PathBuf::from("."));
+
+        if !output_dir.exists() {
+            std::fs::create_dir_all(&output_dir)?;
+        }
+
+        let connection_registry =
+            re_redap_client::ConnectionRegistry::new_with_stored_credentials();
+
+        for url in &self.urls {
+            let data_source = LogDataSource::from_uri(
+                re_log_types::FileSource::Cli,
+                url,
+                &FromUriOptions {
+                    follow: false,
+                    accept_extensionless_http: true,
+                },
+            );
+
+            let Some(data_source) = data_source else {
+                anyhow::bail!("Could not interpret URI: {url}");
+            };
+
+            let output_path = output_dir.join(output_filename(&data_source, url));
+
+            // Register stored credentials for gRPC dataset segments.
+            if let LogDataSource::RedapDatasetSegment { ref uri, .. } = data_source {
+                connection_registry
+                    .set_credentials(&uri.origin, re_redap_client::Credentials::Stored);
+            }
+
+            let on_auth_err: re_data_source::AuthErrorHandler = Arc::new(|uri, err| {
+                re_log::error!(%uri, "Authentication error: {err}");
+            });
+
+            let downloaded = Arc::new(AtomicU64::new(0));
+            let downloaded_for_progress = downloaded.clone();
+
+            let streaming_options = re_redap_client::StreamingOptions {
+                force_full_download: true,
+                on_progress: Some(Arc::new(move |bytes_downloaded, total_bytes| {
+                    downloaded_for_progress.store(bytes_downloaded, Ordering::Relaxed);
+                    match total_bytes {
+                        Some(total) => {
+                            let percent = if 0 < total {
+                                100.0 * bytes_downloaded as f64 / total as f64
+                            } else {
+                                100.0
+                            };
+                            eprint!(
+                                "\r  {:.1}% ({} / {})",
+                                percent,
+                                re_format::format_bytes(bytes_downloaded as _),
+                                re_format::format_bytes(total as _),
+                            );
+                        }
+                        None => {
+                            eprint!("\r  {}", re_format::format_bytes(bytes_downloaded as _),);
+                        }
+                    }
+                })),
+            };
+
+            let rx = data_source.stream_with_options(
+                on_auth_err,
+                &connection_registry,
+                streaming_options,
+            )?;
+
+            eprintln!("Downloading {url}…");
+
+            save_to_rrd(&rx, &output_path)?;
+
+            let total = downloaded.load(Ordering::Relaxed);
+            if 0 < total {
+                // Clear the progress line
+                eprint!("\r\x1b[2K");
+            }
+
+            eprintln!("Saved {output_path:?}");
+        }
+
+        Ok(())
+    }
+}
+
+/// Derive an output `.rrd` filename from the data source.
+fn output_filename(data_source: &LogDataSource, original_url: &str) -> std::path::PathBuf {
+    match data_source {
+        LogDataSource::RedapDatasetSegment { uri, .. } => format!("{}.rrd", uri.segment_id).into(),
+
+        #[cfg(not(target_arch = "wasm32"))]
+        LogDataSource::FilePath { path, .. } => path
+            .file_name()
+            .map(Into::into)
+            .unwrap_or_else(|| "output.rrd".into()),
+
+        LogDataSource::HttpUrl { url, .. } => {
+            let path = url.path();
+            let filename = path.rsplit('/').next().unwrap_or("output.rrd");
+            if filename.is_empty() {
+                "output.rrd".into()
+            } else if filename.ends_with(".rrd") || filename.ends_with(".rbl") {
+                filename.into()
+            } else {
+                format!("{filename}.rrd").into()
+            }
+        }
+
+        _ => {
+            re_log::warn!("Cannot derive filename from {original_url:?}, using fallback");
+            "output.rrd".into()
+        }
+    }
+}
+
+/// Receive all messages from the channel and write them to an `.rrd` file.
+fn save_to_rrd(
+    rx: &re_log_channel::LogReceiver,
+    output_path: &std::path::Path,
+) -> anyhow::Result<()> {
+    let encoding_options = re_log_encoding::rrd::EncodingOptions::PROTOBUF_COMPRESSED;
+    let file = std::fs::File::create(output_path)?;
+    let mut encoder = re_log_encoding::Encoder::new_eager(
+        re_build_info::CrateVersion::LOCAL,
+        encoding_options,
+        file,
+    )?;
+
+    while let Ok(msg) = rx.recv() {
+        if let Some(payload) = msg.into_data() {
+            match payload {
+                DataSourceMessage::LogMsg(log_msg) => {
+                    encoder.append(&log_msg)?;
+                }
+                other => {
+                    re_log::trace!("Skipping {} (not storable in .rrd)", other.variant_name());
+                }
+            }
+        }
+    }
+
+    encoder.finish()?;
+
+    Ok(())
+}
diff --git a/crates/top/rerun/src/commands/entrypoint.rs b/crates/top/rerun/src/commands/entrypoint.rs
index 51215716fb98..c8c275cfeaa1 100644
--- a/crates/top/rerun/src/commands/entrypoint.rs
+++ b/crates/top/rerun/src/commands/entrypoint.rs
@@ -14,6 +14,7 @@ use super::auth::AuthCommands;
 use crate::CallSource;
 #[cfg(feature = "analytics")]
 use crate::commands::AnalyticsCommands;
+use crate::commands::DownloadCommand;
 #[cfg(feature = "data_loaders")]
 use crate::commands::McapCommands;
 use crate::commands::RrdCommands;
@@ -581,6 +582,11 @@ enum Command {
     #[command(subcommand)]
     Auth(AuthCommands),
 
+    /// Download recordings and save them as .rrd files.
+    ///
+    /// Supports downloading from Rerun Cloud as well as any other supported URI.
+    Download(DownloadCommand),
+
     /// Generates the Rerun CLI manual (markdown).
     ///
     /// Example: `rerun man > docs/content/reference/cli.md`
@@ -694,6 +700,8 @@ where
             #[cfg(feature = "analytics")]
             Command::Analytics(analytics) => analytics.run().map_err(Into::into),
 
+            Command::Download(cmd) => cmd.run(tokio_runtime.handle()),
+
             Command::Manual => {
                 let man = Args::generate_markdown_manual();
                 let web_header = unindent::unindent(
@@ -1753,6 +1761,8 @@ fn record_cli_command_analytics(args: &Args) {
             ("mcap", Some(subcommand))
         }
 
+        Some(Command::Download(_)) => ("download", None),
+
         #[cfg(feature = "native_viewer")]
         Some(Command::Reset) => ("reset", None),
 
diff --git a/crates/top/rerun/src/commands/mod.rs b/crates/top/rerun/src/commands/mod.rs
index ab71859d7c20..73bd90052e87 100644
--- a/crates/top/rerun/src/commands/mod.rs
+++ b/crates/top/rerun/src/commands/mod.rs
@@ -23,6 +23,7 @@ impl CallSource {
 
 #[cfg(feature = "auth")]
 mod auth;
+mod download;
 
 mod entrypoint;
 #[cfg(feature = "data_loaders")]
@@ -35,6 +36,7 @@ mod analytics;
 
 #[cfg(feature = "analytics")]
 pub(crate) use self::analytics::AnalyticsCommands;
+pub use self::download::DownloadCommand;
 pub use self::entrypoint::run;
 #[cfg(feature = "data_loaders")]
 pub use self::mcap::McapCommands;
diff --git a/docs/content/reference/cli.md b/docs/content/reference/cli.md
index 3e8cbaf24fbc..a4eec695fbca 100644
--- a/docs/content/reference/cli.md
+++ b/docs/content/reference/cli.md
@@ -18,6 +18,7 @@ The Rerun command-line interface:
 
 * `analytics`: Configure the behavior of our analytics.
 * `auth`: Authentication with the redap.
+* `download`: Download recordings and save them as .rrd files.
 * `man`: Generates the Rerun CLI manual (markdown).
 * `mcap`: Manipulate the contents of .mcap files.
 * `reset`: Reset the memory of the Rerun Viewer.
@@ -324,6 +325,26 @@ It's closer to an API key than an access token, as it can be revoked before it e
 >
 > [Default: `read`]
 
+## rerun download
+
+Download recordings and save them as .rrd files.
+
+Supports downloading from Rerun Cloud as well as any other supported URI.
+
+**Usage**: `rerun download [OPTIONS] …`
+
+**Arguments**
+
+* ``
+> One or more URIs to download.
+
+**Options**
+
+* `-o, --output-dir `
+> Override the output directory for the downloaded `.rrd` files.
+>
+> Defaults to the current working directory.
+
 ## rerun mcap
 
 Manipulate the contents of .mcap files.

From f2bc7b2d7b234e335c87a7525e2fb106ebe0bb68 Mon Sep 17 00:00:00 2001
From: Emil Ernerfeldt 
Date: Thu, 12 Mar 2026 23:05:05 +0100
Subject: [PATCH 121/513] Rename `Caches` to `Memoizers` and put inside a
 parent `StoreCache`

The goal is that all store subscribers should go into the `StoreCache`

---------

Source-Ref: 71dd376f3c1b71eceadf1fba844225d52a09e1bb
Co-authored-by: Claude Opus 4.6 
---
 .../src/component_fallbacks.rs                |  8 +-
 .../re_component_ui/src/transform_frame_id.rs |  2 +-
 .../src/variant_uis/redap_thumbnail.rs        |  8 +-
 crates/viewer/re_data_ui/src/image_ui.rs      | 29 +++----
 crates/viewer/re_data_ui/src/tensor_ui.rs     |  4 +-
 .../re_data_ui/src/transform_frames_ui.rs     |  2 +-
 crates/viewer/re_data_ui/src/video_ui.rs      | 26 +++---
 .../re_selection_panel/src/selection_panel.rs |  2 +-
 crates/viewer/re_test_context/src/lib.rs      |  2 +-
 .../src/contexts/transform_tree_context.rs    |  6 +-
 .../re_view_spatial/src/shared_fallbacks.rs   |  2 +-
 .../src/visualizers/assets3d.rs               |  2 +-
 .../src/visualizers/depth_images.rs           |  3 +-
 .../src/visualizers/encoded_depth_image.rs    |  2 +-
 .../src/visualizers/encoded_image.rs          |  2 +-
 .../re_view_spatial/src/visualizers/meshes.rs |  2 +-
 .../visualizers/utilities/proc_mesh_vis.rs    | 10 +--
 .../visualizers/utilities/textured_rect.rs    |  3 +-
 .../video/video_frame_reference.rs            |  2 +-
 .../src/visualizers/video/video_stream.rs     |  3 +-
 .../viewer/re_view_tensor/src/view_class.rs   |  2 +-
 crates/viewer/re_viewer/src/app.rs            |  6 +-
 .../src/active_store_context.rs               | 13 ++-
 .../src/cache/cache_trait.rs                  | 38 +++++++++
 .../src/cache/{caches.rs => memoizers.rs}     | 60 ++++---------
 .../viewer/re_viewer_context/src/cache/mod.rs | 10 ++-
 .../src/cache/store_cache.rs                  | 84 +++++++++++++++++++
 .../re_viewer_context/src/image_info.rs       |  6 +-
 crates/viewer/re_viewer_context/src/lib.rs    |  2 +-
 .../viewer/re_viewer_context/src/store_hub.rs | 81 +++++++++---------
 .../src/store_view_context.rs                 | 11 ++-
 .../benches/data_query.rs                     |  4 +-
 .../src/view_contents.rs                      |  4 +-
 33 files changed, 269 insertions(+), 172 deletions(-)
 create mode 100644 crates/viewer/re_viewer_context/src/cache/cache_trait.rs
 rename crates/viewer/re_viewer_context/src/cache/{caches.rs => memoizers.rs} (77%)
 create mode 100644 crates/viewer/re_viewer_context/src/cache/store_cache.rs

diff --git a/crates/viewer/re_component_fallbacks/src/component_fallbacks.rs b/crates/viewer/re_component_fallbacks/src/component_fallbacks.rs
index 2a41033a404a..d16474032cdc 100644
--- a/crates/viewer/re_component_fallbacks/src/component_fallbacks.rs
+++ b/crates/viewer/re_component_fallbacks/src/component_fallbacks.rs
@@ -293,7 +293,7 @@ pub fn archetype_field_fallbacks(registry: &mut FallbackProviderRegistry) {
                         re_sdk_types::image::ImageKind::Depth,
                     );
                     let cache = ctx.store_ctx().caches;
-                    let image_stats = cache.entry(|c: &mut ImageStatsCache| c.entry(&image));
+                    let image_stats = cache.memoizer(|c: &mut ImageStatsCache| c.entry(&image));
                     let default_range =
                         ColormapWithRange::default_range_for_depth_images(&image_stats);
                     return [default_range[0] as f64, default_range[1] as f64].into();
@@ -333,7 +333,7 @@ pub fn archetype_field_fallbacks(registry: &mut FallbackProviderRegistry) {
 
                 let cache = ctx.store_ctx().caches;
                 let blob_bytes = blob.0.to_vec();
-                if let Ok(image) = cache.entry(|c: &mut ImageDecodeCache| {
+                if let Ok(image) = cache.memoizer(|c: &mut ImageDecodeCache| {
                     c.entry_encoded_depth(
                         row_id,
                         archetypes::EncodedDepthImage::descriptor_blob().component,
@@ -341,7 +341,7 @@ pub fn archetype_field_fallbacks(registry: &mut FallbackProviderRegistry) {
                         media_type.as_ref(),
                     )
                 }) {
-                    let image_stats = cache.entry(|c: &mut ImageStatsCache| c.entry(&image));
+                    let image_stats = cache.memoizer(|c: &mut ImageStatsCache| c.entry(&image));
                     let default_range =
                         ColormapWithRange::default_range_for_depth_images(&image_stats);
                     return [default_range[0] as f64, default_range[1] as f64].into();
@@ -394,7 +394,7 @@ pub fn archetype_field_fallbacks(registry: &mut FallbackProviderRegistry) {
                     archetypes::Tensor::descriptor_data().component,
                 )
             {
-                let tensor_stats = ctx.store_ctx().caches.entry(|c: &mut TensorStatsCache| {
+                let tensor_stats = ctx.store_ctx().memoizer(|c: &mut TensorStatsCache| {
                     c.entry(re_log_types::hash::Hash64::hash(row_id), &tensor)
                 });
                 tensor_data_range_heuristic(&tensor_stats, tensor.dtype())
diff --git a/crates/viewer/re_component_ui/src/transform_frame_id.rs b/crates/viewer/re_component_ui/src/transform_frame_id.rs
index ddb402a3399a..243960c73f94 100644
--- a/crates/viewer/re_component_ui/src/transform_frame_id.rs
+++ b/crates/viewer/re_component_ui/src/transform_frame_id.rs
@@ -21,7 +21,7 @@ pub fn edit_or_view_transform_frame_id(
             let suggestions = if let Some(store_ctx) = ctx.active_store_context {
                 let caches = store_ctx.caches;
                 let frame_id_registry =
-                    caches.entry(|c: &mut re_viewer_context::TransformDatabaseStoreCache| {
+                    caches.memoizer(|c: &mut re_viewer_context::TransformDatabaseStoreCache| {
                         c.frame_id_registry(store_ctx.recording)
                     });
 
diff --git a/crates/viewer/re_component_ui/src/variant_uis/redap_thumbnail.rs b/crates/viewer/re_component_ui/src/variant_uis/redap_thumbnail.rs
index f94bd2758365..aa96d05ab4ef 100644
--- a/crates/viewer/re_component_ui/src/variant_uis/redap_thumbnail.rs
+++ b/crates/viewer/re_component_ui/src/variant_uis/redap_thumbnail.rs
@@ -22,11 +22,9 @@ pub fn redap_thumbnail(
 
     let media_type = MediaType::guess_from_data(slice);
 
-    let image = ctx
-        .caches
-        .entry(|c: &mut re_viewer_context::ImageDecodeCache| {
-            c.entry_encoded_color(row_id, component, slice, media_type.as_ref())
-        })?;
+    let image = ctx.memoizer(|c: &mut re_viewer_context::ImageDecodeCache| {
+        c.entry_encoded_color(row_id, component, slice, media_type.as_ref())
+    })?;
 
     re_data_ui::image_preview_ui(
         ctx,
diff --git a/crates/viewer/re_data_ui/src/image_ui.rs b/crates/viewer/re_data_ui/src/image_ui.rs
index cb27290a3ae5..8e211234d0e9 100644
--- a/crates/viewer/re_data_ui/src/image_ui.rs
+++ b/crates/viewer/re_data_ui/src/image_ui.rs
@@ -28,9 +28,7 @@ pub fn image_preview_ui(
     image: &ImageInfo,
     colormap_with_range: Option<&ColormapWithRange>,
 ) -> Option<()> {
-    let image_stats = app_ctx
-        .caches
-        .entry(|c: &mut ImageStatsCache| c.entry(image));
+    let image_stats = app_ctx.memoizer(|c: &mut ImageStatsCache| c.entry(image));
     let annotations = store_ctx.map(|store_ctx| crate::annotations(store_ctx, entity_path));
     let debug_name = entity_path.to_string();
 
@@ -332,7 +330,7 @@ pub struct ImageUi {
 
 impl ImageUi {
     pub fn new(ctx: &StoreViewContext<'_>, image: ImageInfo) -> Self {
-        let image_stats = ctx.caches.entry(|c: &mut ImageStatsCache| c.entry(&image));
+        let image_stats = ctx.memoizer(|c: &mut ImageStatsCache| c.entry(&image));
         let data_range = image_data_range_heuristic(&image_stats, &image.format);
         Self {
             image,
@@ -348,17 +346,16 @@ impl ImageUi {
         blob: &re_sdk_types::datatypes::Blob,
         media_type: Option<&MediaType>,
     ) -> Option {
-        ctx.caches
-            .entry(|c: &mut re_viewer_context::ImageDecodeCache| {
-                c.entry_encoded_color(
-                    blob_row_id,
-                    blob_component_descriptor.component,
-                    blob,
-                    media_type,
-                )
-            })
-            .ok()
-            .map(|image| Self::new(ctx, image))
+        ctx.memoizer(|c: &mut re_viewer_context::ImageDecodeCache| {
+            c.entry_encoded_color(
+                blob_row_id,
+                blob_component_descriptor.component,
+                blob,
+                media_type,
+            )
+        })
+        .ok()
+        .map(|image| Self::new(ctx, image))
     }
 
     pub fn from_components(
@@ -393,7 +390,7 @@ impl ImageUi {
             image_format.0,
             kind,
         );
-        let image_stats = ctx.caches.entry(|c: &mut ImageStatsCache| c.entry(&image));
+        let image_stats = ctx.memoizer(|c: &mut ImageStatsCache| c.entry(&image));
 
         let colormap = find_and_deserialize_archetype_mono_component::(
             entity_components,
diff --git a/crates/viewer/re_data_ui/src/tensor_ui.rs b/crates/viewer/re_data_ui/src/tensor_ui.rs
index 64dbb460f193..64ba6da843fa 100644
--- a/crates/viewer/re_data_ui/src/tensor_ui.rs
+++ b/crates/viewer/re_data_ui/src/tensor_ui.rs
@@ -58,9 +58,7 @@ pub fn tensor_ui(
 ) {
     // See if we can convert the tensor to a GPU texture.
     // Even if not, we will show info about the tensor.
-    let tensor_stats = ctx
-        .caches
-        .entry(|c: &mut TensorStatsCache| c.entry(tensor_cache_key, tensor));
+    let tensor_stats = ctx.memoizer(|c: &mut TensorStatsCache| c.entry(tensor_cache_key, tensor));
 
     if ui_layout.is_single_line() {
         ui.horizontal(|ui| {
diff --git a/crates/viewer/re_data_ui/src/transform_frames_ui.rs b/crates/viewer/re_data_ui/src/transform_frames_ui.rs
index d20e72d4a404..6e94d2c3338b 100644
--- a/crates/viewer/re_data_ui/src/transform_frames_ui.rs
+++ b/crates/viewer/re_data_ui/src/transform_frames_ui.rs
@@ -74,7 +74,7 @@ impl TransformFramesUi {
 
         let mut frame_id_hash = TransformFrameIdHash::new(&frame_id);
 
-        let (frame_ids, transforms) = ctx.caches.entry(|c: &mut TransformDatabaseStoreCache| {
+        let (frame_ids, transforms) = ctx.memoizer(|c: &mut TransformDatabaseStoreCache| {
             (
                 c.frame_id_registry(ctx.db),
                 c.transforms_for_timeline(ctx.db, ctx.timeline_name()),
diff --git a/crates/viewer/re_data_ui/src/video_ui.rs b/crates/viewer/re_data_ui/src/video_ui.rs
index 684485aee841..14edc4d809d5 100644
--- a/crates/viewer/re_data_ui/src/video_ui.rs
+++ b/crates/viewer/re_data_ui/src/video_ui.rs
@@ -621,19 +621,17 @@ impl VideoUi {
         media_type: Option<&MediaType>,
         video_timestamp: Option,
     ) -> Option {
-        let result = ctx
-            .caches
-            .entry(|c: &mut re_viewer_context::VideoAssetCache| {
-                let debug_name = entity_path.to_string();
-                c.entry(
-                    debug_name,
-                    blob_row_id,
-                    blob_component_descriptor.component,
-                    blob,
-                    media_type,
-                    ctx.app_options.video_decoder_settings(),
-                )
-            });
+        let result = ctx.memoizer(|c: &mut re_viewer_context::VideoAssetCache| {
+            let debug_name = entity_path.to_string();
+            c.entry(
+                debug_name,
+                blob_row_id,
+                blob_component_descriptor.component,
+                blob,
+                media_type,
+                ctx.app_options.video_decoder_settings(),
+            )
+        });
 
         let certain_this_is_a_video =
             blob_component_descriptor.archetype == Some(archetypes::AssetVideo::name());
@@ -661,7 +659,7 @@ impl VideoUi {
             return None;
         }
 
-        let video_stream_result = ctx.caches.entry(|c: &mut VideoStreamCache| {
+        let video_stream_result = ctx.memoizer(|c: &mut VideoStreamCache| {
             c.entry(
                 ctx.db,
                 entity_path,
diff --git a/crates/viewer/re_selection_panel/src/selection_panel.rs b/crates/viewer/re_selection_panel/src/selection_panel.rs
index 755450bfcd9e..69bc6e8ef1f7 100644
--- a/crates/viewer/re_selection_panel/src/selection_panel.rs
+++ b/crates/viewer/re_selection_panel/src/selection_panel.rs
@@ -777,7 +777,7 @@ fn coordinate_frame_ui(ui: &mut egui::Ui, ctx: &ViewContext<'_>, data_result: &D
             let suggestions = {
                 let caches = ctx.viewer_ctx.store_context.caches;
                 let frame_id_registry =
-                    caches.entry(|c: &mut re_viewer_context::TransformDatabaseStoreCache| {
+                    caches.memoizer(|c: &mut re_viewer_context::TransformDatabaseStoreCache| {
                         c.frame_id_registry(ctx.viewer_ctx.recording())
                     });
 
diff --git a/crates/viewer/re_test_context/src/lib.rs b/crates/viewer/re_test_context/src/lib.rs
index 42991c7fe8ae..c5e80b860baa 100644
--- a/crates/viewer/re_test_context/src/lib.rs
+++ b/crates/viewer/re_test_context/src/lib.rs
@@ -260,7 +260,7 @@ impl TestContext {
 
         let mut store_hub = StoreHub::test_hub();
         store_hub.insert_entity_db(recording_store);
-        store_hub.cache_entry(&recording_store_id); // create cache
+        store_hub.store_cache_entry(&recording_store_id); // create per-store state
         store_hub.insert_entity_db(blueprint_store);
         store_hub
             .set_cloned_blueprint_active_for_app(&blueprint_id)
diff --git a/crates/viewer/re_view_spatial/src/contexts/transform_tree_context.rs b/crates/viewer/re_view_spatial/src/contexts/transform_tree_context.rs
index b3a8d0c6b710..2d03f74c8c91 100644
--- a/crates/viewer/re_view_spatial/src/contexts/transform_tree_context.rs
+++ b/crates/viewer/re_view_spatial/src/contexts/transform_tree_context.rs
@@ -170,12 +170,12 @@ impl ViewContextSystem for TransformTreeContext {
         re_tracing::profile_function!();
 
         let caches = ctx.store_context.caches;
-        let transform_forest = caches.entry(|c: &mut TransformDatabaseStoreCache| {
+        let transform_forest = caches.memoizer(|c: &mut TransformDatabaseStoreCache| {
             c.update_transform_forest(ctx.recording(), &ctx.current_query())
         });
 
         let frame_id_registry = caches
-            .entry(|c: &mut TransformDatabaseStoreCache| c.frame_id_registry(ctx.recording()));
+            .memoizer(|c: &mut TransformDatabaseStoreCache| c.frame_id_registry(ctx.recording()));
 
         let frame_ids = frame_id_registry
             .iter_frame_ids()
@@ -305,7 +305,7 @@ impl ViewContextSystem for TransformTreeContext {
         }
 
         let caches = ctx.viewer_ctx.store_context.caches;
-        let transforms_for_timeline = caches.entry(|c: &mut TransformDatabaseStoreCache| {
+        let transforms_for_timeline = caches.memoizer(|c: &mut TransformDatabaseStoreCache| {
             c.transforms_for_timeline(ctx.recording(), query.timeline)
         });
 
diff --git a/crates/viewer/re_view_spatial/src/shared_fallbacks.rs b/crates/viewer/re_view_spatial/src/shared_fallbacks.rs
index 95d1ffbaea4c..14767bd42e00 100644
--- a/crates/viewer/re_view_spatial/src/shared_fallbacks.rs
+++ b/crates/viewer/re_view_spatial/src/shared_fallbacks.rs
@@ -182,7 +182,7 @@ pub fn register_fallbacks(system_registry: &mut re_viewer_context::ViewSystemReg
             'scope: {
                 let caches = ctx.store_ctx().caches;
                 let (frame_id_registry, transform_forest) =
-                    caches.entry(|c: &mut re_viewer_context::TransformDatabaseStoreCache| {
+                    caches.memoizer(|c: &mut re_viewer_context::TransformDatabaseStoreCache| {
                         (c.frame_id_registry(ctx.recording()), c.transform_forest())
                     });
 
diff --git a/crates/viewer/re_view_spatial/src/visualizers/assets3d.rs b/crates/viewer/re_view_spatial/src/visualizers/assets3d.rs
index 90742289e2a8..d67675c1ba88 100644
--- a/crates/viewer/re_view_spatial/src/visualizers/assets3d.rs
+++ b/crates/viewer/re_view_spatial/src/visualizers/assets3d.rs
@@ -54,7 +54,7 @@ impl Asset3DVisualizer {
 
             // TODO(#5974): this is subtly wrong, the key should actually be a hash of everything that got
             // cached, which includes the media type…
-            let mesh = ctx.store_ctx().caches.entry(|c: &mut MeshCache| {
+            let mesh = ctx.store_ctx().memoizer(|c: &mut MeshCache| {
                 let key = MeshCacheKey {
                     versioned_instance_path_hash: picking_instance_hash.versioned(primary_row_id),
                     query_result_hash: data.query_result_hash,
diff --git a/crates/viewer/re_view_spatial/src/visualizers/depth_images.rs b/crates/viewer/re_view_spatial/src/visualizers/depth_images.rs
index 689b503bbe5f..891ff762ab88 100644
--- a/crates/viewer/re_view_spatial/src/visualizers/depth_images.rs
+++ b/crates/viewer/re_view_spatial/src/visualizers/depth_images.rs
@@ -86,8 +86,7 @@ pub fn process_depth_image_data(
             // Don't use fallback provider since it has to query information we already have.
             let image_stats = ctx
                 .store_ctx()
-                .caches
-                .entry(|c: &mut ImageStatsCache| c.entry(&image_info));
+                .memoizer(|c: &mut ImageStatsCache| c.entry(&image_info));
             ColormapWithRange::default_range_for_depth_images(&image_stats)
         });
     let colormap_with_range = ColormapWithRange {
diff --git a/crates/viewer/re_view_spatial/src/visualizers/encoded_depth_image.rs b/crates/viewer/re_view_spatial/src/visualizers/encoded_depth_image.rs
index ed085eec81bd..c9e452d43d90 100644
--- a/crates/viewer/re_view_spatial/src/visualizers/encoded_depth_image.rs
+++ b/crates/viewer/re_view_spatial/src/visualizers/encoded_depth_image.rs
@@ -119,7 +119,7 @@ impl VisualizerSystem for EncodedDepthImageVisualizer {
                         .and_then(|types| types.first().cloned())
                         .map(|mt| MediaType(mt.into()));
 
-                    let image = match ctx.store_ctx().caches.entry(|c: &mut ImageDecodeCache| {
+                    let image = match ctx.store_ctx().memoizer(|c: &mut ImageDecodeCache| {
                         c.entry_encoded_depth(
                             row_id,
                             EncodedDepthImage::descriptor_blob().component,
diff --git a/crates/viewer/re_view_spatial/src/visualizers/encoded_image.rs b/crates/viewer/re_view_spatial/src/visualizers/encoded_image.rs
index b12accdfe537..db17d55f49cd 100644
--- a/crates/viewer/re_view_spatial/src/visualizers/encoded_image.rs
+++ b/crates/viewer/re_view_spatial/src/visualizers/encoded_image.rs
@@ -108,7 +108,7 @@ impl EncodedImageVisualizer {
                 .and_then(|media_types| media_types.first().cloned())
                 .map(|media_type| MediaType(media_type.into()));
 
-            let image = ctx.store_ctx().caches.entry(|c: &mut ImageDecodeCache| {
+            let image = ctx.store_ctx().memoizer(|c: &mut ImageDecodeCache| {
                 c.entry_encoded_color(
                     tensor_data_row_id,
                     EncodedImage::descriptor_blob().component,
diff --git a/crates/viewer/re_view_spatial/src/visualizers/meshes.rs b/crates/viewer/re_view_spatial/src/visualizers/meshes.rs
index 5fcf448f53d7..53a35459d133 100644
--- a/crates/viewer/re_view_spatial/src/visualizers/meshes.rs
+++ b/crates/viewer/re_view_spatial/src/visualizers/meshes.rs
@@ -60,7 +60,7 @@ impl Mesh3DVisualizer {
                 continue;
             }
 
-            let mesh = ctx.store_ctx().caches.entry(|c: &mut MeshCache| {
+            let mesh = ctx.store_ctx().memoizer(|c: &mut MeshCache| {
                 let key = MeshCacheKey {
                     versioned_instance_path_hash: picking_instance_hash.versioned(primary_row_id),
                     query_result_hash: data.query_result_hash,
diff --git a/crates/viewer/re_view_spatial/src/visualizers/utilities/proc_mesh_vis.rs b/crates/viewer/re_view_spatial/src/visualizers/utilities/proc_mesh_vis.rs
index 39e485a69920..b2244a468284 100644
--- a/crates/viewer/re_view_spatial/src/visualizers/utilities/proc_mesh_vis.rs
+++ b/crates/viewer/re_view_spatial/src/visualizers/utilities/proc_mesh_vis.rs
@@ -244,8 +244,7 @@ impl<'ctx> ProcMeshDrawableBuilder<'ctx> {
                 let Some(wireframe_mesh) =
                     query_context
                         .store_ctx()
-                        .caches
-                        .entry(|c: &mut proc_mesh::WireframeCache| {
+                        .memoizer(|c: &mut proc_mesh::WireframeCache| {
                             c.entry(proc_mesh_key, self.render_ctx)
                         })
                 else {
@@ -281,10 +280,9 @@ impl<'ctx> ProcMeshDrawableBuilder<'ctx> {
 
             if draw_solid {
                 let store_ctx = query_context.store_ctx();
-                let Some(solid_mesh) = store_ctx
-                    .caches
-                    .entry(|c: &mut proc_mesh::SolidCache| c.entry(proc_mesh_key, self.render_ctx))
-                else {
+                let Some(solid_mesh) = store_ctx.memoizer(|c: &mut proc_mesh::SolidCache| {
+                    c.entry(proc_mesh_key, self.render_ctx)
+                }) else {
                     return Err(ViewSystemExecutionError::DrawDataCreationError(
                         "Failed to allocate solid mesh".into(),
                     ));
diff --git a/crates/viewer/re_view_spatial/src/visualizers/utilities/textured_rect.rs b/crates/viewer/re_view_spatial/src/visualizers/utilities/textured_rect.rs
index c98267289dca..cd9bb9f9512e 100644
--- a/crates/viewer/re_view_spatial/src/visualizers/utilities/textured_rect.rs
+++ b/crates/viewer/re_view_spatial/src/visualizers/utilities/textured_rect.rs
@@ -20,8 +20,7 @@ pub fn textured_rect_from_image(
     let debug_name = ent_path.to_string();
     let image_stats = ctx
         .store_context
-        .caches
-        .entry(|c: &mut ImageStatsCache| c.entry(image));
+        .memoizer(|c: &mut ImageStatsCache| c.entry(image));
 
     gpu_bridge::image_to_gpu(
         ctx.render_ctx(),
diff --git a/crates/viewer/re_view_spatial/src/visualizers/video/video_frame_reference.rs b/crates/viewer/re_view_spatial/src/visualizers/video/video_frame_reference.rs
index bb6e6f5b80f5..908419377377 100644
--- a/crates/viewer/re_view_spatial/src/visualizers/video/video_frame_reference.rs
+++ b/crates/viewer/re_view_spatial/src/visualizers/video/video_frame_reference.rs
@@ -268,7 +268,7 @@ fn latest_at_query_video_from_datastore(
     let media_type =
         results.component_instance::(0, AssetVideo::descriptor_media_type().component);
 
-    let video = ctx.store_context.caches.entry(|c: &mut VideoAssetCache| {
+    let video = ctx.store_context.memoizer(|c: &mut VideoAssetCache| {
         let debug_name = entity_path.to_string();
         c.entry(
             debug_name,
diff --git a/crates/viewer/re_view_spatial/src/visualizers/video/video_stream.rs b/crates/viewer/re_view_spatial/src/visualizers/video/video_stream.rs
index 213430fe7b4b..228f52cab216 100644
--- a/crates/viewer/re_view_spatial/src/visualizers/video/video_stream.rs
+++ b/crates/viewer/re_view_spatial/src/visualizers/video/video_stream.rs
@@ -123,8 +123,7 @@ impl VisualizerSystem for VideoStreamVisualizer {
 
             let video = match viewer_ctx
                 .store_context
-                .caches
-                .entry(|c: &mut VideoStreamCache| {
+                .memoizer(|c: &mut VideoStreamCache| {
                     c.entry(
                         viewer_ctx.recording(),
                         entity_path,
diff --git a/crates/viewer/re_view_tensor/src/view_class.rs b/crates/viewer/re_view_tensor/src/view_class.rs
index 2fc18b0b7fcf..ea9291a25945 100644
--- a/crates/viewer/re_view_tensor/src/view_class.rs
+++ b/crates/viewer/re_view_tensor/src/view_class.rs
@@ -135,7 +135,7 @@ Set the displayed dimensions in a selection panel.",
                 ..
             }) = &state.tensor
             {
-                let tensor_stats = ctx.store_context.caches.entry(|c: &mut TensorStatsCache| {
+                let tensor_stats = ctx.store_context.memoizer(|c: &mut TensorStatsCache| {
                     c.entry(Hash64::hash(*tensor_row_id), tensor)
                 });
 
diff --git a/crates/viewer/re_viewer/src/app.rs b/crates/viewer/re_viewer/src/app.rs
index a9121cb33051..05cbe75a3e92 100644
--- a/crates/viewer/re_viewer/src/app.rs
+++ b/crates/viewer/re_viewer/src/app.rs
@@ -2501,7 +2501,7 @@ impl App {
                     let entity_db = store_hub.entity_db_entry(&store_id);
                     let store_event = entity_db.add_rrd_manifest_message(rrd_manifest);
 
-                    if let Some(caches) = store_hub.caches_for_store(&store_id) {
+                    if let Some(caches) = store_hub.store_caches(&store_id) {
                         // Downgrade to read-only, so we can access caches.
                         let entity_db = store_hub
                             .entity_db(&store_id)
@@ -2691,7 +2691,7 @@ impl App {
         // Keep all caches up to date, even if they're in the background.
         // This ensures that when we switch to a different recording, the caches are already valid.
         if let Some(entity_db) = store_hub.entity_db(store_id)
-            && let Some(caches) = store_hub.caches_for_store(store_id)
+            && let Some(caches) = store_hub.store_caches(store_id)
         {
             caches.on_store_events(store_events, entity_db);
         }
@@ -3381,7 +3381,7 @@ impl App {
 
         // Even if we wanted, we cannot get rid of this overhead
         let unpurgable_cache_size = active_recording_id
-            .and_then(|id| store_hub.caches_for_store(id))
+            .and_then(|id| store_hub.store_caches(id))
             .map_or(0, |caches| caches.memory_use_after_last_purge());
 
         const APP_OVERHEAD_BYTES: u64 = 300_000_000;
diff --git a/crates/viewer/re_viewer_context/src/active_store_context.rs b/crates/viewer/re_viewer_context/src/active_store_context.rs
index cfd3e7563686..ab343d5b296c 100644
--- a/crates/viewer/re_viewer_context/src/active_store_context.rs
+++ b/crates/viewer/re_viewer_context/src/active_store_context.rs
@@ -1,7 +1,7 @@
 use re_entity_db::EntityDb;
 use re_log_types::{ApplicationId, StoreId};
 
-use crate::Caches;
+use crate::{Cache, StoreCache};
 
 /// The current Blueprint and Recording being displayed by the viewer
 pub struct ActiveStoreContext<'a> {
@@ -16,8 +16,8 @@ pub struct ActiveStoreContext<'a> {
     /// If none is active, this will point to a dummy empty recording.
     pub recording: &'a EntityDb,
 
-    /// Things that need caching.
-    pub caches: &'a Caches,
+    /// Per-recording caches.
+    pub caches: &'a StoreCache,
 
     /// Should we enable the heuristics during this frame?
     pub should_enable_heuristics: bool,
@@ -35,4 +35,11 @@ impl ActiveStoreContext<'_> {
     pub fn recording_store_id(&self) -> &StoreId {
         self.recording.store_id()
     }
+
+    /// Accesses a memoization cache for reading and writing.
+    ///
+    /// Shorthand for `self.caches.memoizer(f)`.
+    pub fn memoizer(&self, f: impl FnOnce(&mut C) -> R) -> R {
+        self.caches.memoizer(f)
+    }
 }
diff --git a/crates/viewer/re_viewer_context/src/cache/cache_trait.rs b/crates/viewer/re_viewer_context/src/cache/cache_trait.rs
new file mode 100644
index 000000000000..dbc9f1a0ca7b
--- /dev/null
+++ b/crates/viewer/re_viewer_context/src/cache/cache_trait.rs
@@ -0,0 +1,38 @@
+use re_byte_size::MemUsageTree;
+use re_chunk_store::ChunkStoreEvent;
+use re_entity_db::EntityDb;
+
+/// A cache for memoizing things in order to speed up immediate mode UI & other immediate mode style things.
+///
+/// See also egus's cache system, in [`egui::cache`] ().
+pub trait Cache: std::any::Any + Send + Sync + re_byte_size::MemUsageTreeCapture {
+    fn name(&self) -> &'static str;
+
+    /// Called once per frame to potentially flush the cache.
+    fn begin_frame(&mut self) {}
+
+    /// Attempt to free up memory.
+    ///
+    /// This should attempt to purge everything
+    /// that is not currently in use.
+    ///
+    /// Called BEFORE `begin_frame` (if at all).
+    fn purge_memory(&mut self);
+
+    /// Returns a memory usage tree containing only GPU memory (VRAM) usage.
+    ///
+    /// This should report GPU memory usage with per-item breakdown where applicable.
+    /// Defaults to an empty tree (0 bytes) for caches that don't use GPU memory.
+    fn vram_usage(&self) -> MemUsageTree {
+        MemUsageTree::Bytes(0)
+    }
+
+    /// React to the chunk store's changelog, if needed.
+    ///
+    /// Useful to e.g. invalidate unreachable data.
+    /// Since caches are created per store, each cache consistently receives events only for the same store.
+    fn on_store_events(&mut self, events: &[&ChunkStoreEvent], entity_db: &EntityDb) {
+        _ = events;
+        _ = entity_db;
+    }
+}
diff --git a/crates/viewer/re_viewer_context/src/cache/caches.rs b/crates/viewer/re_viewer_context/src/cache/memoizers.rs
similarity index 77%
rename from crates/viewer/re_viewer_context/src/cache/caches.rs
rename to crates/viewer/re_viewer_context/src/cache/memoizers.rs
index d2dc54368b21..b957b4e6b26a 100644
--- a/crates/viewer/re_viewer_context/src/cache/caches.rs
+++ b/crates/viewer/re_viewer_context/src/cache/memoizers.rs
@@ -8,6 +8,8 @@ use re_entity_db::EntityDb;
 use re_log_types::StoreId;
 use re_mutex::Mutex;
 
+use crate::Cache;
+
 /// A wrapper around a cache that allows for shared access with its own lock.
 ///
 /// This reduces lock contention by having one lock per cache type
@@ -32,22 +34,22 @@ impl SharedCache {
 }
 
 /// Does memoization of different objects for the immediate mode UI.
-pub struct Caches {
+pub struct Memoizers {
+    /// The store for which these caches are caching data.
+    store_id: StoreId,
+
     /// Master map from cache type to the cache itself.
     ///
     /// The master mutex is only held briefly to look up or insert a cache.
     /// Each cache has its own mutex for actual access.
     caches: Mutex>>,
 
-    /// The store for which these caches are caching data.
-    pub store_id: StoreId,
-
     /// How much memory we used after the last call to [`Self::purge_memory`].
     memory_use_after_last_purge: u64,
 }
 
-impl Caches {
-    /// Creates a new instance of `Caches` associated with a specific store.
+impl Memoizers {
+    /// Creates a new instance of [`Memoizers`] associated with a specific store.
     pub fn new(store_id: StoreId) -> Self {
         Self {
             caches: Mutex::new(HashMap::default()),
@@ -56,6 +58,11 @@ impl Caches {
         }
     }
 
+    /// The store for which these caches are caching data.
+    pub fn store_id(&self) -> &StoreId {
+        &self.store_id
+    }
+
     /// Call once per frame to potentially flush the cache(s).
     pub fn begin_frame(&self) {
         re_tracing::profile_function!();
@@ -155,46 +162,13 @@ impl Caches {
         let cache = cache_guard.as_mut();
         f((cache as &mut dyn std::any::Any)
             .downcast_mut::()
-            .expect("Downcast failed, this indicates a bug in how `Caches` adds new cache types."))
-    }
-}
-
-/// A cache for memoizing things in order to speed up immediate mode UI & other immediate mode style things.
-///
-/// See also egus's cache system, in [`egui::cache`] ().
-pub trait Cache: std::any::Any + Send + Sync + re_byte_size::MemUsageTreeCapture {
-    fn name(&self) -> &'static str;
-
-    /// Called once per frame to potentially flush the cache.
-    fn begin_frame(&mut self) {}
-
-    /// Attempt to free up memory.
-    ///
-    /// This should attempt to purge everything
-    /// that is not currently in use.
-    ///
-    /// Called BEFORE `begin_frame` (if at all).
-    fn purge_memory(&mut self);
-
-    /// Returns a memory usage tree containing only GPU memory (VRAM) usage.
-    ///
-    /// This should report GPU memory usage with per-item breakdown where applicable.
-    /// Defaults to an empty tree (0 bytes) for caches that don't use GPU memory.
-    fn vram_usage(&self) -> MemUsageTree {
-        MemUsageTree::Bytes(0)
-    }
-
-    /// React to the chunk store's changelog, if needed.
-    ///
-    /// Useful to e.g. invalidate unreachable data.
-    /// Since caches are created per store, each cache consistently receives events only for the same store.
-    fn on_store_events(&mut self, events: &[&ChunkStoreEvent], entity_db: &EntityDb) {
-        _ = events;
-        _ = entity_db;
+            .expect(
+                "Downcast failed, this indicates a bug in how `Memoizers` adds new cache types.",
+            ))
     }
 }
 
-impl MemUsageTreeCapture for Caches {
+impl MemUsageTreeCapture for Memoizers {
     fn capture_mem_usage_tree(&self) -> MemUsageTree {
         re_tracing::profile_function!();
 
diff --git a/crates/viewer/re_viewer_context/src/cache/mod.rs b/crates/viewer/re_viewer_context/src/cache/mod.rs
index f29a945d55d5..3159a8dfbad5 100644
--- a/crates/viewer/re_viewer_context/src/cache/mod.rs
+++ b/crates/viewer/re_viewer_context/src/cache/mod.rs
@@ -1,17 +1,21 @@
 //! Viewer caches
 //!
-//! Caches are registered lazily upon first use, see [`Caches::entry`].
+//! Caches are registered lazily upon first use, see [`Memoizers::entry`].
 //! The concrete caches exposed here are always available for all viewer crates.
 
-mod caches;
+mod cache_trait;
 mod image_decode_cache;
 mod image_stats_cache;
+mod memoizers;
+mod store_cache;
 mod tensor_stats_cache;
 mod transform_database_store;
 mod video_asset_cache;
 mod video_stream_cache;
 
-pub use caches::{Cache, Caches};
+pub use cache_trait::Cache;
+pub use memoizers::Memoizers;
+pub use store_cache::StoreCache;
 // TODO(andreas): Do we _really_ have to have all these caches in `re_viewer_context`?
 // Caches are fully dynamic and registration based, so they can be added at runtime by any crate.
 // The reason this happens it that various viewer crates wants to access these, mostly for ui purposes.
diff --git a/crates/viewer/re_viewer_context/src/cache/store_cache.rs b/crates/viewer/re_viewer_context/src/cache/store_cache.rs
new file mode 100644
index 000000000000..e13ac3e826f1
--- /dev/null
+++ b/crates/viewer/re_viewer_context/src/cache/store_cache.rs
@@ -0,0 +1,84 @@
+use re_byte_size::{MemUsageNode, MemUsageTree, MemUsageTreeCapture};
+use re_chunk_store::ChunkStoreEvent;
+use re_entity_db::EntityDb;
+use re_log_types::StoreId;
+
+use crate::{Cache, Memoizers};
+
+/// Viewer-specific state associated with each store (recording or blueprint).
+///
+/// This bundles together all per-store caches and subscribers
+/// that the viewer needs beyond the raw [`EntityDb`] data.
+pub struct StoreCache {
+    pub memoizers: Memoizers,
+}
+
+impl StoreCache {
+    pub fn new(store_id: StoreId) -> Self {
+        Self {
+            memoizers: Memoizers::new(store_id),
+        }
+    }
+
+    /// The store for which these caches are caching data.
+    pub fn store_id(&self) -> &StoreId {
+        self.memoizers.store_id()
+    }
+
+    /// Call once per frame to potentially flush the cache.
+    pub fn begin_frame(&self) {
+        let Self { memoizers } = self;
+        memoizers.begin_frame();
+    }
+
+    /// Attempt to free up memory.
+    ///
+    /// This should attempt to purge everything
+    /// that is not currently in use.
+    ///
+    /// Called BEFORE `begin_frame` (if at all).
+    pub fn purge_memory(&mut self) {
+        let Self { memoizers } = self;
+        memoizers.purge_memory();
+    }
+
+    /// React to the chunk store's changelog, e.g. to invalidate unreachable data.
+    pub fn on_store_events(&self, events: &[ChunkStoreEvent], entity_db: &EntityDb) {
+        let Self { memoizers } = self;
+        memoizers.on_store_events(events, entity_db);
+    }
+
+    /// How much memory we used after the last call to [`Self::purge_memory`].
+    ///
+    /// This is the lower bound on how much memory we need.
+    ///
+    /// Some caches just cannot shrink below a certain size,
+    /// and we need to take that into account when budgeting for other things.
+    pub fn memory_use_after_last_purge(&self) -> u64 {
+        let Self { memoizers } = self;
+        memoizers.memory_use_after_last_purge()
+    }
+
+    /// Returns a memory usage tree containing only GPU memory (VRAM) usage.
+    pub fn vram_usage(&self) -> MemUsageTree {
+        let Self { memoizers } = self;
+        memoizers.vram_usage()
+    }
+
+    /// Accesses a memoization cache for reading and writing.
+    ///
+    /// Adds the cache lazily if it wasn't already there.
+    pub fn memoizer(&self, f: impl FnOnce(&mut C) -> R) -> R {
+        self.memoizers.entry::(f)
+    }
+}
+
+impl MemUsageTreeCapture for StoreCache {
+    fn capture_mem_usage_tree(&self) -> MemUsageTree {
+        let Self { memoizers } = self;
+
+        let mut node = MemUsageNode::new();
+        node.add("memoizers", memoizers.capture_mem_usage_tree());
+        node.into_tree()
+    }
+}
diff --git a/crates/viewer/re_viewer_context/src/image_info.rs b/crates/viewer/re_viewer_context/src/image_info.rs
index f1a890756bdc..492918dd0b7e 100644
--- a/crates/viewer/re_viewer_context/src/image_info.rs
+++ b/crates/viewer/re_viewer_context/src/image_info.rs
@@ -61,8 +61,7 @@ pub fn resolution_of_image_at(
 
         let image = ctx
             .store_context
-            .caches
-            .entry(|c: &mut crate::ImageDecodeCache| {
+            .memoizer(|c: &mut crate::ImageDecodeCache| {
                 c.entry_encoded_color(
                     row_id,
                     archetypes::EncodedImage::descriptor_blob().component,
@@ -94,8 +93,7 @@ pub fn resolution_of_image_at(
 
         let depth_image = ctx
             .store_context
-            .caches
-            .entry(|c: &mut crate::ImageDecodeCache| {
+            .memoizer(|c: &mut crate::ImageDecodeCache| {
                 c.entry_encoded_depth(
                     row_id,
                     archetypes::EncodedDepthImage::descriptor_blob().component,
diff --git a/crates/viewer/re_viewer_context/src/lib.rs b/crates/viewer/re_viewer_context/src/lib.rs
index 2daa54604d5a..990bb5be90da 100644
--- a/crates/viewer/re_viewer_context/src/lib.rs
+++ b/crates/viewer/re_viewer_context/src/lib.rs
@@ -63,7 +63,7 @@ pub use self::blueprint_id::{
     BlueprintId, BlueprintIdRegistry, ContainerId, GLOBAL_VIEW_ID, ViewId,
 };
 pub use self::cache::{
-    Cache, Caches, ImageDecodeCache, ImageStatsCache, SharablePlayableVideoStream,
+    Cache, ImageDecodeCache, ImageStatsCache, Memoizers, SharablePlayableVideoStream, StoreCache,
     TensorStatsCache, TransformDatabaseStoreCache, VideoAssetCache, VideoStreamCache,
     VideoStreamProcessingError,
 };
diff --git a/crates/viewer/re_viewer_context/src/store_hub.rs b/crates/viewer/re_viewer_context/src/store_hub.rs
index 7d8b259e987f..5cf81dac44e3 100644
--- a/crates/viewer/re_viewer_context/src/store_hub.rs
+++ b/crates/viewer/re_viewer_context/src/store_hub.rs
@@ -18,7 +18,7 @@ use re_sdk_types::archetypes;
 use re_sdk_types::components::Timestamp;
 
 use crate::{
-    ActiveStoreContext, BlueprintUndoState, Caches, RecordingOrTable, Route, StorageContext,
+    ActiveStoreContext, BlueprintUndoState, RecordingOrTable, Route, StorageContext, StoreCache,
     TableStore, TableStores,
 };
 
@@ -57,8 +57,8 @@ pub struct StoreHub {
     /// These applications should enable the heuristics early next frame.
     should_enable_heuristics_by_app_id: HashSet,
 
-    /// Viewer caches (e.g. image decode cache).
-    caches_per_recording: HashMap,
+    /// Viewer-specific state (caches, subscribers, etc.) per store.
+    store_caches: HashMap,
 
     /// The [`ChunkStoreGeneration`] from when the [`EntityDb`] was last saved
     blueprint_last_save: HashMap,
@@ -188,7 +188,7 @@ impl StoreHub {
             should_enable_heuristics_by_app_id: Default::default(),
 
             data_source_order: Default::default(),
-            caches_per_recording: Default::default(),
+            store_caches: Default::default(),
             blueprint_last_save: Default::default(),
             blueprint_last_gc: Default::default(),
 
@@ -225,8 +225,8 @@ impl StoreHub {
                     .clone(),
             ))
         });
-        static EMPTY_CACHES: LazyLock =
-            LazyLock::new(|| Caches::new(re_log_types::StoreId::empty_recording()));
+        static EMPTY_CACHES: LazyLock =
+            LazyLock::new(|| StoreCache::new(re_log_types::StoreId::empty_recording()));
 
         let store_context = 'ctx: {
             // If we have an app-id, then use it to look up the blueprint.
@@ -286,7 +286,7 @@ impl StoreHub {
             let should_enable_heuristics = self.should_enable_heuristics_by_app_id.remove(app_id);
             let caches = route
                 .recording_id()
-                .and_then(|store_id| self.caches_per_recording.get(store_id));
+                .and_then(|store_id| self.store_caches.get(store_id));
 
             let caches = caches.unwrap_or_else(|| {
                 if recording.is_some() {
@@ -387,7 +387,7 @@ impl StoreHub {
     }
 
     fn remove_store(&mut self, store_id: &StoreId) {
-        _ = self.caches_per_recording.remove(store_id);
+        _ = self.store_caches.remove(store_id);
         let removed_store = self.store_bundle.remove(store_id);
 
         let Some(removed_store) = removed_store else {
@@ -491,7 +491,7 @@ impl StoreHub {
                 false
             }
         });
-        self.caches_per_recording
+        self.store_caches
             .retain(|store_id, _| store_ids_retained.contains(store_id));
 
         self.table_stores.clear();
@@ -537,7 +537,7 @@ impl StoreHub {
                 true
             }
         });
-        self.caches_per_recording
+        self.store_caches
             .retain(|store_id, _| !store_ids_removed.contains(store_id));
 
         self.default_blueprint_by_app_id.remove(app_id);
@@ -547,19 +547,18 @@ impl StoreHub {
     // ---------------------
     // Recording management
 
-    /// Get the [`Caches`] for a given store.
+    /// Get the [`StoreCache`] for a given store.
     ///
-    /// Returns `None` if no caches exist for this store.
-    pub fn caches_for_store(&self, store_id: &StoreId) -> Option<&Caches> {
-        self.caches_per_recording.get(store_id)
+    /// Returns `None` if no state exists for this store.
+    pub fn store_caches(&self, store_id: &StoreId) -> Option<&StoreCache> {
+        self.store_caches.get(store_id)
     }
 
-    /// Get or create the cache if it doesn't already axist
-    pub fn cache_entry(&mut self, recording_id: &StoreId) -> &Caches {
-        // Make sure the recording has associated caches, always.
-        self.caches_per_recording
-            .entry(recording_id.clone())
-            .or_insert_with(|| Caches::new(recording_id.clone()))
+    /// Get or create the [`StoreCache`] for a given store.
+    pub fn store_cache_entry(&mut self, store_id: &StoreId) -> &mut StoreCache {
+        self.store_caches
+            .entry(store_id.clone())
+            .or_insert_with(|| StoreCache::new(store_id.clone()))
     }
 
     /// Ensure caches and blueprints are set up for the given recording.
@@ -578,7 +577,7 @@ impl StoreHub {
             self.load_persisted_blueprints_for_app(&app_id);
         }
 
-        self.cache_entry(recording_id);
+        self.store_cache_entry(recording_id);
     }
 
     // ---------------------
@@ -740,7 +739,7 @@ impl StoreHub {
         re_tracing::profile_function!();
 
         #[expect(clippy::iter_over_hash_type)]
-        for cache in self.caches_per_recording.values_mut() {
+        for cache in self.store_caches.values_mut() {
             cache.purge_memory();
         }
 
@@ -831,8 +830,8 @@ impl StoreHub {
         let store_events = entity_db.gc_with_target(target, time_cursor);
         let store_size_after = entity_db.total_size_bytes();
 
-        if let Some(caches) = self.caches_per_recording.get_mut(store_id) {
-            caches.on_store_events(&store_events, entity_db);
+        if let Some(cache) = self.store_caches.get(store_id) {
+            cache.on_store_events(&store_events, entity_db);
         }
 
         store_size_before.saturating_sub(store_size_after)
@@ -897,8 +896,8 @@ impl StoreHub {
                 });
                 if !store_events.is_empty() {
                     re_log::debug!("Garbage-collected blueprint store");
-                    if let Some(caches) = self.caches_per_recording.get_mut(blueprint_id) {
-                        caches.on_store_events(&store_events, blueprint);
+                    if let Some(cache) = self.store_caches.get(blueprint_id) {
+                        cache.on_store_events(&store_events, blueprint);
                     }
                 }
 
@@ -908,14 +907,14 @@ impl StoreHub {
         }
     }
 
-    /// See [`crate::Caches::begin_frame`].
+    /// See [`crate::StoreCache::begin_frame`].
     pub fn begin_frame_caches(&mut self) {
-        self.caches_per_recording.retain(|store_id, caches| {
+        self.store_caches.retain(|store_id, cache| {
             if self.store_bundle.contains(store_id) {
-                caches.begin_frame();
-                true // keep caches for existing recordings
+                cache.begin_frame();
+                true // keep cache for existing recordings
             } else {
-                false // remove caches for recordings that no longer exist
+                false // remove cache for recordings that no longer exist
             }
         });
     }
@@ -1020,7 +1019,7 @@ impl StoreHub {
             table_stores,
             data_source_order: _,
             should_enable_heuristics_by_app_id: _,
-            caches_per_recording,
+            store_caches,
             blueprint_last_save: _,
             blueprint_last_gc: _,
         } = self;
@@ -1030,9 +1029,9 @@ impl StoreHub {
         for store in store_bundle.entity_dbs() {
             let store_id = store.store_id();
             let engine = store.storage_engine();
-            let cache_vram_usage = caches_per_recording
+            let cache_vram_usage = store_caches
                 .get(store_id)
-                .map(|caches| caches.vram_usage())
+                .map(|cache| cache.vram_usage())
                 .unwrap_or_default();
             store_stats.insert(
                 store_id.clone(),
@@ -1067,7 +1066,7 @@ impl MemUsageTreeCapture for StoreHub {
         let Self {
             store_bundle,
             table_stores,
-            caches_per_recording,
+            store_caches,
 
             // Small stuff:
             persistence: _,
@@ -1081,16 +1080,16 @@ impl MemUsageTreeCapture for StoreHub {
 
         let mut node = MemUsageNode::new();
 
-        // Collect all store IDs from both store_bundle and caches_per_recording
+        // Collect all store IDs from both store_bundle and store_caches
         let mut all_store_ids = std::collections::BTreeSet::new();
         for entity_db in store_bundle.entity_dbs() {
             all_store_ids.insert(entity_db.store_id().clone());
         }
-        for store_id in caches_per_recording.keys() {
+        for store_id in store_caches.keys() {
             all_store_ids.insert(store_id.clone());
         }
 
-        // Group stores by recording ID, combining EntityDb and Caches
+        // Group stores by recording ID, combining EntityDb and StoreCache
         let mut stores_node = MemUsageNode::new();
         for store_id in all_store_ids {
             let recording_id = format!("{store_id:?}");
@@ -1101,9 +1100,9 @@ impl MemUsageTreeCapture for StoreHub {
                 recording_node.add("EntityDb", entity_db.capture_mem_usage_tree());
             }
 
-            // Add Caches if they exist for this store
-            if let Some(caches) = caches_per_recording.get(&store_id) {
-                recording_node.add("Caches", caches.capture_mem_usage_tree());
+            // Add StoreCache if it exists for this store
+            if let Some(cache) = store_caches.get(&store_id) {
+                recording_node.add("StoreCache", cache.capture_mem_usage_tree());
             }
 
             stores_node.add(recording_id, recording_node.into_tree());
diff --git a/crates/viewer/re_viewer_context/src/store_view_context.rs b/crates/viewer/re_viewer_context/src/store_view_context.rs
index 55fde541dd79..dabe790943c5 100644
--- a/crates/viewer/re_viewer_context/src/store_view_context.rs
+++ b/crates/viewer/re_viewer_context/src/store_view_context.rs
@@ -1,7 +1,7 @@
 use re_chunk::{Timeline, TimelineName};
 use re_entity_db::EntityDb;
 
-use crate::{AppContext, AppOptions, Caches, TimeControl};
+use crate::{AppContext, AppOptions, Cache, StoreCache, TimeControl};
 
 /// Context for viewing a specific store,
 /// (either a recording, or a blueprint).
@@ -18,7 +18,7 @@ pub struct StoreViewContext<'a> {
     pub time_ctrl: &'a TimeControl,
 
     /// Needed to display images, videos, etc
-    pub caches: &'a Caches,
+    pub caches: &'a StoreCache,
 }
 
 impl<'a> std::ops::Deref for StoreViewContext<'a> {
@@ -72,4 +72,11 @@ impl<'a> StoreViewContext<'a> {
     pub fn command_sender(&self) -> &crate::CommandSender {
         self.app_ctx.command_sender
     }
+
+    /// Accesses a memoization cache for reading and writing.
+    ///
+    /// Shorthand for `self.caches.memoizer(f)`.
+    pub fn memoizer(&self, f: impl FnOnce(&mut C) -> R) -> R {
+        self.caches.memoizer(f)
+    }
 }
diff --git a/crates/viewer/re_viewport_blueprint/benches/data_query.rs b/crates/viewer/re_viewport_blueprint/benches/data_query.rs
index a5cb206756d7..aa478cbaae5a 100644
--- a/crates/viewer/re_viewport_blueprint/benches/data_query.rs
+++ b/crates/viewer/re_viewport_blueprint/benches/data_query.rs
@@ -12,7 +12,7 @@ use re_sdk_types::archetypes::Points2D;
 use re_sdk_types::components::Position2D;
 use re_types_core::ViewClassIdentifier;
 use re_viewer_context::{
-    ActiveStoreContext, Caches, PerVisualizerType, QueryRange, ViewClassRegistry,
+    ActiveStoreContext, PerVisualizerType, QueryRange, StoreCache, ViewClassRegistry,
     VisualizableEntities, VisualizableReason, blueprint_timeline,
 };
 use re_viewport_blueprint::ViewContents;
@@ -78,7 +78,7 @@ fn query_tree_many_entities(c: &mut Criterion) {
         blueprint: &blueprint,
         default_blueprint: None,
         recording: &recording,
-        caches: &Caches::new(recording.store_id().clone()),
+        caches: &StoreCache::new(recording.store_id().clone()),
         should_enable_heuristics: false,
     };
 
diff --git a/crates/viewer/re_viewport_blueprint/src/view_contents.rs b/crates/viewer/re_viewport_blueprint/src/view_contents.rs
index 043437c4a01b..4d2972a49c95 100644
--- a/crates/viewer/re_viewport_blueprint/src/view_contents.rs
+++ b/crates/viewer/re_viewport_blueprint/src/view_contents.rs
@@ -778,7 +778,7 @@ mod tests {
     use re_log_types::{StoreId, TimePoint, Timeline};
     use re_types_core::reflection::Reflection;
     use re_viewer_context::{
-        ActiveStoreContext, Caches, FallbackProviderRegistry, IdentifiedViewSystem as _,
+        ActiveStoreContext, FallbackProviderRegistry, IdentifiedViewSystem as _, StoreCache,
         ViewClass as _, ViewClassRegistry, VisualizableReason, blueprint_timeline,
     };
 
@@ -852,7 +852,7 @@ mod tests {
             blueprint: &blueprint,
             default_blueprint: None,
             recording: &recording,
-            caches: &Caches::new(recording.store_id().clone()),
+            caches: &StoreCache::new(recording.store_id().clone()),
             should_enable_heuristics: false,
         };
 

From 3d2f38696630dba5f2714566eded28e5e868dacb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?G=C3=A1bor=20Gyebn=C3=A1r?= 
Date: Fri, 13 Mar 2026 11:29:47 +0100
Subject: [PATCH 122/513] Fixes color picker's HSV drift and missing alpha

### Related

Closes RR-4022

### What

Fixes:
- As the color circle is dragged, there was a hue drift due to
back-and-forth conversion between rgba and hsva.
- Alpha component can be edited now again (regressed mean while)

image

Source-Ref: 459fbab51663679a08c4c1d9371f9b0dbcca4e95
---
 .../re_component_ui/src/color_swatch.rs       | 29 ++++++++++---------
 1 file changed, 16 insertions(+), 13 deletions(-)

diff --git a/crates/viewer/re_component_ui/src/color_swatch.rs b/crates/viewer/re_component_ui/src/color_swatch.rs
index 3896b45753c9..46e1ed168b5f 100644
--- a/crates/viewer/re_component_ui/src/color_swatch.rs
+++ b/crates/viewer/re_component_ui/src/color_swatch.rs
@@ -1,7 +1,6 @@
 use egui::{
-    Color32, Popup, PopupCloseBehavior, Response, Rgba, Ui, Vec2, Widget,
-    color_picker::{Alpha, color_picker_hsva_2d},
-    epaint::Hsva,
+    Color32, Popup, PopupCloseBehavior, Response, Ui, Vec2, Widget,
+    color_picker::{Alpha, color_picker_color32},
 };
 use re_sdk_types::datatypes::Rgba32;
 use re_ui::UiExt as _;
@@ -22,9 +21,6 @@ impl<'a> ColorSwatch<'a> {
 impl Widget for ColorSwatch<'_> {
     fn ui(self, ui: &mut Ui) -> Response {
         let [r, g, b, a] = self.color.to_array();
-        #[expect(clippy::disallowed_methods)] // This is not a hard-coded color.
-        let egui_color = Color32::from_rgba_unmultiplied(r, g, b, a);
-
         // Draw the color box.
         let size = Vec2::splat(ui.tokens().color_swatch_size);
         let (rect, response) = ui.allocate_exact_size(size, egui::Sense::click());
@@ -34,8 +30,14 @@ impl Widget for ColorSwatch<'_> {
             } else {
                 ui.tokens().color_swatch_noninteractive_stroke
             };
-            ui.painter()
-                .rect(rect, 3.0, egui_color, stroke, egui::StrokeKind::Inside);
+            ui.painter().rect(
+                rect,
+                3.0,
+                #[expect(clippy::disallowed_methods)] // This is not a hard-coded color.
+                Color32::from_rgb(r, g, b),
+                stroke,
+                egui::StrokeKind::Inside,
+            );
         }
 
         // Show the color code on hover.
@@ -45,7 +47,7 @@ impl Widget for ColorSwatch<'_> {
         });
 
         // Allow editing the color if it's mutable.
-        if let Some(color) = self.color.as_mut() {
+        if let Some(target_color) = self.color.as_mut() {
             let popup_id = ui.auto_id_with("popup");
             const COLOR_SLIDER_WIDTH: f32 = 275.0;
             let mut color_changed = false;
@@ -54,11 +56,12 @@ impl Widget for ColorSwatch<'_> {
                 .close_behavior(PopupCloseBehavior::CloseOnClickOutside)
                 .show(|ui| {
                     ui.spacing_mut().slider_width = COLOR_SLIDER_WIDTH;
-                    let mut hsva = Hsva::from(egui_color);
 
-                    if color_picker_hsva_2d(ui, &mut hsva, Alpha::Opaque) {
-                        let new_color = Color32::from(Rgba::from(hsva));
-                        *color = Rgba32::from(new_color);
+                    #[expect(clippy::disallowed_methods)] // This is not a hard-coded color.
+                    let mut egui_color = Color32::from_rgba_unmultiplied(r, g, b, a);
+
+                    if color_picker_color32(ui, &mut egui_color, Alpha::OnlyBlend) {
+                        *target_color = Rgba32::from(egui_color);
                         color_changed = true;
                     }
                 });

From 7545fd19fb1b1ab9310d41119ce35b47559b8c70 Mon Sep 17 00:00:00 2001
From: Emil Ernerfeldt 
Date: Fri, 13 Mar 2026 11:35:07 +0100
Subject: [PATCH 123/513] Fix ctrl-C of occluded viewer

Broke in recent egui updates

Source-Ref: 6fcbc02e0621d19faeefbcc948280461850d74b8
---
 crates/viewer/re_viewer/src/app.rs | 28 ++++++++++++++++++++++++++++
 1 file changed, 28 insertions(+)

diff --git a/crates/viewer/re_viewer/src/app.rs b/crates/viewer/re_viewer/src/app.rs
index 05cbe75a3e92..899492731e15 100644
--- a/crates/viewer/re_viewer/src/app.rs
+++ b/crates/viewer/re_viewer/src/app.rs
@@ -714,6 +714,7 @@ impl App {
         store_context: Option<&ActiveStoreContext<'_>>,
         route: &Route,
     ) {
+        re_tracing::profile_function!();
         while let Some(cmd) = self.command_receiver.recv_ui() {
             self.run_ui_command(
                 egui_ctx,
@@ -3674,6 +3675,33 @@ impl eframe::App for App {
 
         self.run_pending_system_commands(&mut store_hub, egui_ctx);
 
+        {
+            // We also need to check for Ui commands, especially `UiCommand::Quit`.
+
+            let route = self.state.navigation.current().clone();
+            let (storage_context, store_context) = store_hub.read_context(&route);
+
+            let blueprint_query = self
+                .state
+                .blueprint_query_for_viewer(store_context.blueprint);
+
+            let app_blueprint = AppBlueprint::new(
+                Some(store_context.blueprint),
+                &blueprint_query,
+                egui_ctx,
+                self.panel_state_overrides_active
+                    .then_some(self.panel_state_overrides),
+            );
+
+            self.run_pending_ui_commands(
+                egui_ctx,
+                &app_blueprint,
+                &storage_context,
+                Some(&store_context),
+                &route,
+            );
+        }
+
         self.state.cleanup(&store_hub);
 
         // Return the `StoreHub` to the Viewer so we have it on the next frame

From 95d06884f4f28579af09b6cc773b2652ca5242e3 Mon Sep 17 00:00:00 2001
From: Ilya Zlobintsev 
Date: Fri, 13 Mar 2026 11:46:39 +0100
Subject: [PATCH 124/513] Enable perf_telemetry feature in the wheel build

### What

Trace context is currently broken in the SDK because of a missing
feature in the wheel build.

### Testing

Tested that it works within component-tests benchmarks.

Source-Ref: b9b9bce0829ba585c2c24c7796d2c0c364992ddc
---
 scripts/ci/build_and_upload_wheels.py | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/scripts/ci/build_and_upload_wheels.py b/scripts/ci/build_and_upload_wheels.py
index 4d1321b78cbb..e8e0db4aa8cd 100755
--- a/scripts/ci/build_and_upload_wheels.py
+++ b/scripts/ci/build_and_upload_wheels.py
@@ -70,11 +70,11 @@ def build_and_upload(
         run("pixi run rerun-build-web-release")
 
     if mode is BuildMode.PYPI:
-        maturin_feature_flags = "--no-default-features --features pypi"
+        maturin_feature_flags = "--no-default-features --features perf_telemetry,pypi"
     elif mode is BuildMode.PR:
-        maturin_feature_flags = "--no-default-features --features extension-module"
+        maturin_feature_flags = "--no-default-features --features perf_telemetry,extension-module"
     elif mode is BuildMode.EXTRA:
-        maturin_feature_flags = "--no-default-features --features pypi,extra"
+        maturin_feature_flags = "--no-default-features --features perf_telemetry,pypi,extra"
 
     dist = f"dist/{target}"
 

From abedf6446f0eda60644f28010dd6edd00d953f24 Mon Sep 17 00:00:00 2001
From: Andreas Reich 
Date: Fri, 13 Mar 2026 11:46:47 +0100
Subject: [PATCH 125/513] Remove any_scalar from viewer manifest

Fixes RR-4015

Source-Ref: bd0b3db091b73985fdffe1a1e519bd4bb5966749
---
 examples/python/any_scalar/README.md | 1 -
 1 file changed, 1 deletion(-)

diff --git a/examples/python/any_scalar/README.md b/examples/python/any_scalar/README.md
index 9a23c6f1f197..6ee6fc1071fc 100644
--- a/examples/python/any_scalar/README.md
+++ b/examples/python/any_scalar/README.md
@@ -4,7 +4,6 @@ tags = ["Any scalar", "Plotting", "DynamicArchetype"]
 thumbnail = "https://static.rerun.io/any_scalar_example_market/4076a99f7fd5912af93258aa0c6c775a96f8b8e7/480w.png"
 thumbnail_dimensions = [480, 259]
 channel = "release"
-include_in_manifest = true
 -->
 
 

From 3e26be7f35ec6bab974367ecfda02cc8b505e80d Mon Sep 17 00:00:00 2001
From: Andreas Reich 
Date: Fri, 13 Mar 2026 11:52:56 +0100
Subject: [PATCH 126/513] Ignore NaN/Inf values for bounding box calculations
 used in 3d eye camera operations

### Related

* Fixes
https://linear.app/rerun/issue/RR-4035/focusing-camera-on-points-with-nan-breaks-things

### What

Forbids `macaw::BoundingBox::from_points` entirely

Source-Ref: 5f265ffb8d1bd5979aa6b02dd85fbc0ca89d4bd3
---
 clippy.toml                                       |  1 +
 crates/viewer/re_renderer/src/importer/dae.rs     |  2 +-
 crates/viewer/re_renderer/src/importer/gltf.rs    |  2 +-
 crates/viewer/re_renderer/src/importer/obj.rs     |  2 +-
 crates/viewer/re_renderer/src/importer/stl.rs     |  2 +-
 crates/viewer/re_renderer/src/lib.rs              |  1 +
 .../re_renderer/src/renderer/mesh_renderer.rs     |  2 +-
 .../viewer/re_renderer/src/renderer/rectangles.rs |  2 +-
 crates/viewer/re_renderer/src/util.rs             | 10 ++++++++++
 crates/viewer/re_view_spatial/src/mesh_loader.rs  |  2 +-
 .../re_view_spatial/src/visualizers/points2d.rs   |  8 +++++---
 .../re_view_spatial/src/visualizers/points3d.rs   |  3 ++-
 .../utilities/spatial_view_visualizer.rs          | 15 +++++++++++++++
 scripts/clippy_wasm/clippy.toml                   |  1 +
 14 files changed, 42 insertions(+), 11 deletions(-)
 create mode 100644 crates/viewer/re_renderer/src/util.rs

diff --git a/clippy.toml b/clippy.toml
index a135020488a8..7cd470544bca 100644
--- a/clippy.toml
+++ b/clippy.toml
@@ -72,6 +72,7 @@ disallowed-methods = [
   { path = "egui::Ui::spinner", reason = "Use `ui.loading_indicator` from `re_ui::UiEx" },
   { path = "glam::Vec2::normalize", reason = "normalize() can create NaNs. Use try_normalize or normalize_or_zero" },
   { path = "glam::Vec3::normalize", reason = "normalize() can create NaNs. Use try_normalize or normalize_or_zero" },
+  { path = "macaw::BoundingBox::from_points", reason = "from_points propagates NaN/inf. Use re_renderer::util::bounding_box_from_points instead" },
   { path = "sha1::Digest::new", reason = "SHA1 is cryptographically broken" },
   { path = "std::env::temp_dir", reason = "Use the tempdir crate instead" },
   { path = "std::panic::catch_unwind", reason = "We compile with `panic = 'abort'`" },
diff --git a/crates/viewer/re_renderer/src/importer/dae.rs b/crates/viewer/re_renderer/src/importer/dae.rs
index a7005856e1e6..360cc1f5c6b3 100644
--- a/crates/viewer/re_renderer/src/importer/dae.rs
+++ b/crates/viewer/re_renderer/src/importer/dae.rs
@@ -227,7 +227,7 @@ fn import_geometry(
 
     let num_vertices = pos_raw.len();
     let vertex_positions = bytemuck::cast_vec(pos_raw);
-    let bbox = macaw::BoundingBox::from_points(vertex_positions.iter().copied());
+    let bbox = crate::util::bounding_box_from_points(vertex_positions.iter().copied());
 
     let cpu_mesh = mesh::CpuMesh {
         label: label.clone(),
diff --git a/crates/viewer/re_renderer/src/importer/gltf.rs b/crates/viewer/re_renderer/src/importer/gltf.rs
index 67f824f38e5b..cced746937fc 100644
--- a/crates/viewer/re_renderer/src/importer/gltf.rs
+++ b/crates/viewer/re_renderer/src/importer/gltf.rs
@@ -274,7 +274,7 @@ fn import_mesh(
         return Err(GltfImportError::NoTrianglePrimitives { mesh_name });
     }
 
-    let bbox = macaw::BoundingBox::from_points(vertex_positions.iter().copied());
+    let bbox = crate::util::bounding_box_from_points(vertex_positions.iter().copied());
 
     let mesh = CpuMesh {
         label: mesh.name().into(),
diff --git a/crates/viewer/re_renderer/src/importer/obj.rs b/crates/viewer/re_renderer/src/importer/obj.rs
index 7ea34ef1aa3a..5c544312d646 100644
--- a/crates/viewer/re_renderer/src/importer/obj.rs
+++ b/crates/viewer/re_renderer/src/importer/obj.rs
@@ -41,7 +41,7 @@ pub fn load_obj_from_buffer(
             .chunks_exact(3)
             .map(|p| glam::vec3(p[0], p[1], p[2]))
             .collect();
-        let bbox = macaw::BoundingBox::from_points(vertex_positions.iter().copied());
+        let bbox = crate::util::bounding_box_from_points(vertex_positions.iter().copied());
 
         let triangle_indices = mesh
             .indices
diff --git a/crates/viewer/re_renderer/src/importer/stl.rs b/crates/viewer/re_renderer/src/importer/stl.rs
index 566437b7e3eb..6f8e2b1998e2 100644
--- a/crates/viewer/re_renderer/src/importer/stl.rs
+++ b/crates/viewer/re_renderer/src/importer/stl.rs
@@ -55,7 +55,7 @@ pub fn load_stl_from_buffer(
     };
 
     let vertex_positions = bytemuck::cast_vec(triangles);
-    let bbox = macaw::BoundingBox::from_points(vertex_positions.iter().copied());
+    let bbox = crate::util::bounding_box_from_points(vertex_positions.iter().copied());
 
     let mesh = mesh::CpuMesh {
         label: name.clone(),
diff --git a/crates/viewer/re_renderer/src/lib.rs b/crates/viewer/re_renderer/src/lib.rs
index b0c8820d9387..c4d9f22ea92d 100644
--- a/crates/viewer/re_renderer/src/lib.rs
+++ b/crates/viewer/re_renderer/src/lib.rs
@@ -58,6 +58,7 @@ mod queueable_draw_data;
 mod rect;
 mod size;
 mod transform;
+pub mod util;
 mod wgpu_resources;
 
 #[cfg(test)]
diff --git a/crates/viewer/re_renderer/src/renderer/mesh_renderer.rs b/crates/viewer/re_renderer/src/renderer/mesh_renderer.rs
index 3cf277886139..643475bef568 100644
--- a/crates/viewer/re_renderer/src/renderer/mesh_renderer.rs
+++ b/crates/viewer/re_renderer/src/renderer/mesh_renderer.rs
@@ -675,7 +675,7 @@ mod tests {
             glam::Vec3::new(-1.0, -1.0, 0.0),
             glam::Vec3::new(1.0, -1.0, 0.0),
         ];
-        let bbox = macaw::BoundingBox::from_points(vertex_positions.iter().copied());
+        let bbox = crate::util::bounding_box_from_points(vertex_positions.iter().copied());
         let cpu_mesh = CpuMesh {
             label: "test_mesh".into(),
             triangle_indices: vec![glam::UVec3::new(0, 1, 2)],
diff --git a/crates/viewer/re_renderer/src/renderer/rectangles.rs b/crates/viewer/re_renderer/src/renderer/rectangles.rs
index a1da1fac8da0..4a1f217e5ad7 100644
--- a/crates/viewer/re_renderer/src/renderer/rectangles.rs
+++ b/crates/viewer/re_renderer/src/renderer/rectangles.rs
@@ -184,7 +184,7 @@ impl TexturedRect {
         let extent_u = self.extent_u;
         let extent_v = self.extent_v;
 
-        macaw::BoundingBox::from_points(
+        crate::util::bounding_box_from_points(
             [
                 left_top,
                 left_top + extent_u,
diff --git a/crates/viewer/re_renderer/src/util.rs b/crates/viewer/re_renderer/src/util.rs
new file mode 100644
index 000000000000..1aa6e01a1c88
--- /dev/null
+++ b/crates/viewer/re_renderer/src/util.rs
@@ -0,0 +1,10 @@
+/// Like [`macaw::BoundingBox::from_points`], but ignores NaN and infinity values.
+pub fn bounding_box_from_points(points: impl Iterator) -> macaw::BoundingBox {
+    let mut bbox = macaw::BoundingBox::nothing();
+    for p in points {
+        if p.is_finite() {
+            bbox.extend(p);
+        }
+    }
+    bbox
+}
diff --git a/crates/viewer/re_view_spatial/src/mesh_loader.rs b/crates/viewer/re_view_spatial/src/mesh_loader.rs
index ff5eff9d16e4..0f287f7d365a 100644
--- a/crates/viewer/re_view_spatial/src/mesh_loader.rs
+++ b/crates/viewer/re_view_spatial/src/mesh_loader.rs
@@ -166,7 +166,7 @@ impl LoadedMesh {
 
         let bbox = {
             re_tracing::profile_scope!("bbox");
-            macaw::BoundingBox::from_points(vertex_positions.iter().copied())
+            re_renderer::util::bounding_box_from_points(vertex_positions.iter().copied())
         };
 
         let albedo = try_get_or_create_albedo_texture(
diff --git a/crates/viewer/re_view_spatial/src/visualizers/points2d.rs b/crates/viewer/re_view_spatial/src/visualizers/points2d.rs
index 9bfa78db65e0..c4010719309b 100644
--- a/crates/viewer/re_view_spatial/src/visualizers/points2d.rs
+++ b/crates/viewer/re_view_spatial/src/visualizers/points2d.rs
@@ -117,9 +117,11 @@ impl Points2DVisualizer {
                 }
             }
 
-            let obj_space_bounding_box = macaw::BoundingBox::from_points(positions.iter().copied());
-            self.data
-                .add_bounding_box(entity_path.hash(), obj_space_bounding_box, world_from_obj);
+            let obj_space_bounding_box = self.data.add_bounding_box_from_points(
+                entity_path.hash(),
+                positions.iter().copied(),
+                world_from_obj,
+            );
 
             load_keypoint_connections(
                 line_builder,
diff --git a/crates/viewer/re_view_spatial/src/visualizers/points3d.rs b/crates/viewer/re_view_spatial/src/visualizers/points3d.rs
index d1e6a7f4cce1..05b2f2d4b08c 100644
--- a/crates/viewer/re_view_spatial/src/visualizers/points3d.rs
+++ b/crates/viewer/re_view_spatial/src/visualizers/points3d.rs
@@ -81,7 +81,8 @@ impl Points3DVisualizer {
 
             let positions = bytemuck::cast_slice(data.positions);
 
-            let obj_space_bounding_box = macaw::BoundingBox::from_points(positions.iter().copied());
+            let obj_space_bounding_box =
+                re_renderer::util::bounding_box_from_points(positions.iter().copied());
 
             // Has not custom fallback for radius, so we use the default.
             // TODO(andreas): It would be nice to have this handle this fallback as part of the query.
diff --git a/crates/viewer/re_view_spatial/src/visualizers/utilities/spatial_view_visualizer.rs b/crates/viewer/re_view_spatial/src/visualizers/utilities/spatial_view_visualizer.rs
index 2c562f382bf0..f0d1205f8be7 100644
--- a/crates/viewer/re_view_spatial/src/visualizers/utilities/spatial_view_visualizer.rs
+++ b/crates/viewer/re_view_spatial/src/visualizers/utilities/spatial_view_visualizer.rs
@@ -57,6 +57,21 @@ impl SpatialViewVisualizerData {
             .push((entity, bbox.transform_affine3(&world_from_obj)));
     }
 
+    /// Computes a bounding box from points, ignoring NaN and infinity values,
+    /// then adds it via [`Self::add_bounding_box`].
+    ///
+    /// Returns the computed object-space bounding box.
+    pub fn add_bounding_box_from_points(
+        &mut self,
+        entity: EntityPathHash,
+        points: impl Iterator,
+        world_from_obj: glam::Affine3A,
+    ) -> macaw::BoundingBox {
+        let bbox = re_renderer::util::bounding_box_from_points(points);
+        self.add_bounding_box(entity, bbox, world_from_obj);
+        bbox
+    }
+
     pub fn add_pickable_rect_to_bounding_box(
         &mut self,
         pickable_rect: &PickableTexturedRect,
diff --git a/scripts/clippy_wasm/clippy.toml b/scripts/clippy_wasm/clippy.toml
index 0b176f2702f3..1310594faebf 100644
--- a/scripts/clippy_wasm/clippy.toml
+++ b/scripts/clippy_wasm/clippy.toml
@@ -81,6 +81,7 @@ disallowed-methods = [
   { path = "egui::Ui::spinner", reason = "Use `ui.loading_indicator` from `re_ui::UiEx" },
   { path = "glam::Vec2::normalize", reason = "normalize() can create NaNs. Use try_normalize or normalize_or_zero" },
   { path = "glam::Vec3::normalize", reason = "normalize() can create NaNs. Use try_normalize or normalize_or_zero" },
+  { path = "macaw::BoundingBox::from_points", reason = "from_points propagates NaN/inf. Use re_renderer::util::bounding_box_from_points instead" },
   { path = "poll_promise::Promise::block_and_take", reason = "Cannot block on Web" },
   { path = "poll_promise::Promise::block_until_ready_mut", reason = "Cannot block on Web" },
   { path = "poll_promise::Promise::block_until_ready", reason = "Cannot block on Web" },

From 294843352bfbdd9d89051537789b1ba938f9e53a Mon Sep 17 00:00:00 2001
From: Antoine Beyeler <49431240+abey79@users.noreply.github.com>
Date: Fri, 13 Mar 2026 12:14:44 +0100
Subject: [PATCH 127/513] Add a python wrapper to `Recording`
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

### What

I have ✨plans✨ to add features to `rerun.recording.Recording`. As a prep
work, this PR introduce the python-wrapper/Rust internal pattern to
`Recording` and friends, to make it easier down the line.

Source-Ref: abea61535063e9d7873a86c3c443b118e646c216
---
 pyrightconfig.json                            |  3 +-
 rerun_py/rerun_bindings/rerun_bindings.pyi    | 85 +++----------------
 rerun_py/rerun_sdk/rerun/catalog/_entry.py    |  3 +-
 rerun_py/rerun_sdk/rerun/recording.py         |  8 --
 .../rerun_sdk/rerun/recording/__init__.py     | 51 +++++++++++
 .../rerun_sdk/rerun/recording/_recording.py   | 61 +++++++++++++
 rerun_py/rerun_sdk/rerun/sinks.py             |  2 +-
 rerun_py/src/catalog/dataset_entry.rs         |  9 +-
 rerun_py/src/python_bridge.rs                 |  4 +-
 rerun_py/src/recording/mod.rs                 |  6 +-
 rerun_py/src/recording/rrd.rs                 | 63 +++++---------
 11 files changed, 161 insertions(+), 134 deletions(-)
 delete mode 100644 rerun_py/rerun_sdk/rerun/recording.py
 create mode 100644 rerun_py/rerun_sdk/rerun/recording/__init__.py
 create mode 100644 rerun_py/rerun_sdk/rerun/recording/_recording.py

diff --git a/pyrightconfig.json b/pyrightconfig.json
index 7339c33a7aa5..907d12ccbcaf 100644
--- a/pyrightconfig.json
+++ b/pyrightconfig.json
@@ -17,6 +17,7 @@
   "defineConstant": {
     "DEBUG": true
   },
-  "venv": "venv",
+  "venvPath": ".",
+  "venv": ".venv",
   "reportPrivateImportUsage": false
 }
diff --git a/rerun_py/rerun_bindings/rerun_bindings.pyi b/rerun_py/rerun_bindings/rerun_bindings.pyi
index 5b7a3ed9519c..97ff4f7c6bd1 100644
--- a/rerun_py/rerun_bindings/rerun_bindings.pyi
+++ b/rerun_py/rerun_bindings/rerun_bindings.pyi
@@ -4,7 +4,7 @@ import os
 from collections.abc import Callable, Iterator
 from datetime import datetime, timedelta
 from enum import Enum
-from typing import TYPE_CHECKING, Any
+from typing import Any
 
 import datafusion as dfn
 import numpy as np
@@ -16,9 +16,6 @@ from .types import (
     VectorDistanceMetricLike as VectorDistanceMetricLike,
 )
 
-if TYPE_CHECKING:
-    from rerun.catalog import Schema
-
 # NOTE
 #
 # The pure Python wrapper/internal pyo3 object is documented in `rerun_py/ARCHITECTURE.md`.
@@ -190,74 +187,20 @@ class SchemaInternal:
     ) -> ComponentColumnDescriptor: ...
     def __arrow_c_schema__(self) -> Any: ...
 
-class Recording:
-    """
-    A single Rerun recording.
-
-    This can be loaded from an RRD file using [`load_recording()`][rerun.recording.load_recording].
-
-    A recording is a collection of data that was logged to Rerun. This data is organized
-    as a column for each index (timeline) and each entity/component pair that was logged.
-
-    You can examine the [`.schema()`][rerun.recording.Recording.schema] of the recording to see
-    what data is available.
-    """
-
-    def schema(self) -> Schema:
-        """The schema describing all the columns available in the recording."""
-
-    def recording_id(self) -> str:
-        """The recording ID of the recording."""
-
-    def application_id(self) -> str:
-        """The application ID of the recording."""
-
-class RRDArchive:
-    """
-    An archive loaded from an RRD.
-
-    RRD archives may include 1 or more recordings or blueprints.
-    """
-
-    def num_recordings(self) -> int:
-        """The number of recordings in the archive."""
-
-    def all_recordings(self) -> list[Recording]:
-        """All the recordings in the archive."""
-
-def load_recording(path_to_rrd: str | os.PathLike[str]) -> Recording:
-    """
-    Load a single recording from an RRD file.
-
-    Will raise a `ValueError` if the file does not contain exactly one recording.
-
-    Parameters
-    ----------
-    path_to_rrd:
-        The path to the file to load.
-
-    Returns
-    -------
-    Recording
-        The loaded recording.
-
-    """
+class RecordingInternal:
+    def schema(self) -> SchemaInternal: ...
+    def recording_id(self) -> str: ...
+    def application_id(self) -> str: ...
 
-def load_archive(path_to_rrd: str | os.PathLike[str]) -> RRDArchive:
-    """
-    Load a rerun archive from an RRD file.
+class RRDArchiveInternal:
+    def num_recordings(self) -> int: ...
+    def all_recordings(self) -> list[RecordingInternal]: ...
 
-    Parameters
-    ----------
-    path_to_rrd:
-        The path to the file to load.
+def load_recording(path_to_rrd: str | os.PathLike[str]) -> RecordingInternal:
+    """Load a single recording from an RRD file."""
 
-    Returns
-    -------
-    RRDArchive
-        The loaded archive.
-
-    """
+def load_archive(path_to_rrd: str | os.PathLike[str]) -> RRDArchiveInternal:
+    """Load a rerun archive from an RRD file."""
 
 # AI generated stubs for `PyRecordingStream` related class and functions
 # TODO(#9187): this will be entirely replaced when `RecordingStream` is itself written in Rust
@@ -865,7 +808,7 @@ def send_blueprint(
 ) -> None:
     """Send a blueprint to the given recording stream."""
 
-def send_recording(rrd: Recording, recording: PyRecordingStream | None = None) -> None:
+def send_recording(rrd: RecordingInternal, recording: PyRecordingStream | None = None) -> None:
     """
     Send all chunks from a [`PyRecording`] to the given recording stream.
 
@@ -1011,7 +954,7 @@ class DatasetEntryInternal:
 
     # ---
 
-    def download_segment(self, segment_id: str) -> Recording: ...
+    def download_segment(self, segment_id: str) -> RecordingInternal: ...
 
     # ---
 
diff --git a/rerun_py/rerun_sdk/rerun/catalog/_entry.py b/rerun_py/rerun_sdk/rerun/catalog/_entry.py
index 530b1aaee1fd..a0d4df7dbdab 100644
--- a/rerun_py/rerun_sdk/rerun/catalog/_entry.py
+++ b/rerun_py/rerun_sdk/rerun/catalog/_entry.py
@@ -508,8 +508,9 @@ def register_prefix(
 
     def download_segment(self, segment_id: str) -> Recording:
         """Download a segment from the dataset."""
+        from rerun.recording import Recording
 
-        return self._internal.download_segment(segment_id)
+        return Recording(self._internal.download_segment(segment_id))
 
     def filter_segments(self, segment_ids: str | Sequence[str] | datafusion.DataFrame) -> DatasetView:
         """
diff --git a/rerun_py/rerun_sdk/rerun/recording.py b/rerun_py/rerun_sdk/rerun/recording.py
deleted file mode 100644
index ec702afd10ed..000000000000
--- a/rerun_py/rerun_sdk/rerun/recording.py
+++ /dev/null
@@ -1,8 +0,0 @@
-from __future__ import annotations
-
-from rerun_bindings import (
-    Recording as Recording,
-    RRDArchive as RRDArchive,
-    load_archive as load_archive,
-    load_recording as load_recording,
-)
diff --git a/rerun_py/rerun_sdk/rerun/recording/__init__.py b/rerun_py/rerun_sdk/rerun/recording/__init__.py
new file mode 100644
index 000000000000..c4820ad534d3
--- /dev/null
+++ b/rerun_py/rerun_sdk/rerun/recording/__init__.py
@@ -0,0 +1,51 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
+from rerun_bindings import (
+    load_archive as _load_archive,
+    load_recording as _load_recording,
+)
+
+from ._recording import Recording as Recording, RRDArchive as RRDArchive
+
+if TYPE_CHECKING:
+    from pathlib import Path
+
+
+def load_recording(path_to_rrd: str | Path) -> Recording:
+    """
+    Load a single recording from an RRD file.
+
+    Will raise a `ValueError` if the file does not contain exactly one recording.
+
+    Parameters
+    ----------
+    path_to_rrd:
+        The path to the file to load.
+
+    Returns
+    -------
+    Recording
+        The loaded recording.
+
+    """
+    return Recording(_load_recording(path_to_rrd))
+
+
+def load_archive(path_to_rrd: str | Path) -> RRDArchive:
+    """
+    Load a rerun archive from an RRD file.
+
+    Parameters
+    ----------
+    path_to_rrd:
+        The path to the file to load.
+
+    Returns
+    -------
+    RRDArchive
+        The loaded archive.
+
+    """
+    return RRDArchive(_load_archive(path_to_rrd))
diff --git a/rerun_py/rerun_sdk/rerun/recording/_recording.py b/rerun_py/rerun_sdk/rerun/recording/_recording.py
new file mode 100644
index 000000000000..c5c460901820
--- /dev/null
+++ b/rerun_py/rerun_sdk/rerun/recording/_recording.py
@@ -0,0 +1,61 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
+if TYPE_CHECKING:
+    from rerun.catalog import Schema
+    from rerun_bindings import RecordingInternal, RRDArchiveInternal
+
+
+class Recording:
+    """
+    A single Rerun recording.
+
+    This can be loaded from an RRD file using [`load_recording()`][rerun.recording.load_recording].
+
+    A recording is a collection of data that was logged to Rerun. This data is organized
+    as a column for each index (timeline) and each entity/component pair that was logged.
+
+    You can examine the [`.schema()`][rerun.recording.Recording.schema] of the recording to see
+    what data is available.
+    """
+
+    _internal: RecordingInternal
+
+    def __init__(self, inner: RecordingInternal) -> None:
+        self._internal = inner
+
+    def schema(self) -> Schema:
+        """The schema describing all the columns available in the recording."""
+        from rerun.catalog import Schema
+
+        return Schema(self._internal.schema())
+
+    def recording_id(self) -> str:
+        """The recording ID of the recording."""
+        return self._internal.recording_id()
+
+    def application_id(self) -> str:
+        """The application ID of the recording."""
+        return self._internal.application_id()
+
+
+class RRDArchive:
+    """
+    An archive loaded from an RRD.
+
+    RRD archives may include 1 or more recordings or blueprints.
+    """
+
+    _internal: RRDArchiveInternal
+
+    def __init__(self, inner: RRDArchiveInternal) -> None:
+        self._internal = inner
+
+    def num_recordings(self) -> int:
+        """The number of recordings in the archive."""
+        return self._internal.num_recordings()
+
+    def all_recordings(self) -> list[Recording]:
+        """All the recordings in the archive."""
+        return [Recording(r) for r in self._internal.all_recordings()]
diff --git a/rerun_py/rerun_sdk/rerun/sinks.py b/rerun_py/rerun_sdk/rerun/sinks.py
index df419764424e..957cd7de810f 100644
--- a/rerun_py/rerun_sdk/rerun/sinks.py
+++ b/rerun_py/rerun_sdk/rerun/sinks.py
@@ -449,7 +449,7 @@ def send_recording(rrd: Recording, recording: RecordingStream | None = None) ->
         raise ValueError("No application id found. You must call rerun.init before sending a recording.")
 
     bindings.send_recording(
-        rrd,
+        rrd._internal,
         recording=recording.to_native() if recording is not None else None,
     )
 
diff --git a/rerun_py/src/catalog/dataset_entry.rs b/rerun_py/src/catalog/dataset_entry.rs
index a09eddf67a88..86bd79dd80cb 100644
--- a/rerun_py/src/catalog/dataset_entry.rs
+++ b/rerun_py/src/catalog/dataset_entry.rs
@@ -31,7 +31,7 @@ use super::{
 };
 use crate::catalog::entry::set_entry_name;
 use crate::catalog::{AnyComponentColumn, PyIndexColumnSelector, PySchemaInternal};
-use crate::recording::PyRecording;
+use crate::recording::PyRecordingInternal;
 use crate::utils::wait_for_future;
 
 /// A dataset entry in the catalog.
@@ -441,7 +441,10 @@ impl PyDatasetEntryInternal {
 
     /// Download a segment from the dataset.
     #[instrument(skip(self_), err)]
-    fn download_segment(self_: PyRef<'_, Self>, segment_id: String) -> PyResult {
+    fn download_segment(
+        self_: PyRef<'_, Self>,
+        segment_id: String,
+    ) -> PyResult {
         let catalog_client = self_.client.borrow(self_.py());
         let connection = catalog_client.connection();
         let dataset_id = self_.entry_details.id;
@@ -487,7 +490,7 @@ impl PyDatasetEntryInternal {
 
         let handle = ChunkStoreHandle::new(store?);
 
-        Ok(PyRecording { store: handle })
+        Ok(PyRecordingInternal { store: handle })
     }
 
     // TODO(RR-2824): we should have a generic `create_index(PyIndexConfig)`
diff --git a/rerun_py/src/python_bridge.rs b/rerun_py/src/python_bridge.rs
index 79a8adea2a1a..c29bd6c06a16 100644
--- a/rerun_py/src/python_bridge.rs
+++ b/rerun_py/src/python_bridge.rs
@@ -39,7 +39,7 @@ impl PyRuntimeErrorExt for PyRuntimeError {
     }
 }
 
-use crate::recording::PyRecording;
+use crate::recording::PyRecordingInternal;
 
 // The bridge needs to have complete control over the lifetimes of the individual recordings,
 // otherwise all the recording shutdown machinery (which includes deallocating C, Rust and Python
@@ -2103,7 +2103,7 @@ fn send_blueprint(
 ///     ⚠️ This API is experimental and may change or be removed in future versions! ⚠️
 #[pyfunction]
 #[pyo3(signature = (rrd, recording = None))]
-fn send_recording(rrd: &PyRecording, recording: Option<&PyRecordingStream>) {
+fn send_recording(rrd: &PyRecordingInternal, recording: Option<&PyRecordingStream>) {
     let Some(recording) = get_data_recording(recording) else {
         return;
     };
diff --git a/rerun_py/src/recording/mod.rs b/rerun_py/src/recording/mod.rs
index f41dc2df6a95..9cc5325e1230 100644
--- a/rerun_py/src/recording/mod.rs
+++ b/rerun_py/src/recording/mod.rs
@@ -3,12 +3,12 @@ mod rrd;
 use pyo3::types::{PyModule, PyModuleMethods as _};
 use pyo3::{Bound, PyResult, wrap_pyfunction};
 
-pub use self::rrd::{PyRRDArchive, PyRecording, load_archive, load_recording};
+pub use self::rrd::{PyRRDArchiveInternal, PyRecordingInternal, load_archive, load_recording};
 
 /// Register the `rerun.recording` module.
 pub fn register(m: &Bound<'_, PyModule>) -> PyResult<()> {
-    m.add_class::()?;
-    m.add_class::()?;
+    m.add_class::()?;
+    m.add_class::()?;
 
     m.add_function(wrap_pyfunction!(load_archive, m)?)?;
     m.add_function(wrap_pyfunction!(load_recording, m)?)?;
diff --git a/rerun_py/src/recording/rrd.rs b/rerun_py/src/recording/rrd.rs
index 0faf102054f9..27bb2a1076b8 100644
--- a/rerun_py/src/recording/rrd.rs
+++ b/rerun_py/src/recording/rrd.rs
@@ -1,9 +1,7 @@
 use std::collections::BTreeMap;
 
 use pyo3::exceptions::{PyRuntimeError, PyValueError};
-use pyo3::prelude::PyAnyMethods as _;
-use pyo3::types::PyModule;
-use pyo3::{Py, PyAny, PyResult, Python, pyclass, pyfunction, pymethods};
+use pyo3::{PyResult, pyclass, pyfunction, pymethods};
 
 use re_chunk_store::{ChunkStore, ChunkStoreConfig, ChunkStoreHandle};
 use re_log_types::StoreId;
@@ -13,14 +11,18 @@ use crate::catalog::PySchemaInternal;
 /// An archive loaded from an RRD.
 ///
 /// RRD archives may include 1 or more recordings or blueprints.
-#[pyclass(frozen, name = "RRDArchive", module = "rerun_bindings.rerun_bindings")] // NOLINT: ignore[py-cls-eq] non-trivial implementation
+#[pyclass(  // NOLINT: ignore[py-cls-eq] non-trivial implementation
+    frozen,
+    name = "RRDArchiveInternal",
+    module = "rerun_bindings.rerun_bindings"
+)]
 #[derive(Clone)]
-pub struct PyRRDArchive {
+pub struct PyRRDArchiveInternal {
     pub datasets: BTreeMap,
 }
 
 #[pymethods] // NOLINT: ignore[py-mthd-str]
-impl PyRRDArchive {
+impl PyRRDArchiveInternal {
     /// The number of recordings in the archive.
     fn num_recordings(&self) -> usize {
         self.datasets
@@ -31,11 +33,11 @@ impl PyRRDArchive {
 
     /// All the recordings in the archive.
     // TODO(jleibs): This should return an iterator
-    fn all_recordings(&self) -> Vec {
+    fn all_recordings(&self) -> Vec {
         self.datasets
             .iter()
             .filter(|(id, _)| id.is_recording())
-            .map(|(_, store)| PyRecording {
+            .map(|(_, store)| PyRecordingInternal {
                 store: store.clone(),
             })
             .collect()
@@ -51,24 +53,19 @@ impl PyRRDArchive {
 ///
 /// You can examine the [`.schema()`][rerun.recording.Recording.schema] of the recording to see
 /// what data is available.
-#[pyclass(name = "Recording", module = "rerun_bindings.rerun_bindings")] // NOLINT: ignore[py-cls-eq] non-trivial implementation
-pub struct PyRecording {
+#[pyclass(name = "RecordingInternal", module = "rerun_bindings.rerun_bindings")] // NOLINT: ignore[py-cls-eq] non-trivial implementation
+pub struct PyRecordingInternal {
     pub(crate) store: ChunkStoreHandle,
 }
 
 #[pymethods] // NOLINT: ignore[py-mthd-str]
-impl PyRecording {
+impl PyRecordingInternal {
     /// The schema describing all the columns available in the recording.
-    fn schema(&self, py: Python<'_>) -> PyResult> {
-        let schema_internal = PySchemaInternal {
+    fn schema(&self) -> PySchemaInternal {
+        PySchemaInternal {
             columns: self.store.read().schema().chunk_column_descriptors().into(),
             metadata: Default::default(),
-        };
-
-        // Import rerun.catalog.Schema and instantiate it with the internal schema
-        let schema_class = PyModule::import(py, "rerun.catalog")?.getattr("Schema")?;
-        let schema = schema_class.call1((schema_internal,))?;
-        Ok(schema.into())
+        }
     }
 
     /// The recording ID of the recording.
@@ -83,20 +80,8 @@ impl PyRecording {
 }
 
 /// Load a single recording from an RRD file.
-///
-/// Will raise a `ValueError` if the file does not contain exactly one recording.
-///
-/// Parameters
-/// ----------
-/// path_to_rrd:
-///     The path to the file to load.
-///
-/// Returns
-/// -------
-/// Recording
-///     The loaded recording.
 #[pyfunction]
-pub fn load_recording(path_to_rrd: std::path::PathBuf) -> PyResult {
+pub fn load_recording(path_to_rrd: std::path::PathBuf) -> PyResult {
     let archive = load_archive(path_to_rrd)?;
 
     let num_recordings = archive.num_recordings();
@@ -117,25 +102,15 @@ pub fn load_recording(path_to_rrd: std::path::PathBuf) -> PyResult
 }
 
 /// Load a rerun archive from an RRD file.
-///
-/// Parameters
-/// ----------
-/// path_to_rrd:
-///     The path to the file to load.
-///
-/// Returns
-/// -------
-/// RRDArchive
-///     The loaded archive.
 #[pyfunction]
-pub fn load_archive(path_to_rrd: std::path::PathBuf) -> PyResult {
+pub fn load_archive(path_to_rrd: std::path::PathBuf) -> PyResult {
     let stores = ChunkStore::from_rrd_filepath(&ChunkStoreConfig::DEFAULT, path_to_rrd)
         .map_err(|err| PyRuntimeError::new_err(err.to_string()))?
         .into_iter()
         .map(|(store_id, store)| (store_id, ChunkStoreHandle::new(store)))
         .collect();
 
-    let archive = PyRRDArchive { datasets: stores };
+    let archive = PyRRDArchiveInternal { datasets: stores };
 
     Ok(archive)
 }

From 792078b44263160b6fff75d4d3d5174f50c5e95d Mon Sep 17 00:00:00 2001
From: Andreas Reich 
Date: Fri, 13 Mar 2026 12:47:02 +0100
Subject: [PATCH 128/513] Fix debug assert for missing list item scope

Fixes
```
thread 'main' panicked at 'DEBUG ASSERT: ListItem was not wrapped in list_item_scope()'
re_ui/src/list_item/scope.rs:234
stack backtrace:
   9: core::panicking::panic_fmt
             at core/src/panicking.rs:80:14
  10: re_ui::list_item::scope::LayoutInfoStack::top::{{closure}}
             at re_ui/src/list_item/scope.rs:234:13
      egui::context::Context::data_mut::{{closure}}
             at egui/src/context.rs:1015:31
      egui::context::Context::write
             at egui/src/context.rs:756:9
  11: egui::context::Context::data_mut
             at egui/src/context.rs:1015:14
      re_ui::list_item::scope::LayoutInfoStack::top
             at re_ui/src/list_item/scope.rs:225:13
      re_ui::list_item::list_item::ListItem::ui
             at re_ui/src/list_item/list_item.rs:512:27
  12: re_ui::list_item::list_item::ListItem::show_flat::{{closure}}
             at re_ui/src/list_item/list_item.rs:315:28
      core::ops::function::FnOnce::call_once{{vtable.shim}}
             at core/src/ops/function.rs:250:5
  13:  as core::ops::function::FnOnce>::call_once
             at alloc/src/boxed.rs:2005:9
  14: egui::ui::Ui::scope_dyn
             at egui/src/ui.rs:2423:19
  15: egui::ui::Ui::scope
             at egui/src/ui.rs:2402:14
  16: re_ui::list_item::list_item::ListItem::show_flat
             at re_ui/src/list_item/list_item.rs:315:12
  17: re_ui::ui_ext::UiExt::list_item_label
             at re_ui/src/ui_ext.rs:683:14
      re_data_ui::instance_path_ui::instance_path_ui
             at re_data_ui/src/instance_path_ui.rs:163:24
  18: re_data_ui::instance_path_ui::::data_ui
             at re_data_ui/src/instance_path_ui.rs:74:9
  19: re_data_ui::item_ui::instance_hover_card_ui
             at re_data_ui/src/item_ui.rs:524:19
```

Source-Ref: 02a9f4cf6a4727f04833304dd382143ba4cd6635
---
 .../viewer/re_data_ui/src/instance_path_ui.rs | 37 ++++++++++---------
 1 file changed, 19 insertions(+), 18 deletions(-)

diff --git a/crates/viewer/re_data_ui/src/instance_path_ui.rs b/crates/viewer/re_data_ui/src/instance_path_ui.rs
index 20205343d939..2ceb6fa81fc4 100644
--- a/crates/viewer/re_data_ui/src/instance_path_ui.rs
+++ b/crates/viewer/re_data_ui/src/instance_path_ui.rs
@@ -159,24 +159,25 @@ fn instance_path_ui(
 
                 if !showed_short_summary {
                     // Show just a very short summary:
-
-                    ui.list_item_label(format_plural_s(num_components, "component"));
-
-                    let archetype_count = components_by_archetype.len();
-                    ui.list_item_label(format!(
-                        "{}: {}",
-                        format_plural_s(archetype_count, "archetype"),
-                        components_by_archetype
-                            .keys()
-                            .map(|archetype| {
-                                if let Some(archetype) = archetype {
-                                    archetype.short_name()
-                                } else {
-                                    ""
-                                }
-                            })
-                            .join(", ")
-                    ));
+                    ui.list_item_scope(instance_path, |ui| {
+                        ui.list_item_label(format_plural_s(num_components, "component"));
+
+                        let archetype_count = components_by_archetype.len();
+                        ui.list_item_label(format!(
+                            "{}: {}",
+                            format_plural_s(archetype_count, "archetype"),
+                            components_by_archetype
+                                .keys()
+                                .map(|archetype| {
+                                    if let Some(archetype) = archetype {
+                                        archetype.short_name()
+                                    } else {
+                                        ""
+                                    }
+                                })
+                                .join(", ")
+                        ));
+                    });
                 }
             }
         }

From 2916462609f769bd8fe6b1cd33d4f3d7e46e73f2 Mon Sep 17 00:00:00 2001
From: Emil Ernerfeldt 
Date: Fri, 13 Mar 2026 12:59:58 +0100
Subject: [PATCH 129/513] Collapse multi-line labels if there are too many on
 screen

When there are a lot of big labels on screen, things gets messy. This
helps

![animate-labels](https://github.com/user-attachments/assets/0ecde630-933b-4222-8734-329bec767a46)

Source-Ref: 6d37507fde95b967a58ce1b08a708f471dc968b3
---
 Cargo.lock                                    |  10 +
 crates/viewer/re_ui/data/dark_theme.ron       |   1 +
 crates/viewer/re_ui/data/light_theme.ron      |   1 +
 crates/viewer/re_ui/src/design_tokens.rs      |   5 +
 crates/viewer/re_view_spatial/src/ui.rs       | 288 ++++++++++++------
 crates/viewer/re_view_spatial/src/ui_2d.rs    |   2 +-
 crates/viewer/re_view_spatial/src/ui_3d.rs    |   2 +-
 .../src/visualizers/utilities/labels.rs       |   2 +-
 tests/rust/test_label_compaction/Cargo.toml   |  17 ++
 tests/rust/test_label_compaction/src/main.rs  |  45 +++
 10 files changed, 278 insertions(+), 95 deletions(-)
 create mode 100644 tests/rust/test_label_compaction/Cargo.toml
 create mode 100644 tests/rust/test_label_compaction/src/main.rs

diff --git a/Cargo.lock b/Cargo.lock
index 9910772bd12b..7881bfb8c368 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -12351,6 +12351,16 @@ dependencies = [
  "rerun",
 ]
 
+[[package]]
+name = "test_label_compaction"
+version = "0.31.0-alpha.1+dev"
+dependencies = [
+ "anyhow",
+ "clap",
+ "re_log",
+ "rerun",
+]
+
 [[package]]
 name = "test_out_of_order_transforms"
 version = "0.31.0-alpha.1+dev"
diff --git a/crates/viewer/re_ui/data/dark_theme.ron b/crates/viewer/re_ui/data/dark_theme.ron
index aadf8b7ae3e1..041d47df53b6 100644
--- a/crates/viewer/re_ui/data/dark_theme.ron
+++ b/crates/viewer/re_ui/data/dark_theme.ron
@@ -8,6 +8,7 @@
   "default_modal_width": 400,
 
   "spatial_label_bg_opacity": 0.5,
+  "slow_animation_duration_sec": 0.2,
 
   "loop_selection_alpha": 0.55, // Multiplied with selection_bg_fill
   "loop_selection_alpha_inactive": 0.20, // Multiplied with selection_bg_fill
diff --git a/crates/viewer/re_ui/data/light_theme.ron b/crates/viewer/re_ui/data/light_theme.ron
index 87c1290fa2c2..52f632d79c14 100644
--- a/crates/viewer/re_ui/data/light_theme.ron
+++ b/crates/viewer/re_ui/data/light_theme.ron
@@ -8,6 +8,7 @@
   "default_modal_width": 400,
 
   "spatial_label_bg_opacity": 0.5,
+  "slow_animation_duration_sec": 0.2,
 
   "loop_selection_alpha": 0.55, // Multiplied with selection_bg_fill
   "loop_selection_alpha_inactive": 0.20, // Multiplied with selection_bg_fill
diff --git a/crates/viewer/re_ui/src/design_tokens.rs b/crates/viewer/re_ui/src/design_tokens.rs
index 363bc4e4f5f0..fd6f4b51cf21 100644
--- a/crates/viewer/re_ui/src/design_tokens.rs
+++ b/crates/viewer/re_ui/src/design_tokens.rs
@@ -79,6 +79,10 @@ pub struct DesignTokens {
     /// Opacity multiplier for the background of 2D labels in spatial views.
     pub spatial_label_bg_opacity: f32,
 
+    /// Animation duration in seconds for some things that should feel smooth,
+    /// (as opposed as the default egui animaion that should feel _snappy_).
+    pub slow_animation_duration_sec: f32,
+
     /// Background color for viewport views.
     pub viewport_background: Color32,
 
@@ -307,6 +311,7 @@ impl DesignTokens {
             info_text_color: get_color("info_text_color"),
 
             spatial_label_bg_opacity: get_scalar("spatial_label_bg_opacity")?,
+            slow_animation_duration_sec: get_scalar("slow_animation_duration_sec")?,
 
             viewport_background: get_color("viewport_background"),
 
diff --git a/crates/viewer/re_view_spatial/src/ui.rs b/crates/viewer/re_view_spatial/src/ui.rs
index 58740edb0694..f1ca9dcbff7d 100644
--- a/crates/viewer/re_view_spatial/src/ui.rs
+++ b/crates/viewer/re_view_spatial/src/ui.rs
@@ -189,7 +189,7 @@ impl SpatialViewState {
 }
 
 pub fn create_labels(
-    mut labels: Vec,
+    labels: &[UiLabel],
     ui_from_scene: egui::emath::RectTransform,
     eye3d: &Eye,
     parent_ui: &egui::Ui,
@@ -200,120 +200,100 @@ pub fn create_labels(
 
     let ui_from_world_3d = eye3d.ui_from_world(*ui_from_scene.to());
 
-    // Closest last (painters algorithm)
-    labels.sort_by_key(|label| {
-        if let UiLabelTarget::Position3D(pos) = label.target {
-            OrderedFloat::from(-ui_from_world_3d.project_point3(pos).z)
-        } else {
-            OrderedFloat::from(0.0)
-        }
-    });
-
-    let mut label_shapes = Vec::with_capacity(labels.len() * 2);
-    let mut ui_rects = Vec::with_capacity(labels.len());
-
-    for label in labels {
-        let (wrap_width, text_anchor_pos) = match label.target {
-            UiLabelTarget::Rect(rect) => {
-                if spatial_kind == SpatialViewKind::ThreeD {
-                    continue; // TODO(#1640): 2D labels are not visible in 3D for now.
-                }
-                let rect_in_ui = ui_from_scene.transform_rect(rect);
-                (
-                    // Place the text centered below the rect
-                    (rect_in_ui.width() - 4.0).at_least(60.0),
-                    rect_in_ui.center_bottom(),
-                )
-            }
-            UiLabelTarget::Point2D(pos) => {
-                if spatial_kind == SpatialViewKind::ThreeD {
-                    continue; // TODO(#1640): 2D labels are not visible in 3D for now.
-                }
-                let pos_in_ui = ui_from_scene.transform_pos(pos);
-                (f32::INFINITY, pos_in_ui)
-            }
-            UiLabelTarget::Position3D(pos) => {
-                if spatial_kind == SpatialViewKind::TwoD {
-                    continue; // TODO(#1640): 3D labels are not visible in 2D for now.
-                }
-                let pos_in_ui = ui_from_world_3d * pos.extend(1.0);
-                if pos_in_ui.w <= 0.0 {
-                    continue; // behind camera
-                }
-                let pos_in_ui = pos_in_ui / pos_in_ui.w;
-                (f32::INFINITY, egui::pos2(pos_in_ui.x, pos_in_ui.y))
-            }
-        };
-
-        let font_id = egui::TextStyle::Body.resolve(parent_ui.style());
-        let is_error = matches!(label.style, UiLabelStyle::Error);
+    let resolved_labels =
+        resolve_label_positions(labels, &ui_from_scene, &ui_from_world_3d, spatial_kind);
+
+    // When there are many visible multi-line labels, collapse them to their
+    // first line to reduce visual clutter.
+    let num_multiline_labels = resolved_labels
+        .iter()
+        .filter(|(label, _, _)| label.text.contains('\n'))
+        .count();
+    let show_full_labels = num_multiline_labels <= 5; // TODO(emilk): very simplistic heuristic
+    // 0=only show first line, 1=show all lines.
+    let label_expansion = parent_ui.animate_bool_with_time(
+        parent_ui.id().with("label-animation"),
+        show_full_labels,
+        parent_ui.tokens().slow_animation_duration_sec,
+    );
+
+    let mut label_shapes = Vec::with_capacity(resolved_labels.len() * 2);
+    let mut ui_rects = Vec::with_capacity(resolved_labels.len());
+
+    for (label, wrap_width, text_anchor_pos) in &resolved_labels {
         let text_color = match label.style {
             UiLabelStyle::Default => parent_ui.visuals().strong_text_color(),
             UiLabelStyle::Color(color) => color,
             UiLabelStyle::Error => parent_ui.style().visuals.strong_text_color(),
         };
-        let format = egui::TextFormat::simple(font_id, text_color);
 
-        let galley = parent_ui.fonts_mut(|fonts| {
-            fonts.layout_job({
-                egui::text::LayoutJob {
+        let layout_text = |text: &str| -> std::sync::Arc {
+            let font_id = egui::TextStyle::Body.resolve(parent_ui.style());
+            let format = egui::TextFormat::simple(font_id, text_color);
+            parent_ui.fonts_mut(|fonts| {
+                fonts.layout_job(egui::text::LayoutJob {
                     sections: vec![egui::text::LayoutSection {
                         leading_space: 0.0,
-                        byte_range: 0..label.text.len(),
-                        format,
+                        byte_range: 0..text.len(),
+                        format: format.clone(),
                     }],
-                    text: label.text.clone(),
+                    text: text.to_owned(),
                     wrap: TextWrapping {
-                        max_width: wrap_width,
+                        max_width: *wrap_width,
                         ..Default::default()
                     },
                     break_on_newline: true,
                     halign: egui::Align::Center,
                     ..Default::default()
-                }
+                })
             })
-        });
+        };
 
-        let offset = egui::vec2(0.0, 5.0); // Add some margin
-        let text_rect =
-            egui::Align2::CENTER_TOP.anchor_size(text_anchor_pos + offset, galley.size());
-        let bg_rect = text_rect.expand2(egui::vec2(2.0, 0.0));
+        // The compact galley is always just the first line.
+        // The full galley is only needed when the text has newlines.
+        let (compact_galley, full_galley) =
+            if let Some((first_line, _)) = label.text.split_once('\n') {
+                (
+                    layout_text(&format!("{first_line}…")),
+                    Some(layout_text(&label.text)),
+                )
+            } else {
+                (layout_text(&label.text), None)
+            };
 
-        let highlight = highlights
-            .entity_highlight(label.labeled_instance.entity_path_hash)
-            .index_highlight(
-                label.labeled_instance.instance,
-                label.visualizer_instruction,
-            );
-        let background_color = match highlight.hover {
-            HoverHighlight::None => match highlight.selection {
-                SelectionHighlight::None => {
-                    if is_error {
-                        parent_ui.error_label_background_color()
-                    } else {
-                        parent_ui.style().visuals.widgets.inactive.bg_fill
-                    }
-                }
-                SelectionHighlight::SiblingSelection => {
-                    parent_ui.style().visuals.widgets.active.bg_fill
-                }
-                SelectionHighlight::Selection => parent_ui.style().visuals.widgets.active.bg_fill,
-            },
-            HoverHighlight::Hovered => parent_ui.style().visuals.widgets.hovered.bg_fill,
+        let label_expansion = if full_galley.is_some() {
+            label_expansion
+        } else {
+            0.0 // No need to animate if there is only one line.
         };
 
-        let background_color =
-            background_color.gamma_multiply(parent_ui.tokens().spatial_label_bg_opacity);
+        let text_offset = egui::vec2(0.0, 5.0);
 
+        let compact_text_rect = egui::Align2::CENTER_TOP
+            .anchor_size(*text_anchor_pos + text_offset, compact_galley.size());
+
+        let full_text_rect = full_galley.as_ref().map(|fg| {
+            egui::Align2::CENTER_TOP.anchor_size(*text_anchor_pos + text_offset, fg.size())
+        });
+
+        let bg_rect = if let Some(full_text_rect) = full_text_rect {
+            compact_text_rect.lerp_towards(&full_text_rect, label_expansion)
+        } else {
+            compact_text_rect
+        }
+        .expand2(egui::vec2(6.0, 4.0));
+
+        // Background
+        let is_error = label.style == UiLabelStyle::Error;
+        let background_color = label_background_color(parent_ui, highlights, label, is_error);
         let rect_stroke = if is_error {
             egui::Stroke::new(1.0, parent_ui.style().visuals.error_fg_color)
         } else {
             egui::Stroke::NONE
         };
-
         label_shapes.push(
             egui::epaint::RectShape::new(
-                bg_rect.expand(4.0),
+                bg_rect,
                 4.0,
                 background_color,
                 rect_stroke,
@@ -321,11 +301,42 @@ pub fn create_labels(
             )
             .into(),
         );
-        label_shapes.push(egui::Shape::galley(
-            text_rect.center_top(),
-            galley,
-            text_color,
-        ));
+
+        // Compact (single-line) text: fade out as full text fades in.
+        label_shapes.push(
+            egui::epaint::TextShape::new(
+                compact_text_rect.center_top(),
+                compact_galley,
+                text_color,
+            )
+            .with_opacity_factor(1.0 - label_expansion)
+            .into(),
+        );
+
+        // Full (multi-line) text: fade in and scale up during expansion.
+        if let (Some(full_galley), Some(full_text_rect)) = (full_galley, full_text_rect)
+            && 0.0 < label_expansion
+        {
+            let mut text_shape =
+                egui::epaint::TextShape::new(full_text_rect.center_top(), full_galley, text_color)
+                    .with_opacity_factor(label_expansion);
+
+            // Scale from compact size to full size, pivoting at the top-center.
+            let full_height = full_text_rect.height();
+            if 0.0 < full_height {
+                let scale = egui::emath::lerp(
+                    compact_text_rect.height() / full_height..=1.0,
+                    label_expansion,
+                );
+                let pivot = full_text_rect.center_top().to_vec2();
+                text_shape.transform(egui::emath::TSTransform {
+                    scaling: scale,
+                    translation: pivot * (1.0 - scale),
+                });
+            }
+
+            label_shapes.push(text_shape.into());
+        }
 
         ui_rects.push(PickableUiRect {
             rect: ui_from_scene.inverse().transform_rect(bg_rect),
@@ -336,6 +347,99 @@ pub fn create_labels(
     (label_shapes, ui_rects)
 }
 
+/// Resolve screen positions for labels, culling those outside the viewport
+/// or incompatible with the current view kind. Sorted by depth (closest last).
+fn resolve_label_positions(
+    labels: &[UiLabel],
+    ui_from_scene: &egui::emath::RectTransform,
+    ui_from_world_3d: &glam::Mat4,
+    spatial_kind: SpatialViewKind,
+) -> Vec<(UiLabel, f32, egui::Pos2)> {
+    let viewport = ui_from_scene.to().expand(100.0);
+
+    let mut resolved = Vec::with_capacity(labels.len());
+    for label in labels {
+        let (wrap_width, text_anchor_pos) = match label.target {
+            UiLabelTarget::Rect(rect) => {
+                if spatial_kind == SpatialViewKind::ThreeD {
+                    continue; // TODO(#1640): 2D labels are not visible in 3D for now.
+                }
+                let rect_in_ui = ui_from_scene.transform_rect(rect);
+                (
+                    (rect_in_ui.width() - 4.0).at_least(60.0),
+                    rect_in_ui.center_bottom(),
+                )
+            }
+            UiLabelTarget::Point2D(pos) => {
+                if spatial_kind == SpatialViewKind::ThreeD {
+                    continue; // TODO(#1640): 2D labels are not visible in 3D for now.
+                }
+                let pos_in_ui = ui_from_scene.transform_pos(pos);
+                (f32::INFINITY, pos_in_ui)
+            }
+            UiLabelTarget::Position3D(pos) => {
+                if spatial_kind == SpatialViewKind::TwoD {
+                    continue; // TODO(#1640): 3D labels are not visible in 2D for now.
+                }
+                let pos_in_ui = *ui_from_world_3d * pos.extend(1.0);
+                if pos_in_ui.w <= 0.0 {
+                    continue; // behind camera
+                }
+                let pos_in_ui = pos_in_ui / pos_in_ui.w;
+                (f32::INFINITY, egui::pos2(pos_in_ui.x, pos_in_ui.y))
+            }
+        };
+
+        if !viewport.contains(text_anchor_pos) {
+            continue;
+        }
+
+        resolved.push((label.clone(), wrap_width, text_anchor_pos));
+    }
+
+    // Closest last (painters algorithm)
+    resolved.sort_by_key(|(label, _, _)| {
+        if let UiLabelTarget::Position3D(pos) = label.target {
+            OrderedFloat::from(-ui_from_world_3d.project_point3(pos).z)
+        } else {
+            OrderedFloat::from(0.0)
+        }
+    });
+
+    resolved
+}
+
+/// Compute the background color for a label based on highlights.
+fn label_background_color(
+    ui: &egui::Ui,
+    highlights: &ViewHighlights,
+    label: &UiLabel,
+    is_error: bool,
+) -> egui::Color32 {
+    let highlight = highlights
+        .entity_highlight(label.labeled_instance.entity_path_hash)
+        .index_highlight(
+            label.labeled_instance.instance,
+            label.visualizer_instruction,
+        );
+    let color = match highlight.hover {
+        HoverHighlight::None => match highlight.selection {
+            SelectionHighlight::None => {
+                if is_error {
+                    ui.error_label_background_color()
+                } else {
+                    ui.style().visuals.widgets.inactive.bg_fill
+                }
+            }
+            SelectionHighlight::SiblingSelection | SelectionHighlight::Selection => {
+                ui.style().visuals.widgets.active.bg_fill
+            }
+        },
+        HoverHighlight::Hovered => ui.style().visuals.widgets.hovered.bg_fill,
+    };
+    color.gamma_multiply(ui.tokens().spatial_label_bg_opacity)
+}
+
 pub fn paint_loading_indicators(
     ui: &mut egui::Ui,
     ui_from_scene: egui::emath::RectTransform,
diff --git a/crates/viewer/re_view_spatial/src/ui_2d.rs b/crates/viewer/re_view_spatial/src/ui_2d.rs
index 3e9dab7edf8f..08164eb87d51 100644
--- a/crates/viewer/re_view_spatial/src/ui_2d.rs
+++ b/crates/viewer/re_view_spatial/src/ui_2d.rs
@@ -236,7 +236,7 @@ impl SpatialView2D {
 
         // Create labels now since their shapes participate are added to scene.ui for picking.
         let (label_shapes, ui_rects) = create_labels(
-            collect_ui_labels(&system_output.view_systems),
+            &collect_ui_labels(&system_output.view_systems),
             ui_from_scene,
             &eye,
             ui,
diff --git a/crates/viewer/re_view_spatial/src/ui_3d.rs b/crates/viewer/re_view_spatial/src/ui_3d.rs
index 83c050a217fc..5ec5343f9397 100644
--- a/crates/viewer/re_view_spatial/src/ui_3d.rs
+++ b/crates/viewer/re_view_spatial/src/ui_3d.rs
@@ -215,7 +215,7 @@ impl SpatialView3D {
 
         // Create labels now since their shapes participate are added to scene.ui for picking.
         let (label_shapes, ui_rects) = create_labels(
-            collect_ui_labels(&system_output.view_systems),
+            &collect_ui_labels(&system_output.view_systems),
             RectTransform::from_to(ui_rect, ui_rect),
             &eye,
             ui,
diff --git a/crates/viewer/re_view_spatial/src/visualizers/utilities/labels.rs b/crates/viewer/re_view_spatial/src/visualizers/utilities/labels.rs
index 0b4dd913cecc..ed6b24f68aed 100644
--- a/crates/viewer/re_view_spatial/src/visualizers/utilities/labels.rs
+++ b/crates/viewer/re_view_spatial/src/visualizers/utilities/labels.rs
@@ -20,7 +20,7 @@ pub enum UiLabelTarget {
     Position3D(glam::Vec3),
 }
 
-#[derive(Clone)]
+#[derive(Clone, PartialEq, Eq)]
 pub enum UiLabelStyle {
     Default,
 
diff --git a/tests/rust/test_label_compaction/Cargo.toml b/tests/rust/test_label_compaction/Cargo.toml
new file mode 100644
index 000000000000..5bec5defca4d
--- /dev/null
+++ b/tests/rust/test_label_compaction/Cargo.toml
@@ -0,0 +1,17 @@
+[package]
+name = "test_label_compaction"
+version.workspace = true
+edition.workspace = true
+rust-version.workspace = true
+license.workspace = true
+publish = false
+
+[lints]
+workspace = true
+
+[dependencies]
+re_log = { workspace = true, features = ["setup"] }
+rerun = { path = "../../../crates/top/rerun", features = ["clap"] }
+
+anyhow.workspace = true
+clap = { workspace = true, features = ["derive"] }
diff --git a/tests/rust/test_label_compaction/src/main.rs b/tests/rust/test_label_compaction/src/main.rs
new file mode 100644
index 000000000000..5d6363e1979e
--- /dev/null
+++ b/tests/rust/test_label_compaction/src/main.rs
@@ -0,0 +1,45 @@
+//! Test for label compaction in 3D spatial views.
+//!
+//! Logs many labeled 3D points to test that multi-line labels
+//! are compacted to their first line when there are many on screen.
+//!
+//! ## Usage
+//! ```
+//! cargo r -p test_label_compaction
+//! ```
+
+#[derive(Debug, clap::Parser)]
+#[clap(author, version, about)]
+struct Args {
+    #[command(flatten)]
+    rerun: rerun::clap::RerunArgs,
+}
+
+fn main() -> anyhow::Result<()> {
+    re_log::setup_logging();
+
+    use clap::Parser as _;
+    let args = Args::parse();
+
+    let (rec, _serve_guard) = args.rerun.init("rerun_example_label_compaction")?;
+
+    // 20 points with multi-line labels, arranged in a grid
+    for i in 0..20 {
+        let x = (i % 5) as f32 * 3.0;
+        let y = (i / 5) as f32 * 3.0;
+        rec.log(
+            format!("points/{i}"),
+            &rerun::Points3D::new([(x, y, 0.0)]).with_labels([format!(
+                "Point {i}\n\
+                 Position: ({x:.1}, {y:.1}, 0.0)\n\
+                 Status: active\n\
+                 Category: test-label-{}\n\
+                 Priority: {}",
+                i % 4,
+                ["low", "medium", "high", "critical"][i as usize % 4],
+            )]),
+        )?;
+    }
+
+    Ok(())
+}

From 115225e1612782edd441b77f8bd223457b752e71 Mon Sep 17 00:00:00 2001
From: Emil Ernerfeldt 
Date: Fri, 13 Mar 2026 13:33:35 +0100
Subject: [PATCH 130/513] Fix `rerun download` when there are several manifest
 parts

Source-Ref: 63610d13f3913bea9cd84cecd43a07036c559a8c
---
 Cargo.lock                                    |  1 +
 crates/store/re_data_loader/Cargo.toml        |  9 ++--
 crates/store/re_data_loader/src/lib.rs        | 12 +++++
 .../src/loader_mcap/tests/util.rs             | 18 +++----
 .../re_data_loader/tests/test_mcap_loader.rs  | 10 +---
 .../re_entity_db/src/rrd_manifest_index.rs    | 10 +++-
 .../src/rrd/footer/raw_rrd_manifest.rs        | 49 +++++++++++--------
 .../src/rrd/footer/rrd_manifest.rs            |  7 ++-
 crates/store/re_redap_client/src/grpc.rs      | 49 +++++++++++++------
 crates/viewer/re_viewer/src/app.rs            |  2 +-
 10 files changed, 104 insertions(+), 63 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index 7881bfb8c368..99bc5ff3f3c8 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -8786,6 +8786,7 @@ dependencies = [
  "re_chunk_store",
  "re_crash_handler",
  "re_error",
+ "re_format",
  "re_lenses",
  "re_lenses_core",
  "re_log",
diff --git a/crates/store/re_data_loader/Cargo.toml b/crates/store/re_data_loader/Cargo.toml
index 6cf5be511353..6e1640199904 100644
--- a/crates/store/re_data_loader/Cargo.toml
+++ b/crates/store/re_data_loader/Cargo.toml
@@ -24,20 +24,21 @@ default = []
 
 
 [dependencies]
-re_lenses_core.workspace = true
 re_arrow_util.workspace = true
 re_build_info.workspace = true
 re_chunk.workspace = true
 re_error.workspace = true
+re_format.workspace = true
+re_lenses_core.workspace = true
 re_lenses.workspace = true
-re_log.workspace = true
+re_log_channel.workspace = true
 re_log_encoding = { workspace = true, features = ["decoder"] }
 re_log_types.workspace = true
+re_log.workspace = true
 re_mcap.workspace = true
-re_log_channel.workspace = true
 re_quota_channel.workspace = true
-re_tracing.workspace = true
 re_sdk_types = { workspace = true, features = ["ecolor", "glam", "image", "video"] }
+re_tracing.workspace = true
 re_video.workspace = true
 
 ahash.workspace = true
diff --git a/crates/store/re_data_loader/src/lib.rs b/crates/store/re_data_loader/src/lib.rs
index 0f95ceb5d7f5..5d7528fce504 100644
--- a/crates/store/re_data_loader/src/lib.rs
+++ b/crates/store/re_data_loader/src/lib.rs
@@ -428,6 +428,18 @@ impl LoadedData {
             Self::LogMsg(_name, msg) => Ok(msg),
         }
     }
+
+    /// Convert the data into a [`Chunk`], ignoring all non-chunk-related things.
+    pub fn into_chunk(self) -> Option {
+        match self {
+            Self::Chunk(_name, _store_id, chunk) => Some(chunk),
+            Self::ArrowMsg(_name, _store_id, arrow_msg) => Chunk::from_arrow_msg(&arrow_msg).ok(),
+            Self::LogMsg(_name, msg) => match msg {
+                LogMsg::ArrowMsg(_store_id, arrow_msg) => Chunk::from_arrow_msg(&arrow_msg).ok(),
+                LogMsg::SetStoreInfo { .. } | LogMsg::BlueprintActivationCommand { .. } => None,
+            },
+        }
+    }
 }
 
 // ----------------------------------------------------------------------------
diff --git a/crates/store/re_data_loader/src/loader_mcap/tests/util.rs b/crates/store/re_data_loader/src/loader_mcap/tests/util.rs
index b7638c8d00e9..cbc4e5aeeec4 100644
--- a/crates/store/re_data_loader/src/loader_mcap/tests/util.rs
+++ b/crates/store/re_data_loader/src/loader_mcap/tests/util.rs
@@ -29,16 +29,14 @@ pub fn load_mcap(path: impl AsRef) -> LoadedMcap {
             panic!("Failed to load MCAP file at {}: {err}", path.display());
         });
 
-    let chunks = rx
-        .iter()
-        .filter_map(|res| {
-            if let LoadedData::Chunk(_, _, chunk) = res {
-                Some(chunk)
-            } else {
-                None
-            }
-        })
-        .collect();
+    let chunks: Vec = rx.iter().filter_map(LoadedData::into_chunk).collect();
+
+    if 10_000 < chunks.len() {
+        re_log::warn!(
+            "MCAP file contained {} chunks. Consider running `rerun rrd compact` on the output.",
+            re_format::format_uint(chunks.len()),
+        );
+    }
 
     LoadedMcap { chunks }
 }
diff --git a/crates/store/re_data_loader/tests/test_mcap_loader.rs b/crates/store/re_data_loader/tests/test_mcap_loader.rs
index 01e04010aaca..e07208abf005 100644
--- a/crates/store/re_data_loader/tests/test_mcap_loader.rs
+++ b/crates/store/re_data_loader/tests/test_mcap_loader.rs
@@ -28,15 +28,7 @@ mod tests {
         drop(tx);
 
         // Collect chunks
-        rx.iter()
-            .filter_map(|res| {
-                if let LoadedData::Chunk(_, _, chunk) = res {
-                    Some(chunk)
-                } else {
-                    None
-                }
-            })
-            .collect()
+        rx.iter().filter_map(LoadedData::into_chunk).collect()
     }
 
     // TODO(grtlr): This should be something like a snippet / backwards-compatibility test, but
diff --git a/crates/store/re_entity_db/src/rrd_manifest_index.rs b/crates/store/re_entity_db/src/rrd_manifest_index.rs
index 5a02c3bbfa0d..ecf6fe9691d7 100644
--- a/crates/store/re_entity_db/src/rrd_manifest_index.rs
+++ b/crates/store/re_entity_db/src/rrd_manifest_index.rs
@@ -235,7 +235,7 @@ impl RrdManifestIndex {
         }
 
         let new_full_manifest = if let Some(existing) = self.manifest.take() {
-            Arc::new(RrdManifest::concat(&existing, &delta)?)
+            Arc::new(RrdManifest::concat(&[&existing, &delta])?)
         } else {
             delta
         };
@@ -407,6 +407,14 @@ impl RrdManifestIndex {
     /// Mark the manifest as complete (all parts have been received).
     pub fn set_manifest_complete(&mut self) {
         self.manifest_complete = true;
+
+        let num_root_chunks = self.root_chunks.len();
+        if 10_000 < num_root_chunks {
+            re_log::warn!(
+                "There are {} rooot chunks in this recording. Consider running `rerun rrd compact` on the original.",
+                re_format::format_uint(num_root_chunks)
+            );
+        }
     }
 
     /// The manifest as it currently stands.
diff --git a/crates/store/re_log_encoding/src/rrd/footer/raw_rrd_manifest.rs b/crates/store/re_log_encoding/src/rrd/footer/raw_rrd_manifest.rs
index 3f8a12f6542e..391b7b8ed9b4 100644
--- a/crates/store/re_log_encoding/src/rrd/footer/raw_rrd_manifest.rs
+++ b/crates/store/re_log_encoding/src/rrd/footer/raw_rrd_manifest.rs
@@ -1,5 +1,6 @@
 use std::collections::{BTreeMap, HashMap};
 
+use arrow::array::RecordBatch;
 use arrow::buffer::NullBuffer;
 use arrow::datatypes::Field;
 use arrow::{
@@ -236,40 +237,48 @@ impl std::fmt::Display for RrdManifestSha256 {
 }
 
 impl RawRrdManifest {
-    /// Concatenate two manifests by appending the rows of `other` onto `self`.
+    /// Concatenate multiple manifests by appending all rows together.
     ///
-    /// Both manifests must be for the same recording (same `store_id`).
-    /// The sorbet schemas are merged, and the data `RecordBatch`es are concatenated.
+    /// All manifests must be for the same recording (same `store_id` and sorbet schema).
+    /// The data `RecordBatch`es are concatenated.
     ///
     /// This is used when the server sends a manifest in multiple parts.
-    pub fn concat(self, other: Self) -> Result {
+    pub fn concat(manifests: &[&Self]) -> Result {
         re_tracing::profile_function!();
 
-        re_log::debug_assert_eq!(self.store_id, other.store_id);
+        let first = manifests.first().ok_or_else(|| {
+            ArrowError::InvalidArgumentError("No manifests to concatenate".to_owned())
+        })?;
 
-        let (sorbet_schema, sorbet_schema_sha256) =
-            if self.sorbet_schema_sha256 == other.sorbet_schema_sha256 {
-                (self.sorbet_schema, self.sorbet_schema_sha256)
-            } else {
+        for other in &manifests[1..] {
+            if first.store_id != other.store_id {
+                return Err(ArrowError::SchemaError(
+                    "Mismatching store_id in RawRrdManifest::concat".to_owned(),
+                ));
+            }
+
+            if first.sorbet_schema_sha256 != other.sorbet_schema_sha256 {
                 return Err(ArrowError::SchemaError(
                     "Mismatching sorbet recording schemas in RawRrdManifest::concat".to_owned(),
                 ));
-            };
+            }
 
-        if self.data.schema() != other.data.schema() {
-            re_log::debug!(
-                "Different schemas in the RrdManifest ({} columns in existing, {} in the new part)",
-                self.data.num_columns(),
-                other.data.num_columns(),
-            );
+            if first.data.schema() != other.data.schema() {
+                re_log::debug!(
+                    "Different schemas in the RrdManifest ({} columns in existing, {} in the new part)",
+                    first.data.num_columns(),
+                    other.data.num_columns(),
+                );
+            }
         }
 
-        let data = arrow::compute::concat_batches(&self.data.schema(), &[self.data, other.data])?;
+        let batches: Vec<&RecordBatch> = manifests.iter().map(|m| &m.data).collect();
+        let data = arrow::compute::concat_batches(&first.data.schema(), batches)?;
 
         Ok(Self {
-            store_id: self.store_id,
-            sorbet_schema,
-            sorbet_schema_sha256,
+            store_id: first.store_id.clone(),
+            sorbet_schema: first.sorbet_schema.clone(),
+            sorbet_schema_sha256: first.sorbet_schema_sha256,
             data,
         })
     }
diff --git a/crates/store/re_log_encoding/src/rrd/footer/rrd_manifest.rs b/crates/store/re_log_encoding/src/rrd/footer/rrd_manifest.rs
index 6419d9be63c3..275ecce4b7e2 100644
--- a/crates/store/re_log_encoding/src/rrd/footer/rrd_manifest.rs
+++ b/crates/store/re_log_encoding/src/rrd/footer/rrd_manifest.rs
@@ -185,11 +185,10 @@ impl RrdManifest {
         &self.recording_schema
     }
 
-    pub fn concat(a: &Self, b: &Self) -> CodecResult {
+    pub fn concat(manifests: &[&Self]) -> CodecResult {
         re_tracing::profile_function!();
-        let a = a.raw.clone();
-        let b = b.raw.clone();
-        let combined = a.concat(b).map_err(|err| {
+        let raws: Vec<&RawRrdManifest> = manifests.iter().map(|m| &m.raw).collect();
+        let combined = RawRrdManifest::concat(&raws).map_err(|err| {
             CodecError::FrameDecoding(format!("Failed to concatenate RRD manifests: {err}"))
         })?;
         Self::try_new(combined)
diff --git a/crates/store/re_redap_client/src/grpc.rs b/crates/store/re_redap_client/src/grpc.rs
index b4f475a84c2c..c380c2a8bdb9 100644
--- a/crates/store/re_redap_client/src/grpc.rs
+++ b/crates/store/re_redap_client/src/grpc.rs
@@ -509,13 +509,12 @@ async fn stream_segment_from_server(
         Ok(manifest_stream) => {
             let mut manifest_stream = std::pin::pin!(manifest_stream);
 
-            let mut last_rrd_manifest: Option> = None;
-            let mut part_nr: usize = 0;
+            let mut rrd_manifest_parts: Vec> = Vec::new();
 
             while let Some(part_result) = manifest_stream.next().await {
                 let raw_rrd_manifest_part = part_result?;
 
-                part_nr += 1;
+                let part_nr = rrd_manifest_parts.len() + 1;
                 re_log::debug!(
                     "Received RRD manifest part #{part_nr}/? ({} deflated, {:.1}s elapsed)",
                     re_format::format_bytes(raw_rrd_manifest_part.total_size_bytes() as _),
@@ -540,17 +539,18 @@ async fn stream_segment_from_server(
                     return Ok(ControlFlow::Break(()));
                 }
 
-                last_rrd_manifest = Some(rrd_manifest);
+                rrd_manifest_parts.push(rrd_manifest);
             }
 
-            let Some(rrd_manifest) = last_rrd_manifest else {
+            if rrd_manifest_parts.is_empty() {
                 return Err(ApiError::serialization(
                     "failed to parse the response for /GetRrdManifest (no data)",
                 ));
-            };
+            }
 
+            let part_nr = rrd_manifest_parts.len();
             re_log::debug!(
-                "The server supports on-demand streaming. Full RRD manifest loaded in {:.1}s in {}",
+                "Full RRD manifest loaded in {:.1}s in {}",
                 start_time.elapsed().as_secs_f32(),
                 re_format::format_plural_s(part_nr, "part")
             );
@@ -569,8 +569,16 @@ async fn stream_segment_from_server(
                     return Ok(ControlFlow::Continue(()));
                 }
                 StoreKind::Recording | StoreKind::Blueprint => {
-                    // Load all of the chunks in one go; most important first:
-                    let batch = sort_batch(rrd_manifest.data()).map_err(|err| {
+                    re_log::debug!("Loading all of the chunks in one go; most important first");
+                    let refs: Vec<&re_log_encoding::RrdManifest> =
+                        rrd_manifest_parts.iter().map(|m| m.as_ref()).collect();
+                    let combined = re_log_encoding::RrdManifest::concat(&refs).map_err(|err| {
+                        ApiError::invalid_arguments_with_source(
+                            err,
+                            "Failed to concatenate RRD manifest parts",
+                        )
+                    })?;
+                    let batch = sort_batch(combined.data()).map_err(|err| {
                         ApiError::invalid_arguments_with_source(err, "Failed to sort chunk index")
                     })?;
                     return load_chunks(client, tx, &store_id, batch, options).await;
@@ -712,18 +720,28 @@ async fn load_chunks(
     full_batch: RecordBatch,
     options: &StreamingOptions,
 ) -> ApiResult> {
-    re_log::trace!("Requesting {} chunks from server…", full_batch.num_rows());
+    let num_chunks = full_batch.num_rows();
+
+    re_log::debug!(
+        "Downloading {} chunks from server…",
+        re_format::format_uint(num_chunks)
+    );
+    if 10_000 < num_chunks {
+        re_log::warn!(
+            "There are {} chunks in this recording. Consider running `rerun rrd compact` on it!",
+            re_format::format_uint(num_chunks)
+        );
+    }
 
     use futures::stream::FuturesUnordered;
 
     // Batch requests in groups of N=32 rows.
     const BATCH_SIZE: usize = 32;
-    let num_rows = full_batch.num_rows();
     let total_size_bytes = total_size_bytes_from_batch(&full_batch);
     let mut futures = FuturesUnordered::new();
 
-    for start in (0..num_rows).step_by(BATCH_SIZE) {
-        let end = usize::min(start + BATCH_SIZE, num_rows);
+    for start in (0..num_chunks).step_by(BATCH_SIZE) {
+        let end = usize::min(start + BATCH_SIZE, num_chunks);
         let small_batch = full_batch.slice(start, end - start);
 
         let mut client = client.clone();
@@ -750,7 +768,10 @@ async fn load_chunks(
         }
     }
 
-    re_log::trace!("Finished downloading {} chunks.", num_rows);
+    re_log::trace!(
+        "Finished downloading {} chunks.",
+        re_format::format_uint(num_chunks)
+    );
 
     Ok(ControlFlow::Continue(()))
 }
diff --git a/crates/viewer/re_viewer/src/app.rs b/crates/viewer/re_viewer/src/app.rs
index 899492731e15..1f000d0238cf 100644
--- a/crates/viewer/re_viewer/src/app.rs
+++ b/crates/viewer/re_viewer/src/app.rs
@@ -3301,7 +3301,7 @@ impl App {
             let db = store_hub.entity_db_entry(&store_id);
 
             if cfg!(debug_assertions) && db.can_fetch_chunks_from_redap() {
-                // Some sanity checks:
+                re_tracing::profile_scope!("debug-sanity-check");
                 let storage_engine = db.storage_engine();
                 let store = storage_engine.store();
 

From f9ca49536b1d852e9bba7037f0a358c50c83ee70 Mon Sep 17 00:00:00 2001
From: Andreas Reich 
Date: Fri, 13 Mar 2026 14:22:43 +0100
Subject: [PATCH 131/513] Fix spamming errors when trying to show static
 scalars in the time series view

### Related

* Fixes RR-4038

### What

Before:

image

After: (not the same scene)
image

..also fix error flow in the time series view

Source-Ref: eff1252d7a348e44c71808b5ae4125c388de5a46
---
 crates/viewer/re_view_time_series/src/lib.rs  |  1 -
 .../src/line_visualizer_system.rs             | 50 +++++++++++------
 .../src/point_visualizer_system.rs            | 53 ++++++++++++-------
 crates/viewer/re_view_time_series/src/util.rs | 14 +----
 4 files changed, 70 insertions(+), 48 deletions(-)

diff --git a/crates/viewer/re_view_time_series/src/lib.rs b/crates/viewer/re_view_time_series/src/lib.rs
index d5b8c0252b22..4de423263d50 100644
--- a/crates/viewer/re_view_time_series/src/lib.rs
+++ b/crates/viewer/re_view_time_series/src/lib.rs
@@ -19,7 +19,6 @@ use re_sdk_types::{
     components::{AggregationPolicy, MarkerShape},
 };
 use re_viewer_context::external::re_entity_db::InstancePath;
-use re_viewport_blueprint::ViewPropertyQueryError;
 pub use view_class::TimeSeriesView;
 
 /// Maximum number of time series shown per entity when the scalar component
diff --git a/crates/viewer/re_view_time_series/src/line_visualizer_system.rs b/crates/viewer/re_view_time_series/src/line_visualizer_system.rs
index 42a142898a27..c0b801f3cec2 100644
--- a/crates/viewer/re_view_time_series/src/line_visualizer_system.rs
+++ b/crates/viewer/re_view_time_series/src/line_visualizer_system.rs
@@ -4,7 +4,7 @@ use re_chunk_store::{LatestAtQuery, RangeQuery, RowId};
 use re_log_types::{EntityPath, TimeInt};
 use re_sdk_types::components::{self, AggregationPolicy, InterpolationMode, StrokeWidth};
 use re_sdk_types::{Archetype as _, archetypes};
-use re_view::range_with_blueprint_resolved_data;
+use re_view::{ChunksWithComponent, range_with_blueprint_resolved_data};
 use re_viewer_context::external::re_entity_db::InstancePath;
 use re_viewer_context::{
     IdentifiedViewSystem, SingleRequiredComponentConstraint, ViewContext, ViewQuery,
@@ -16,7 +16,7 @@ use crate::series_query::{
     allocate_plot_points, collect_colors, collect_radius_ui, collect_scalars, collect_series_name,
     collect_series_visibility, determine_num_series,
 };
-use crate::{PlotPoint, PlotPointAttrs, PlotSeries, PlotSeriesKind, ViewPropertyQueryError, util};
+use crate::{PlotPoint, PlotPointAttrs, PlotSeries, PlotSeriesKind, util};
 
 /// The system for rendering [`archetypes::SeriesLines`] archetypes.
 #[derive(Default, Debug)]
@@ -71,7 +71,7 @@ impl VisualizerSystem for SeriesLinesSystem {
             .iter_visualizer_instruction_for(Self::identifier())
             .collect();
 
-        let all_series: Result, _> = data_results
+        let all_series: Vec<_> = data_results
             .par_iter()
             .map(|(data_result, instruction)| {
                 Self::load_series(
@@ -85,7 +85,7 @@ impl VisualizerSystem for SeriesLinesSystem {
             })
             .collect();
 
-        self.all_series.extend(all_series?.into_iter().flatten());
+        self.all_series.extend(all_series.into_iter().flatten());
 
         Ok(output)
     }
@@ -99,7 +99,7 @@ impl SeriesLinesSystem {
         data_result: &re_viewer_context::DataResult,
         instruction: &re_viewer_context::VisualizerInstruction,
         output: &re_viewer_context::VisualizerExecutionOutput,
-    ) -> Result, ViewPropertyQueryError> {
+    ) -> Vec {
         re_tracing::profile_function!(data_result.entity_path.to_string());
 
         let current_query = ctx.current_query();
@@ -107,7 +107,17 @@ impl SeriesLinesSystem {
 
         let data_time_range =
             util::data_result_time_range(ctx.viewer_ctx, data_result, view_query.timeline);
-        let query_range = util::determine_query_range(ctx, data_time_range)?;
+        let query_range = match util::determine_query_range(ctx, data_time_range) {
+            Ok(range) => range,
+            Err(err) => {
+                output.report_unspecified_source(
+                    instruction.id,
+                    VisualizerReportSeverity::Error,
+                    format!("Failed to determine query range: {err}"),
+                );
+                return Vec::new();
+            }
+        };
         let query = re_chunk_store::RangeQuery::new(view_query.timeline, query_range)
             // We must fetch data with extended bounds, otherwise the query clamping would
             // cut-off the data early at the edge of the view.
@@ -151,10 +161,23 @@ impl SeriesLinesSystem {
             re_view::VisualizerInstructionQueryResults::new(instruction, &results, output);
 
         // If we have no scalars, we can't do anything.
-        let scalar_iter =
-            results.iter_required(archetypes::Scalars::descriptor_scalars().component);
+        let scalar_component = archetypes::Scalars::descriptor_scalars().component;
+        let scalar_iter = results.iter_required(scalar_component);
         let all_scalar_chunks = scalar_iter.chunks();
 
+        // Filter out static times if any slipped in.
+        // It's enough to check the first one chunk since an entire column has to be either temporal or static.
+        let empty_chunks;
+        let all_scalar_chunks = if let Some(chunk) = all_scalar_chunks.chunks.first()
+            && chunk.is_static()
+        {
+            results.report_for_component(scalar_component, VisualizerReportSeverity::Error, "Can't plot data that was logged statically in a time series since there's no temporal dimension");
+            empty_chunks = ChunksWithComponent::empty(scalar_component);
+            &empty_chunks // Proceed with empty data so we catch other errors as well.
+        } else {
+            all_scalar_chunks
+        };
+
         // All the default values for a `PlotPoint`, accounting for both overrides and default values.
         // We know there's only a single value fallback for stroke width, so this is fine, albeit a bit hacky in case we add an array fallback later.
         let fallback_stroke_width: StrokeWidth = typed_fallback_for(
@@ -297,7 +320,7 @@ impl SeriesLinesSystem {
                 InstancePath::instance(data_result.entity_path.clone(), instance as u64)
             };
 
-            if let Err(err) = util::points_to_series(
+            util::points_to_series(
                 instance_path,
                 time_per_pixel,
                 visible,
@@ -308,15 +331,10 @@ impl SeriesLinesSystem {
                 aggregator,
                 &mut series,
                 instruction.id,
-            ) {
-                results.report_unspecified_source(
-                    VisualizerReportSeverity::Error,
-                    format!("Failed to create series: {err}"),
-                );
-            }
+            );
         }
 
-        Ok(series)
+        series
     }
 }
 
diff --git a/crates/viewer/re_view_time_series/src/point_visualizer_system.rs b/crates/viewer/re_view_time_series/src/point_visualizer_system.rs
index 3b07eaa06e79..3d345526b59f 100644
--- a/crates/viewer/re_view_time_series/src/point_visualizer_system.rs
+++ b/crates/viewer/re_view_time_series/src/point_visualizer_system.rs
@@ -3,7 +3,7 @@ use rayon::prelude::*;
 use re_sdk_types::archetypes::SeriesPoints;
 use re_sdk_types::components::{self, MarkerShape, MarkerSize};
 use re_sdk_types::{Archetype as _, Loggable as _, archetypes};
-use re_view::{clamped_or_nothing, range_with_blueprint_resolved_data};
+use re_view::{ChunksWithComponent, clamped_or_nothing, range_with_blueprint_resolved_data};
 use re_viewer_context::external::re_entity_db::InstancePath;
 use re_viewer_context::{
     IdentifiedViewSystem, SingleRequiredComponentConstraint, ViewContext, ViewQuery,
@@ -15,10 +15,7 @@ use crate::series_query::{
     all_scalars_indices, allocate_plot_points, collect_colors, collect_radius_ui, collect_scalars,
     collect_series_name, collect_series_visibility, determine_num_series,
 };
-use crate::{
-    PlotPoint, PlotPointAttrs, PlotSeries, PlotSeriesKind, ScatterAttrs, ViewPropertyQueryError,
-    util,
-};
+use crate::{PlotPoint, PlotPointAttrs, PlotSeries, PlotSeriesKind, ScatterAttrs, util};
 
 /// The system for rendering [`archetypes::SeriesPoints`] archetypes.
 #[derive(Default, Debug)]
@@ -71,7 +68,7 @@ impl VisualizerSystem for SeriesPointsSystem {
             .iter_visualizer_instruction_for(Self::identifier())
             .collect();
 
-        let all_series: Result, _> = data_results
+        let all_series: Vec<_> = data_results
             .par_iter()
             .map(|(data_result, instruction)| {
                 Self::load_series(
@@ -85,7 +82,7 @@ impl VisualizerSystem for SeriesPointsSystem {
             })
             .collect();
 
-        self.all_series.extend(all_series?.into_iter().flatten());
+        self.all_series.extend(all_series.into_iter().flatten());
 
         Ok(output)
     }
@@ -99,7 +96,7 @@ impl SeriesPointsSystem {
         data_result: &re_viewer_context::DataResult,
         instruction: &re_viewer_context::VisualizerInstruction,
         output: &VisualizerExecutionOutput,
-    ) -> Result, ViewPropertyQueryError> {
+    ) -> Vec {
         re_tracing::profile_function!(data_result.entity_path.to_string());
 
         let current_query = ctx.current_query();
@@ -107,7 +104,17 @@ impl SeriesPointsSystem {
 
         let data_time_range =
             util::data_result_time_range(ctx.viewer_ctx, data_result, view_query.timeline);
-        let query_range = util::determine_query_range(ctx, data_time_range)?;
+        let query_range = match util::determine_query_range(ctx, data_time_range) {
+            Ok(range) => range,
+            Err(err) => {
+                output.report_unspecified_source(
+                    instruction.id,
+                    VisualizerReportSeverity::Error,
+                    format!("Failed to determine query range: {err}"),
+                );
+                return Vec::new();
+            }
+        };
         let query = re_chunk_store::RangeQuery::new(view_query.timeline, query_range);
 
         let mut results = range_with_blueprint_resolved_data(
@@ -148,10 +155,23 @@ impl SeriesPointsSystem {
             re_view::VisualizerInstructionQueryResults::new(instruction, &results, output);
 
         // If we have no scalars, we can't do anything.
-        let scalar_iter =
-            results.iter_required(archetypes::Scalars::descriptor_scalars().component);
+        let scalar_component = archetypes::Scalars::descriptor_scalars().component;
+        let scalar_iter = results.iter_required(scalar_component);
         let all_scalar_chunks = scalar_iter.chunks();
 
+        // Filter out static times if any slipped in.
+        // It's enough to check the first one chunk since an entire column has to be either temporal or static.
+        let empty_chunks;
+        let all_scalar_chunks = if let Some(chunk) = all_scalar_chunks.chunks.first()
+            && chunk.is_static()
+        {
+            results.report_for_component(scalar_component, VisualizerReportSeverity::Error, "Can't plot data that was logged statically in a time series since there's no temporal dimension");
+            empty_chunks = ChunksWithComponent::empty(scalar_component);
+            &empty_chunks // Proceed with empty data so we catch other errors as well.
+        } else {
+            all_scalar_chunks
+        };
+
         // All the default values for a `PlotPoint`, accounting for both overrides and default values.
         // We know there's only a single value fallback for stroke width, so this is fine, albeit a bit hacky in case we add an array fallback later.
         let fallback_size: MarkerSize = typed_fallback_for(
@@ -326,7 +346,7 @@ impl SeriesPointsSystem {
                 InstancePath::instance(data_result.entity_path.clone(), instance as u64)
             };
 
-            if let Err(err) = util::points_to_series(
+            util::points_to_series(
                 instance_path,
                 time_per_pixel,
                 visible,
@@ -338,14 +358,9 @@ impl SeriesPointsSystem {
                 re_sdk_types::components::AggregationPolicy::Off,
                 &mut series,
                 instruction.id,
-            ) {
-                results.report_unspecified_source(
-                    VisualizerReportSeverity::Error,
-                    format!("Failed to create series: {err}"),
-                );
-            }
+            );
         }
 
-        Ok(series)
+        series
     }
 }
diff --git a/crates/viewer/re_view_time_series/src/util.rs b/crates/viewer/re_view_time_series/src/util.rs
index 8f062005256f..9712e91d4bb8 100644
--- a/crates/viewer/re_view_time_series/src/util.rs
+++ b/crates/viewer/re_view_time_series/src/util.rs
@@ -139,20 +139,12 @@ pub fn points_to_series(
     aggregator: AggregationPolicy,
     all_series: &mut Vec,
     visualizer_instruction_id: VisualizerInstructionId,
-) -> Result<(), String> {
+) {
     re_tracing::profile_function!(&instance_path.to_string());
 
     if points.is_empty() {
         // No values being present is not an error, maybe data comes in later!
-        return Ok(());
-    }
-
-    // Filter out static times if any slipped in.
-    // It's enough to check the first one since an entire column has to be either temporal or static.
-    if let Some(first) = points.first()
-        && first.time == re_log_types::TimeInt::STATIC.as_i64()
-    {
-        return Err("Can't plot data that was logged statically in a time series since there's no temporal dimension.".to_owned());
+        return;
     }
 
     let (aggregation_factor, points) = apply_aggregation(aggregator, time_per_pixel, points, query);
@@ -199,8 +191,6 @@ pub fn points_to_series(
             visualizer_instruction_id,
         );
     }
-
-    Ok(())
 }
 
 /// Apply the given aggregation to the provided points.

From 959f77f5408414c0bbd2936ddcc7fe5d6cfcbf4b Mon Sep 17 00:00:00 2001
From: Michael Grupp 
Date: Fri, 13 Mar 2026 15:08:03 +0100
Subject: [PATCH 132/513] Create static timeline for /tf_static ROS 2 MCAP
 channels

### What

"tf_static" is the convention for the ROS TF topic name that carries
static transforms. Due to this, we should also log those as static
chunks.

Note that this patch only applies to this specific context:
* ROS 2 data (not Foxglove)
* "/tf_static" MCAP channel

Source-Ref: 4d30345e02df00957d771278ef85c9d509203aa6
---
 crates/store/re_mcap/src/decoders/mod.rs      |  2 +-
 crates/store/re_mcap/src/parsers/decode.rs    | 13 ++++-
 .../parsers/ros2msg/tf2_msgs/tf_message.rs    | 52 ++++++++++++++++++-
 3 files changed, 64 insertions(+), 3 deletions(-)

diff --git a/crates/store/re_mcap/src/decoders/mod.rs b/crates/store/re_mcap/src/decoders/mod.rs
index 7c1269fc59c2..e268864e281a 100644
--- a/crates/store/re_mcap/src/decoders/mod.rs
+++ b/crates/store/re_mcap/src/decoders/mod.rs
@@ -212,7 +212,7 @@ impl MessageDecoderRunner {
 
                     let parser = self.inner.message_parser(channel, msg_offsets.len())?;
                     let entity_path = EntityPath::from(channel.topic.as_str());
-                    let ctx = ParserContext::new(entity_path, time_type);
+                    let ctx = ParserContext::new(entity_path, channel.topic.clone(), time_type);
                     Some((channel_id, (ctx, parser)))
                 })
                 .collect::>();
diff --git a/crates/store/re_mcap/src/parsers/decode.rs b/crates/store/re_mcap/src/parsers/decode.rs
index 42648ab0d52f..b65c65f461e2 100644
--- a/crates/store/re_mcap/src/parsers/decode.rs
+++ b/crates/store/re_mcap/src/parsers/decode.rs
@@ -71,15 +71,21 @@ impl IsEnabled for ChannelId {}
 /// Common context used by parsers to build timelines and store entity paths.
 pub struct ParserContext {
     entity_path: EntityPath,
+    channel_topic: String,
     time_type: TimeType,
     pub timelines: IntMap,
 }
 
 impl ParserContext {
     /// Construct a new parser context with the given [`EntityPath`] and [`TimeType`].
-    pub fn new(entity_path: EntityPath, time_type: TimeType) -> Self {
+    pub fn new(
+        entity_path: EntityPath,
+        channel_topic: impl Into,
+        time_type: TimeType,
+    ) -> Self {
         Self {
             entity_path,
+            channel_topic: channel_topic.into(),
             time_type,
             timelines: IntMap::default(),
         }
@@ -154,4 +160,9 @@ impl ParserContext {
     pub fn entity_path(&self) -> &EntityPath {
         &self.entity_path
     }
+
+    /// Get the MCAP channel topic associated with this context.
+    pub fn channel_topic(&self) -> &str {
+        &self.channel_topic
+    }
 }
diff --git a/crates/store/re_mcap/src/parsers/ros2msg/tf2_msgs/tf_message.rs b/crates/store/re_mcap/src/parsers/ros2msg/tf2_msgs/tf_message.rs
index bea3029028b9..b6dc7e342c1f 100644
--- a/crates/store/re_mcap/src/parsers/ros2msg/tf2_msgs/tf_message.rs
+++ b/crates/store/re_mcap/src/parsers/ros2msg/tf2_msgs/tf_message.rs
@@ -14,6 +14,14 @@ use crate::parsers::{
 };
 use crate::util::{TimestampCell, log_and_publish_timepoint_from_msg};
 
+const STATIC_TF_TOPIC: &str = "/tf_static";
+
+fn static_chunk_timelines()
+-> re_chunk::external::nohash_hasher::IntMap {
+    // Chunks without any timelines are treated as static by Rerun.
+    re_chunk::external::nohash_hasher::IntMap::default()
+}
+
 pub struct TfMessageParser {
     translations: Vec,
     quaternions: Vec,
@@ -104,7 +112,11 @@ impl MessageParser for TfMessageParser {
         } = *self;
 
         let entity_path = ctx.entity_path().clone();
-        let timelines = ctx.build_timelines();
+        let timelines = if ctx.channel_topic() == STATIC_TF_TOPIC {
+            static_chunk_timelines()
+        } else {
+            ctx.build_timelines()
+        };
 
         let chunk = Chunk::from_auto_row_ids(
             ChunkId::new(),
@@ -122,3 +134,41 @@ impl MessageParser for TfMessageParser {
         Ok(vec![chunk])
     }
 }
+
+#[cfg(test)]
+mod tests {
+    use re_chunk::TimePoint;
+    use re_log_types::{TimeCell, TimeType, TimelineName};
+
+    use super::*;
+
+    fn test_parser() -> TfMessageParser {
+        TfMessageParser {
+            translations: vec![Translation3D::new(1.0, 2.0, 3.0)],
+            quaternions: vec![Quaternion::from_xyzw([0.0, 0.0, 0.0, 1.0]).into()],
+            parent_frame_ids: vec!["parent".to_owned()],
+            child_frame_ids: vec!["child".to_owned()],
+        }
+    }
+
+    #[test]
+    fn tf_static_topic_produces_static_chunk() {
+        let ctx = ParserContext::new("/tf_static".into(), STATIC_TF_TOPIC, TimeType::TimestampNs);
+        let chunk = Box::new(test_parser()).finalize(ctx).unwrap().remove(0);
+
+        assert!(chunk.is_static());
+    }
+
+    #[test]
+    fn non_tf_static_topic_stays_temporal() {
+        let mut ctx = ParserContext::new("/tf".into(), "tf", TimeType::TimestampNs);
+        ctx.add_timepoint(TimePoint::from([(
+            TimelineName::log_time(),
+            TimeCell::from_timestamp_nanos_since_epoch(123),
+        )]));
+
+        let chunk = Box::new(test_parser()).finalize(ctx).unwrap().remove(0);
+
+        assert!(!chunk.is_static());
+    }
+}

From 454cab063260637eb67e9aa776a4d69acd60a163 Mon Sep 17 00:00:00 2001
From: Isse 
Date: Fri, 13 Mar 2026 17:38:51 +0100
Subject: [PATCH 133/513] Fix importing foxglove cameras

### Related

- Closes RR-4042

### What

Regression on main from 0.30, pinholes don't use `CoordinateFrame`

Source-Ref: be9bcc83bd8279833cf43b3f9038aae7557be288
---
 .../lenses/foxglove/camera_calibration.rs          | 14 ++++++++++----
 .../src/loader_mcap/lenses/helpers.rs              | 13 ++++++++++++-
 2 files changed, 22 insertions(+), 5 deletions(-)

diff --git a/crates/store/re_data_loader/src/loader_mcap/lenses/foxglove/camera_calibration.rs b/crates/store/re_data_loader/src/loader_mcap/lenses/foxglove/camera_calibration.rs
index 45d821f9fc0a..9610772ef24d 100644
--- a/crates/store/re_data_loader/src/loader_mcap/lenses/foxglove/camera_calibration.rs
+++ b/crates/store/re_data_loader/src/loader_mcap/lenses/foxglove/camera_calibration.rs
@@ -2,9 +2,11 @@ use re_lenses::{Lens, LensError, op};
 use re_lenses_core::Selector;
 use re_lenses_core::combinators::{MapList, Transform as _};
 use re_log_types::{EntityPathFilter, TimeType};
-use re_sdk_types::archetypes::{CoordinateFrame, Pinhole};
+use re_sdk_types::archetypes::Pinhole;
 
-use crate::loader_mcap::lenses::helpers::row_major_3x3_to_column_major;
+use crate::loader_mcap::lenses::helpers::{
+    row_major_3x3_to_column_major, width_height_to_resolution,
+};
 
 use super::{FOXGLOVE_TIMESTAMP, IMAGE_PLANE_SUFFIX};
 
@@ -23,16 +25,20 @@ pub fn camera_calibration(time_type: TimeType) -> Result {
             Selector::parse(".timestamp")?.then(MapList::new(op::timespec_to_nanos())),
         )?
         .component(
-            CoordinateFrame::descriptor_frame(),
+            Pinhole::descriptor_child_frame(),
             Selector::parse(".frame_id")?
                 .then(MapList::new(op::string_suffix_nonempty(IMAGE_PLANE_SUFFIX))),
         )?
+        .component(
+            Pinhole::descriptor_resolution(),
+            Selector::parse(".")?.then(MapList::new(width_height_to_resolution())),
+        )?
         .component(
             Pinhole::descriptor_image_from_camera(),
             Selector::parse(".K")?.then(MapList::new(row_major_3x3_to_column_major())),
         )?
         .component(
-            CoordinateFrame::descriptor_frame(),
+            Pinhole::descriptor_parent_frame(),
             Selector::parse(".frame_id")?,
         )
     })?
diff --git a/crates/store/re_data_loader/src/loader_mcap/lenses/helpers.rs b/crates/store/re_data_loader/src/loader_mcap/lenses/helpers.rs
index 4fddc6a092a6..d19647c5385d 100644
--- a/crates/store/re_data_loader/src/loader_mcap/lenses/helpers.rs
+++ b/crates/store/re_data_loader/src/loader_mcap/lenses/helpers.rs
@@ -1,6 +1,8 @@
 //! Common helper functions for transforming Arrow data in lenses.
 
-use arrow::array::{Array, FixedSizeListArray, Float32Array, Float64Array, ListArray, StructArray};
+use arrow::array::{
+    Array, FixedSizeListArray, Float32Array, Float64Array, ListArray, StructArray, UInt32Array,
+};
 use re_lenses_core::combinators::{
     GetField, ListToFixedSizeList, MapFixedSizeList, PrimitiveCast, RowMajorToColumnMajor,
     StructToFixedList, Transform,
@@ -22,6 +24,15 @@ pub fn xyzw_struct_to_fixed() -> impl Transform::new()))
 }
 
+/// Returns a transform that converts u32 width and height fields to a Resolution component (fixed-size list of 2 f32 values).
+pub fn width_height_to_resolution()
+-> impl Transform {
+    StructToFixedList::new(["width", "height"]).then(MapFixedSizeList::new(PrimitiveCast::<
+        UInt32Array,
+        Float32Array,
+    >::new()))
+}
+
 /// Returns a transform that converts 3x3 row-major f64 matrices stored in variable-size lists to column-major f32 fixed-size lists.
 pub fn row_major_3x3_to_column_major()
 -> impl Transform {

From 6ab7971d88444509e84a2ede36d3b3dff142fa92 Mon Sep 17 00:00:00 2001
From: Nick <24689722+ntjohnson1@users.noreply.github.com>
Date: Fri, 13 Mar 2026 19:33:53 +0100
Subject: [PATCH 134/513] Add description back to python package

I don't know how this ever worked. We must have gotten lucky before that
some tool detected things for us automatically. I can't find the readme
path in the recent history back to 0.27, however the wayback machine
shows we had a description in December.

https://packaging.python.org/en/latest/tutorials/packaging-projects/#configuring-metadata

Source-Ref: 98987f33e930f43af20a7e8ca24951069d79b25a
---
 rerun_py/pyproject.toml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/rerun_py/pyproject.toml b/rerun_py/pyproject.toml
index a01d8cc5ce61..dbd9fd6e8871 100644
--- a/rerun_py/pyproject.toml
+++ b/rerun_py/pyproject.toml
@@ -22,6 +22,7 @@ dependencies = [
   "typing_extensions>=4.5",
 ]
 description = "The Rerun Logging SDK"
+readme = "README.md"
 keywords = ["computer-vision", "logging", "rerun"]
 name = "rerun-sdk"
 requires-python = ">=3.10"

From e24407f936bd0ade4313e832a0939484a958fc0d Mon Sep 17 00:00:00 2001
From: Emil Ernerfeldt 
Date: Fri, 13 Mar 2026 20:59:55 +0100
Subject: [PATCH 135/513] Speed up many-entities (Refactor view class store
 subscriber)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

## Summary

- Move `VisualizerEntitySubscriber` from global `ChunkStoreSubscriber`
to per-store
ownership in `StoreCache` — each store now owns its own set of entity
subscribers,
with store events forwarded explicitly instead of via the global
subscriber
mechanism.
- Make `ViewClassRegistry` immutable after startup — the registry now
stores only
`VisualizerEntityConfig` (builder data: query info, constraints, enum
components)
instead of live subscribers. A `create_entity_subscribers()` method
builds fresh
per-store subscribers from this config.
- Simplify `VisualizerEntitySubscriber` — removed the
`per_store_mapping:
HashMap` in favor of a single mapping field (since each
subscriber is
now per-store). Removed `ChunkStoreSubscriber` trait impl. Split config
fields into
 a separate `VisualizerEntityConfig` struct.

##  Motivation
Less global mutable state.
Easier to get references to needed visualizers

The previous design had `VisualizerEntitySubscriber` registered as a
global
`ChunkStoreSubscriber` that accumulated state for all stores in a single
`HashMap`. This coupled the registry to mutable per-store
state and
made it impossible to treat `ViewClassRegistry` as immutable after
startup. The new
design cleanly separates immutable builder data (in the registry) from
mutable
per-store state (in `StoreCache`).

Source-Ref: 412f13fda723036ef3cb171ce55ed5d7bc612354
---
 Cargo.lock                                    |   1 +
 crates/store/re_chunk/src/slice.rs            |   4 -
 crates/store/re_chunk_store/src/events.rs     |  32 +++
 crates/store/re_lenses_core/Cargo.toml        |   3 +-
 .../store/re_lenses_core/src/selector/mod.rs  |   6 +
 .../re_lenses_core/src/selector/parser.rs     |  30 +++
 .../re_renderer/src/point_cloud_builder.rs    |   2 +
 crates/viewer/re_test_context/src/lib.rs      |  32 +--
 .../re_view/src/annotation_context_utils.rs   |   2 +-
 .../re_view_bar_chart/src/view_class.rs       |   2 +-
 .../re_view_spatial/src/shared_fallbacks.rs   |   3 +
 crates/viewer/re_view_spatial/src/view_3d.rs  |   2 +-
 .../src/visualizers/points3d.rs               |  38 ++-
 .../visualizers/utilities/entity_iterator.rs  |   2 +
 .../viewer/re_view_tensor/src/view_class.rs   |   2 +-
 .../re_view_time_series/src/view_class.rs     |   2 +-
 crates/viewer/re_viewer/src/app.rs            |  38 +--
 crates/viewer/re_viewer/src/app_state.rs      |   7 +-
 .../src/cache/store_cache.rs                  | 105 +++++++-
 .../viewer/re_viewer_context/src/store_hub.rs |  77 +++++-
 .../src/typed_entity_collections.rs           |  69 +++++
 .../viewer/re_viewer_context/src/view/mod.rs  |   2 +-
 .../re_viewer_context/src/view/view_class.rs  |   2 +-
 .../src/view/view_class_registry.rs           |  93 ++-----
 .../src/view/visualizer_entity_subscriber.rs  | 248 ++++++++++--------
 .../re_viewer_context/src/viewer_context.rs   |   6 +-
 .../benches/data_query.rs                     |  14 +-
 .../viewer/re_viewport_blueprint/src/view.rs  |   4 +-
 .../src/view_contents.rs                      |  10 +-
 .../custom_view/src/points3d_color_view.rs    |   2 +-
 .../src/kittest_harness_ext.rs                |  10 +-
 31 files changed, 566 insertions(+), 284 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index 99bc5ff3f3c8..245b7c1e5ed8 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -9154,6 +9154,7 @@ dependencies = [
  "itertools 0.14.0",
  "nohash-hasher",
  "re_arrow_util",
+ "re_byte_size",
  "re_chunk",
  "re_log",
  "re_log_types",
diff --git a/crates/store/re_chunk/src/slice.rs b/crates/store/re_chunk/src/slice.rs
index 9b041399181d..5bb326a9cd50 100644
--- a/crates/store/re_chunk/src/slice.rs
+++ b/crates/store/re_chunk/src/slice.rs
@@ -177,10 +177,6 @@ impl Chunk {
         // The original chunk is unsorted, but the new sliced one actually ends up being sorted.
         chunk.is_sorted = is_sorted || chunk.is_sorted_uncached();
 
-        #[cfg(debug_assertions)]
-        #[expect(clippy::unwrap_used)] // debug-only
-        chunk.sanity_check().unwrap();
-
         chunk
     }
 
diff --git a/crates/store/re_chunk_store/src/events.rs b/crates/store/re_chunk_store/src/events.rs
index e59922633443..8b9b7075a196 100644
--- a/crates/store/re_chunk_store/src/events.rs
+++ b/crates/store/re_chunk_store/src/events.rs
@@ -42,6 +42,38 @@ pub struct ChunkMeta {
     pub components: Vec,
 }
 
+impl ChunkMeta {
+    /// Build a [`ChunkMeta`] from an existing physical [`Chunk`].
+    pub fn from_chunk(chunk: &Chunk) -> Self {
+        let components: Vec = chunk
+            .components()
+            .values()
+            .map(|column| ChunkComponentMeta {
+                descriptor: column.descriptor.clone(),
+                inner_arrow_datatype: Some(column.list_array.value_type()),
+                has_data: !column.list_array.values().is_empty(),
+                is_static_only: chunk.is_static(),
+            })
+            .collect();
+
+        Self {
+            entity_path: chunk.entity_path().clone(),
+            components,
+        }
+    }
+
+    /// Build [`ChunkMeta`]s from an [`RrdManifest`], one per entity path.
+    pub fn from_manifest(manifest: &RrdManifest) -> Vec {
+        re_tracing::profile_function!();
+        // Reuse the same logic as ChunkStoreDiffVirtualAddition::chunk_metas.
+        ChunkStoreDiffVirtualAddition {
+            rrd_manifest: Arc::new(manifest.clone()),
+        }
+        .chunk_metas()
+        .collect()
+    }
+}
+
 /// The atomic unit of change in the Rerun [`ChunkStore`].
 ///
 /// A [`ChunkStoreEvent`] describes the changes caused by the addition or deletion of a
diff --git a/crates/store/re_lenses_core/Cargo.toml b/crates/store/re_lenses_core/Cargo.toml
index 78b863547b72..d8032a1e1cd9 100644
--- a/crates/store/re_lenses_core/Cargo.toml
+++ b/crates/store/re_lenses_core/Cargo.toml
@@ -21,9 +21,10 @@ all-features = true
 
 [dependencies]
 re_arrow_util.workspace = true
+re_byte_size.workspace = true
 re_chunk.workspace = true
-re_log.workspace = true
 re_log_types.workspace = true
+re_log.workspace = true
 re_sdk_types.workspace = true
 
 arrow.workspace = true
diff --git a/crates/store/re_lenses_core/src/selector/mod.rs b/crates/store/re_lenses_core/src/selector/mod.rs
index 6f92446c4c0b..a0bc88abb120 100644
--- a/crates/store/re_lenses_core/src/selector/mod.rs
+++ b/crates/store/re_lenses_core/src/selector/mod.rs
@@ -56,6 +56,12 @@ use parser::{Expr, Segment, SegmentKind};
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub struct Selector(Expr);
 
+impl re_byte_size::SizeBytes for Selector {
+    fn heap_size_bytes(&self) -> u64 {
+        self.0.heap_size_bytes()
+    }
+}
+
 impl std::fmt::Display for Selector {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         write!(f, "{}", self.0)
diff --git a/crates/store/re_lenses_core/src/selector/parser.rs b/crates/store/re_lenses_core/src/selector/parser.rs
index 7783b0472f8a..2961350d608f 100644
--- a/crates/store/re_lenses_core/src/selector/parser.rs
+++ b/crates/store/re_lenses_core/src/selector/parser.rs
@@ -37,6 +37,15 @@ pub enum SegmentKind {
     Each,
 }
 
+impl re_byte_size::SizeBytes for SegmentKind {
+    fn heap_size_bytes(&self) -> u64 {
+        match self {
+            Self::Field(s) => s.heap_size_bytes(),
+            Self::Index(_) | Self::Each => 0,
+        }
+    }
+}
+
 impl std::fmt::Display for SegmentKind {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         match self {
@@ -58,6 +67,17 @@ pub struct Segment {
     pub assert_non_null: bool,
 }
 
+impl re_byte_size::SizeBytes for Segment {
+    fn heap_size_bytes(&self) -> u64 {
+        let Self {
+            kind,
+            suppressed,
+            assert_non_null,
+        } = self;
+        kind.heap_size_bytes() + suppressed.heap_size_bytes() + assert_non_null.heap_size_bytes()
+    }
+}
+
 impl std::fmt::Display for Segment {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         write!(f, "{}", self.kind)?;
@@ -91,6 +111,16 @@ pub enum Expr {
     Pipe(Box, Box),
 }
 
+impl re_byte_size::SizeBytes for Expr {
+    fn heap_size_bytes(&self) -> u64 {
+        match self {
+            Self::Identity => 0,
+            Self::Path(segments) => segments.heap_size_bytes(),
+            Self::Pipe(left, right) => left.heap_size_bytes() + right.heap_size_bytes(),
+        }
+    }
+}
+
 impl std::fmt::Display for Expr {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         match self {
diff --git a/crates/viewer/re_renderer/src/point_cloud_builder.rs b/crates/viewer/re_renderer/src/point_cloud_builder.rs
index 8167715d9b3c..a31f17e6ad19 100644
--- a/crates/viewer/re_renderer/src/point_cloud_builder.rs
+++ b/crates/viewer/re_renderer/src/point_cloud_builder.rs
@@ -45,6 +45,8 @@ impl<'ctx> PointCloudBuilder<'ctx> {
         &mut self,
         expected_number_of_additional_points: usize,
     ) -> Result {
+        re_tracing::profile_function_if!(100_000 < expected_number_of_additional_points);
+
         // We know that the maximum number is independent of datatype, so we can use the same value for all.
         self.position_radius_buffer
             .reserve(expected_number_of_additional_points)?;
diff --git a/crates/viewer/re_test_context/src/lib.rs b/crates/viewer/re_test_context/src/lib.rs
index c5e80b860baa..d1a133809ef9 100644
--- a/crates/viewer/re_test_context/src/lib.rs
+++ b/crates/viewer/re_test_context/src/lib.rs
@@ -260,7 +260,6 @@ impl TestContext {
 
         let mut store_hub = StoreHub::test_hub();
         store_hub.insert_entity_db(recording_store);
-        store_hub.store_cache_entry(&recording_store_id); // create per-store state
         store_hub.insert_entity_db(blueprint_store);
         store_hub
             .set_cloned_blueprint_active_for_app(&blueprint_id)
@@ -543,20 +542,19 @@ impl TestContext {
         build_chunk: impl FnOnce(ChunkBuilder) -> ChunkBuilder,
     ) {
         let builder = build_chunk(Chunk::builder(entity_path));
+        let chunk = Arc::new(builder.build().expect("chunk should be successfully built"));
         let store_hub = self.store_hub.get_mut();
-        let active_recording = store_hub.entity_db_mut(&self.recording_store_id).unwrap();
-        active_recording
-            .add_chunk(&Arc::new(
-                builder.build().expect("chunk should be successfully built"),
-            ))
+        store_hub
+            .add_chunk_for_tests(&self.recording_store_id, &chunk)
             .expect("chunk should be successfully added");
     }
 
     pub fn add_chunks(&mut self, chunks: impl Iterator) {
         let store_hub = self.store_hub.get_mut();
-        let active_recording = store_hub.entity_db_mut(&self.recording_store_id).unwrap();
         for chunk in chunks {
-            active_recording.add_chunk(&Arc::new(chunk)).unwrap();
+            store_hub
+                .add_chunk_for_tests(&self.recording_store_id, &Arc::new(chunk))
+                .unwrap();
         }
     }
 
@@ -624,17 +622,19 @@ impl TestContext {
             );
         }
 
+        // Ensure the per-store cache exists with up-to-date visualizer subscribers.
+        store_hub.store_cache_entry(&self.recording_store_id, &self.view_class_registry);
+
         let route = Route::LocalRecording {
             recording_id: self.recording_store_id.clone(),
         };
         let (storage_context, store_context) = store_hub.read_context(&route);
 
-        let indicated_entities_per_visualizer = self
-            .view_class_registry
-            .indicated_entities_per_visualizer(store_context.recording.store_id());
-        let visualizable_entities_per_visualizer = self
-            .view_class_registry
-            .visualizable_entities_for_visualizer_systems(store_context.recording.store_id());
+        let visualizable_entities_per_visualizer = store_context
+            .caches
+            .visualizable_entities_for_visualizer_systems();
+        let indicated_entities_per_visualizer =
+            store_context.caches.indicated_entities_per_visualizer();
 
         let drag_and_drop_manager =
             re_viewer_context::DragAndDropManager::new(ItemCollection::default());
@@ -851,10 +851,10 @@ impl TestContext {
                         .store_hub
                         .try_lock()
                         .expect("Failed to lock store hub mutex");
-                    let db = store_hub.entity_db_mut(&store_id).unwrap();
 
                     for chunk in chunks {
-                        db.add_chunk(&Arc::new(chunk))
+                        store_hub
+                            .add_chunk_for_tests(&store_id, &Arc::new(chunk))
                             .expect("Updating the chunk store failed");
                     }
                 }
diff --git a/crates/viewer/re_view/src/annotation_context_utils.rs b/crates/viewer/re_view/src/annotation_context_utils.rs
index 0a23218198b7..be22209747e9 100644
--- a/crates/viewer/re_view/src/annotation_context_utils.rs
+++ b/crates/viewer/re_view/src/annotation_context_utils.rs
@@ -63,7 +63,7 @@ pub fn process_annotation_and_keypoint_slices(
     class_ids: &[re_sdk_types::components::ClassId],
     annotations: &Annotations,
 ) -> (ResolvedAnnotationInfos, Keypoints) {
-    re_tracing::profile_function!();
+    re_tracing::profile_function_if!(100_000 < num_instances);
 
     let mut keypoints: Keypoints = HashMap::default();
 
diff --git a/crates/viewer/re_view_bar_chart/src/view_class.rs b/crates/viewer/re_view_bar_chart/src/view_class.rs
index d1fae94a2d2f..ea42d9a9335c 100644
--- a/crates/viewer/re_view_bar_chart/src/view_class.rs
+++ b/crates/viewer/re_view_bar_chart/src/view_class.rs
@@ -105,7 +105,7 @@ impl ViewClass for BarChartView {
         &self,
         _entity_path: &EntityPath,
         visualizers_with_reason: &[(ViewSystemIdentifier, &VisualizableReason)],
-        _indicated_entities_per_visualizer: &PerVisualizerType,
+        _indicated_entities_per_visualizer: &PerVisualizerType<&IndicatedEntities>,
     ) -> RecommendedVisualizers {
         // Default implementation would not suggest the BarChart visualizer for tensors and 1D images,
         // since they're not indicated with a BarChart indicator.
diff --git a/crates/viewer/re_view_spatial/src/shared_fallbacks.rs b/crates/viewer/re_view_spatial/src/shared_fallbacks.rs
index 14767bd42e00..090e7b6a626a 100644
--- a/crates/viewer/re_view_spatial/src/shared_fallbacks.rs
+++ b/crates/viewer/re_view_spatial/src/shared_fallbacks.rs
@@ -148,6 +148,7 @@ pub fn register_fallbacks(system_registry: &mut re_viewer_context::ViewSystemReg
     system_registry.register_fallback_provider(
         blueprint::archetypes::SpatialInformation::descriptor_target_frame().component,
         |ctx| {
+            re_tracing::profile_scope!("SpatialInformation fallback");
             // 1. Check if the space root has a defined coordinate frame.
             // 2. Check if all coordinate frames logged on entities included in the filter share the same
             //    root frame, if so use that frame.
@@ -201,6 +202,8 @@ pub fn register_fallbacks(system_registry: &mut re_viewer_context::ViewSystemReg
                         return true;
                     }
 
+                    re_tracing::profile_scope!("visit-node");
+
                     let Some(root_from_frame) = node
                         .data_result
                         .latest_at_with_blueprint_resolved_data_for_component(
diff --git a/crates/viewer/re_view_spatial/src/view_3d.rs b/crates/viewer/re_view_spatial/src/view_3d.rs
index d76c79ad5576..e403b2c732f9 100644
--- a/crates/viewer/re_view_spatial/src/view_3d.rs
+++ b/crates/viewer/re_view_spatial/src/view_3d.rs
@@ -324,7 +324,7 @@ impl ViewClass for SpatialView3D {
         &self,
         entity_path: &EntityPath,
         visualizers_with_reason: &[(ViewSystemIdentifier, &VisualizableReason)],
-        indicated_entities_per_visualizer: &PerVisualizerType,
+        indicated_entities_per_visualizer: &PerVisualizerType<&IndicatedEntities>,
     ) -> RecommendedVisualizers {
         let axes_viz = TransformAxes3DVisualizer::identifier();
         let camera_viz = CamerasVisualizer::identifier();
diff --git a/crates/viewer/re_view_spatial/src/visualizers/points3d.rs b/crates/viewer/re_view_spatial/src/visualizers/points3d.rs
index 05b2f2d4b08c..07f92f80e5a2 100644
--- a/crates/viewer/re_view_spatial/src/visualizers/points3d.rs
+++ b/crates/viewer/re_view_spatial/src/visualizers/points3d.rs
@@ -58,6 +58,7 @@ impl Points3DVisualizer {
         ent_context: &SpatialSceneVisualizerInstructionContext<'_>,
         data: impl Iterator>,
     ) -> Result<(), ViewSystemExecutionError> {
+        re_tracing::profile_function!();
         let entity_path = ctx.target_entity_path;
 
         for data in data {
@@ -66,9 +67,14 @@ impl Points3DVisualizer {
                 continue;
             }
 
-            let picking_ids = (0..num_instances)
-                .map(|i| PickingLayerInstanceId(i as _))
-                .collect_vec();
+            re_tracing::profile_scope!("num_points", num_instances.to_string());
+
+            let picking_ids = {
+                re_tracing::profile_scope_if!(100_000 < num_instances, "picking_ids");
+                (0..num_instances)
+                    .map(|i| PickingLayerInstanceId(i as _))
+                    .collect_vec()
+            };
 
             let (annotation_infos, keypoints) = process_annotation_and_keypoint_slices(
                 query.latest_at,
@@ -81,8 +87,10 @@ impl Points3DVisualizer {
 
             let positions = bytemuck::cast_slice(data.positions);
 
-            let obj_space_bounding_box =
-                re_renderer::util::bounding_box_from_points(positions.iter().copied());
+            let obj_space_bounding_box = {
+                re_tracing::profile_scope_if!(100_000 < num_instances, "bounding_box");
+                re_renderer::util::bounding_box_from_points(positions.iter().copied())
+            };
 
             // Has not custom fallback for radius, so we use the default.
             // TODO(andreas): It would be nice to have this handle this fallback as part of the query.
@@ -106,6 +114,8 @@ impl Points3DVisualizer {
                 .iter()
                 .map(|transform| transform.as_affine3a())
             {
+                re_tracing::profile_scope!("one-transform");
+
                 let point_batch = point_builder
                     .batch(entity_path.to_string())
                     .world_from_obj(world_from_obj)
@@ -192,6 +202,7 @@ impl VisualizerSystem for Points3DVisualizer {
         view_query: &ViewQuery<'_>,
         context_systems: &ViewContextCollection,
     ) -> Result {
+        re_tracing::profile_function!();
         let output = VisualizerExecutionOutput::default();
 
         let mut point_builder = PointCloudBuilder::new(ctx.viewer_ctx.render_ctx());
@@ -214,18 +225,23 @@ impl VisualizerSystem for Points3DVisualizer {
             &output,
             self.data.preferred_view_kind,
             |ctx, spatial_ctx, results| {
+                re_tracing::profile_scope!("Point3D");
+
                 let all_positions =
                     results.iter_required(Points3D::descriptor_positions().component);
                 if all_positions.is_empty() {
                     return Ok(());
                 }
 
-                let num_positions = all_positions
-                    .chunks()
-                    .iter()
-                    .flat_map(|chunk| chunk.iter_slices::<[f32; 3]>())
-                    .map(|points| points.len())
-                    .sum();
+                let num_positions = {
+                    re_tracing::profile_scope!("num_positions");
+                    all_positions
+                        .chunks()
+                        .iter()
+                        .flat_map(|chunk| chunk.iter_slices::<[f32; 3]>())
+                        .map(|points| points.len())
+                        .sum()
+                };
 
                 if num_positions == 0 {
                     return Ok(());
diff --git a/crates/viewer/re_view_spatial/src/visualizers/utilities/entity_iterator.rs b/crates/viewer/re_view_spatial/src/visualizers/utilities/entity_iterator.rs
index 80b0ff2f50b8..b71c06d9a569 100644
--- a/crates/viewer/re_view_spatial/src/visualizers/utilities/entity_iterator.rs
+++ b/crates/viewer/re_view_spatial/src/visualizers/utilities/entity_iterator.rs
@@ -36,6 +36,8 @@ where
         &VisualizerInstructionQueryResults<'_>,
     ) -> Result<(), ViewSystemExecutionError>,
 {
+    re_tracing::profile_function!(A::name());
+
     let view_kind = super::spatial_view_kind_from_view_class(ctx.view_class_identifier);
     let transforms = context_systems.get::(output)?;
     let depth_offsets = context_systems.get::(output)?;
diff --git a/crates/viewer/re_view_tensor/src/view_class.rs b/crates/viewer/re_view_tensor/src/view_class.rs
index ea9291a25945..0710e37b17ee 100644
--- a/crates/viewer/re_view_tensor/src/view_class.rs
+++ b/crates/viewer/re_view_tensor/src/view_class.rs
@@ -100,7 +100,7 @@ Set the displayed dimensions in a selection panel.",
         &self,
         _entity_path: &EntityPath,
         visualizers_with_reason: &[(ViewSystemIdentifier, &VisualizableReason)],
-        _indicated_entities_per_visualizer: &PerVisualizerType,
+        _indicated_entities_per_visualizer: &PerVisualizerType<&IndicatedEntities>,
     ) -> RecommendedVisualizers {
         // Default implementation would not suggest the Tensor visualizer for images,
         // since they're not indicated with a Tensor indicator.
diff --git a/crates/viewer/re_view_time_series/src/view_class.rs b/crates/viewer/re_view_time_series/src/view_class.rs
index c1545d15fa4f..371e0642489e 100644
--- a/crates/viewer/re_view_time_series/src/view_class.rs
+++ b/crates/viewer/re_view_time_series/src/view_class.rs
@@ -276,7 +276,7 @@ impl ViewClass for TimeSeriesView {
         &self,
         entity_path: &EntityPath,
         visualizers_with_reason: &[(ViewSystemIdentifier, &VisualizableReason)],
-        indicated_entities_per_visualizer: &PerVisualizerType,
+        indicated_entities_per_visualizer: &PerVisualizerType<&IndicatedEntities>,
     ) -> RecommendedVisualizers {
         let available_visualizers: HashMap =
             visualizers_with_reason
diff --git a/crates/viewer/re_viewer/src/app.rs b/crates/viewer/re_viewer/src/app.rs
index 1f000d0238cf..1baee20e817b 100644
--- a/crates/viewer/re_viewer/src/app.rs
+++ b/crates/viewer/re_viewer/src/app.rs
@@ -852,7 +852,7 @@ impl App {
             } => {
                 match store_id.kind() {
                     StoreKind::Recording => {
-                        store_hub.load_blueprint_and_caches(&store_id); // Ensure caches and blueprints
+                        store_hub.load_blueprint_and_caches(&store_id, &self.view_class_registry); // Ensure caches and blueprints
                         let route = Route::LocalRecording {
                             recording_id: store_id.clone(),
                         };
@@ -931,7 +931,7 @@ impl App {
             SystemCommand::ActivateApp(app_id) => {
                 store_hub.load_persisted_blueprints_for_app(&app_id);
                 if let Some(recording_id) = store_hub.earliest_recording_for_app(&app_id) {
-                    store_hub.load_blueprint_and_caches(&recording_id);
+                    store_hub.load_blueprint_and_caches(&recording_id, &self.view_class_registry);
                     self.state
                         .navigation
                         .replace(Route::LocalRecording { recording_id });
@@ -1028,7 +1028,7 @@ impl App {
                 }
 
                 if let Some(recording_id) = new_route.recording_id() {
-                    store_hub.load_blueprint_and_caches(recording_id);
+                    store_hub.load_blueprint_and_caches(recording_id, &self.view_class_registry);
                 }
 
                 if matches!(new_route, Route::Loading(_)) {
@@ -1234,7 +1234,8 @@ impl App {
                     // If the selected item has its own page, switch to it.
                     if let Some(route) = Route::from_item(item) {
                         if let Route::LocalRecording { recording_id } = &route {
-                            store_hub.load_blueprint_and_caches(recording_id);
+                            store_hub
+                                .load_blueprint_and_caches(recording_id, &self.view_class_registry);
                         }
                         self.state.navigation.replace(route);
                     }
@@ -2502,13 +2503,10 @@ impl App {
                     let entity_db = store_hub.entity_db_entry(&store_id);
                     let store_event = entity_db.add_rrd_manifest_message(rrd_manifest);
 
-                    if let Some(caches) = store_hub.store_caches(&store_id) {
-                        // Downgrade to read-only, so we can access caches.
-                        let entity_db = store_hub
-                            .entity_db(&store_id)
-                            .expect("Just queried it mutable and that was fine.");
-
-                        caches.on_store_events(&[store_event], entity_db);
+                    if let Some((entity_db, cache)) =
+                        store_hub.entity_db_and_cache(&store_id, &self.view_class_registry)
+                    {
+                        cache.on_store_events(&[store_event], entity_db);
                     }
                 }
 
@@ -2644,7 +2642,10 @@ impl App {
                                 if let Some(recording_id) =
                                     store_hub.earliest_recording_for_app(&app_id)
                                 {
-                                    store_hub.load_blueprint_and_caches(&recording_id);
+                                    store_hub.load_blueprint_and_caches(
+                                        &recording_id,
+                                        &self.view_class_registry,
+                                    );
                                     self.state
                                         .selection_state
                                         .set_selection(Item::StoreId(recording_id.clone()));
@@ -2683,7 +2684,7 @@ impl App {
 
     fn process_store_events_for_db(
         &self,
-        store_hub: &StoreHub,
+        store_hub: &mut StoreHub,
         store_id: &StoreId,
         store_events: &[re_chunk_store::ChunkStoreEvent],
     ) {
@@ -2691,10 +2692,10 @@ impl App {
 
         // Keep all caches up to date, even if they're in the background.
         // This ensures that when we switch to a different recording, the caches are already valid.
-        if let Some(entity_db) = store_hub.entity_db(store_id)
-            && let Some(caches) = store_hub.store_caches(store_id)
+        if let Some((entity_db, cache)) =
+            store_hub.entity_db_and_cache(store_id, &self.view_class_registry)
         {
-            caches.on_store_events(store_events, entity_db);
+            cache.on_store_events(store_events, entity_db);
         }
 
         self.validate_loaded_events(store_events);
@@ -2869,7 +2870,7 @@ impl App {
             return;
         }
 
-        store_hub.load_blueprint_and_caches(store_id);
+        store_hub.load_blueprint_and_caches(store_id, &self.view_class_registry);
         self.state.navigation.replace(Route::LocalRecording {
             recording_id: store_id.clone(),
         });
@@ -3857,7 +3858,8 @@ impl eframe::App for App {
                 if let Some(app_id) = any_other_app_id {
                     store_hub.load_persisted_blueprints_for_app(&app_id);
                     if let Some(recording_id) = store_hub.earliest_recording_for_app(&app_id) {
-                        store_hub.load_blueprint_and_caches(&recording_id);
+                        store_hub
+                            .load_blueprint_and_caches(&recording_id, &self.view_class_registry);
                         self.state
                             .selection_state
                             .set_selection(Item::StoreId(recording_id.clone()));
diff --git a/crates/viewer/re_viewer/src/app_state.rs b/crates/viewer/re_viewer/src/app_state.rs
index 1ba66058e7d2..e3d44e9daebf 100644
--- a/crates/viewer/re_viewer/src/app_state.rs
+++ b/crates/viewer/re_viewer/src/app_state.rs
@@ -323,10 +323,11 @@ impl AppState {
 
                 let recording = store_context.recording;
 
-                let visualizable_entities_per_visualizer = view_class_registry
-                    .visualizable_entities_for_visualizer_systems(recording.store_id());
+                let visualizable_entities_per_visualizer = store_context
+                    .caches
+                    .visualizable_entities_for_visualizer_systems();
                 let indicated_entities_per_visualizer =
-                    view_class_registry.indicated_entities_per_visualizer(recording.store_id());
+                    store_context.caches.indicated_entities_per_visualizer();
 
                 let app_blueprint_ctx = AppBlueprintCtx {
                     command_sender,
diff --git a/crates/viewer/re_viewer_context/src/cache/store_cache.rs b/crates/viewer/re_viewer_context/src/cache/store_cache.rs
index e13ac3e826f1..17f72f4b1b91 100644
--- a/crates/viewer/re_viewer_context/src/cache/store_cache.rs
+++ b/crates/viewer/re_viewer_context/src/cache/store_cache.rs
@@ -1,9 +1,14 @@
-use re_byte_size::{MemUsageNode, MemUsageTree, MemUsageTreeCapture};
+use nohash_hasher::IntMap;
+use re_byte_size::{MemUsageNode, MemUsageTree, MemUsageTreeCapture, SizeBytes as _};
 use re_chunk_store::ChunkStoreEvent;
 use re_entity_db::EntityDb;
 use re_log_types::StoreId;
 
-use crate::{Cache, Memoizers};
+use crate::view::visualizer_entity_subscriber::VisualizerEntitySubscriber;
+use crate::{
+    Cache, IndicatedEntities, Memoizers, PerVisualizerType, ViewClassRegistry,
+    ViewSystemIdentifier, VisualizableEntities,
+};
 
 /// Viewer-specific state associated with each store (recording or blueprint).
 ///
@@ -11,12 +16,40 @@ use crate::{Cache, Memoizers};
 /// that the viewer needs beyond the raw [`EntityDb`] data.
 pub struct StoreCache {
     pub memoizers: Memoizers,
+
+    /// Per-visualizer entity subscribers that track which entities are visualizable.
+    ///
+    /// Store events are forwarded to these to keep
+    /// per-entity visualizability data up to date.
+    entity_subscribers: IntMap,
 }
 
 impl StoreCache {
-    pub fn new(store_id: StoreId) -> Self {
+    /// Create a new cache without entity subscribers or bootstrapping.
+    ///
+    /// Useful as a placeholder/fallback or in tests.
+    pub fn empty(view_class_registry: &ViewClassRegistry, store_id: StoreId) -> Self {
         Self {
             memoizers: Memoizers::new(store_id),
+            entity_subscribers: view_class_registry.create_entity_subscribers(),
+        }
+    }
+
+    pub fn new(view_class_registry: &ViewClassRegistry, entity_db: &EntityDb) -> Self {
+        re_tracing::profile_function!();
+
+        let mut entity_subscribers = view_class_registry.create_entity_subscribers();
+
+        // Bootstrap all subscribers from existing store data
+        // so they're up-to-date even without having received incremental events.
+        #[expect(clippy::iter_over_hash_type)] // This is order-independent
+        for subscriber in entity_subscribers.values_mut() {
+            subscriber.bootstrap(entity_db);
+        }
+
+        Self {
+            memoizers: Memoizers::new(entity_db.store_id().clone()),
+            entity_subscribers,
         }
     }
 
@@ -27,7 +60,10 @@ impl StoreCache {
 
     /// Call once per frame to potentially flush the cache.
     pub fn begin_frame(&self) {
-        let Self { memoizers } = self;
+        let Self {
+            memoizers,
+            entity_subscribers: _,
+        } = self;
         memoizers.begin_frame();
     }
 
@@ -38,14 +74,25 @@ impl StoreCache {
     ///
     /// Called BEFORE `begin_frame` (if at all).
     pub fn purge_memory(&mut self) {
-        let Self { memoizers } = self;
+        let Self {
+            memoizers,
+            entity_subscribers: _,
+        } = self;
         memoizers.purge_memory();
     }
 
     /// React to the chunk store's changelog, e.g. to invalidate unreachable data.
-    pub fn on_store_events(&self, events: &[ChunkStoreEvent], entity_db: &EntityDb) {
-        let Self { memoizers } = self;
+    pub fn on_store_events(&mut self, events: &[ChunkStoreEvent], entity_db: &EntityDb) {
+        let Self {
+            memoizers,
+            entity_subscribers,
+        } = self;
         memoizers.on_store_events(events, entity_db);
+
+        #[expect(clippy::iter_over_hash_type)] // This is order-independent
+        for subscriber in entity_subscribers.values_mut() {
+            subscriber.on_events(events);
+        }
     }
 
     /// How much memory we used after the last call to [`Self::purge_memory`].
@@ -55,13 +102,19 @@ impl StoreCache {
     /// Some caches just cannot shrink below a certain size,
     /// and we need to take that into account when budgeting for other things.
     pub fn memory_use_after_last_purge(&self) -> u64 {
-        let Self { memoizers } = self;
+        let Self {
+            memoizers,
+            entity_subscribers: _,
+        } = self;
         memoizers.memory_use_after_last_purge()
     }
 
     /// Returns a memory usage tree containing only GPU memory (VRAM) usage.
     pub fn vram_usage(&self) -> MemUsageTree {
-        let Self { memoizers } = self;
+        let Self {
+            memoizers,
+            entity_subscribers: _,
+        } = self;
         memoizers.vram_usage()
     }
 
@@ -71,14 +124,46 @@ impl StoreCache {
     pub fn memoizer(&self, f: impl FnOnce(&mut C) -> R) -> R {
         self.memoizers.entry::(f)
     }
+
+    /// For each visualizer, return the set of entities that may be visualizable with it.
+    pub fn visualizable_entities_for_visualizer_systems(
+        &self,
+    ) -> PerVisualizerType<&VisualizableEntities> {
+        re_tracing::profile_function!();
+
+        PerVisualizerType(
+            self.entity_subscribers
+                .iter()
+                .map(|(id, sub)| (*id, sub.visualizable_entities()))
+                .collect(),
+        )
+    }
+
+    /// For each visualizer, the set of entities that have at least one component with a matching archetype name.
+    pub fn indicated_entities_per_visualizer(&self) -> PerVisualizerType<&IndicatedEntities> {
+        re_tracing::profile_function!();
+
+        PerVisualizerType(
+            self.entity_subscribers
+                .iter()
+                .map(|(id, sub)| (*id, sub.indicated_entities()))
+                .collect(),
+        )
+    }
 }
 
 impl MemUsageTreeCapture for StoreCache {
     fn capture_mem_usage_tree(&self) -> MemUsageTree {
-        let Self { memoizers } = self;
+        re_tracing::profile_function!();
+
+        let Self {
+            memoizers,
+            entity_subscribers,
+        } = self;
 
         let mut node = MemUsageNode::new();
         node.add("memoizers", memoizers.capture_mem_usage_tree());
+        node.add("entity_subscribers", entity_subscribers.total_size_bytes());
         node.into_tree()
     }
 }
diff --git a/crates/viewer/re_viewer_context/src/store_hub.rs b/crates/viewer/re_viewer_context/src/store_hub.rs
index 5cf81dac44e3..6a8ee1784d88 100644
--- a/crates/viewer/re_viewer_context/src/store_hub.rs
+++ b/crates/viewer/re_viewer_context/src/store_hub.rs
@@ -19,7 +19,7 @@ use re_sdk_types::components::Timestamp;
 
 use crate::{
     ActiveStoreContext, BlueprintUndoState, RecordingOrTable, Route, StorageContext, StoreCache,
-    TableStore, TableStores,
+    TableStore, TableStores, ViewClassRegistry,
 };
 
 /// Interface for accessing all blueprints and recordings.
@@ -42,7 +42,6 @@ use crate::{
 ///
 /// The default blueprint is usually the blueprint set by the SDK.
 /// This lets users reset the active blueprint to the one sent by the SDK.
-#[derive(Default)]
 pub struct StoreHub {
     /// How we load and save blueprints.
     persistence: BlueprintPersistence,
@@ -225,8 +224,12 @@ impl StoreHub {
                     .clone(),
             ))
         });
-        static EMPTY_CACHES: LazyLock =
-            LazyLock::new(|| StoreCache::new(re_log_types::StoreId::empty_recording()));
+        static EMPTY_CACHES: LazyLock = LazyLock::new(|| {
+            StoreCache::empty(
+                &ViewClassRegistry::default(),
+                re_log_types::StoreId::empty_recording(),
+            )
+        });
 
         let store_context = 'ctx: {
             // If we have an app-id, then use it to look up the blueprint.
@@ -381,6 +384,33 @@ impl StoreHub {
         self.store_bundle.insert(entity_db);
     }
 
+    /// Add a chunk to a store and forward events to the store's [`StoreCache`] (if one exists).
+    ///
+    /// This is the correct way to add data when a [`StoreCache`] may already exist,
+    /// e.g. in test harnesses that bypass the normal message channel.
+    pub fn add_chunk_for_tests(
+        &mut self,
+        store_id: &StoreId,
+        chunk: &std::sync::Arc,
+    ) -> anyhow::Result> {
+        let entity_db = self
+            .store_bundle
+            .get_mut(store_id)
+            .context("missing store")?;
+        let events = entity_db.add_chunk(chunk)?;
+
+        // Forward events to the cache so subscribers stay up to date.
+        let entity_db = self
+            .store_bundle
+            .get(store_id)
+            .expect("store was just accessed");
+        if let Some(cache) = self.store_caches.get_mut(store_id) {
+            cache.on_store_events(&events, entity_db);
+        }
+
+        Ok(events)
+    }
+
     /// Inserts a new table into the store (potentially overwriting an existing entry).
     pub fn insert_table_store(&mut self, id: TableId, store: TableStore) -> Option {
         self.table_stores.insert(id, store)
@@ -555,17 +585,42 @@ impl StoreHub {
     }
 
     /// Get or create the [`StoreCache`] for a given store.
-    pub fn store_cache_entry(&mut self, store_id: &StoreId) -> &mut StoreCache {
+    pub fn store_cache_entry(
+        &mut self,
+        store_id: &StoreId,
+        view_class_registry: &ViewClassRegistry,
+    ) -> &mut StoreCache {
+        let entity_db = self.store_bundle.entry(store_id);
         self.store_caches
             .entry(store_id.clone())
-            .or_insert_with(|| StoreCache::new(store_id.clone()))
+            .or_insert_with(|| StoreCache::new(view_class_registry, entity_db))
+    }
+
+    /// Get both the [`EntityDb`] and [`StoreCache`] for a given store.
+    ///
+    /// Uses split borrows to allow simultaneous access.
+    pub fn entity_db_and_cache(
+        &mut self,
+        store_id: &StoreId,
+        view_class_registry: &ViewClassRegistry,
+    ) -> Option<(&EntityDb, &mut StoreCache)> {
+        let entity_db = self.store_bundle.get(store_id)?;
+        let cache = self
+            .store_caches
+            .entry(store_id.clone())
+            .or_insert_with(|| StoreCache::new(view_class_registry, entity_db));
+        Some((entity_db, cache))
     }
 
     /// Ensure caches and blueprints are set up for the given recording.
     ///
     /// Call this when a recording becomes active (e.g. via [`Route::LocalRecording`]).
     // TODO(RR-3033): get rid of this?
-    pub fn load_blueprint_and_caches(&mut self, recording_id: &StoreId) {
+    pub fn load_blueprint_and_caches(
+        &mut self,
+        recording_id: &StoreId,
+        view_class_registry: &ViewClassRegistry,
+    ) {
         debug_assert!(recording_id.is_recording());
 
         // Ensure persisted blueprints are loaded for this recording's app.
@@ -577,7 +632,7 @@ impl StoreHub {
             self.load_persisted_blueprints_for_app(&app_id);
         }
 
-        self.store_cache_entry(recording_id);
+        self.store_cache_entry(recording_id, view_class_registry);
     }
 
     // ---------------------
@@ -830,7 +885,7 @@ impl StoreHub {
         let store_events = entity_db.gc_with_target(target, time_cursor);
         let store_size_after = entity_db.total_size_bytes();
 
-        if let Some(cache) = self.store_caches.get(store_id) {
+        if let Some(cache) = self.store_caches.get_mut(store_id) {
             cache.on_store_events(&store_events, entity_db);
         }
 
@@ -896,7 +951,7 @@ impl StoreHub {
                 });
                 if !store_events.is_empty() {
                     re_log::debug!("Garbage-collected blueprint store");
-                    if let Some(cache) = self.store_caches.get(blueprint_id) {
+                    if let Some(cache) = self.store_caches.get_mut(blueprint_id) {
                         cache.on_store_events(&store_events, blueprint);
                     }
                 }
@@ -1019,6 +1074,7 @@ impl StoreHub {
             table_stores,
             data_source_order: _,
             should_enable_heuristics_by_app_id: _,
+
             store_caches,
             blueprint_last_save: _,
             blueprint_last_gc: _,
@@ -1074,6 +1130,7 @@ impl MemUsageTreeCapture for StoreHub {
             active_blueprint_by_app_id: _,
             data_source_order: _,
             should_enable_heuristics_by_app_id: _,
+
             blueprint_last_save: _,
             blueprint_last_gc: _,
         } = self;
diff --git a/crates/viewer/re_viewer_context/src/typed_entity_collections.rs b/crates/viewer/re_viewer_context/src/typed_entity_collections.rs
index 7e68c6bd6284..a1249d34f715 100644
--- a/crates/viewer/re_viewer_context/src/typed_entity_collections.rs
+++ b/crates/viewer/re_viewer_context/src/typed_entity_collections.rs
@@ -42,6 +42,26 @@ pub enum DatatypeMatch {
     },
 }
 
+impl re_byte_size::SizeBytes for DatatypeMatch {
+    fn heap_size_bytes(&self) -> u64 {
+        match self {
+            Self::PhysicalDatatypeOnly {
+                arrow_datatype,
+                component_type,
+                selectors,
+            } => {
+                arrow_datatype.heap_size_bytes()
+                    + component_type.heap_size_bytes()
+                    + selectors.heap_size_bytes()
+            }
+            Self::NativeSemantics {
+                arrow_datatype,
+                component_type,
+            } => arrow_datatype.heap_size_bytes() + component_type.heap_size_bytes(),
+        }
+    }
+}
+
 impl DatatypeMatch {
     pub fn component_type(&self) -> &Option {
         match self {
@@ -93,6 +113,31 @@ pub struct BufferAndFormatMatch {
     pub format_matches: IntSet,
 }
 
+impl re_byte_size::SizeBytes for SingleRequiredComponentMatch {
+    fn heap_size_bytes(&self) -> u64 {
+        let Self {
+            target_component,
+            matches,
+        } = self;
+        target_component.heap_size_bytes() + matches.heap_size_bytes()
+    }
+}
+
+impl re_byte_size::SizeBytes for BufferAndFormatMatch {
+    fn heap_size_bytes(&self) -> u64 {
+        let Self {
+            buffer_target,
+            format_target,
+            buffer_matches,
+            format_matches,
+        } = self;
+        buffer_target.heap_size_bytes()
+            + format_target.heap_size_bytes()
+            + buffer_matches.heap_size_bytes()
+            + format_matches.heap_size_bytes()
+    }
+}
+
 /// Describes why a given entity was marked as visualizable.
 #[derive(Clone, Debug)]
 pub enum VisualizableReason {
@@ -109,6 +154,16 @@ pub enum VisualizableReason {
     BufferAndFormatMatch(BufferAndFormatMatch),
 }
 
+impl re_byte_size::SizeBytes for VisualizableReason {
+    fn heap_size_bytes(&self) -> u64 {
+        match self {
+            Self::Always | Self::ExactMatchAny => 0,
+            Self::SingleRequiredComponentMatch(m) => m.heap_size_bytes(),
+            Self::BufferAndFormatMatch(m) => m.heap_size_bytes(),
+        }
+    }
+}
+
 impl VisualizableReason {
     /// Returns true if this match reason is a perfect match for the given component identifier.
     pub fn full_native_match(&self, component_identifier: ComponentIdentifier) -> bool {
@@ -147,6 +202,12 @@ impl VisualizableReason {
 #[derive(Default, Clone, Debug)]
 pub struct VisualizableEntities(pub IntMap);
 
+impl re_byte_size::SizeBytes for VisualizableEntities {
+    fn heap_size_bytes(&self) -> u64 {
+        self.0.heap_size_bytes()
+    }
+}
+
 impl std::ops::Deref for VisualizableEntities {
     type Target = IntMap;
 
@@ -201,6 +262,14 @@ impl Default for PerVisualizerType {
     }
 }
 
+impl PerVisualizerType {
+    /// Convert from `PerVisualizerType` to `PerVisualizerType<&T>`.
+    #[inline]
+    pub fn as_ref(&self) -> PerVisualizerType<&T> {
+        PerVisualizerType(self.0.iter().map(|(&k, v)| (k, v)).collect())
+    }
+}
+
 impl re_byte_size::SizeBytes for PerVisualizerType
 where
     T: re_byte_size::SizeBytes,
diff --git a/crates/viewer/re_viewer_context/src/view/mod.rs b/crates/viewer/re_viewer_context/src/view/mod.rs
index 52b558b2062f..780b7becf0c1 100644
--- a/crates/viewer/re_viewer_context/src/view/mod.rs
+++ b/crates/viewer/re_viewer_context/src/view/mod.rs
@@ -16,7 +16,7 @@ mod view_context_system;
 mod view_query;
 mod view_states;
 mod visualizability_constraints;
-mod visualizer_entity_subscriber;
+pub(crate) mod visualizer_entity_subscriber;
 mod visualizer_system;
 
 pub use highlights::{
diff --git a/crates/viewer/re_viewer_context/src/view/view_class.rs b/crates/viewer/re_viewer_context/src/view/view_class.rs
index 2b6448a7f8ca..cb49439555cf 100644
--- a/crates/viewer/re_viewer_context/src/view/view_class.rs
+++ b/crates/viewer/re_viewer_context/src/view/view_class.rs
@@ -163,7 +163,7 @@ pub trait ViewClass: Send + Sync {
         &self,
         entity_path: &EntityPath,
         visualizers_with_reason: &[(ViewSystemIdentifier, &VisualizableReason)],
-        indicated_entities_per_visualizer: &PerVisualizerType,
+        indicated_entities_per_visualizer: &PerVisualizerType<&IndicatedEntities>,
     ) -> RecommendedVisualizers {
         let recommended = visualizers_with_reason
             .iter()
diff --git a/crates/viewer/re_viewer_context/src/view/view_class_registry.rs b/crates/viewer/re_viewer_context/src/view/view_class_registry.rs
index 09f30b77e8df..8e568aa07b4f 100644
--- a/crates/viewer/re_viewer_context/src/view/view_class_registry.rs
+++ b/crates/viewer/re_viewer_context/src/view/view_class_registry.rs
@@ -4,16 +4,14 @@ use ahash::{HashMap, HashSet};
 use itertools::Itertools as _;
 use nohash_hasher::{IntMap, IntSet};
 use re_chunk::{ComponentIdentifier, ComponentType};
-use re_chunk_store::{ChunkStore, ChunkStoreSubscriberHandle};
 use re_sdk_types::ViewClassIdentifier;
 
 use super::view_class_placeholder::ViewClassPlaceholder;
-use super::visualizer_entity_subscriber::VisualizerEntitySubscriber;
+use super::visualizer_entity_subscriber::{VisualizerEntityConfig, VisualizerEntitySubscriber};
 use crate::view::view_context_system::ViewContextSystemOncePerFrameResult;
 use crate::{
-    IdentifiedViewSystem, IndicatedEntities, PerVisualizerType, QueryContext, ViewClass,
-    ViewContextCollection, ViewContextSystem, ViewSystemIdentifier, ViewerContext,
-    VisualizableEntities, VisualizerCollection, VisualizerSystem,
+    IdentifiedViewSystem, QueryContext, ViewClass, ViewContextCollection, ViewContextSystem,
+    ViewSystemIdentifier, ViewerContext, VisualizerCollection, VisualizerSystem,
 };
 use crate::{
     component_fallbacks::FallbackProviderRegistry, view::view_context_system::ViewSystemState,
@@ -108,17 +106,20 @@ impl ViewSystemRegistrator<'_> {
                 .entry(T::identifier())
                 .or_insert_with(move || {
                     let visualizer = T::default();
-                    let entity_subscriber_handle =
-                        ChunkStore::register_subscriber(Box::new(VisualizerEntitySubscriber::new(
-                            &visualizer,
-                            known_builtin_enum_components,
-                            app_options,
-                        )));
+
+                    let visualizer_query_info = visualizer.visualizer_query_info(app_options);
+
+                    let entity_config = VisualizerEntityConfig {
+                        visualizer: T::identifier(),
+                        relevant_archetype: visualizer_query_info.relevant_archetype,
+                        constraints: Arc::new(visualizer_query_info.constraints),
+                        known_builtin_enum_components,
+                    };
 
                     VisualizerTypeRegistryEntry {
                         factory_method: Box::new(|| Box::::default()),
                         used_by: Default::default(),
-                        entity_subscriber_handle,
+                        entity_config,
                     }
                 })
                 .used_by
@@ -192,15 +193,8 @@ struct VisualizerTypeRegistryEntry {
     factory_method: Box Box + Send + Sync>,
     used_by: HashSet,
 
-    /// Handle to subscription of [`VisualizerEntitySubscriber`] for this visualizer.
-    entity_subscriber_handle: ChunkStoreSubscriberHandle,
-}
-
-impl Drop for VisualizerTypeRegistryEntry {
-    fn drop(&mut self) {
-        // TODO(andreas): ChunkStore unsubscribe is not yet implemented!
-        //ChunkStore::unregister_subscriber(self.entity_subscriber_handle);
-    }
+    /// Configuration data for building per-store [`VisualizerEntitySubscriber`] instances.
+    entity_config: VisualizerEntityConfig,
 }
 
 /// Registry of all known view types.
@@ -421,56 +415,17 @@ impl ViewClassRegistry {
             .sorted_by_key(|entry| entry.class.display_name())
     }
 
-    /// For each visualizer, return the set of entities that may be visualizable with it.
+    /// Create a set of empty entity subscribers for a new store.
     ///
-    /// The list is kept up to date by store subscribers.
-    pub fn visualizable_entities_for_visualizer_systems(
+    /// Each subscriber is built from the config stored in the registry,
+    /// with empty per-store data.
+    pub fn create_entity_subscribers(
         &self,
-        store_id: &re_log_types::StoreId,
-    ) -> PerVisualizerType {
-        re_tracing::profile_function!();
-
-        PerVisualizerType::(
-            self.visualizers
-                .iter()
-                .map(|(id, entry)| {
-                    (
-                        *id,
-                        ChunkStore::with_subscriber::(
-                            entry.entity_subscriber_handle,
-                            |subscriber| subscriber.visualizable_entities(store_id).cloned(),
-                        )
-                        .flatten()
-                        .unwrap_or_default(),
-                    )
-                })
-                .collect(),
-        )
-    }
-
-    /// For each visualizer, the set of entities that have at least one component with a matching archetype name.
-    pub fn indicated_entities_per_visualizer(
-        &self,
-        store_id: &re_log_types::StoreId,
-    ) -> PerVisualizerType {
-        re_tracing::profile_function!();
-
-        PerVisualizerType::(
-            self.visualizers
-                .iter()
-                .map(|(id, entry)| {
-                    (
-                        *id,
-                        ChunkStore::with_subscriber::(
-                            entry.entity_subscriber_handle,
-                            |subscriber| subscriber.indicated_entities(store_id).cloned(),
-                        )
-                        .flatten()
-                        .unwrap_or_default(),
-                    )
-                })
-                .collect(),
-        )
+    ) -> IntMap {
+        self.visualizers
+            .iter()
+            .map(|(id, entry)| (*id, entry.entity_config.create_subscriber()))
+            .collect()
     }
 
     /// Runs the once-per-frame execution method for each context system once for each view that needs it.
diff --git a/crates/viewer/re_viewer_context/src/view/visualizer_entity_subscriber.rs b/crates/viewer/re_viewer_context/src/view/visualizer_entity_subscriber.rs
index c19bb75aa1d8..1692ea1b0a96 100644
--- a/crates/viewer/re_viewer_context/src/view/visualizer_entity_subscriber.rs
+++ b/crates/viewer/re_viewer_context/src/view/visualizer_entity_subscriber.rs
@@ -1,10 +1,9 @@
 use std::collections::hash_map::Entry;
 use std::sync::Arc;
 
-use ahash::HashMap;
 use nohash_hasher::IntSet;
 use re_chunk::{ArchetypeName, ComponentIdentifier, ComponentType};
-use re_chunk_store::{ChunkStoreEvent, ChunkStoreSubscriber};
+use re_chunk_store::ChunkStoreEvent;
 use re_log::debug_panic;
 use re_log_types::{EntityPath, StoreId};
 
@@ -14,42 +13,71 @@ use crate::typed_entity_collections::{
     BufferAndFormatMatch, DatatypeMatch, SingleRequiredComponentMatch, VisualizableReason,
 };
 use crate::{
-    IdentifiedViewSystem, IndicatedEntities, ViewSystemIdentifier, VisualizabilityConstraints,
-    VisualizableEntities, VisualizerSystem,
+    IndicatedEntities, ViewSystemIdentifier, VisualizabilityConstraints, VisualizableEntities,
 };
 
-/// A store subscriber that keep track which entities in a store can be
-/// processed by a single given visualizer type.
-///
-/// The list of entities is additive:
-/// If an entity was at any point in time passes the "visualizable" filter for the visualizer, it will be
-/// kept in the list of entities.
-///
-/// "visualizable" is determined by the set of required components
+/// Configuration data needed to build a [`VisualizerEntitySubscriber`].
 ///
-/// There's only a single entity subscriber per visualizer *type*.
-/// This means that if the same visualizer is used in multiple views, only a single
-/// `VisualizerEntitySubscriber` is created for all of them.
-pub struct VisualizerEntitySubscriber {
-    /// Visualizer type this subscriber is associated with.
-    visualizer: ViewSystemIdentifier,
+/// This is the immutable "template" stored in the [`crate::ViewClassRegistry`],
+/// extracted from a visualizer's query info at registration time.
+#[derive(Clone)] // Cheap to clone; uses ref-counted data internally.
+pub struct VisualizerEntityConfig {
+    /// Visualizer type this config is associated with.
+    pub visualizer: ViewSystemIdentifier,
 
     /// See [`crate::VisualizerQueryInfo::relevant_archetype`]
-    relevant_archetype: Option,
+    pub relevant_archetype: Option,
 
     /// The mode for checking component requirements.
     ///
     /// See [`crate::VisualizerQueryInfo::constraints`]
-    constraints: VisualizabilityConstraints,
+    pub constraints: Arc,
 
     /// Lists all known builtin enums components.
     ///
     /// Used by [`VisualizabilityConstraints::SingleRequiredComponent`] to skip physical-only matches
     /// for enum types (which should only match via native semantics).
     // TODO(andreas): It would be great if we could just always access the latest reflection data, but this is really hard to pipe through to a store subscriber.
-    known_builtin_enum_components: Arc>,
+    pub known_builtin_enum_components: Arc>,
+}
+
+impl re_byte_size::SizeBytes for VisualizerEntityConfig {
+    fn heap_size_bytes(&self) -> u64 {
+        0 // We use Arc:s, so this is more or less amortized
+    }
+}
+
+impl VisualizerEntityConfig {
+    /// Create a new [`VisualizerEntitySubscriber`] from this config with empty per-store data.
+    pub fn create_subscriber(&self) -> VisualizerEntitySubscriber {
+        VisualizerEntitySubscriber {
+            config: self.clone(),
+            mapping: Default::default(),
+        }
+    }
+}
+
+/// A per-store subscriber that tracks which entities can be
+/// processed by a single given visualizer type.
+///
+/// The list of entities is additive:
+/// If an entity was at any point in time passes the "visualizable" filter for the visualizer, it will be
+/// kept in the list of entities.
+///
+/// "visualizable" is determined by the set of required components
+///
+/// There's only a single entity subscriber per visualizer *type* per store.
+pub struct VisualizerEntitySubscriber {
+    config: VisualizerEntityConfig,
+    mapping: VisualizerEntityMapping,
+}
 
-    per_store_mapping: HashMap,
+impl re_byte_size::SizeBytes for VisualizerEntitySubscriber {
+    fn heap_size_bytes(&self) -> u64 {
+        re_tracing::profile_function!();
+        let Self { config, mapping } = self;
+        config.heap_size_bytes() + mapping.heap_size_bytes()
+    }
 }
 
 /// Per-entity state for a [`VisualizabilityConstraints::BufferAndFormat`] constraint.
@@ -61,6 +89,16 @@ struct BufferAndFormatEntityState {
     all_formats_matches: IntSet,
 }
 
+impl re_byte_size::SizeBytes for BufferAndFormatEntityState {
+    fn heap_size_bytes(&self) -> u64 {
+        let Self {
+            all_buffer_matches,
+            all_formats_matches,
+        } = self;
+        all_buffer_matches.heap_size_bytes() + all_formats_matches.heap_size_bytes()
+    }
+}
+
 #[derive(Default)]
 struct VisualizerEntityMapping {
     /// Which entities the visualizer can be applied to.
@@ -78,6 +116,19 @@ struct VisualizerEntityMapping {
     buffer_and_format_state: IntMap,
 }
 
+impl re_byte_size::SizeBytes for VisualizerEntityMapping {
+    fn heap_size_bytes(&self) -> u64 {
+        let Self {
+            visualizable_entities,
+            indicated_entities,
+            buffer_and_format_state,
+        } = self;
+        visualizable_entities.heap_size_bytes()
+            + indicated_entities.heap_size_bytes()
+            + buffer_and_format_state.heap_size_bytes()
+    }
+}
+
 impl VisualizerEntityMapping {
     /// Adds a visualizability reason for the given entity and combines it with an existing one if any.
     ///
@@ -154,28 +205,10 @@ impl VisualizerEntityMapping {
 }
 
 impl VisualizerEntitySubscriber {
-    pub fn new(
-        visualizer: &T,
-        known_builtin_enum_components: Arc>,
-        app_options: &crate::AppOptions,
-    ) -> Self {
-        let visualizer_query_info = visualizer.visualizer_query_info(app_options);
-
-        Self {
-            visualizer: T::identifier(),
-            relevant_archetype: visualizer_query_info.relevant_archetype,
-            constraints: visualizer_query_info.constraints,
-            known_builtin_enum_components,
-            per_store_mapping: Default::default(),
-        }
-    }
-
     /// List of entities that are visualizable by the visualizer.
     #[inline]
-    pub fn visualizable_entities(&self, store: &StoreId) -> Option<&VisualizableEntities> {
-        self.per_store_mapping
-            .get(store)
-            .map(|mapping| &mapping.visualizable_entities)
+    pub fn visualizable_entities(&self) -> &VisualizableEntities {
+        &self.mapping.visualizable_entities
     }
 
     /// List of entities that at some point in time had a component of an archetypes matching the visualizer's query.
@@ -184,10 +217,8 @@ impl VisualizerEntitySubscriber {
     /// Does *not* imply that any of the given entities is also in the visualizable-set!
     ///
     /// If the visualizer has no archetypes, this list will contain all entities in the store.
-    pub fn indicated_entities(&self, store: &StoreId) -> Option<&IndicatedEntities> {
-        self.per_store_mapping
-            .get(store)
-            .map(|mapping| &mapping.indicated_entities)
+    pub fn indicated_entities(&self) -> &IndicatedEntities {
+        &self.mapping.indicated_entities
     }
 }
 
@@ -195,10 +226,7 @@ impl VisualizerEntitySubscriber {
 ///
 /// This is the shared core logic between physical chunk additions and virtual manifest additions.
 fn process_entity_components(
-    relevant_archetype: Option,
-    constraints: &VisualizabilityConstraints,
-    visualizer: &ViewSystemIdentifier,
-    known_enum_types: &IntSet,
+    config: &VisualizerEntityConfig,
     store_mapping: &mut VisualizerEntityMapping,
     store_id: &StoreId,
     re_chunk_store::ChunkMeta {
@@ -206,6 +234,13 @@ fn process_entity_components(
         components,
     }: re_chunk_store::ChunkMeta,
 ) {
+    let VisualizerEntityConfig {
+        relevant_archetype,
+        constraints,
+        visualizer,
+        known_builtin_enum_components,
+    } = config;
+
     // Update indicated_entities.
     if relevant_archetype.is_none_or(|archetype| {
         components
@@ -219,7 +254,7 @@ fn process_entity_components(
     }
 
     // Check component requirements.
-    match constraints {
+    match constraints.as_ref() {
         VisualizabilityConstraints::None => {
             re_log::trace!(
                 "Entity {entity_path:?} in store {store_id:?} may now be visualizable by {visualizer:?} (no requirements)",
@@ -261,7 +296,7 @@ fn process_entity_components(
                 };
 
                 if let Some(match_info) = constraint.check_datatype_match(
-                    known_enum_types,
+                    known_builtin_enum_components,
                     arrow_datatype,
                     c.descriptor.component_type,
                     c.descriptor.component,
@@ -339,35 +374,39 @@ fn process_entity_components(
     }
 }
 
-impl ChunkStoreSubscriber for VisualizerEntitySubscriber {
-    #[inline]
-    fn name(&self) -> String {
-        self.visualizer.as_str().to_owned()
-    }
-
-    #[inline]
-    fn as_any(&self) -> &dyn std::any::Any {
-        self
-    }
+impl VisualizerEntitySubscriber {
+    /// Bootstrap from an existing [`re_entity_db::EntityDb`], processing all existing data
+    /// so that the subscriber is up-to-date without having received incremental events.
+    pub fn bootstrap(&mut self, entity_db: &re_entity_db::EntityDb) {
+        re_tracing::profile_function!(self.config.visualizer);
+
+        let store_id = entity_db.store_id().clone();
+        let engine = entity_db.storage_engine();
+        let store = engine.store();
+
+        // Process manifest (virtual additions).
+        if let Some(manifest) = entity_db.rrd_manifest_index().manifest() {
+            for meta in re_chunk_store::ChunkMeta::from_manifest(manifest) {
+                process_entity_components(&self.config, &mut self.mapping, &store_id, meta);
+            }
+        }
 
-    #[inline]
-    fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
-        self
+        // Process existing physical chunks.
+        for chunk in store.iter_physical_chunks() {
+            let meta = re_chunk_store::ChunkMeta::from_chunk(chunk);
+            process_entity_components(&self.config, &mut self.mapping, &store_id, meta);
+        }
     }
 
-    fn on_events(&mut self, events: &[ChunkStoreEvent]) {
-        re_tracing::profile_function!(self.visualizer);
+    /// Process store events to update the per-entity visualizability data.
+    pub fn on_events(&mut self, events: &[ChunkStoreEvent]) {
+        re_tracing::profile_function!(self.config.visualizer);
 
         // TODO(andreas): Need to react to store removals as well. As of writing doesn't exist yet.
         //                These removals also need to keep in mind that things from the rrd manifest
         //                shouldn't be removed.
 
         for event in events {
-            let store_mapping = self
-                .per_store_mapping
-                .entry(event.store_id.clone())
-                .or_default();
-
             match &event.diff {
                 re_chunk_store::ChunkStoreDiff::Addition(add) => {
                     // This is a purely additive datastructure, and it doesn't keep track of actual chunks,
@@ -375,11 +414,8 @@ impl ChunkStoreSubscriber for VisualizerEntitySubscriber {
                     // Therefore, the meta of the delta chunk is all we need, always.
 
                     process_entity_components(
-                        self.relevant_archetype,
-                        &self.constraints,
-                        &self.visualizer,
-                        &self.known_builtin_enum_components,
-                        store_mapping,
+                        &self.config,
+                        &mut self.mapping,
                         &event.store_id,
                         add.chunk_meta(),
                     );
@@ -387,11 +423,8 @@ impl ChunkStoreSubscriber for VisualizerEntitySubscriber {
                 re_chunk_store::ChunkStoreDiff::VirtualAddition(virtual_add) => {
                     for meta in virtual_add.chunk_metas() {
                         process_entity_components(
-                            self.relevant_archetype,
-                            &self.constraints,
-                            &self.visualizer,
-                            &self.known_builtin_enum_components,
-                            store_mapping,
+                            &self.config,
+                            &mut self.mapping,
                             &event.store_id,
                             meta,
                         );
@@ -438,11 +471,15 @@ mod tests {
     /// Create a subscriber with a [`VisualizabilityConstraints::BufferAndFormat`] constraint.
     fn test_subscriber_with_buffer_and_format_constraint() -> VisualizerEntitySubscriber {
         VisualizerEntitySubscriber {
-            visualizer: "TestVisualizer".into(),
-            relevant_archetype: None,
-            constraints: VisualizabilityConstraints::BufferAndFormat(test_constraint()),
-            known_builtin_enum_components: Arc::new(IntSet::default()),
-            per_store_mapping: Default::default(),
+            config: VisualizerEntityConfig {
+                visualizer: "TestVisualizer".into(),
+                relevant_archetype: None,
+                constraints: Arc::new(VisualizabilityConstraints::BufferAndFormat(
+                    test_constraint(),
+                )),
+                known_builtin_enum_components: Arc::new(IntSet::default()),
+            },
+            mapping: Default::default(),
         }
     }
 
@@ -494,12 +531,9 @@ mod tests {
     /// Returns the match struct for further inspection.
     fn expect_buffer_and_format_visualizable<'a>(
         subscriber: &'a VisualizerEntitySubscriber,
-        store_id: &StoreId,
         entity: &EntityPath,
     ) -> &'a BufferAndFormatMatch {
-        let entities = subscriber
-            .visualizable_entities(store_id)
-            .expect("store should exist");
+        let entities = subscriber.visualizable_entities();
         let reason = entities.get(entity).expect("entity should be visualizable");
         match reason {
             VisualizableReason::BufferAndFormatMatch(m) => m,
@@ -507,14 +541,8 @@ mod tests {
         }
     }
 
-    fn assert_not_visualizable(
-        subscriber: &VisualizerEntitySubscriber,
-        store_id: &StoreId,
-        entity: &EntityPath,
-    ) {
-        let is_visualizable = subscriber
-            .visualizable_entities(store_id)
-            .is_some_and(|e| e.contains_key(entity));
+    fn assert_not_visualizable(subscriber: &VisualizerEntitySubscriber, entity: &EntityPath) {
+        let is_visualizable = subscriber.visualizable_entities().contains_key(entity);
         assert!(
             !is_visualizable,
             "entity {entity} should NOT be visualizable yet"
@@ -545,7 +573,7 @@ mod tests {
 
         sub.on_events(&[addition_event(&store_id, chunk)]);
 
-        let m = expect_buffer_and_format_visualizable(&sub, &store_id, &entity);
+        let m = expect_buffer_and_format_visualizable(&sub, &entity);
         assert_eq!(m.buffer_matches.len(), 1);
         assert!(matches!(
             m.buffer_matches.get(&"buf".into()),
@@ -581,10 +609,10 @@ mod tests {
             let mut sub = test_subscriber_with_buffer_and_format_constraint();
 
             sub.on_events(&[addition_event(&store_id, first_chunk)]);
-            assert_not_visualizable(&sub, &store_id, &entity);
+            assert_not_visualizable(&sub, &entity);
 
             sub.on_events(&[addition_event(&store_id, second_chunk)]);
-            expect_buffer_and_format_visualizable(&sub, &store_id, &entity);
+            expect_buffer_and_format_visualizable(&sub, &entity);
         }
     }
 
@@ -610,7 +638,7 @@ mod tests {
         );
         sub.on_events(&[addition_event(&store_id, chunk)]);
 
-        let m = expect_buffer_and_format_visualizable(&sub, &store_id, &entity);
+        let m = expect_buffer_and_format_visualizable(&sub, &entity);
         assert!(matches!(
             m.buffer_matches.get(&"buf".into()),
             Some(DatatypeMatch::PhysicalDatatypeOnly { .. })
@@ -638,7 +666,7 @@ mod tests {
             ],
         );
         sub.on_events(&[addition_event(&store_id, chunk)]);
-        assert_not_visualizable(&sub, &store_id, &entity);
+        assert_not_visualizable(&sub, &entity);
     }
 
     #[test]
@@ -662,7 +690,7 @@ mod tests {
             ],
         );
         sub.on_events(&[addition_event(&store_id, chunk)]);
-        assert_not_visualizable(&sub, &store_id, &entity);
+        assert_not_visualizable(&sub, &entity);
     }
 
     #[test]
@@ -686,7 +714,7 @@ mod tests {
             ],
         );
         sub.on_events(&[addition_event(&store_id, chunk1)]);
-        expect_buffer_and_format_visualizable(&sub, &store_id, &entity);
+        expect_buffer_and_format_visualizable(&sub, &entity);
 
         // Event 2: second buffer arrives.
         let chunk2 = make_chunk(
@@ -699,7 +727,7 @@ mod tests {
         sub.on_events(&[addition_event(&store_id, chunk2)]);
 
         // Both buffer matches should be visible.
-        let m = expect_buffer_and_format_visualizable(&sub, &store_id, &entity);
+        let m = expect_buffer_and_format_visualizable(&sub, &entity);
         assert_eq!(m.buffer_matches.len(), 2);
         assert!(matches!(
             m.buffer_matches.get(&"buf1".into()),
@@ -733,7 +761,7 @@ mod tests {
         );
         sub.on_events(&[addition_event(&store_id, chunk1)]);
 
-        let m = expect_buffer_and_format_visualizable(&sub, &store_id, &entity);
+        let m = expect_buffer_and_format_visualizable(&sub, &entity);
         assert_eq!(m.buffer_matches.len(), 1);
         assert_eq!(m.format_matches.len(), 1);
 
@@ -754,7 +782,7 @@ mod tests {
         sub.on_events(&[addition_event(&store_id, chunk2)]);
 
         // Both buffers and both formats should be tracked in a single entry.
-        let m = expect_buffer_and_format_visualizable(&sub, &store_id, &entity);
+        let m = expect_buffer_and_format_visualizable(&sub, &entity);
         assert_eq!(m.buffer_matches.len(), 2);
         assert!(matches!(
             m.buffer_matches.get(&"buf1".into()),
@@ -809,7 +837,7 @@ mod tests {
         );
         sub.on_events(&[addition_event(&store_id, chunk)]);
 
-        let m = expect_buffer_and_format_visualizable(&sub, &store_id, &entity);
+        let m = expect_buffer_and_format_visualizable(&sub, &entity);
         let data_match = m
             .buffer_matches
             .get(&ComponentIdentifier::from("data"))
diff --git a/crates/viewer/re_viewer_context/src/viewer_context.rs b/crates/viewer/re_viewer_context/src/viewer_context.rs
index fc8b02704584..ca6af0931e0e 100644
--- a/crates/viewer/re_viewer_context/src/viewer_context.rs
+++ b/crates/viewer/re_viewer_context/src/viewer_context.rs
@@ -33,13 +33,13 @@ pub struct ViewerContext<'a> {
 
     /// For each visualizer, the set of entities that are known to have all its required components.
     // TODO(andreas): This could have a generation id, allowing to update heuristics entities etc. more lazily.
-    pub visualizable_entities_per_visualizer: &'a PerVisualizerType,
+    pub visualizable_entities_per_visualizer: &'a PerVisualizerType<&'a VisualizableEntities>,
 
     /// For each visualizer, the set of entities with relevant archetypes.
     ///
     /// TODO(andreas): Should we always do the intersection with `maybe_visualizable_entities_per_visualizer`
     ///                 or are we ever interested in a (definitely-)non-visualizable but archetype-matching entity?
-    pub indicated_entities_per_visualizer: &'a PerVisualizerType,
+    pub indicated_entities_per_visualizer: &'a PerVisualizerType<&'a IndicatedEntities>,
 
     /// All the query results for this frame.
     pub query_results: &'a HashMap,
@@ -364,7 +364,7 @@ impl<'a> ViewerContext<'a> {
                 .filter(|(viz_id, _entities)| {
                     view_class_entry.visualizer_system_ids.contains(viz_id)
                 })
-                .map(|(viz_id, entities)| (*viz_id, entities)),
+                .map(|(viz_id, entities)| (*viz_id, *entities)),
         )
     }
 }
diff --git a/crates/viewer/re_viewport_blueprint/benches/data_query.rs b/crates/viewer/re_viewport_blueprint/benches/data_query.rs
index aa478cbaae5a..3a1bb66a9121 100644
--- a/crates/viewer/re_viewport_blueprint/benches/data_query.rs
+++ b/crates/viewer/re_viewport_blueprint/benches/data_query.rs
@@ -74,15 +74,15 @@ fn query_tree_many_entities(c: &mut Criterion) {
         "bench_app",
     ));
 
+    let view_class_registry = ViewClassRegistry::default();
+
     let ctx = ActiveStoreContext {
         blueprint: &blueprint,
         default_blueprint: None,
         recording: &recording,
-        caches: &StoreCache::new(recording.store_id().clone()),
+        caches: &StoreCache::new(&view_class_registry, &recording),
         should_enable_heuristics: false,
     };
-
-    let view_class_registry = ViewClassRegistry::default();
     let blueprint_query = LatestAtQuery::latest(blueprint_timeline());
     let active_timeline = Timeline::new_sequence("frame");
     let query_range = QueryRange::LatestAt;
@@ -104,8 +104,8 @@ fn query_tree_many_entities(c: &mut Criterion) {
                     &view_class_registry,
                     &blueprint_query,
                     &query_range,
-                    &visualizable_entities,
-                    &indicated_entities,
+                    &visualizable_entities.as_ref(),
+                    &indicated_entities.as_ref(),
                     &app_options,
                 )
             });
@@ -137,8 +137,8 @@ fn query_tree_many_entities(c: &mut Criterion) {
                     &view_class_registry,
                     &blueprint_query,
                     &query_range,
-                    &visualizable_entities,
-                    &indicated_entities,
+                    &visualizable_entities.as_ref(),
+                    &indicated_entities.as_ref(),
                     &app_options,
                 )
             });
diff --git a/crates/viewer/re_viewport_blueprint/src/view.rs b/crates/viewer/re_viewport_blueprint/src/view.rs
index dff4b0aa3585..348f587a00c8 100644
--- a/crates/viewer/re_viewport_blueprint/src/view.rs
+++ b/crates/viewer/re_viewport_blueprint/src/view.rs
@@ -640,7 +640,7 @@ mod tests {
                 add_to_blueprint(&base_override_path, batch.as_ref());
             }
 
-            let query_result = update_overrides(&test_ctx, &view, &visualizable_entities);
+            let query_result = update_overrides(&test_ctx, &view, &visualizable_entities.as_ref());
 
             query_result.tree.visit(&mut |node| {
                 let result = &node.data_result;
@@ -666,7 +666,7 @@ mod tests {
     fn update_overrides(
         test_ctx: &TestContext,
         view: &ViewBlueprint,
-        visualizable_entities: &PerVisualizerType,
+        visualizable_entities: &PerVisualizerType<&VisualizableEntities>,
     ) -> re_viewer_context::DataQueryResult {
         let mut result = None;
 
diff --git a/crates/viewer/re_viewport_blueprint/src/view_contents.rs b/crates/viewer/re_viewport_blueprint/src/view_contents.rs
index 4d2972a49c95..fbd344afb864 100644
--- a/crates/viewer/re_viewport_blueprint/src/view_contents.rs
+++ b/crates/viewer/re_viewport_blueprint/src/view_contents.rs
@@ -268,8 +268,8 @@ impl ViewContents {
         view_class_registry: &re_viewer_context::ViewClassRegistry,
         blueprint_query: &LatestAtQuery,
         query_range: &QueryRange,
-        visualizable_entities_per_visualizer: &PerVisualizerType,
-        indicated_entities_per_visualizer: &PerVisualizerType,
+        visualizable_entities_per_visualizer: &PerVisualizerType<&VisualizableEntities>,
+        indicated_entities_per_visualizer: &PerVisualizerType<&IndicatedEntities>,
         app_options: &re_viewer_context::AppOptions,
     ) -> DataQueryResult {
         re_tracing::profile_function!();
@@ -461,7 +461,7 @@ struct DataQueryPropertyResolver<'a> {
     view_query_range: &'a QueryRange,
     view_class: &'a dyn re_viewer_context::ViewClass,
     visualizable_entities_per_visualizer_in_view: &'a VisualizableEntitiesPerVisualizerInView<'a>,
-    indicated_entities_per_visualizer: &'a PerVisualizerType,
+    indicated_entities_per_visualizer: &'a PerVisualizerType<&'a IndicatedEntities>,
 }
 
 impl DataQueryPropertyResolver<'_> {
@@ -852,7 +852,7 @@ mod tests {
             blueprint: &blueprint,
             default_blueprint: None,
             recording: &recording,
-            caches: &StoreCache::new(recording.store_id().clone()),
+            caches: &StoreCache::new(&view_class_registry, &recording),
             should_enable_heuristics: false,
         };
 
@@ -956,7 +956,7 @@ mod tests {
                 &view_class_registry,
                 &LatestAtQuery::latest(blueprint_timeline()),
                 &query_range,
-                &visualizable_entities_for_visualizer_systems,
+                &visualizable_entities_for_visualizer_systems.as_ref(),
                 &PerVisualizerType::default(),
                 &re_viewer_context::AppOptions::default(),
             );
diff --git a/examples/rust/custom_view/src/points3d_color_view.rs b/examples/rust/custom_view/src/points3d_color_view.rs
index d284b33e4742..f913adf26c8e 100644
--- a/examples/rust/custom_view/src/points3d_color_view.rs
+++ b/examples/rust/custom_view/src/points3d_color_view.rs
@@ -133,7 +133,7 @@ impl ViewClass for ColorCoordinatesView {
         &self,
         _entity_path: &EntityPath,
         visualizers: &[(ViewSystemIdentifier, &VisualizableReason)],
-        _indicated_entities_per_visualizer: &PerVisualizerType,
+        _indicated_entities_per_visualizer: &PerVisualizerType<&IndicatedEntities>,
     ) -> RecommendedVisualizers {
         if visualizers
             .iter()
diff --git a/tests/rust/re_integration_test/src/kittest_harness_ext.rs b/tests/rust/re_integration_test/src/kittest_harness_ext.rs
index c77e4118285d..9394bacd0f5d 100644
--- a/tests/rust/re_integration_test/src/kittest_harness_ext.rs
+++ b/tests/rust/re_integration_test/src/kittest_harness_ext.rs
@@ -221,14 +221,10 @@ impl<'h> HarnessExt<'h> for egui_kittest::Harness<'h, re_viewer::App> {
             .expect("expected a recording route")
             .clone();
         let builder = build_chunk(Chunk::builder(entity_path));
+        let chunk = Arc::new(builder.build().expect("chunk should be successfully built"));
         let store_hub = app.testonly_get_store_hub();
-        let active_recording = store_hub
-            .entity_db_mut(&recording_id)
-            .expect("Failed to find recording");
-        active_recording
-            .add_chunk(&Arc::new(
-                builder.build().expect("chunk should be successfully built"),
-            ))
+        store_hub
+            .add_chunk_for_tests(&recording_id, &chunk)
             .expect("chunk should be successfully added");
         self.run_ok();
     }

From 0e786279e52e064c392e9c77e69710cccf77895c Mon Sep 17 00:00:00 2001
From: ruthwikdasyam 
Date: Sat, 14 Mar 2026 01:01:30 -0700
Subject: [PATCH 136/513] feat: deag, click on enable

---
 dimos/src/interaction/keyboard.rs |  88 ++++++++++++++++++------
 dimos/src/viewer.rs               | 107 +-----------------------------
 2 files changed, 72 insertions(+), 123 deletions(-)

diff --git a/dimos/src/interaction/keyboard.rs b/dimos/src/interaction/keyboard.rs
index b6cdcd809c37..9e7d2b79776a 100644
--- a/dimos/src/interaction/keyboard.rs
+++ b/dimos/src/interaction/keyboard.rs
@@ -19,7 +19,6 @@ const BASE_ANGULAR_SPEED: f64 = 0.8;  // rad/s
 const FAST_MULTIPLIER: f64 = 2.0;     // Shift modifier
 
 /// Overlay styling
-const OVERLAY_MARGIN: f32 = 12.0;
 const OVERLAY_PADDING: f32 = 10.0;
 const OVERLAY_ROUNDING: f32 = 8.0;
 const OVERLAY_BG: egui::Color32 = egui::Color32::from_rgba_premultiplied(20, 20, 30, 220);
@@ -66,11 +65,13 @@ impl KeyState {
 }
 
 /// Handles keyboard input and publishes Twist via LCM.
+/// Must be activated by clicking the overlay before keys are captured.
 pub struct KeyboardHandler {
     publisher: LcmPublisher,
     state: KeyState,
     was_active: bool,
     estop_flash: bool,  // true briefly after space pressed
+    engaged: bool,      // true when user has clicked the overlay to activate
 }
 
 impl KeyboardHandler {
@@ -82,29 +83,30 @@ impl KeyboardHandler {
             state: KeyState::new(),
             was_active: false,
             estop_flash: false,
+            engaged: false,
         })
     }
 
     /// Process keyboard input from egui and publish Twist if keys are held.
     /// Called once per frame from DimosApp.ui().
+    /// Only captures keys when the overlay has been clicked (engaged).
     ///
     /// Returns true if any movement key is active (for UI overlay).
     pub fn process(&mut self, ctx: &egui::Context) -> bool {
         self.estop_flash = false;
 
-        // Check if any text widget has focus - if so, skip keyboard capture
-        let text_has_focus = ctx.memory(|m| m.focused().is_some());
-        if text_has_focus {
+        // If not engaged, don't capture any keys
+        if !self.engaged {
             if self.was_active {
                 if let Err(e) = self.publish_stop() {
-                    re_log::warn!("Failed to send stop command on focus change: {e:?}");
+                    re_log::warn!("Failed to send stop on disengage: {e:?}");
                 }
                 self.was_active = false;
             }
             return false;
         }
 
-        // Update key state from egui input
+        // Update key state from egui input (engaged flag is the only gate)
         self.update_key_state(ctx);
 
         // Check for emergency stop (Space key pressed - one-shot action)
@@ -134,33 +136,71 @@ impl KeyboardHandler {
         self.state.any_active()
     }
 
-    /// Draw keyboard overlay HUD. Always shown (dim when idle, bright when active).
-    pub fn draw_overlay(&self, ctx: &egui::Context) {
+    /// Draw keyboard overlay HUD at bottom-right of the 3D viewport area.
+    /// Clickable: clicking the overlay toggles engaged state.
+    pub fn draw_overlay(&mut self, ctx: &egui::Context) {
+        let screen_rect = ctx.content_rect();
+        // Default position: bottom-right of the 3D viewport area
+        let overlay_width = 140.0;
+        let overlay_height = 160.0;
+        let right_panel_offset = 320.0;
+        let bottom_timeline_offset = 120.0;
+        let default_pos = egui::pos2(
+            screen_rect.max.x - overlay_width - right_panel_offset,
+            screen_rect.max.y - overlay_height - bottom_timeline_offset,
+        );
+
         egui::Area::new("keyboard_hud".into())
-            .fixed_pos(egui::pos2(OVERLAY_MARGIN, OVERLAY_MARGIN))
+            .default_pos(default_pos)
+            .movable(true)
             .order(egui::Order::Foreground)
-            .interactable(false)
+            .interactable(true)
             .show(ctx, |ui| {
-                egui::Frame::new()
+                let border_color = if self.engaged {
+                    egui::Color32::from_rgb(60, 180, 75) // green border when active
+                } else {
+                    egui::Color32::from_rgb(80, 80, 100) // dim border when inactive
+                };
+
+                let response = egui::Frame::new()
                     .fill(OVERLAY_BG)
                     .corner_radius(egui::CornerRadius::same(OVERLAY_ROUNDING as u8))
                     .inner_margin(egui::Margin::same(OVERLAY_PADDING as i8))
+                    .stroke(egui::Stroke::new(2.0, border_color))
                     .show(ui, |ui| {
                         self.draw_hud_content(ui);
                     });
+
+                // Make the frame rect clickable (Frame doesn't have click sense by default)
+                let click_response = ui.interact(
+                    response.response.rect,
+                    ui.id().with("wasd_click"),
+                    egui::Sense::click(),
+                ).on_hover_cursor(egui::CursorIcon::Default);
+
+                // Toggle engaged state on click
+                if click_response.clicked() {
+                    self.engaged = !self.engaged;
+                    if !self.engaged {
+                        // Send stop when disengaging
+                        if let Err(e) = self.publish_stop() {
+                            re_log::warn!("Failed to send stop on disengage: {e:?}");
+                        }
+                        self.state.reset();
+                        self.was_active = false;
+                    }
+                    re_log::info!(
+                        "Keyboard teleop {}",
+                        if self.engaged { "ENGAGED" } else { "DISENGAGED" }
+                    );
+                }
             });
+
     }
 
     fn draw_hud_content(&self, ui: &mut egui::Ui) {
-        let active = self.state.any_active() || self.estop_flash;
-
         // Title
-        let title_color = if active {
-            egui::Color32::WHITE
-        } else {
-            egui::Color32::from_rgb(120, 120, 140)
-        };
-        ui.label(egui::RichText::new("🎮 Keyboard Teleop").color(title_color).size(13.0));
+        ui.label(egui::RichText::new("Keyboard Teleop").color(LABEL_COLOR).size(13.0));
         ui.add_space(4.0);
 
         // Key grid:  [Q] [W] [E]
@@ -352,6 +392,7 @@ mod tests {
             state,
             was_active: false,
             estop_flash: false,
+            engaged: true,
         };
         let (lin_x, lin_y, _, _, _, ang_z) = handler.compute_twist();
         assert_eq!(lin_x, BASE_LINEAR_SPEED);
@@ -368,6 +409,7 @@ mod tests {
             state,
             was_active: false,
             estop_flash: false,
+            engaged: true,
         };
         let (lin_x, lin_y, _, _, _, ang_z) = handler.compute_twist();
         assert_eq!(lin_x, 0.0);
@@ -381,6 +423,7 @@ mod tests {
             state,
             was_active: false,
             estop_flash: false,
+            engaged: true,
         };
         let (lin_x, lin_y, _, _, _, ang_z) = handler.compute_twist();
         assert_eq!(lin_x, 0.0);
@@ -397,6 +440,7 @@ mod tests {
             state,
             was_active: false,
             estop_flash: false,
+            engaged: true,
         };
         let (lin_x, lin_y, _, _, _, ang_z) = handler.compute_twist();
         assert_eq!(lin_x, 0.0);
@@ -410,6 +454,7 @@ mod tests {
             state,
             was_active: false,
             estop_flash: false,
+            engaged: true,
         };
         let (lin_x, lin_y, _, _, _, ang_z) = handler.compute_twist();
         assert_eq!(lin_x, 0.0);
@@ -427,6 +472,7 @@ mod tests {
             state,
             was_active: false,
             estop_flash: false,
+            engaged: true,
         };
         let (lin_x, lin_y, _, _, _, ang_z) = handler.compute_twist();
         assert_eq!(lin_x, BASE_LINEAR_SPEED * FAST_MULTIPLIER);
@@ -444,6 +490,7 @@ mod tests {
             state,
             was_active: false,
             estop_flash: false,
+            engaged: true,
         };
         let (lin_x, lin_y, _, _, _, ang_z) = handler.compute_twist();
         assert_eq!(lin_x, BASE_LINEAR_SPEED);
@@ -471,6 +518,7 @@ mod tests {
         assert!(handler.is_ok());
         let handler = handler.unwrap();
         assert!(!handler.was_active);
+        assert!(!handler.engaged);
         assert!(!handler.state.any_active());
     }
 
@@ -484,6 +532,7 @@ mod tests {
             state,
             was_active: false,
             estop_flash: false,
+            engaged: true,
         };
         let (lin_x, lin_y, _, _, _, ang_z) = handler.compute_twist();
         assert_eq!(lin_x, 0.0);
@@ -498,6 +547,7 @@ mod tests {
             state: KeyState::new(),
             was_active: false,
             estop_flash: false,
+            engaged: true,
         };
         let (lin_x, lin_y, lin_z, ang_x, ang_y, ang_z) = handler.compute_twist();
         assert_eq!(lin_x, 0.0);
diff --git a/dimos/src/viewer.rs b/dimos/src/viewer.rs
index 7af7282ef188..86a7532458b1 100644
--- a/dimos/src/viewer.rs
+++ b/dimos/src/viewer.rs
@@ -1,25 +1,15 @@
-use std::cell::RefCell;
-use std::rc::Rc;
-use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
-
 use clap::Parser;
-use dimos_viewer::interaction::{LcmPublisher, KeyboardHandler, click_event_from_ms};
+use dimos_viewer::interaction::KeyboardHandler;
 use rerun::external::{eframe, egui, re_crash_handler, re_grpc_server, re_log, re_memory, re_viewer};
 
 #[global_allocator]
 static GLOBAL: re_memory::AccountingAllocator =
     re_memory::AccountingAllocator::new(mimalloc::MiMalloc);
 
-/// LCM channel for click events (follows RViz convention)
-const LCM_CHANNEL: &str = "/clicked_point#geometry_msgs.PointStamped";
-/// Minimum time between click events (debouncing)
-const CLICK_DEBOUNCE_MS: u64 = 100;
-/// Maximum rapid clicks to log as warning
-const RAPID_CLICK_THRESHOLD: usize = 5;
 /// Default gRPC listen port (9877 to avoid conflict with stock Rerun on 9876)
 const DEFAULT_PORT: u16 = 9877;
 
-/// DimOS Interactive Viewer — a custom Rerun viewer with LCM click-to-navigate.
+/// DimOS Interactive Viewer — a custom Rerun viewer with WASD keyboard teleop.
 ///
 /// Accepts the same CLI flags as the stock `rerun` binary so it can be spawned
 /// seamlessly via `rerun_bindings.spawn(executable_name="dimos-viewer")`.
@@ -73,7 +63,7 @@ impl eframe::App for DimosApp {
         // Process keyboard input before delegating to Rerun
         self.keyboard.process(ui.ctx());
 
-        // Always draw the keyboard HUD overlay (dims when inactive)
+        // Draw the keyboard HUD overlay (click to engage/disengage)
         self.keyboard.draw_overlay(ui.ctx());
 
         // Delegate to Rerun's main ui method
@@ -124,20 +114,11 @@ async fn main() -> Result<(), Box> {
         re_grpc_server::shutdown::never(),
     );
 
-    // Create LCM publisher for click events
-    let lcm_publisher = LcmPublisher::new(LCM_CHANNEL.to_string())
-        .expect("Failed to create LCM publisher");
-    re_log::info!("LCM publisher created for channel: {LCM_CHANNEL}");
-
     // Create keyboard handler
     let keyboard_handler = KeyboardHandler::new()
         .expect("Failed to create keyboard handler");
     re_log::info!("Keyboard handler initialized for WASD controls on /cmd_vel");
 
-    // State for debouncing and rapid click detection
-    let last_click_time = Rc::new(RefCell::new(Instant::now()));
-    let rapid_click_count = Rc::new(RefCell::new(0usize));
-
     let mut native_options = re_viewer::native::eframe_options(None);
     native_options.viewport = native_options
         .viewport
@@ -151,88 +132,6 @@ async fn main() -> Result<(), Box> {
 
     let startup_options = re_viewer::StartupOptions {
         memory_limit,
-        on_event: Some(Rc::new({
-            let last_click_time = last_click_time.clone();
-            let rapid_click_count = rapid_click_count.clone();
-
-            move |event: re_viewer::ViewerEvent| {
-                if let re_viewer::ViewerEventKind::SelectionChange { items } = event.kind {
-                    let mut has_position = false;
-                    let mut no_position_count = 0;
-
-                    for item in items {
-                        match item {
-                            re_viewer::SelectionChangeItem::Entity {
-                                entity_path,
-                                view_name: _,
-                                position: Some(pos),
-                                ..
-                            } => {
-                                has_position = true;
-
-                                // Debouncing
-                                let now = Instant::now();
-                                let elapsed = now.duration_since(*last_click_time.borrow());
-
-                                if elapsed < Duration::from_millis(CLICK_DEBOUNCE_MS) {
-                                    let mut count = rapid_click_count.borrow_mut();
-                                    *count += 1;
-                                    if *count == RAPID_CLICK_THRESHOLD {
-                                        re_log::warn!(
-                                            "Rapid click detected ({} clicks within {}ms)",
-                                            RAPID_CLICK_THRESHOLD,
-                                            CLICK_DEBOUNCE_MS
-                                        );
-                                    }
-                                    continue;
-                                } else {
-                                    *rapid_click_count.borrow_mut() = 0;
-                                }
-                                *last_click_time.borrow_mut() = now;
-
-                                let timestamp_ms = SystemTime::now()
-                                    .duration_since(UNIX_EPOCH)
-                                    .unwrap_or_default()
-                                    .as_millis() as u64;
-
-                                // Build click event and publish via LCM
-                                let click = click_event_from_ms(
-                                    [pos.x, pos.y, pos.z],
-                                    &entity_path.to_string(),
-                                    timestamp_ms,
-                                );
-
-                                match lcm_publisher.publish(&click) {
-                                    Ok(_) => {
-                                        re_log::debug!(
-                                            "LCM click event published: entity={}, pos=({:.2}, {:.2}, {:.2})",
-                                            entity_path,
-                                            pos.x,
-                                            pos.y,
-                                            pos.z
-                                        );
-                                    }
-                                    Err(err) => {
-                                        re_log::error!("Failed to publish LCM click event: {err:?}");
-                                    }
-                                }
-                            }
-                            re_viewer::SelectionChangeItem::Entity { position: None, .. } => {
-                                no_position_count += 1;
-                            }
-                            _ => {}
-                        }
-                    }
-
-                    if !has_position && no_position_count > 0 {
-                        re_log::trace!(
-                            "Selection change without position data ({no_position_count} items). \
-                             This is normal for hover/keyboard navigation."
-                        );
-                    }
-                }
-            }
-        })),
         ..Default::default()
     };
 

From 117df644935d9972a82aa44b716f480a8c9128fa Mon Sep 17 00:00:00 2001
From: ruthwikdasyam 
Date: Sat, 14 Mar 2026 01:31:29 -0700
Subject: [PATCH 137/513] feat: click anywhere else to disengage

---
 dimos/src/interaction/keyboard.rs | 27 +++++++++++++++++++++++----
 1 file changed, 23 insertions(+), 4 deletions(-)

diff --git a/dimos/src/interaction/keyboard.rs b/dimos/src/interaction/keyboard.rs
index 9e7d2b79776a..88a556c9c042 100644
--- a/dimos/src/interaction/keyboard.rs
+++ b/dimos/src/interaction/keyboard.rs
@@ -150,7 +150,7 @@ impl KeyboardHandler {
             screen_rect.max.y - overlay_height - bottom_timeline_offset,
         );
 
-        egui::Area::new("keyboard_hud".into())
+        let area_response = egui::Area::new("keyboard_hud".into())
             .default_pos(default_pos)
             .movable(true)
             .order(egui::Order::Foreground)
@@ -176,7 +176,12 @@ impl KeyboardHandler {
                     response.response.rect,
                     ui.id().with("wasd_click"),
                     egui::Sense::click(),
-                ).on_hover_cursor(egui::CursorIcon::Default);
+                );
+
+                // Force arrow cursor over the entire overlay (overrides label I-beam)
+                if click_response.hovered() {
+                    ctx.set_cursor_icon(egui::CursorIcon::Default);
+                }
 
                 // Toggle engaged state on click
                 if click_response.clicked() {
@@ -194,8 +199,22 @@ impl KeyboardHandler {
                         if self.engaged { "ENGAGED" } else { "DISENGAGED" }
                     );
                 }
-            });
-
+            })
+            .response;
+
+        // Disengage when clicking anywhere outside the overlay
+        if self.engaged
+            && !ctx.rect_contains_pointer(area_response.layer_id, area_response.interact_rect)
+            && ctx.input(|i| i.pointer.primary_clicked())
+        {
+            self.engaged = false;
+            if let Err(e) = self.publish_stop() {
+                re_log::warn!("Failed to send stop on outside click: {e:?}");
+            }
+            self.state.reset();
+            self.was_active = false;
+            re_log::info!("Keyboard teleop DISENGAGED (clicked outside)");
+        }
     }
 
     fn draw_hud_content(&self, ui: &mut egui::Ui) {

From d57c63de81056958acf45382ef8d2e2eab6a9c52 Mon Sep 17 00:00:00 2001
From: ruthwikdasyam 
Date: Sat, 14 Mar 2026 01:44:21 -0700
Subject: [PATCH 138/513] feat: click to navigate

---
 dimos/src/viewer.rs | 105 +++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 103 insertions(+), 2 deletions(-)

diff --git a/dimos/src/viewer.rs b/dimos/src/viewer.rs
index 86a7532458b1..f2c5f98ca31c 100644
--- a/dimos/src/viewer.rs
+++ b/dimos/src/viewer.rs
@@ -1,15 +1,25 @@
+use std::cell::RefCell;
+use std::rc::Rc;
+use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
+
 use clap::Parser;
-use dimos_viewer::interaction::KeyboardHandler;
+use dimos_viewer::interaction::{LcmPublisher, KeyboardHandler, click_event_from_ms};
 use rerun::external::{eframe, egui, re_crash_handler, re_grpc_server, re_log, re_memory, re_viewer};
 
 #[global_allocator]
 static GLOBAL: re_memory::AccountingAllocator =
     re_memory::AccountingAllocator::new(mimalloc::MiMalloc);
 
+/// LCM channel for click events (follows RViz convention)
+const LCM_CHANNEL: &str = "/clicked_point#geometry_msgs.PointStamped";
+/// Minimum time between click events (debouncing)
+const CLICK_DEBOUNCE_MS: u64 = 100;
+/// Maximum rapid clicks to log as warning
+const RAPID_CLICK_THRESHOLD: usize = 5;
 /// Default gRPC listen port (9877 to avoid conflict with stock Rerun on 9876)
 const DEFAULT_PORT: u16 = 9877;
 
-/// DimOS Interactive Viewer — a custom Rerun viewer with WASD keyboard teleop.
+/// DimOS Interactive Viewer — a custom Rerun viewer with LCM click-to-navigate.
 ///
 /// Accepts the same CLI flags as the stock `rerun` binary so it can be spawned
 /// seamlessly via `rerun_bindings.spawn(executable_name="dimos-viewer")`.
@@ -114,11 +124,20 @@ async fn main() -> Result<(), Box> {
         re_grpc_server::shutdown::never(),
     );
 
+    // Create LCM publisher for click events
+    let lcm_publisher = LcmPublisher::new(LCM_CHANNEL.to_string())
+        .expect("Failed to create LCM publisher");
+    re_log::info!("LCM publisher created for channel: {LCM_CHANNEL}");
+
     // Create keyboard handler
     let keyboard_handler = KeyboardHandler::new()
         .expect("Failed to create keyboard handler");
     re_log::info!("Keyboard handler initialized for WASD controls on /cmd_vel");
 
+    // State for debouncing and rapid click detection
+    let last_click_time = Rc::new(RefCell::new(Instant::now()));
+    let rapid_click_count = Rc::new(RefCell::new(0usize));
+
     let mut native_options = re_viewer::native::eframe_options(None);
     native_options.viewport = native_options
         .viewport
@@ -132,6 +151,88 @@ async fn main() -> Result<(), Box> {
 
     let startup_options = re_viewer::StartupOptions {
         memory_limit,
+        on_event: Some(Rc::new({
+            let last_click_time = last_click_time.clone();
+            let rapid_click_count = rapid_click_count.clone();
+
+            move |event: re_viewer::ViewerEvent| {
+                if let re_viewer::ViewerEventKind::SelectionChange { items } = event.kind {
+                    let mut has_position = false;
+                    let mut no_position_count = 0;
+
+                    for item in items {
+                        match item {
+                            re_viewer::SelectionChangeItem::Entity {
+                                entity_path,
+                                view_name: _,
+                                position: Some(pos),
+                                ..
+                            } => {
+                                has_position = true;
+
+                                // Debouncing
+                                let now = Instant::now();
+                                let elapsed = now.duration_since(*last_click_time.borrow());
+
+                                if elapsed < Duration::from_millis(CLICK_DEBOUNCE_MS) {
+                                    let mut count = rapid_click_count.borrow_mut();
+                                    *count += 1;
+                                    if *count == RAPID_CLICK_THRESHOLD {
+                                        re_log::warn!(
+                                            "Rapid click detected ({} clicks within {}ms)",
+                                            RAPID_CLICK_THRESHOLD,
+                                            CLICK_DEBOUNCE_MS
+                                        );
+                                    }
+                                    continue;
+                                } else {
+                                    *rapid_click_count.borrow_mut() = 0;
+                                }
+                                *last_click_time.borrow_mut() = now;
+
+                                let timestamp_ms = SystemTime::now()
+                                    .duration_since(UNIX_EPOCH)
+                                    .unwrap_or_default()
+                                    .as_millis() as u64;
+
+                                // Build click event and publish via LCM
+                                let click = click_event_from_ms(
+                                    [pos.x, pos.y, pos.z],
+                                    &entity_path.to_string(),
+                                    timestamp_ms,
+                                );
+
+                                match lcm_publisher.publish(&click) {
+                                    Ok(_) => {
+                                        re_log::debug!(
+                                            "LCM click event published: entity={}, pos=({:.2}, {:.2}, {:.2})",
+                                            entity_path,
+                                            pos.x,
+                                            pos.y,
+                                            pos.z
+                                        );
+                                    }
+                                    Err(err) => {
+                                        re_log::error!("Failed to publish LCM click event: {err:?}");
+                                    }
+                                }
+                            }
+                            re_viewer::SelectionChangeItem::Entity { position: None, .. } => {
+                                no_position_count += 1;
+                            }
+                            _ => {}
+                        }
+                    }
+
+                    if !has_position && no_position_count > 0 {
+                        re_log::trace!(
+                            "Selection change without position data ({no_position_count} items). \
+                             This is normal for hover/keyboard navigation."
+                        );
+                    }
+                }
+            }
+        })),
         ..Default::default()
     };
 

From 25ee3cb27e5a5db80d9551614ab7a76634bfbcce Mon Sep 17 00:00:00 2001
From: Michael Grupp 
Date: Sat, 14 Mar 2026 09:46:21 +0100
Subject: [PATCH 139/513] Ensure that URDF primitives are colored correctly

Either the material color or white. Previously: psychedelic random color
party

Source-Ref: 629cd5ddf14ad470d6cbc8b8194dafcb0e9faa4d
---
 .../re_data_loader/src/loader_urdf/mod.rs     | 53 ++++++++++++-------
 tests/assets/rrd/snippets/howto/load_urdf.rrd |  4 +-
 2 files changed, 36 insertions(+), 21 deletions(-)

diff --git a/crates/store/re_data_loader/src/loader_urdf/mod.rs b/crates/store/re_data_loader/src/loader_urdf/mod.rs
index 5d493d55b7e5..60184b8c5fce 100644
--- a/crates/store/re_data_loader/src/loader_urdf/mod.rs
+++ b/crates/store/re_data_loader/src/loader_urdf/mod.rs
@@ -11,7 +11,8 @@ use crossbeam::channel::Sender;
 use re_chunk::{ChunkBuilder, ChunkId, EntityPath, RowId, TimePoint};
 use re_log_types::StoreId;
 use re_sdk_types::archetypes::{Asset3D, CoordinateFrame, InstancePoses3D, Transform3D};
-use re_sdk_types::datatypes::Vec3D;
+use re_sdk_types::components::Color;
+use re_sdk_types::datatypes::{Rgba32, Vec3D};
 use re_sdk_types::external::glam;
 use re_sdk_types::{AsComponents, Component as _, ComponentDescriptor};
 use urdf_rs::{Geometry, Joint, Vec3, Vec4};
@@ -447,26 +448,17 @@ fn log_geometry(
             use re_sdk_types::components::MediaType;
 
             let mesh_bytes = load_ros_resource(urdf_tree.urdf_dir.as_ref(), filename)?;
-            let mut asset3d =
-                Asset3D::from_file_contents(mesh_bytes, MediaType::guess_from_path(filename));
+            let asset3d =
+                Asset3D::from_file_contents(mesh_bytes, MediaType::guess_from_path(filename))
+                    .with_albedo_factor(material_albedo_factor(material));
 
             if let Some(material) = material {
                 let urdf_rs::Material {
                     name: _,
-                    color,
+                    color: _,
                     texture,
                 } = material;
-                if let Some(color) = color {
-                    let urdf_rs::Color {
-                        rgba: Vec4([r, g, b, a]),
-                    } = color;
-                    asset3d = asset3d.with_albedo_factor(
-                        // TODO(emilk): is this linear or sRGB?
-                        re_sdk_types::datatypes::Rgba32::from_linear_unmultiplied_rgba_f32(
-                            *r as f32, *g as f32, *b as f32, *a as f32,
-                        ),
-                    );
-                }
+
                 if texture.is_some() {
                     re_log::warn_once!("Material texture not supported"); // TODO(emilk): support textures
                 }
@@ -484,7 +476,8 @@ fn log_geometry(
                 timepoint,
                 &re_sdk_types::archetypes::Boxes3D::from_sizes([Vec3D::new(
                     *x as _, *y as _, *z as _,
-                )]),
+                )])
+                .with_colors([material_color(material)]),
             )?;
         }
         Geometry::Cylinder { radius, length } => {
@@ -497,7 +490,8 @@ fn log_geometry(
                 &re_sdk_types::archetypes::Cylinders3D::from_lengths_and_radii(
                     [*length as f32],
                     [*radius as f32],
-                ),
+                )
+                .with_colors([material_color(material)]),
             )?;
         }
         Geometry::Capsule { radius, length } => {
@@ -510,7 +504,8 @@ fn log_geometry(
                 &re_sdk_types::archetypes::Capsules3D::from_lengths_and_radii(
                     [*length as f32],
                     [*radius as f32],
-                ),
+                )
+                .with_colors([material_color(material)]),
             )?;
         }
         Geometry::Sphere { radius } => {
@@ -519,13 +514,33 @@ fn log_geometry(
                 store_id,
                 entity_path,
                 timepoint,
-                &re_sdk_types::archetypes::Ellipsoids3D::from_radii([*radius as f32]),
+                &re_sdk_types::archetypes::Ellipsoids3D::from_radii([*radius as f32])
+                    .with_colors([material_color(material)]),
             )?;
         }
     }
     Ok(())
 }
 
+/// Extracts the RGBA color from a URDF material. Falls back to white if no color is specified.
+fn material_color(material: Option<&urdf_rs::Material>) -> Color {
+    Color::new(material_albedo_factor(material))
+}
+
+fn material_albedo_factor(material: Option<&urdf_rs::Material>) -> Rgba32 {
+    material
+        .and_then(|material| material.color.as_ref())
+        .map(|color| {
+            let urdf_rs::Color {
+                rgba: Vec4([r, g, b, a]),
+            } = color;
+
+            // TODO(emilk): is this linear or sRGB?
+            Rgba32::from_linear_unmultiplied_rgba_f32(*r as f32, *g as f32, *b as f32, *a as f32)
+        })
+        .unwrap_or(Rgba32::WHITE)
+}
+
 fn quat_from_rpy(rpy: &[f64; 3]) -> glam::Quat {
     glam::Quat::from_euler(
         glam::EulerRot::ZYX,
diff --git a/tests/assets/rrd/snippets/howto/load_urdf.rrd b/tests/assets/rrd/snippets/howto/load_urdf.rrd
index 6bf4e92b3eea..a17934d5b0a5 100644
--- a/tests/assets/rrd/snippets/howto/load_urdf.rrd
+++ b/tests/assets/rrd/snippets/howto/load_urdf.rrd
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:262305c1c27720dc4cf576f37bc6c7c1af65319b68bb16763a8a36f58126267d
-size 63518
+oid sha256:18215618e0e3f3331fd56b2d0a5134c594965be1557d74d56f7165cf49b3d213
+size 46026

From 3edfef6c57f18abc393c0c5fe09ace7b9dbc29c0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?G=C3=A1bor=20Gyebn=C3=A1r?= 
Date: Sat, 14 Mar 2026 09:46:43 +0100
Subject: [PATCH 140/513] Fixes broken view defaults

### Related

Closes RR-4016

### What

Fixes the blueprint query when we display the view defaults.

Source-Ref: 6eca026a1f1303c9097e5e49e83c6499e267798c
---
 .../re_selection_panel/src/defaults_ui.rs     | 26 ++++--
 .../tests/snapshots/view_defaults.png         |  3 +
 .../tests/view_defaults_test.rs               | 85 +++++++++++++++++++
 3 files changed, 107 insertions(+), 7 deletions(-)
 create mode 100644 tests/rust/re_integration_test/tests/snapshots/view_defaults.png
 create mode 100644 tests/rust/re_integration_test/tests/view_defaults_test.rs

diff --git a/crates/viewer/re_selection_panel/src/defaults_ui.rs b/crates/viewer/re_selection_panel/src/defaults_ui.rs
index 463b6624c70c..1f2834295c28 100644
--- a/crates/viewer/re_selection_panel/src/defaults_ui.rs
+++ b/crates/viewer/re_selection_panel/src/defaults_ui.rs
@@ -38,11 +38,14 @@ pub fn view_components_defaults_section_ui(
     ui: &mut egui::Ui,
     view: &ViewBlueprint,
 ) {
+    // For accessing the blueprint store/cache (NOT for querying — its `.query()` uses the wrong timeline).
     let blueprint_store_view_ctx = ctx.viewer_ctx.blueprint_store_view_ctx();
+    // The undo-aware query on the `blueprint` timeline. Must be used for all blueprint queries.
+    let blueprint_query = ctx.blueprint_query();
 
     // TODO(andreas): Components in `active_defaults` should be sorted by field order within each archetype.
     // Right now, they're just sorted by descriptor, which is not the same.
-    let active_defaults = active_defaults(&blueprint_store_view_ctx, view);
+    let active_defaults = active_defaults(&blueprint_store_view_ctx, blueprint_query, view);
     let visualizers = ctx.new_visualizer_collection();
     let visualized_components_by_archetype =
         visualized_components_by_archetype(&visualizers, ctx.viewer_ctx.app_options());
@@ -67,7 +70,7 @@ pub fn view_components_defaults_section_ui(
                 ctx,
                 ui,
                 &view.defaults_path,
-                &blueprint_store_view_ctx.query(),
+                blueprint_query,
                 components_to_show_in_add_menu.unwrap_or_default(),
             );
         })
@@ -84,7 +87,14 @@ override is specified.\n
 Click on the `+` button to add a new default value.";
 
     let body = |ui: &mut egui::Ui| {
-        active_default_ui(ctx, &blueprint_store_view_ctx, ui, &active_defaults, view);
+        active_default_ui(
+            ctx,
+            &blueprint_store_view_ctx,
+            blueprint_query,
+            ui,
+            &active_defaults,
+            view,
+        );
     };
     ui.section_collapsing_header("Component defaults")
         .with_button(add_button)
@@ -95,6 +105,7 @@ Click on the `+` button to add a new default value.";
 fn active_default_ui(
     ctx: &ViewContext<'_>,
     blueprint_store_view_ctx: &StoreViewContext<'_>,
+    blueprint_query: &LatestAtQuery,
     ui: &mut egui::Ui,
     active_defaults: &BTreeMap,
     view: &ViewBlueprint,
@@ -104,7 +115,7 @@ fn active_default_ui(
         target_entity_path: &view.defaults_path,
         instruction_id: None,
         archetype_name: None,
-        query: blueprint_store_view_ctx.query().clone(),
+        query: blueprint_query.clone(),
     };
 
     re_ui::list_item::list_item_scope(ui, "defaults", |ui| {
@@ -217,12 +228,13 @@ fn visualized_components_by_archetype(
 }
 
 fn active_defaults(
-    blueprint_ctx: &StoreViewContext<'_>,
+    store_view_ctx: &StoreViewContext<'_>,
+    blueprint_query: &LatestAtQuery,
     view: &ViewBlueprint,
 ) -> BTreeMap {
     // Cleared components should act as unset, so we filter out everything that's empty,
     // even if they are listed in `all_components`.
-    let engine = blueprint_ctx.db.storage_engine();
+    let engine = store_view_ctx.db.storage_engine();
 
     engine
         .store()
@@ -232,7 +244,7 @@ fn active_defaults(
         .filter_map(|component| {
             let data = engine
                 .cache()
-                .latest_at(&blueprint_ctx.query(), &view.defaults_path, [component])
+                .latest_at(blueprint_query, &view.defaults_path, [component])
                 .component_batch_raw(component)?;
             (!data.is_empty()).then_some((component, data))
         })
diff --git a/tests/rust/re_integration_test/tests/snapshots/view_defaults.png b/tests/rust/re_integration_test/tests/snapshots/view_defaults.png
new file mode 100644
index 000000000000..d2d8b000ffb0
--- /dev/null
+++ b/tests/rust/re_integration_test/tests/snapshots/view_defaults.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:a26111e35ea3b2899fde9faeef588be2e4e44c6286689f23fb7664971544435c
+size 178589
diff --git a/tests/rust/re_integration_test/tests/view_defaults_test.rs b/tests/rust/re_integration_test/tests/view_defaults_test.rs
new file mode 100644
index 000000000000..68936983ad91
--- /dev/null
+++ b/tests/rust/re_integration_test/tests/view_defaults_test.rs
@@ -0,0 +1,85 @@
+//! Test that view component defaults can be written and read back from the blueprint store.
+
+use re_integration_test::HarnessExt as _;
+use re_view_time_series::TimeSeriesView;
+use re_viewer::external::re_chunk::LatestAtQuery;
+use re_viewer::external::re_sdk_types;
+use re_viewer::external::re_viewer_context::{
+    BlueprintContext as _, ViewClass as _, blueprint_timeline,
+};
+use re_viewer::viewer_test_utils::{self, HarnessOptions};
+use re_viewport_blueprint::ViewBlueprint;
+
+/// Adds a `StrokeWidth` component default to a time series view and verifies
+/// it can be read back from the blueprint store on the blueprint timeline.
+#[tokio::test(flavor = "multi_thread")]
+pub async fn test_view_defaults_stroke_width() {
+    let mut harness = viewer_test_utils::viewer_harness(&HarnessOptions {
+        window_size: Some(egui::Vec2::new(1200.0, 1000.0)),
+        max_steps: Some(100),
+        ..Default::default()
+    });
+    harness.init_recording();
+    harness.set_blueprint_panel_opened(true);
+    harness.set_selection_panel_opened(true);
+    harness.set_time_panel_opened(false);
+
+    let timeline = re_sdk::Timeline::new_sequence("frame");
+
+    // Log scalar data so the time series view has something to visualize.
+    for i in 0..50 {
+        harness.log_entity("plot", |builder| {
+            builder.with_archetype_auto_row(
+                [(timeline, i)],
+                &re_sdk_types::archetypes::Scalars::single((i as f64 / 10.0).sin()),
+            )
+        });
+    }
+
+    // Create a time series view and save a StrokeWidth default on its defaults path.
+    let (defaults_path, component_id) =
+        harness.setup_viewport_blueprint(|viewer_context, blueprint| {
+            let view = ViewBlueprint::new_with_root_wildcard(TimeSeriesView::identifier());
+            let defaults_path = view.defaults_path.clone();
+
+            let descriptor = re_sdk_types::archetypes::SeriesLines::descriptor_widths();
+            let component_id = descriptor.component;
+            let stroke_width = re_sdk_types::components::StrokeWidth::from(5.0f32);
+            viewer_context.save_blueprint_component(
+                defaults_path.clone(),
+                &descriptor,
+                &stroke_width,
+            );
+
+            blueprint.add_view_at_root(view);
+            (defaults_path, component_id)
+        });
+
+    // Expand the blueprint tree and select the view to show defaults in the selection panel.
+    harness
+        .blueprint_tree()
+        .right_click_label("Viewport (Grid container)");
+    harness.click_label("Expand all");
+    harness.run();
+
+    // Select the view to show the selection panel with component defaults.
+    harness.blueprint_tree().click_label("/");
+    harness.run();
+
+    harness.snapshot_app("view_defaults");
+
+    // Verify the default is queryable from the blueprint store on the blueprint timeline.
+    let stroke_width = harness.run_with_viewer_context(move |viewer_context| {
+        let blueprint_db = viewer_context.store_context.blueprint;
+        let query = LatestAtQuery::latest(blueprint_timeline());
+        let results = blueprint_db.latest_at(&query, &defaults_path, [component_id]);
+        results.component_mono::(component_id)
+    });
+
+    let stroke_width =
+        stroke_width.expect("StrokeWidth default should be queryable from blueprint store");
+    assert_eq!(
+        stroke_width.0.0, 5.0,
+        "StrokeWidth should match the saved default value"
+    );
+}

From f0c99c5b743cf7cf341ca4c3375ebbb43cc44ade Mon Sep 17 00:00:00 2001
From: Michael Grupp 
Date: Sat, 14 Mar 2026 11:20:43 +0100
Subject: [PATCH 141/513] Ignore empty MCAP channels

MCAP can contain channels that have no messages. Outputting an entity
for those is extremely confusing and carries nothing except channel
stats columns (which, obviously also only show that there's no
messages).

--> **if there's nothing, output nothing.**

I only added a small debug log, but apart from that the behavior should
be clear for end users and requires no extra attention through warnings
etc.

Source-Ref: 3ba26beb55d534fa58eacb0f5f6348e68725c4a5
---
 .../re_data_loader/src/loader_mcap/loader.rs  |   2 +-
 .../test_mcap_loader__tests__ros2.snap        |   4 +-
 crates/store/re_mcap/src/decoders/metadata.rs |   2 +-
 crates/store/re_mcap/src/decoders/mod.rs      | 110 ++++++++++++++++--
 crates/store/re_mcap/src/decoders/protobuf.rs |   2 +-
 crates/store/re_mcap/src/decoders/schema.rs   |   8 +-
 crates/store/re_mcap/src/util.rs              |  43 +++++++
 7 files changed, 153 insertions(+), 18 deletions(-)

diff --git a/crates/store/re_data_loader/src/loader_mcap/loader.rs b/crates/store/re_data_loader/src/loader_mcap/loader.rs
index a94ee8d6e339..9021c6c84362 100644
--- a/crates/store/re_data_loader/src/loader_mcap/loader.rs
+++ b/crates/store/re_data_loader/src/loader_mcap/loader.rs
@@ -296,7 +296,7 @@ pub fn load_mcap(
     // TODO(#10862): Add warning for channel that miss semantic information.
     DecoderRegistry::all_builtin(raw_fallback_enabled)
         .select(selected_decoders)
-        .plan(&summary)?
+        .plan(mcap, &summary)?
         .run(mcap, &summary, time_type, &mut send_chunk)?;
 
     Ok(())
diff --git a/crates/store/re_data_loader/tests/snapshots/test_mcap_loader__tests__ros2.snap b/crates/store/re_data_loader/tests/snapshots/test_mcap_loader__tests__ros2.snap
index 60577e9a205e..c75b5b7e589e 100644
--- a/crates/store/re_data_loader/tests/snapshots/test_mcap_loader__tests__ros2.snap
+++ b/crates/store/re_data_loader/tests/snapshots/test_mcap_loader__tests__ros2.snap
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:5ab4c373cd567961dc5614554d7d65b8a05830f028d6d047c40a7a21d5aa8810
-size 266168
+oid sha256:d51febe9f36a18d7f9dee7c8b90fb99394326ccfc86abad197d804fe1be26661
+size 244367
diff --git a/crates/store/re_mcap/src/decoders/metadata.rs b/crates/store/re_mcap/src/decoders/metadata.rs
index a57ef929f5a2..6117aafb75ac 100644
--- a/crates/store/re_mcap/src/decoders/metadata.rs
+++ b/crates/store/re_mcap/src/decoders/metadata.rs
@@ -117,7 +117,7 @@ mod tests {
         let mut chunks = Vec::new();
         let registry = DecoderRegistry::empty().register_file_decoder::();
         registry
-            .plan(&summary)
+            .plan(buffer, &summary)
             .expect("failed to plan")
             .run(buffer, &summary, TimeType::TimestampNs, &mut |chunk| {
                 chunks.push(chunk);
diff --git a/crates/store/re_mcap/src/decoders/mod.rs b/crates/store/re_mcap/src/decoders/mod.rs
index e268864e281a..f4c8b9780187 100644
--- a/crates/store/re_mcap/src/decoders/mod.rs
+++ b/crates/store/re_mcap/src/decoders/mod.rs
@@ -23,6 +23,7 @@ pub use self::schema::McapSchemaDecoder;
 pub use self::stats::McapStatisticDecoder;
 use crate::Error;
 use crate::parsers::{ChannelId, MessageParser, ParserContext};
+use crate::util::collect_empty_channels;
 
 /// Globally unique identifier for a decoder.
 #[derive(Clone, Debug, PartialOrd, Ord, PartialEq, Eq)]
@@ -418,13 +419,19 @@ impl DecoderRegistry {
     }
 
     /// Build a concrete execution plan for a given file.
-    pub fn plan(&self, summary: &mcap::Summary) -> anyhow::Result {
+    pub fn plan(
+        &self,
+        mcap_bytes: &[u8],
+        summary: &mcap::Summary,
+    ) -> anyhow::Result {
         let file_decoders = self
             .file_factories
             .values()
             .map(|f| f())
             .collect::>();
 
+        let empty_channels = collect_empty_channels(mcap_bytes, summary)?;
+
         // instantiate message decoders and init them (supports_channel may depend on init)
         let mut msg_decoders: Vec<(DecoderIdentifier, Box)> = self
             .msg_order
@@ -440,10 +447,22 @@ impl DecoderRegistry {
         let mut assignments: Vec = Vec::new();
 
         for channel_id in summary.channels.values() {
+            let channel_id = ChannelId::from(channel_id.id);
+            let channel = summary.channels[&channel_id.0].as_ref();
+
+            if empty_channels.contains(&channel_id) {
+                re_log::debug!(
+                    "Skipping MCAP channel '{}' (id={}) because it contains no messages.",
+                    channel.topic,
+                    channel_id.0,
+                );
+                continue;
+            }
+
             // explicit priority order
             let mut chosen: Option = None;
             for (id, decoder) in &msg_decoders {
-                if decoder.supports_channel(channel_id.as_ref()) {
+                if decoder.supports_channel(channel) {
                     chosen = Some(id.clone());
                     break;
                 }
@@ -458,31 +477,28 @@ impl DecoderRegistry {
                 }
             }
 
-            let schema_name = channel_id.schema.as_ref().map(|s| s.name.clone());
+            let schema_name = channel.schema.as_ref().map(|s| s.name.clone());
 
-            let schema_encoding = channel_id
+            let schema_encoding = channel
                 .schema
                 .as_ref()
                 .map(|s| s.encoding.as_str())
                 .unwrap_or("Unknown");
 
             if let Some(id) = chosen {
-                by_decoder
-                    .entry(id.clone())
-                    .or_default()
-                    .insert(ChannelId::from(channel_id.id));
+                by_decoder.entry(id.clone()).or_default().insert(channel_id);
 
                 assignments.push(DecoderAssignment {
-                    channel_id: ChannelId::from(channel_id.id),
-                    topic: channel_id.topic.clone(),
+                    channel_id,
+                    topic: channel.topic.clone(),
                     encoding: schema_encoding.to_owned(),
-                    schema_name: channel_id.schema.as_ref().map(|s| s.name.clone()),
+                    schema_name: channel.schema.as_ref().map(|s| s.name.clone()),
                     decoder: id,
                 });
             } else {
                 re_log::debug!(
                     "No message decoder selected for topic '{}' (encoding='{}', schema='{:?}')",
-                    channel_id.topic,
+                    channel.topic,
                     schema_encoding,
                     schema_name,
                 );
@@ -504,3 +520,73 @@ impl DecoderRegistry {
         })
     }
 }
+
+#[cfg(test)]
+mod tests {
+    use std::io;
+
+    use re_chunk::Chunk;
+    use re_log_types::TimeType;
+
+    use super::*;
+
+    #[test]
+    fn skips_channels_without_messages() {
+        let (summary, buffer, empty_channel_id, active_channel_id) = {
+            let cursor = io::Cursor::new(Vec::new());
+            let mut writer = mcap::Writer::new(cursor).expect("failed to create writer");
+
+            let empty_channel_id = writer
+                .add_channel(0, "empty_topic", "raw", &Default::default())
+                .expect("failed to add empty channel");
+            let active_channel_id = writer
+                .add_channel(0, "active_topic", "raw", &Default::default())
+                .expect("failed to add active channel");
+
+            writer
+                .write_to_known_channel(
+                    &mcap::records::MessageHeader {
+                        channel_id: active_channel_id,
+                        sequence: 0,
+                        log_time: 1,
+                        publish_time: 1,
+                    },
+                    &[1, 2, 3],
+                )
+                .expect("failed to write message");
+
+            let summary = writer.finish().expect("failed to finish writer");
+            let buffer = writer.into_inner().into_inner();
+
+            (summary, buffer, empty_channel_id, active_channel_id)
+        };
+
+        let plan = DecoderRegistry::empty()
+            .register_file_decoder::()
+            .register_message_decoder::()
+            .plan(&buffer, &summary)
+            .expect("failed to plan");
+
+        assert_eq!(plan.assignments.len(), 1);
+        assert_eq!(plan.assignments[0].channel_id, ChannelId(active_channel_id));
+        assert_ne!(plan.assignments[0].channel_id, ChannelId(empty_channel_id));
+
+        let mut chunks = Vec::::new();
+        plan.run(&buffer, &summary, TimeType::TimestampNs, &mut |chunk| {
+            chunks.push(chunk);
+        })
+        .expect("failed to run plan");
+
+        assert_eq!(chunks.len(), 2);
+        assert!(
+            chunks
+                .iter()
+                .all(|chunk| !chunk.entity_path().to_string().ends_with("empty_topic"))
+        );
+        assert!(
+            chunks
+                .iter()
+                .any(|chunk| chunk.entity_path().to_string().ends_with("active_topic"))
+        );
+    }
+}
diff --git a/crates/store/re_mcap/src/decoders/protobuf.rs b/crates/store/re_mcap/src/decoders/protobuf.rs
index 44e4f8a0a806..429e32b1f30f 100644
--- a/crates/store/re_mcap/src/decoders/protobuf.rs
+++ b/crates/store/re_mcap/src/decoders/protobuf.rs
@@ -952,7 +952,7 @@ mod integration_tests {
 
         let registry = DecoderRegistry::empty().register_message_decoder::();
         registry
-            .plan(summary)
+            .plan(buffer, summary)
             .expect("failed to plan")
             .run(buffer, summary, TimeType::TimestampNs, &mut send_chunk)
             .expect("failed to run decoder");
diff --git a/crates/store/re_mcap/src/decoders/schema.rs b/crates/store/re_mcap/src/decoders/schema.rs
index fd996762c04b..036956b8f912 100644
--- a/crates/store/re_mcap/src/decoders/schema.rs
+++ b/crates/store/re_mcap/src/decoders/schema.rs
@@ -20,11 +20,17 @@ impl Decoder for McapSchemaDecoder {
 
     fn process(
         &mut self,
-        _mcap_bytes: &[u8],
+        mcap_bytes: &[u8],
         summary: &mcap::Summary,
         emit: &mut dyn FnMut(Chunk),
     ) -> Result<(), Error> {
+        let empty_channels = crate::util::collect_empty_channels(mcap_bytes, summary)?;
+
         for channel in summary.channels.values() {
+            if empty_channels.contains(&crate::parsers::ChannelId(channel.id)) {
+                continue;
+            }
+
             let mut components = from_channel(channel).as_serialized_batches();
             if let Some(schema) = channel.schema.as_ref() {
                 components.extend(
diff --git a/crates/store/re_mcap/src/util.rs b/crates/store/re_mcap/src/util.rs
index d4ce1a231c78..f270b033abb7 100644
--- a/crates/store/re_mcap/src/util.rs
+++ b/crates/store/re_mcap/src/util.rs
@@ -1,3 +1,4 @@
+use std::collections::BTreeSet;
 use std::io::{Read, Seek};
 
 use mcap::Summary;
@@ -6,6 +7,9 @@ use re_chunk::TimePoint;
 use re_log_types::{TimeCell, TimeType};
 use saturating_cast::SaturatingCast as _;
 
+use crate::Error;
+use crate::parsers::ChannelId;
+
 /// Read out the summary of an MCAP file.
 pub fn read_summary(mut reader: R) -> anyhow::Result> {
     let mut summary_reader = SummaryReader::new();
@@ -24,6 +28,45 @@ pub fn read_summary(mut reader: R) -> anyhow::Result Result, Error> {
+    let all_channels = summary
+        .channels
+        .keys()
+        .copied()
+        .map(ChannelId)
+        .collect::>();
+
+    if let Some(stats) = &summary.stats {
+        let nonempty_channels = stats
+            .channel_message_counts
+            .iter()
+            .filter_map(|(&channel_id, &count)| (count > 0).then_some(ChannelId(channel_id)))
+            .collect::>();
+
+        return Ok(all_channels
+            .difference(&nonempty_channels)
+            .copied()
+            .collect());
+    }
+
+    let mut empty_channels = all_channels;
+
+    for chunk in &summary.chunk_indexes {
+        for (channel, msg_offsets) in summary.read_message_indexes(mcap_bytes, chunk)? {
+            if !msg_offsets.is_empty() {
+                // Channel has at least one message, so it's not empty.
+                empty_channels.remove(&ChannelId(channel.id));
+            }
+        }
+    }
+
+    Ok(empty_channels)
+}
+
 /// Extracts log and publish time from an MCAP message as a `TimePoint`.
 ///
 /// The `time_type` parameter controls whether the timelines are created as

From 357aab8c821f6d51c33a1d49afbcef92848b3f85 Mon Sep 17 00:00:00 2001
From: Michael Grupp 
Date: Sat, 14 Mar 2026 15:37:55 +0100
Subject: [PATCH 142/513] Ignore 'rosbag2' metadata field

It's useless for us and just creates unnecessary noise.

See comment in the code for background info.

Source-Ref: 4da6e86d81690a84c483866ded92e6f13d3e7acf
---
 .../snapshots/test_mcap_loader__tests__ros2.snap    |  4 ++--
 crates/store/re_mcap/src/decoders/metadata.rs       | 13 +++++++++++++
 2 files changed, 15 insertions(+), 2 deletions(-)

diff --git a/crates/store/re_data_loader/tests/snapshots/test_mcap_loader__tests__ros2.snap b/crates/store/re_data_loader/tests/snapshots/test_mcap_loader__tests__ros2.snap
index c75b5b7e589e..51e2390902ad 100644
--- a/crates/store/re_data_loader/tests/snapshots/test_mcap_loader__tests__ros2.snap
+++ b/crates/store/re_data_loader/tests/snapshots/test_mcap_loader__tests__ros2.snap
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:d51febe9f36a18d7f9dee7c8b90fb99394326ccfc86abad197d804fe1be26661
-size 244367
+oid sha256:c2179635c214f57d9d1e347c3c21680df9279fe57eb6daabb723ea18c8e0ca18
+size 243163
diff --git a/crates/store/re_mcap/src/decoders/metadata.rs b/crates/store/re_mcap/src/decoders/metadata.rs
index 6117aafb75ac..cecfb66dc2a7 100644
--- a/crates/store/re_mcap/src/decoders/metadata.rs
+++ b/crates/store/re_mcap/src/decoders/metadata.rs
@@ -17,6 +17,7 @@ use crate::Error;
 pub struct McapMetadataDecoder;
 
 const ARCHETYPE_NAME: &str = "McapMetadata";
+const ROSBAG2_METADATA_NAME: &str = "rosbag2";
 
 impl Decoder for McapMetadataDecoder {
     fn identifier() -> DecoderIdentifier {
@@ -49,6 +50,18 @@ impl Decoder for McapMetadataDecoder {
                 }
             };
 
+            if metadata.name == ROSBAG2_METADATA_NAME {
+                // "rosbag2" is a dump of the metadata YAML file that is specific to ROS2's rosbag2 tool.
+                // It's mainly a backwards-compatibility feature for conversion to the legacy SQL rosbag format,
+                // so we can safely ignore it (it is potentially large).
+                // See also: https://docs.ros.org/en/kilted/Releases/Release-Jazzy-Jalisco.html#store-serialized-metadata-in-bag-files-directly
+                re_log::debug_once!(
+                    "Skipping ROS MCAP metadata record '{}' as it is not relevant for Rerun.",
+                    ROSBAG2_METADATA_NAME
+                );
+                continue;
+            }
+
             re_log::debug!(
                 "Processing MCAP metadata record '{}' with {} entries",
                 metadata.name,

From c1baf07b6777668430c6b05b37e8d9e215e3ec0d Mon Sep 17 00:00:00 2001
From: Ruthwik 
Date: Sat, 14 Mar 2026 12:51:26 -0700
Subject: [PATCH 143/513] fix: remove logging

---
 dimos/src/interaction/keyboard.rs | 5 -----
 dimos/src/viewer.rs               | 2 +-
 2 files changed, 1 insertion(+), 6 deletions(-)

diff --git a/dimos/src/interaction/keyboard.rs b/dimos/src/interaction/keyboard.rs
index 88a556c9c042..b3f631b711ec 100644
--- a/dimos/src/interaction/keyboard.rs
+++ b/dimos/src/interaction/keyboard.rs
@@ -194,10 +194,6 @@ impl KeyboardHandler {
                         self.state.reset();
                         self.was_active = false;
                     }
-                    re_log::info!(
-                        "Keyboard teleop {}",
-                        if self.engaged { "ENGAGED" } else { "DISENGAGED" }
-                    );
                 }
             })
             .response;
@@ -213,7 +209,6 @@ impl KeyboardHandler {
             }
             self.state.reset();
             self.was_active = false;
-            re_log::info!("Keyboard teleop DISENGAGED (clicked outside)");
         }
     }
 
diff --git a/dimos/src/viewer.rs b/dimos/src/viewer.rs
index f2c5f98ca31c..0b71b71f8484 100644
--- a/dimos/src/viewer.rs
+++ b/dimos/src/viewer.rs
@@ -132,7 +132,7 @@ async fn main() -> Result<(), Box> {
     // Create keyboard handler
     let keyboard_handler = KeyboardHandler::new()
         .expect("Failed to create keyboard handler");
-    re_log::info!("Keyboard handler initialized for WASD controls on /cmd_vel");
+
 
     // State for debouncing and rapid click detection
     let last_click_time = Rc::new(RefCell::new(Instant::now()));

From a952495af4abfab8cf3f8312d92fe6a7b10cbb85 Mon Sep 17 00:00:00 2001
From: Ruthwik 
Date: Sat, 14 Mar 2026 12:57:40 -0700
Subject: [PATCH 144/513] fix: reposition default

---
 dimos/src/interaction/keyboard.rs | 7 +++----
 1 file changed, 3 insertions(+), 4 deletions(-)

diff --git a/dimos/src/interaction/keyboard.rs b/dimos/src/interaction/keyboard.rs
index b3f631b711ec..67afe4e39c31 100644
--- a/dimos/src/interaction/keyboard.rs
+++ b/dimos/src/interaction/keyboard.rs
@@ -140,13 +140,12 @@ impl KeyboardHandler {
     /// Clickable: clicking the overlay toggles engaged state.
     pub fn draw_overlay(&mut self, ctx: &egui::Context) {
         let screen_rect = ctx.content_rect();
-        // Default position: bottom-right of the 3D viewport area
-        let overlay_width = 140.0;
+        // Default position: bottom-left, just above the timeline bar
         let overlay_height = 160.0;
-        let right_panel_offset = 320.0;
+        let left_margin = 12.0;
         let bottom_timeline_offset = 120.0;
         let default_pos = egui::pos2(
-            screen_rect.max.x - overlay_width - right_panel_offset,
+            screen_rect.min.x + left_margin,
             screen_rect.max.y - overlay_height - bottom_timeline_offset,
         );
 

From d22140933a37967573cfb8275e6a21ec397bc4b8 Mon Sep 17 00:00:00 2001
From: Ruthwik 
Date: Sat, 14 Mar 2026 13:16:51 -0700
Subject: [PATCH 145/513] fix: default position

---
 dimos/src/interaction/keyboard.rs | 1 +
 1 file changed, 1 insertion(+)

diff --git a/dimos/src/interaction/keyboard.rs b/dimos/src/interaction/keyboard.rs
index 67afe4e39c31..1e7d51f3c71f 100644
--- a/dimos/src/interaction/keyboard.rs
+++ b/dimos/src/interaction/keyboard.rs
@@ -150,6 +150,7 @@ impl KeyboardHandler {
         );
 
         let area_response = egui::Area::new("keyboard_hud".into())
+            .pivot(egui::Align2::LEFT_BOTTOM)
             .default_pos(default_pos)
             .movable(true)
             .order(egui::Order::Foreground)

From 88eda7d331c89e29b5195d3ee9e799daba500a98 Mon Sep 17 00:00:00 2001
From: Jeff Hykin 
Date: Sat, 14 Mar 2026 15:03:05 -0700
Subject: [PATCH 146/513] feat: add --connect arg to dimos-viewer for
 connecting to existing gRPC proxy

When --connect is provided (with optional URL), the viewer connects to an
existing gRPC proxy server instead of spawning its own. This mirrors the
stock rerun binary's --connect behavior. Defaults to rerun+http://127.0.0.1:/proxy.
---
 dimos/src/viewer.rs | 46 +++++++++++++++++++++++++++++++--------------
 1 file changed, 32 insertions(+), 14 deletions(-)

diff --git a/dimos/src/viewer.rs b/dimos/src/viewer.rs
index 0b71b71f8484..c4f4606e98d3 100644
--- a/dimos/src/viewer.rs
+++ b/dimos/src/viewer.rs
@@ -4,7 +4,7 @@ use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
 
 use clap::Parser;
 use dimos_viewer::interaction::{LcmPublisher, KeyboardHandler, click_event_from_ms};
-use rerun::external::{eframe, egui, re_crash_handler, re_grpc_server, re_log, re_memory, re_viewer};
+use rerun::external::{eframe, egui, re_crash_handler, re_grpc_client, re_grpc_server, re_log, re_memory, re_uri, re_viewer};
 
 #[global_allocator]
 static GLOBAL: re_memory::AccountingAllocator =
@@ -48,6 +48,16 @@ struct Args {
     /// Hint that data will arrive shortly (suppresses "waiting for data" message).
     #[arg(long)]
     expect_data_soon: bool,
+
+    /// Do not start a local gRPC server; instead connect to an existing one.
+    ///
+    /// Optionally accepts a URL to a gRPC proxy server.
+    /// The scheme must be one of `rerun://`, `rerun+http://`, or `rerun+https://`,
+    /// and the pathname must be `/proxy`.
+    ///
+    /// Defaults to `rerun+http://127.0.0.1:/proxy`.
+    #[arg(long)]
+    connect: Option>,
 }
 
 /// Wraps re_viewer::App to add keyboard control interception.
@@ -110,19 +120,27 @@ async fn main() -> Result<(), Box> {
     re_log::setup_logging();
     re_crash_handler::install_crash_handlers(re_viewer::build_info());
 
-    // Listen for gRPC connections from Rerun's logging SDKs.
-    let listen_addr = format!("0.0.0.0:{}", args.port);
-    re_log::info!("Listening for SDK connections on {listen_addr}");
-    let server_memory_limit = re_memory::MemoryLimit::parse(&args.server_memory_limit)
-        .expect("Bad --server-memory-limit");
-    let rx_log = re_grpc_server::spawn_with_recv(
-        listen_addr.parse()?,
-        re_grpc_server::ServerOptions {
-            memory_limit: server_memory_limit,
-            ..Default::default()
-        },
-        re_grpc_server::shutdown::never(),
-    );
+    // Either connect to an existing gRPC proxy, or spawn a local server.
+    let rx_log = if let Some(url) = args.connect.clone() {
+        let url = url.unwrap_or_else(|| format!("rerun+http://127.0.0.1:{}/proxy", args.port));
+        let proxy_uri: re_uri::ProxyUri = url.parse()
+            .expect("Bad --connect URL: expected format rerun+http://host:port/proxy");
+        re_log::info!("Connecting to existing gRPC proxy at {proxy_uri}");
+        re_grpc_client::stream(proxy_uri)
+    } else {
+        let listen_addr = format!("0.0.0.0:{}", args.port);
+        re_log::info!("Listening for SDK connections on {listen_addr}");
+        let server_memory_limit = re_memory::MemoryLimit::parse(&args.server_memory_limit)
+            .expect("Bad --server-memory-limit");
+        re_grpc_server::spawn_with_recv(
+            listen_addr.parse()?,
+            re_grpc_server::ServerOptions {
+                memory_limit: server_memory_limit,
+                ..Default::default()
+            },
+            re_grpc_server::shutdown::never(),
+        )
+    };
 
     // Create LCM publisher for click events
     let lcm_publisher = LcmPublisher::new(LCM_CHANNEL.to_string())

From 692954b0daf87b4efa018f211f3bdbda25ee8853 Mon Sep 17 00:00:00 2001
From: Jeff Hykin 
Date: Sun, 15 Mar 2026 11:33:16 -0700
Subject: [PATCH 147/513] fix: resolve CI lint, formatting, and cargo-shear
 failures

- Remove unused `parking_lot` dependency (cargo-shear)
- Add `[lints] workspace = true` to dimos/Cargo.toml (rerun lint)
- Fix TOML formatting in dimos/pyproject.toml (taplo)
- Rename error variables from `e` to `err` (rerun lint convention)
- Fix trailing whitespace in keyboard.rs
- Add blank lines before doc comments on struct fields/constants
- Use Display instead of Debug for error formatting in viewer.rs
- Commit updated Cargo.lock

Co-Authored-By: Claude Opus 4.6 (1M context) 
---
 Cargo.lock                        |  3 +--
 dimos/Cargo.toml                  |  4 +++-
 dimos/pyproject.toml              | 22 +++++++++++-----------
 dimos/src/interaction/handle.rs   |  4 ++--
 dimos/src/interaction/keyboard.rs | 26 +++++++++++++-------------
 dimos/src/interaction/lcm.rs      |  3 +++
 dimos/src/viewer.rs               |  5 ++++-
 7 files changed, 37 insertions(+), 30 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index 0748530c4368..b3be7f215f21 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3110,12 +3110,11 @@ dependencies = [
 
 [[package]]
 name = "dimos-viewer"
-version = "0.30.0-alpha.1+dev"
+version = "0.30.0-alpha.4"
 dependencies = [
  "bincode",
  "clap",
  "mimalloc",
- "parking_lot",
  "rerun",
  "serde",
  "tokio",
diff --git a/dimos/Cargo.toml b/dimos/Cargo.toml
index dde4b393b475..3dfc9283fcca 100644
--- a/dimos/Cargo.toml
+++ b/dimos/Cargo.toml
@@ -29,7 +29,6 @@ rerun = { path = "../crates/top/rerun", default-features = false, features = [
 clap = { workspace = true, features = ["derive"] }
 bincode.workspace = true
 mimalloc.workspace = true
-parking_lot.workspace = true
 serde = { workspace = true, features = ["derive"] }
 tokio = { workspace = true, features = [
   "io-util",
@@ -40,3 +39,6 @@ tokio = { workspace = true, features = [
   "sync",
   "time",
 ] }
+
+[lints]
+workspace = true
diff --git a/dimos/pyproject.toml b/dimos/pyproject.toml
index 83159e2bec27..1b1376b75811 100644
--- a/dimos/pyproject.toml
+++ b/dimos/pyproject.toml
@@ -8,18 +8,18 @@ version = "0.30.0a4"
 description = "Interactive Rerun viewer for DimOS with click-to-navigate support"
 readme = "README.md"
 requires-python = ">=3.10"
-license = {text = "MIT OR Apache-2.0"}
-authors = [{name = "Dimensional Inc.", email = "engineering@dimensional.com"}]
+license = { text = "MIT OR Apache-2.0" }
+authors = [{ name = "Dimensional Inc.", email = "engineering@dimensional.com" }]
 classifiers = [
-    "Development Status :: 4 - Beta",
-    "Intended Audience :: Developers",
-    "Topic :: Scientific/Engineering",
-    "Topic :: Scientific/Engineering :: Visualization",
-    "Programming Language :: Rust",
-    "License :: OSI Approved :: MIT License",
-    "License :: OSI Approved :: Apache Software License",
-    "Operating System :: POSIX :: Linux",
-    "Operating System :: MacOS",
+  "Development Status :: 4 - Beta",
+  "Intended Audience :: Developers",
+  "Topic :: Scientific/Engineering",
+  "Topic :: Scientific/Engineering :: Visualization",
+  "Programming Language :: Rust",
+  "License :: OSI Approved :: MIT License",
+  "License :: OSI Approved :: Apache Software License",
+  "Operating System :: POSIX :: Linux",
+  "Operating System :: MacOS",
 ]
 
 [project.urls]
diff --git a/dimos/src/interaction/handle.rs b/dimos/src/interaction/handle.rs
index 0f71a6f11fd6..2378724bcd4c 100644
--- a/dimos/src/interaction/handle.rs
+++ b/dimos/src/interaction/handle.rs
@@ -34,8 +34,8 @@ impl InteractionHandle {
             is_2d,
         };
 
-        if let Err(e) = self.tx.send(event) {
-            eprintln!("Failed to send click event: {}", e);
+        if let Err(err) = self.tx.send(event) {
+            eprintln!("Failed to send click event: {}", err);
         }
     }
 }
diff --git a/dimos/src/interaction/keyboard.rs b/dimos/src/interaction/keyboard.rs
index 1e7d51f3c71f..26184566765d 100644
--- a/dimos/src/interaction/keyboard.rs
+++ b/dimos/src/interaction/keyboard.rs
@@ -1,5 +1,5 @@
 //! Keyboard handler for WASD movement controls that publish Twist messages.
-//! 
+//!
 //! Converts keyboard input to robot velocity commands following teleop conventions:
 //! - WASD/arrows for linear/angular motion
 //! - QE for strafing
@@ -98,8 +98,8 @@ impl KeyboardHandler {
         // If not engaged, don't capture any keys
         if !self.engaged {
             if self.was_active {
-                if let Err(e) = self.publish_stop() {
-                    re_log::warn!("Failed to send stop on disengage: {e:?}");
+                if let Err(err) = self.publish_stop() {
+                    re_log::warn!("Failed to send stop on disengage: {err:?}");
                 }
                 self.was_active = false;
             }
@@ -112,8 +112,8 @@ impl KeyboardHandler {
         // Check for emergency stop (Space key pressed - one-shot action)
         if ctx.input(|i| i.key_pressed(egui::Key::Space)) {
             self.state.reset();
-            if let Err(e) = self.publish_stop() {
-                re_log::warn!("Failed to send emergency stop: {e:?}");
+            if let Err(err) = self.publish_stop() {
+                re_log::warn!("Failed to send emergency stop: {err:?}");
             }
             self.was_active = false;
             self.estop_flash = true;
@@ -122,13 +122,13 @@ impl KeyboardHandler {
 
         // Publish twist command if keys are active, or stop if just released
         if self.state.any_active() {
-            if let Err(e) = self.publish_twist() {
-                re_log::warn!("Failed to publish twist command: {e:?}");
+            if let Err(err) = self.publish_twist() {
+                re_log::warn!("Failed to publish twist command: {err:?}");
             }
             self.was_active = true;
         } else if self.was_active {
-            if let Err(e) = self.publish_stop() {
-                re_log::warn!("Failed to send stop on key release: {e:?}");
+            if let Err(err) = self.publish_stop() {
+                re_log::warn!("Failed to send stop on key release: {err:?}");
             }
             self.was_active = false;
         }
@@ -188,8 +188,8 @@ impl KeyboardHandler {
                     self.engaged = !self.engaged;
                     if !self.engaged {
                         // Send stop when disengaging
-                        if let Err(e) = self.publish_stop() {
-                            re_log::warn!("Failed to send stop on disengage: {e:?}");
+                        if let Err(err) = self.publish_stop() {
+                            re_log::warn!("Failed to send stop on disengage: {err:?}");
                         }
                         self.state.reset();
                         self.was_active = false;
@@ -204,8 +204,8 @@ impl KeyboardHandler {
             && ctx.input(|i| i.pointer.primary_clicked())
         {
             self.engaged = false;
-            if let Err(e) = self.publish_stop() {
-                re_log::warn!("Failed to send stop on outside click: {e:?}");
+            if let Err(err) = self.publish_stop() {
+                re_log::warn!("Failed to send stop on outside click: {err:?}");
             }
             self.state.reset();
             self.was_active = false;
diff --git a/dimos/src/interaction/lcm.rs b/dimos/src/interaction/lcm.rs
index 6b6b4146a8f5..58263413153a 100644
--- a/dimos/src/interaction/lcm.rs
+++ b/dimos/src/interaction/lcm.rs
@@ -61,10 +61,13 @@ pub struct ClickEvent {
     pub x: f64,
     pub y: f64,
     pub z: f64,
+
     /// Rerun entity path (stored in frame_id per our convention).
     pub entity_path: String,
+
     /// Unix timestamp in seconds.
     pub timestamp_sec: i32,
+
     /// Nanosecond remainder.
     pub timestamp_nsec: i32,
 }
diff --git a/dimos/src/viewer.rs b/dimos/src/viewer.rs
index c4f4606e98d3..de73c093a824 100644
--- a/dimos/src/viewer.rs
+++ b/dimos/src/viewer.rs
@@ -12,10 +12,13 @@ static GLOBAL: re_memory::AccountingAllocator =
 
 /// LCM channel for click events (follows RViz convention)
 const LCM_CHANNEL: &str = "/clicked_point#geometry_msgs.PointStamped";
+
 /// Minimum time between click events (debouncing)
 const CLICK_DEBOUNCE_MS: u64 = 100;
+
 /// Maximum rapid clicks to log as warning
 const RAPID_CLICK_THRESHOLD: usize = 5;
+
 /// Default gRPC listen port (9877 to avoid conflict with stock Rerun on 9876)
 const DEFAULT_PORT: u16 = 9877;
 
@@ -231,7 +234,7 @@ async fn main() -> Result<(), Box> {
                                         );
                                     }
                                     Err(err) => {
-                                        re_log::error!("Failed to publish LCM click event: {err:?}");
+                                        re_log::error!("Failed to publish LCM click event: {err}");
                                     }
                                 }
                             }

From 735f8d6ccf343b4f9da0d1d041b25db4825c704b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jochen=20G=C3=B6rtler?= 
Date: Mon, 16 Mar 2026 13:14:11 +0100
Subject: [PATCH 148/513] Replace `TableError` and `CatalogError` with
 `RedapError`
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

### Related

* Closes RR-3691

### What

This will also improve the error reporting in the viewer, as we won't
show any file path anymore. If the dataplatform sends an `trace-id` it
will be appended as `(trace-id: )` via `impl Display for ApiError`.

| Before | After |
|--------|--------|
| image | image |

---------

Signed-off-by: Andrea Reale 

Source-Ref: 4db5dd25c85bcb622ac01c62ba4b7d2f19c40da5
Co-authored-by: Andrea Reale <154321632+andrea-reale@users.noreply.github.com>
Co-authored-by: Andreas Reich 
Co-authored-by: Emil Ernerfeldt 
Co-authored-by: Isse 
Co-authored-by: Zeljko Mihaljcic <7150613+zehiko@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 
Co-authored-by: Jeremy Leibs 
Co-authored-by: Tobias Hintze 
Co-authored-by: Gábor Gyebnár 
Co-authored-by: Ilya Zlobintsev 
Co-authored-by: Antoine Beyeler <49431240+abey79@users.noreply.github.com>
---
 Cargo.lock                                     | 1 -
 crates/store/re_data_source/Cargo.toml         | 1 -
 crates/store/re_data_source/src/data_source.rs | 2 +-
 3 files changed, 1 insertion(+), 3 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index 245b7c1e5ed8..acb3b4661223 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -8814,7 +8814,6 @@ dependencies = [
  "itertools 0.14.0",
  "rayon",
  "re_data_loader",
- "re_error",
  "re_format",
  "re_grpc_client",
  "re_log",
diff --git a/crates/store/re_data_source/Cargo.toml b/crates/store/re_data_source/Cargo.toml
index e3a9eff7e9b3..94727c82a9ca 100644
--- a/crates/store/re_data_source/Cargo.toml
+++ b/crates/store/re_data_source/Cargo.toml
@@ -24,7 +24,6 @@ default = []
 
 [dependencies]
 re_data_loader.workspace = true
-re_error.workspace = true
 re_format.workspace = true
 re_grpc_client.workspace = true
 re_log_channel.workspace = true
diff --git a/crates/store/re_data_source/src/data_source.rs b/crates/store/re_data_source/src/data_source.rs
index ba018764d448..daa142e03bda 100644
--- a/crates/store/re_data_source/src/data_source.rs
+++ b/crates/store/re_data_source/src/data_source.rs
@@ -341,7 +341,7 @@ impl LogDataSource {
                         if let Some(err) = err.as_client_credentials_error() {
                             on_auth_err(uri, err);
                         } else {
-                            re_log::warn!("Error while streaming: {}", re_error::format_ref(&err));
+                            re_log::error!("Error while streaming: {}", err);
                         }
                     }
                 });

From 5ac960404ffb3919353870a28e2e1cdf8d8d53d4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jochen=20G=C3=B6rtler?= 
Date: Mon, 16 Mar 2026 13:16:33 +0100
Subject: [PATCH 149/513] Fix `rerun rrd stats` reporting identical
 compressed/uncompressed sizes

### Related

* Closes https://github.com/rerun-io/rerun/issues/12693

### What

Title.

Source-Ref: db90bccc79cb07de0198a4815b4f065d119beb27
Co-authored-by: Claude 
---
 crates/top/rerun/src/commands/rrd/stats.rs | 30 ++++++++++++++--------
 1 file changed, 20 insertions(+), 10 deletions(-)

diff --git a/crates/top/rerun/src/commands/rrd/stats.rs b/crates/top/rerun/src/commands/rrd/stats.rs
index 71d0dd3c18fe..1dbae6618412 100644
--- a/crates/top/rerun/src/commands/rrd/stats.rs
+++ b/crates/top/rerun/src/commands/rrd/stats.rs
@@ -48,13 +48,16 @@ impl StatsCommand {
 
         let (rx_raw, _) = read_raw_rrd_streams_from_file_or_stdin(path_to_input_rrds);
 
-        let (tx_uncompressed, rx_uncompressed) = crossbeam::channel::bounded(100);
+        // Each message is accompanied by the original compressed payload size (in bytes).
+        // For uncompressed messages, this equals the payload size.
+        let (tx_uncompressed, rx_uncompressed) =
+            crossbeam::channel::bounded::<(u64, anyhow::Result)>(100);
         let decompress_thread_handle = std::thread::Builder::new()
             .name("decompress".to_owned())
             .spawn(move || {
                 for (_source, res) in rx_raw {
                     let Ok(Msg::ArrowMsg(mut msg)) = res else {
-                        send_crossbeam(&tx_uncompressed, res)?;
+                        send_crossbeam(&tx_uncompressed, (0, res))?;
                         continue;
                     };
 
@@ -64,6 +67,8 @@ impl StatsCommand {
                         re_protos::common::v1alpha1::Compression::None as _;
                     const COMPRESSION_LZ4: i32 = re_protos::common::v1alpha1::Compression::Lz4 as _;
 
+                    let compressed_size = msg.payload.len() as u64;
+
                     match msg.compression {
                         COMPRESSION_NONE => {}
 
@@ -82,7 +87,10 @@ impl StatsCommand {
 
                     send_crossbeam(
                         &tx_uncompressed,
-                        Ok(re_protos::log_msg::v1alpha1::log_msg::Msg::ArrowMsg(msg)),
+                        (
+                            compressed_size,
+                            Ok(re_protos::log_msg::v1alpha1::log_msg::Msg::ArrowMsg(msg)),
+                        ),
                     )?;
                 }
 
@@ -92,13 +100,13 @@ impl StatsCommand {
         re_log::info!("processing input…");
         let mut num_msgs = 0;
         let mut last_checkpoint = std::time::Instant::now();
-        for res in rx_uncompressed {
+        for (compressed_size, res) in rx_uncompressed {
             let mut is_success = true;
 
             match res {
                 Ok(msg) => {
                     num_msgs += 1;
-                    match compute_stats(!*no_decode, &msg) {
+                    match compute_stats(!*no_decode, compressed_size, &msg) {
                         Ok(Some(stats)) => {
                             num_chunks += 1;
 
@@ -348,13 +356,13 @@ struct ChunkStatsApplication {
     num_components: u64,
 }
 
-fn compute_stats(app: bool, msg: &Msg) -> anyhow::Result> {
+fn compute_stats(app: bool, compressed_size: u64, msg: &Msg) -> anyhow::Result> {
     if let Msg::ArrowMsg(arrow_msg) = msg {
         let re_protos::log_msg::v1alpha1::ArrowMsg {
             store_id: _,
             chunk_id: _,
             compression: _,
-            uncompressed_size,
+            uncompressed_size: _,
             encoding: _,
             payload,
             is_static: _,
@@ -458,14 +466,16 @@ fn compute_stats(app: bool, msg: &Msg) -> anyhow::Result> {
             None
         };
 
+        let ipc_size_bytes_uncompressed = payload.len() as u64;
         return Ok(Some(ChunkStats {
             app,
             transport: ChunkStatsTransport {
-                ipc_size_bytes_compressed: payload.len() as _,
-                ipc_size_bytes_uncompressed: *uncompressed_size,
+                ipc_size_bytes_compressed: compressed_size,
+                ipc_size_bytes_uncompressed,
 
                 ipc_schema_size_bytes,
-                ipc_data_size_bytes: *uncompressed_size - ipc_schema_size_bytes,
+                ipc_data_size_bytes: ipc_size_bytes_uncompressed
+                    .saturating_sub(ipc_schema_size_bytes),
             },
         }));
     }

From 4ab863a68a0225f443a91d8c79b49b2cd1583480 Mon Sep 17 00:00:00 2001
From: Antoine Beyeler <49431240+abey79@users.noreply.github.com>
Date: Mon, 16 Mar 2026 14:34:56 +0100
Subject: [PATCH 150/513] Add `save()` to `Recording`

### What

Another baby step towards making `Recording` more useful. This PR has
the ability to `save()` a `Recording` as a file. To do so, `Recording`
now contains a `StoreInfo`, so the round trip from `load_recording()`
isn't lossy.

Source-Ref: 6acee8e2b9c7d1d6bf4dfbd5f22c4e28e06787dc
---
 rerun_py/rerun_bindings/rerun_bindings.pyi    |   1 +
 .../rerun_sdk/rerun/recording/_recording.py   |   6 ++
 rerun_py/src/catalog/dataset_entry.rs         |   5 +-
 rerun_py/src/recording/rrd.rs                 | 100 ++++++++++++++++--
 rerun_py/tests/unit/test_recording.py         |  69 ++++++++++++
 5 files changed, 171 insertions(+), 10 deletions(-)

diff --git a/rerun_py/rerun_bindings/rerun_bindings.pyi b/rerun_py/rerun_bindings/rerun_bindings.pyi
index 97ff4f7c6bd1..4385504ca611 100644
--- a/rerun_py/rerun_bindings/rerun_bindings.pyi
+++ b/rerun_py/rerun_bindings/rerun_bindings.pyi
@@ -191,6 +191,7 @@ class RecordingInternal:
     def schema(self) -> SchemaInternal: ...
     def recording_id(self) -> str: ...
     def application_id(self) -> str: ...
+    def save(self, path: str) -> None: ...
 
 class RRDArchiveInternal:
     def num_recordings(self) -> int: ...
diff --git a/rerun_py/rerun_sdk/rerun/recording/_recording.py b/rerun_py/rerun_sdk/rerun/recording/_recording.py
index c5c460901820..0feb03d74d0c 100644
--- a/rerun_py/rerun_sdk/rerun/recording/_recording.py
+++ b/rerun_py/rerun_sdk/rerun/recording/_recording.py
@@ -3,6 +3,8 @@
 from typing import TYPE_CHECKING
 
 if TYPE_CHECKING:
+    from pathlib import Path
+
     from rerun.catalog import Schema
     from rerun_bindings import RecordingInternal, RRDArchiveInternal
 
@@ -39,6 +41,10 @@ def application_id(self) -> str:
         """The application ID of the recording."""
         return self._internal.application_id()
 
+    def save(self, path: str | Path) -> None:
+        """Save this recording to an RRD file."""
+        self._internal.save(str(path))
+
 
 class RRDArchive:
     """
diff --git a/rerun_py/src/catalog/dataset_entry.rs b/rerun_py/src/catalog/dataset_entry.rs
index 86bd79dd80cb..ade40e7680a3 100644
--- a/rerun_py/src/catalog/dataset_entry.rs
+++ b/rerun_py/src/catalog/dataset_entry.rs
@@ -490,7 +490,10 @@ impl PyDatasetEntryInternal {
 
         let handle = ChunkStoreHandle::new(store?);
 
-        Ok(PyRecordingInternal { store: handle })
+        Ok(PyRecordingInternal {
+            store: handle,
+            store_info: None,
+        })
     }
 
     // TODO(RR-2824): we should have a generic `create_index(PyIndexConfig)`
diff --git a/rerun_py/src/recording/rrd.rs b/rerun_py/src/recording/rrd.rs
index 27bb2a1076b8..1eed7d9b8b0d 100644
--- a/rerun_py/src/recording/rrd.rs
+++ b/rerun_py/src/recording/rrd.rs
@@ -1,10 +1,12 @@
 use std::collections::BTreeMap;
+use std::sync::Arc;
 
 use pyo3::exceptions::{PyRuntimeError, PyValueError};
 use pyo3::{PyResult, pyclass, pyfunction, pymethods};
 
+use re_chunk::Chunk;
 use re_chunk_store::{ChunkStore, ChunkStoreConfig, ChunkStoreHandle};
-use re_log_types::StoreId;
+use re_log_types::{LogMsg, SetStoreInfo, StoreId, StoreInfo, StoreSource};
 
 use crate::catalog::PySchemaInternal;
 
@@ -18,7 +20,7 @@ use crate::catalog::PySchemaInternal;
 )]
 #[derive(Clone)]
 pub struct PyRRDArchiveInternal {
-    pub datasets: BTreeMap,
+    pub datasets: BTreeMap)>,
 }
 
 #[pymethods] // NOLINT: ignore[py-mthd-str]
@@ -37,8 +39,9 @@ impl PyRRDArchiveInternal {
         self.datasets
             .iter()
             .filter(|(id, _)| id.is_recording())
-            .map(|(_, store)| PyRecordingInternal {
+            .map(|(_, (store, store_info))| PyRecordingInternal {
                 store: store.clone(),
+                store_info: store_info.clone(),
             })
             .collect()
     }
@@ -56,6 +59,7 @@ impl PyRRDArchiveInternal {
 #[pyclass(name = "RecordingInternal", module = "rerun_bindings.rerun_bindings")] // NOLINT: ignore[py-cls-eq] non-trivial implementation
 pub struct PyRecordingInternal {
     pub(crate) store: ChunkStoreHandle,
+    pub(crate) store_info: Option,
 }
 
 #[pymethods] // NOLINT: ignore[py-mthd-str]
@@ -77,6 +81,52 @@ impl PyRecordingInternal {
     fn application_id(&self) -> String {
         self.store.read().id().application_id().to_string()
     }
+
+    /// Save this recording to an RRD file.
+    #[expect(clippy::needless_pass_by_value)]
+    fn save(&self, path: std::path::PathBuf) -> PyResult<()> {
+        let store = self.store.read();
+        let store_id = store.id().clone();
+
+        let info = self.store_info.clone().unwrap_or_else(|| {
+            StoreInfo::new(
+                store_id.clone(),
+                StoreSource::Other("rerun-sdk-python".into()),
+            )
+        });
+
+        let file =
+            std::fs::File::create(&path).map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
+
+        let mut encoder = re_log_encoding::Encoder::new_eager(
+            re_build_info::CrateVersion::LOCAL,
+            re_log_encoding::EncodingOptions::PROTOBUF_COMPRESSED,
+            file,
+        )
+        .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
+
+        encoder
+            .append(&LogMsg::SetStoreInfo(SetStoreInfo {
+                row_id: re_tuid::Tuid::new(),
+                info,
+            }))
+            .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
+
+        for chunk in store.iter_physical_chunks() {
+            let arrow_msg = chunk
+                .to_arrow_msg()
+                .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
+            encoder
+                .append(&LogMsg::ArrowMsg(store_id.clone(), arrow_msg))
+                .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
+        }
+
+        encoder
+            .finish()
+            .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
+
+        Ok(())
+    }
 }
 
 /// Load a single recording from an RRD file.
@@ -103,14 +153,46 @@ pub fn load_recording(path_to_rrd: std::path::PathBuf) -> PyResult PyResult {
-    let stores = ChunkStore::from_rrd_filepath(&ChunkStoreConfig::DEFAULT, path_to_rrd)
-        .map_err(|err| PyRuntimeError::new_err(err.to_string()))?
+    let rrd_file = std::fs::File::open(&path_to_rrd)
+        .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
+    let decoder = re_log_encoding::Decoder::decode_eager(std::io::BufReader::new(rrd_file))
+        .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
+
+    let mut stores: BTreeMap = BTreeMap::new();
+    let mut store_infos: BTreeMap = BTreeMap::new();
+
+    for msg_result in decoder {
+        let msg = msg_result.map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
+        match msg {
+            LogMsg::SetStoreInfo(set_store_info) => {
+                let info = set_store_info.info;
+                stores.entry(info.store_id.clone()).or_insert_with(|| {
+                    ChunkStore::new(info.store_id.clone(), ChunkStoreConfig::DEFAULT)
+                });
+                store_infos.insert(info.store_id.clone(), info);
+            }
+            LogMsg::ArrowMsg(store_id, arrow_msg) => {
+                let chunk = Chunk::from_arrow_msg(&arrow_msg)
+                    .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
+                if let Some(store) = stores.get_mut(&store_id) {
+                    store
+                        .insert_chunk(&Arc::new(chunk))
+                        .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
+                }
+            }
+            LogMsg::BlueprintActivationCommand(_) => {}
+        }
+    }
+
+    let datasets = stores
         .into_iter()
-        .map(|(store_id, store)| (store_id, ChunkStoreHandle::new(store)))
+        .map(|(store_id, store)| {
+            let info = store_infos.remove(&store_id);
+            (store_id, (ChunkStoreHandle::new(store), info))
+        })
         .collect();
 
-    let archive = PyRRDArchiveInternal { datasets: stores };
-
-    Ok(archive)
+    Ok(PyRRDArchiveInternal { datasets })
 }
diff --git a/rerun_py/tests/unit/test_recording.py b/rerun_py/tests/unit/test_recording.py
index 39178ad39bce..c0321703b893 100644
--- a/rerun_py/tests/unit/test_recording.py
+++ b/rerun_py/tests/unit/test_recording.py
@@ -3,6 +3,7 @@
 from __future__ import annotations
 
 import pathlib
+import subprocess
 import uuid
 from typing import TYPE_CHECKING
 
@@ -280,3 +281,71 @@ def test_load_recording_path_types(tmp_path: pathlib.Path) -> None:
     # Test with Path object
     recording = rr.recording.load_recording(pathlib.Path(tmp_path) / "tmp.rrd")
     assert recording is not None
+
+
+def test_save_roundtrip(tmp_path: pathlib.Path) -> None:
+    """Test that save() produces an RRD that preserves metadata and schema."""
+
+    original_rrd = tmp_path / "original.rrd"
+    roundtrip_rrd = tmp_path / "roundtrip.rrd"
+
+    expected_recording_id = uuid.uuid4()
+
+    with rr.RecordingStream(APP_ID, recording_id=expected_recording_id) as rec:
+        rec.save(original_rrd)
+        rec.set_time("my_index", sequence=1)
+        rec.log("points", rr.Points3D([[1, 2, 3], [4, 5, 6]]))
+        rec.set_time("my_index", sequence=2)
+        rec.log("points", rr.Points3D([[7, 8, 9]], colors=[[255, 0, 0]]))
+        rec.log("static_text", rr.TextLog("Hello"), static=True)
+
+    recording = rr.recording.load_recording(original_rrd)
+    recording.save(roundtrip_rrd)
+
+    # Load the roundtripped recording and verify metadata is preserved
+    roundtripped = rr.recording.load_recording(roundtrip_rrd)
+
+    assert roundtripped.application_id() == APP_ID
+    assert roundtripped.recording_id() == str(expected_recording_id)
+
+    # Verify schema is preserved
+    original_schema = recording.schema()
+    roundtrip_schema = roundtripped.schema()
+
+    assert str(original_schema) == str(roundtrip_schema)
+
+
+def test_save_roundtrip_compare(tmp_path: pathlib.Path) -> None:
+    """Test that compacting then roundtripping produces an identical RRD."""
+
+    original_rrd = tmp_path / "original.rrd"
+    compacted_rrd = tmp_path / "compacted.rrd"
+    roundtrip_rrd = tmp_path / "roundtrip.rrd"
+
+    with rr.RecordingStream(APP_ID, recording_id=uuid.uuid4()) as rec:
+        rec.save(original_rrd)
+        rec.set_time("my_index", sequence=1)
+        rec.log("points", rr.Points3D([[1, 2, 3]]))
+        rec.log("static_text", rr.TextLog("Hello"), static=True)
+
+    # Compact the original so chunk boundaries match what ChunkStore produces
+    process = subprocess.run(
+        ["rerun", "rrd", "compact", str(original_rrd), "-o", str(compacted_rrd)],
+        check=False,
+        capture_output=True,
+    )
+    assert process.returncode == 0, f"RRD compact failed: {process.stderr.decode('utf-8')}"
+
+    # Roundtrip via load + save
+    rr.recording.load_recording(compacted_rrd).save(roundtrip_rrd)
+
+    # Compare compacted vs roundtripped
+    process = subprocess.run(
+        ["rerun", "rrd", "compare", "--unordered", str(compacted_rrd), str(roundtrip_rrd)],
+        check=False,
+        capture_output=True,
+    )
+    if process.returncode != 0:
+        print(process.stdout.decode("utf-8"))
+        print(process.stderr.decode("utf-8"))
+    assert process.returncode == 0, f"RRD compare failed: {process.stderr.decode('utf-8')}"

From be5a50ff0e7c2bd92db47cf38d16d4c3ff960b21 Mon Sep 17 00:00:00 2001
From: Andreas Reich 
Date: Mon, 16 Mar 2026 15:22:40 +0100
Subject: [PATCH 151/513] Add cull mode for front/back face culling on `Mesh3D`
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

### Related

* Fixes https://github.com/rerun-io/rerun/issues/1741

### What

This adds the ability to triangle meshes to cull back or front faces. We
keep no culling as a default since it's the safe bet for a "visualize
anything" renderer as ours.

https://github.com/user-attachments/assets/dfbb326a-eca0-4144-82d3-36034cecb662

We agreed that CCW (counter clock wise) triangle winding order is the
most common variant and not spelling all that out makes for better UX.
The alternative proposal was to have a separate enum that first defines
what a front face is (CW or CCW) and then another one to determine what
to cull (nothing/back/front).

Ironically, the ARKit example that we have where this setting obviously
makes sense uses CW triangle winding order which is why you tell it
“remove back faces” when you mean “remove front faces”. Meh! But fine I
guess :shrug:

---------

Source-Ref: cc644e1dae1d5e8a3e5d4ebfebf4032278857a12
Co-authored-by: Claude Opus 4.6 
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
---
 .../definitions/rerun/archetypes/mesh3d.fbs   |  10 +-
 .../definitions/rerun/components.fbs          |   1 +
 .../rerun/components/mesh_face_rendering.fbs  |  25 ++
 .../re_sdk_types/src/archetypes/mesh3d.rs     |  77 ++++-
 .../src/components/.gitattributes             |   1 +
 .../src/components/mesh_face_rendering.rs     | 171 +++++++++++
 .../src/components/mesh_face_rendering_ext.rs |  21 ++
 .../store/re_sdk_types/src/components/mod.rs  |   3 +
 .../store/re_sdk_types/src/reflection/mod.rs  |  18 ++
 .../store/re_sdk_types/tests/types/mesh3d.rs  |   1 +
 crates/viewer/re_component_ui/src/lib.rs      |   7 +-
 .../MeshFaceRendering_placeholder.png         |   3 +
 .../MeshFaceRendering_placeholder.png         |   3 +
 .../re_renderer/src/importer/cpu_model.rs     |   1 +
 .../re_renderer/src/renderer/mesh_renderer.rs | 271 ++++++++++++++----
 .../viewer/re_renderer_examples/multiview.rs  |   1 +
 .../viewer/re_renderer_examples/outlines.rs   |   1 +
 crates/viewer/re_renderer_examples/picking.rs |   1 +
 .../src/visualizers/assets3d.rs               |   2 +
 .../re_view_spatial/src/visualizers/meshes.rs |  24 +-
 .../visualizers/utilities/proc_mesh_vis.rs    |   5 +
 .../tests/mesh_face_rendering.rs              | 139 +++++++++
 .../mesh_face_rendering_back_opaque.png       |   3 +
 .../mesh_face_rendering_back_transparent.png  |   3 +
 ...esh_face_rendering_double_sided_opaque.png |   3 +
 ...ace_rendering_double_sided_transparent.png |   3 +
 .../mesh_face_rendering_front_opaque.png      |   3 +
 .../mesh_face_rendering_front_transparent.png |   3 +
 ...arch_fallback_rerun.archetypes.Mesh3D.snap |   1 +
 .../reference/types/archetypes/mesh3d.md      |   6 +-
 docs/content/reference/types/components.md    |   1 +
 .../reference/types/components/.gitattributes |   1 +
 .../types/components/mesh_face_rendering.md   |  39 +++
 .../arkit_scenes/arkit_scenes/__main__.py     |   1 +
 rerun_cpp/src/rerun/archetypes/mesh3d.cpp     |  16 +-
 rerun_cpp/src/rerun/archetypes/mesh3d.hpp     |  39 ++-
 rerun_cpp/src/rerun/components.hpp            |   1 +
 rerun_cpp/src/rerun/components/.gitattributes |   2 +
 .../rerun/components/mesh_face_rendering.cpp  |  58 ++++
 .../rerun/components/mesh_face_rendering.hpp  |  67 +++++
 rerun_py/rerun_sdk/rerun/archetypes/mesh3d.py |  30 +-
 .../rerun_sdk/rerun/archetypes/mesh3d_ext.py  |   7 +-
 .../rerun_sdk/rerun/components/.gitattributes |   1 +
 .../rerun_sdk/rerun/components/__init__.py    |  10 +
 .../rerun/components/mesh_face_rendering.py   |  95 ++++++
 45 files changed, 1100 insertions(+), 79 deletions(-)
 create mode 100644 crates/store/re_sdk_types/definitions/rerun/components/mesh_face_rendering.fbs
 create mode 100644 crates/store/re_sdk_types/src/components/mesh_face_rendering.rs
 create mode 100644 crates/store/re_sdk_types/src/components/mesh_face_rendering_ext.rs
 create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/MeshFaceRendering_placeholder.png
 create mode 100644 crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/MeshFaceRendering_placeholder.png
 create mode 100644 crates/viewer/re_view_spatial/tests/mesh_face_rendering.rs
 create mode 100644 crates/viewer/re_view_spatial/tests/snapshots/mesh_face_rendering_back_opaque.png
 create mode 100644 crates/viewer/re_view_spatial/tests/snapshots/mesh_face_rendering_back_transparent.png
 create mode 100644 crates/viewer/re_view_spatial/tests/snapshots/mesh_face_rendering_double_sided_opaque.png
 create mode 100644 crates/viewer/re_view_spatial/tests/snapshots/mesh_face_rendering_double_sided_transparent.png
 create mode 100644 crates/viewer/re_view_spatial/tests/snapshots/mesh_face_rendering_front_opaque.png
 create mode 100644 crates/viewer/re_view_spatial/tests/snapshots/mesh_face_rendering_front_transparent.png
 create mode 100644 docs/content/reference/types/components/mesh_face_rendering.md
 create mode 100644 rerun_cpp/src/rerun/components/mesh_face_rendering.cpp
 create mode 100644 rerun_cpp/src/rerun/components/mesh_face_rendering.hpp
 create mode 100644 rerun_py/rerun_sdk/rerun/components/mesh_face_rendering.py

diff --git a/crates/store/re_sdk_types/definitions/rerun/archetypes/mesh3d.fbs b/crates/store/re_sdk_types/definitions/rerun/archetypes/mesh3d.fbs
index 877ec5369b2e..13b3fe478fc3 100644
--- a/crates/store/re_sdk_types/definitions/rerun/archetypes/mesh3d.fbs
+++ b/crates/store/re_sdk_types/definitions/rerun/archetypes/mesh3d.fbs
@@ -9,8 +9,9 @@ namespace rerun.archetypes;
 /// If there are multiple [archetypes.InstancePoses3D] instances logged to the same entity as a mesh,
 /// an instance of the mesh will be drawn for each transform.
 ///
-/// The viewer draws meshes always two-sided. However, for transparency ordering
-/// front faces are assumed to those with counter clockwise triangle winding order (this is the same as in the GLTF specification).
+/// For transparency ordering, as well as back face culling (disabled by default),
+/// front faces are assumed to be those with counter clockwise triangle winding order
+/// (this is the same as in the GLTF specification).
 ///
 /// \example archetypes/mesh3d_indexed title="Simple indexed 3D mesh" image="https://static.rerun.io/mesh3d_indexed/57c70dc992e6dc0bd9c5222ca084f5b6240cea75/1200w.png"
 /// \example archetypes/mesh3d_instancing title="3D mesh with instancing" image="https://static.rerun.io/mesh3d_leaf_transforms3d/c2d0ee033129da53168f5705625a9b033f3a3d61/1200w.png"
@@ -52,6 +53,11 @@ table Mesh3D (
   /// Alpha channel governs the overall mesh transparency.
   albedo_factor: rerun.components.AlbedoFactor ("attr.rerun.component_optional", nullable, order: 3300);
 
+  /// Determines which faces of the mesh are rendered.
+  ///
+  /// The default is [components.MeshFaceRendering.DoubleSided], meaning both front and back faces are shown.
+  face_rendering: rerun.components.MeshFaceRendering ("attr.rerun.component_optional", nullable, order: 3350);
+
   /// Optional albedo texture.
   ///
   /// Used with the [components.Texcoord2D] of the mesh.
diff --git a/crates/store/re_sdk_types/definitions/rerun/components.fbs b/crates/store/re_sdk_types/definitions/rerun/components.fbs
index 2fde1bc92709..eadb626785b3 100644
--- a/crates/store/re_sdk_types/definitions/rerun/components.fbs
+++ b/crates/store/re_sdk_types/definitions/rerun/components.fbs
@@ -40,6 +40,7 @@ include "./components/magnification_filter.fbs";
 include "./components/marker_shape.fbs";
 include "./components/marker_size.fbs";
 include "./components/media_type.fbs";
+include "./components/mesh_face_rendering.fbs";
 include "./components/name.fbs";
 include "./components/opacity.fbs";
 include "./components/pinhole_projection.fbs";
diff --git a/crates/store/re_sdk_types/definitions/rerun/components/mesh_face_rendering.fbs b/crates/store/re_sdk_types/definitions/rerun/components/mesh_face_rendering.fbs
new file mode 100644
index 000000000000..000ffcc82895
--- /dev/null
+++ b/crates/store/re_sdk_types/definitions/rerun/components/mesh_face_rendering.fbs
@@ -0,0 +1,25 @@
+namespace rerun.components;
+
+/// Determines which faces of a mesh are rendered.
+///
+/// For this purpose, we assume that the winding order of vertices in a mesh is
+/// consistent and that front faces are defined as those with vertices in counter clockwise order.
+enum MeshFaceRendering: ubyte (
+    "attr.docs.unreleased"
+) {
+    /// Invalid value. Won't show up in generated types.
+    Invalid = 0,
+
+    /// Show both back and front faces.
+    DoubleSided(default),
+
+    /// Only front faces are shown.
+    ///
+    /// Front faces are assumed to have a counter clockwise vertex winding order on screen.
+    Front,
+
+    /// Only back faces are shown.
+    ///
+    /// Back faces are assumed to have a clockwise vertex winding order on screen.
+    Back,
+}
diff --git a/crates/store/re_sdk_types/src/archetypes/mesh3d.rs b/crates/store/re_sdk_types/src/archetypes/mesh3d.rs
index bde0e1406146..bc7b44ec682e 100644
--- a/crates/store/re_sdk_types/src/archetypes/mesh3d.rs
+++ b/crates/store/re_sdk_types/src/archetypes/mesh3d.rs
@@ -28,8 +28,9 @@ use ::re_types_core::{DeserializationError, DeserializationResult};
 /// If there are multiple [`archetypes::InstancePoses3D`][crate::archetypes::InstancePoses3D] instances logged to the same entity as a mesh,
 /// an instance of the mesh will be drawn for each transform.
 ///
-/// The viewer draws meshes always two-sided. However, for transparency ordering
-/// front faces are assumed to those with counter clockwise triangle winding order (this is the same as in the GLTF specification).
+/// For transparency ordering, as well as back face culling (disabled by default),
+/// front faces are assumed to be those with counter clockwise triangle winding order
+/// (this is the same as in the GLTF specification).
 ///
 /// ## Examples
 ///
@@ -138,6 +139,11 @@ pub struct Mesh3D {
     /// Alpha channel governs the overall mesh transparency.
     pub albedo_factor: Option,
 
+    /// Determines which faces of the mesh are rendered.
+    ///
+    /// The default is [`components::MeshFaceRendering::DoubleSided`][crate::components::MeshFaceRendering::DoubleSided], meaning both front and back faces are shown.
+    pub face_rendering: Option,
+
     /// Optional albedo texture.
     ///
     /// Used with the [`components::Texcoord2D`][crate::components::Texcoord2D] of the mesh.
@@ -230,6 +236,18 @@ impl Mesh3D {
         }
     }
 
+    /// Returns the [`ComponentDescriptor`] for [`Self::face_rendering`].
+    ///
+    /// The corresponding component is [`crate::components::MeshFaceRendering`].
+    #[inline]
+    pub fn descriptor_face_rendering() -> ComponentDescriptor {
+        ComponentDescriptor {
+            archetype: Some("rerun.archetypes.Mesh3D".into()),
+            component: "Mesh3D:face_rendering".into(),
+            component_type: Some("rerun.components.MeshFaceRendering".into()),
+        }
+    }
+
     /// Returns the [`ComponentDescriptor`] for [`Self::albedo_texture_buffer`].
     ///
     /// The corresponding component is [`crate::components::ImageBuffer`].
@@ -278,19 +296,20 @@ static RECOMMENDED_COMPONENTS: std::sync::LazyLock<[ComponentDescriptor; 2usize]
         ]
     });
 
-static OPTIONAL_COMPONENTS: std::sync::LazyLock<[ComponentDescriptor; 6usize]> =
+static OPTIONAL_COMPONENTS: std::sync::LazyLock<[ComponentDescriptor; 7usize]> =
     std::sync::LazyLock::new(|| {
         [
             Mesh3D::descriptor_vertex_colors(),
             Mesh3D::descriptor_vertex_texcoords(),
             Mesh3D::descriptor_albedo_factor(),
+            Mesh3D::descriptor_face_rendering(),
             Mesh3D::descriptor_albedo_texture_buffer(),
             Mesh3D::descriptor_albedo_texture_format(),
             Mesh3D::descriptor_class_ids(),
         ]
     });
 
-static ALL_COMPONENTS: std::sync::LazyLock<[ComponentDescriptor; 9usize]> =
+static ALL_COMPONENTS: std::sync::LazyLock<[ComponentDescriptor; 10usize]> =
     std::sync::LazyLock::new(|| {
         [
             Mesh3D::descriptor_vertex_positions(),
@@ -299,6 +318,7 @@ static ALL_COMPONENTS: std::sync::LazyLock<[ComponentDescriptor; 9usize]> =
             Mesh3D::descriptor_vertex_colors(),
             Mesh3D::descriptor_vertex_texcoords(),
             Mesh3D::descriptor_albedo_factor(),
+            Mesh3D::descriptor_face_rendering(),
             Mesh3D::descriptor_albedo_texture_buffer(),
             Mesh3D::descriptor_albedo_texture_format(),
             Mesh3D::descriptor_class_ids(),
@@ -306,8 +326,8 @@ static ALL_COMPONENTS: std::sync::LazyLock<[ComponentDescriptor; 9usize]> =
     });
 
 impl Mesh3D {
-    /// The total number of components in the archetype: 1 required, 2 recommended, 6 optional
-    pub const NUM_COMPONENTS: usize = 9usize;
+    /// The total number of components in the archetype: 1 required, 2 recommended, 7 optional
+    pub const NUM_COMPONENTS: usize = 10usize;
 }
 
 impl ::re_types_core::Archetype for Mesh3D {
@@ -378,6 +398,11 @@ impl ::re_types_core::Archetype for Mesh3D {
             .map(|array| {
                 SerializedComponentBatch::new(array.clone(), Self::descriptor_albedo_factor())
             });
+        let face_rendering = arrays_by_descr
+            .get(&Self::descriptor_face_rendering())
+            .map(|array| {
+                SerializedComponentBatch::new(array.clone(), Self::descriptor_face_rendering())
+            });
         let albedo_texture_buffer = arrays_by_descr
             .get(&Self::descriptor_albedo_texture_buffer())
             .map(|array| {
@@ -406,6 +431,7 @@ impl ::re_types_core::Archetype for Mesh3D {
             vertex_colors,
             vertex_texcoords,
             albedo_factor,
+            face_rendering,
             albedo_texture_buffer,
             albedo_texture_format,
             class_ids,
@@ -424,6 +450,7 @@ impl ::re_types_core::AsComponents for Mesh3D {
             self.vertex_colors.clone(),
             self.vertex_texcoords.clone(),
             self.albedo_factor.clone(),
+            self.face_rendering.clone(),
             self.albedo_texture_buffer.clone(),
             self.albedo_texture_format.clone(),
             self.class_ids.clone(),
@@ -459,6 +486,7 @@ impl Mesh3D {
             vertex_colors: None,
             vertex_texcoords: None,
             albedo_factor: None,
+            face_rendering: None,
             albedo_texture_buffer: None,
             albedo_texture_format: None,
             class_ids: None,
@@ -500,6 +528,10 @@ impl Mesh3D {
                 crate::components::AlbedoFactor::arrow_empty(),
                 Self::descriptor_albedo_factor(),
             )),
+            face_rendering: Some(SerializedComponentBatch::new(
+                crate::components::MeshFaceRendering::arrow_empty(),
+                Self::descriptor_face_rendering(),
+            )),
             albedo_texture_buffer: Some(SerializedComponentBatch::new(
                 crate::components::ImageBuffer::arrow_empty(),
                 Self::descriptor_albedo_texture_buffer(),
@@ -552,6 +584,9 @@ impl Mesh3D {
             self.albedo_factor
                 .map(|albedo_factor| albedo_factor.partitioned(_lengths.clone()))
                 .transpose()?,
+            self.face_rendering
+                .map(|face_rendering| face_rendering.partitioned(_lengths.clone()))
+                .transpose()?,
             self.albedo_texture_buffer
                 .map(|albedo_texture_buffer| albedo_texture_buffer.partitioned(_lengths.clone()))
                 .transpose()?,
@@ -579,6 +614,7 @@ impl Mesh3D {
         let len_vertex_colors = self.vertex_colors.as_ref().map(|b| b.array.len());
         let len_vertex_texcoords = self.vertex_texcoords.as_ref().map(|b| b.array.len());
         let len_albedo_factor = self.albedo_factor.as_ref().map(|b| b.array.len());
+        let len_face_rendering = self.face_rendering.as_ref().map(|b| b.array.len());
         let len_albedo_texture_buffer = self.albedo_texture_buffer.as_ref().map(|b| b.array.len());
         let len_albedo_texture_format = self.albedo_texture_format.as_ref().map(|b| b.array.len());
         let len_class_ids = self.class_ids.as_ref().map(|b| b.array.len());
@@ -589,6 +625,7 @@ impl Mesh3D {
             .or(len_vertex_colors)
             .or(len_vertex_texcoords)
             .or(len_albedo_factor)
+            .or(len_face_rendering)
             .or(len_albedo_texture_buffer)
             .or(len_albedo_texture_format)
             .or(len_class_ids)
@@ -679,6 +716,33 @@ impl Mesh3D {
         self
     }
 
+    /// Determines which faces of the mesh are rendered.
+    ///
+    /// The default is [`components::MeshFaceRendering::DoubleSided`][crate::components::MeshFaceRendering::DoubleSided], meaning both front and back faces are shown.
+    #[inline]
+    pub fn with_face_rendering(
+        mut self,
+        face_rendering: impl Into,
+    ) -> Self {
+        self.face_rendering =
+            try_serialize_field(Self::descriptor_face_rendering(), [face_rendering]);
+        self
+    }
+
+    /// This method makes it possible to pack multiple [`crate::components::MeshFaceRendering`] in a single component batch.
+    ///
+    /// This only makes sense when used in conjunction with [`Self::columns`]. [`Self::with_face_rendering`] should
+    /// be used when logging a single row's worth of data.
+    #[inline]
+    pub fn with_many_face_rendering(
+        mut self,
+        face_rendering: impl IntoIterator>,
+    ) -> Self {
+        self.face_rendering =
+            try_serialize_field(Self::descriptor_face_rendering(), face_rendering);
+        self
+    }
+
     /// Optional albedo texture.
     ///
     /// Used with the [`components::Texcoord2D`][crate::components::Texcoord2D] of the mesh.
@@ -766,6 +830,7 @@ impl ::re_byte_size::SizeBytes for Mesh3D {
             + self.vertex_colors.heap_size_bytes()
             + self.vertex_texcoords.heap_size_bytes()
             + self.albedo_factor.heap_size_bytes()
+            + self.face_rendering.heap_size_bytes()
             + self.albedo_texture_buffer.heap_size_bytes()
             + self.albedo_texture_format.heap_size_bytes()
             + self.class_ids.heap_size_bytes()
diff --git a/crates/store/re_sdk_types/src/components/.gitattributes b/crates/store/re_sdk_types/src/components/.gitattributes
index d78ebc1a802d..b112a14d1a7d 100644
--- a/crates/store/re_sdk_types/src/components/.gitattributes
+++ b/crates/store/re_sdk_types/src/components/.gitattributes
@@ -40,6 +40,7 @@ magnification_filter.rs linguist-generated=true
 marker_shape.rs linguist-generated=true
 marker_size.rs linguist-generated=true
 media_type.rs linguist-generated=true
+mesh_face_rendering.rs linguist-generated=true
 mod.rs linguist-generated=true
 name.rs linguist-generated=true
 opacity.rs linguist-generated=true
diff --git a/crates/store/re_sdk_types/src/components/mesh_face_rendering.rs b/crates/store/re_sdk_types/src/components/mesh_face_rendering.rs
new file mode 100644
index 000000000000..11b2bcee6b24
--- /dev/null
+++ b/crates/store/re_sdk_types/src/components/mesh_face_rendering.rs
@@ -0,0 +1,171 @@
+// DO NOT EDIT! This file was auto-generated by crates/build/re_types_builder/src/codegen/rust/api.rs
+// Based on "crates/store/re_sdk_types/definitions/rerun/components/mesh_face_rendering.fbs".
+
+#![allow(unused_braces)]
+#![allow(unused_imports)]
+#![allow(unused_parens)]
+#![allow(clippy::allow_attributes)]
+#![allow(clippy::clone_on_copy)]
+#![allow(clippy::cloned_instead_of_copied)]
+#![allow(clippy::map_flatten)]
+#![allow(clippy::needless_question_mark)]
+#![allow(clippy::new_without_default)]
+#![allow(clippy::redundant_closure)]
+#![allow(clippy::too_many_arguments)]
+#![allow(clippy::too_many_lines)]
+#![allow(clippy::wildcard_imports)]
+#![allow(non_camel_case_types)]
+
+use ::re_types_core::SerializationResult;
+use ::re_types_core::try_serialize_field;
+use ::re_types_core::{ComponentBatch as _, SerializedComponentBatch};
+use ::re_types_core::{ComponentDescriptor, ComponentType};
+use ::re_types_core::{DeserializationError, DeserializationResult};
+
+/// **Component**: Determines which faces of a mesh are rendered.
+///
+/// For this purpose, we assume that the winding order of vertices in a mesh is
+/// consistent and that front faces are defined as those with vertices in counter clockwise order.
+#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Default)]
+#[repr(u8)]
+pub enum MeshFaceRendering {
+    /// Show both back and front faces.
+    #[default]
+    DoubleSided = 1,
+
+    /// Only front faces are shown.
+    ///
+    /// Front faces are assumed to have a counter clockwise vertex winding order on screen.
+    Front = 2,
+
+    /// Only back faces are shown.
+    ///
+    /// Back faces are assumed to have a clockwise vertex winding order on screen.
+    Back = 3,
+}
+
+impl ::re_types_core::Component for MeshFaceRendering {
+    #[inline]
+    fn name() -> ComponentType {
+        "rerun.components.MeshFaceRendering".into()
+    }
+}
+
+::re_types_core::macros::impl_into_cow!(MeshFaceRendering);
+
+impl ::re_types_core::Loggable for MeshFaceRendering {
+    #[inline]
+    fn arrow_datatype() -> arrow::datatypes::DataType {
+        use arrow::datatypes::*;
+        DataType::UInt8
+    }
+
+    fn to_arrow_opt<'a>(
+        data: impl IntoIterator>>>,
+    ) -> SerializationResult
+    where
+        Self: Clone + 'a,
+    {
+        #![allow(clippy::manual_is_variant_and)]
+        use ::re_types_core::{Loggable as _, ResultExt as _, arrow_helpers::as_array_ref};
+        use arrow::{array::*, buffer::*, datatypes::*};
+        Ok({
+            let (somes, data0): (Vec<_>, Vec<_>) = data
+                .into_iter()
+                .map(|datum| {
+                    let datum: Option<::std::borrow::Cow<'a, Self>> = datum.map(Into::into);
+                    let datum = datum.map(|datum| *datum as u8);
+                    (datum.is_some(), datum)
+                })
+                .unzip();
+            let data0_validity: Option = {
+                let any_nones = somes.iter().any(|some| !*some);
+                any_nones.then(|| somes.into())
+            };
+            as_array_ref(PrimitiveArray::::new(
+                ScalarBuffer::from(
+                    data0
+                        .into_iter()
+                        .map(|v| v.unwrap_or_default())
+                        .collect::>(),
+                ),
+                data0_validity,
+            ))
+        })
+    }
+
+    fn from_arrow_opt(
+        arrow_data: &dyn arrow::array::Array,
+    ) -> DeserializationResult>>
+    where
+        Self: Sized,
+    {
+        use ::re_types_core::{Loggable as _, ResultExt as _, arrow_zip_validity::ZipValidity};
+        use arrow::{array::*, buffer::*, datatypes::*};
+        Ok(arrow_data
+            .as_any()
+            .downcast_ref::()
+            .ok_or_else(|| {
+                let expected = Self::arrow_datatype();
+                let actual = arrow_data.data_type().clone();
+                DeserializationError::datatype_mismatch(expected, actual)
+            })
+            .with_context("rerun.components.MeshFaceRendering#enum")?
+            .into_iter()
+            .map(|typ| match typ {
+                Some(1) => Ok(Some(Self::DoubleSided)),
+                Some(2) => Ok(Some(Self::Front)),
+                Some(3) => Ok(Some(Self::Back)),
+                None => Ok(None),
+                Some(invalid) => Err(DeserializationError::missing_union_arm(
+                    Self::arrow_datatype(),
+                    "",
+                    invalid as _,
+                )),
+            })
+            .collect::>>>()
+            .with_context("rerun.components.MeshFaceRendering")?)
+    }
+}
+
+impl std::fmt::Display for MeshFaceRendering {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        match self {
+            Self::DoubleSided => write!(f, "DoubleSided"),
+            Self::Front => write!(f, "Front"),
+            Self::Back => write!(f, "Back"),
+        }
+    }
+}
+
+impl ::re_types_core::reflection::Enum for MeshFaceRendering {
+    #[inline]
+    fn variants() -> &'static [Self] {
+        &[Self::DoubleSided, Self::Front, Self::Back]
+    }
+
+    #[inline]
+    fn docstring_md(self) -> &'static str {
+        match self {
+            Self::DoubleSided => "Show both back and front faces.",
+            Self::Front => {
+                "Only front faces are shown.\n\nFront faces are assumed to have a counter clockwise vertex winding order on screen."
+            }
+            Self::Back => {
+                "Only back faces are shown.\n\nBack faces are assumed to have a clockwise vertex winding order on screen."
+            }
+        }
+    }
+}
+
+impl ::re_byte_size::SizeBytes for MeshFaceRendering {
+    #[inline]
+    fn heap_size_bytes(&self) -> u64 {
+        0
+    }
+
+    #[inline]
+    fn is_pod() -> bool {
+        true
+    }
+}
diff --git a/crates/store/re_sdk_types/src/components/mesh_face_rendering_ext.rs b/crates/store/re_sdk_types/src/components/mesh_face_rendering_ext.rs
new file mode 100644
index 000000000000..9d2cfa338b3d
--- /dev/null
+++ b/crates/store/re_sdk_types/src/components/mesh_face_rendering_ext.rs
@@ -0,0 +1,21 @@
+use super::MeshFaceRendering;
+
+impl MeshFaceRendering {
+    /// Instantiate a new [`MeshFaceRendering`] from a u8 value.
+    ///
+    /// Returns `None` if the value doesn't match any of the enum's arms.
+    pub fn from_u8(value: u8) -> Option {
+        // NOTE: This code will be optimized out, it's only here to make sure this method fails to
+        // compile if the enum is modified.
+        match Self::default() {
+            Self::DoubleSided | Self::Front | Self::Back => {}
+        }
+
+        match value {
+            v if v == Self::DoubleSided as u8 => Some(Self::DoubleSided),
+            v if v == Self::Front as u8 => Some(Self::Front),
+            v if v == Self::Back as u8 => Some(Self::Back),
+            _ => None,
+        }
+    }
+}
diff --git a/crates/store/re_sdk_types/src/components/mod.rs b/crates/store/re_sdk_types/src/components/mod.rs
index 84f077daa483..f0762cf3b4ed 100644
--- a/crates/store/re_sdk_types/src/components/mod.rs
+++ b/crates/store/re_sdk_types/src/components/mod.rs
@@ -65,6 +65,8 @@ mod marker_size;
 mod marker_size_ext;
 mod media_type;
 mod media_type_ext;
+mod mesh_face_rendering;
+mod mesh_face_rendering_ext;
 mod name;
 mod name_ext;
 mod opacity;
@@ -173,6 +175,7 @@ pub use self::magnification_filter::MagnificationFilter;
 pub use self::marker_shape::MarkerShape;
 pub use self::marker_size::MarkerSize;
 pub use self::media_type::MediaType;
+pub use self::mesh_face_rendering::MeshFaceRendering;
 pub use self::name::Name;
 pub use self::opacity::Opacity;
 pub use self::pinhole_projection::PinholeProjection;
diff --git a/crates/store/re_sdk_types/src/reflection/mod.rs b/crates/store/re_sdk_types/src/reflection/mod.rs
index 0639c1d431b6..e6ca741947b1 100644
--- a/crates/store/re_sdk_types/src/reflection/mod.rs
+++ b/crates/store/re_sdk_types/src/reflection/mod.rs
@@ -1063,6 +1063,17 @@ fn generate_component_reflection() -> Result::name(),
+            ComponentReflection {
+                docstring_md: "Determines which faces of a mesh are rendered.\n\nFor this purpose, we assume that the winding order of vertices in a mesh is\nconsistent and that front faces are defined as those with vertices in counter clockwise order.",
+                deprecation_summary: None,
+                custom_placeholder: Some(MeshFaceRendering::default().to_arrow()?),
+                datatype: MeshFaceRendering::arrow_datatype(),
+                is_enum: true,
+                verify_arrow_array: MeshFaceRendering::verify_arrow_array,
+            },
+        ),
         (
             ::name(),
             ComponentReflection {
@@ -2881,6 +2892,13 @@ fn generate_archetype_reflection() -> ArchetypeReflectionMap {
                         docstring_md: "A color multiplier applied to the whole mesh.\n\nAlpha channel governs the overall mesh transparency.",
                         flags: ArchetypeFieldFlags::UI_EDITABLE,
                     },
+                    ArchetypeFieldReflection {
+                        name: "face_rendering",
+                        display_name: "Face rendering",
+                        component_type: "rerun.components.MeshFaceRendering".into(),
+                        docstring_md: "Determines which faces of the mesh are rendered.\n\nThe default is [`components.MeshFaceRendering#DoubleSided`](https://rerun.io/docs/reference/types/components/mesh_face_rendering?speculative-link), meaning both front and back faces are shown.",
+                        flags: ArchetypeFieldFlags::UI_EDITABLE,
+                    },
                     ArchetypeFieldReflection {
                         name: "albedo_texture_buffer",
                         display_name: "Albedo texture buffer",
diff --git a/crates/store/re_sdk_types/tests/types/mesh3d.rs b/crates/store/re_sdk_types/tests/types/mesh3d.rs
index f93416ddedb2..d4b9488e2a84 100644
--- a/crates/store/re_sdk_types/tests/types/mesh3d.rs
+++ b/crates/store/re_sdk_types/tests/types/mesh3d.rs
@@ -47,6 +47,7 @@ fn roundtrip() {
             ClassId::from(127), //
         ]
         .serialized(Mesh3D::descriptor_class_ids()),
+        face_rendering: None,
     };
 
     let arch = Mesh3D::new([[1.0, 2.0, 3.0], [10.0, 20.0, 30.0]])
diff --git a/crates/viewer/re_component_ui/src/lib.rs b/crates/viewer/re_component_ui/src/lib.rs
index 85f691aae978..ab33e2c96920 100644
--- a/crates/viewer/re_component_ui/src/lib.rs
+++ b/crates/viewer/re_component_ui/src/lib.rs
@@ -47,9 +47,9 @@ use re_sdk_types::blueprint::components::{
 use re_sdk_types::components::{
     AggregationPolicy, AlbedoFactor, AxisLength, Color, DepthMeter, DrawOrder, FillMode, FillRatio,
     GammaCorrection, GraphType, HalfSize3D, ImagePlaneDistance, InterpolationMode, Length,
-    LinearSpeed, MagnificationFilter, MarkerSize, Name, Opacity, Position2D, Position3D, Range1D,
-    Scale3D, ShowLabels, StrokeWidth, Text, Timestamp, TransformRelation, Translation3D,
-    ValueRange, Vector3D, VideoCodec, Visible,
+    LinearSpeed, MagnificationFilter, MarkerSize, MeshFaceRendering, Name, Opacity, Position2D,
+    Position3D, Range1D, Scale3D, ShowLabels, StrokeWidth, Text, Timestamp, TransformRelation,
+    Translation3D, ValueRange, Vector3D, VideoCodec, Visible,
 };
 use re_viewer_context::gpu_bridge::colormap_edit_or_view_ui;
 
@@ -128,6 +128,7 @@ pub fn create_component_ui_registry() -> re_viewer_context::ComponentUiRegistry
     registry.add_singleline_edit_or_view::(edit_view_enum);
     registry.add_singleline_edit_or_view::(edit_view_enum);
     registry.add_singleline_edit_or_view::(edit_view_enum);
+    registry.add_singleline_edit_or_view::(edit_view_enum);
     registry.add_singleline_edit_or_view::(edit_view_enum);
     registry.add_singleline_edit_or_view::(edit_view_enum);
     registry.add_singleline_edit_or_view::(edit_view_enum);
diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/MeshFaceRendering_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/MeshFaceRendering_placeholder.png
new file mode 100644
index 000000000000..337364cf47c1
--- /dev/null
+++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_narrow_dark/MeshFaceRendering_placeholder.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:a4f9aa932766601e758cba27e1bab3ace98e2836a8684ea706b2039514a371ed
+size 4045
diff --git a/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/MeshFaceRendering_placeholder.png b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/MeshFaceRendering_placeholder.png
new file mode 100644
index 000000000000..e1560337ddeb
--- /dev/null
+++ b/crates/viewer/re_component_ui/tests/snapshots/all_components_list_item_wide_light/MeshFaceRendering_placeholder.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:f2e76d7643db37d7436a68ffe10dd81e22577b7b683cc90afaaa222aa8eb8921
+size 4673
diff --git a/crates/viewer/re_renderer/src/importer/cpu_model.rs b/crates/viewer/re_renderer/src/importer/cpu_model.rs
index e7fb1c5d7394..e1bc2d0b9348 100644
--- a/crates/viewer/re_renderer/src/importer/cpu_model.rs
+++ b/crates/viewer/re_renderer/src/importer/cpu_model.rs
@@ -97,6 +97,7 @@ impl CpuModel {
                     additive_tint: Default::default(),
                     outline_mask_ids: Default::default(),
                     picking_layer_id: Default::default(),
+                    cull_mode: Default::default(),
                 })
             })
             .collect())
diff --git a/crates/viewer/re_renderer/src/renderer/mesh_renderer.rs b/crates/viewer/re_renderer/src/renderer/mesh_renderer.rs
index 643475bef568..203d9cb24b88 100644
--- a/crates/viewer/re_renderer/src/renderer/mesh_renderer.rs
+++ b/crates/viewer/re_renderer/src/renderer/mesh_renderer.rs
@@ -104,6 +104,10 @@ struct MeshBatch {
     /// This can only ever be true if [`Self::draw_phase`] is [`DrawPhase::Transparent`].
     has_transparent_tint: bool,
 
+    /// Controls face culling for this batch during opaque/transparent drawing.
+    /// `None` means no culling (show both faces), matching `wgpu::PrimitiveState::cull_mode`.
+    cull_mode: Option,
+
     /// Position of the batch in world space, used for distance sorting.
     position: glam::Vec3A,
 }
@@ -151,6 +155,10 @@ pub struct GpuMeshInstance {
 
     /// Picking layer id.
     pub picking_layer_id: PickingLayerId,
+
+    /// Controls face culling for this instance.
+    /// `None` means no culling (show both faces), matching `wgpu::PrimitiveState::cull_mode`.
+    pub cull_mode: Option,
 }
 
 impl GpuMeshInstance {
@@ -162,10 +170,48 @@ impl GpuMeshInstance {
             additive_tint: Color32::BLACK,
             outline_mask_ids: OutlineMaskPreference::NONE,
             picking_layer_id: PickingLayerId::default(),
+            cull_mode: None,
         }
     }
 }
 
+/// Batch key: all properties that interrupt instancing.
+/// Instances sharing the same batch key can be draw their individual meshes in a single instanced draw call.
+#[derive(Clone, Copy, PartialEq, Eq)]
+struct BatchKey {
+    mesh_ptr: *const GpuMesh,
+    cull_mode: Option,
+}
+
+impl PartialOrd for BatchKey {
+    fn partial_cmp(&self, other: &Self) -> Option {
+        Some(self.cmp(other))
+    }
+}
+
+impl Ord for BatchKey {
+    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
+        let Self {
+            mesh_ptr,
+            cull_mode,
+        } = self;
+
+        fn face_to_u32(face: Option) -> u32 {
+            match face {
+                // All things equal, draw back faces first, then no-culling and then front faces.
+                // (this is a _cull_ mode, so it specifies which faces to cull, not which to draw!)
+                Some(wgpu::Face::Front) => 0,
+                None => 1,
+                Some(wgpu::Face::Back) => 2,
+            }
+        }
+
+        mesh_ptr
+            .cmp(&other.mesh_ptr)
+            .then_with(|| face_to_u32(*cull_mode).cmp(&face_to_u32(other.cull_mode)))
+    }
+}
+
 impl MeshDrawData {
     /// Transforms and uploads mesh instance data to be consumed by gpu.
     ///
@@ -203,14 +249,15 @@ impl MeshDrawData {
         // NOTE: can't use HashMap here or we get undeterrministic rendering order.
         // See  for more.
         // Using a `BTreeMap` at least gives the same order every frame,
-        // but since it uses the pointer address as the key,
+        // but since it uses the pointer address as part of the key,
         // it will still change if we run the app multiple times.
-        let mut instances_by_mesh: BTreeMap<_, Vec<_>> = BTreeMap::new();
+        let mut instances_by_batch_key: BTreeMap> = BTreeMap::new();
         for instance in instances {
-            instances_by_mesh
-                // Use pointer equality, this is enough to determine if two instances use the same mesh.
-                // (different mesh allocations have different gpu buffers internally, so they are by this definition not equal)
-                .entry(Arc::as_ptr(&instance.gpu_mesh))
+            instances_by_batch_key
+                .entry(BatchKey {
+                    mesh_ptr: Arc::as_ptr(&instance.gpu_mesh),
+                    cull_mode: instance.cull_mode,
+                })
                 .or_insert_with(|| Vec::with_capacity(instances.len()))
                 .push((instance, EnumSet::::new())); // Draw phase is filled out later.
         }
@@ -227,7 +274,7 @@ impl MeshDrawData {
             )?;
 
             let mut num_processed_instances = 0;
-            for (_mesh_ptr, mut instances) in instances_by_mesh {
+            for (batch_key, mut instances) in instances_by_batch_key {
                 let Some(first_instance) = instances.first() else {
                     continue;
                 };
@@ -300,6 +347,7 @@ impl MeshDrawData {
                             instance_range: instance_idx..(instance_idx + 1),
                             draw_phase: DrawPhase::Transparent,
                             has_transparent_tint: !instance.additive_tint.is_opaque(),
+                            cull_mode: batch_key.cull_mode,
                             position: instance.world_from_mesh.transform_point3a(mesh_center),
                         });
                     }
@@ -321,6 +369,7 @@ impl MeshDrawData {
                                 instance_range: instance_start..(instance_start + num_instances),
                                 draw_phase: phase,
                                 has_transparent_tint: false,
+                                cull_mode: batch_key.cull_mode,
                                 // Ordering isn't super important, so for many instances just pick the first as representative.
                                 position: chunk[0].0.world_from_mesh.transform_point3a(mesh_center),
                             });
@@ -338,6 +387,7 @@ impl MeshDrawData {
                         ..(num_processed_instances + instances.len() as u32),
                     draw_phase: DrawPhase::PickingLayer,
                     has_transparent_tint: false,
+                    cull_mode: batch_key.cull_mode,
                     // Ordering isn't super important, so for many instances just pick the first as representative.
                     position: first_instance
                         .world_from_mesh
@@ -363,12 +413,20 @@ impl MeshDrawData {
 
 pub struct MeshRenderer {
     rp_shaded: GpuRenderPipelineHandle,
+    rp_shaded_cull_back: GpuRenderPipelineHandle,
+    rp_shaded_cull_front: GpuRenderPipelineHandle,
 
     rp_shaded_alpha_blended_cull_back: GpuRenderPipelineHandle,
     rp_shaded_alpha_blended_cull_front: GpuRenderPipelineHandle,
 
     rp_picking_layer: GpuRenderPipelineHandle,
+    rp_picking_layer_cull_back: GpuRenderPipelineHandle,
+    rp_picking_layer_cull_front: GpuRenderPipelineHandle,
+
     rp_outline_mask: GpuRenderPipelineHandle,
+    rp_outline_mask_cull_back: GpuRenderPipelineHandle,
+    rp_outline_mask_cull_front: GpuRenderPipelineHandle,
+
     pub bind_group_layout: GpuBindGroupLayoutHandle,
 }
 
@@ -423,13 +481,12 @@ impl Renderer for MeshRenderer {
             &include_shader_module!("../../shader/instanced_mesh.wgsl"),
         );
 
-        // TODO(#1741): Make this configurable.
-        // Use GLTF convention right now.
+        // We always assume counter-clockwise faces as front.
         let front_face = wgpu::FrontFace::Ccw;
 
         let primitive = wgpu::PrimitiveState {
             topology: wgpu::PrimitiveTopology::TriangleList,
-            cull_mode: None, //Some(wgpu::Face::Back), // TODO(#1741): Need to specify from outside if mesh is CW or CCW?
+            cull_mode: None,
             front_face,
             ..Default::default()
         };
@@ -453,6 +510,28 @@ impl Renderer for MeshRenderer {
             multisample: ViewBuilder::main_target_default_msaa_state(ctx.render_config(), false),
         };
         let rp_shaded = render_pipelines.get_or_create(ctx, &rp_shaded_desc);
+        let rp_shaded_cull_back = render_pipelines.get_or_create(
+            ctx,
+            &RenderPipelineDesc {
+                label: "MeshRenderer::rp_shaded_cull_back".into(),
+                primitive: wgpu::PrimitiveState {
+                    cull_mode: Some(wgpu::Face::Back),
+                    ..primitive
+                },
+                ..rp_shaded_desc.clone()
+            },
+        );
+        let rp_shaded_cull_front = render_pipelines.get_or_create(
+            ctx,
+            &RenderPipelineDesc {
+                label: "MeshRenderer::rp_shaded_cull_front".into(),
+                primitive: wgpu::PrimitiveState {
+                    cull_mode: Some(wgpu::Face::Front),
+                    ..primitive
+                },
+                ..rp_shaded_desc.clone()
+            },
+        );
 
         let rp_shaded_alpha_blended_cull_back_desc = RenderPipelineDesc {
             label: "MeshRenderer::rp_shaded_alpha_blended_front".into(),
@@ -464,7 +543,6 @@ impl Renderer for MeshRenderer {
             depth_stencil: Some(ViewBuilder::MAIN_TARGET_DEFAULT_DEPTH_STATE_NO_WRITE),
             primitive: wgpu::PrimitiveState {
                 cull_mode: Some(wgpu::Face::Back),
-                front_face,
                 ..primitive
             },
             ..rp_shaded_desc.clone()
@@ -482,35 +560,82 @@ impl Renderer for MeshRenderer {
         let rp_shaded_alpha_blended_cull_front =
             render_pipelines.get_or_create(ctx, &rp_shaded_alpha_blended_cull_front_desc);
 
-        let rp_picking_layer = render_pipelines.get_or_create(
+        let rp_picking_layer_desc = RenderPipelineDesc {
+            label: "MeshRenderer::rp_picking_layer".into(),
+            fragment_entrypoint: "fs_main_picking_layer".into(),
+            render_targets: smallvec![Some(PickingLayerProcessor::PICKING_LAYER_FORMAT.into())],
+            depth_stencil: PickingLayerProcessor::PICKING_LAYER_DEPTH_STATE,
+            multisample: PickingLayerProcessor::PICKING_LAYER_MSAA_STATE,
+            ..rp_shaded_desc.clone()
+        };
+        let rp_picking_layer = render_pipelines.get_or_create(ctx, &rp_picking_layer_desc);
+        let rp_picking_layer_cull_back = render_pipelines.get_or_create(
             ctx,
             &RenderPipelineDesc {
-                label: "MeshRenderer::rp_picking_layer".into(),
-                fragment_entrypoint: "fs_main_picking_layer".into(),
-                render_targets: smallvec![Some(PickingLayerProcessor::PICKING_LAYER_FORMAT.into())],
-                depth_stencil: PickingLayerProcessor::PICKING_LAYER_DEPTH_STATE,
-                multisample: PickingLayerProcessor::PICKING_LAYER_MSAA_STATE,
-                ..rp_shaded_desc.clone()
+                label: "MeshRenderer::rp_picking_layer_cull_back".into(),
+                primitive: wgpu::PrimitiveState {
+                    cull_mode: Some(wgpu::Face::Back),
+                    ..primitive
+                },
+                ..rp_picking_layer_desc.clone()
+            },
+        );
+        let rp_picking_layer_cull_front = render_pipelines.get_or_create(
+            ctx,
+            &RenderPipelineDesc {
+                label: "MeshRenderer::rp_picking_layer_cull_front".into(),
+                primitive: wgpu::PrimitiveState {
+                    cull_mode: Some(wgpu::Face::Front),
+                    ..primitive
+                },
+                ..rp_picking_layer_desc
+            },
+        );
+
+        let rp_outline_mask_desc = RenderPipelineDesc {
+            label: "MeshRenderer::rp_outline_mask".into(),
+            fragment_entrypoint: "fs_main_outline_mask".into(),
+            render_targets: smallvec![Some(OutlineMaskProcessor::MASK_FORMAT.into())],
+            depth_stencil: OutlineMaskProcessor::MASK_DEPTH_STATE,
+            multisample: OutlineMaskProcessor::mask_default_msaa_state(ctx.device_caps().tier),
+            ..rp_shaded_desc
+        };
+        let rp_outline_mask = render_pipelines.get_or_create(ctx, &rp_outline_mask_desc);
+        let rp_outline_mask_cull_back = render_pipelines.get_or_create(
+            ctx,
+            &RenderPipelineDesc {
+                label: "MeshRenderer::rp_outline_mask_cull_back".into(),
+                primitive: wgpu::PrimitiveState {
+                    cull_mode: Some(wgpu::Face::Back),
+                    ..primitive
+                },
+                ..rp_outline_mask_desc.clone()
             },
         );
-        let rp_outline_mask = render_pipelines.get_or_create(
+        let rp_outline_mask_cull_front = render_pipelines.get_or_create(
             ctx,
             &RenderPipelineDesc {
-                label: "MeshRenderer::rp_outline_mask".into(),
-                fragment_entrypoint: "fs_main_outline_mask".into(),
-                render_targets: smallvec![Some(OutlineMaskProcessor::MASK_FORMAT.into())],
-                depth_stencil: OutlineMaskProcessor::MASK_DEPTH_STATE,
-                multisample: OutlineMaskProcessor::mask_default_msaa_state(ctx.device_caps().tier),
-                ..rp_shaded_desc
+                label: "MeshRenderer::rp_outline_mask_cull_front".into(),
+                primitive: wgpu::PrimitiveState {
+                    cull_mode: Some(wgpu::Face::Front),
+                    ..primitive
+                },
+                ..rp_outline_mask_desc
             },
         );
 
         Self {
             rp_shaded,
+            rp_shaded_cull_back,
+            rp_shaded_cull_front,
             rp_shaded_alpha_blended_cull_back,
             rp_shaded_alpha_blended_cull_front,
             rp_picking_layer,
+            rp_picking_layer_cull_back,
+            rp_picking_layer_cull_front,
             rp_outline_mask,
+            rp_outline_mask_cull_back,
+            rp_outline_mask_cull_front,
             bind_group_layout,
         }
     }
@@ -524,15 +649,12 @@ impl Renderer for MeshRenderer {
     ) -> Result<(), DrawError> {
         re_tracing::profile_function!();
 
-        let pipeline_handle = match phase {
-            DrawPhase::OutlineMask => Some(self.rp_outline_mask),
-            DrawPhase::Opaque => Some(self.rp_shaded),
-            DrawPhase::PickingLayer => Some(self.rp_picking_layer),
-            DrawPhase::Transparent => None, // Handled later since we have to switch back and forth between front & back face culling.
+        match phase {
+            DrawPhase::Opaque
+            | DrawPhase::Transparent
+            | DrawPhase::PickingLayer
+            | DrawPhase::OutlineMask => {}
             _ => unreachable!("We were called on a phase we weren't subscribed to: {phase:?}"),
-        };
-        if let Some(pipeline_handle) = pipeline_handle {
-            pass.set_pipeline(render_pipelines.get(pipeline_handle)?);
         }
 
         for DrawInstruction {
@@ -576,6 +698,32 @@ impl Renderer for MeshRenderer {
                     wgpu::IndexFormat::Uint32,
                 );
 
+                // Set per-batch pipeline based on cull mode.
+                // For the transparent phase this is done per-material below.
+                if phase != DrawPhase::Transparent {
+                    let pipeline = match (phase, mesh_batch.cull_mode) {
+                        (DrawPhase::Opaque, None) => self.rp_shaded,
+                        (DrawPhase::Opaque, Some(wgpu::Face::Back)) => self.rp_shaded_cull_back,
+                        (DrawPhase::Opaque, Some(wgpu::Face::Front)) => self.rp_shaded_cull_front,
+                        (DrawPhase::PickingLayer, None) => self.rp_picking_layer,
+                        (DrawPhase::PickingLayer, Some(wgpu::Face::Back)) => {
+                            self.rp_picking_layer_cull_back
+                        }
+                        (DrawPhase::PickingLayer, Some(wgpu::Face::Front)) => {
+                            self.rp_picking_layer_cull_front
+                        }
+                        (DrawPhase::OutlineMask, None) => self.rp_outline_mask,
+                        (DrawPhase::OutlineMask, Some(wgpu::Face::Back)) => {
+                            self.rp_outline_mask_cull_back
+                        }
+                        (DrawPhase::OutlineMask, Some(wgpu::Face::Front)) => {
+                            self.rp_outline_mask_cull_front
+                        }
+                        _ => unreachable!(),
+                    };
+                    pass.set_pipeline(render_pipelines.get(pipeline)?);
+                }
+
                 for material in &mesh_batch.mesh.materials {
                     if phase == DrawPhase::Transparent
                         && !material.has_transparency
@@ -591,33 +739,39 @@ impl Renderer for MeshRenderer {
 
                     pass.set_bind_group(1, &material.bind_group, &[]);
 
-                    #[expect(clippy::branches_sharing_code)]
+                    let indices = material.index_range.clone();
+                    let instances = mesh_batch.instance_range.clone();
                     if phase == DrawPhase::Transparent {
-                        // First draw without front faces.
-                        pass.set_pipeline(
-                            render_pipelines.get(self.rp_shaded_alpha_blended_cull_front)?,
-                        );
-                        pass.draw_indexed(
-                            material.index_range.clone(),
-                            0,
-                            mesh_batch.instance_range.clone(),
-                        );
-
-                        // And then without back faces.
-                        pass.set_pipeline(
-                            render_pipelines.get(self.rp_shaded_alpha_blended_cull_back)?,
-                        );
-                        pass.draw_indexed(
-                            material.index_range.clone(),
-                            0,
-                            mesh_batch.instance_range.clone(),
-                        );
+                        match mesh_batch.cull_mode {
+                            None => {
+                                // Default two-pass: first cull front faces, then cull back faces.
+                                pass.set_pipeline(
+                                    render_pipelines
+                                        .get(self.rp_shaded_alpha_blended_cull_front)?,
+                                );
+                                pass.draw_indexed(indices.clone(), 0, instances.clone());
+
+                                pass.set_pipeline(
+                                    render_pipelines.get(self.rp_shaded_alpha_blended_cull_back)?,
+                                );
+                                pass.draw_indexed(indices, 0, instances);
+                            }
+                            Some(wgpu::Face::Back) => {
+                                pass.set_pipeline(
+                                    render_pipelines.get(self.rp_shaded_alpha_blended_cull_back)?,
+                                );
+                                pass.draw_indexed(indices, 0, instances);
+                            }
+                            Some(wgpu::Face::Front) => {
+                                pass.set_pipeline(
+                                    render_pipelines
+                                        .get(self.rp_shaded_alpha_blended_cull_front)?,
+                                );
+                                pass.draw_indexed(indices, 0, instances);
+                            }
+                        }
                     } else {
-                        pass.draw_indexed(
-                            material.index_range.clone(),
-                            0,
-                            mesh_batch.instance_range.clone(),
-                        );
+                        pass.draw_indexed(indices, 0, instances);
                     }
                 }
             }
@@ -729,6 +883,7 @@ mod tests {
             additive_tint: Color32::WHITE,
             outline_mask_ids: OutlineMaskPreference::NONE,
             picking_layer_id: PickingLayerId::default(),
+            cull_mode: None,
         }
     }
 
diff --git a/crates/viewer/re_renderer_examples/multiview.rs b/crates/viewer/re_renderer_examples/multiview.rs
index ee43d64c9538..a323de80987a 100644
--- a/crates/viewer/re_renderer_examples/multiview.rs
+++ b/crates/viewer/re_renderer_examples/multiview.rs
@@ -48,6 +48,7 @@ fn build_mesh_instances(
                     additive_tint: *c,
                     outline_mask_ids: Default::default(),
                     picking_layer_id: Default::default(),
+                    cull_mode: None,
                 },
             )
         })
diff --git a/crates/viewer/re_renderer_examples/outlines.rs b/crates/viewer/re_renderer_examples/outlines.rs
index 62736a972e68..d393a46ecf8c 100644
--- a/crates/viewer/re_renderer_examples/outlines.rs
+++ b/crates/viewer/re_renderer_examples/outlines.rs
@@ -116,6 +116,7 @@ impl framework::Example for Outlines {
                         outline_mask_ids: props.outline_mask_ids,
                         picking_layer_id: Default::default(),
                         additive_tint: Color32::BLACK,
+                        cull_mode: None,
                     })
             })
             .collect_vec();
diff --git a/crates/viewer/re_renderer_examples/picking.rs b/crates/viewer/re_renderer_examples/picking.rs
index 56c74fe4ac5a..ec2323191f28 100644
--- a/crates/viewer/re_renderer_examples/picking.rs
+++ b/crates/viewer/re_renderer_examples/picking.rs
@@ -182,6 +182,7 @@ impl framework::Example for Picking {
                     Color32::BLACK
                 },
                 outline_mask_ids: Default::default(),
+                cull_mode: None,
             })
             .collect_vec();
 
diff --git a/crates/viewer/re_view_spatial/src/visualizers/assets3d.rs b/crates/viewer/re_view_spatial/src/visualizers/assets3d.rs
index d67675c1ba88..ae659274b390 100644
--- a/crates/viewer/re_view_spatial/src/visualizers/assets3d.rs
+++ b/crates/viewer/re_view_spatial/src/visualizers/assets3d.rs
@@ -94,6 +94,8 @@ impl Asset3DVisualizer {
                                 picking_instance_hash,
                             ),
                             additive_tint: re_renderer::Color32::BLACK,
+                            // TODO(andreas): honor the culling settings from the mesh file if any.
+                            cull_mode: Default::default(),
                         }
                     }));
 
diff --git a/crates/viewer/re_view_spatial/src/visualizers/meshes.rs b/crates/viewer/re_view_spatial/src/visualizers/meshes.rs
index 53a35459d133..e79c25437dff 100644
--- a/crates/viewer/re_view_spatial/src/visualizers/meshes.rs
+++ b/crates/viewer/re_view_spatial/src/visualizers/meshes.rs
@@ -33,6 +33,7 @@ struct Mesh3DComponentData<'a> {
     index: (TimeInt, RowId),
     query_result_hash: Hash64,
     native_mesh: NativeMesh3D<'a>,
+    cull_mode: Option,
 }
 
 // NOTE: Do not put profile scopes in these methods. They are called for all entities and all
@@ -95,6 +96,7 @@ impl Mesh3DVisualizer {
                                 picking_instance_hash,
                             ),
                             additive_tint: re_renderer::Color32::BLACK,
+                            cull_mode: data.cull_mode,
                         }
                     }));
 
@@ -161,10 +163,12 @@ impl VisualizerSystem for Mesh3DVisualizer {
                     results.iter_optional(Mesh3D::descriptor_albedo_texture_buffer().component);
                 let all_albedo_formats =
                     results.iter_optional(Mesh3D::descriptor_albedo_texture_format().component);
+                let all_face_rendering =
+                    results.iter_optional(Mesh3D::descriptor_face_rendering().component);
 
                 let query_result_hash = results.query_result_hash();
 
-                let data = re_query::range_zip_1x7(
+                let data = re_query::range_zip_1x8(
                     all_vertex_positions.slice::<[f32; 3]>(),
                     all_vertex_normals.slice::<[f32; 3]>(),
                     all_vertex_colors.slice::(),
@@ -174,6 +178,7 @@ impl VisualizerSystem for Mesh3DVisualizer {
                     all_albedo_buffers.slice::<&[u8]>(),
                     // Legit call to `component_slow`, `ImageFormat` is real complicated.
                     all_albedo_formats.component_slow::(),
+                    all_face_rendering.slice::(),
                 )
                 .map(
                     |(
@@ -186,6 +191,7 @@ impl VisualizerSystem for Mesh3DVisualizer {
                         albedo_factors,
                         albedo_buffers,
                         albedo_formats,
+                        face_rendering,
                     )| {
                         Mesh3DComponentData {
                             index,
@@ -212,6 +218,9 @@ impl VisualizerSystem for Mesh3DVisualizer {
                                     .first()
                                     .map(|format| format.0),
                             },
+                            cull_mode: face_rendering
+                                .and_then(|s| s.first().copied())
+                                .and_then(face_rendering_to_wgpu_cull),
                         }
                     },
                 );
@@ -235,3 +244,16 @@ impl VisualizerSystem for Mesh3DVisualizer {
         Some(self.0.as_any())
     }
 }
+
+/// Converts a raw `MeshFaceRendering` u8 discriminant to `Option` for culling.
+fn face_rendering_to_wgpu_cull(value: u8) -> Option {
+    use re_sdk_types::components::MeshFaceRendering;
+
+    match MeshFaceRendering::from_u8(value)? {
+        MeshFaceRendering::DoubleSided => None,
+        // To show only front faces, cull back faces.
+        MeshFaceRendering::Front => Some(re_renderer::external::wgpu::Face::Back),
+        // To show only back faces, cull front faces.
+        MeshFaceRendering::Back => Some(re_renderer::external::wgpu::Face::Front),
+    }
+}
diff --git a/crates/viewer/re_view_spatial/src/visualizers/utilities/proc_mesh_vis.rs b/crates/viewer/re_view_spatial/src/visualizers/utilities/proc_mesh_vis.rs
index b2244a468284..7b6b77b4f3c5 100644
--- a/crates/viewer/re_view_spatial/src/visualizers/utilities/proc_mesh_vis.rs
+++ b/crates/viewer/re_view_spatial/src/visualizers/utilities/proc_mesh_vis.rs
@@ -309,6 +309,11 @@ impl<'ctx> ProcMeshDrawableBuilder<'ctx> {
                         InstancePathHash::instance(entity_path, instance),
                     ),
                     additive_tint: tint,
+                    cull_mode: if tint.is_opaque() {
+                        Some(re_renderer::external::wgpu::Face::Back)
+                    } else {
+                        None
+                    },
                 });
             }
         }
diff --git a/crates/viewer/re_view_spatial/tests/mesh_face_rendering.rs b/crates/viewer/re_view_spatial/tests/mesh_face_rendering.rs
new file mode 100644
index 000000000000..a5230301b694
--- /dev/null
+++ b/crates/viewer/re_view_spatial/tests/mesh_face_rendering.rs
@@ -0,0 +1,139 @@
+use re_log_types::TimePoint;
+use re_sdk_types::RowId;
+use re_sdk_types::archetypes::Mesh3D;
+use re_sdk_types::components::{AlbedoFactor, MeshFaceRendering};
+use re_sdk_types::datatypes::Rgba32;
+use re_test_context::TestContext;
+use re_test_viewport::TestContextExt as _;
+use re_viewer_context::{RecommendedView, ViewClass as _};
+use re_viewport_blueprint::ViewBlueprint;
+
+/// Creates a tetrahedron mesh with rainbow vertex colors.
+fn rainbow_tetrahedron(
+    face_rendering: MeshFaceRendering,
+    albedo_factor: Option,
+) -> Mesh3D {
+    // Regular tetrahedron vertices.
+    let vertices: [[f32; 3]; 4] = [
+        [0.0, 1.0, 0.0],   // top
+        [-1.0, -0.5, 0.5], // front-left
+        [1.0, -0.5, 0.5],  // front-right
+        [0.0, -0.5, -0.8], // back
+    ];
+
+    // 4 faces, wound counter-clockwise when viewed from outside.
+    let indices: [[u32; 3]; 4] = [
+        [0, 2, 1], // front
+        [0, 3, 2], // right
+        [0, 1, 3], // left
+        [1, 2, 3], // bottom
+    ];
+
+    // Rainbow vertex colors (RGBA as u32, 0xRRGGBBAA).
+    let colors: [u32; 4] = [
+        0xFF0000FF, // red
+        0x00FF00FF, // green
+        0x0000FFFF, // blue
+        0xFFFF00FF, // yellow
+    ];
+
+    let mesh = Mesh3D::new(vertices)
+        .with_triangle_indices(indices)
+        .with_vertex_colors(colors)
+        .with_face_rendering(face_rendering);
+
+    if let Some(factor) = albedo_factor {
+        mesh.with_albedo_factor(factor)
+    } else {
+        mesh
+    }
+}
+
+fn run_mesh_face_rendering_test(
+    face_rendering: MeshFaceRendering,
+    albedo_factor: Option,
+    snapshot_name: &str,
+) {
+    let mut test_context = TestContext::new_with_view_class::();
+
+    test_context.log_entity("world/mesh", |builder| {
+        builder.with_archetype(
+            RowId::new(),
+            TimePoint::default(),
+            &rainbow_tetrahedron(face_rendering, albedo_factor),
+        )
+    });
+
+    let view_id = test_context.setup_viewport_blueprint(|_ctx, blueprint| {
+        let view_blueprint = ViewBlueprint::new(
+            re_view_spatial::SpatialView3D::identifier(),
+            RecommendedView::root(),
+        );
+        let view_id = view_blueprint.id;
+        blueprint.add_views(std::iter::once(view_blueprint), None, None);
+        view_id
+    });
+
+    let mut harness = test_context
+        .setup_kittest_for_rendering_3d(egui::vec2(300.0, 300.0))
+        .build_ui(|ui| test_context.run_with_single_view(ui, view_id));
+
+    harness.snapshot(snapshot_name);
+}
+
+/// Semi-transparent albedo factor: white color with 25% alpha.
+const SEMI_TRANSPARENT: AlbedoFactor = AlbedoFactor(Rgba32(0xFFFFFF40));
+
+#[test]
+pub fn test_mesh_face_rendering_double_sided_opaque() {
+    run_mesh_face_rendering_test(
+        MeshFaceRendering::DoubleSided,
+        None,
+        "mesh_face_rendering_double_sided_opaque",
+    );
+}
+
+#[test]
+pub fn test_mesh_face_rendering_back_opaque() {
+    run_mesh_face_rendering_test(
+        MeshFaceRendering::Back,
+        None,
+        "mesh_face_rendering_back_opaque",
+    );
+}
+
+#[test]
+pub fn test_mesh_face_rendering_front_opaque() {
+    run_mesh_face_rendering_test(
+        MeshFaceRendering::Front,
+        None,
+        "mesh_face_rendering_front_opaque",
+    );
+}
+
+#[test]
+pub fn test_mesh_face_rendering_double_sided_transparent() {
+    run_mesh_face_rendering_test(
+        MeshFaceRendering::DoubleSided,
+        Some(SEMI_TRANSPARENT),
+        "mesh_face_rendering_double_sided_transparent",
+    );
+}
+
+#[test]
+pub fn test_mesh_face_rendering_back_transparent() {
+    run_mesh_face_rendering_test(
+        MeshFaceRendering::Back,
+        Some(SEMI_TRANSPARENT),
+        "mesh_face_rendering_back_transparent",
+    );
+}
+
+#[test]
+pub fn test_mesh_face_rendering_front_transparent() {
+    run_mesh_face_rendering_test(
+        MeshFaceRendering::Front,
+        Some(SEMI_TRANSPARENT),
+        "mesh_face_rendering_front_transparent",
+    );
+}
diff --git a/crates/viewer/re_view_spatial/tests/snapshots/mesh_face_rendering_back_opaque.png b/crates/viewer/re_view_spatial/tests/snapshots/mesh_face_rendering_back_opaque.png
new file mode 100644
index 000000000000..ae7252275df1
--- /dev/null
+++ b/crates/viewer/re_view_spatial/tests/snapshots/mesh_face_rendering_back_opaque.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:02658d01e9a7b8b935a4b84d0592711c5cad2a79f63053fb66fe282fe1a68893
+size 58432
diff --git a/crates/viewer/re_view_spatial/tests/snapshots/mesh_face_rendering_back_transparent.png b/crates/viewer/re_view_spatial/tests/snapshots/mesh_face_rendering_back_transparent.png
new file mode 100644
index 000000000000..280c3b352534
--- /dev/null
+++ b/crates/viewer/re_view_spatial/tests/snapshots/mesh_face_rendering_back_transparent.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:3577a22b4d7bc5c42fa8b42e83dd9fda51d955c7208134950c7e05f9a21e217a
+size 52821
diff --git a/crates/viewer/re_view_spatial/tests/snapshots/mesh_face_rendering_double_sided_opaque.png b/crates/viewer/re_view_spatial/tests/snapshots/mesh_face_rendering_double_sided_opaque.png
new file mode 100644
index 000000000000..ae7252275df1
--- /dev/null
+++ b/crates/viewer/re_view_spatial/tests/snapshots/mesh_face_rendering_double_sided_opaque.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:02658d01e9a7b8b935a4b84d0592711c5cad2a79f63053fb66fe282fe1a68893
+size 58432
diff --git a/crates/viewer/re_view_spatial/tests/snapshots/mesh_face_rendering_double_sided_transparent.png b/crates/viewer/re_view_spatial/tests/snapshots/mesh_face_rendering_double_sided_transparent.png
new file mode 100644
index 000000000000..30591acff273
--- /dev/null
+++ b/crates/viewer/re_view_spatial/tests/snapshots/mesh_face_rendering_double_sided_transparent.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:73d1d4716fe7a89a51189924812a43034967e14c3e385c5d5970581b60c61715
+size 57322
diff --git a/crates/viewer/re_view_spatial/tests/snapshots/mesh_face_rendering_front_opaque.png b/crates/viewer/re_view_spatial/tests/snapshots/mesh_face_rendering_front_opaque.png
new file mode 100644
index 000000000000..6db3ff8fe076
--- /dev/null
+++ b/crates/viewer/re_view_spatial/tests/snapshots/mesh_face_rendering_front_opaque.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:61c9259014d062f2d06026f16b34d154bf517a8eead4409a626462bcc8fc383c
+size 60753
diff --git a/crates/viewer/re_view_spatial/tests/snapshots/mesh_face_rendering_front_transparent.png b/crates/viewer/re_view_spatial/tests/snapshots/mesh_face_rendering_front_transparent.png
new file mode 100644
index 000000000000..9ed96e4a294f
--- /dev/null
+++ b/crates/viewer/re_view_spatial/tests/snapshots/mesh_face_rendering_front_transparent.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:69139aef124d454f2734b6875f0f30533e4f84d22fc8feb7e7cf4203145bb932
+size 53878
diff --git a/crates/viewer/re_viewer/tests/snapshots/all_component_fallbacks__arch_fallback_rerun.archetypes.Mesh3D.snap b/crates/viewer/re_viewer/tests/snapshots/all_component_fallbacks__arch_fallback_rerun.archetypes.Mesh3D.snap
index 538bfb210a24..dd23a9bcc975 100644
--- a/crates/viewer/re_viewer/tests/snapshots/all_component_fallbacks__arch_fallback_rerun.archetypes.Mesh3D.snap
+++ b/crates/viewer/re_viewer/tests/snapshots/all_component_fallbacks__arch_fallback_rerun.archetypes.Mesh3D.snap
@@ -8,6 +8,7 @@ vertex_normals: [[0.0, 0.0, 0.0]]
 vertex_colors: [3161148927]
 vertex_texcoords: [[0.0, 0.0]]
 albedo_factor: [4294967295]
+face_rendering: [1]
 albedo_texture_buffer: [[0]]
 albedo_texture_format: [{width: 0, height: 0, pixel_format: null, color_model: null, channel_datatype: null}]
 class_ids: [0]
diff --git a/docs/content/reference/types/archetypes/mesh3d.md b/docs/content/reference/types/archetypes/mesh3d.md
index 73d1898f9ebd..85f20076439d 100644
--- a/docs/content/reference/types/archetypes/mesh3d.md
+++ b/docs/content/reference/types/archetypes/mesh3d.md
@@ -10,8 +10,9 @@ See also [`archetypes.Asset3D`](https://rerun.io/docs/reference/types/archetypes
 If there are multiple [`archetypes.InstancePoses3D`](https://rerun.io/docs/reference/types/archetypes/instance_poses3d) instances logged to the same entity as a mesh,
 an instance of the mesh will be drawn for each transform.
 
-The viewer draws meshes always two-sided. However, for transparency ordering
-front faces are assumed to those with counter clockwise triangle winding order (this is the same as in the GLTF specification).
+For transparency ordering, as well as back face culling (disabled by default),
+front faces are assumed to be those with counter clockwise triangle winding order
+(this is the same as in the GLTF specification).
 
 ## Fields
 ### Required
@@ -25,6 +26,7 @@ front faces are assumed to those with counter clockwise triangle winding order (
 * `vertex_colors`: [`Color`](../components/color.md)
 * `vertex_texcoords`: [`Texcoord2D`](../components/texcoord2d.md)
 * `albedo_factor`: [`AlbedoFactor`](../components/albedo_factor.md)
+* `face_rendering`: [`MeshFaceRendering`](../components/mesh_face_rendering.md)
 * `albedo_texture_buffer`: [`ImageBuffer`](../components/image_buffer.md)
 * `albedo_texture_format`: [`ImageFormat`](../components/image_format.md)
 * `class_ids`: [`ClassId`](../components/class_id.md)
diff --git a/docs/content/reference/types/components.md b/docs/content/reference/types/components.md
index 5e3c3954cca8..5e3412f37786 100644
--- a/docs/content/reference/types/components.md
+++ b/docs/content/reference/types/components.md
@@ -53,6 +53,7 @@ on [Entities and Components](../../concepts/logging-and-ingestion/entity-compone
 * [`MarkerShape`](components/marker_shape.md): The visual appearance of a point in e.g. a 2D plot.
 * [`MarkerSize`](components/marker_size.md): Radius of a marker of a point in e.g. a 2D plot, measured in UI points.
 * [`MediaType`](components/media_type.md): A standardized media type (RFC2046, formerly known as MIME types), encoded as a string.
+* [`MeshFaceRendering`](components/mesh_face_rendering.md): Determines which faces of a mesh are rendered.
 * [`Name`](components/name.md): A display name, typically for an entity or a item like a plot series.
 * [`Opacity`](components/opacity.md): Degree of transparency ranging from 0.0 (fully transparent) to 1.0 (fully opaque).
 * [`PinholeProjection`](components/pinhole_projection.md): Camera projection, from image coordinates to view coordinates.
diff --git a/docs/content/reference/types/components/.gitattributes b/docs/content/reference/types/components/.gitattributes
index f258cf57b36e..2f52f00716d7 100644
--- a/docs/content/reference/types/components/.gitattributes
+++ b/docs/content/reference/types/components/.gitattributes
@@ -41,6 +41,7 @@ magnification_filter.md linguist-generated=true
 marker_shape.md linguist-generated=true
 marker_size.md linguist-generated=true
 media_type.md linguist-generated=true
+mesh_face_rendering.md linguist-generated=true
 name.md linguist-generated=true
 opacity.md linguist-generated=true
 pinhole_projection.md linguist-generated=true
diff --git a/docs/content/reference/types/components/mesh_face_rendering.md b/docs/content/reference/types/components/mesh_face_rendering.md
new file mode 100644
index 000000000000..1c42306344ee
--- /dev/null
+++ b/docs/content/reference/types/components/mesh_face_rendering.md
@@ -0,0 +1,39 @@
+---
+title: "MeshFaceRendering"
+---
+
+
+Determines which faces of a mesh are rendered.
+
+For this purpose, we assume that the winding order of vertices in a mesh is
+consistent and that front faces are defined as those with vertices in counter clockwise order.
+
+## Variants
+#### `DoubleSided` = 1
+Show both back and front faces.
+
+#### `Front` = 2
+Only front faces are shown.
+
+Front faces are assumed to have a counter clockwise vertex winding order on screen.
+
+#### `Back` = 3
+Only back faces are shown.
+
+Back faces are assumed to have a clockwise vertex winding order on screen.
+
+
+## Arrow datatype
+```
+UInt8
+```
+
+## API reference links
+ * 🌊 [C++ API docs for `MeshFaceRendering`](https://ref.rerun.io/docs/cpp/stable/namespacererun_1_1components.html?speculative-link)
+ * 🐍 [Python API docs for `MeshFaceRendering`](https://ref.rerun.io/docs/python/stable/common/components?speculative-link#rerun.components.MeshFaceRendering)
+ * 🦀 [Rust API docs for `MeshFaceRendering`](https://docs.rs/rerun/latest/rerun/components/enum.MeshFaceRendering.html?speculative-link)
+
+
+## Used by
+
+* [`Mesh3D`](../archetypes/mesh3d.md)
diff --git a/examples/python/arkit_scenes/arkit_scenes/__main__.py b/examples/python/arkit_scenes/arkit_scenes/__main__.py
index f16fe35dc2e2..7fcf8b6ec8ff 100755
--- a/examples/python/arkit_scenes/arkit_scenes/__main__.py
+++ b/examples/python/arkit_scenes/arkit_scenes/__main__.py
@@ -213,6 +213,7 @@ def log_arkit(recording_path: Path, include_highres: bool) -> None:
             vertex_positions=mesh.vertices,  # type: ignore[attr-defined]
             vertex_colors=mesh.visual.vertex_colors,  # type: ignore[attr-defined]
             triangle_indices=mesh.faces,  # type: ignore[attr-defined]
+            face_rendering="Back",  # We want to hide the front facing faces, but the dataset uses mostly clockwise winding order which is the opposite of what Rerun assumes (CCW).
         ),
         static=True,
     )
diff --git a/rerun_cpp/src/rerun/archetypes/mesh3d.cpp b/rerun_cpp/src/rerun/archetypes/mesh3d.cpp
index 768315000481..07d0dbe43244 100644
--- a/rerun_cpp/src/rerun/archetypes/mesh3d.cpp
+++ b/rerun_cpp/src/rerun/archetypes/mesh3d.cpp
@@ -26,6 +26,9 @@ namespace rerun::archetypes {
         archetype.albedo_factor =
             ComponentBatch::empty(Descriptor_albedo_factor)
                 .value_or_throw();
+        archetype.face_rendering =
+            ComponentBatch::empty(Descriptor_face_rendering)
+                .value_or_throw();
         archetype.albedo_texture_buffer =
             ComponentBatch::empty(Descriptor_albedo_texture_buffer)
                 .value_or_throw();
@@ -40,7 +43,7 @@ namespace rerun::archetypes {
 
     Collection Mesh3D::columns(const Collection& lengths_) {
         std::vector columns;
-        columns.reserve(9);
+        columns.reserve(10);
         if (vertex_positions.has_value()) {
             columns.push_back(vertex_positions.value().partitioned(lengths_).value_or_throw());
         }
@@ -59,6 +62,9 @@ namespace rerun::archetypes {
         if (albedo_factor.has_value()) {
             columns.push_back(albedo_factor.value().partitioned(lengths_).value_or_throw());
         }
+        if (face_rendering.has_value()) {
+            columns.push_back(face_rendering.value().partitioned(lengths_).value_or_throw());
+        }
         if (albedo_texture_buffer.has_value()) {
             columns.push_back(albedo_texture_buffer.value().partitioned(lengths_).value_or_throw());
         }
@@ -90,6 +96,9 @@ namespace rerun::archetypes {
         if (albedo_factor.has_value()) {
             return columns(std::vector(albedo_factor.value().length(), 1));
         }
+        if (face_rendering.has_value()) {
+            return columns(std::vector(face_rendering.value().length(), 1));
+        }
         if (albedo_texture_buffer.has_value()) {
             return columns(std::vector(albedo_texture_buffer.value().length(), 1));
         }
@@ -110,7 +119,7 @@ namespace rerun {
     ) {
         using namespace archetypes;
         std::vector cells;
-        cells.reserve(9);
+        cells.reserve(10);
 
         if (archetype.vertex_positions.has_value()) {
             cells.push_back(archetype.vertex_positions.value());
@@ -130,6 +139,9 @@ namespace rerun {
         if (archetype.albedo_factor.has_value()) {
             cells.push_back(archetype.albedo_factor.value());
         }
+        if (archetype.face_rendering.has_value()) {
+            cells.push_back(archetype.face_rendering.value());
+        }
         if (archetype.albedo_texture_buffer.has_value()) {
             cells.push_back(archetype.albedo_texture_buffer.value());
         }
diff --git a/rerun_cpp/src/rerun/archetypes/mesh3d.hpp b/rerun_cpp/src/rerun/archetypes/mesh3d.hpp
index e8db712271b8..64c40f2c7a53 100644
--- a/rerun_cpp/src/rerun/archetypes/mesh3d.hpp
+++ b/rerun_cpp/src/rerun/archetypes/mesh3d.hpp
@@ -11,6 +11,7 @@
 #include "../components/color.hpp"
 #include "../components/image_buffer.hpp"
 #include "../components/image_format.hpp"
+#include "../components/mesh_face_rendering.hpp"
 #include "../components/position3d.hpp"
 #include "../components/texcoord2d.hpp"
 #include "../components/triangle_indices.hpp"
@@ -30,8 +31,9 @@ namespace rerun::archetypes {
     /// If there are multiple `archetypes::InstancePoses3D` instances logged to the same entity as a mesh,
     /// an instance of the mesh will be drawn for each transform.
     ///
-    /// The viewer draws meshes always two-sided. However, for transparency ordering
-    /// front faces are assumed to those with counter clockwise triangle winding order (this is the same as in the GLTF specification).
+    /// For transparency ordering, as well as back face culling (disabled by default),
+    /// front faces are assumed to be those with counter clockwise triangle winding order
+    /// (this is the same as in the GLTF specification).
     ///
     /// ## Examples
     ///
@@ -134,6 +136,11 @@ namespace rerun::archetypes {
         /// Alpha channel governs the overall mesh transparency.
         std::optional albedo_factor;
 
+        /// Determines which faces of the mesh are rendered.
+        ///
+        /// The default is `components::MeshFaceRendering::DoubleSided`, meaning both front and back faces are shown.
+        std::optional face_rendering;
+
         /// Optional albedo texture.
         ///
         /// Used with the `components::Texcoord2D` of the mesh.
@@ -185,6 +192,11 @@ namespace rerun::archetypes {
             ArchetypeName, "Mesh3D:albedo_factor",
             Loggable::ComponentType
         );
+        /// `ComponentDescriptor` for the `face_rendering` field.
+        static constexpr auto Descriptor_face_rendering = ComponentDescriptor(
+            ArchetypeName, "Mesh3D:face_rendering",
+            Loggable::ComponentType
+        );
         /// `ComponentDescriptor` for the `albedo_texture_buffer` field.
         static constexpr auto Descriptor_albedo_texture_buffer = ComponentDescriptor(
             ArchetypeName, "Mesh3D:albedo_texture_buffer",
@@ -292,6 +304,29 @@ namespace rerun::archetypes {
             return std::move(*this);
         }
 
+        /// Determines which faces of the mesh are rendered.
+        ///
+        /// The default is `components::MeshFaceRendering::DoubleSided`, meaning both front and back faces are shown.
+        Mesh3D with_face_rendering(const rerun::components::MeshFaceRendering& _face_rendering) && {
+            face_rendering =
+                ComponentBatch::from_loggable(_face_rendering, Descriptor_face_rendering)
+                    .value_or_throw();
+            return std::move(*this);
+        }
+
+        /// This method makes it possible to pack multiple `face_rendering` in a single component batch.
+        ///
+        /// This only makes sense when used in conjunction with `columns`. `with_face_rendering` should
+        /// be used when logging a single row's worth of data.
+        Mesh3D with_many_face_rendering(
+            const Collection& _face_rendering
+        ) && {
+            face_rendering =
+                ComponentBatch::from_loggable(_face_rendering, Descriptor_face_rendering)
+                    .value_or_throw();
+            return std::move(*this);
+        }
+
         /// Optional albedo texture.
         ///
         /// Used with the `components::Texcoord2D` of the mesh.
diff --git a/rerun_cpp/src/rerun/components.hpp b/rerun_cpp/src/rerun/components.hpp
index 2f734052d303..218f0e5e5860 100644
--- a/rerun_cpp/src/rerun/components.hpp
+++ b/rerun_cpp/src/rerun/components.hpp
@@ -42,6 +42,7 @@
 #include "components/marker_shape.hpp"
 #include "components/marker_size.hpp"
 #include "components/media_type.hpp"
+#include "components/mesh_face_rendering.hpp"
 #include "components/name.hpp"
 #include "components/opacity.hpp"
 #include "components/pinhole_projection.hpp"
diff --git a/rerun_cpp/src/rerun/components/.gitattributes b/rerun_cpp/src/rerun/components/.gitattributes
index 5a86d334b130..1f05d6ecc650 100644
--- a/rerun_cpp/src/rerun/components/.gitattributes
+++ b/rerun_cpp/src/rerun/components/.gitattributes
@@ -54,6 +54,8 @@ marker_shape.cpp linguist-generated=true
 marker_shape.hpp linguist-generated=true
 marker_size.hpp linguist-generated=true
 media_type.hpp linguist-generated=true
+mesh_face_rendering.cpp linguist-generated=true
+mesh_face_rendering.hpp linguist-generated=true
 name.hpp linguist-generated=true
 opacity.hpp linguist-generated=true
 pinhole_projection.hpp linguist-generated=true
diff --git a/rerun_cpp/src/rerun/components/mesh_face_rendering.cpp b/rerun_cpp/src/rerun/components/mesh_face_rendering.cpp
new file mode 100644
index 000000000000..82e0ee27ee36
--- /dev/null
+++ b/rerun_cpp/src/rerun/components/mesh_face_rendering.cpp
@@ -0,0 +1,58 @@
+// DO NOT EDIT! This file was auto-generated by crates/build/re_types_builder/src/codegen/cpp/mod.rs
+// Based on "crates/store/re_sdk_types/definitions/rerun/components/mesh_face_rendering.fbs".
+
+#include "mesh_face_rendering.hpp"
+
+#include 
+#include 
+
+namespace rerun {
+    const std::shared_ptr& Loggable::arrow_datatype(
+    ) {
+        static const auto datatype = arrow::uint8();
+        return datatype;
+    }
+
+    Result> Loggable::to_arrow(
+        const components::MeshFaceRendering* instances, size_t num_instances
+    ) {
+        // TODO(andreas): Allow configuring the memory pool.
+        arrow::MemoryPool* pool = arrow::default_memory_pool();
+        auto datatype = arrow_datatype();
+
+        ARROW_ASSIGN_OR_RAISE(auto builder, arrow::MakeBuilder(datatype, pool))
+        if (instances && num_instances > 0) {
+            RR_RETURN_NOT_OK(Loggable::fill_arrow_array_builder(
+                static_cast(builder.get()),
+                instances,
+                num_instances
+            ));
+        }
+        std::shared_ptr array;
+        ARROW_RETURN_NOT_OK(builder->Finish(&array));
+        return array;
+    }
+
+    rerun::Error Loggable::fill_arrow_array_builder(
+        arrow::UInt8Builder* builder, const components::MeshFaceRendering* elements,
+        size_t num_elements
+    ) {
+        if (builder == nullptr) {
+            return rerun::Error(ErrorCode::UnexpectedNullArgument, "Passed array builder is null.");
+        }
+        if (elements == nullptr) {
+            return rerun::Error(
+                ErrorCode::UnexpectedNullArgument,
+                "Cannot serialize null pointer to arrow array."
+            );
+        }
+
+        ARROW_RETURN_NOT_OK(builder->Reserve(static_cast(num_elements)));
+        for (size_t elem_idx = 0; elem_idx < num_elements; elem_idx += 1) {
+            const auto variant = elements[elem_idx];
+            ARROW_RETURN_NOT_OK(builder->Append(static_cast(variant)));
+        }
+
+        return Error::ok();
+    }
+} // namespace rerun
diff --git a/rerun_cpp/src/rerun/components/mesh_face_rendering.hpp b/rerun_cpp/src/rerun/components/mesh_face_rendering.hpp
new file mode 100644
index 000000000000..8c4522d884a9
--- /dev/null
+++ b/rerun_cpp/src/rerun/components/mesh_face_rendering.hpp
@@ -0,0 +1,67 @@
+// DO NOT EDIT! This file was auto-generated by crates/build/re_types_builder/src/codegen/cpp/mod.rs
+// Based on "crates/store/re_sdk_types/definitions/rerun/components/mesh_face_rendering.fbs".
+
+#pragma once
+
+#include "../result.hpp"
+
+#include 
+#include 
+
+namespace arrow {
+    /// \private
+    template 
+    class NumericBuilder;
+
+    class Array;
+    class DataType;
+    class UInt8Type;
+    using UInt8Builder = NumericBuilder;
+} // namespace arrow
+
+namespace rerun::components {
+    /// **Component**: Determines which faces of a mesh are rendered.
+    ///
+    /// For this purpose, we assume that the winding order of vertices in a mesh is
+    /// consistent and that front faces are defined as those with vertices in counter clockwise order.
+    enum class MeshFaceRendering : uint8_t {
+
+        /// Show both back and front faces.
+        DoubleSided = 1,
+
+        /// Only front faces are shown.
+        ///
+        /// Front faces are assumed to have a counter clockwise vertex winding order on screen.
+        Front = 2,
+
+        /// Only back faces are shown.
+        ///
+        /// Back faces are assumed to have a clockwise vertex winding order on screen.
+        Back = 3,
+    };
+} // namespace rerun::components
+
+namespace rerun {
+    template 
+    struct Loggable;
+
+    /// \private
+    template <>
+    struct Loggable {
+        static constexpr std::string_view ComponentType = "rerun.components.MeshFaceRendering";
+
+        /// Returns the arrow data type this type corresponds to.
+        static const std::shared_ptr& arrow_datatype();
+
+        /// Serializes an array of `rerun::components::MeshFaceRendering` into an arrow array.
+        static Result> to_arrow(
+            const components::MeshFaceRendering* instances, size_t num_instances
+        );
+
+        /// Fills an arrow array builder with an array of this type.
+        static rerun::Error fill_arrow_array_builder(
+            arrow::UInt8Builder* builder, const components::MeshFaceRendering* elements,
+            size_t num_elements
+        );
+    };
+} // namespace rerun
diff --git a/rerun_py/rerun_sdk/rerun/archetypes/mesh3d.py b/rerun_py/rerun_sdk/rerun/archetypes/mesh3d.py
index 7fcf5ff547ff..9d695803e2ec 100644
--- a/rerun_py/rerun_sdk/rerun/archetypes/mesh3d.py
+++ b/rerun_py/rerun_sdk/rerun/archetypes/mesh3d.py
@@ -36,8 +36,9 @@ class Mesh3D(Mesh3DExt, Archetype, VisualizableArchetype):
     If there are multiple [`archetypes.InstancePoses3D`][rerun.archetypes.InstancePoses3D] instances logged to the same entity as a mesh,
     an instance of the mesh will be drawn for each transform.
 
-    The viewer draws meshes always two-sided. However, for transparency ordering
-    front faces are assumed to those with counter clockwise triangle winding order (this is the same as in the GLTF specification).
+    For transparency ordering, as well as back face culling (disabled by default),
+    front faces are assumed to be those with counter clockwise triangle winding order
+    (this is the same as in the GLTF specification).
 
     Examples
     --------
@@ -121,6 +122,7 @@ def __attrs_clear__(self) -> None:
             vertex_colors=None,
             vertex_texcoords=None,
             albedo_factor=None,
+            face_rendering=None,
             albedo_texture_buffer=None,
             albedo_texture_format=None,
             class_ids=None,
@@ -144,6 +146,7 @@ def from_fields(
         vertex_colors: datatypes.Rgba32ArrayLike | None = None,
         vertex_texcoords: datatypes.Vec2DArrayLike | None = None,
         albedo_factor: datatypes.Rgba32Like | None = None,
+        face_rendering: components.MeshFaceRenderingLike | None = None,
         albedo_texture_buffer: datatypes.BlobLike | None = None,
         albedo_texture_format: datatypes.ImageFormatLike | None = None,
         class_ids: datatypes.ClassIdArrayLike | None = None,
@@ -173,6 +176,10 @@ def from_fields(
             A color multiplier applied to the whole mesh.
 
             Alpha channel governs the overall mesh transparency.
+        face_rendering:
+            Determines which faces of the mesh are rendered.
+
+            The default is [`components.MeshFaceRendering.DoubleSided`][rerun.components.MeshFaceRendering.DoubleSided], meaning both front and back faces are shown.
         albedo_texture_buffer:
             Optional albedo texture.
 
@@ -200,6 +207,7 @@ def from_fields(
                 "vertex_colors": vertex_colors,
                 "vertex_texcoords": vertex_texcoords,
                 "albedo_factor": albedo_factor,
+                "face_rendering": face_rendering,
                 "albedo_texture_buffer": albedo_texture_buffer,
                 "albedo_texture_format": albedo_texture_format,
                 "class_ids": class_ids,
@@ -229,6 +237,7 @@ def columns(
         vertex_colors: datatypes.Rgba32ArrayLike | None = None,
         vertex_texcoords: datatypes.Vec2DArrayLike | None = None,
         albedo_factor: datatypes.Rgba32ArrayLike | None = None,
+        face_rendering: components.MeshFaceRenderingArrayLike | None = None,
         albedo_texture_buffer: datatypes.BlobArrayLike | None = None,
         albedo_texture_format: datatypes.ImageFormatArrayLike | None = None,
         class_ids: datatypes.ClassIdArrayLike | None = None,
@@ -261,6 +270,10 @@ def columns(
             A color multiplier applied to the whole mesh.
 
             Alpha channel governs the overall mesh transparency.
+        face_rendering:
+            Determines which faces of the mesh are rendered.
+
+            The default is [`components.MeshFaceRendering.DoubleSided`][rerun.components.MeshFaceRendering.DoubleSided], meaning both front and back faces are shown.
         albedo_texture_buffer:
             Optional albedo texture.
 
@@ -288,6 +301,7 @@ def columns(
                 vertex_colors=vertex_colors,
                 vertex_texcoords=vertex_texcoords,
                 albedo_factor=albedo_factor,
+                face_rendering=face_rendering,
                 albedo_texture_buffer=albedo_texture_buffer,
                 albedo_texture_format=albedo_texture_format,
                 class_ids=class_ids,
@@ -304,6 +318,7 @@ def columns(
             "Mesh3D:vertex_colors": vertex_colors,
             "Mesh3D:vertex_texcoords": vertex_texcoords,
             "Mesh3D:albedo_factor": albedo_factor,
+            "Mesh3D:face_rendering": face_rendering,
             "Mesh3D:albedo_texture_buffer": albedo_texture_buffer,
             "Mesh3D:albedo_texture_format": albedo_texture_format,
             "Mesh3D:class_ids": class_ids,
@@ -401,6 +416,17 @@ def columns(
     #
     # (Docstring intentionally commented out to hide this field from the docs)
 
+    face_rendering: components.MeshFaceRenderingBatch | None = field(
+        metadata={"component": True},
+        default=None,
+        converter=components.MeshFaceRenderingBatch._converter,  # type: ignore[misc]
+    )
+    # Determines which faces of the mesh are rendered.
+    #
+    # The default is [`components.MeshFaceRendering.DoubleSided`][rerun.components.MeshFaceRendering.DoubleSided], meaning both front and back faces are shown.
+    #
+    # (Docstring intentionally commented out to hide this field from the docs)
+
     albedo_texture_buffer: components.ImageBufferBatch | None = field(
         metadata={"component": True},
         default=None,
diff --git a/rerun_py/rerun_sdk/rerun/archetypes/mesh3d_ext.py b/rerun_py/rerun_sdk/rerun/archetypes/mesh3d_ext.py
index fccabbd94312..0c118c379376 100644
--- a/rerun_py/rerun_sdk/rerun/archetypes/mesh3d_ext.py
+++ b/rerun_py/rerun_sdk/rerun/archetypes/mesh3d_ext.py
@@ -12,7 +12,7 @@
 from ..error_utils import _send_warning_or_raise, catch_and_log_exceptions
 
 if TYPE_CHECKING:
-    from .. import datatypes
+    from .. import components, datatypes
 
     ImageLike = (
         npt.NDArray[np.float16]
@@ -54,6 +54,7 @@ def __init__(
         vertex_texcoords: datatypes.Vec2DArrayLike | None = None,
         albedo_texture: ImageLike | None = None,
         albedo_factor: datatypes.Rgba32Like | None = None,
+        face_rendering: components.MeshFaceRenderingLike | None = None,
         class_ids: datatypes.ClassIdArrayLike | None = None,
     ) -> None:
         """
@@ -80,6 +81,9 @@ def __init__(
             Optional albedo texture. Used with `vertex_texcoords` on `Mesh3D`.
             Currently supports only sRGB(A) textures, ignoring alpha.
             (meaning that the texture must have 3 or 4 channels)
+        face_rendering:
+            Determines which faces of the mesh are rendered.
+            The default is `DoubleSided`, meaning both front and back faces are shown.
         class_ids:
             Optional class Ids for the vertices.
             The class ID provides colors and labels if not specified explicitly.
@@ -126,6 +130,7 @@ def __init__(
                 albedo_texture_buffer=albedo_texture_buffer,
                 albedo_texture_format=albedo_texture_format,
                 albedo_factor=albedo_factor,
+                face_rendering=face_rendering,
                 class_ids=class_ids,
             )
             return
diff --git a/rerun_py/rerun_sdk/rerun/components/.gitattributes b/rerun_py/rerun_sdk/rerun/components/.gitattributes
index d9ea6b86d049..4b673de69915 100644
--- a/rerun_py/rerun_sdk/rerun/components/.gitattributes
+++ b/rerun_py/rerun_sdk/rerun/components/.gitattributes
@@ -42,6 +42,7 @@ magnification_filter.py linguist-generated=true
 marker_shape.py linguist-generated=true
 marker_size.py linguist-generated=true
 media_type.py linguist-generated=true
+mesh_face_rendering.py linguist-generated=true
 name.py linguist-generated=true
 opacity.py linguist-generated=true
 pinhole_projection.py linguist-generated=true
diff --git a/rerun_py/rerun_sdk/rerun/components/__init__.py b/rerun_py/rerun_sdk/rerun/components/__init__.py
index 46ffb85c4261..84f35b74d1e1 100644
--- a/rerun_py/rerun_sdk/rerun/components/__init__.py
+++ b/rerun_py/rerun_sdk/rerun/components/__init__.py
@@ -67,6 +67,12 @@
 from .marker_shape import MarkerShape, MarkerShapeArrayLike, MarkerShapeBatch, MarkerShapeLike
 from .marker_size import MarkerSize, MarkerSizeBatch
 from .media_type import MediaType, MediaTypeBatch
+from .mesh_face_rendering import (
+    MeshFaceRendering,
+    MeshFaceRenderingArrayLike,
+    MeshFaceRenderingBatch,
+    MeshFaceRenderingLike,
+)
 from .name import Name, NameBatch
 from .opacity import Opacity, OpacityBatch
 from .pinhole_projection import PinholeProjection, PinholeProjectionBatch
@@ -217,6 +223,10 @@
     "MarkerSizeBatch",
     "MediaType",
     "MediaTypeBatch",
+    "MeshFaceRendering",
+    "MeshFaceRenderingArrayLike",
+    "MeshFaceRenderingBatch",
+    "MeshFaceRenderingLike",
     "Name",
     "NameBatch",
     "Opacity",
diff --git a/rerun_py/rerun_sdk/rerun/components/mesh_face_rendering.py b/rerun_py/rerun_sdk/rerun/components/mesh_face_rendering.py
new file mode 100644
index 000000000000..6bd32458570f
--- /dev/null
+++ b/rerun_py/rerun_sdk/rerun/components/mesh_face_rendering.py
@@ -0,0 +1,95 @@
+# DO NOT EDIT! This file was auto-generated by crates/build/re_types_builder/src/codegen/python/mod.rs
+# Based on "crates/store/re_sdk_types/definitions/rerun/components/mesh_face_rendering.fbs".
+
+# You can extend this class by creating a "MeshFaceRenderingExt" class in "mesh_face_rendering_ext.py".
+
+from __future__ import annotations
+
+from collections.abc import Sequence
+from typing import Literal
+
+import pyarrow as pa
+
+from .._baseclasses import (
+    BaseBatch,
+    ComponentBatchMixin,
+)
+
+__all__ = ["MeshFaceRendering", "MeshFaceRenderingArrayLike", "MeshFaceRenderingBatch", "MeshFaceRenderingLike"]
+
+
+from enum import Enum
+
+
+class MeshFaceRendering(Enum):
+    """
+    **Component**: Determines which faces of a mesh are rendered.
+
+    For this purpose, we assume that the winding order of vertices in a mesh is
+    consistent and that front faces are defined as those with vertices in counter clockwise order.
+    """
+
+    DoubleSided = 1
+    """Show both back and front faces."""
+
+    Front = 2
+    """
+    Only front faces are shown.
+
+    Front faces are assumed to have a counter clockwise vertex winding order on screen.
+    """
+
+    Back = 3
+    """
+    Only back faces are shown.
+
+    Back faces are assumed to have a clockwise vertex winding order on screen.
+    """
+
+    @classmethod
+    def auto(cls, val: str | int | MeshFaceRendering) -> MeshFaceRendering:
+        """Best-effort converter, including a case-insensitive string matcher."""
+        if isinstance(val, MeshFaceRendering):
+            return val
+        if isinstance(val, int):
+            return cls(val)
+        try:
+            return cls[val]
+        except KeyError:
+            val_lower = val.lower()
+            for variant in cls:
+                if variant.name.lower() == val_lower:
+                    return variant
+        raise ValueError(f"Cannot convert {val} to {cls.__name__}")
+
+    def __str__(self) -> str:
+        """Returns the variant name."""
+        return self.name
+
+
+MeshFaceRenderingLike = (
+    MeshFaceRendering | Literal["Back", "DoubleSided", "Front", "back", "doublesided", "front"] | int
+)
+"""A type alias for any MeshFaceRendering-like object."""
+
+MeshFaceRenderingArrayLike = (
+    MeshFaceRendering
+    | Literal["Back", "DoubleSided", "Front", "back", "doublesided", "front"]
+    | int
+    | Sequence[MeshFaceRenderingLike]
+)
+"""A type alias for any MeshFaceRendering-like array object."""
+
+
+class MeshFaceRenderingBatch(BaseBatch[MeshFaceRenderingArrayLike], ComponentBatchMixin):
+    _ARROW_DATATYPE = pa.uint8()
+    _COMPONENT_TYPE: str = "rerun.components.MeshFaceRendering"
+
+    @staticmethod
+    def _native_to_pa_array(data: MeshFaceRenderingArrayLike, data_type: pa.DataType) -> pa.Array:
+        if isinstance(data, (MeshFaceRendering, int, str)):
+            data = [data]
+
+        pa_data = [MeshFaceRendering.auto(v).value if v is not None else None for v in data]  # type: ignore[redundant-expr]
+
+        return pa.array(pa_data, type=data_type)

From 8d8eb407f37312a075bb564ce2d90e998f6c75ae Mon Sep 17 00:00:00 2001
From: Michael Grupp 
Date: Mon, 16 Mar 2026 16:55:42 +0100
Subject: [PATCH 152/513] Ensure that share modals are always on top

E.g. when you are in a context like settings or chunk browser.

Before, if you clicked "share" in that context, the modal would not show
up but open deferred once you exit the screen.

Source-Ref: 2f476307cac20eecb78e0b4430f80848c76e6a2b
---
 crates/viewer/re_viewer/src/app_state.rs      | 13 ++++++++++--
 crates/viewer/re_viewer/src/ui/share_modal.rs | 20 ++++++++++++-------
 2 files changed, 24 insertions(+), 9 deletions(-)

diff --git a/crates/viewer/re_viewer/src/app_state.rs b/crates/viewer/re_viewer/src/app_state.rs
index e3d44e9daebf..3dd3e6369266 100644
--- a/crates/viewer/re_viewer/src/app_state.rs
+++ b/crates/viewer/re_viewer/src/app_state.rs
@@ -233,6 +233,9 @@ impl AppState {
                 if !show_settings_ui {
                     self.navigation.replace((**previous).clone());
                 }
+
+                self.share_modal
+                    .ui(None, ui, startup_options.web_viewer_base_url().as_ref());
             }
 
             Route::ChunkStoreBrowser { previous, .. } => {
@@ -245,6 +248,9 @@ impl AppState {
                 if !should_datastore_ui_remain_active {
                     self.navigation.replace((**previous).clone());
                 }
+
+                self.share_modal
+                    .ui(None, ui, startup_options.web_viewer_base_url().as_ref());
             }
 
             // TODO(RR-3033): This needs to be further cleaned up and split into separately handled routes.
@@ -729,8 +735,11 @@ impl AppState {
 
                 self.redap_servers.modals_ui(&ctx.app_ctx, ui);
                 self.open_url_modal.ui(ui);
-                self.share_modal
-                    .ui(&ctx, ui, startup_options.web_viewer_base_url().as_ref());
+                self.share_modal.ui(
+                    Some(&ctx),
+                    ui,
+                    startup_options.web_viewer_base_url().as_ref(),
+                );
 
                 // Only in integration tests: call the test hook if any.
                 #[cfg(feature = "testing")]
diff --git a/crates/viewer/re_viewer/src/ui/share_modal.rs b/crates/viewer/re_viewer/src/ui/share_modal.rs
index 4aec629513c3..7976b69ddee5 100644
--- a/crates/viewer/re_viewer/src/ui/share_modal.rs
+++ b/crates/viewer/re_viewer/src/ui/share_modal.rs
@@ -97,7 +97,7 @@ impl ShareModal {
     /// Draws the share modal dialog if its open.
     pub fn ui(
         &mut self,
-        ctx: &ViewerContext<'_>,
+        viewer_ctx: Option<&ViewerContext<'_>>,
         ui: &egui::Ui,
         web_viewer_base_url: Option<&url::Url>,
     ) {
@@ -182,7 +182,7 @@ impl ShareModal {
                 }
 
                 ui.list_item_scope("share_dialog_url_settings", |ui| {
-                    url_settings_ui(ctx, ui, url, &mut self.create_web_viewer_url);
+                    url_settings_ui(viewer_ctx, ui, url, &mut self.create_web_viewer_url);
                 });
             },
         );
@@ -230,7 +230,7 @@ fn selectable_value_with_available_width<'a, Value: PartialEq>(
 const MIN_TOGGLE_WIDTH_RH: f32 = 120.0;
 
 fn url_settings_ui(
-    ctx: &ViewerContext<'_>,
+    ctx: Option<&ViewerContext<'_>>,
     ui: &mut egui::Ui,
     url: &mut ViewerOpenUrl,
     create_web_viewer_url: &mut bool,
@@ -244,11 +244,17 @@ fn url_settings_ui(
         });
     }));
 
-    if let Some(fragments) = url.fragment_mut() {
+    if let Some(fragments) = url.fragment_mut()
+        && let Some(ctx) = ctx
+    {
         ui.add_space(8.0);
 
-        let timestamp_format = ctx.app_options().timestamp_format;
-        fragment_ui(ui, fragments, timestamp_format, ctx.time_ctrl);
+        fragment_ui(
+            ui,
+            fragments,
+            ctx.app_options().timestamp_format,
+            ctx.time_ctrl,
+        );
     }
 }
 
@@ -402,7 +408,7 @@ mod tests {
                 re_ui::apply_style_and_install_loaders(ui.ctx());
 
                 test_ctx.run(ui.ctx(), |ctx| {
-                    modal.lock().ui(ctx, ui, None);
+                    modal.lock().ui(Some(ctx), ui, None);
                 });
 
                 test_ctx.handle_system_commands(ui.ctx());

From 45340013b495c48d1f242c4c5ef23bfb062795e2 Mon Sep 17 00:00:00 2001
From: Isse 
Date: Mon, 16 Mar 2026 17:44:14 +0100
Subject: [PATCH 153/513] Add zoom in limit to camera, because zooming in too
 far broke the view

### Related

* Closes RR-4009

### What

Adds a minimum limit (0.02) to zooming in the view, further than that
the camera starts clipping the orbit marker.

Source-Ref: b8c780e1a03cd8c02b636990d73ec2cfbcb509a9
---
 crates/viewer/re_view_spatial/src/eye.rs     | 19 +++++++++++++++----
 crates/viewer/re_view_spatial/src/view_3d.rs |  3 +++
 2 files changed, 18 insertions(+), 4 deletions(-)

diff --git a/crates/viewer/re_view_spatial/src/eye.rs b/crates/viewer/re_view_spatial/src/eye.rs
index d97d81a4ee92..ce07149a1df9 100644
--- a/crates/viewer/re_view_spatial/src/eye.rs
+++ b/crates/viewer/re_view_spatial/src/eye.rs
@@ -32,6 +32,8 @@ pub struct Eye {
 impl Eye {
     pub const DEFAULT_FOV_Y: f32 = 55.0_f32 * std::f32::consts::TAU / 360.0;
 
+    pub const PERSPECTIVE_NEAR_PLANE: f32 = 0.01;
+
     pub fn from_camera(camera: &PinholeWrapper) -> Option {
         let fov_y = camera.pinhole.fov_y();
 
@@ -43,7 +45,7 @@ impl Eye {
 
     pub fn near(&self) -> f32 {
         if self.is_perspective() {
-            0.01 // TODO(emilk)
+            Self::PERSPECTIVE_NEAR_PLANE // TODO(emilk)
         } else {
             -1000.0 // TODO(andreas)
         }
@@ -213,7 +215,7 @@ pub struct EyeState {
 
 /// Utility struct for handling eye control parameter changes,
 /// e.g. via user input or blueprint.
-struct EyeController {
+pub(crate) struct EyeController {
     pos: Vec3,
     look_target: Vec3,
     kind: Eye3DKind,
@@ -228,11 +230,20 @@ impl EyeController {
     /// Avoids zentith/nadir singularity.
     const MAX_PITCH: f32 = 0.99 * 0.25 * std::f32::consts::TAU;
 
+    /// Avoids breaking the view by zooming in too far.
+    pub const MIN_ORBIT_DISTANCE: f32 = Eye::PERSPECTIVE_NEAR_PLANE * 2.0;
+
     fn get_eye(&self) -> Eye {
         Eye {
             world_from_rub_view: IsoTransform::look_at_rh(
                 self.pos,
-                if self.pos.distance_squared(self.look_target) < 1e-6 {
+                // Check that the positions we base look target on are far enough
+                // apart here because if the eye is this zoomed in, look target
+                // likely is a value that can drastically change from small inputs
+                // because of float precision.
+                if self.pos.distance_squared(self.look_target)
+                    < (Self::MIN_ORBIT_DISTANCE * 0.5).powi(2)
+                {
                     self.pos + Vec3::Y
                 } else {
                     self.look_target
@@ -488,7 +499,7 @@ impl EyeController {
         match self.kind {
             Eye3DKind::Orbital => {
                 let radius = self.pos.distance(self.look_target);
-                let new_radius = radius / zoom_factor;
+                let new_radius = (radius / zoom_factor).at_least(Self::MIN_ORBIT_DISTANCE);
 
                 // The user may be scrolling to move the camera closer, but are not realizing
                 // the radius is now tiny.
diff --git a/crates/viewer/re_view_spatial/src/view_3d.rs b/crates/viewer/re_view_spatial/src/view_3d.rs
index e403b2c732f9..61e20d79ca3d 100644
--- a/crates/viewer/re_view_spatial/src/view_3d.rs
+++ b/crates/viewer/re_view_spatial/src/view_3d.rs
@@ -1,4 +1,5 @@
 use ahash::HashSet;
+use egui::NumExt as _;
 use glam::Vec3;
 use itertools::Itertools as _;
 use nohash_hasher::IntSet;
@@ -197,6 +198,8 @@ impl ViewClass for SpatialView3D {
                     radius = 1.0;
                 }
 
+                radius = radius.at_least(crate::eye::EyeController::MIN_ORBIT_DISTANCE);
+
 
                 let scene_view_coordinates =
                     view_state.state_3d.scene_view_coordinates.unwrap_or_default();

From 5beb61e5313ea41bb8ed6ced62aa0eeb6e896aec Mon Sep 17 00:00:00 2001
From: Isaac Blankenau <119615777+iblnkn@users.noreply.github.com>
Date: Mon, 16 Mar 2026 19:41:50 +0100
Subject: [PATCH 154/513] Add 5% vertical margin to time series plot bounds

### Related
* Closes
[RR-4072](https://linear.app/rerun/issue/RR-4072/plot-bound-adjustment)

### What

- Adds a 5% vertical margin to the auto-computed y-axis range for time
series plots. Previously, the min/max bounds were set exactly to the
min/max of the displayed data, causing data points to touch the
top/bottom edges of the plot.
- The margin is applied in the fallback provider, so it only affects
auto-computed ranges. Explicit user-set ranges (from
  panning/zooming) are not modified.

---------

Source-Ref: 4de83752c96da395d1d54b420d504528493bca28
Co-authored-by: Wumpf <1220815+Wumpf@users.noreply.github.com>
---
 crates/viewer/re_view_time_series/src/fallbacks.rs       | 7 ++++---
 crates/viewer/re_view_time_series/src/view_class.rs      | 9 +++++++++
 ...blueprint_overrides_and_defaults_with_time_series.png | 4 ++--
 ..._overrides_and_defaults_with_time_series_absolute.png | 4 ++--
 ..._and_defaults_with_time_series_absolute_until_end.png | 4 ++--
 ...rides_and_defaults_with_time_series_around_cursor.png | 4 ++--
 ...overrides_and_defaults_with_time_series_at_cursor.png | 4 ++--
 ...verrides_and_defaults_with_time_series_everything.png | 4 ++--
 ...nd_defaults_with_time_series_start_until_absolute.png | 4 ++--
 .../tests/snapshots/bootstrapped_secondaries_full.png    | 4 ++--
 .../tests/snapshots/bootstrapped_secondaries_partial.png | 4 ++--
 .../tests/snapshots/clear_series_points_and_line.png     | 4 ++--
 ...lear_series_points_and_line_two_series_per_entity.png | 4 ++--
 .../tests/snapshots/explicit_component_mapping.png       | 4 ++--
 .../snapshots/explicit_component_mapping_nested.png      | 4 ++--
 .../tests/snapshots/interpolation_mode_Linear.png        | 4 ++--
 .../tests/snapshots/interpolation_mode_StepAfter.png     | 4 ++--
 .../tests/snapshots/interpolation_mode_StepBefore.png    | 4 ++--
 .../tests/snapshots/interpolation_mode_StepMid.png       | 4 ++--
 ...perties_multiple_properties_two_series_per_entity.png | 4 ++--
 .../snapshots/line_properties_two_series_per_entity.png  | 4 ++--
 .../snapshots/per_series_visibility_show_second_only.png | 4 ++--
 .../snapshots/per_series_visibility_splat_false.png      | 4 ++--
 .../tests/snapshots/per_series_visibility_splat_true.png | 4 ++--
 ...perties_multiple_properties_two_series_per_entity.png | 4 ++--
 .../snapshots/point_properties_two_series_per_entity.png | 4 ++--
 .../snapshots/special_characters_in_entity_path.png      | 4 ++--
 .../tests/snapshots/transform3d_time_series.png          | 4 ++--
 .../visible_time_range_absolute_until_end_view_data.png  | 4 ++--
 ...sible_time_range_absolute_until_end_view_timeline.png | 4 ++--
 .../snapshots/visible_time_range_absolute_view_data.png  | 4 ++--
 .../visible_time_range_absolute_view_timeline.png        | 4 ++--
 .../visible_time_range_around_cursor_view_data.png       | 4 ++--
 .../visible_time_range_around_cursor_view_timeline.png   | 4 ++--
 .../visible_time_range_everything_view_data.png          | 4 ++--
 .../visible_time_range_everything_view_timeline.png      | 4 ++--
 ...visible_time_range_start_until_absolute_view_data.png | 4 ++--
 ...ble_time_range_start_until_absolute_view_timeline.png | 4 ++--
 .../snapshots/all_view_selection_uis/Dark/TimeSeries.png | 4 ++--
 .../all_view_selection_uis/Light/TimeSeries.png          | 4 ++--
 .../tests/snapshots/add_visualizer_axes_3.png            | 4 ++--
 .../tests/snapshots/drop_multiple_streams_to_view_1.png  | 4 ++--
 .../tests/snapshots/drop_multiple_streams_to_view_2.png  | 4 ++--
 .../tests/snapshots/drop_multiple_streams_to_view_3.png  | 4 ++--
 .../tests/snapshots/drop_multiple_streams_to_view_4.png  | 4 ++--
 .../tests/snapshots/drop_multiple_streams_to_view_5.png  | 4 ++--
 .../tests/snapshots/drop_multiple_streams_to_view_6.png  | 4 ++--
 .../tests/snapshots/drop_stream_to_view_1.png            | 4 ++--
 .../tests/snapshots/drop_stream_to_view_2.png            | 4 ++--
 .../tests/snapshots/drop_stream_to_view_3.png            | 4 ++--
 .../tests/snapshots/drop_stream_to_view_4.png            | 4 ++--
 .../tests/snapshots/drop_stream_to_view_5.png            | 4 ++--
 .../tests/snapshots/drop_stream_to_view_6.png            | 4 ++--
 ...per_visualizer_instruction_errors_1_warnings_only.png | 4 ++--
 ...sualizer_instruction_errors_1b_warnings_only_menu.png | 4 ++--
 ...zer_instruction_errors_2_warnings_and_errors_menu.png | 4 ++--
 ...truction_errors_2b_warnings_and_errors_dataresult.png | 4 ++--
 .../per_visualizer_instruction_errors_3_errors_only.png  | 4 ++--
 ...visualizer_instruction_errors_3b_errors_only_menu.png | 4 ++--
 .../tests/snapshots/series_count_exceeds_max.png         | 4 ++--
 .../tests/snapshots/simplify_container_hierarchy_3.png   | 4 ++--
 .../tests/snapshots/simplify_root_hierarchy_3.png        | 4 ++--
 .../tests/snapshots/view_defaults.png                    | 4 ++--
 .../tests/snapshots/view_visualizers_1_initial.png       | 4 ++--
 .../snapshots/view_visualizers_2_plots_view_selected.png | 4 ++--
 .../snapshots/view_visualizers_3_other_view_selected.png | 4 ++--
 .../snapshots/view_visualizers_4_hover_sin_line.png      | 4 ++--
 .../tests/snapshots/view_visualizers_5_after_hide.png    | 4 ++--
 .../view_visualizers_6_hover_different_after_hide.png    | 4 ++--
 .../snapshots/view_visualizers_add_1_view_selected.png   | 4 ++--
 .../snapshots/view_visualizers_add_2_popup_open.png      | 4 ++--
 .../view_visualizers_add_multi_1_starting_state.png      | 4 ++--
 .../view_visualizers_add_multi_2_selected_visualizer.png | 4 ++--
 .../view_visualizers_add_multi_3_removed_visualizer.png  | 4 ++--
 .../view_visualizers_add_multi_4_view_selected_again.png | 4 ++--
 .../view_visualizers_add_multi_5_popup_open.png          | 4 ++--
 .../view_visualizers_add_multi_6_first_added.png         | 4 ++--
 .../view_visualizers_add_multi_7_second_added.png        | 4 ++--
 .../tests/snapshots/view_visualizers_ctx_menu_1_open.png | 4 ++--
 .../snapshots/view_visualizers_ctx_menu_2_after_hide.png | 4 ++--
 .../view_visualizers_ctx_menu_3_show_option.png          | 4 ++--
 .../snapshots/view_visualizers_ctx_menu_4_after_show.png | 4 ++--
 .../view_visualizers_ctx_menu_5_after_remove.png         | 4 ++--
 .../view_visualizers_multi_scalar_view_selected.png      | 4 ++--
 84 files changed, 177 insertions(+), 167 deletions(-)

diff --git a/crates/viewer/re_view_time_series/src/fallbacks.rs b/crates/viewer/re_view_time_series/src/fallbacks.rs
index 5411567d3a44..6fe460422895 100644
--- a/crates/viewer/re_view_time_series/src/fallbacks.rs
+++ b/crates/viewer/re_view_time_series/src/fallbacks.rs
@@ -7,7 +7,7 @@ use re_sdk_types::{
 use re_viewer_context::ViewStateExt as _;
 
 use crate::MAX_NUM_ITEMS_IN_PLOT_LEGEND_BEFORE_HIDDEN;
-use crate::view_class::{TimeSeriesViewState, make_range_sane};
+use crate::view_class::{TimeSeriesViewState, add_margin_to_range, make_range_sane};
 
 /// Register fallback providers for TimeSeriesView-related components and view properties.
 pub fn register_fallbacks(system_registry: &mut re_viewer_context::ViewSystemRegistrator<'_>) {
@@ -102,10 +102,11 @@ pub fn register_fallbacks(system_registry: &mut re_viewer_context::ViewSystemReg
             .as_any()
             .downcast_ref::()
             .map(|s| {
-                make_range_sane(
+                let range = make_range_sane(
                     s.scalar_range
                         .unwrap_or(re_sdk_types::components::Range1D::EMPTY),
-                )
+                );
+                add_margin_to_range(range, 0.05)
             })
             .unwrap_or_default()
     });
diff --git a/crates/viewer/re_view_time_series/src/view_class.rs b/crates/viewer/re_view_time_series/src/view_class.rs
index 371e0642489e..0775e259ffa0 100644
--- a/crates/viewer/re_view_time_series/src/view_class.rs
+++ b/crates/viewer/re_view_time_series/src/view_class.rs
@@ -1447,6 +1447,15 @@ fn round_nanos_to_start_of_day(ns: i64) -> i64 {
     (ns.saturating_add(nanos_per_day / 2)) / nanos_per_day * nanos_per_day
 }
 
+/// Add a relative margin to a range so the data doesn't touch the plot edges.
+///
+/// `fraction` is the fraction of the range to add on each side (e.g. 0.05 = 5%).
+pub fn add_margin_to_range(range: Range1D, fraction: f64) -> Range1D {
+    let span = range.end() - range.start();
+    let margin = span * fraction;
+    Range1D::new(range.start() - margin, range.end() + margin)
+}
+
 /// Make sure the range is finite and positive, or `egui_plot` might be buggy.
 pub fn make_range_sane(y_range: Range1D) -> Range1D {
     let (mut start, mut end) = (y_range.start(), y_range.end());
diff --git a/crates/viewer/re_view_time_series/tests/snapshots/blueprint_overrides_and_defaults_with_time_series.png b/crates/viewer/re_view_time_series/tests/snapshots/blueprint_overrides_and_defaults_with_time_series.png
index 0bf1aff908f5..26ced1f841b5 100644
--- a/crates/viewer/re_view_time_series/tests/snapshots/blueprint_overrides_and_defaults_with_time_series.png
+++ b/crates/viewer/re_view_time_series/tests/snapshots/blueprint_overrides_and_defaults_with_time_series.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:d51edc8fd0f48e87c01cafaf1362d739ca4c80ce9d6239673c3a1c3c5d54a7bc
-size 21041
+oid sha256:e58ee1f23f869e11893bce528e5b03026233bcc639346005b29e9bcd199313aa
+size 21616
diff --git a/crates/viewer/re_view_time_series/tests/snapshots/blueprint_overrides_and_defaults_with_time_series_absolute.png b/crates/viewer/re_view_time_series/tests/snapshots/blueprint_overrides_and_defaults_with_time_series_absolute.png
index 58e27a5f24c2..4fcf9dffc0d4 100644
--- a/crates/viewer/re_view_time_series/tests/snapshots/blueprint_overrides_and_defaults_with_time_series_absolute.png
+++ b/crates/viewer/re_view_time_series/tests/snapshots/blueprint_overrides_and_defaults_with_time_series_absolute.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:7cbd9f6acd37355e3c7923ba55873f478b1d22c3f02ddda2549ce177534a20db
-size 10877
+oid sha256:56e6809e31e334fa638f6331e60bb9bf08531ea936c997cfd35bb34d37c9d1f4
+size 11398
diff --git a/crates/viewer/re_view_time_series/tests/snapshots/blueprint_overrides_and_defaults_with_time_series_absolute_until_end.png b/crates/viewer/re_view_time_series/tests/snapshots/blueprint_overrides_and_defaults_with_time_series_absolute_until_end.png
index e7f049556793..7896bad3f007 100644
--- a/crates/viewer/re_view_time_series/tests/snapshots/blueprint_overrides_and_defaults_with_time_series_absolute_until_end.png
+++ b/crates/viewer/re_view_time_series/tests/snapshots/blueprint_overrides_and_defaults_with_time_series_absolute_until_end.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:be338383320313866479655faef4035b40814fa1d94c4010c1fe43267c6f20e7
-size 16475
+oid sha256:9ae417a5a12b3756aaaa86b23f43a9b2c6404e4e20fdc3f5b83fa9ab05e8af78
+size 17083
diff --git a/crates/viewer/re_view_time_series/tests/snapshots/blueprint_overrides_and_defaults_with_time_series_around_cursor.png b/crates/viewer/re_view_time_series/tests/snapshots/blueprint_overrides_and_defaults_with_time_series_around_cursor.png
index 9bd47de42a24..749916c3c865 100644
--- a/crates/viewer/re_view_time_series/tests/snapshots/blueprint_overrides_and_defaults_with_time_series_around_cursor.png
+++ b/crates/viewer/re_view_time_series/tests/snapshots/blueprint_overrides_and_defaults_with_time_series_around_cursor.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:365da76c48a65bd90d50839061806b58c370faf88ac438e9d9d73c23c4fcf8ce
-size 16500
+oid sha256:c63082acf277c7c24c6f8d5181c68a7bca12d945a0a5a0adb25cea61942005ac
+size 17018
diff --git a/crates/viewer/re_view_time_series/tests/snapshots/blueprint_overrides_and_defaults_with_time_series_at_cursor.png b/crates/viewer/re_view_time_series/tests/snapshots/blueprint_overrides_and_defaults_with_time_series_at_cursor.png
index 5b048f348d88..185538a91445 100644
--- a/crates/viewer/re_view_time_series/tests/snapshots/blueprint_overrides_and_defaults_with_time_series_at_cursor.png
+++ b/crates/viewer/re_view_time_series/tests/snapshots/blueprint_overrides_and_defaults_with_time_series_at_cursor.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:0fc5e96175f9e458c4debb80444b87ec74f54eb7d7690ad147553a28dfca6c31
-size 14574
+oid sha256:648ada01eca6751f778b0cf472ce264ad441793c92f12483636897688db36ab8
+size 15211
diff --git a/crates/viewer/re_view_time_series/tests/snapshots/blueprint_overrides_and_defaults_with_time_series_everything.png b/crates/viewer/re_view_time_series/tests/snapshots/blueprint_overrides_and_defaults_with_time_series_everything.png
index 0bf1aff908f5..26ced1f841b5 100644
--- a/crates/viewer/re_view_time_series/tests/snapshots/blueprint_overrides_and_defaults_with_time_series_everything.png
+++ b/crates/viewer/re_view_time_series/tests/snapshots/blueprint_overrides_and_defaults_with_time_series_everything.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:d51edc8fd0f48e87c01cafaf1362d739ca4c80ce9d6239673c3a1c3c5d54a7bc
-size 21041
+oid sha256:e58ee1f23f869e11893bce528e5b03026233bcc639346005b29e9bcd199313aa
+size 21616
diff --git a/crates/viewer/re_view_time_series/tests/snapshots/blueprint_overrides_and_defaults_with_time_series_start_until_absolute.png b/crates/viewer/re_view_time_series/tests/snapshots/blueprint_overrides_and_defaults_with_time_series_start_until_absolute.png
index d028a4743a78..8513464e22b4 100644
--- a/crates/viewer/re_view_time_series/tests/snapshots/blueprint_overrides_and_defaults_with_time_series_start_until_absolute.png
+++ b/crates/viewer/re_view_time_series/tests/snapshots/blueprint_overrides_and_defaults_with_time_series_start_until_absolute.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:781789729fec8d75603545809d05f4c81fc1696eb8284fd3ab1516ec39230890
-size 16348
+oid sha256:bbe283e2fd24db5a9475fed8147d731ec00b0326e8ac7ef55ce09d32819867b8
+size 12678
diff --git a/crates/viewer/re_view_time_series/tests/snapshots/bootstrapped_secondaries_full.png b/crates/viewer/re_view_time_series/tests/snapshots/bootstrapped_secondaries_full.png
index ca4fb8cf7bbb..e34bbffd7ae6 100644
--- a/crates/viewer/re_view_time_series/tests/snapshots/bootstrapped_secondaries_full.png
+++ b/crates/viewer/re_view_time_series/tests/snapshots/bootstrapped_secondaries_full.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:8893c6c3d216456c19c0122723041445ba35a124cc75e8a35ec64b80d83ea031
-size 16075
+oid sha256:12a6f7b359cac5efd7640103025b5ca9158608f5110883b0343581750483d047
+size 17022
diff --git a/crates/viewer/re_view_time_series/tests/snapshots/bootstrapped_secondaries_partial.png b/crates/viewer/re_view_time_series/tests/snapshots/bootstrapped_secondaries_partial.png
index ca4fb8cf7bbb..e34bbffd7ae6 100644
--- a/crates/viewer/re_view_time_series/tests/snapshots/bootstrapped_secondaries_partial.png
+++ b/crates/viewer/re_view_time_series/tests/snapshots/bootstrapped_secondaries_partial.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:8893c6c3d216456c19c0122723041445ba35a124cc75e8a35ec64b80d83ea031
-size 16075
+oid sha256:12a6f7b359cac5efd7640103025b5ca9158608f5110883b0343581750483d047
+size 17022
diff --git a/crates/viewer/re_view_time_series/tests/snapshots/clear_series_points_and_line.png b/crates/viewer/re_view_time_series/tests/snapshots/clear_series_points_and_line.png
index e8ff9bf4fa19..1a287f45a626 100644
--- a/crates/viewer/re_view_time_series/tests/snapshots/clear_series_points_and_line.png
+++ b/crates/viewer/re_view_time_series/tests/snapshots/clear_series_points_and_line.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:3af8a3c34d843bbb3c1d64afa3f045c09ccfc24064657aeac84abedda2dc2e4b
-size 23791
+oid sha256:ad122f7e9817e64298f3cbab6315e991cf9fa468237e50f883f9f3e7938085d3
+size 24243
diff --git a/crates/viewer/re_view_time_series/tests/snapshots/clear_series_points_and_line_two_series_per_entity.png b/crates/viewer/re_view_time_series/tests/snapshots/clear_series_points_and_line_two_series_per_entity.png
index ce9e8908c21f..603415ee8a28 100644
--- a/crates/viewer/re_view_time_series/tests/snapshots/clear_series_points_and_line_two_series_per_entity.png
+++ b/crates/viewer/re_view_time_series/tests/snapshots/clear_series_points_and_line_two_series_per_entity.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:a420dc7866228340dc912f055cce1e4ce61e44fe3431bf93804a8623f7b3f388
-size 33614
+oid sha256:fcb4bbf48ffa1f94f403e6056bc8fb1727819e5fcc690493cf25bffc46d10b7d
+size 34043
diff --git a/crates/viewer/re_view_time_series/tests/snapshots/explicit_component_mapping.png b/crates/viewer/re_view_time_series/tests/snapshots/explicit_component_mapping.png
index c67a50e90f7e..bf1307adbe52 100644
--- a/crates/viewer/re_view_time_series/tests/snapshots/explicit_component_mapping.png
+++ b/crates/viewer/re_view_time_series/tests/snapshots/explicit_component_mapping.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:8431ac56ede3ea822dab389635bc1865e366f5a5b809b2e83fe6813b03c77831
-size 26652
+oid sha256:2a5a0a1838c62ce0302b98321f17f077956227b4b08b25d6ba3fd99ff622c66b
+size 27223
diff --git a/crates/viewer/re_view_time_series/tests/snapshots/explicit_component_mapping_nested.png b/crates/viewer/re_view_time_series/tests/snapshots/explicit_component_mapping_nested.png
index d31163cd4e29..c1b809540c7d 100644
--- a/crates/viewer/re_view_time_series/tests/snapshots/explicit_component_mapping_nested.png
+++ b/crates/viewer/re_view_time_series/tests/snapshots/explicit_component_mapping_nested.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:92331b1049bfede88cf8754ad538a6b6418b47f22b3267cfda26a7e0160c4ef2
-size 40756
+oid sha256:87fa458a6afb883e57d237ee2f94f02d98671a69f68d225a431df97e8bb5c068
+size 40853
diff --git a/crates/viewer/re_view_time_series/tests/snapshots/interpolation_mode_Linear.png b/crates/viewer/re_view_time_series/tests/snapshots/interpolation_mode_Linear.png
index 5844c608d992..642e210079ae 100644
--- a/crates/viewer/re_view_time_series/tests/snapshots/interpolation_mode_Linear.png
+++ b/crates/viewer/re_view_time_series/tests/snapshots/interpolation_mode_Linear.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:70f618e54bcd56146287482253c6f405f185975d494fd44211b0d49df5e67c54
-size 23723
+oid sha256:2e2cb0ae7af3337b51dcb5025870bd52fe23e8e4fa6c41e322dabc8905d5e4c4
+size 23833
diff --git a/crates/viewer/re_view_time_series/tests/snapshots/interpolation_mode_StepAfter.png b/crates/viewer/re_view_time_series/tests/snapshots/interpolation_mode_StepAfter.png
index f0bcdff987a2..fdde771dfb63 100644
--- a/crates/viewer/re_view_time_series/tests/snapshots/interpolation_mode_StepAfter.png
+++ b/crates/viewer/re_view_time_series/tests/snapshots/interpolation_mode_StepAfter.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:0d673ca6a91a28841e264a9b398d7652aca2254339bf6b1f403aa47277ea4118
-size 22285
+oid sha256:24adc87a49c64d48148dc09c29a4d26d300d7155f05e5f3fcc5a61e19d7e47f1
+size 22846
diff --git a/crates/viewer/re_view_time_series/tests/snapshots/interpolation_mode_StepBefore.png b/crates/viewer/re_view_time_series/tests/snapshots/interpolation_mode_StepBefore.png
index 0db0b49a56a8..fac38f1c5149 100644
--- a/crates/viewer/re_view_time_series/tests/snapshots/interpolation_mode_StepBefore.png
+++ b/crates/viewer/re_view_time_series/tests/snapshots/interpolation_mode_StepBefore.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:a701ef168e3bfec078c2011c4258d08aec71152a04b81c15397aba20e464bb07
-size 23011
+oid sha256:b90a982505471e4ed8fcca83bc57f391935ad5d3c69d1787e6803ef472d66ffb
+size 23464
diff --git a/crates/viewer/re_view_time_series/tests/snapshots/interpolation_mode_StepMid.png b/crates/viewer/re_view_time_series/tests/snapshots/interpolation_mode_StepMid.png
index fa5ac3f9b7e0..824c0c21b61e 100644
--- a/crates/viewer/re_view_time_series/tests/snapshots/interpolation_mode_StepMid.png
+++ b/crates/viewer/re_view_time_series/tests/snapshots/interpolation_mode_StepMid.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:c8e96d1fcfb474e86cebf7a3addb59b556661fe935892065fc1951060ce9a35f
-size 22412
+oid sha256:d1500be02956b5faaddafea74227b353f4222a710e2f2146dc3847e8d6113228
+size 22985
diff --git a/crates/viewer/re_view_time_series/tests/snapshots/line_properties_multiple_properties_two_series_per_entity.png b/crates/viewer/re_view_time_series/tests/snapshots/line_properties_multiple_properties_two_series_per_entity.png
index a5e6b271f5a9..0e98f1fd09fd 100644
--- a/crates/viewer/re_view_time_series/tests/snapshots/line_properties_multiple_properties_two_series_per_entity.png
+++ b/crates/viewer/re_view_time_series/tests/snapshots/line_properties_multiple_properties_two_series_per_entity.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:59b935260759e210e848d815b48da12c28f1e7d71a95fff4cc951b0f4f2717c7
-size 42866
+oid sha256:b5081e8a3314ed54639c8d2a150cdcfcb13a9e2bd461a7454b195d197c88cbf0
+size 43890
diff --git a/crates/viewer/re_view_time_series/tests/snapshots/line_properties_two_series_per_entity.png b/crates/viewer/re_view_time_series/tests/snapshots/line_properties_two_series_per_entity.png
index c1087e687305..534ade5c4094 100644
--- a/crates/viewer/re_view_time_series/tests/snapshots/line_properties_two_series_per_entity.png
+++ b/crates/viewer/re_view_time_series/tests/snapshots/line_properties_two_series_per_entity.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:007874e4ca681e8bf1ea064e52e5cd1c2092e502211a2e4234f38acb3dce050a
-size 44419
+oid sha256:47f95fe62215e01b12c52b34d36fc30f48368d3372f8b9fc1433cf0156a97ddb
+size 44832
diff --git a/crates/viewer/re_view_time_series/tests/snapshots/per_series_visibility_show_second_only.png b/crates/viewer/re_view_time_series/tests/snapshots/per_series_visibility_show_second_only.png
index 8f85509a92e4..307e63dd1ad9 100644
--- a/crates/viewer/re_view_time_series/tests/snapshots/per_series_visibility_show_second_only.png
+++ b/crates/viewer/re_view_time_series/tests/snapshots/per_series_visibility_show_second_only.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:bfc21a10a12260e603801e49c5fdee33d321e71c03496443ad440c46e4fb5a83
-size 23756
+oid sha256:1bf404a20126fdcb45258b7ba03dff78d050e355638e2eb97e93d210c8f6d851
+size 24753
diff --git a/crates/viewer/re_view_time_series/tests/snapshots/per_series_visibility_splat_false.png b/crates/viewer/re_view_time_series/tests/snapshots/per_series_visibility_splat_false.png
index 567003d39c5a..653051baeb38 100644
--- a/crates/viewer/re_view_time_series/tests/snapshots/per_series_visibility_splat_false.png
+++ b/crates/viewer/re_view_time_series/tests/snapshots/per_series_visibility_splat_false.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:fc4516523144e36e257bd5d7fefa152ed130af279c5168022e8f367af72a9f69
-size 16917
+oid sha256:64aa06871ac2202519e79e01d344d752b972b5513942ba6c0aaefdc44ec4f586
+size 17779
diff --git a/crates/viewer/re_view_time_series/tests/snapshots/per_series_visibility_splat_true.png b/crates/viewer/re_view_time_series/tests/snapshots/per_series_visibility_splat_true.png
index 04c237955a86..9385adfe94ac 100644
--- a/crates/viewer/re_view_time_series/tests/snapshots/per_series_visibility_splat_true.png
+++ b/crates/viewer/re_view_time_series/tests/snapshots/per_series_visibility_splat_true.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:77e60eacbd3dce19afc24708ff66b056d7a5fdd7b5d1c99986351d9f404288f9
-size 30220
+oid sha256:5d88c0207a84db1b52152a61d1b9263a575b6c602dcc859f0a6dbfeb7600c39c
+size 30742
diff --git a/crates/viewer/re_view_time_series/tests/snapshots/point_properties_multiple_properties_two_series_per_entity.png b/crates/viewer/re_view_time_series/tests/snapshots/point_properties_multiple_properties_two_series_per_entity.png
index 26420c19931e..45d11857af06 100644
--- a/crates/viewer/re_view_time_series/tests/snapshots/point_properties_multiple_properties_two_series_per_entity.png
+++ b/crates/viewer/re_view_time_series/tests/snapshots/point_properties_multiple_properties_two_series_per_entity.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:0283216e837db496ba86f38573481f9fe024c76f9713fbe3051f2c4607c8afdd
-size 38728
+oid sha256:7d7844db8e320834df767a42f579d46d25bb281ca2242b63f9fab627723b92c8
+size 40443
diff --git a/crates/viewer/re_view_time_series/tests/snapshots/point_properties_two_series_per_entity.png b/crates/viewer/re_view_time_series/tests/snapshots/point_properties_two_series_per_entity.png
index 89d681086810..f85fdc682d52 100644
--- a/crates/viewer/re_view_time_series/tests/snapshots/point_properties_two_series_per_entity.png
+++ b/crates/viewer/re_view_time_series/tests/snapshots/point_properties_two_series_per_entity.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:ace233bf1aefecf6c29aeb52b3e9793319b360c53f98d8e088eb863cae6f05e1
-size 41064
+oid sha256:619faac9492d0201a4c1e41f2ef13775d432f98fba6703c3a31c62547a4c3927
+size 42310
diff --git a/crates/viewer/re_view_time_series/tests/snapshots/special_characters_in_entity_path.png b/crates/viewer/re_view_time_series/tests/snapshots/special_characters_in_entity_path.png
index 0e0d105a6b2b..3656a1c5048d 100644
--- a/crates/viewer/re_view_time_series/tests/snapshots/special_characters_in_entity_path.png
+++ b/crates/viewer/re_view_time_series/tests/snapshots/special_characters_in_entity_path.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:e798a69e3ba7877fd76babbcbf9958365598ebf8402c23c21b23efcaab6396ec
-size 22471
+oid sha256:298e87ab5d9486a68886577a40934e4c9b3f250377c3db9f0370140ba9a40d83
+size 22580
diff --git a/crates/viewer/re_view_time_series/tests/snapshots/transform3d_time_series.png b/crates/viewer/re_view_time_series/tests/snapshots/transform3d_time_series.png
index 1205281523ae..8e259188ea5d 100644
--- a/crates/viewer/re_view_time_series/tests/snapshots/transform3d_time_series.png
+++ b/crates/viewer/re_view_time_series/tests/snapshots/transform3d_time_series.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:75da9e4a61790ff222dc22ef0a5fb0f73f68ade250b998bfc0ddd279598f980f
-size 50861
+oid sha256:b358654fab707e22a4ecde8686ce1b03f8dc0ff5b5786f4eedb5b416e6495afe
+size 49975
diff --git a/crates/viewer/re_view_time_series/tests/snapshots/visible_time_range_absolute_until_end_view_data.png b/crates/viewer/re_view_time_series/tests/snapshots/visible_time_range_absolute_until_end_view_data.png
index e7f049556793..7896bad3f007 100644
--- a/crates/viewer/re_view_time_series/tests/snapshots/visible_time_range_absolute_until_end_view_data.png
+++ b/crates/viewer/re_view_time_series/tests/snapshots/visible_time_range_absolute_until_end_view_data.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:be338383320313866479655faef4035b40814fa1d94c4010c1fe43267c6f20e7
-size 16475
+oid sha256:9ae417a5a12b3756aaaa86b23f43a9b2c6404e4e20fdc3f5b83fa9ab05e8af78
+size 17083
diff --git a/crates/viewer/re_view_time_series/tests/snapshots/visible_time_range_absolute_until_end_view_timeline.png b/crates/viewer/re_view_time_series/tests/snapshots/visible_time_range_absolute_until_end_view_timeline.png
index ef684e53fb6a..9b66cd7a0d9b 100644
--- a/crates/viewer/re_view_time_series/tests/snapshots/visible_time_range_absolute_until_end_view_timeline.png
+++ b/crates/viewer/re_view_time_series/tests/snapshots/visible_time_range_absolute_until_end_view_timeline.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:32fc6ad2b49338d3133ff9528f9b9096c2997c88ee65450c91f5afe73ee74bb9
-size 19557
+oid sha256:0efc92e9db4655ad836e53ccd44983afdc138f01c3b4ee2323cc7fc2e93d8444
+size 20135
diff --git a/crates/viewer/re_view_time_series/tests/snapshots/visible_time_range_absolute_view_data.png b/crates/viewer/re_view_time_series/tests/snapshots/visible_time_range_absolute_view_data.png
index 58e27a5f24c2..4fcf9dffc0d4 100644
--- a/crates/viewer/re_view_time_series/tests/snapshots/visible_time_range_absolute_view_data.png
+++ b/crates/viewer/re_view_time_series/tests/snapshots/visible_time_range_absolute_view_data.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:7cbd9f6acd37355e3c7923ba55873f478b1d22c3f02ddda2549ce177534a20db
-size 10877
+oid sha256:56e6809e31e334fa638f6331e60bb9bf08531ea936c997cfd35bb34d37c9d1f4
+size 11398
diff --git a/crates/viewer/re_view_time_series/tests/snapshots/visible_time_range_absolute_view_timeline.png b/crates/viewer/re_view_time_series/tests/snapshots/visible_time_range_absolute_view_timeline.png
index bf6cb5fa377d..497b2420972a 100644
--- a/crates/viewer/re_view_time_series/tests/snapshots/visible_time_range_absolute_view_timeline.png
+++ b/crates/viewer/re_view_time_series/tests/snapshots/visible_time_range_absolute_view_timeline.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:325c9e0cb6f9c33e1b92013bbfd0dc031ec2eac752f4177f717902a0c8909079
-size 16962
+oid sha256:eabd3676389095ca39a5d0b0d8ac59cc90e56568a7b33369d184a5ee76eb72a7
+size 17805
diff --git a/crates/viewer/re_view_time_series/tests/snapshots/visible_time_range_around_cursor_view_data.png b/crates/viewer/re_view_time_series/tests/snapshots/visible_time_range_around_cursor_view_data.png
index 315f4bf421ec..3a57a976b709 100644
--- a/crates/viewer/re_view_time_series/tests/snapshots/visible_time_range_around_cursor_view_data.png
+++ b/crates/viewer/re_view_time_series/tests/snapshots/visible_time_range_around_cursor_view_data.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:cbab6b4db34634af90cc1498e7fb1c34f20ca1347e63983b8280f4920ac30cd4
-size 14423
+oid sha256:1576fc18c8d3269f24c1ab2da09327361f55e87766d88cd590dc10219b28a7b1
+size 14634
diff --git a/crates/viewer/re_view_time_series/tests/snapshots/visible_time_range_around_cursor_view_timeline.png b/crates/viewer/re_view_time_series/tests/snapshots/visible_time_range_around_cursor_view_timeline.png
index 1c57807a9686..c698e051120a 100644
--- a/crates/viewer/re_view_time_series/tests/snapshots/visible_time_range_around_cursor_view_timeline.png
+++ b/crates/viewer/re_view_time_series/tests/snapshots/visible_time_range_around_cursor_view_timeline.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:344633d8f72678b1b5f620fb04c5c5e1616759a4f3962d5e796b128d938853ec
-size 18286
+oid sha256:212915a1431ae17160229499c06583df001dec070a643deca6ccb93fd360feca
+size 18762
diff --git a/crates/viewer/re_view_time_series/tests/snapshots/visible_time_range_everything_view_data.png b/crates/viewer/re_view_time_series/tests/snapshots/visible_time_range_everything_view_data.png
index 0bf1aff908f5..26ced1f841b5 100644
--- a/crates/viewer/re_view_time_series/tests/snapshots/visible_time_range_everything_view_data.png
+++ b/crates/viewer/re_view_time_series/tests/snapshots/visible_time_range_everything_view_data.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:d51edc8fd0f48e87c01cafaf1362d739ca4c80ce9d6239673c3a1c3c5d54a7bc
-size 21041
+oid sha256:e58ee1f23f869e11893bce528e5b03026233bcc639346005b29e9bcd199313aa
+size 21616
diff --git a/crates/viewer/re_view_time_series/tests/snapshots/visible_time_range_everything_view_timeline.png b/crates/viewer/re_view_time_series/tests/snapshots/visible_time_range_everything_view_timeline.png
index 0bf1aff908f5..26ced1f841b5 100644
--- a/crates/viewer/re_view_time_series/tests/snapshots/visible_time_range_everything_view_timeline.png
+++ b/crates/viewer/re_view_time_series/tests/snapshots/visible_time_range_everything_view_timeline.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:d51edc8fd0f48e87c01cafaf1362d739ca4c80ce9d6239673c3a1c3c5d54a7bc
-size 21041
+oid sha256:e58ee1f23f869e11893bce528e5b03026233bcc639346005b29e9bcd199313aa
+size 21616
diff --git a/crates/viewer/re_view_time_series/tests/snapshots/visible_time_range_start_until_absolute_view_data.png b/crates/viewer/re_view_time_series/tests/snapshots/visible_time_range_start_until_absolute_view_data.png
index d028a4743a78..8513464e22b4 100644
--- a/crates/viewer/re_view_time_series/tests/snapshots/visible_time_range_start_until_absolute_view_data.png
+++ b/crates/viewer/re_view_time_series/tests/snapshots/visible_time_range_start_until_absolute_view_data.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:781789729fec8d75603545809d05f4c81fc1696eb8284fd3ab1516ec39230890
-size 16348
+oid sha256:bbe283e2fd24db5a9475fed8147d731ec00b0326e8ac7ef55ce09d32819867b8
+size 12678
diff --git a/crates/viewer/re_view_time_series/tests/snapshots/visible_time_range_start_until_absolute_view_timeline.png b/crates/viewer/re_view_time_series/tests/snapshots/visible_time_range_start_until_absolute_view_timeline.png
index 73390bd2cf90..f103077c54ec 100644
--- a/crates/viewer/re_view_time_series/tests/snapshots/visible_time_range_start_until_absolute_view_timeline.png
+++ b/crates/viewer/re_view_time_series/tests/snapshots/visible_time_range_start_until_absolute_view_timeline.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:40f0db0237f0957069aa97eec3c2e7904d4bd6fbaa908972bb9a8b0646f588b2
-size 18539
+oid sha256:3c85f45c27bbb70c2efd8bc2c57a6b3720f3db9a0dac6f894501e77d1badf227
+size 16769
diff --git a/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Dark/TimeSeries.png b/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Dark/TimeSeries.png
index d6e6e15ebb17..99fa8c83d14d 100644
--- a/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Dark/TimeSeries.png
+++ b/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Dark/TimeSeries.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:7eeb106aa38c5011184e0de763b8c9c26a414fba19a180298344abf97b7e922d
-size 32902
+oid sha256:e770784c879fdfba5ddce461366493397266b9335ea5d181bf277d68914bdc88
+size 32575
diff --git a/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Light/TimeSeries.png b/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Light/TimeSeries.png
index 4dd5e8fb9aa8..95b749774b84 100644
--- a/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Light/TimeSeries.png
+++ b/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Light/TimeSeries.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:f109ce3c6400117ba0c5aac1bf741487ffc73301ab6cca2fb070909d5568fe45
-size 32672
+oid sha256:dcce3224c33eca81bc1b155b5be48693b63ed5f0641ab3eb588f04f00b0b1266
+size 32303
diff --git a/tests/rust/re_integration_test/tests/snapshots/add_visualizer_axes_3.png b/tests/rust/re_integration_test/tests/snapshots/add_visualizer_axes_3.png
index 6405b87e7411..e1ba70d8469e 100644
--- a/tests/rust/re_integration_test/tests/snapshots/add_visualizer_axes_3.png
+++ b/tests/rust/re_integration_test/tests/snapshots/add_visualizer_axes_3.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:7b35be6f4bdd149e8c550f7314af83cd5ad5c7c23d5aea7f566ceac652559796
-size 184029
+oid sha256:93ccab959f648e9c251ca5c422f5c05a290b9354a72f73046283005676426613
+size 182843
diff --git a/tests/rust/re_integration_test/tests/snapshots/drop_multiple_streams_to_view_1.png b/tests/rust/re_integration_test/tests/snapshots/drop_multiple_streams_to_view_1.png
index c9e68703e356..6e12f258eb2b 100644
--- a/tests/rust/re_integration_test/tests/snapshots/drop_multiple_streams_to_view_1.png
+++ b/tests/rust/re_integration_test/tests/snapshots/drop_multiple_streams_to_view_1.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:3434da1d2949e696a889493a5cf44794d17e0711dcb00b0f232e360059c16ad3
-size 128711
+oid sha256:269e8c752d5e41f896c6cd59b0336e3ab5cb67d5c0097868e2bb57c7fab5479e
+size 129032
diff --git a/tests/rust/re_integration_test/tests/snapshots/drop_multiple_streams_to_view_2.png b/tests/rust/re_integration_test/tests/snapshots/drop_multiple_streams_to_view_2.png
index f38ea26d3c2c..125ef4f4a93f 100644
--- a/tests/rust/re_integration_test/tests/snapshots/drop_multiple_streams_to_view_2.png
+++ b/tests/rust/re_integration_test/tests/snapshots/drop_multiple_streams_to_view_2.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:f0c29dc8c3687680ca7dc27c67933253cfd1d8412a89d6152a2fb9956f14615b
-size 101831
+oid sha256:a2ac541e08d1827f6f050e52aca8ac2a77678e7d2719d9674e6781ddaf57f331
+size 102117
diff --git a/tests/rust/re_integration_test/tests/snapshots/drop_multiple_streams_to_view_3.png b/tests/rust/re_integration_test/tests/snapshots/drop_multiple_streams_to_view_3.png
index 6b37a88c535e..f20fadda527c 100644
--- a/tests/rust/re_integration_test/tests/snapshots/drop_multiple_streams_to_view_3.png
+++ b/tests/rust/re_integration_test/tests/snapshots/drop_multiple_streams_to_view_3.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:14c90c6be3b37825126421037b8d673d4b07865e716e7855fc92e5496615806c
-size 135605
+oid sha256:7e644d42147023c5574f6d8afe83200d08ed9921462cee48b6c2fa418ecc8d09
+size 135358
diff --git a/tests/rust/re_integration_test/tests/snapshots/drop_multiple_streams_to_view_4.png b/tests/rust/re_integration_test/tests/snapshots/drop_multiple_streams_to_view_4.png
index e73f38c351b7..c060a72208a6 100644
--- a/tests/rust/re_integration_test/tests/snapshots/drop_multiple_streams_to_view_4.png
+++ b/tests/rust/re_integration_test/tests/snapshots/drop_multiple_streams_to_view_4.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:57489a0bc3a9261ffef3af6eb3288bce2a4ed2a3ce7d535124efd6674c7b6bf4
-size 109041
+oid sha256:a633ad66ddac245df85ba924c3178d6acbef05f7dfbfb0665d68fe9d27603456
+size 108836
diff --git a/tests/rust/re_integration_test/tests/snapshots/drop_multiple_streams_to_view_5.png b/tests/rust/re_integration_test/tests/snapshots/drop_multiple_streams_to_view_5.png
index 2f320164c935..b372b70592c5 100644
--- a/tests/rust/re_integration_test/tests/snapshots/drop_multiple_streams_to_view_5.png
+++ b/tests/rust/re_integration_test/tests/snapshots/drop_multiple_streams_to_view_5.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:2c83dde2f3ed83edb8d114543b5e208025dbd11626841d1c04b79e54968b5acb
-size 108858
+oid sha256:289c0e2152479144c4a868b7f3bca446741e7aadaafb7c4275ce57afeb56466d
+size 108791
diff --git a/tests/rust/re_integration_test/tests/snapshots/drop_multiple_streams_to_view_6.png b/tests/rust/re_integration_test/tests/snapshots/drop_multiple_streams_to_view_6.png
index ccb4dfbbcf50..745995023b18 100644
--- a/tests/rust/re_integration_test/tests/snapshots/drop_multiple_streams_to_view_6.png
+++ b/tests/rust/re_integration_test/tests/snapshots/drop_multiple_streams_to_view_6.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:32df3a58b5d341bb326866fb42a02ee2d3b3129bdbca74505db7335752498ab2
-size 151865
+oid sha256:3435108fa5ef2dbcdbbf4ec37b2c8811d7b6df09541f85838cb7c295101663e6
+size 151008
diff --git a/tests/rust/re_integration_test/tests/snapshots/drop_stream_to_view_1.png b/tests/rust/re_integration_test/tests/snapshots/drop_stream_to_view_1.png
index c136a24369e9..51badcfdb2b7 100644
--- a/tests/rust/re_integration_test/tests/snapshots/drop_stream_to_view_1.png
+++ b/tests/rust/re_integration_test/tests/snapshots/drop_stream_to_view_1.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:03cde5481138dd77898ce424b3a8537b1173dc2493836d90dfde4187b14a14c6
-size 101450
+oid sha256:daa56204f9eda41fdfb4cbf0228c9125213189384dc9cd1bcc6e5f7a6d695877
+size 102101
diff --git a/tests/rust/re_integration_test/tests/snapshots/drop_stream_to_view_2.png b/tests/rust/re_integration_test/tests/snapshots/drop_stream_to_view_2.png
index c9e68703e356..6e12f258eb2b 100644
--- a/tests/rust/re_integration_test/tests/snapshots/drop_stream_to_view_2.png
+++ b/tests/rust/re_integration_test/tests/snapshots/drop_stream_to_view_2.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:3434da1d2949e696a889493a5cf44794d17e0711dcb00b0f232e360059c16ad3
-size 128711
+oid sha256:269e8c752d5e41f896c6cd59b0336e3ab5cb67d5c0097868e2bb57c7fab5479e
+size 129032
diff --git a/tests/rust/re_integration_test/tests/snapshots/drop_stream_to_view_3.png b/tests/rust/re_integration_test/tests/snapshots/drop_stream_to_view_3.png
index 108c7fff304f..7b588b792707 100644
--- a/tests/rust/re_integration_test/tests/snapshots/drop_stream_to_view_3.png
+++ b/tests/rust/re_integration_test/tests/snapshots/drop_stream_to_view_3.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:b96a10360f656a8a01f44820330c5d747593a0f2483f78310550a010f28d2db4
-size 132192
+oid sha256:a9f6ff5221d38e72166b3525c1414b12f8b4ad481d66a5cf12a19655bcec8649
+size 132344
diff --git a/tests/rust/re_integration_test/tests/snapshots/drop_stream_to_view_4.png b/tests/rust/re_integration_test/tests/snapshots/drop_stream_to_view_4.png
index c9e68703e356..6e12f258eb2b 100644
--- a/tests/rust/re_integration_test/tests/snapshots/drop_stream_to_view_4.png
+++ b/tests/rust/re_integration_test/tests/snapshots/drop_stream_to_view_4.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:3434da1d2949e696a889493a5cf44794d17e0711dcb00b0f232e360059c16ad3
-size 128711
+oid sha256:269e8c752d5e41f896c6cd59b0336e3ab5cb67d5c0097868e2bb57c7fab5479e
+size 129032
diff --git a/tests/rust/re_integration_test/tests/snapshots/drop_stream_to_view_5.png b/tests/rust/re_integration_test/tests/snapshots/drop_stream_to_view_5.png
index f357ed1e77b5..b5ea871fed1d 100644
--- a/tests/rust/re_integration_test/tests/snapshots/drop_stream_to_view_5.png
+++ b/tests/rust/re_integration_test/tests/snapshots/drop_stream_to_view_5.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:8ce7c97a657989e2d628c1cd70e08eaa53303ee33a4a1a5b3ccc14b6589702dc
-size 130019
+oid sha256:7338bb69ecc3377836885bbf449155753c15b353c5d743ba900086ca4637ea21
+size 130470
diff --git a/tests/rust/re_integration_test/tests/snapshots/drop_stream_to_view_6.png b/tests/rust/re_integration_test/tests/snapshots/drop_stream_to_view_6.png
index 6b37a88c535e..f20fadda527c 100644
--- a/tests/rust/re_integration_test/tests/snapshots/drop_stream_to_view_6.png
+++ b/tests/rust/re_integration_test/tests/snapshots/drop_stream_to_view_6.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:14c90c6be3b37825126421037b8d673d4b07865e716e7855fc92e5496615806c
-size 135605
+oid sha256:7e644d42147023c5574f6d8afe83200d08ed9921462cee48b6c2fa418ecc8d09
+size 135358
diff --git a/tests/rust/re_integration_test/tests/snapshots/per_visualizer_instruction_errors_1_warnings_only.png b/tests/rust/re_integration_test/tests/snapshots/per_visualizer_instruction_errors_1_warnings_only.png
index 6510e1df48d6..ec85cbb3e2cc 100644
--- a/tests/rust/re_integration_test/tests/snapshots/per_visualizer_instruction_errors_1_warnings_only.png
+++ b/tests/rust/re_integration_test/tests/snapshots/per_visualizer_instruction_errors_1_warnings_only.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:5a5d0bf3eab9567c6ac37dbb75c4d426c0ee713d9e7981746c0fcc6b7ed9cc69
-size 88272
+oid sha256:9df598c1f578226146767ebb50db61d86fa106eae4725d929eb75416e7b5e2a1
+size 89168
diff --git a/tests/rust/re_integration_test/tests/snapshots/per_visualizer_instruction_errors_1b_warnings_only_menu.png b/tests/rust/re_integration_test/tests/snapshots/per_visualizer_instruction_errors_1b_warnings_only_menu.png
index fa8d48513e7d..3fefea25c136 100644
--- a/tests/rust/re_integration_test/tests/snapshots/per_visualizer_instruction_errors_1b_warnings_only_menu.png
+++ b/tests/rust/re_integration_test/tests/snapshots/per_visualizer_instruction_errors_1b_warnings_only_menu.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:b119f26d308154d30193acf046db73cfce1bb2ba7200fdf3d715f396c710dcde
-size 98943
+oid sha256:9cfb002a19ce9252673b64885f22ac854e66ded4a1b7ff078615b1c65a432d4f
+size 100045
diff --git a/tests/rust/re_integration_test/tests/snapshots/per_visualizer_instruction_errors_2_warnings_and_errors_menu.png b/tests/rust/re_integration_test/tests/snapshots/per_visualizer_instruction_errors_2_warnings_and_errors_menu.png
index 10dcd7d1260c..8b01a4ed8289 100644
--- a/tests/rust/re_integration_test/tests/snapshots/per_visualizer_instruction_errors_2_warnings_and_errors_menu.png
+++ b/tests/rust/re_integration_test/tests/snapshots/per_visualizer_instruction_errors_2_warnings_and_errors_menu.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:08e210368f8259b9ddcd2f58fd19aa597470fe9d022afaf4f95ee384bb7c9f4c
-size 109392
+oid sha256:55ea58b03e50301a1f15d3c4313ad0428bbf9170b48f85ca4cf16f0c67f77dba
+size 110047
diff --git a/tests/rust/re_integration_test/tests/snapshots/per_visualizer_instruction_errors_2b_warnings_and_errors_dataresult.png b/tests/rust/re_integration_test/tests/snapshots/per_visualizer_instruction_errors_2b_warnings_and_errors_dataresult.png
index 5493c0837f76..b7a5c5afe08b 100644
--- a/tests/rust/re_integration_test/tests/snapshots/per_visualizer_instruction_errors_2b_warnings_and_errors_dataresult.png
+++ b/tests/rust/re_integration_test/tests/snapshots/per_visualizer_instruction_errors_2b_warnings_and_errors_dataresult.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:6768062ff424107d085be8099d28a332495be680f1605926675b3aaa6893323d
-size 106926
+oid sha256:8ce26b3b187c24062bfa533b53fd1b2cff1881460a063f173db5812e8d5226a5
+size 106909
diff --git a/tests/rust/re_integration_test/tests/snapshots/per_visualizer_instruction_errors_3_errors_only.png b/tests/rust/re_integration_test/tests/snapshots/per_visualizer_instruction_errors_3_errors_only.png
index f98fcd4f47ff..f7425d7ac8e5 100644
--- a/tests/rust/re_integration_test/tests/snapshots/per_visualizer_instruction_errors_3_errors_only.png
+++ b/tests/rust/re_integration_test/tests/snapshots/per_visualizer_instruction_errors_3_errors_only.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:9b718809ca05753fdd2bebaf8fff41ede19a10ce9ee2186a9b4b1ff21c4a2475
-size 103525
+oid sha256:0060816c4d9309b70402e21ff810a2690a9f524e126c6dfcbcb0d827cdca9964
+size 103478
diff --git a/tests/rust/re_integration_test/tests/snapshots/per_visualizer_instruction_errors_3b_errors_only_menu.png b/tests/rust/re_integration_test/tests/snapshots/per_visualizer_instruction_errors_3b_errors_only_menu.png
index 43157f6b7d72..04821e1d855b 100644
--- a/tests/rust/re_integration_test/tests/snapshots/per_visualizer_instruction_errors_3b_errors_only_menu.png
+++ b/tests/rust/re_integration_test/tests/snapshots/per_visualizer_instruction_errors_3b_errors_only_menu.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:45ec51453b4a5eb8a6ca344a3e0f0f5bc9b19777d014b2231e817b57dbecbd90
-size 108153
+oid sha256:bd79a1502910057e2a64e64a889c703770470c8f9f3c1a38475b03c069ec2edd
+size 108996
diff --git a/tests/rust/re_integration_test/tests/snapshots/series_count_exceeds_max.png b/tests/rust/re_integration_test/tests/snapshots/series_count_exceeds_max.png
index 1693aa6928ca..4c0c50b91837 100644
--- a/tests/rust/re_integration_test/tests/snapshots/series_count_exceeds_max.png
+++ b/tests/rust/re_integration_test/tests/snapshots/series_count_exceeds_max.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:f4f6e8966b1d6890fa3ed40c3691f25aa99ddb9a17bb3505896c46220c165879
-size 1431584
+oid sha256:5ab773d3119d8e3cbf8b070f3aa8fb15b900feef713a8beb2c51efeca96ecb46
+size 1369932
diff --git a/tests/rust/re_integration_test/tests/snapshots/simplify_container_hierarchy_3.png b/tests/rust/re_integration_test/tests/snapshots/simplify_container_hierarchy_3.png
index bfc2833ea095..4eea790cdf9d 100644
--- a/tests/rust/re_integration_test/tests/snapshots/simplify_container_hierarchy_3.png
+++ b/tests/rust/re_integration_test/tests/snapshots/simplify_container_hierarchy_3.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:e51d7b2bad9ebd4b3603c289b8e7df98c815f1615584e0c905fa540b3ab4bfa8
-size 126981
+oid sha256:8eacdc3ee2199af0e4aab9e73e099bc67fe270a96772dae8e964695df2f07971
+size 127315
diff --git a/tests/rust/re_integration_test/tests/snapshots/simplify_root_hierarchy_3.png b/tests/rust/re_integration_test/tests/snapshots/simplify_root_hierarchy_3.png
index adb36e9c5eeb..5bb102fedb47 100644
--- a/tests/rust/re_integration_test/tests/snapshots/simplify_root_hierarchy_3.png
+++ b/tests/rust/re_integration_test/tests/snapshots/simplify_root_hierarchy_3.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:62beafa381c450eff945ada60b372cd5d56d9baeeeb8f500ea49c7b9f4d2f2d4
-size 128353
+oid sha256:b7af7ce373d7b94a6d5caa23bdd0d47a17384c4e4edd9f226a8623ab8ea4bfca
+size 128427
diff --git a/tests/rust/re_integration_test/tests/snapshots/view_defaults.png b/tests/rust/re_integration_test/tests/snapshots/view_defaults.png
index d2d8b000ffb0..ba477b5f5b04 100644
--- a/tests/rust/re_integration_test/tests/snapshots/view_defaults.png
+++ b/tests/rust/re_integration_test/tests/snapshots/view_defaults.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:a26111e35ea3b2899fde9faeef588be2e4e44c6286689f23fb7664971544435c
-size 178589
+oid sha256:f02e6d3aece3ebc67d4461489d8316dc140e3f0e5541567f86f4e3e53b1ae173
+size 178267
diff --git a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_1_initial.png b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_1_initial.png
index 357c9898b86e..bd450cd9a5dd 100644
--- a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_1_initial.png
+++ b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_1_initial.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:05a20199c100f00f6c7bf3af90d821870c51510ca06750a4b59042b0e1012eab
-size 176401
+oid sha256:6ad2fca05761e9c2a7634504cbe00625a75a7752f6ec0fd6bcbebd6059d8b42b
+size 171325
diff --git a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_2_plots_view_selected.png b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_2_plots_view_selected.png
index fa64ae145d41..cf29c0d49254 100644
--- a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_2_plots_view_selected.png
+++ b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_2_plots_view_selected.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:9c45f8e5dbe1324f8a51be5c69a3a10f05c9fecca55928ca245342f71ba9c74e
-size 239338
+oid sha256:7e5beeccce7dd440fc62e4b171fc7febffa2b819bce5591371bd4c91d29bd3c0
+size 233987
diff --git a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_3_other_view_selected.png b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_3_other_view_selected.png
index 3475812f84d7..b0fe7c29065d 100644
--- a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_3_other_view_selected.png
+++ b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_3_other_view_selected.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:7b255a642e4d432f40bdb415827896491c0c5e6d45ccb0597c1ca091e60128b4
-size 224508
+oid sha256:a88c7058c78058f1f97a1791fae21644c126c49031ae0fd56929bd9354fd6c88
+size 219665
diff --git a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_4_hover_sin_line.png b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_4_hover_sin_line.png
index 0b7e4bbf7309..c721f3a9f34e 100644
--- a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_4_hover_sin_line.png
+++ b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_4_hover_sin_line.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:b576291191ec029ec6a2f590a05c352a4f38dd73bcdc6d7a10a0fc366e1339ba
-size 239044
+oid sha256:fe6eca4f5d05c3906bc7f777d0d77c16706a8ab35dd1070debf1cd8e3dd5f5e7
+size 233921
diff --git a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_5_after_hide.png b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_5_after_hide.png
index b3f8e0bfb66b..c285c32ff93b 100644
--- a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_5_after_hide.png
+++ b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_5_after_hide.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:507153b974bdf2299a930a3185c3448439850c47d6334fc5feeebdcb10990c6c
-size 232888
+oid sha256:a721c150b5f2c7868700cfa5bb847bd8d7d768a2d04dcc5a36492644637785bb
+size 227890
diff --git a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_6_hover_different_after_hide.png b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_6_hover_different_after_hide.png
index a492995d75f9..b4b2ede77ce4 100644
--- a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_6_hover_different_after_hide.png
+++ b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_6_hover_different_after_hide.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:e01cd38845bbaab7a7e693103823ee1ddc354bb15abd2d8435ed68691ed9fbef
-size 235913
+oid sha256:0c5b3ea0fe27f492503894fc187aec6edcaf754a12f5a10a1d533df3f8b829af
+size 230845
diff --git a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_1_view_selected.png b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_1_view_selected.png
index ce00d3619934..7b7ad99bb668 100644
--- a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_1_view_selected.png
+++ b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_1_view_selected.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:ed172863ece32457608b53140d65146e210d3b7f8b493a06185af01a6cb923a7
-size 173529
+oid sha256:1377d731890a2c556de4113fac2db7e6a5c0d2cffd4ee683bd70aa83d29b3333
+size 172243
diff --git a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_2_popup_open.png b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_2_popup_open.png
index 2436802c1786..25b4c7fb324c 100644
--- a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_2_popup_open.png
+++ b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_2_popup_open.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:e798b3aec15d7010b1347b64f3ebd88b1a741e500d996dfa59121a3111b38252
-size 172364
+oid sha256:14acfd051510e94fdd9559b71df12d149021f2e16d53d068f2fdb6f6a6b5f6cd
+size 171877
diff --git a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_1_starting_state.png b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_1_starting_state.png
index 54b5bd9adf56..cfa0cef666e3 100644
--- a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_1_starting_state.png
+++ b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_1_starting_state.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:72befdae015e006cb6b00f4f5493f573add88cda0783ae9e3224883d73f84ce4
-size 198557
+oid sha256:02d5b0d18dc58506c451ba55143f44523dc719feef352e44b9a0ff914e73b01c
+size 174840
diff --git a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_2_selected_visualizer.png b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_2_selected_visualizer.png
index 2c952690bc81..a7e17c1f63ff 100644
--- a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_2_selected_visualizer.png
+++ b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_2_selected_visualizer.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:8e1f919ba28e7312c1b287c671458f806b4afa438ecd534aa7318740f85300a8
-size 202917
+oid sha256:eaa124cc4e30e916a173b4abf69c3504765f0d97308baa98029b0e9798edc0d6
+size 178154
diff --git a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_3_removed_visualizer.png b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_3_removed_visualizer.png
index e5db7fbf9264..ea88cf9b9bbb 100644
--- a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_3_removed_visualizer.png
+++ b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_3_removed_visualizer.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:65b0ecce495c697504b918d3651a7d2add11f63c2ad87a545c66467faad9bece
-size 141809
+oid sha256:751ed0850ccaa8f1261b29725c974ccf3aa6bc6876a3d062c7e3cf9a2ed5d200
+size 144269
diff --git a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_4_view_selected_again.png b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_4_view_selected_again.png
index d4f7a1ad7bf5..d43f193a3f5b 100644
--- a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_4_view_selected_again.png
+++ b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_4_view_selected_again.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:73adf5a295da95d9b47fb7b02029ec28c278f4b3137ec5260b0ea077178bd193
-size 184568
+oid sha256:3549eb61addff187dd698c2547214139d2a660cd90da71aadbb3bf8549439803
+size 186635
diff --git a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_5_popup_open.png b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_5_popup_open.png
index 251571b8c468..21f67aa323cb 100644
--- a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_5_popup_open.png
+++ b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_5_popup_open.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:4ce6b6f4a1d53d95e2ca51b476601df10240d6d7d22c757c12b2a576fb99748f
-size 209511
+oid sha256:cdaf03d94cf815a8e73426d86822ff1c1f0a02fb3e07d77b965beae6f50e7197
+size 210975
diff --git a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_6_first_added.png b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_6_first_added.png
index 44b51240b6b5..21c6587281b9 100644
--- a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_6_first_added.png
+++ b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_6_first_added.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:270c883d6fbe47cbd8e0e65e93147510dc65534968642e0f15c3cd6b2fc2ff4d
-size 196471
+oid sha256:15c7cd4fa9396c9ec24138b6c2c0c89a93fc86de5a828cd848966b248cf85f0d
+size 172957
diff --git a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_7_second_added.png b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_7_second_added.png
index 78836c24e307..e1b80c8f50a6 100644
--- a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_7_second_added.png
+++ b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_add_multi_7_second_added.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:3659639c6c6142656f750aa2d53ce8b61c92315a1bd535e5625afc2c0e30e05f
-size 198705
+oid sha256:f55ac8125a2a592bf89fb8afba6019460640e57aa6bc6b3b08dcdfd88f9bb91c
+size 175109
diff --git a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_ctx_menu_1_open.png b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_ctx_menu_1_open.png
index c96c7371e547..88d40b62cef2 100644
--- a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_ctx_menu_1_open.png
+++ b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_ctx_menu_1_open.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:9e54f3a0b02a1f4b4a891479e5cbb4683245a0eb6f818c253b445fad6d4b798f
-size 179866
+oid sha256:7c9f4161e7efb1951e30cb37d278082a6d29fe2ee16ebe946611d6b01901921f
+size 178560
diff --git a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_ctx_menu_2_after_hide.png b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_ctx_menu_2_after_hide.png
index 9be6af46aa62..604d3bf7fbc9 100644
--- a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_ctx_menu_2_after_hide.png
+++ b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_ctx_menu_2_after_hide.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:9b150fc1e08ebf9a3e9b0234a52645738a005eedb5e006284e4005b18e5cfa58
-size 161550
+oid sha256:3dca173e30303e6ae06bd57570fe94632524a446d84dfdd1d4c8969b30b2406c
+size 160867
diff --git a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_ctx_menu_3_show_option.png b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_ctx_menu_3_show_option.png
index 97520e9c7c1c..74d637f06c58 100644
--- a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_ctx_menu_3_show_option.png
+++ b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_ctx_menu_3_show_option.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:bdb3b59fd39423e1643b60b8d64684fd5057c102dca1a2dd5717a4663ebfd613
-size 166129
+oid sha256:11eec7ecd437019a79eab3f02316c029b8833e79fe3e4df1401d403f48e02e78
+size 165488
diff --git a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_ctx_menu_4_after_show.png b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_ctx_menu_4_after_show.png
index cc35deee0625..f8a76b556841 100644
--- a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_ctx_menu_4_after_show.png
+++ b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_ctx_menu_4_after_show.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:df70c4b9972c767e78b7d29cc4c5e32056a2316e9f6de46843b05713f9d37052
-size 173504
+oid sha256:58ae9fead0718a56d2049b0bd76bb229099815d08da20dfa46e14095b6c332cb
+size 172211
diff --git a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_ctx_menu_5_after_remove.png b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_ctx_menu_5_after_remove.png
index 399c3359cf05..b1ed0c15b021 100644
--- a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_ctx_menu_5_after_remove.png
+++ b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_ctx_menu_5_after_remove.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:9254753994908ab05e93be4f0ae158680dfc3028d26c67df7a1744c3f7e3f42e
-size 157941
+oid sha256:af3ab7824d0245a52cf4704ab2f310613f660934b2232dbb13f821592d3258a8
+size 157407
diff --git a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_multi_scalar_view_selected.png b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_multi_scalar_view_selected.png
index 8ddb96382242..68b8b55df5a0 100644
--- a/tests/rust/re_integration_test/tests/snapshots/view_visualizers_multi_scalar_view_selected.png
+++ b/tests/rust/re_integration_test/tests/snapshots/view_visualizers_multi_scalar_view_selected.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:aef308f4db2a927b30e407eb00b9a846a486efb493570db58a3af027629781c0
-size 240254
+oid sha256:8558c12226827d6c1428013e0ae760d13ef2399b63f45138d958da77387b9ad4
+size 237613

From cb62f8c1b421bc79f28d429bfe83018c8703e908 Mon Sep 17 00:00:00 2001
From: Emil Ernerfeldt 
Date: Mon, 16 Mar 2026 22:15:00 +0100
Subject: [PATCH 155/513] Much faster 3D point clouds

This speed up point clouds by up to 8x, by caching the preparations we
make in order to create a nice structure to upload to the GPU. We still
upload to the GPU each frame.

## Before
Screenshot 2026-03-13 at 19 03 23

## ~After~ Middle
Screenshot 2026-03-13 at 19 07 42

## After
image

---------

Source-Ref: 2f85b1cec8250088486e7a6ae1ddfe21c716637e
Co-authored-by: Andreas Reich 
Co-authored-by: Wumpf <1220815+Wumpf@users.noreply.github.com>
---
 Cargo.lock                                    |   1 +
 crates/utils/re_byte_size/Cargo.toml          |   2 +
 .../utils/re_byte_size/src/primitive_sizes.rs |   5 +-
 crates/utils/re_byte_size/src/std_sizes.rs    |  10 +
 .../src/draw_phases/picking_layer.rs          |  12 +
 crates/viewer/re_renderer/src/lib.rs          |   1 +
 .../re_renderer/src/point_cloud_builder.rs    |  69 ++---
 .../re_renderer/src/renderer/point_cloud.rs   |  28 ++
 crates/viewer/re_renderer/src/size.rs         |  12 +
 .../re_renderer_examples/depth_cloud.rs       |   9 +-
 .../viewer/re_renderer_examples/multiview.rs  |   2 +-
 crates/viewer/re_renderer_examples/picking.rs |   2 +-
 .../re_view/src/annotation_context_utils.rs   |   2 +
 crates/viewer/re_view_spatial/Cargo.toml      |   2 +-
 .../src/visualizers/arrows2d.rs               |  11 +-
 .../src/visualizers/arrows3d.rs               |  11 +-
 .../src/visualizers/boxes2d.rs                |  11 +-
 .../src/visualizers/boxes3d.rs                |   1 +
 .../src/visualizers/capsules3d.rs             |   1 +
 .../src/visualizers/cylinders3d.rs            |   1 +
 .../src/visualizers/ellipsoids.rs             |   1 +
 .../src/visualizers/lines2d.rs                |  11 +-
 .../src/visualizers/lines3d.rs                |  11 +-
 .../re_view_spatial/src/visualizers/mod.rs    |  40 ++-
 .../src/visualizers/points2d.rs               |  11 +-
 .../src/visualizers/points3d.rs               | 267 +++++++++++++++---
 .../visualizers/utilities/proc_mesh_vis.rs    |   7 +-
 .../tests/annotation_context_update.rs        | 141 +++++++++
 .../annotation_context_update_frame1.png      |   3 +
 .../annotation_context_update_frame2.png      |   3 +
 ...h_base_transform_EntityHierarchy_boxes.png |   4 +-
 ...base_transform_EntityHierarchy_spheres.png |   4 +-
 ...th_base_transform_FrameHierarchy_boxes.png |   4 +-
 ..._base_transform_FrameHierarchy_spheres.png |   4 +-
 ...lamping_with_base_transform_None_boxes.png |   4 +-
 ...mping_with_base_transform_None_spheres.png |   4 +-
 .../re_viewer_context/src/annotations.rs      |  24 ++
 .../tests/snapshots/add_visualizer_axes_1.png |   4 +-
 .../tests/snapshots/add_visualizer_axes_2.png |   4 +-
 .../tests/snapshots/add_visualizer_axes_3.png |   4 +-
 .../tests/snapshots/add_visualizer_axes_4.png |   4 +-
 .../tests/snapshots/add_visualizer_axes_5.png |   4 +-
 .../tests/snapshots/deselect_on_escape_1.png  |   4 +-
 .../tests/snapshots/deselect_on_escape_2.png  |   4 +-
 .../tests/snapshots/deselect_on_escape_3.png  |   4 +-
 .../tests/snapshots/deselect_on_escape_4.png  |   4 +-
 .../tests/snapshots/deselect_on_escape_5.png  |   4 +-
 .../tests/snapshots/deselect_on_escape_6.png  |   4 +-
 .../tests/snapshots/deselect_on_escape_7.png  |   4 +-
 .../tests/snapshots/deselect_on_escape_8.png  |   4 +-
 .../snapshots/heuristics_mixed_2d_and_3d.png  |   4 +-
 .../snapshots/heuristics_mixed_all_root.png   |   4 +-
 .../tests/snapshots/origin_camera_2d.png      |   4 +-
 .../tests/snapshots/origin_camera_3d.png      |   4 +-
 .../tests/snapshots/origin_image_2d.png       |   4 +-
 .../tests/snapshots/origin_image_3d.png       |   4 +-
 .../tests/snapshots/origin_keypoint_2d.png    |   4 +-
 .../tests/snapshots/origin_keypoint_3d.png    |   4 +-
 .../tests/snapshots/origin_root_2d.png        |   4 +-
 .../tests/snapshots/origin_root_3d.png        |   4 +-
 .../tests/snapshots/origin_world_2d.png       |   4 +-
 .../tests/snapshots/origin_world_3d.png       |   4 +-
 .../simplify_container_hierarchy_3.png        |   4 +-
 .../snapshots/simplify_root_hierarchy_3.png   |   4 +-
 .../tests/snapshots/source_component_6.png    |   4 +-
 .../tests/snapshots/source_component_7.png    |   4 +-
 .../tests/snapshots/source_component_8.png    |   4 +-
 67 files changed, 646 insertions(+), 210 deletions(-)
 create mode 100644 crates/viewer/re_view_spatial/tests/annotation_context_update.rs
 create mode 100644 crates/viewer/re_view_spatial/tests/snapshots/annotation_context_update_frame1.png
 create mode 100644 crates/viewer/re_view_spatial/tests/snapshots/annotation_context_update_frame2.png

diff --git a/Cargo.lock b/Cargo.lock
index acb3b4661223..f0a95a52012e 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -8583,6 +8583,7 @@ name = "re_byte_size"
 version = "0.31.0-alpha.1+dev"
 dependencies = [
  "arrow",
+ "ecolor",
  "glam",
  "half",
  "insta",
diff --git a/crates/utils/re_byte_size/Cargo.toml b/crates/utils/re_byte_size/Cargo.toml
index 962c518a0fd1..8b4d4ce1bb7e 100644
--- a/crates/utils/re_byte_size/Cargo.toml
+++ b/crates/utils/re_byte_size/Cargo.toml
@@ -20,6 +20,7 @@ all-features = true
 
 
 [features]
+ecolor = ["dep:ecolor"]
 glam = ["dep:glam"]
 
 
@@ -31,6 +32,7 @@ smallvec.workspace = true
 vec1.workspace = true # Small enough to always depend on it.
 
 # Optional dependencies:
+ecolor = { workspace = true, optional = true }
 glam = { workspace = true, optional = true }
 
 
diff --git a/crates/utils/re_byte_size/src/primitive_sizes.rs b/crates/utils/re_byte_size/src/primitive_sizes.rs
index 78a89d1333fa..050d5f9b8ec2 100644
--- a/crates/utils/re_byte_size/src/primitive_sizes.rs
+++ b/crates/utils/re_byte_size/src/primitive_sizes.rs
@@ -29,5 +29,8 @@ impl_size_bytes_pod!(
 );
 impl_size_bytes_pod!(half::f16);
 
+#[cfg(feature = "ecolor")]
+impl_size_bytes_pod!(ecolor::Color32);
+
 #[cfg(feature = "glam")]
-impl_size_bytes_pod!(glam::DAffine3);
+impl_size_bytes_pod!(glam::Vec3, glam::DAffine3);
diff --git a/crates/utils/re_byte_size/src/std_sizes.rs b/crates/utils/re_byte_size/src/std_sizes.rs
index 09b698ccaad0..5bdc09f4c5a5 100644
--- a/crates/utils/re_byte_size/src/std_sizes.rs
+++ b/crates/utils/re_byte_size/src/std_sizes.rs
@@ -205,6 +205,16 @@ impl SizeBytes for Vec {
     }
 }
 
+impl SizeBytes for std::borrow::Cow<'_, [T]> {
+    #[inline]
+    fn heap_size_bytes(&self) -> u64 {
+        match self {
+            std::borrow::Cow::Borrowed(_) => 0,
+            std::borrow::Cow::Owned(v) => v.heap_size_bytes(),
+        }
+    }
+}
+
 impl SizeBytes for VecDeque {
     #[inline]
     fn heap_size_bytes(&self) -> u64 {
diff --git a/crates/viewer/re_renderer/src/draw_phases/picking_layer.rs b/crates/viewer/re_renderer/src/draw_phases/picking_layer.rs
index 5d0c51ecf338..c380dd95a497 100644
--- a/crates/viewer/re_renderer/src/draw_phases/picking_layer.rs
+++ b/crates/viewer/re_renderer/src/draw_phases/picking_layer.rs
@@ -103,6 +103,18 @@ pub struct PickingLayerObjectId(pub u64);
 #[derive(Clone, Copy, bytemuck::Zeroable, bytemuck::Pod, Default, Debug, PartialEq, Eq)]
 pub struct PickingLayerInstanceId(pub u64);
 
+impl re_byte_size::SizeBytes for PickingLayerInstanceId {
+    #[inline]
+    fn heap_size_bytes(&self) -> u64 {
+        0
+    }
+
+    #[inline]
+    fn is_pod() -> bool {
+        true
+    }
+}
+
 /// Combination of `PickingLayerObjectId` and `PickingLayerInstanceId`.
 ///
 /// This is the same memory order as it is found in the GPU picking layer texture.
diff --git a/crates/viewer/re_renderer/src/lib.rs b/crates/viewer/re_renderer/src/lib.rs
index c4d9f22ea92d..20ef26175bf0 100644
--- a/crates/viewer/re_renderer/src/lib.rs
+++ b/crates/viewer/re_renderer/src/lib.rs
@@ -102,6 +102,7 @@ pub use line_drawable_builder::{LineBatchBuilder, LineDrawableBuilder, LineStrip
 pub use point_cloud_builder::{PointCloudBatchBuilder, PointCloudBuilder};
 pub use queueable_draw_data::QueueableDrawData;
 pub use rect::{RectF32, RectInt};
+pub use renderer::gpu_data::PositionRadius;
 pub use size::Size;
 pub use texture_info::Texture2DBufferInfo;
 pub use transform::RectTransform;
diff --git a/crates/viewer/re_renderer/src/point_cloud_builder.rs b/crates/viewer/re_renderer/src/point_cloud_builder.rs
index a31f17e6ad19..48079b9c75cb 100644
--- a/crates/viewer/re_renderer/src/point_cloud_builder.rs
+++ b/crates/viewer/re_renderer/src/point_cloud_builder.rs
@@ -1,4 +1,3 @@
-use itertools::{Itertools as _, izip};
 use re_log::{ResultExt as _, debug_assert_eq};
 
 use crate::allocator::DataTextureSource;
@@ -132,7 +131,10 @@ impl PointCloudBatchBuilder<'_, '_> {
         self
     }
 
-    /// Add several 3D points
+    /// Add several 3D points.
+    ///
+    /// If possible, prefer to using [`Self::add_points`] instead,
+    /// which avoids doing any extra allocations.
     ///
     /// Returns a `PointBuilder` which can be used to set the colors, radii, and user-data for the points.
     ///
@@ -140,8 +142,8 @@ impl PointCloudBatchBuilder<'_, '_> {
     /// Missing radii will default to `Size::AUTO`.
     /// Missing colors will default to white.
     #[inline]
-    pub fn add_points(
-        mut self,
+    pub fn add_points_slow(
+        self,
         positions: &[glam::Vec3],
         radii: &[Size],
         colors: &[Color32],
@@ -149,6 +151,25 @@ impl PointCloudBatchBuilder<'_, '_> {
     ) -> Self {
         re_tracing::profile_function!();
 
+        let positions_and_radii = PositionRadius::from_many(positions, radii);
+        self.add_points(&positions_and_radii, colors, picking_ids)
+    }
+
+    /// Add several 3D points
+    ///
+    /// Returns a `PointBuilder` which can be used to set the colors, radii, and user-data for the points.
+    ///
+    /// Will add all positions.
+    /// Missing colors will default to white.
+    #[inline]
+    pub fn add_points(
+        mut self,
+        positions_and_radii: &[PositionRadius],
+        colors: &[Color32],
+        picking_ids: &[PickingLayerInstanceId],
+    ) -> Self {
+        re_tracing::profile_function!();
+
         debug_assert_eq!(
             self.0.position_radius_buffer.len(),
             self.0.color_buffer.len()
@@ -158,25 +179,27 @@ impl PointCloudBatchBuilder<'_, '_> {
             self.0.picking_instance_ids_buffer.len()
         );
 
+        let num_points = positions_and_radii.len();
+
         // Do a reserve ahead of time, to check whether we're hitting the data texture limit.
         // The limit is the same for all data textures, so we only need to check one.
         let Some(num_available_points) = self
             .0
             .position_radius_buffer
-            .reserve(positions.len())
+            .reserve(num_points)
             .ok_or_log_error()
         else {
             return self;
         };
 
-        let num_points = if positions.len() > num_available_points {
+        let num_points = if num_points > num_available_points {
             re_log::error_once!(
                 "Reached maximum number of points for point cloud of {}. Ignoring all excess points.",
                 self.0.position_radius_buffer.len() + num_available_points
             );
             num_available_points
         } else {
-            positions.len()
+            num_points
         };
 
         if num_points == 0 {
@@ -184,40 +207,18 @@ impl PointCloudBatchBuilder<'_, '_> {
         }
 
         // Shorten slices if needed:
-        let positions = &positions[0..num_points.min(positions.len())];
-        let radii = &radii[0..num_points.min(radii.len())];
+        let positions_and_radii =
+            &positions_and_radii[0..num_points.min(positions_and_radii.len())];
         let colors = &colors[0..num_points.min(colors.len())];
         let picking_ids = &picking_ids[0..num_points.min(picking_ids.len())];
 
         self.batch_mut().point_count += num_points as u32;
 
         {
-            re_tracing::profile_scope!("positions & radii");
-
-            // TODO(andreas): It would be nice to pass on the iterator as is so we don't have to do yet another
-            // copy of the data and instead write into the buffers directly - if done right this should be the fastest.
-            // But it's surprisingly tricky to do this effectively.
-            let vertices = if positions.len() == radii.len() {
-                // Optimize common-case with simpler iterators.
-                re_tracing::profile_scope!("collect_vec");
-                izip!(positions.iter().copied(), radii.iter().copied())
-                    .map(|(pos, radius)| PositionRadius { pos, radius })
-                    .collect_vec()
-            } else {
-                re_tracing::profile_scope!("collect_vec");
-                izip!(
-                    positions.iter().copied(),
-                    radii.iter().copied().chain(std::iter::repeat(
-                        *radii.last().unwrap_or(&Size::ONE_UI_POINT)
-                    ))
-                )
-                .map(|(pos, radius)| PositionRadius { pos, radius })
-                .collect_vec()
-            };
-
+            re_tracing::profile_scope!("positions_and_radii");
             self.0
                 .position_radius_buffer
-                .extend_from_slice(&vertices)
+                .extend_from_slice(positions_and_radii)
                 .ok_or_log_error();
         }
         {
@@ -265,7 +266,7 @@ impl PointCloudBatchBuilder<'_, '_> {
         picking_ids: &[PickingLayerInstanceId],
     ) -> Self {
         re_tracing::profile_function!();
-        self.add_points(positions, radii, colors, picking_ids)
+        self.add_points_slow(positions, radii, colors, picking_ids)
             .flags(PointCloudBatchFlags::FLAG_DRAW_AS_CIRCLES)
     }
 
diff --git a/crates/viewer/re_renderer/src/renderer/point_cloud.rs b/crates/viewer/re_renderer/src/renderer/point_cloud.rs
index b52c4619106d..5c69b05d172d 100644
--- a/crates/viewer/re_renderer/src/renderer/point_cloud.rs
+++ b/crates/viewer/re_renderer/src/renderer/point_cloud.rs
@@ -69,6 +69,34 @@ pub mod gpu_data {
     }
     static_assertions::assert_eq_size!(PositionRadius, glam::Vec4);
 
+    impl PositionRadius {
+        /// If there are fewer radii than positions,
+        /// the last radius will be repeated for the remaining positions
+        /// (clamp to edge).
+        pub fn from_many(positions: &[glam::Vec3], radii: &[Size]) -> Vec {
+            use itertools::izip;
+
+            re_tracing::profile_function_if!(10_0000 < positions.len());
+            if positions.len() == radii.len() {
+                // Optimize common-case with simpler iterators.
+                re_tracing::profile_scope_if!(10_000 < positions.len(), "zipped");
+                izip!(positions.iter().copied(), radii.iter().copied())
+                    .map(|(pos, radius)| Self { pos, radius })
+                    .collect()
+            } else {
+                re_tracing::profile_scope_if!(10_000 < positions.len(), "extended-radius");
+                izip!(
+                    positions.iter().copied(),
+                    radii.iter().copied().chain(std::iter::repeat(
+                        *radii.last().unwrap_or(&Size::ONE_UI_POINT)
+                    ))
+                )
+                .map(|(pos, radius)| Self { pos, radius })
+                .collect()
+            }
+        }
+    }
+
     /// Uniform buffer that changes once per draw data rendering.
     #[repr(C)]
     #[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
diff --git a/crates/viewer/re_renderer/src/size.rs b/crates/viewer/re_renderer/src/size.rs
index a917f2a7fa40..041896432b18 100644
--- a/crates/viewer/re_renderer/src/size.rs
+++ b/crates/viewer/re_renderer/src/size.rs
@@ -12,6 +12,18 @@ use re_log::debug_assert;
 #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
 pub struct Size(pub f32);
 
+impl re_byte_size::SizeBytes for Size {
+    #[inline]
+    fn heap_size_bytes(&self) -> u64 {
+        0
+    }
+
+    #[inline]
+    fn is_pod() -> bool {
+        true
+    }
+}
+
 impl Size {
     /// Zero radius.
     pub const ZERO: Self = Self(0.0);
diff --git a/crates/viewer/re_renderer_examples/depth_cloud.rs b/crates/viewer/re_renderer_examples/depth_cloud.rs
index bcb94b14f237..c6348ec6686f 100644
--- a/crates/viewer/re_renderer_examples/depth_cloud.rs
+++ b/crates/viewer/re_renderer_examples/depth_cloud.rs
@@ -98,9 +98,12 @@ impl RenderDepthClouds {
                 .multiunzip();
 
             let mut builder = PointCloudBuilder::new(re_ctx);
-            builder
-                .batch("backprojected point cloud")
-                .add_points(&points, &radii, &colors, &[]);
+            builder.batch("backprojected point cloud").add_points_slow(
+                &points,
+                &radii,
+                &colors,
+                &[],
+            );
             builder.into_draw_data()?
         };
 
diff --git a/crates/viewer/re_renderer_examples/multiview.rs b/crates/viewer/re_renderer_examples/multiview.rs
index a323de80987a..caa2e8122fd1 100644
--- a/crates/viewer/re_renderer_examples/multiview.rs
+++ b/crates/viewer/re_renderer_examples/multiview.rs
@@ -325,7 +325,7 @@ impl Example for Multiview {
         builder
             .batch("Random Points")
             .world_from_obj(glam::Affine3A::from_rotation_x(secs_since_startup))
-            .add_points(
+            .add_points_slow(
                 &self.random_points_positions,
                 &self.random_points_radii,
                 &self.random_points_colors,
diff --git a/crates/viewer/re_renderer_examples/picking.rs b/crates/viewer/re_renderer_examples/picking.rs
index ec2323191f28..4ed7aa88e75e 100644
--- a/crates/viewer/re_renderer_examples/picking.rs
+++ b/crates/viewer/re_renderer_examples/picking.rs
@@ -160,7 +160,7 @@ impl framework::Example for Picking {
             point_builder
                 .batch(format!("Random Points {i}"))
                 .picking_object_id(re_renderer::PickingLayerObjectId(i as u64 + 1)) // offset by one since 0=default=no hit
-                .add_points(
+                .add_points_slow(
                     &point_set.positions,
                     &point_set.radii,
                     &point_set.colors,
diff --git a/crates/viewer/re_view/src/annotation_context_utils.rs b/crates/viewer/re_view/src/annotation_context_utils.rs
index be22209747e9..d1000a8c1080 100644
--- a/crates/viewer/re_view/src/annotation_context_utils.rs
+++ b/crates/viewer/re_view/src/annotation_context_utils.rs
@@ -34,11 +34,13 @@ pub fn process_color_slice<'a>(
                 re_tracing::profile_scope!("no colors, same annotation");
                 let color = annotation_info
                     .color()
+                    // TODO(RR-3840): Fallback should be already incorporated into the query.
                     .unwrap_or_else(|| typed_fallback_for::(ctx, component).into());
                 vec![color; *count]
             }
             ResolvedAnnotationInfos::Many(annotation_info) => {
                 re_tracing::profile_scope!("no-colors, many annotations");
+                // TODO(RR-3840): Fallback should be already incorporated into the query.
                 let fallback = typed_fallback_for::(ctx, component).into();
                 annotation_info
                     .iter()
diff --git a/crates/viewer/re_view_spatial/Cargo.toml b/crates/viewer/re_view_spatial/Cargo.toml
index a3847688e0d7..97864888ee75 100644
--- a/crates/viewer/re_view_spatial/Cargo.toml
+++ b/crates/viewer/re_view_spatial/Cargo.toml
@@ -26,7 +26,7 @@ nasm = ["re_video/nasm"]
 [dependencies]
 
 re_arrow_util.workspace = true
-re_byte_size.workspace = true
+re_byte_size = { workspace = true, features = ["ecolor", "glam"] }
 re_chunk_store.workspace = true
 re_data_ui.workspace = true
 re_entity_db.workspace = true
diff --git a/crates/viewer/re_view_spatial/src/visualizers/arrows2d.rs b/crates/viewer/re_view_spatial/src/visualizers/arrows2d.rs
index 365569e739be..ea6519a77a53 100644
--- a/crates/viewer/re_view_spatial/src/visualizers/arrows2d.rs
+++ b/crates/viewer/re_view_spatial/src/visualizers/arrows2d.rs
@@ -59,10 +59,13 @@ impl Arrows2DVisualizer {
                 &ent_context.annotations,
             );
 
-            // Has not custom fallback for radius, so we use the default.
-            // TODO(andreas): It would be nice to have this handle this fallback as part of the query.
-            let radii =
-                process_radius_slice(entity_path, num_instances, data.radii, Radius::default());
+            let radii = process_radius_slice(
+                ctx,
+                entity_path,
+                num_instances,
+                data.radii,
+                Arrows2D::descriptor_radii().component,
+            );
             let colors = process_color_slice(
                 ctx,
                 Arrows2D::descriptor_colors().component,
diff --git a/crates/viewer/re_view_spatial/src/visualizers/arrows3d.rs b/crates/viewer/re_view_spatial/src/visualizers/arrows3d.rs
index d73a3da40010..543264aab491 100644
--- a/crates/viewer/re_view_spatial/src/visualizers/arrows3d.rs
+++ b/crates/viewer/re_view_spatial/src/visualizers/arrows3d.rs
@@ -56,10 +56,13 @@ impl Arrows3DVisualizer {
                 &ent_context.annotations,
             );
 
-            // Has not custom fallback for radius, so we use the default.
-            // TODO(andreas): It would be nice to have this handle this fallback as part of the query.
-            let radii =
-                process_radius_slice(entity_path, num_instances, data.radii, Radius::default());
+            let radii = process_radius_slice(
+                ctx,
+                entity_path,
+                num_instances,
+                data.radii,
+                Arrows3D::descriptor_radii().component,
+            );
             let colors = process_color_slice(
                 ctx,
                 Arrows3D::descriptor_colors().component,
diff --git a/crates/viewer/re_view_spatial/src/visualizers/boxes2d.rs b/crates/viewer/re_view_spatial/src/visualizers/boxes2d.rs
index fcb48cea2a3c..c8bc36243174 100644
--- a/crates/viewer/re_view_spatial/src/visualizers/boxes2d.rs
+++ b/crates/viewer/re_view_spatial/src/visualizers/boxes2d.rs
@@ -56,10 +56,13 @@ impl Boxes2DVisualizer {
                 &ent_context.annotations,
             );
 
-            // Has not custom fallback for radius, so we use the default.
-            // TODO(andreas): It would be nice to have this handle this fallback as part of the query.
-            let radii =
-                process_radius_slice(entity_path, num_instances, data.radii, Radius::default());
+            let radii = process_radius_slice(
+                ctx,
+                entity_path,
+                num_instances,
+                data.radii,
+                Boxes2D::descriptor_radii().component,
+            );
             let colors = process_color_slice(
                 ctx,
                 Boxes2D::descriptor_colors().component,
diff --git a/crates/viewer/re_view_spatial/src/visualizers/boxes3d.rs b/crates/viewer/re_view_spatial/src/visualizers/boxes3d.rs
index 1e0b48cf0537..7c24e494f322 100644
--- a/crates/viewer/re_view_spatial/src/visualizers/boxes3d.rs
+++ b/crates/viewer/re_view_spatial/src/visualizers/boxes3d.rs
@@ -51,6 +51,7 @@ impl Boxes3DVisualizer {
                 query_context,
                 ent_context,
                 Boxes3D::descriptor_colors().component,
+                Boxes3D::descriptor_radii().component,
                 Boxes3D::descriptor_show_labels().component,
                 constant_instance_transform,
                 ProcMeshBatch {
diff --git a/crates/viewer/re_view_spatial/src/visualizers/capsules3d.rs b/crates/viewer/re_view_spatial/src/visualizers/capsules3d.rs
index d573c802e519..5a336aa6d7a8 100644
--- a/crates/viewer/re_view_spatial/src/visualizers/capsules3d.rs
+++ b/crates/viewer/re_view_spatial/src/visualizers/capsules3d.rs
@@ -88,6 +88,7 @@ impl Capsules3DVisualizer {
                 query_context,
                 ent_context,
                 Capsules3D::descriptor_colors().component,
+                Capsules3D::descriptor_line_radii().component,
                 Capsules3D::descriptor_show_labels().component,
                 glam::Affine3A::IDENTITY,
                 ProcMeshBatch {
diff --git a/crates/viewer/re_view_spatial/src/visualizers/cylinders3d.rs b/crates/viewer/re_view_spatial/src/visualizers/cylinders3d.rs
index 2b21e72044df..468444c61f86 100644
--- a/crates/viewer/re_view_spatial/src/visualizers/cylinders3d.rs
+++ b/crates/viewer/re_view_spatial/src/visualizers/cylinders3d.rs
@@ -83,6 +83,7 @@ impl Cylinders3DVisualizer {
                 query_context,
                 ent_context,
                 Cylinders3D::descriptor_colors().component,
+                Cylinders3D::descriptor_line_radii().component,
                 Cylinders3D::descriptor_show_labels().component,
                 glam::Affine3A::IDENTITY,
                 ProcMeshBatch {
diff --git a/crates/viewer/re_view_spatial/src/visualizers/ellipsoids.rs b/crates/viewer/re_view_spatial/src/visualizers/ellipsoids.rs
index a162d0bd93e7..cdc111eddf77 100644
--- a/crates/viewer/re_view_spatial/src/visualizers/ellipsoids.rs
+++ b/crates/viewer/re_view_spatial/src/visualizers/ellipsoids.rs
@@ -53,6 +53,7 @@ impl Ellipsoids3DVisualizer {
                 query_context,
                 ent_context,
                 Ellipsoids3D::descriptor_colors().component,
+                Ellipsoids3D::descriptor_line_radii().component,
                 Ellipsoids3D::descriptor_show_labels().component,
                 glam::Affine3A::IDENTITY,
                 ProcMeshBatch {
diff --git a/crates/viewer/re_view_spatial/src/visualizers/lines2d.rs b/crates/viewer/re_view_spatial/src/visualizers/lines2d.rs
index 5f5db3615782..7270acc950e5 100644
--- a/crates/viewer/re_view_spatial/src/visualizers/lines2d.rs
+++ b/crates/viewer/re_view_spatial/src/visualizers/lines2d.rs
@@ -56,10 +56,13 @@ impl Lines2DVisualizer {
                 &ent_context.annotations,
             );
 
-            // Has not custom fallback for radius, so we use the default.
-            // TODO(andreas): It would be nice to have this handle this fallback as part of the query.
-            let radii =
-                process_radius_slice(entity_path, num_instances, data.radii, Radius::default());
+            let radii = process_radius_slice(
+                ctx,
+                entity_path,
+                num_instances,
+                data.radii,
+                LineStrips2D::descriptor_radii().component,
+            );
             let colors = process_color_slice(
                 ctx,
                 LineStrips2D::descriptor_colors().component,
diff --git a/crates/viewer/re_view_spatial/src/visualizers/lines3d.rs b/crates/viewer/re_view_spatial/src/visualizers/lines3d.rs
index 885c238c4636..09586e3485cf 100644
--- a/crates/viewer/re_view_spatial/src/visualizers/lines3d.rs
+++ b/crates/viewer/re_view_spatial/src/visualizers/lines3d.rs
@@ -56,10 +56,13 @@ impl Lines3DVisualizer {
                 &ent_context.annotations,
             );
 
-            // Has not custom fallback for radius, so we use the default.
-            // TODO(andreas): It would be nice to have this handle this fallback as part of the query.
-            let radii =
-                process_radius_slice(entity_path, num_instances, data.radii, Radius::default());
+            let radii = process_radius_slice(
+                ctx,
+                entity_path,
+                num_instances,
+                data.radii,
+                LineStrips3D::descriptor_radii().component,
+            );
             let colors = process_color_slice(
                 ctx,
                 LineStrips3D::descriptor_colors().component,
diff --git a/crates/viewer/re_view_spatial/src/visualizers/mod.rs b/crates/viewer/re_view_spatial/src/visualizers/mod.rs
index 51f1b63ff9c7..f87d4a03d889 100644
--- a/crates/viewer/re_view_spatial/src/visualizers/mod.rs
+++ b/crates/viewer/re_view_spatial/src/visualizers/mod.rs
@@ -26,7 +26,7 @@ mod video;
 pub use cameras::CamerasVisualizer;
 pub use depth_images::{DepthImageProcessResult, DepthImageVisualizer};
 pub use encoded_depth_image::EncodedDepthImageVisualizer;
-use re_sdk_types::{ComponentDescriptor, archetypes};
+use re_sdk_types::{ComponentDescriptor, ComponentIdentifier, archetypes};
 pub use transform_axes_3d::{TransformAxes3DVisualizer, add_axis_arrows};
 pub use utilities::{
     SpatialViewVisualizerData, UiLabel, UiLabelStyle, UiLabelTarget, entity_iterator,
@@ -55,10 +55,11 @@ pub struct LoadingIndicator {
 use ahash::HashMap;
 use re_entity_db::EntityPath;
 use re_sdk_types::datatypes::{KeypointId, KeypointPair};
-use re_view::clamped_or_nothing;
+use re_view::clamped_or_else;
 use re_viewer_context::{
-    Annotations, IdentifiedViewSystem as _, ViewClassRegistryError, ViewSystemExecutionError,
-    ViewSystemIdentifier, ViewSystemRegistrator, VisualizerCollection, auto_color_egui,
+    Annotations, IdentifiedViewSystem as _, QueryContext, ViewClassRegistryError,
+    ViewSystemExecutionError, ViewSystemIdentifier, ViewSystemRegistrator, VisualizerCollection,
+    auto_color_egui, typed_fallback_for,
 };
 
 /// Collection of keypoints for annotation context.
@@ -183,38 +184,29 @@ pub fn collect_ui_labels(visualizers: &VisualizerCollection) -> Vec {
 /// Process [`re_sdk_types::components::Radius`] components to [`re_renderer::Size`] using auto size
 /// where no radius is specified.
 pub fn process_radius_slice(
+    ctx: &QueryContext<'_>,
     entity_path: &EntityPath,
     num_instances: usize,
     radii: &[re_sdk_types::components::Radius],
-    fallback_radius: re_sdk_types::components::Radius,
+    component: ComponentIdentifier,
 ) -> Vec {
     re_tracing::profile_function!();
 
-    if let Some(last_radius) = radii.last() {
-        if radii.len() == num_instances {
-            // Common happy path
-            radii
-                .iter()
-                .map(|radius| process_radius(entity_path, *radius))
-                .collect()
-        } else if radii.len() == 1 {
-            // Common happy path
-            let last_radius = process_radius(entity_path, *last_radius);
-            vec![last_radius; num_instances]
-        } else {
-            clamped_or_nothing(radii, num_instances)
-                .map(|radius| process_radius(entity_path, *radius))
-                .collect()
-        }
-    } else {
-        vec![re_renderer::Size(*fallback_radius.0); num_instances]
-    }
+    clamped_or_else(radii, || {
+        // TODO(RR-3840): Fallback should be already incorporated into the query.
+        typed_fallback_for::(ctx, component)
+    })
+    .take(num_instances)
+    .map(|radius| process_radius(entity_path, radius))
+    .collect()
 }
 
 fn process_radius(
     entity_path: &EntityPath,
     radius: re_sdk_types::components::Radius,
 ) -> re_renderer::Size {
+    // TODO(andreas): surface as visualizer warning. Handle correctly when cached!
+    // TODO(andreas): array casting _is_ possible here. Maybe it's faster to do these checks separately?
     if radius.0.is_infinite() {
         re_log::warn_once!("Found infinite radius in entity {entity_path}");
     } else if radius.0.is_nan() {
diff --git a/crates/viewer/re_view_spatial/src/visualizers/points2d.rs b/crates/viewer/re_view_spatial/src/visualizers/points2d.rs
index c4010719309b..e433fdb8d7a3 100644
--- a/crates/viewer/re_view_spatial/src/visualizers/points2d.rs
+++ b/crates/viewer/re_view_spatial/src/visualizers/points2d.rs
@@ -66,10 +66,13 @@ impl Points2DVisualizer {
                 &ent_context.annotations,
             );
 
-            // Has not custom fallback for radius, so we use the default.
-            // TODO(andreas): It would be nice to have this handle this fallback as part of the query.
-            let radii =
-                process_radius_slice(entity_path, num_instances, data.radii, Radius::default());
+            let radii = process_radius_slice(
+                ctx,
+                entity_path,
+                num_instances,
+                data.radii,
+                Points2D::descriptor_radii().component,
+            );
             let colors = process_color_slice(
                 ctx,
                 Points2D::descriptor_colors().component,
diff --git a/crates/viewer/re_view_spatial/src/visualizers/points3d.rs b/crates/viewer/re_view_spatial/src/visualizers/points3d.rs
index 07f92f80e5a2..d93647273acf 100644
--- a/crates/viewer/re_view_spatial/src/visualizers/points3d.rs
+++ b/crates/viewer/re_view_spatial/src/visualizers/points3d.rs
@@ -1,18 +1,24 @@
+use std::sync::Arc;
+
 use itertools::Itertools as _;
-use re_renderer::{LineDrawableBuilder, PickingLayerInstanceId, PointCloudBuilder};
+use nohash_hasher::IntMap;
+use re_byte_size::SizeBytes as _;
+use re_entity_db::EntityDb;
+use re_log_types::hash::Hash64;
+use re_renderer::{LineDrawableBuilder, PickingLayerInstanceId, PointCloudBuilder, PositionRadius};
 use re_sdk_types::Archetype as _;
 use re_sdk_types::ArrowString;
 use re_sdk_types::archetypes::Points3D;
 use re_sdk_types::components::{ClassId, Color, KeypointId, Position3D, Radius, ShowLabels};
 use re_view::{process_annotation_and_keypoint_slices, process_color_slice};
 use re_viewer_context::{
-    IdentifiedViewSystem, QueryContext, ViewContext, ViewContextCollection, ViewQuery,
-    ViewSystemExecutionError, VisualizerExecutionOutput, VisualizerQueryInfo, VisualizerSystem,
-    typed_fallback_for,
+    Cache, IdentifiedViewSystem, QueryContext, ResolvedAnnotationInfos, ViewContext,
+    ViewContextCollection, ViewQuery, ViewSystemExecutionError, VisualizerExecutionOutput,
+    VisualizerQueryInfo, VisualizerSystem, typed_fallback_for,
 };
 
 use super::utilities::LabeledBatch;
-use super::{SpatialViewVisualizerData, process_labels_3d};
+use super::{Keypoints, SpatialViewVisualizerData, process_labels_3d};
 use crate::contexts::SpatialSceneVisualizerInstructionContext;
 use crate::view_kind::SpatialViewKind;
 use crate::visualizers::{load_keypoint_connections, process_radius_slice};
@@ -32,6 +38,8 @@ impl Default for Points3DVisualizer {
 }
 
 struct Points3DComponentData<'a> {
+    query_result_hash: Hash64,
+
     // Point of views
     positions: &'a [Position3D],
 
@@ -46,6 +54,196 @@ struct Points3DComponentData<'a> {
     show_labels: Option,
 }
 
+/// Processed/computed point cloud data ready for rendering.
+///
+/// This bundles together the results of processing raw component data
+/// (computing annotations, colors, radii, bounding boxes, etc.)
+/// so that it can be memoized based on `data.query_hash`.
+struct Points3DCpu {
+    position_radii: Vec,
+    obj_space_bounding_box: macaw::BoundingBox,
+    picking_ids: Vec,
+    annotation_infos: ResolvedAnnotationInfos,
+    keypoints: Keypoints,
+    colors: Vec,
+}
+
+impl Points3DCpu {
+    fn compute(
+        ctx: &QueryContext<'_>,
+        entity_path: &re_log_types::EntityPath,
+        query: &ViewQuery<'_>,
+        ent_context: &SpatialSceneVisualizerInstructionContext<'_>,
+        data: &Points3DComponentData<'_>,
+    ) -> Self {
+        let num_instances = data.positions.len();
+        re_tracing::profile_function!(num_instances.to_string());
+
+        let picking_ids = {
+            re_tracing::profile_scope_if!(100_000 < num_instances, "picking_ids");
+            (0..num_instances)
+                .map(|i| PickingLayerInstanceId(i as _))
+                .collect_vec()
+        };
+        let (annotation_infos, keypoints) = process_annotation_and_keypoint_slices(
+            query.latest_at,
+            num_instances,
+            data.positions.iter().map(|p| p.0.into()),
+            data.keypoint_ids,
+            data.class_ids,
+            &ent_context.annotations,
+        );
+
+        let positions: &[glam::Vec3] = bytemuck::cast_slice(data.positions);
+
+        let obj_space_bounding_box = {
+            re_tracing::profile_scope_if!(100_000 < num_instances, "bounding_box");
+            re_renderer::util::bounding_box_from_points(positions.iter().copied())
+        };
+
+        let radii = process_radius_slice(
+            ctx,
+            entity_path,
+            num_instances,
+            data.radii,
+            Points3D::descriptor_radii().component,
+        );
+        let colors = process_color_slice(
+            ctx,
+            Points3D::descriptor_colors().component,
+            num_instances,
+            &annotation_infos,
+            data.colors,
+        );
+
+        let position_radii = PositionRadius::from_many(positions, &radii);
+
+        Self {
+            position_radii,
+            obj_space_bounding_box,
+            picking_ids,
+            annotation_infos,
+            keypoints,
+            colors,
+        }
+    }
+
+    fn heap_size_bytes(&self) -> u64 {
+        let Self {
+            position_radii,
+            obj_space_bounding_box: _,
+            picking_ids,
+            annotation_infos,
+            keypoints,
+            colors,
+        } = self;
+
+        (position_radii.capacity() * std::mem::size_of::()) as u64
+            + picking_ids.heap_size_bytes()
+            + annotation_infos.heap_size_bytes()
+            + keypoints.heap_size_bytes()
+            + colors.heap_size_bytes()
+    }
+}
+
+// --- Points3DCache ---
+
+/// All the inputs that affect the output of [`Points3DCpu::compute`],
+/// beyond the point data itself (which is covered by `query_result_hash`).
+struct Points3DCacheKey {
+    /// Hash of the query results (positions, colors, radii, `class_ids`, etc.).
+    query_result_hash: Hash64,
+
+    /// The [`super::Annotations::row_id`] of the resolved annotation context.
+    /// Changes when the annotation context is re-logged.
+    annotation_row_id: re_chunk_store::RowId,
+}
+
+impl Points3DCacheKey {
+    fn hash(&self) -> Hash64 {
+        let Self {
+            query_result_hash,
+            annotation_row_id,
+        } = self;
+        Hash64::hash((query_result_hash, annotation_row_id))
+    }
+}
+
+struct Points3DCacheEntry {
+    cpu: Arc,
+    last_used_generation: u64,
+}
+
+/// Caches [`Points3DCpu`] to avoid recomputing annotations, colors, radii, etc. every frame.
+#[derive(Default)]
+pub struct Points3DCache {
+    cache: IntMap,
+    generation: u64,
+}
+
+impl Points3DCache {
+    fn entry(
+        &mut self,
+        key: &Points3DCacheKey,
+        compute: impl FnOnce() -> Points3DCpu,
+    ) -> Arc {
+        let hash = key.hash();
+        let entry = self
+            .cache
+            .entry(hash)
+            .or_insert_with(|| Points3DCacheEntry {
+                cpu: Arc::new(compute()),
+                last_used_generation: 0,
+            });
+        entry.last_used_generation = self.generation;
+        entry.cpu.clone()
+    }
+}
+
+impl Cache for Points3DCache {
+    fn name(&self) -> &'static str {
+        "Points3DCache"
+    }
+
+    fn begin_frame(&mut self) {
+        self.cache
+            .retain(|_, entry| entry.last_used_generation == self.generation);
+        self.generation += 1;
+    }
+
+    fn purge_memory(&mut self) {
+        self.cache.clear();
+    }
+
+    fn on_store_events(
+        &mut self,
+        _events: &[&re_chunk_store::ChunkStoreEvent],
+        _entity_db: &EntityDb,
+    ) {
+    }
+}
+
+impl re_byte_size::SizeBytes for Points3DCache {
+    fn heap_size_bytes(&self) -> u64 {
+        let Self {
+            cache,
+            generation: _,
+        } = self;
+        // Count the underlying data of the Arc directly instead of weighing active
+        cache
+            .values()
+            .map(|entry| entry.cpu.heap_size_bytes() + std::mem::size_of_val(&entry.cpu) as u64)
+            .sum::()
+            + (cache.capacity() * std::mem::size_of::<(Hash64, Points3DCacheEntry)>()) as u64
+    }
+}
+
+impl re_byte_size::MemUsageTreeCapture for Points3DCache {
+    fn capture_mem_usage_tree(&self) -> re_byte_size::MemUsageTree {
+        re_byte_size::MemUsageTree::Bytes(self.total_size_bytes())
+    }
+}
+
 // NOTE: Do not put profile scopes in these methods. They are called for all entities and all
 // timestamps within a time range -- it's _a lot_.
 impl Points3DVisualizer {
@@ -67,42 +265,16 @@ impl Points3DVisualizer {
                 continue;
             }
 
-            re_tracing::profile_scope!("num_points", num_instances.to_string());
-
-            let picking_ids = {
-                re_tracing::profile_scope_if!(100_000 < num_instances, "picking_ids");
-                (0..num_instances)
-                    .map(|i| PickingLayerInstanceId(i as _))
-                    .collect_vec()
+            let cache_key = Points3DCacheKey {
+                query_result_hash: data.query_result_hash,
+                annotation_row_id: ent_context.annotations.row_id(),
             };
 
-            let (annotation_infos, keypoints) = process_annotation_and_keypoint_slices(
-                query.latest_at,
-                num_instances,
-                data.positions.iter().map(|p| p.0.into()),
-                data.keypoint_ids,
-                data.class_ids,
-                &ent_context.annotations,
-            );
-
-            let positions = bytemuck::cast_slice(data.positions);
-
-            let obj_space_bounding_box = {
-                re_tracing::profile_scope_if!(100_000 < num_instances, "bounding_box");
-                re_renderer::util::bounding_box_from_points(positions.iter().copied())
-            };
-
-            // Has not custom fallback for radius, so we use the default.
-            // TODO(andreas): It would be nice to have this handle this fallback as part of the query.
-            let radii =
-                process_radius_slice(entity_path, num_instances, data.radii, Radius::default());
-            let colors = process_color_slice(
-                ctx,
-                Points3D::descriptor_colors().component,
-                num_instances,
-                &annotation_infos,
-                data.colors,
-            );
+            let cpu = ctx.store_ctx().memoizer(|c: &mut Points3DCache| {
+                c.entry(&cache_key, || {
+                    Points3DCpu::compute(ctx, entity_path, query, ent_context, &data)
+                })
+            });
 
             // TODO(grtlr): The following is a quick fix to get multiple instance poses to work
             // with point clouds: We sent the same point cloud multiple times to the GPU (bad
@@ -123,7 +295,7 @@ impl Points3DVisualizer {
                     .picking_object_id(re_renderer::PickingLayerObjectId(entity_path.hash64()));
 
                 let mut point_range_builder =
-                    point_batch.add_points(positions, &radii, &colors, &picking_ids);
+                    point_batch.add_points(&cpu.position_radii, &cpu.colors, &cpu.picking_ids);
 
                 // Determine if there's any sub-ranges that need extra highlighting.
                 {
@@ -144,7 +316,7 @@ impl Points3DVisualizer {
 
                 self.data.add_bounding_box(
                     entity_path.hash(),
-                    obj_space_bounding_box,
+                    cpu.obj_space_bounding_box,
                     world_from_obj,
                 );
 
@@ -153,7 +325,7 @@ impl Points3DVisualizer {
                     &ent_context.annotations,
                     world_from_obj,
                     entity_path,
-                    &keypoints,
+                    &cpu.keypoints,
                 )?;
 
                 self.data.ui_labels.extend(process_labels_3d(
@@ -161,14 +333,14 @@ impl Points3DVisualizer {
                         entity_path,
                         visualizer_instruction: ent_context.visualizer_instruction,
                         num_instances,
-                        overall_position: obj_space_bounding_box.center(),
-                        instance_positions: positions.iter().copied(),
+                        overall_position: cpu.obj_space_bounding_box.center(),
+                        instance_positions: cpu.position_radii.iter().map(|pr| pr.pos),
                         labels: &data.labels,
-                        colors: &colors,
+                        colors: &cpu.colors,
                         show_labels: data.show_labels.unwrap_or_else(|| {
                             typed_fallback_for(ctx, Points3D::descriptor_show_labels().component)
                         }),
-                        annotation_infos: &annotation_infos,
+                        annotation_infos: &cpu.annotation_infos,
                     },
                     world_from_obj,
                 ));
@@ -258,6 +430,8 @@ impl VisualizerSystem for Points3DVisualizer {
                 let all_show_labels =
                     results.iter_optional(Points3D::descriptor_show_labels().component);
 
+                let query_result_hash = results.query_result_hash();
+
                 let data = re_query::range_zip_1x6(
                     all_positions.slice::<[f32; 3]>(), // RowId 5
                     all_colors.slice::(),         // RowId 7
@@ -279,6 +453,7 @@ impl VisualizerSystem for Points3DVisualizer {
                         show_labels,
                     )| {
                         Points3DComponentData {
+                            query_result_hash,
                             positions: bytemuck::cast_slice(positions),
                             colors: colors.map_or(&[], |colors| bytemuck::cast_slice(colors)),
                             radii: radii.map_or(&[], |radii| bytemuck::cast_slice(radii)),
diff --git a/crates/viewer/re_view_spatial/src/visualizers/utilities/proc_mesh_vis.rs b/crates/viewer/re_view_spatial/src/visualizers/utilities/proc_mesh_vis.rs
index 7b6b77b4f3c5..e3f36bd2ffae 100644
--- a/crates/viewer/re_view_spatial/src/visualizers/utilities/proc_mesh_vis.rs
+++ b/crates/viewer/re_view_spatial/src/visualizers/utilities/proc_mesh_vis.rs
@@ -143,11 +143,13 @@ impl<'ctx> ProcMeshDrawableBuilder<'ctx> {
     }
 
     /// Add a batch of data to be drawn.
+    #[expect(clippy::too_many_arguments)]
     pub fn add_batch(
         &mut self,
         query_context: &QueryContext<'_>,
         ent_context: &SpatialSceneVisualizerInstructionContext<'_>,
         color_component: ComponentIdentifier,
+        line_radii_component: ComponentIdentifier,
         show_labels_component: ComponentIdentifier,
         constant_instance_transform: glam::Affine3A,
         batch: ProcMeshBatch<'_, impl Iterator, impl Iterator>,
@@ -179,13 +181,12 @@ impl<'ctx> ProcMeshDrawableBuilder<'ctx> {
             &ent_context.annotations,
         );
 
-        // Has not custom fallback for radius, so we use the default.
-        // TODO(andreas): It would be nice to have this handle this fallback as part of the query.
         let line_radii = process_radius_slice(
+            query_context,
             entity_path,
             num_instances,
             batch.line_radii,
-            components::Radius::default(),
+            line_radii_component,
         );
         let colors = process_color_slice(
             query_context,
diff --git a/crates/viewer/re_view_spatial/tests/annotation_context_update.rs b/crates/viewer/re_view_spatial/tests/annotation_context_update.rs
new file mode 100644
index 000000000000..7dcb05004650
--- /dev/null
+++ b/crates/viewer/re_view_spatial/tests/annotation_context_update.rs
@@ -0,0 +1,141 @@
+//! Test that changing an `AnnotationContext` across timeline steps correctly updates
+//! the colors of a `Points3D` point cloud (i.e. invalidates the `Points3D` cache).
+
+use re_log_types::{TimeInt, TimePoint, Timeline};
+use re_sdk_types::blueprint::archetypes::EyeControls3D;
+use re_sdk_types::components::Position3D;
+use re_test_context::TestContext;
+use re_test_context::external::egui_kittest::SnapshotResults;
+use re_test_viewport::TestContextExt as _;
+use re_viewer_context::{BlueprintContext as _, TimeControlCommand, ViewClass as _, ViewId};
+use re_viewport_blueprint::{ViewBlueprint, ViewProperty};
+
+/// Log a point cloud with `class_ids`, and an annotation context that changes color between two frames.
+///
+/// Frame 1: annotation maps class 0 → red, class 1 → green
+/// Frame 2: annotation maps class 0 → blue, class 1 → yellow
+///
+/// The point cloud itself is static (same positions and `class_ids` on both frames).
+/// If the cache correctly accounts for the annotation context, the colors should change.
+#[test]
+pub fn test_annotation_context_update_on_points3d() {
+    let mut test_context = TestContext::new_with_view_class::();
+
+    let timeline = Timeline::new_sequence("frame");
+    test_context.set_active_timeline(*timeline.name());
+
+    let frame = |seq: i64| {
+        TimePoint::default().with(
+            timeline,
+            TimeInt::from_sequence(seq.try_into().expect("unexpected min value")),
+        )
+    };
+
+    // Log a static point cloud with two points using class_ids.
+    test_context.log_entity("points", |builder| {
+        builder.with_archetype_auto_row(
+            TimePoint::STATIC,
+            &re_sdk_types::archetypes::Points3D::new([[0.0, 0.0, 0.0], [1.0, 0.0, 0.0]])
+                .with_radii([0.3])
+                .with_class_ids([0, 1]),
+        )
+    });
+
+    // Frame 1: class 0 → red, class 1 → green
+    test_context.log_entity("/", |builder| {
+        builder.with_archetype_auto_row(
+            frame(1),
+            &re_sdk_types::archetypes::AnnotationContext::new([
+                (
+                    0,
+                    "red",
+                    re_sdk_types::datatypes::Rgba32::from_rgb(255, 0, 0),
+                ),
+                (
+                    1,
+                    "green",
+                    re_sdk_types::datatypes::Rgba32::from_rgb(0, 255, 0),
+                ),
+            ]),
+        )
+    });
+
+    // Frame 2: class 0 → blue, class 1 → yellow
+    test_context.log_entity("/", |builder| {
+        builder.with_archetype_auto_row(
+            frame(2),
+            &re_sdk_types::archetypes::AnnotationContext::new([
+                (
+                    0,
+                    "blue",
+                    re_sdk_types::datatypes::Rgba32::from_rgb(0, 0, 255),
+                ),
+                (
+                    1,
+                    "yellow",
+                    re_sdk_types::datatypes::Rgba32::from_rgb(255, 255, 0),
+                ),
+            ]),
+        )
+    });
+
+    let view_id = setup_blueprint(&mut test_context);
+
+    run_view_ui_and_save_snapshot(&test_context, view_id, "annotation_context_update");
+}
+
+fn setup_blueprint(test_context: &mut TestContext) -> ViewId {
+    test_context.setup_viewport_blueprint(|ctx, blueprint| {
+        let view_blueprint =
+            ViewBlueprint::new_with_root_wildcard(re_view_spatial::SpatialView3D::identifier());
+
+        let view_id = view_blueprint.id;
+        blueprint.add_views(std::iter::once(view_blueprint), None, None);
+
+        // Set eye position so both points are clearly visible.
+        let property = ViewProperty::from_archetype::(
+            ctx.blueprint_db(),
+            ctx.blueprint_query(),
+            view_id,
+        );
+        property.save_blueprint_component(
+            ctx,
+            &EyeControls3D::descriptor_position(),
+            &Position3D::new(0.5, 2.0, 3.0),
+        );
+        property.save_blueprint_component(
+            ctx,
+            &EyeControls3D::descriptor_look_target(),
+            &Position3D::new(0.5, 0.0, 0.0),
+        );
+
+        view_id
+    })
+}
+
+fn run_view_ui_and_save_snapshot(test_context: &TestContext, view_id: ViewId, name: &str) {
+    let size = egui::vec2(200.0, 200.0);
+
+    let mut snapshot_results = SnapshotResults::new();
+    let mut harness = test_context
+        .setup_kittest_for_rendering_3d(size)
+        .build_ui(|ui| {
+            test_context.run_with_single_view(ui, view_id);
+        });
+
+    // Frame 1: should show red + green points.
+    test_context.send_time_commands(
+        test_context.active_store_id(),
+        [TimeControlCommand::SetTime(1_i64.into())],
+    );
+    harness.run();
+    snapshot_results.add(harness.try_snapshot(format!("{name}_frame1")));
+
+    // Frame 2: should show blue + yellow points (not red + green).
+    test_context.send_time_commands(
+        test_context.active_store_id(),
+        [TimeControlCommand::SetTime(2_i64.into())],
+    );
+    harness.run();
+    snapshot_results.add(harness.try_snapshot(format!("{name}_frame2")));
+}
diff --git a/crates/viewer/re_view_spatial/tests/snapshots/annotation_context_update_frame1.png b/crates/viewer/re_view_spatial/tests/snapshots/annotation_context_update_frame1.png
new file mode 100644
index 000000000000..5f844bf9c915
--- /dev/null
+++ b/crates/viewer/re_view_spatial/tests/snapshots/annotation_context_update_frame1.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:2622e8c3b032d980d21eda01ede8e2fc8034bc4ad628d5f3bf3c1af82c2e6750
+size 16250
diff --git a/crates/viewer/re_view_spatial/tests/snapshots/annotation_context_update_frame2.png b/crates/viewer/re_view_spatial/tests/snapshots/annotation_context_update_frame2.png
new file mode 100644
index 000000000000..708319cc1df1
--- /dev/null
+++ b/crates/viewer/re_view_spatial/tests/snapshots/annotation_context_update_frame2.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:eafcc62002284b8c6cabc31179f9e0ce94fd4b503bd0249c7eadf166cbe392bd
+size 16655
diff --git a/crates/viewer/re_view_spatial/tests/snapshots/transform_clamping_with_base_transform_EntityHierarchy_boxes.png b/crates/viewer/re_view_spatial/tests/snapshots/transform_clamping_with_base_transform_EntityHierarchy_boxes.png
index 8b013369af66..2c698091f077 100644
--- a/crates/viewer/re_view_spatial/tests/snapshots/transform_clamping_with_base_transform_EntityHierarchy_boxes.png
+++ b/crates/viewer/re_view_spatial/tests/snapshots/transform_clamping_with_base_transform_EntityHierarchy_boxes.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:086b157ba3363ef21010cd0b470260da6746da9e04d1f5c6bf80bb719f0fc66e
-size 52301
+oid sha256:c2a0fa0f3441e137539150ffb1b2419d0d17c14be7489dc130afea82e2586b97
+size 51902
diff --git a/crates/viewer/re_view_spatial/tests/snapshots/transform_clamping_with_base_transform_EntityHierarchy_spheres.png b/crates/viewer/re_view_spatial/tests/snapshots/transform_clamping_with_base_transform_EntityHierarchy_spheres.png
index 7c1fd95d225f..aaaacc7833b7 100644
--- a/crates/viewer/re_view_spatial/tests/snapshots/transform_clamping_with_base_transform_EntityHierarchy_spheres.png
+++ b/crates/viewer/re_view_spatial/tests/snapshots/transform_clamping_with_base_transform_EntityHierarchy_spheres.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:4059aa0e4d55a0ae1b7ef49688b904bdc0fef4b4448bb25d32c79bc0876948c4
-size 57220
+oid sha256:e13b0f67481c8c6c5eaab9f1c72d8ae39afd0c3ec829877a71238aea2ff382bb
+size 57735
diff --git a/crates/viewer/re_view_spatial/tests/snapshots/transform_clamping_with_base_transform_FrameHierarchy_boxes.png b/crates/viewer/re_view_spatial/tests/snapshots/transform_clamping_with_base_transform_FrameHierarchy_boxes.png
index 8b013369af66..2c698091f077 100644
--- a/crates/viewer/re_view_spatial/tests/snapshots/transform_clamping_with_base_transform_FrameHierarchy_boxes.png
+++ b/crates/viewer/re_view_spatial/tests/snapshots/transform_clamping_with_base_transform_FrameHierarchy_boxes.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:086b157ba3363ef21010cd0b470260da6746da9e04d1f5c6bf80bb719f0fc66e
-size 52301
+oid sha256:c2a0fa0f3441e137539150ffb1b2419d0d17c14be7489dc130afea82e2586b97
+size 51902
diff --git a/crates/viewer/re_view_spatial/tests/snapshots/transform_clamping_with_base_transform_FrameHierarchy_spheres.png b/crates/viewer/re_view_spatial/tests/snapshots/transform_clamping_with_base_transform_FrameHierarchy_spheres.png
index 7c1fd95d225f..aaaacc7833b7 100644
--- a/crates/viewer/re_view_spatial/tests/snapshots/transform_clamping_with_base_transform_FrameHierarchy_spheres.png
+++ b/crates/viewer/re_view_spatial/tests/snapshots/transform_clamping_with_base_transform_FrameHierarchy_spheres.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:4059aa0e4d55a0ae1b7ef49688b904bdc0fef4b4448bb25d32c79bc0876948c4
-size 57220
+oid sha256:e13b0f67481c8c6c5eaab9f1c72d8ae39afd0c3ec829877a71238aea2ff382bb
+size 57735
diff --git a/crates/viewer/re_view_spatial/tests/snapshots/transform_clamping_with_base_transform_None_boxes.png b/crates/viewer/re_view_spatial/tests/snapshots/transform_clamping_with_base_transform_None_boxes.png
index 771629ccd735..52d831c8e07c 100644
--- a/crates/viewer/re_view_spatial/tests/snapshots/transform_clamping_with_base_transform_None_boxes.png
+++ b/crates/viewer/re_view_spatial/tests/snapshots/transform_clamping_with_base_transform_None_boxes.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:1c2471b3a9f67ac65643685a9e3261acb57ae91c9618e1391081341741d5c4e6
-size 52984
+oid sha256:2ec276a9e53ed89d3471c03e6bdbfed0a3e1120d9aba9e3c5550c26e6121659e
+size 52235
diff --git a/crates/viewer/re_view_spatial/tests/snapshots/transform_clamping_with_base_transform_None_spheres.png b/crates/viewer/re_view_spatial/tests/snapshots/transform_clamping_with_base_transform_None_spheres.png
index cddcf588b035..a4724ac68736 100644
--- a/crates/viewer/re_view_spatial/tests/snapshots/transform_clamping_with_base_transform_None_spheres.png
+++ b/crates/viewer/re_view_spatial/tests/snapshots/transform_clamping_with_base_transform_None_spheres.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:34ece0ed62ef152f0c00002005eab12c99b9144276f84ea9b6c645eab76e17cc
-size 59471
+oid sha256:0799188856a70a7bbc0d4bf00872f3fb99e0172d4511cf70b643cc82127f87b0
+size 59873
diff --git a/crates/viewer/re_viewer_context/src/annotations.rs b/crates/viewer/re_viewer_context/src/annotations.rs
index 89f81d0c83ae..bda559da42cc 100644
--- a/crates/viewer/re_viewer_context/src/annotations.rs
+++ b/crates/viewer/re_viewer_context/src/annotations.rs
@@ -52,6 +52,9 @@ impl Annotations {
         }
     }
 
+    /// The [`RowId`] of the annotation context that was used to create this [`Annotations`].
+    ///
+    /// This can be used as a cache key to determine if the annotation context has changed.
     #[inline]
     pub fn row_id(&self) -> RowId {
         self.row_id
@@ -176,6 +179,17 @@ impl ResolvedAnnotationInfo {
     }
 }
 
+impl re_byte_size::SizeBytes for ResolvedAnnotationInfo {
+    #[inline]
+    fn heap_size_bytes(&self) -> u64 {
+        let Self {
+            class_id,
+            annotation_info,
+        } = self;
+        class_id.heap_size_bytes() + annotation_info.heap_size_bytes()
+    }
+}
+
 // ----------------------------------------------------------------------------
 
 /// Many [`ResolvedAnnotationInfo`], with optimization
@@ -188,6 +202,16 @@ pub enum ResolvedAnnotationInfos {
     Many(Vec),
 }
 
+impl re_byte_size::SizeBytes for ResolvedAnnotationInfos {
+    #[inline]
+    fn heap_size_bytes(&self) -> u64 {
+        match self {
+            Self::Same(_count, info) => info.heap_size_bytes(),
+            Self::Many(infos) => infos.heap_size_bytes(),
+        }
+    }
+}
+
 impl ResolvedAnnotationInfos {
     pub fn iter(&self) -> impl Iterator {
         use itertools::Either;
diff --git a/tests/rust/re_integration_test/tests/snapshots/add_visualizer_axes_1.png b/tests/rust/re_integration_test/tests/snapshots/add_visualizer_axes_1.png
index a18c8e2c3534..bff344a44543 100644
--- a/tests/rust/re_integration_test/tests/snapshots/add_visualizer_axes_1.png
+++ b/tests/rust/re_integration_test/tests/snapshots/add_visualizer_axes_1.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:37498f081bd98dc037253947c212f05114d02fc1d2410d869c512091baafce13
-size 157772
+oid sha256:4a365d99516549e6672d71efb090cabbca571ba198e24a0c40c2f69c540b4b7e
+size 154938
diff --git a/tests/rust/re_integration_test/tests/snapshots/add_visualizer_axes_2.png b/tests/rust/re_integration_test/tests/snapshots/add_visualizer_axes_2.png
index 7c68eeb7f973..89095396aa89 100644
--- a/tests/rust/re_integration_test/tests/snapshots/add_visualizer_axes_2.png
+++ b/tests/rust/re_integration_test/tests/snapshots/add_visualizer_axes_2.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:4e096bd35573d266ecec8f66edae7e8cebc5c66a1ab242ed3047da1abed0d233
-size 187414
+oid sha256:afccd34d47c00ae00282b6ae89748d612f042d71db0d7ef61058f2c2cbce8cf1
+size 184883
diff --git a/tests/rust/re_integration_test/tests/snapshots/add_visualizer_axes_3.png b/tests/rust/re_integration_test/tests/snapshots/add_visualizer_axes_3.png
index e1ba70d8469e..f2f98f8869e7 100644
--- a/tests/rust/re_integration_test/tests/snapshots/add_visualizer_axes_3.png
+++ b/tests/rust/re_integration_test/tests/snapshots/add_visualizer_axes_3.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:93ccab959f648e9c251ca5c422f5c05a290b9354a72f73046283005676426613
-size 182843
+oid sha256:31066fb9486bbee63b82ed7c822b7bf9568ea2d455b27b5c15629d1ec7dd307d
+size 180884
diff --git a/tests/rust/re_integration_test/tests/snapshots/add_visualizer_axes_4.png b/tests/rust/re_integration_test/tests/snapshots/add_visualizer_axes_4.png
index c25e62d95f41..4cb9ca035899 100644
--- a/tests/rust/re_integration_test/tests/snapshots/add_visualizer_axes_4.png
+++ b/tests/rust/re_integration_test/tests/snapshots/add_visualizer_axes_4.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:b3d69b31375ac29658eaabb9744fa8944ccad92e59f892bd81424efa7c41518d
-size 191470
+oid sha256:7c33d34b43156cef9b141ec550b41b4f75db370e7efb24912f4f4647762f2b5a
+size 187859
diff --git a/tests/rust/re_integration_test/tests/snapshots/add_visualizer_axes_5.png b/tests/rust/re_integration_test/tests/snapshots/add_visualizer_axes_5.png
index 2a806f8ba04e..a4814d3494fc 100644
--- a/tests/rust/re_integration_test/tests/snapshots/add_visualizer_axes_5.png
+++ b/tests/rust/re_integration_test/tests/snapshots/add_visualizer_axes_5.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:00926de4051a6dbf0423d942cb63d896f1da17807272b5ecb381ef34175e6adb
-size 192365
+oid sha256:bc6b7c77fa4c21b63eea6b09cc5d55bbd431c310074b73f4171573187c193a57
+size 189317
diff --git a/tests/rust/re_integration_test/tests/snapshots/deselect_on_escape_1.png b/tests/rust/re_integration_test/tests/snapshots/deselect_on_escape_1.png
index 049afba5e918..95201aa06360 100644
--- a/tests/rust/re_integration_test/tests/snapshots/deselect_on_escape_1.png
+++ b/tests/rust/re_integration_test/tests/snapshots/deselect_on_escape_1.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:fbaab2cde0271845d2e173e70ead96b5ad0ebd5bc2be9184a86aa676b9191e91
-size 129250
+oid sha256:8b5ff507ad49d72fd59c1eb5ab0dcc76fed07f0d245ff63af4a3244dfdc5cd74
+size 126535
diff --git a/tests/rust/re_integration_test/tests/snapshots/deselect_on_escape_2.png b/tests/rust/re_integration_test/tests/snapshots/deselect_on_escape_2.png
index 3d2115a6acd3..e6bf1a06a425 100644
--- a/tests/rust/re_integration_test/tests/snapshots/deselect_on_escape_2.png
+++ b/tests/rust/re_integration_test/tests/snapshots/deselect_on_escape_2.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:a5c620c33a82943f20eb9e33540f45d05ca96670c235fb31fa9b0f00d76bd239
-size 172889
+oid sha256:59925a781b687c9bef577403da8a1008fd671ac4141cd81e800e5df931564a9e
+size 171770
diff --git a/tests/rust/re_integration_test/tests/snapshots/deselect_on_escape_3.png b/tests/rust/re_integration_test/tests/snapshots/deselect_on_escape_3.png
index d4f73ebf51fc..7d202c412c7c 100644
--- a/tests/rust/re_integration_test/tests/snapshots/deselect_on_escape_3.png
+++ b/tests/rust/re_integration_test/tests/snapshots/deselect_on_escape_3.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:f230ed492ece4d9009e20c51adfda4d97cb2c693128e84a0d9d34ff4c1c509d8
-size 147614
+oid sha256:d7a10b458fdb888ac69f8f3f6f415511f84866e7c98fbd41984195309809d065
+size 146244
diff --git a/tests/rust/re_integration_test/tests/snapshots/deselect_on_escape_4.png b/tests/rust/re_integration_test/tests/snapshots/deselect_on_escape_4.png
index 4c0a24e039dc..2a2dcbf02ceb 100644
--- a/tests/rust/re_integration_test/tests/snapshots/deselect_on_escape_4.png
+++ b/tests/rust/re_integration_test/tests/snapshots/deselect_on_escape_4.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:d37d92be62caa75405142ab147f9a61a0beeef375d4b779fd721a0ea112d43e8
-size 217205
+oid sha256:bde0a6859db57463115d18d5b85ac7f71ab8969fc1a66e6e1c42c722011f20c2
+size 217464
diff --git a/tests/rust/re_integration_test/tests/snapshots/deselect_on_escape_5.png b/tests/rust/re_integration_test/tests/snapshots/deselect_on_escape_5.png
index 4020929a2671..448b5b99b9f4 100644
--- a/tests/rust/re_integration_test/tests/snapshots/deselect_on_escape_5.png
+++ b/tests/rust/re_integration_test/tests/snapshots/deselect_on_escape_5.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:a03bf72c3b713fb9fa951aaa736f2e6e72750418739e7e848d9fafcf9194122b
-size 173000
+oid sha256:d26c84c0a6affa855beda9a192b87f0ecb7d01b2f97da22de25e3ac6d94b193a
+size 171929
diff --git a/tests/rust/re_integration_test/tests/snapshots/deselect_on_escape_6.png b/tests/rust/re_integration_test/tests/snapshots/deselect_on_escape_6.png
index 5111ec6bd5b3..429d9052fb13 100644
--- a/tests/rust/re_integration_test/tests/snapshots/deselect_on_escape_6.png
+++ b/tests/rust/re_integration_test/tests/snapshots/deselect_on_escape_6.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:383ddd956a6bc70a829c07e5b9ca93ca06f30400f9a7c697360b50b6fdf3a598
-size 190344
+oid sha256:c6a4296d087786809d5b963df203215dd8ff1e30c6a1e3bd21edadfdc82b5c71
+size 189286
diff --git a/tests/rust/re_integration_test/tests/snapshots/deselect_on_escape_7.png b/tests/rust/re_integration_test/tests/snapshots/deselect_on_escape_7.png
index 153e0573aef0..1f0122b1c458 100644
--- a/tests/rust/re_integration_test/tests/snapshots/deselect_on_escape_7.png
+++ b/tests/rust/re_integration_test/tests/snapshots/deselect_on_escape_7.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:a630d86f24eb82e5abe831968b24b7d03b3a16790f4dccc10e4baaa59eb1645b
-size 172935
+oid sha256:b7c1f09a714e9a62855071a7ce90aeb49a7c6646a622c91bef8e878c71417179
+size 171806
diff --git a/tests/rust/re_integration_test/tests/snapshots/deselect_on_escape_8.png b/tests/rust/re_integration_test/tests/snapshots/deselect_on_escape_8.png
index 5e0ec492d3bb..49ae53197acb 100644
--- a/tests/rust/re_integration_test/tests/snapshots/deselect_on_escape_8.png
+++ b/tests/rust/re_integration_test/tests/snapshots/deselect_on_escape_8.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:e726d7d85f9db0c47b388ad6d91eb7d090d8baa10acc3b290fca169f8c4193ae
-size 128348
+oid sha256:15d62f66b0aec2409b068b2cea4245c8848e05fb3a8fb0740fb942b2be72acf6
+size 125635
diff --git a/tests/rust/re_integration_test/tests/snapshots/heuristics_mixed_2d_and_3d.png b/tests/rust/re_integration_test/tests/snapshots/heuristics_mixed_2d_and_3d.png
index e8fa41bc639a..a5e82e6d41d7 100644
--- a/tests/rust/re_integration_test/tests/snapshots/heuristics_mixed_2d_and_3d.png
+++ b/tests/rust/re_integration_test/tests/snapshots/heuristics_mixed_2d_and_3d.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:9cb9eb0ef81339200d042c7c0364913fff5000f0681a0940c78194dc3e0f2956
-size 95005
+oid sha256:4b3e5918655e98bf302fef2a07143002b7aed7cea333a66e142dc1947f0aa658
+size 93308
diff --git a/tests/rust/re_integration_test/tests/snapshots/heuristics_mixed_all_root.png b/tests/rust/re_integration_test/tests/snapshots/heuristics_mixed_all_root.png
index 51c7c0f6719d..206542955272 100644
--- a/tests/rust/re_integration_test/tests/snapshots/heuristics_mixed_all_root.png
+++ b/tests/rust/re_integration_test/tests/snapshots/heuristics_mixed_all_root.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:7ac4154224e88c1c92f8e1cf9c8052959ffc840fee08f66cb3865c063901afaf
-size 129337
+oid sha256:991b118a77a1c22edb02463a4368549f52d3b3c093821a6f51f81d9454802c97
+size 128604
diff --git a/tests/rust/re_integration_test/tests/snapshots/origin_camera_2d.png b/tests/rust/re_integration_test/tests/snapshots/origin_camera_2d.png
index 4b083ff383e7..59d8f042321e 100644
--- a/tests/rust/re_integration_test/tests/snapshots/origin_camera_2d.png
+++ b/tests/rust/re_integration_test/tests/snapshots/origin_camera_2d.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:3e3345bf1f7876a14f48fd11cae41944dfb735858f93c9bb8b9bfc4e90cd1607
-size 150082
+oid sha256:e79920bfe5ea660702accb966aae557e094fd0c5929e85c5a81a236fd42493c2
+size 148663
diff --git a/tests/rust/re_integration_test/tests/snapshots/origin_camera_3d.png b/tests/rust/re_integration_test/tests/snapshots/origin_camera_3d.png
index 99204babadeb..fbc050a684c9 100644
--- a/tests/rust/re_integration_test/tests/snapshots/origin_camera_3d.png
+++ b/tests/rust/re_integration_test/tests/snapshots/origin_camera_3d.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:151a1409330801d36319a8595093810178e483da104c527546b5318e4eba6798
-size 194958
+oid sha256:c92949628128382cbf68ae59f696437147788d92d6455a5e3011a87bb111e355
+size 193470
diff --git a/tests/rust/re_integration_test/tests/snapshots/origin_image_2d.png b/tests/rust/re_integration_test/tests/snapshots/origin_image_2d.png
index 0198a8e6a8a9..e8b2b63e8a35 100644
--- a/tests/rust/re_integration_test/tests/snapshots/origin_image_2d.png
+++ b/tests/rust/re_integration_test/tests/snapshots/origin_image_2d.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:805b852e2c4542f4c73f832a1819d9a69594fd253fb4947e41ca84178e9d9f23
-size 176545
+oid sha256:1ad171c169dfdcb36461355f64ae6fc986fbfd02380e6fc15aec7b92076f3253
+size 175208
diff --git a/tests/rust/re_integration_test/tests/snapshots/origin_image_3d.png b/tests/rust/re_integration_test/tests/snapshots/origin_image_3d.png
index 781b3ff781b4..9800fc8dfbd9 100644
--- a/tests/rust/re_integration_test/tests/snapshots/origin_image_3d.png
+++ b/tests/rust/re_integration_test/tests/snapshots/origin_image_3d.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:eb879e7f199b94cd97fd85edca18dc4c9283f276aaf0b270e13b340aa7fe1ef3
-size 194943
+oid sha256:87f8271f46cce09a00eca8251cc0f29af3d78a6f702224fce0f997c5031dbaf7
+size 193474
diff --git a/tests/rust/re_integration_test/tests/snapshots/origin_keypoint_2d.png b/tests/rust/re_integration_test/tests/snapshots/origin_keypoint_2d.png
index 07318c061d08..f486e3373fc5 100644
--- a/tests/rust/re_integration_test/tests/snapshots/origin_keypoint_2d.png
+++ b/tests/rust/re_integration_test/tests/snapshots/origin_keypoint_2d.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:34e9d948f9115a89899cf4bff9af12b38c22c1337f312b226326c293e4a4586e
-size 163284
+oid sha256:42348f7054ebdda1af759ef999a0af8a705d88a34effb393a9d58ccc2beb3a56
+size 161869
diff --git a/tests/rust/re_integration_test/tests/snapshots/origin_keypoint_3d.png b/tests/rust/re_integration_test/tests/snapshots/origin_keypoint_3d.png
index 992ef3c4a33c..742532f459fd 100644
--- a/tests/rust/re_integration_test/tests/snapshots/origin_keypoint_3d.png
+++ b/tests/rust/re_integration_test/tests/snapshots/origin_keypoint_3d.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:22a669e5bfa9b5226d2d479eeb80826aae686d8bbd74708274cd897fb96dec63
-size 191220
+oid sha256:adbb18ed071850930e0ffd565cf09c3acb73e9187d1a2dfa53f4c9a56123adec
+size 189225
diff --git a/tests/rust/re_integration_test/tests/snapshots/origin_root_2d.png b/tests/rust/re_integration_test/tests/snapshots/origin_root_2d.png
index 999f7fa4b5b7..d582f6296ea7 100644
--- a/tests/rust/re_integration_test/tests/snapshots/origin_root_2d.png
+++ b/tests/rust/re_integration_test/tests/snapshots/origin_root_2d.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:9cfe4045fd60be73287912014e6aeb428ceceb8d21b36f6645864eea3618f861
-size 148430
+oid sha256:f93e81a82e9b74f9a4dd0bdb669d64325a7d886ee53656fa3f1ff086cdf0b6a5
+size 147064
diff --git a/tests/rust/re_integration_test/tests/snapshots/origin_root_3d.png b/tests/rust/re_integration_test/tests/snapshots/origin_root_3d.png
index defb37b42a9d..c0806d49bf9b 100644
--- a/tests/rust/re_integration_test/tests/snapshots/origin_root_3d.png
+++ b/tests/rust/re_integration_test/tests/snapshots/origin_root_3d.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:a14d5dd61ded6d948001888cf47d525d0afd81545ba5bcc5e19d7d26ceaff515
-size 201108
+oid sha256:394ce7a56259ddf42e87812c1f523b4c4b791d948436044d51abc0b3186c3b6a
+size 198856
diff --git a/tests/rust/re_integration_test/tests/snapshots/origin_world_2d.png b/tests/rust/re_integration_test/tests/snapshots/origin_world_2d.png
index b8ae95619657..4fd7c24652c5 100644
--- a/tests/rust/re_integration_test/tests/snapshots/origin_world_2d.png
+++ b/tests/rust/re_integration_test/tests/snapshots/origin_world_2d.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:0588446a654315bff40c357a59b659b66875bcbbc196dad322775890756cb301
-size 148998
+oid sha256:789a177de802439b9ff48896b682cc63db5b6bf86243c2b80676063e910a0660
+size 147634
diff --git a/tests/rust/re_integration_test/tests/snapshots/origin_world_3d.png b/tests/rust/re_integration_test/tests/snapshots/origin_world_3d.png
index 362f656389d3..530385266cc5 100644
--- a/tests/rust/re_integration_test/tests/snapshots/origin_world_3d.png
+++ b/tests/rust/re_integration_test/tests/snapshots/origin_world_3d.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:1b55903bb516e1a9ca16b1b745d22ca3fdafe279ed73028cf5588b8efb04fb65
-size 193805
+oid sha256:28938a2bc79d42900c2f0f3ad9917707e3a1d9477436d897a499d4b17ce64c8b
+size 192492
diff --git a/tests/rust/re_integration_test/tests/snapshots/simplify_container_hierarchy_3.png b/tests/rust/re_integration_test/tests/snapshots/simplify_container_hierarchy_3.png
index 4eea790cdf9d..c428a0ec6fd7 100644
--- a/tests/rust/re_integration_test/tests/snapshots/simplify_container_hierarchy_3.png
+++ b/tests/rust/re_integration_test/tests/snapshots/simplify_container_hierarchy_3.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:8eacdc3ee2199af0e4aab9e73e099bc67fe270a96772dae8e964695df2f07971
-size 127315
+oid sha256:50c4755bdc9f75e35a06a5fb19a3bca236897e65ae8899736d11437a4229f618
+size 127318
diff --git a/tests/rust/re_integration_test/tests/snapshots/simplify_root_hierarchy_3.png b/tests/rust/re_integration_test/tests/snapshots/simplify_root_hierarchy_3.png
index 5bb102fedb47..8aebe9011ce3 100644
--- a/tests/rust/re_integration_test/tests/snapshots/simplify_root_hierarchy_3.png
+++ b/tests/rust/re_integration_test/tests/snapshots/simplify_root_hierarchy_3.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:b7af7ce373d7b94a6d5caa23bdd0d47a17384c4e4edd9f226a8623ab8ea4bfca
-size 128427
+oid sha256:f146aebc89f9186c2ab14e0b32405d41d9d240423149ec68b544dfa90c6d1dc1
+size 128430
diff --git a/tests/rust/re_integration_test/tests/snapshots/source_component_6.png b/tests/rust/re_integration_test/tests/snapshots/source_component_6.png
index e99025de3aa0..cd5863560bef 100644
--- a/tests/rust/re_integration_test/tests/snapshots/source_component_6.png
+++ b/tests/rust/re_integration_test/tests/snapshots/source_component_6.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:988035594fb1a161b3d336be840af2e9d0d10d9f26b7d0aafa15f95a8c635a20
-size 152054
+oid sha256:bded746183500a46a975e19eef9892bd5e07223621e95c8b8deb8bee9bc959fd
+size 207127
diff --git a/tests/rust/re_integration_test/tests/snapshots/source_component_7.png b/tests/rust/re_integration_test/tests/snapshots/source_component_7.png
index 4ffcb52bf033..b58429a773a1 100644
--- a/tests/rust/re_integration_test/tests/snapshots/source_component_7.png
+++ b/tests/rust/re_integration_test/tests/snapshots/source_component_7.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:d703feb53fd1078f24102525308192c78742b12ef1e104ae18a3ba6192684c7b
-size 160022
+oid sha256:e241af9fa0ea82f1b3a4c6e4c251eac98b857dba59d4d05934fa9bb5a3b2850f
+size 215639
diff --git a/tests/rust/re_integration_test/tests/snapshots/source_component_8.png b/tests/rust/re_integration_test/tests/snapshots/source_component_8.png
index 65df226cd428..14417c8b9113 100644
--- a/tests/rust/re_integration_test/tests/snapshots/source_component_8.png
+++ b/tests/rust/re_integration_test/tests/snapshots/source_component_8.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:55d36697d114363d3491a15b975cbf4782b4139bf950b5836980497c16348f7d
-size 170148
+oid sha256:a245c3b80e4932ab06cb3e367ea1a444ac61ba98575dd4544acb0dc4168253f6
+size 225798

From b367ae90de2c569b3259748df2e3c5bd4a63be85 Mon Sep 17 00:00:00 2001
From: Andreas Reich 
Date: Tue, 17 Mar 2026 00:41:29 +0100
Subject: [PATCH 156/513] Ignore outliers for focusing on 2d & 3d point clouds

### Related

* Fixes RR-4039

### What

Default camera for structure-from-motion demo at the same timestamp:

Before:
image

After:
image

We're now internally distinguishing a "region of interest" that can be
different from the bounding box. Since the bounding box is still useful
for users we kept it around. So far we're not exposing this ROI (don't
abbreviate it otherwise startup founders go crazy ;D).

We're using a very simple online algorithm - it was important to me to
do constant amount of passes and constant memory overhead as opposed to
taking O(n) memory for e.g. median computation - details see commentary.

Risk with the idea: If you have a lot of points in one place and
everything else elsewhere it may focus on the wrong point.

---------

Source-Ref: 9bd7843116b76dd535597a7fd1b0f826085676c0
Co-authored-by: Claude Opus 4.6 (1M context) 
---
 Cargo.lock                                    |   1 +
 .../archetypes/spatial_information.fbs        |   2 +
 crates/viewer/re_renderer/Cargo.toml          |   8 +
 .../re_renderer/benches/bench_bounding_box.rs |  49 ++++++
 crates/viewer/re_renderer/src/util.rs         | 158 ++++++++++++++++++
 crates/viewer/re_view_spatial/src/eye.rs      |  21 ++-
 .../src/scene_bounding_boxes.rs               |  83 ++++++---
 .../re_view_spatial/src/shared_fallbacks.rs   |  12 +-
 crates/viewer/re_view_spatial/src/ui.rs       |  16 +-
 crates/viewer/re_view_spatial/src/ui_3d.rs    |  23 ++-
 crates/viewer/re_view_spatial/src/view_2d.rs  |   5 +-
 crates/viewer/re_view_spatial/src/view_3d.rs  |   8 +-
 .../src/visualizers/points2d.rs               |   8 +-
 .../src/visualizers/points3d.rs               |  17 +-
 .../visualizers/utilities/proc_mesh_vis.rs    |   8 +-
 .../utilities/spatial_view_visualizer.rs      |  59 +++++--
 .../tests/snapshots/draw_order.png            |   4 +-
 .../static_overwrite_color_override.png       |   4 +-
 .../snapshots/static_overwrite_original.png   |   4 +-
 .../static_overwrite_radius_default.png       |   4 +-
 .../all_view_selection_uis/Dark/2D.png        |   4 +-
 .../all_view_selection_uis/Dark/3D.png        |   4 +-
 .../all_view_selection_uis/Light/2D.png       |   4 +-
 .../all_view_selection_uis/Light/3D.png       |   4 +-
 .../snapshots/heuristics_mixed_all_root.png   |   4 +-
 .../tests/snapshots/origin_camera_2d.png      |   4 +-
 .../tests/snapshots/origin_keypoint_3d.png    |   4 +-
 .../tests/snapshots/origin_root_2d.png        |   4 +-
 .../tests/snapshots/origin_world_2d.png       |   4 +-
 .../tests/snapshots/source_component_1.png    |   4 +-
 .../tests/snapshots/source_component_2.png    |   4 +-
 .../tests/snapshots/source_component_3.png    |   4 +-
 .../tests/snapshots/source_component_4.png    |   4 +-
 .../tests/snapshots/source_component_5.png    |   4 +-
 .../tests/snapshots/source_component_6.png    |   4 +-
 .../tests/snapshots/source_component_7.png    |   4 +-
 .../tests/snapshots/source_component_8.png    |   4 +-
 37 files changed, 439 insertions(+), 123 deletions(-)
 create mode 100644 crates/viewer/re_renderer/benches/bench_bounding_box.rs

diff --git a/Cargo.lock b/Cargo.lock
index f0a95a52012e..53c5a2758f05 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -9628,6 +9628,7 @@ dependencies = [
  "bytemuck",
  "cfg_aliases",
  "clean-path",
+ "criterion",
  "crossbeam",
  "dae-parser",
  "document-features",
diff --git a/crates/store/re_sdk_types/definitions/rerun/blueprint/archetypes/spatial_information.fbs b/crates/store/re_sdk_types/definitions/rerun/blueprint/archetypes/spatial_information.fbs
index 5b80b0d6595c..a3272158d83f 100644
--- a/crates/store/re_sdk_types/definitions/rerun/blueprint/archetypes/spatial_information.fbs
+++ b/crates/store/re_sdk_types/definitions/rerun/blueprint/archetypes/spatial_information.fbs
@@ -13,5 +13,7 @@ table SpatialInformation (
   show_axes: rerun.blueprint.components.Enabled ("attr.rerun.component_optional", nullable, order: 100);
 
   /// Whether the bounding box should be shown.
+  // TODO(andreas): Make this an enum so the user can choose between showing bounding boxes,
+  // regions of interest, or per-entity bounding boxes.
   show_bounding_box: rerun.blueprint.components.Enabled ("attr.rerun.component_optional", nullable, order: 200);
 }
diff --git a/crates/viewer/re_renderer/Cargo.toml b/crates/viewer/re_renderer/Cargo.toml
index 3951cbc7e295..3fe0b9429139 100644
--- a/crates/viewer/re_renderer/Cargo.toml
+++ b/crates/viewer/re_renderer/Cargo.toml
@@ -114,9 +114,17 @@ wasm-bindgen.workspace = true
 
 [dev-dependencies]
 re_log = { workspace = true, features = ["setup"] }
+criterion.workspace = true
 pollster.workspace = true
 unindent.workspace = true
 
+[lib]
+bench = false
+
+[[bench]]
+name = "bench_bounding_box"
+harness = false
+
 # For build.rs:
 [build-dependencies]
 # Rerun
diff --git a/crates/viewer/re_renderer/benches/bench_bounding_box.rs b/crates/viewer/re_renderer/benches/bench_bounding_box.rs
new file mode 100644
index 000000000000..ae10bef4df93
--- /dev/null
+++ b/crates/viewer/re_renderer/benches/bench_bounding_box.rs
@@ -0,0 +1,49 @@
+use criterion::{Criterion, criterion_group, criterion_main};
+use glam::Vec3;
+
+fn generate_point_cloud(n: usize, num_outliers: usize) -> Vec {
+    let mut points = Vec::with_capacity(n + num_outliers);
+
+    // Cluster of points around (5, 10, 15) with spread ~2.
+    for i in 0..n {
+        let t = i as f32 / n as f32;
+        points.push(Vec3::new(
+            5.0 + (t * 17.3).sin() * 2.0,
+            10.0 + (t * 31.7).cos() * 2.0,
+            15.0 + (t * 53.1).sin() * 2.0,
+        ));
+    }
+
+    // Outliers far away.
+    for i in 0..num_outliers {
+        let v = (i + 1) as f32 * 1000.0;
+        points.push(Vec3::new(v, -v, v));
+    }
+
+    points
+}
+
+fn bench_bounding_boxes(c: &mut Criterion) {
+    let mut group = c.benchmark_group("bounding_box");
+
+    for n in [100, 1_000, 10_000, 100_000] {
+        let points = generate_point_cloud(n, n / 100);
+
+        group.throughput(criterion::Throughput::Elements(points.len() as u64));
+        group.bench_function(format!("naive/{n}"), |b| {
+            b.iter(|| {
+                criterion::black_box(re_renderer::util::bounding_box_from_points(
+                    points.iter().copied(),
+                ))
+            });
+        });
+        group.bench_function(format!("point_cloud_bounds/{n}"), |b| {
+            b.iter(|| criterion::black_box(re_renderer::util::point_cloud_bounds(&points)));
+        });
+    }
+
+    group.finish();
+}
+
+criterion_group!(benches, bench_bounding_boxes);
+criterion_main!(benches);
diff --git a/crates/viewer/re_renderer/src/util.rs b/crates/viewer/re_renderer/src/util.rs
index 1aa6e01a1c88..022f4ff882c4 100644
--- a/crates/viewer/re_renderer/src/util.rs
+++ b/crates/viewer/re_renderer/src/util.rs
@@ -8,3 +8,161 @@ pub fn bounding_box_from_points(points: impl Iterator) -> mac
     }
     bbox
 }
+
+/// Computes per-axis mean and standard deviation from an iterator of `DVec3` values.
+///
+/// Uses f64 accumulators because the variance formula (`sum_sq/n - mean²`) computes a
+/// small number as the difference of two large ones. With f32's ~7 digits of precision,
+/// points centered far from the origin (e.g. around 10000 with spread ~1) lose nearly all
+/// significant digits in that subtraction. f64's ~15 digits avoid this.
+///
+/// Returns `None` if fewer than 2 values are provided.
+fn mean_and_sigma(values: impl Iterator) -> Option<(glam::DVec3, glam::DVec3)> {
+    let mut count = 0u64;
+    let mut sum = glam::DVec3::ZERO;
+    let mut sum_sq = glam::DVec3::ZERO;
+
+    for d in values {
+        sum += d;
+        sum_sq += d * d;
+        count += 1;
+    }
+
+    if count < 2 {
+        return None;
+    }
+
+    let n = count as f64;
+    let mean = sum / n;
+    let variance = (sum_sq / n - mean * mean).max(glam::DVec3::ZERO);
+    let sigma = glam::DVec3::new(variance.x.sqrt(), variance.y.sqrt(), variance.z.sqrt());
+    Some((mean, sigma))
+}
+
+/// Both the exact bounding box and a region of interest for a point cloud.
+pub struct PointCloudBounds {
+    /// Exact bounding box containing all finite points.
+    pub bbox: macaw::BoundingBox,
+
+    /// Region of interest that excludes spatial outliers.
+    ///
+    /// Useful for camera framing and other heuristics where extreme outliers
+    /// should not dominate the view. For normally distributed data, covers ~95%
+    /// of points; by Chebyshev's inequality, at least 75% for any distribution.
+    pub region_of_interest: macaw::BoundingBox,
+}
+
+/// Computes both an exact bounding box and an outlier-robust region of interest
+/// for a point cloud, using O(1) memory and two passes.
+///
+/// The region of interest is computed via a two-pass robust mean/σ approach:
+/// **Pass 1**: Compute per-axis mean and standard deviation over all finite points.
+/// **Pass 2**: Recompute mean and σ, ignoring points beyond 2σ from the initial mean.
+/// The result is `[mean - 2σ, mean + 2σ]` from the cleaned statistics.
+///
+/// The second pass makes this robust against extreme outliers that would otherwise
+/// skew the mean.
+///
+/// Non-finite points are ignored.
+pub fn point_cloud_bounds(points: &[glam::Vec3]) -> PointCloudBounds {
+    re_tracing::profile_function_if!(points.len() > 10000);
+
+    let bbox = bounding_box_from_points(points.iter().copied());
+
+    let finite_f64 = || {
+        points
+            .iter()
+            .filter(|p| p.is_finite())
+            .map(|p| p.as_dvec3())
+    };
+
+    // Pass 1: raw mean and σ over all finite points.
+    let Some((mean, sigma)) = mean_and_sigma(finite_f64()) else {
+        return PointCloudBounds {
+            bbox,
+            region_of_interest: bbox,
+        };
+    };
+
+    // Pass 2: recompute, excluding points beyond 2σ from the raw mean.
+    let lo = mean - 2.0 * sigma;
+    let hi = mean + 2.0 * sigma;
+    let Some((mean, sigma)) =
+        mean_and_sigma(finite_f64().filter(|d| d.cmpge(lo).all() && d.cmple(hi).all()))
+    else {
+        return PointCloudBounds {
+            bbox,
+            region_of_interest: bbox,
+        };
+    };
+
+    let region_of_interest = macaw::BoundingBox::from_min_max(
+        (mean - 2.0 * sigma).as_vec3(),
+        (mean + 2.0 * sigma).as_vec3(),
+    );
+
+    PointCloudBounds {
+        bbox,
+        region_of_interest,
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use glam::Vec3;
+
+    use super::*;
+
+    #[test]
+    fn point_cloud_bounds_excludes_outlier_from_region_of_interest() {
+        // 9 points with varied x/y/z, clustered roughly in [0..3, 0..4, 0..5],
+        // plus one outlier far away.
+        let cluster_core = [
+            Vec3::new(0.0, 1.0, 2.0),
+            Vec3::new(1.0, 2.0, 0.5),
+            Vec3::new(2.0, 0.5, 4.0),
+            Vec3::new(0.5, 3.0, 1.0),
+            Vec3::new(1.5, 1.5, 3.0),
+            Vec3::new(0.2, 2.5, 0.8),
+            Vec3::new(2.5, 0.2, 4.5),
+            Vec3::new(0.8, 3.5, 2.5),
+            Vec3::new(1.2, 1.8, 1.5),
+        ];
+        let outlier = Vec3::new(100.0, 200.0, 300.0);
+        let points = cluster_core
+            .iter()
+            .copied()
+            .chain(std::iter::once(outlier))
+            .collect::>();
+
+        let bounds = point_cloud_bounds(&points);
+
+        // The exact bbox must contain the outlier.
+        assert!(
+            bounds.bbox.contains(outlier),
+            "bbox must contain outlier: {:?}",
+            bounds.bbox,
+        );
+
+        // The ROI should NOT extend to the outlier.
+        assert!(
+            bounds.region_of_interest.max.x < 5.0
+                && bounds.region_of_interest.max.y < 5.0
+                && bounds.region_of_interest.max.z < 5.0
+                && bounds.region_of_interest.min.x > -1.0
+                && bounds.region_of_interest.min.y > -1.0
+                && bounds.region_of_interest.min.z > -1.0,
+            "outlier should not extend the region of interest: {:?}",
+            bounds.region_of_interest,
+        );
+
+        // The ROI should still contain the bulk of the cluster.
+        for point in cluster_core {
+            assert!(
+                bounds.region_of_interest.contains(point),
+                "inlier point should be in region of interest: {point:?} in {:?}",
+                bounds.region_of_interest,
+            );
+        }
+    }
+}
diff --git a/crates/viewer/re_view_spatial/src/eye.rs b/crates/viewer/re_view_spatial/src/eye.rs
index ce07149a1df9..5b0ddabce65c 100644
--- a/crates/viewer/re_view_spatial/src/eye.rs
+++ b/crates/viewer/re_view_spatial/src/eye.rs
@@ -794,14 +794,20 @@ impl EyeState {
                 //     ..
                 // }) = ctx.selection_state().hovered_space_context()
 
-                if let Some(entity_bbox) = bounding_boxes.per_entity.get(&tracking_entity.hash()) {
+                if let Some(entity_bbox) = bounding_boxes
+                    .region_of_interest_per_entity
+                    .get(&tracking_entity.hash())
+                {
                     // If we're tracking something new, set the current position & look target to the correct view.
                     if new_tracking {
                         let fwd = eye_controller.fwd();
                         let radius = entity_bbox.centered_bounding_sphere_radius() * 1.5;
                         let radius = if radius < 0.0001 {
                             // Handle zero-sized bounding boxes:
-                            (bounding_boxes.current.centered_bounding_sphere_radius() * 1.5)
+                            (bounding_boxes
+                                .region_of_interest_current
+                                .centered_bounding_sphere_radius()
+                                * 1.5)
                                 .at_least(0.02)
                         } else {
                             radius
@@ -936,7 +942,10 @@ impl EyeState {
         // Focusing cameras is not something that happens now, since those are always tracked.
         if let Some(target_eye) = find_camera(cameras, focused_entity) {
             eye_controller.copy_from_eye(&target_eye);
-        } else if let Some(entity_bbox) = bounding_boxes.per_entity.get(&focused_entity.hash()) {
+        } else if let Some(entity_bbox) = bounding_boxes
+            .region_of_interest_per_entity
+            .get(&focused_entity.hash())
+        {
             let fwd = self
                 .last_eye
                 .map(|eye| eye.forward_in_world())
@@ -944,7 +953,11 @@ impl EyeState {
             let radius = entity_bbox.centered_bounding_sphere_radius() * 1.5;
             let radius = if radius < 0.0001 {
                 // Handle zero-sized bounding boxes:
-                (bounding_boxes.current.centered_bounding_sphere_radius() * 1.5).at_least(0.02)
+                (bounding_boxes
+                    .region_of_interest_current
+                    .centered_bounding_sphere_radius()
+                    * 1.5)
+                    .at_least(0.02)
             } else {
                 radius
             };
diff --git a/crates/viewer/re_view_spatial/src/scene_bounding_boxes.rs b/crates/viewer/re_view_spatial/src/scene_bounding_boxes.rs
index 76d13f3ee4d9..f898ae55673e 100644
--- a/crates/viewer/re_view_spatial/src/scene_bounding_boxes.rs
+++ b/crates/viewer/re_view_spatial/src/scene_bounding_boxes.rs
@@ -11,21 +11,32 @@ pub struct SceneBoundingBoxes {
     /// Overall bounding box of the scene for the current query.
     pub current: macaw::BoundingBox,
 
-    /// A bounding box that smoothly transitions to the current bounding box.
-    ///
-    /// If discontinuities are detected, this bounding box will be reset immediately to the current bounding box.
-    pub smoothed: macaw::BoundingBox,
-
     /// Per-entity bounding boxes for the current query.
     pub per_entity: IntMap,
+
+    /// Overall region of interest of the scene for the current query.
+    ///
+    /// For most entities this equals the bounding box, but may exclude outliers.
+    /// Used for camera framing and other heuristics.
+    pub region_of_interest_current: macaw::BoundingBox,
+
+    /// A region of interest that smoothly transitions to the current one.
+    ///
+    /// If discontinuities are detected, this will be reset immediately.
+    pub region_of_interest_smoothed: macaw::BoundingBox,
+
+    /// Per-entity regions of interest for the current query.
+    pub region_of_interest_per_entity: IntMap,
 }
 
 impl Default for SceneBoundingBoxes {
     fn default() -> Self {
         Self {
             current: macaw::BoundingBox::nothing(),
-            smoothed: macaw::BoundingBox::nothing(),
             per_entity: IntMap::default(),
+            region_of_interest_current: macaw::BoundingBox::nothing(),
+            region_of_interest_smoothed: macaw::BoundingBox::nothing(),
+            region_of_interest_per_entity: IntMap::default(),
         }
     }
 }
@@ -39,9 +50,11 @@ impl SceneBoundingBoxes {
     ) {
         re_tracing::profile_function!();
 
-        let previous = self.current;
+        let previous_region_of_interest = self.region_of_interest_current;
         self.current = macaw::BoundingBox::nothing();
         self.per_entity.clear();
+        self.region_of_interest_current = macaw::BoundingBox::nothing();
+        self.region_of_interest_per_entity.clear();
 
         for data in visualizers.iter_visualizer_data::() {
             // If we're in a 3D space, but the visualizer is distintivly 2D, don't count it towards the bounding box.
@@ -54,43 +67,62 @@ impl SceneBoundingBoxes {
                 continue;
             }
 
-            for (entity, bbox) in &data.bounding_boxes {
+            for (entity, bbox) in data.iter_bounding_boxes() {
                 self.per_entity
                     .entry(*entity)
                     .and_modify(|bbox_entry| *bbox_entry = bbox_entry.union(*bbox))
                     .or_insert(*bbox);
             }
-        }
 
-        #[expect(clippy::iter_over_hash_type)] // order-independent:
-        for bbox in self.per_entity.values() {
-            self.current = self.current.union(*bbox);
+            for (entity, region_of_interest) in data.iter_regions_of_interest() {
+                self.region_of_interest_per_entity
+                    .entry(*entity)
+                    .and_modify(|entry| *entry = entry.union(*region_of_interest))
+                    .or_insert(*region_of_interest);
+            }
         }
 
-        // Update smoothed bounding box.
-        let discontinuity = detect_boundingbox_discontinuity(self.current, previous);
-        if !self.smoothed.is_finite() || self.smoothed.is_nothing() || discontinuity {
-            // Reset the smoothed bounding box if it's not valid or we detect a discontinuity.
-            self.smoothed = self.current;
+        self.current = self
+            .per_entity
+            .values()
+            .copied()
+            .fold(macaw::BoundingBox::nothing(), macaw::BoundingBox::union);
+
+        self.region_of_interest_current = self
+            .region_of_interest_per_entity
+            .values()
+            .copied()
+            .fold(macaw::BoundingBox::nothing(), macaw::BoundingBox::union);
+
+        // Smooth the region of interest for stable camera behavior.
+        let discontinuity =
+            detect_discontinuity(self.region_of_interest_current, previous_region_of_interest);
+        if !self.region_of_interest_smoothed.is_finite()
+            || self.region_of_interest_smoothed.is_nothing()
+            || discontinuity
+        {
+            self.region_of_interest_smoothed = self.region_of_interest_current;
         } else {
             let dt = ui.input(|input| input.stable_dt.at_most(0.1));
 
-            // Smooth the bounding box by moving center & size towards the current bounding box.
             let reach_this_factor = 0.9;
             let in_this_many_secs = 0.2;
             let smoothing_factor =
                 egui::emath::exponential_smooth_factor(reach_this_factor, in_this_many_secs, dt);
 
-            let current_center = self.current.center();
-            let current_size = self.current.size();
+            let current_center = self.region_of_interest_current.center();
+            let current_size = self.region_of_interest_current.size();
 
             let new_smoothed_center = self
-                .smoothed
+                .region_of_interest_smoothed
                 .center()
                 .lerp(current_center, smoothing_factor);
-            let new_smoothed_size = self.smoothed.size().lerp(current_size, smoothing_factor);
+            let new_smoothed_size = self
+                .region_of_interest_smoothed
+                .size()
+                .lerp(current_size, smoothing_factor);
 
-            self.smoothed =
+            self.region_of_interest_smoothed =
                 macaw::BoundingBox::from_center_size(new_smoothed_center, new_smoothed_size);
 
             let current_diagonal_length = current_size.length();
@@ -105,10 +137,7 @@ impl SceneBoundingBoxes {
     }
 }
 
-fn detect_boundingbox_discontinuity(
-    current: macaw::BoundingBox,
-    previous: macaw::BoundingBox,
-) -> bool {
+fn detect_discontinuity(current: macaw::BoundingBox, previous: macaw::BoundingBox) -> bool {
     if !previous.is_finite() {
         // Previous bounding box is not finite, so we can't compare.
         return true;
diff --git a/crates/viewer/re_view_spatial/src/shared_fallbacks.rs b/crates/viewer/re_view_spatial/src/shared_fallbacks.rs
index 090e7b6a626a..b55d718d7b47 100644
--- a/crates/viewer/re_view_spatial/src/shared_fallbacks.rs
+++ b/crates/viewer/re_view_spatial/src/shared_fallbacks.rs
@@ -60,7 +60,11 @@ pub fn register_fallbacks(system_registry: &mut re_viewer_context::ViewSystemReg
                 return Default::default();
             };
 
-            let scene_size = state.bounding_boxes.smoothed.size().length();
+            let scene_size = state
+                .bounding_boxes
+                .region_of_interest_smoothed
+                .size()
+                .length();
 
             let d = if scene_size.is_finite() && scene_size > 0.0 {
                 // Works pretty well for `examples/python/open_photogrammetry_format/open_photogrammetry_format.py --no-frames`
@@ -120,7 +124,11 @@ pub fn register_fallbacks(system_registry: &mut re_viewer_context::ViewSystemReg
 
             // If there is a finite bounding box, use the scene size to determine the axis length.
             if let Ok(state) = ctx.view_state().downcast_ref::() {
-                let scene_size = state.bounding_boxes.smoothed.size().length();
+                let scene_size = state
+                    .bounding_boxes
+                    .region_of_interest_smoothed
+                    .size()
+                    .length();
 
                 if scene_size.is_finite() && scene_size > 0.0 {
                     return (scene_size * 0.05).into();
diff --git a/crates/viewer/re_view_spatial/src/ui.rs b/crates/viewer/re_view_spatial/src/ui.rs
index f1ca9dcbff7d..56bb0ab62874 100644
--- a/crates/viewer/re_view_spatial/src/ui.rs
+++ b/crates/viewer/re_view_spatial/src/ui.rs
@@ -119,10 +119,15 @@ impl SpatialViewState {
         ui.vertical(|ui| {
             ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend);
             let BoundingBox { min, max } = self.bounding_boxes.current;
-            ui.label(format!("x [{} - {}]", format_f32(min.x), format_f32(max.x),));
-            ui.label(format!("y [{} - {}]", format_f32(min.y), format_f32(max.y),));
-            if spatial_kind == SpatialViewKind::ThreeD {
-                ui.label(format!("z [{} - {}]", format_f32(min.z), format_f32(max.z),));
+
+            if self.bounding_boxes.current.is_nothing() {
+                ui.label(egui::RichText::new("empty").italics());
+            } else {
+                ui.label(format!("x [{} - {}]", format_f32(min.x), format_f32(max.x),));
+                ui.label(format!("y [{} - {}]", format_f32(min.y), format_f32(max.y),));
+                if spatial_kind == SpatialViewKind::ThreeD {
+                    ui.label(format!("z [{} - {}]", format_f32(min.z), format_f32(max.z),));
+                }
             }
         });
         ui.end_row();
@@ -143,7 +148,8 @@ impl SpatialViewState {
             )
             .clicked()
         {
-            self.bounding_boxes.smoothed = self.bounding_boxes.current;
+            self.bounding_boxes.region_of_interest_smoothed =
+                self.bounding_boxes.region_of_interest_current;
             self.state_3d.reset_eye(ctx, &eye_property);
         }
     }
diff --git a/crates/viewer/re_view_spatial/src/ui_3d.rs b/crates/viewer/re_view_spatial/src/ui_3d.rs
index 5ec5343f9397..b52e7fe754c5 100644
--- a/crates/viewer/re_view_spatial/src/ui_3d.rs
+++ b/crates/viewer/re_view_spatial/src/ui_3d.rs
@@ -209,8 +209,11 @@ impl SpatialView3D {
                 Instance::ALL.get(),
             );
 
-            // If we are showing the axes for the space, then add the space origin to the bounding box.
-            state.bounding_boxes.current.extend(glam::Vec3::ZERO);
+            // If we are showing the axes for the space, then add the space origin to the region of interest, but not the scene bounding box.
+            state
+                .bounding_boxes
+                .region_of_interest_current
+                .extend(glam::Vec3::ZERO);
         }
 
         // Create labels now since their shapes participate are added to scene.ui for picking.
@@ -369,6 +372,8 @@ impl SpatialView3D {
         // TODO(andreas): Make configurable. Could pick up default radius for this view?
         let box_line_radius = Size(*re_sdk_types::components::Radius::default().0);
 
+        // TODO(andreas): Make this an enum so the user can choose between showing
+        // the bounding box (all entities), the region of interest, or per-entity bounding boxes.
         if show_bounding_box {
             line_builder
                 .batch("scene_bbox_current")
@@ -381,8 +386,8 @@ impl SpatialView3D {
         }
         if state.state_3d.show_smoothed_bbox {
             line_builder
-                .batch("scene_bbox_smoothed")
-                .add_box_outline(&state.bounding_boxes.smoothed)
+                .batch("scene_region_of_interest_smoothed")
+                .add_box_outline(&state.bounding_boxes.region_of_interest_smoothed)
                 .map(|lines| {
                     lines
                         .radius(box_line_radius)
@@ -390,10 +395,10 @@ impl SpatialView3D {
                 });
         }
         if state.state_3d.show_per_entity_bbox {
-            let mut batch = line_builder.batch("per_entity_bboxes");
-            for bbox in state.bounding_boxes.per_entity.values() {
+            let mut batch = line_builder.batch("per_entity_regions_of_interest");
+            for region_of_interest in state.bounding_boxes.region_of_interest_per_entity.values() {
                 batch
-                    .add_box_outline(bbox)
+                    .add_box_outline(region_of_interest)
                     .map(|lines| lines.radius(box_line_radius).color(egui::Color32::YELLOW));
             }
         }
@@ -632,7 +637,7 @@ fn show_projections_from_2d_space(
                     add_picking_ray(
                         line_builder,
                         ray,
-                        &state.bounding_boxes.smoothed,
+                        &state.bounding_boxes.region_of_interest_smoothed,
                         thick_ray_length,
                         ray_color,
                     );
@@ -655,7 +660,7 @@ fn show_projections_from_2d_space(
                 add_picking_ray(
                     line_builder,
                     ray,
-                    &state.bounding_boxes.current,
+                    &state.bounding_boxes.region_of_interest_current,
                     distance,
                     ray_color,
                 );
diff --git a/crates/viewer/re_view_spatial/src/view_2d.rs b/crates/viewer/re_view_spatial/src/view_2d.rs
index fb83fc71e71d..087485fc409f 100644
--- a/crates/viewer/re_view_spatial/src/view_2d.rs
+++ b/crates/viewer/re_view_spatial/src/view_2d.rs
@@ -69,7 +69,8 @@ impl ViewClass for SpatialView2D {
                     .map(|pinhole| pinhole.resolution_rect())
                     .unwrap_or_else(|| {
                         // TODO(emilk): if there is a single image in this view, use that as the default bounds
-                        let scene_rect_smoothed = view_state.bounding_boxes.smoothed;
+                        let scene_rect_smoothed =
+                            view_state.bounding_boxes.region_of_interest_smoothed;
                         egui::Rect::from_min_max(
                             scene_rect_smoothed.min.truncate().to_array().into(),
                             scene_rect_smoothed.max.truncate().to_array().into(),
@@ -105,7 +106,7 @@ impl ViewClass for SpatialView2D {
         state.downcast_ref::().ok().map(|state| {
             let (width, height) = state.visual_bounds_2d.map_or_else(
                 || {
-                    let bbox = &state.bounding_boxes.smoothed;
+                    let bbox = &state.bounding_boxes.region_of_interest_smoothed;
                     (
                         (bbox.max.x - bbox.min.x).abs(),
                         (bbox.max.y - bbox.min.y).abs(),
diff --git a/crates/viewer/re_view_spatial/src/view_3d.rs b/crates/viewer/re_view_spatial/src/view_3d.rs
index 61e20d79ca3d..96a3e08109a2 100644
--- a/crates/viewer/re_view_spatial/src/view_3d.rs
+++ b/crates/viewer/re_view_spatial/src/view_3d.rs
@@ -135,7 +135,7 @@ impl ViewClass for SpatialView3D {
 
                 let speed = match kind {
                     Eye3DKind::FirstPerson => {
-                        let l = view_state.bounding_boxes.current.size().length() as f64;
+                        let l = view_state.bounding_boxes.region_of_interest_current.size().length() as f64;
                         if l.is_finite() {
                             (0.1 * l).max(MIN_SPEED)
                         } else {
@@ -168,7 +168,7 @@ impl ViewClass for SpatialView3D {
                     );
                     return Position3D::ZERO;
                 };
-                let center = view_state.bounding_boxes.current.center();
+                let center = view_state.bounding_boxes.region_of_interest_current.center();
 
                 if !center.is_finite() {
                     return Position3D::ZERO;
@@ -187,13 +187,13 @@ impl ViewClass for SpatialView3D {
                     );
                     return Position3D::ZERO;
                 };
-                let mut center = view_state.bounding_boxes.current.center();
+                let mut center = view_state.bounding_boxes.region_of_interest_current.center();
 
                 if !center.is_finite() {
                     center = Vec3::ZERO;
                 }
 
-                let mut radius = 1.5 * view_state.bounding_boxes.current.half_size().length();
+                let mut radius = 1.5 * view_state.bounding_boxes.region_of_interest_current.half_size().length();
                 if !radius.is_finite() || radius == 0.0 {
                     radius = 1.0;
                 }
diff --git a/crates/viewer/re_view_spatial/src/visualizers/points2d.rs b/crates/viewer/re_view_spatial/src/visualizers/points2d.rs
index e433fdb8d7a3..ae0895b1ab09 100644
--- a/crates/viewer/re_view_spatial/src/visualizers/points2d.rs
+++ b/crates/viewer/re_view_spatial/src/visualizers/points2d.rs
@@ -120,9 +120,11 @@ impl Points2DVisualizer {
                 }
             }
 
-            let obj_space_bounding_box = self.data.add_bounding_box_from_points(
+            let point_cloud_bounds = re_renderer::util::point_cloud_bounds(&positions);
+            self.data.add_bounding_box_and_region_of_interest(
                 entity_path.hash(),
-                positions.iter().copied(),
+                point_cloud_bounds.bbox,
+                point_cloud_bounds.region_of_interest,
                 world_from_obj,
             );
 
@@ -139,7 +141,7 @@ impl Points2DVisualizer {
                     entity_path,
                     visualizer_instruction: ent_context.visualizer_instruction,
                     num_instances,
-                    overall_position: obj_space_bounding_box.center().truncate(),
+                    overall_position: point_cloud_bounds.bbox.center().truncate(),
                     instance_positions: data.positions.iter().map(|p| glam::vec2(p.x(), p.y())),
                     labels: &data.labels,
                     colors: &colors,
diff --git a/crates/viewer/re_view_spatial/src/visualizers/points3d.rs b/crates/viewer/re_view_spatial/src/visualizers/points3d.rs
index d93647273acf..b37ef5c7d264 100644
--- a/crates/viewer/re_view_spatial/src/visualizers/points3d.rs
+++ b/crates/viewer/re_view_spatial/src/visualizers/points3d.rs
@@ -61,7 +61,7 @@ struct Points3DComponentData<'a> {
 /// so that it can be memoized based on `data.query_hash`.
 struct Points3DCpu {
     position_radii: Vec,
-    obj_space_bounding_box: macaw::BoundingBox,
+    point_cloud_bounds: re_renderer::util::PointCloudBounds,
     picking_ids: Vec,
     annotation_infos: ResolvedAnnotationInfos,
     keypoints: Keypoints,
@@ -96,9 +96,9 @@ impl Points3DCpu {
 
         let positions: &[glam::Vec3] = bytemuck::cast_slice(data.positions);
 
-        let obj_space_bounding_box = {
+        let point_cloud_bounds = {
             re_tracing::profile_scope_if!(100_000 < num_instances, "bounding_box");
-            re_renderer::util::bounding_box_from_points(positions.iter().copied())
+            re_renderer::util::point_cloud_bounds(positions)
         };
 
         let radii = process_radius_slice(
@@ -120,7 +120,7 @@ impl Points3DCpu {
 
         Self {
             position_radii,
-            obj_space_bounding_box,
+            point_cloud_bounds,
             picking_ids,
             annotation_infos,
             keypoints,
@@ -131,7 +131,7 @@ impl Points3DCpu {
     fn heap_size_bytes(&self) -> u64 {
         let Self {
             position_radii,
-            obj_space_bounding_box: _,
+            point_cloud_bounds: _,
             picking_ids,
             annotation_infos,
             keypoints,
@@ -314,9 +314,10 @@ impl Points3DVisualizer {
                     }
                 }
 
-                self.data.add_bounding_box(
+                self.data.add_bounding_box_and_region_of_interest(
                     entity_path.hash(),
-                    cpu.obj_space_bounding_box,
+                    cpu.point_cloud_bounds.bbox,
+                    cpu.point_cloud_bounds.region_of_interest,
                     world_from_obj,
                 );
 
@@ -333,7 +334,7 @@ impl Points3DVisualizer {
                         entity_path,
                         visualizer_instruction: ent_context.visualizer_instruction,
                         num_instances,
-                        overall_position: cpu.obj_space_bounding_box.center(),
+                        overall_position: cpu.point_cloud_bounds.bbox.center(),
                         instance_positions: cpu.position_radii.iter().map(|pr| pr.pos),
                         labels: &data.labels,
                         colors: &cpu.colors,
diff --git a/crates/viewer/re_view_spatial/src/visualizers/utilities/proc_mesh_vis.rs b/crates/viewer/re_view_spatial/src/visualizers/utilities/proc_mesh_vis.rs
index e3f36bd2ffae..ebe80380248b 100644
--- a/crates/viewer/re_view_spatial/src/visualizers/utilities/proc_mesh_vis.rs
+++ b/crates/viewer/re_view_spatial/src/visualizers/utilities/proc_mesh_vis.rs
@@ -319,9 +319,11 @@ impl<'ctx> ProcMeshDrawableBuilder<'ctx> {
             }
         }
 
-        self.data
-            .bounding_boxes
-            .push((entity_path.hash(), world_space_bounding_box));
+        self.data.add_bounding_box(
+            entity_path.hash(),
+            world_space_bounding_box,
+            glam::Affine3A::IDENTITY,
+        );
 
         self.data.ui_labels.extend(process_labels_3d(
             LabeledBatch {
diff --git a/crates/viewer/re_view_spatial/src/visualizers/utilities/spatial_view_visualizer.rs b/crates/viewer/re_view_spatial/src/visualizers/utilities/spatial_view_visualizer.rs
index f0d1205f8be7..066dbeff0ef8 100644
--- a/crates/viewer/re_view_spatial/src/visualizers/utilities/spatial_view_visualizer.rs
+++ b/crates/viewer/re_view_spatial/src/visualizers/utilities/spatial_view_visualizer.rs
@@ -18,7 +18,14 @@ pub struct SpatialViewVisualizerData {
     pub ui_labels: Vec,
 
     /// Bounding boxes of all visualizations that the visualizer showed.
-    pub bounding_boxes: Vec<(EntityPathHash, macaw::BoundingBox)>,
+    bounding_boxes: Vec<(EntityPathHash, macaw::BoundingBox)>,
+
+    /// Regions of interest for all visualizations, excluding spatial outliers.
+    ///
+    /// Used for camera framing and other heuristics. For most visualizers this is
+    /// identical to the bounding box. Point cloud visualizers may provide a tighter
+    /// region that excludes outlier points.
+    regions_of_interest: Vec<(EntityPathHash, macaw::BoundingBox)>,
 
     /// Textured rectangles that the visualizer produced which can be interacted with.
     pub pickable_rects: Vec,
@@ -33,6 +40,7 @@ impl SpatialViewVisualizerData {
             loading_indicators: Default::default(),
             ui_labels: Default::default(),
             bounding_boxes: Default::default(),
+            regions_of_interest: Default::default(),
             pickable_rects: Default::default(),
             preferred_view_kind,
         }
@@ -47,29 +55,38 @@ impl SpatialViewVisualizerData {
         self.pickable_rects.push(pickable_rect);
     }
 
+    /// Adds a bounding box and region of interest for an entity.
+    ///
+    /// For most visualizers these are the same. Use [`Self::add_bounding_box_and_region_of_interest`]
+    /// when they differ (e.g. for point clouds with outlier rejection).
     pub fn add_bounding_box(
         &mut self,
         entity: EntityPathHash,
         bbox: macaw::BoundingBox,
         world_from_obj: glam::Affine3A,
     ) {
-        self.bounding_boxes
-            .push((entity, bbox.transform_affine3(&world_from_obj)));
+        let transformed = bbox.transform_affine3(&world_from_obj);
+        self.bounding_boxes.push((entity, transformed));
+        self.regions_of_interest.push((entity, transformed));
     }
 
-    /// Computes a bounding box from points, ignoring NaN and infinity values,
-    /// then adds it via [`Self::add_bounding_box`].
+    /// Adds separate bounding box and region of interest for an entity.
     ///
-    /// Returns the computed object-space bounding box.
-    pub fn add_bounding_box_from_points(
+    /// The bounding box is the exact extent; the region of interest excludes outliers
+    /// and is used for camera framing and other heuristics.
+    pub fn add_bounding_box_and_region_of_interest(
         &mut self,
         entity: EntityPathHash,
-        points: impl Iterator,
+        bbox: macaw::BoundingBox,
+        region_of_interest: macaw::BoundingBox,
         world_from_obj: glam::Affine3A,
-    ) -> macaw::BoundingBox {
-        let bbox = re_renderer::util::bounding_box_from_points(points);
-        self.add_bounding_box(entity, bbox, world_from_obj);
-        bbox
+    ) {
+        self.bounding_boxes
+            .push((entity, bbox.transform_affine3(&world_from_obj)));
+        self.regions_of_interest.push((
+            entity,
+            region_of_interest.transform_affine3(&world_from_obj),
+        ));
     }
 
     pub fn add_pickable_rect_to_bounding_box(
@@ -82,13 +99,27 @@ impl SpatialViewVisualizerData {
         // the bounds which in turn influence the size of the image plane.
         // See: https://github.com/rerun-io/rerun/issues/3728
         if class_identifier == SpatialView2D::identifier() {
-            self.bounding_boxes.push((
+            let entry = (
                 pickable_rect.ent_path.hash(),
                 pickable_rect.textured_rect.bounding_box(),
-            ));
+            );
+            self.bounding_boxes.push(entry);
+            self.regions_of_interest.push(entry);
         }
     }
 
+    pub fn iter_bounding_boxes(
+        &self,
+    ) -> impl ExactSizeIterator {
+        self.bounding_boxes.iter()
+    }
+
+    pub fn iter_regions_of_interest(
+        &self,
+    ) -> impl ExactSizeIterator {
+        self.regions_of_interest.iter()
+    }
+
     pub fn as_any(&self) -> &dyn std::any::Any {
         self
     }
diff --git a/crates/viewer/re_view_spatial/tests/snapshots/draw_order.png b/crates/viewer/re_view_spatial/tests/snapshots/draw_order.png
index d306dec4e1a7..a22c56355ae6 100644
--- a/crates/viewer/re_view_spatial/tests/snapshots/draw_order.png
+++ b/crates/viewer/re_view_spatial/tests/snapshots/draw_order.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:8b1e2fcaed3bf6765e74809faed6bff8fc86d61d9db43e06453cc51e4e3190eb
-size 25561
+oid sha256:e38dd9bfc8e876aa07e1afd0c5b416f8308e23cd244dde946ddad84dc196f9e7
+size 25080
diff --git a/crates/viewer/re_view_spatial/tests/snapshots/static_overwrite_color_override.png b/crates/viewer/re_view_spatial/tests/snapshots/static_overwrite_color_override.png
index e81053e7681b..fceddde8f366 100644
--- a/crates/viewer/re_view_spatial/tests/snapshots/static_overwrite_color_override.png
+++ b/crates/viewer/re_view_spatial/tests/snapshots/static_overwrite_color_override.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:8debe77ab83250ad6149ada368c7323fc74e3c54dae5d5f660f2c6e417c34395
-size 46906
+oid sha256:83738bd31f3e7583a8ee9e52cbf85ce8e9ff2e68337abec2fb3d191157fe1c3c
+size 45499
diff --git a/crates/viewer/re_view_spatial/tests/snapshots/static_overwrite_original.png b/crates/viewer/re_view_spatial/tests/snapshots/static_overwrite_original.png
index b0a1efe3a325..6ba0b457b3ec 100644
--- a/crates/viewer/re_view_spatial/tests/snapshots/static_overwrite_original.png
+++ b/crates/viewer/re_view_spatial/tests/snapshots/static_overwrite_original.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:47520a4ca17171669dc5416c7d803c822df8b4f94527aaa56beb3fb85db4bd90
-size 45467
+oid sha256:52571a889ebbef79f0a39449cd0793ab47b6f34ec1223e3a4a68720d17cdb51d
+size 44002
diff --git a/crates/viewer/re_view_spatial/tests/snapshots/static_overwrite_radius_default.png b/crates/viewer/re_view_spatial/tests/snapshots/static_overwrite_radius_default.png
index b1325540930d..74b7e96a1f10 100644
--- a/crates/viewer/re_view_spatial/tests/snapshots/static_overwrite_radius_default.png
+++ b/crates/viewer/re_view_spatial/tests/snapshots/static_overwrite_radius_default.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:1881baf2c3fe6f6f0521dce1c112132d94a3df2e942cacdb676be48540df9ecb
-size 47592
+oid sha256:bda5e35e4f502b2cbd1cf66dc765aed6e643e0bbe2c3d3913bd991aec4f76cbf
+size 46139
diff --git a/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Dark/2D.png b/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Dark/2D.png
index 78c04891bed2..2f0fc4a7218c 100644
--- a/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Dark/2D.png
+++ b/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Dark/2D.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:9bdbe857608f1ea693acf430cefdae2dbe0c9f1614b008cf9bb05f1c76062db2
-size 23373
+oid sha256:928f3e49c50fb4a7e32c0e841e861c97db8f9c4585906c4947bf6c9fa601a637
+size 22460
diff --git a/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Dark/3D.png b/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Dark/3D.png
index 4e2995ecd2e3..5dbe7c2c5f17 100644
--- a/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Dark/3D.png
+++ b/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Dark/3D.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:d8e7391c4afbfcbbd7da1574f92017ec26b1e903682987778ec67a0e0aaa5097
-size 73669
+oid sha256:21723a78bfd36dba2efc458d53d63da03f324f9a8d9e6726656ca451cfeaba88
+size 71787
diff --git a/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Light/2D.png b/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Light/2D.png
index ea6bcf94b18c..e12f479af869 100644
--- a/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Light/2D.png
+++ b/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Light/2D.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:dee31017d73d5782247989451629a3fe8ed0b3f7bbf985aec7eba09eeadd8d69
-size 23296
+oid sha256:b6524f9db3604f3bd01cbcea9fb566f9db98f4ad0d788535053e0a921f0e6d09
+size 22279
diff --git a/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Light/3D.png b/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Light/3D.png
index ff8f3c283bd2..6f2666372240 100644
--- a/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Light/3D.png
+++ b/crates/viewer/re_viewer/tests/snapshots/all_view_selection_uis/Light/3D.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:992c9dcdbfe240e0ef4efd1059b8d5ebdf37e47f9b1009dbccda0e14fac6eb36
-size 73721
+oid sha256:8cee5ca595578ebf333d6500d8ee44ec54dee3f6c3ffaa0730b62f27bc9f8a15
+size 71676
diff --git a/tests/rust/re_integration_test/tests/snapshots/heuristics_mixed_all_root.png b/tests/rust/re_integration_test/tests/snapshots/heuristics_mixed_all_root.png
index 206542955272..e4ae9b2a6e84 100644
--- a/tests/rust/re_integration_test/tests/snapshots/heuristics_mixed_all_root.png
+++ b/tests/rust/re_integration_test/tests/snapshots/heuristics_mixed_all_root.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:991b118a77a1c22edb02463a4368549f52d3b3c093821a6f51f81d9454802c97
-size 128604
+oid sha256:1c226b86757d8533a55fa16de530db257791ae237869d905d21a310baeccc903
+size 126281
diff --git a/tests/rust/re_integration_test/tests/snapshots/origin_camera_2d.png b/tests/rust/re_integration_test/tests/snapshots/origin_camera_2d.png
index 59d8f042321e..5f85fbdf1bb8 100644
--- a/tests/rust/re_integration_test/tests/snapshots/origin_camera_2d.png
+++ b/tests/rust/re_integration_test/tests/snapshots/origin_camera_2d.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:e79920bfe5ea660702accb966aae557e094fd0c5929e85c5a81a236fd42493c2
-size 148663
+oid sha256:adc3b99fa718133ed5ed8862d9873964011fb2da4a71e9ff9f13ffd4c20a0671
+size 148858
diff --git a/tests/rust/re_integration_test/tests/snapshots/origin_keypoint_3d.png b/tests/rust/re_integration_test/tests/snapshots/origin_keypoint_3d.png
index 742532f459fd..03c458630b09 100644
--- a/tests/rust/re_integration_test/tests/snapshots/origin_keypoint_3d.png
+++ b/tests/rust/re_integration_test/tests/snapshots/origin_keypoint_3d.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:adbb18ed071850930e0ffd565cf09c3acb73e9187d1a2dfa53f4c9a56123adec
-size 189225
+oid sha256:bbfa5d5ddc850c27d64db82d78ae6682c58033214a39019f3859feea74877f1d
+size 192028
diff --git a/tests/rust/re_integration_test/tests/snapshots/origin_root_2d.png b/tests/rust/re_integration_test/tests/snapshots/origin_root_2d.png
index d582f6296ea7..dbf3a70ef05e 100644
--- a/tests/rust/re_integration_test/tests/snapshots/origin_root_2d.png
+++ b/tests/rust/re_integration_test/tests/snapshots/origin_root_2d.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:f93e81a82e9b74f9a4dd0bdb669d64325a7d886ee53656fa3f1ff086cdf0b6a5
-size 147064
+oid sha256:99758b3b0dc4e4e9e40a4120acefc728ea8bb07e6ed92e237e1af95bc9d23aa8
+size 147311
diff --git a/tests/rust/re_integration_test/tests/snapshots/origin_world_2d.png b/tests/rust/re_integration_test/tests/snapshots/origin_world_2d.png
index 4fd7c24652c5..a4caffb0f675 100644
--- a/tests/rust/re_integration_test/tests/snapshots/origin_world_2d.png
+++ b/tests/rust/re_integration_test/tests/snapshots/origin_world_2d.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:789a177de802439b9ff48896b682cc63db5b6bf86243c2b80676063e910a0660
-size 147634
+oid sha256:f821c5bec1842f498eca79fe1fcc850b9b5c7df0e8ba4cdc44c132906c0b764d
+size 147890
diff --git a/tests/rust/re_integration_test/tests/snapshots/source_component_1.png b/tests/rust/re_integration_test/tests/snapshots/source_component_1.png
index afb8b1c37341..0f852c96bb51 100644
--- a/tests/rust/re_integration_test/tests/snapshots/source_component_1.png
+++ b/tests/rust/re_integration_test/tests/snapshots/source_component_1.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:7767f98c34f6bce5f16dd7da369ba3367d15e17e6c10a9110f6bc60ca4a5094a
-size 186418
+oid sha256:e906f3424ad5306677e1d51139fafad8de161430ce5e6f1e4c34a507ac2020fc
+size 179683
diff --git a/tests/rust/re_integration_test/tests/snapshots/source_component_2.png b/tests/rust/re_integration_test/tests/snapshots/source_component_2.png
index a2b3f235e536..e66e4e7ddd00 100644
--- a/tests/rust/re_integration_test/tests/snapshots/source_component_2.png
+++ b/tests/rust/re_integration_test/tests/snapshots/source_component_2.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:02ba178c500cab7aa322a8960dbc5a7510edad0f9cd401267bfe7e067742c6fa
-size 190684
+oid sha256:8deff520b8d386b2b9e50e8474b7aa72743cb9bb446a94afd4aaa7f6b219c9da
+size 183337
diff --git a/tests/rust/re_integration_test/tests/snapshots/source_component_3.png b/tests/rust/re_integration_test/tests/snapshots/source_component_3.png
index e208597f19b3..4a8829ac7323 100644
--- a/tests/rust/re_integration_test/tests/snapshots/source_component_3.png
+++ b/tests/rust/re_integration_test/tests/snapshots/source_component_3.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:088b223edcbeb67176dafe18a9ff2cd439cf291868c2e463bbb542214ca9506f
-size 210131
+oid sha256:c1a49b23744277f02dff942d128ae38b5854cfdd1f0323adf7e0629106d2f5d2
+size 196279
diff --git a/tests/rust/re_integration_test/tests/snapshots/source_component_4.png b/tests/rust/re_integration_test/tests/snapshots/source_component_4.png
index 90ad3f920e11..a3c32672d902 100644
--- a/tests/rust/re_integration_test/tests/snapshots/source_component_4.png
+++ b/tests/rust/re_integration_test/tests/snapshots/source_component_4.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:507b1ccae905499f6018403fad8dc84703eac0ee182a3cae053e357b52d677b5
-size 208685
+oid sha256:02afd23fb69ae504b5fb230f35bfefa97c7961477ae63fb28aadf86df7db0748
+size 194963
diff --git a/tests/rust/re_integration_test/tests/snapshots/source_component_5.png b/tests/rust/re_integration_test/tests/snapshots/source_component_5.png
index 896233436dbd..82650ebc9f47 100644
--- a/tests/rust/re_integration_test/tests/snapshots/source_component_5.png
+++ b/tests/rust/re_integration_test/tests/snapshots/source_component_5.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:fac513d19b16696f2a872002ff870c86cfb71bd7495ee49e1ca7156319bc1378
-size 216542
+oid sha256:77bab0ffd1eaf4e8b196141190d5f23a15df0a5af78673941863b868381dc439
+size 202188
diff --git a/tests/rust/re_integration_test/tests/snapshots/source_component_6.png b/tests/rust/re_integration_test/tests/snapshots/source_component_6.png
index cd5863560bef..3092869dd789 100644
--- a/tests/rust/re_integration_test/tests/snapshots/source_component_6.png
+++ b/tests/rust/re_integration_test/tests/snapshots/source_component_6.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:bded746183500a46a975e19eef9892bd5e07223621e95c8b8deb8bee9bc959fd
-size 207127
+oid sha256:4248c0549e3258c2dbc1c1cc0e9479526e0daf9c0dd33f377ea4720548adf642
+size 193660
diff --git a/tests/rust/re_integration_test/tests/snapshots/source_component_7.png b/tests/rust/re_integration_test/tests/snapshots/source_component_7.png
index b58429a773a1..c1aeb5b639a0 100644
--- a/tests/rust/re_integration_test/tests/snapshots/source_component_7.png
+++ b/tests/rust/re_integration_test/tests/snapshots/source_component_7.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:e241af9fa0ea82f1b3a4c6e4c251eac98b857dba59d4d05934fa9bb5a3b2850f
-size 215639
+oid sha256:74a30a5fc066963dc25069a689d51632ff6f93d5893b79b41672f0fcfadef7bd
+size 202270
diff --git a/tests/rust/re_integration_test/tests/snapshots/source_component_8.png b/tests/rust/re_integration_test/tests/snapshots/source_component_8.png
index 14417c8b9113..774f02c5d599 100644
--- a/tests/rust/re_integration_test/tests/snapshots/source_component_8.png
+++ b/tests/rust/re_integration_test/tests/snapshots/source_component_8.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:a245c3b80e4932ab06cb3e367ea1a444ac61ba98575dd4544acb0dc4168253f6
-size 225798
+oid sha256:970bc2cafb47bbf12e549bb0dd2e56f86b9016d59d2f60fab7ae5d39de53e2bc
+size 211841

From bde68700b3e0e58061b8a6332f23c64a703a1e0b Mon Sep 17 00:00:00 2001
From: Pablo Vela 
Date: Tue, 17 Mar 2026 05:17:53 -0500
Subject: [PATCH 157/513] =?UTF-8?q?Add=20`compress()`=20and=20`as=5Fpil=5F?=
 =?UTF-8?q?image()`=20to=20`DepthImage`=20with=20PNG=20compre=E2=80=A6?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

### What

Add png encoding to DepthImage to compress and create EncodedDepthImage

### Testing

Wrote tests for
1. Compression actually happens
2. makes sure fields are preserved
3. make sure compression level does something

### Compatibility

No compatibility breakage, only adds the ability to compress.

Source-Ref: 0ac2a384d10f9f7b606dc78ca0798192505fd0c6
Co-authored-by: Pablo Vela 
Co-authored-by: Claude Opus 4.6 (1M context) 
---
 .../rerun/archetypes/depth_image_ext.py       |  85 ++++++++++++
 rerun_py/tests/unit/test_depth_image.py       | 121 ++++++++++++++++++
 2 files changed, 206 insertions(+)

diff --git a/rerun_py/rerun_sdk/rerun/archetypes/depth_image_ext.py b/rerun_py/rerun_sdk/rerun/archetypes/depth_image_ext.py
index af53ea6dcb96..f4f8f89d6aeb 100644
--- a/rerun_py/rerun_sdk/rerun/archetypes/depth_image_ext.py
+++ b/rerun_py/rerun_sdk/rerun/archetypes/depth_image_ext.py
@@ -1,16 +1,21 @@
 from __future__ import annotations
 
+from io import BytesIO
 from typing import TYPE_CHECKING, Any
 
 import numpy as np
 import numpy.typing as npt
+from PIL import Image as PILImage
 
 from ..components import ColormapLike, ImageFormat
 from ..datatypes import ChannelDatatype, Float32Like
+from ..error_utils import catch_and_log_exceptions
 
 if TYPE_CHECKING:
     from rerun.datatypes.range1d import Range1DLike
 
+    from . import DepthImage, EncodedDepthImage
+
     ImageLike = (
         npt.NDArray[np.float16]
         | npt.NDArray[np.float32]
@@ -27,6 +32,11 @@
     )
 
 
+def _as_arrow_or_none(batch: Any) -> Any:
+    """Extract arrow array from a batch, or return None."""
+    return batch.as_arrow_array() if batch is not None else None
+
+
 def _to_numpy(tensor: ImageLike) -> npt.NDArray[Any]:
     # isinstance is 4x faster than catching AttributeError
     if isinstance(tensor, np.ndarray):
@@ -131,3 +141,78 @@ def __init__(
             point_fill_ratio=point_fill_ratio,
             draw_order=draw_order,
         )
+
+    def image_format(self: Any) -> ImageFormat:
+        """Returns the image format of this depth image."""
+        image_format_arrow = self.format.as_arrow_array()[0].as_py()
+        return ImageFormat(
+            width=image_format_arrow["width"],
+            height=image_format_arrow["height"],
+            channel_datatype=image_format_arrow["channel_datatype"],
+        )
+
+    def as_pil_image(self: Any) -> PILImage.Image:
+        """Convert the depth image to a PIL Image."""
+        image_format = self.image_format()
+
+        np_dtype = image_format.channel_datatype.to_np_dtype()
+        buf = self.buffer.as_arrow_array().values.to_numpy().view(np_dtype)
+        image_array = buf.reshape(image_format.height, image_format.width)
+
+        return PILImage.fromarray(image_array)
+
+    def compress(self: Any, compress_level: int = 6) -> EncodedDepthImage | DepthImage:
+        """
+        Compress the given depth image as a PNG.
+
+        PNG compression is lossless. Only U8 and U16 depth images are supported,
+        as these are the only single-channel types the Rerun Viewer can decode
+        from encoded depth PNGs.
+
+        Note that compressing to PNG costs a bit of CPU time,
+        both when logging and later when viewing them.
+
+        Parameters
+        ----------
+        compress_level:
+            PNG compression level, 0-9. Higher means smaller files but slower.
+            0 = no compression, 1 = fastest, 9 = smallest. Default is 6.
+
+        """
+
+        from ..archetypes import EncodedDepthImage
+
+        with catch_and_log_exceptions(context="DepthImage compression"):
+            if self.format is None:
+                raise ValueError("Cannot PNG compress a depth image without a known image_format")
+
+            if self.buffer is None:
+                raise ValueError("Cannot PNG compress a depth image without data")
+
+            image_format = self.image_format()
+
+            if image_format.channel_datatype not in (ChannelDatatype.U8, ChannelDatatype.U16):
+                raise ValueError(
+                    f"Cannot PNG compress a depth image of datatype {image_format.channel_datatype}. "
+                    "Only U8 and U16 are supported.",
+                )
+
+            pil_image = self.as_pil_image()
+
+            output = BytesIO()
+            pil_image.save(output, format="PNG", compress_level=compress_level)
+            png_bytes = output.getvalue()
+            output.close()
+
+            return EncodedDepthImage(
+                blob=png_bytes,
+                media_type="image/png",
+                meter=_as_arrow_or_none(self.meter),
+                colormap=_as_arrow_or_none(self.colormap),
+                depth_range=_as_arrow_or_none(self.depth_range),
+                point_fill_ratio=_as_arrow_or_none(self.point_fill_ratio),
+                draw_order=_as_arrow_or_none(self.draw_order),
+            )
+
+        # On failure to compress, return the raw depth image
+        return self  # type: ignore[no-any-return]
diff --git a/rerun_py/tests/unit/test_depth_image.py b/rerun_py/tests/unit/test_depth_image.py
index 45a1c20c22bb..a144f36fbf9a 100644
--- a/rerun_py/tests/unit/test_depth_image.py
+++ b/rerun_py/tests/unit/test_depth_image.py
@@ -9,6 +9,7 @@
 import torch
 from rerun.components import DepthMeter, ImageFormat
 from rerun.datatypes import ChannelDatatype, Float32Like
+from rerun.error_utils import RerunWarning
 
 rng = np.random.default_rng(12345)
 RANDOM_IMAGE_SOURCE = rng.uniform(0.0, 1.0, (10, 20))
@@ -83,3 +84,123 @@ def test_depth_image_shapes() -> None:
     for img in BAD_IMAGE_INPUTS:
         with pytest.raises(ValueError):
             rr.DepthImage(img)
+
+
+def _compressed_blob_size(encoded_depth: Any) -> int:
+    """Extract the byte size of the PNG blob from an EncodedDepthImage."""
+    return len(encoded_depth.blob.as_arrow_array()[0].as_py())
+
+
+def test_depth_image_compress() -> None:
+    rr.set_strict_mode(False)
+
+    # U16 supported (most common depth format)
+    depth_data = np.asarray(rng.uniform(0, 65535, (10, 20)), dtype=np.uint16)
+    compressed = rr.DepthImage(depth_data, meter=1000).compress()
+    assert type(compressed) is rr.EncodedDepthImage
+
+    # U8 supported
+    depth_data = np.asarray(rng.uniform(0, 255, (10, 20)), dtype=np.uint8)
+    compressed = rr.DepthImage(depth_data).compress()
+    assert type(compressed) is rr.EncodedDepthImage
+
+    # F32 not supported
+    with pytest.warns(RerunWarning) as warnings:
+        depth_data = np.asarray(rng.uniform(0, 1, (10, 20)), dtype=np.float32)
+        compressed = rr.DepthImage(depth_data).compress()
+
+        assert len(warnings) == 1
+        assert "Cannot PNG compress a depth image of datatype" in str(warnings[0])
+        assert type(compressed) is rr.DepthImage
+
+    # U32 not supported
+    with pytest.warns(RerunWarning) as warnings:
+        depth_data = np.asarray(rng.uniform(0, 65535, (10, 20)), dtype=np.uint32)
+        compressed = rr.DepthImage(depth_data).compress()
+
+        assert len(warnings) == 1
+        assert "Cannot PNG compress a depth image of datatype" in str(warnings[0])
+        assert type(compressed) is rr.DepthImage
+
+
+def test_depth_image_compress_reduces_size() -> None:
+    """Verify that PNG compression actually reduces data size for realistic depth images."""
+    rr.set_strict_mode(True)
+
+    # Smooth gradient (simulates a flat wall receding) — highly compressible
+    rows, cols = 480, 640
+    gradient_u16 = np.tile(np.linspace(500, 10000, cols, dtype=np.uint16), (rows, 1))
+    raw_size = gradient_u16.nbytes
+    compressed = rr.DepthImage(gradient_u16, meter=1000).compress()
+    assert type(compressed) is rr.EncodedDepthImage
+    compressed_size = _compressed_blob_size(compressed)
+    assert compressed_size < raw_size, f"PNG should be smaller than raw for a gradient: {compressed_size} >= {raw_size}"
+
+    # Constant depth (e.g. flat floor) — maximally compressible
+    constant_u16 = np.full((rows, cols), 3000, dtype=np.uint16)
+    raw_size = constant_u16.nbytes
+    compressed = rr.DepthImage(constant_u16).compress()
+    compressed_size = _compressed_blob_size(compressed)
+    assert compressed_size < raw_size, (
+        f"PNG should be smaller than raw for constant data: {compressed_size} >= {raw_size}"
+    )
+    # Constant data should compress very aggressively (>90% reduction)
+    assert compressed_size < raw_size * 0.1, (
+        f"Constant image should compress to <10% of raw: {compressed_size} vs {raw_size}"
+    )
+
+    # U8 gradient
+    gradient_u8 = np.tile(np.linspace(0, 255, cols, dtype=np.uint8), (rows, 1))
+    raw_size = gradient_u8.nbytes
+    compressed = rr.DepthImage(gradient_u8).compress()
+    compressed_size = _compressed_blob_size(compressed)
+    assert compressed_size < raw_size, (
+        f"PNG should be smaller than raw for U8 gradient: {compressed_size} >= {raw_size}"
+    )
+
+    # Stepped depth (simulates discrete depth planes) — should compress well
+    stepped_u16 = np.zeros((rows, cols), dtype=np.uint16)
+    for i in range(4):
+        stepped_u16[i * (rows // 4) : (i + 1) * (rows // 4), :] = 1000 * (i + 1)
+    raw_size = stepped_u16.nbytes
+    compressed = rr.DepthImage(stepped_u16).compress()
+    compressed_size = _compressed_blob_size(compressed)
+    assert compressed_size < raw_size, (
+        f"PNG should be smaller than raw for stepped data: {compressed_size} >= {raw_size}"
+    )
+
+
+def test_depth_image_compress_level() -> None:
+    """Verify that compress_level parameter affects output size."""
+    rr.set_strict_mode(True)
+
+    rows, cols = 480, 640
+    gradient_u16 = np.tile(np.linspace(500, 10000, cols, dtype=np.uint16), (rows, 1))
+
+    size_level_0 = _compressed_blob_size(rr.DepthImage(gradient_u16).compress(compress_level=0))
+    size_level_9 = _compressed_blob_size(rr.DepthImage(gradient_u16).compress(compress_level=9))
+
+    assert size_level_9 < size_level_0, (
+        f"Level 9 should produce smaller output than level 0: {size_level_9} >= {size_level_0}"
+    )
+
+
+def test_depth_image_compress_preserves_fields() -> None:
+    rr.set_strict_mode(True)
+
+    depth_data = np.asarray(rng.uniform(0, 65535, (10, 20)), dtype=np.uint16)
+    original = rr.DepthImage(
+        depth_data,
+        meter=1000,
+        depth_range=[100.0, 60000.0],
+        point_fill_ratio=0.5,
+        draw_order=1.0,
+    )
+    compressed = original.compress()
+
+    assert type(compressed) is rr.EncodedDepthImage
+    assert compressed.meter is not None
+    assert compressed.depth_range is not None
+    assert compressed.point_fill_ratio is not None
+    assert compressed.draw_order is not None
+    assert compressed.media_type is not None

From 9355dd8dc1c40926b9ae55216952b62df6490284 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jochen=20G=C3=B6rtler?= 
Date: Tue, 17 Mar 2026 12:12:52 +0100
Subject: [PATCH 158/513] Bump `lz4_flex` to prevent web viewer crashes

### Related

* Closes RR-4067.
* https://github.com/PSeitz/lz4_flex/pull/205

### What

This bumps `lz4_flex` to `0.13.0` which includes the PR linked above.

### Testing

1. Open web viewer
2. Open DICOM recording
3. Menu -> Save recording...
4. Should not crash

Source-Ref: b09682616fc15c6299351d3e31dc2d9a0b3c99ac
---
 Cargo.lock | 13 +++++++++++--
 Cargo.toml |  2 +-
 deny.toml  |  9 +++++----
 3 files changed, 17 insertions(+), 7 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index 53c5a2758f05..b82588fee96a 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -6229,6 +6229,15 @@ dependencies = [
  "twox-hash",
 ]
 
+[[package]]
+name = "lz4_flex"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "db9a0d582c2874f68138a16ce1867e0ffde6c0bb0a0df85e1f36d04146db488a"
+dependencies = [
+ "twox-hash",
+]
+
 [[package]]
 name = "macaw"
 version = "0.30.0"
@@ -9207,7 +9216,7 @@ dependencies = [
  "insta",
  "itertools 0.14.0",
  "js-sys",
- "lz4_flex 0.12.0",
+ "lz4_flex 0.13.0",
  "mimalloc",
  "parking_lot",
  "re_arrow_util",
@@ -9394,7 +9403,7 @@ dependencies = [
  "arrow",
  "http",
  "jiff",
- "lz4_flex 0.12.0",
+ "lz4_flex 0.13.0",
  "pin-project-lite",
  "prost",
  "prost-types",
diff --git a/Cargo.toml b/Cargo.toml
index 03a75afc868f..c2a176633767 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -290,7 +290,7 @@ libc = "0.2.182"
 linked-hash-map = { version = "0.5.6", default-features = false }
 log = "0.4.29"
 log-once = "0.4.1"
-lz4_flex = "0.12"
+lz4_flex = "0.13"
 macaw = "0.30.0"
 mcap = "0.24.0"
 memmap2 = "0.9.10"
diff --git a/deny.toml b/deny.toml
index ea826c437058..33b67987977a 100644
--- a/deny.toml
+++ b/deny.toml
@@ -31,11 +31,12 @@ all-features = true
 [advisories]
 version = 2
 ignore = [
-  "RUSTSEC-2024-0436", # https://rustsec.org/advisories/RUSTSEC-2024-0436 - paste is unmaintained - https://github.com/dtolnay/paste
-  "RUSTSEC-2024-0014", # https://rustsec.org/advisories/RUSTSEC-2024-0014 - generational-arena is unmaintained
-  "RUSTSEC-2025-0141", # https://rustsec.org/advisories/RUSTSEC-2025-0141 - bincode is unmaintained - https://git.sr.ht/~stygianentity/bincode/tree/v3.0/item/README.md
+  { id = "RUSTSEC-2024-0436", reason = "paste is unmaintained — https://github.com/dtolnay/paste" },
+  { id = "RUSTSEC-2024-0014", reason = "generational-arena is unmaintained" },
+  { id = "RUSTSEC-2025-0141", reason = "bincode is unmaintained — https://git.sr.ht/~stygianentity/bincode/tree/v3.0/item/README.md" },
   # TODO(quickwit-oss/tantivy#2796): Remove when changes trickle down to `lance`.
-  "RUSTSEC-2026-0002", # https://rustsec.org/advisories/RUSTSEC-2026-0002 - lance uses an old version of `lru`
+  { id = "RUSTSEC-2026-0002", reason = "lance uses an old version of `lru`" },
+  { id = "RUSTSEC-2026-0041", reason = "waiting for dependencies (puffin, arrow-ipc) to upgrade" },
 ]
 
 

From 822ac413a502a2e34e9af0462e242660eb3b0d33 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jochen=20G=C3=B6rtler?= 
Date: Tue, 17 Mar 2026 14:10:45 +0100
Subject: [PATCH 159/513] Add `ChunkStoreDiff::SchemaAddition` and use it for
 heuristics

### Related

* Closes RR-4028.

### What

Our heuristics and recommondations are purely additive, i.e. once
something has been recommended once it will only be recommended. An
additional assumption is that we never look at the actual values of
chunks to make decisions, but rather infer everything from the
per-column information. Before this PR, we were recomputed heuristics
for each chunk that would arrive. This was particularly expensive for
chunks with _any scalar_ components.

This PR introduces a new `ChunkStoreDiff::SchemaColumnAddition` event to
chunk stores, that gets emitted iff a new column is added to the chunk
store's schema. This drastically cuts down on how often we need to
recompute heuristics and significantly speeds up ingestion.

Source-Ref: e89fd4af59885940dd14edc7fca55e1b305ac1c3
---
 crates/store/re_chunk_store/src/events.rs     | 130 +++++--
 crates/store/re_chunk_store/src/lib.rs        |   2 +-
 crates/store/re_chunk_store/src/lineage.rs    |  57 +++-
 crates/store/re_chunk_store/src/store.rs      |   9 +-
 .../store/re_chunk_store/src/store_schema.rs  | 228 ++++++++-----
 crates/store/re_chunk_store/src/writes.rs     | 298 ++++++++++++++--
 .../src/data_meta_per_timeline.rs             |  15 +-
 crates/store/re_entity_db/src/entity_db.rs    |  11 +-
 .../re_entity_db/src/rrd_manifest_index.rs    |   2 +-
 crates/store/re_query/src/cache.rs            |   1 +
 crates/store/re_query/src/latest_at.rs        |   2 +-
 .../src/transform_resolution_cache/cache.rs   |   2 +
 ...ecursive_chunks_per_timeline_subscriber.rs |   2 +-
 crates/viewer/re_viewer/src/app.rs            |   4 +-
 .../src/cache/video_stream_cache.rs           |   2 +-
 .../src/view/visualizer_entity_subscriber.rs  | 323 ++++++++----------
 16 files changed, 734 insertions(+), 354 deletions(-)

diff --git a/crates/store/re_chunk_store/src/events.rs b/crates/store/re_chunk_store/src/events.rs
index 8b9b7075a196..da68ae76b8e6 100644
--- a/crates/store/re_chunk_store/src/events.rs
+++ b/crates/store/re_chunk_store/src/events.rs
@@ -14,7 +14,7 @@ use crate::{ChunkId, ChunkStore, ChunkStoreSubscriber, RowId};
 /// Per-component information for chunks.
 ///
 /// Created from either a physical chunk or virtual manifest metadata.
-#[derive(Clone)]
+#[derive(Debug, Clone, PartialEq, Eq)]
 pub struct ChunkComponentMeta {
     pub descriptor: re_sdk_types::ComponentDescriptor,
 
@@ -28,15 +28,18 @@ pub struct ChunkComponentMeta {
     /// For virtual this means `row_count > 0`.
     pub has_data: bool,
 
-    /// Whether this component only has static data.
-    pub is_static_only: bool,
+    /// Whether this component has ever been written as static data.
+    ///
+    /// Once a component is static, it stays static. This flag is monotonic
+    /// and never transitions back to `false`.
+    pub is_static: bool,
 }
 
 /// Chunk meta originating from either a virtual or physical chunk.
 ///
 /// Useful for chunk store subscribers that do the same logic
 /// for physical and virtual additions.
-#[derive(Clone)]
+#[derive(Debug, Clone, PartialEq, Eq)]
 pub struct ChunkMeta {
     pub entity_path: re_chunk::EntityPath,
     pub components: Vec,
@@ -52,7 +55,7 @@ impl ChunkMeta {
                 descriptor: column.descriptor.clone(),
                 inner_arrow_datatype: Some(column.list_array.value_type()),
                 has_data: !column.list_array.values().is_empty(),
-                is_static_only: chunk.is_static(),
+                is_static: chunk.is_static(),
             })
             .collect();
 
@@ -138,6 +141,22 @@ pub enum ChunkStoreDiff {
 
     /// When a physical chunk has been evicted.
     Deletion(ChunkStoreDiffDeletion),
+
+    /// Newly discovered entity/component columns in the schema.
+    ///
+    /// Also emitted when a component's `is_static` flag transitions from `false` to `true`.
+    /// Note: `has_data` does not influence the emission of `SchemaAddition` events.
+    SchemaAddition(ChunkStoreDiffSchemaAddition),
+}
+
+/// Describes newly added columns to the store schema.
+///
+/// This event is emitted when previously unseen entity/component pairs are
+/// discovered, either from a physical chunk addition or from an RRD manifest.
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct ChunkStoreDiffSchemaAddition {
+    /// Newly discovered entity/component pairs, grouped by entity.
+    pub new_columns: Vec,
 }
 
 impl From for ChunkStoreDiff {
@@ -158,6 +177,12 @@ impl From for ChunkStoreDiff {
     }
 }
 
+impl From for ChunkStoreDiff {
+    fn from(value: ChunkStoreDiffSchemaAddition) -> Self {
+        Self::SchemaAddition(value)
+    }
+}
+
 impl ChunkStoreDiff {
     pub fn addition(
         chunk_before_processing: Arc,
@@ -191,6 +216,10 @@ impl ChunkStoreDiff {
         matches!(self, Self::Deletion(_))
     }
 
+    pub fn is_schema_addition(&self) -> bool {
+        matches!(self, Self::SchemaAddition(_))
+    }
+
     pub fn into_addition(self) -> Option {
         match self {
             Self::Addition(addition) => Some(addition),
@@ -238,7 +267,7 @@ impl ChunkStoreDiff {
     pub fn delta(&self) -> i64 {
         match self {
             Self::Addition(_) => 1,
-            Self::VirtualAddition(_) => 0,
+            Self::VirtualAddition(_) | Self::SchemaAddition(_) => 0,
             Self::Deletion(_) => -1,
         }
     }
@@ -256,7 +285,7 @@ impl ChunkStoreDiff {
     pub fn delta_chunk(&self) -> Option<&Arc> {
         match self {
             Self::Addition(addition) => Some(addition.delta_chunk()),
-            Self::VirtualAddition(_) => None,
+            Self::VirtualAddition(_) | Self::SchemaAddition(_) => None,
             Self::Deletion(deletion) => Some(&deletion.chunk),
         }
     }
@@ -376,7 +405,7 @@ impl ChunkStoreDiffAddition {
                 descriptor: column.descriptor.clone(),
                 inner_arrow_datatype: Some(column.list_array.value_type()),
                 has_data: !column.list_array.values().is_empty(),
-                is_static_only: delta_chunk.is_static(),
+                is_static: delta_chunk.is_static(),
             })
             .collect();
 
@@ -435,16 +464,16 @@ impl ChunkStoreDiffVirtualAddition {
                         inner_arrow_datatype: Some(inner_arrow_datatype),
                         // These fields are filled in later in this function
                         has_data: false,
-                        is_static_only: false,
+                        is_static: false,
                     },
                 )
             })
             .collect();
 
-        /// Helper to track what's know about a component from the manifest's static/temporal maps.
+        /// Helper to track what's known about a component from the manifest's static/temporal maps.
         #[derive(Default)]
         struct VirtualComponentInfo {
-            has_temporal: bool,
+            is_static: bool,
 
             has_rows: bool,
         }
@@ -462,7 +491,7 @@ impl ChunkStoreDiffVirtualAddition {
                 entry.insert(
                     component,
                     VirtualComponentInfo {
-                        has_temporal: false,
+                        is_static: true,
                         has_rows: true,
                     },
                 );
@@ -480,7 +509,6 @@ impl ChunkStoreDiffVirtualAddition {
                     let has_rows = per_chunk.values().any(|e| e.num_rows > 0);
 
                     let existing = entry.entry(component).or_default();
-                    existing.has_temporal = true;
                     existing.has_rows |= has_rows;
                 }
             }
@@ -494,17 +522,17 @@ impl ChunkStoreDiffVirtualAddition {
                     .into_iter()
                     .map(|(component, info)| {
                         let has_data = info.has_rows;
-                        let is_static_only = !info.has_temporal;
+                        let is_static = info.is_static;
                         if let Some(meta) = component_schema_info.get(&component) {
                             ChunkComponentMeta {
                                 has_data,
-                                is_static_only,
+                                is_static,
                                 ..meta.clone()
                             }
                         } else {
                             ChunkComponentMeta {
                                 has_data,
-                                is_static_only,
+                                is_static,
                                 descriptor: re_sdk_types::ComponentDescriptor::partial(component),
                                 inner_arrow_datatype: None,
                             }
@@ -562,7 +590,7 @@ impl ChunkStoreDiffDeletion {
 
 #[cfg(test)]
 mod tests {
-    use std::collections::BTreeMap;
+    use std::collections::{BTreeMap, BTreeSet};
 
     use re_chunk::{RowId, TimelineName};
     use re_log_types::example_components::{MyColor, MyIndex, MyPoint, MyPoints};
@@ -612,7 +640,9 @@ mod tests {
 
             for event in events {
                 let delta = event.delta();
-                let delta_chunk = event.delta_chunk().unwrap();
+                let Some(delta_chunk) = event.delta_chunk() else {
+                    continue;
+                };
                 let delta_rows = delta * delta_chunk.num_rows() as i64;
 
                 for row_id in delta_chunk.row_ids() {
@@ -645,6 +675,18 @@ mod tests {
         }
     }
 
+    /// Helper to extract the set of new component descriptors from a [`ChunkStoreDiff::SchemaAddition`].
+    fn schema_addition_descriptors(event: &ChunkStoreEvent) -> BTreeSet {
+        match &event.diff {
+            ChunkStoreDiff::SchemaAddition(sa) => sa
+                .new_columns
+                .iter()
+                .flat_map(|m| m.components.iter().map(|c| c.descriptor.clone()))
+                .collect(),
+            other => panic!("expected SchemaAddition, got {other:?}"),
+        }
+    }
+
     #[test]
     fn store_events() -> anyhow::Result<()> {
         let mut store = ChunkStore::new(
@@ -673,7 +715,18 @@ mod tests {
             )
             .build()?;
 
-        view.on_events(&store.insert_chunk(&Arc::new(chunk1))?);
+        let events = store.insert_chunk(&Arc::new(chunk1))?;
+
+        // chunk1 introduces entity_a with MyIndex — expect Addition + SchemaAddition.
+        assert_eq!(events.len(), 2);
+        assert!(events[0].is_addition());
+        assert!(events[1].is_schema_addition());
+        assert_eq!(
+            schema_addition_descriptors(&events[1]),
+            BTreeSet::from([MyIndex::partial_descriptor()]),
+        );
+
+        view.on_events(&events);
 
         similar_asserts::assert_eq!(
             GlobalCounts::new(
@@ -725,7 +778,18 @@ mod tests {
                 .build()?
         };
 
-        view.on_events(&store.insert_chunk(&Arc::new(chunk2))?);
+        let events = store.insert_chunk(&Arc::new(chunk2))?;
+
+        // chunk2 introduces entity_b with Points + Colors — expect Addition + SchemaAddition.
+        assert_eq!(events.len(), 2);
+        assert!(events[0].is_addition());
+        assert!(events[1].is_schema_addition());
+        assert_eq!(
+            schema_addition_descriptors(&events[1]),
+            BTreeSet::from([MyPoints::descriptor_points(), MyPoints::descriptor_colors(),]),
+        );
+
+        view.on_events(&events);
 
         similar_asserts::assert_eq!(
             GlobalCounts::new(
@@ -777,7 +841,21 @@ mod tests {
                 .build()?
         };
 
-        view.on_events(&store.insert_chunk(&Arc::new(chunk3))?);
+        let events = store.insert_chunk(&Arc::new(chunk3))?;
+
+        // chunk3 adds MyIndex to entity_b (new!) and re-uses Colors (not new, but transitions to static).
+        // Colors already existed on entity_b, but this is a static chunk so Colors gets an
+        // is_static transition (false → true). MyIndex is new on entity_b.
+        assert_eq!(events.len(), 2);
+        assert!(events[0].is_addition());
+        assert!(events[1].is_schema_addition());
+        assert_eq!(
+            schema_addition_descriptors(&events[1]),
+            BTreeSet::from([MyIndex::partial_descriptor(), MyPoints::descriptor_colors(),]),
+            "MyIndex is new on entity_b; Colors gets is_static transition"
+        );
+
+        view.on_events(&events);
 
         similar_asserts::assert_eq!(
             GlobalCounts::new(
@@ -811,6 +889,16 @@ mod tests {
         );
 
         let events = store.gc(&GarbageCollectionOptions::gc_everything()).0;
+
+        // GC should only produce Deletion events, never SchemaAddition.
+        for event in &events {
+            assert!(
+                event.is_deletion(),
+                "GC should only produce deletions, got: {:?}",
+                event.diff
+            );
+        }
+
         view.on_events(&events);
 
         similar_asserts::assert_eq!(
diff --git a/crates/store/re_chunk_store/src/lib.rs b/crates/store/re_chunk_store/src/lib.rs
index c86821670d3a..e0add817381a 100644
--- a/crates/store/re_chunk_store/src/lib.rs
+++ b/crates/store/re_chunk_store/src/lib.rs
@@ -45,7 +45,7 @@ pub use self::dataframe::{
 };
 pub use self::events::{
     ChunkComponentMeta, ChunkMeta, ChunkStoreDiff, ChunkStoreDiffAddition, ChunkStoreDiffDeletion,
-    ChunkStoreDiffVirtualAddition, ChunkStoreEvent,
+    ChunkStoreDiffSchemaAddition, ChunkStoreDiffVirtualAddition, ChunkStoreEvent,
 };
 pub use self::gc::{GarbageCollectionOptions, GarbageCollectionTarget};
 pub use self::lineage::{ChunkDirectLineage, ChunkDirectLineageReport};
diff --git a/crates/store/re_chunk_store/src/lineage.rs b/crates/store/re_chunk_store/src/lineage.rs
index c78035e4b008..012e1b143af9 100644
--- a/crates/store/re_chunk_store/src/lineage.rs
+++ b/crates/store/re_chunk_store/src/lineage.rs
@@ -584,8 +584,7 @@ mod tests {
 
         for chunk in &chunks {
             let events = store.insert_chunk(chunk).unwrap();
-            for event in events {
-                let diff = event.to_addition().unwrap();
+            for diff in events.iter().filter_map(|event| event.to_addition()) {
                 if let ChunkDirectLineageReport::SplitFrom(src, _siblings) = &diff.direct_lineage {
                     assert_eq!(
                         diff.chunk_before_processing.id(),
@@ -675,8 +674,11 @@ mod tests {
                 assert_eq!(chunk.id(), diff.chunk.id(), "ghost index");
             }
 
-            for event in events.into_iter().skip(1) {
-                let diff = event.to_addition().unwrap();
+            for diff in events
+                .iter()
+                .filter_map(|event| event.to_addition())
+                .skip(1)
+            {
                 if let ChunkDirectLineageReport::SplitFrom(src, _siblings) = &diff.direct_lineage {
                     assert_eq!(
                         diff.chunk_before_processing.id(),
@@ -746,12 +748,16 @@ mod tests {
 
         // We will end up with 4 split chunks.
         let events = store.insert_chunk(&chunk).unwrap();
-        assert_eq!(4, events.len());
-        for event in &events {
+        assert_eq!(5, events.len());
+        assert!(
+            events[4].is_schema_addition(),
+            "the first write should emit a schema addition for newly seen columns"
+        );
+        for event in &events[..4] {
             assert_eq!(true, event.is_addition());
 
             // Check that splits are always flattened, very important!
-            let siblings = events
+            let siblings = events[..4]
                 .iter()
                 .filter(|e| e.delta_chunk().unwrap().id() != event.delta_chunk().unwrap().id())
                 .map(|e| e.delta_chunk().unwrap().clone())
@@ -846,10 +852,14 @@ mod tests {
 
         // We will end up with 2 split chunks, both below the num_rows threshold.
         let events = store.insert_chunk(&chunk1).unwrap();
-        assert_eq!(2, events.len());
-        for event in events {
+        assert_eq!(3, events.len());
+        for event in &events[..2] {
             assert_eq!(true, event.is_addition());
         }
+        assert!(
+            events[2].is_schema_addition(),
+            "the first write should emit a schema addition for newly seen columns"
+        );
 
         assert_eq!(2, store.num_physical_chunks());
         for chunk in store.iter_physical_chunks() {
@@ -949,15 +959,16 @@ mod tests {
         let chunk2 = build_chunk(9);
 
         let events = store.insert_chunk(&chunk1).unwrap();
-        assert_eq!(1, events.len());
-        for event in events {
-            assert_eq!(true, event.is_addition());
-        }
+        assert_eq!(2, events.len());
+        assert_eq!(true, events[0].is_addition());
+        assert!(
+            events[1].is_schema_addition(),
+            "the first write should emit a schema addition for newly seen columns"
+        );
+
         let events = store.insert_chunk(&chunk2).unwrap();
         assert_eq!(1, events.len());
-        for event in events {
-            assert_eq!(true, event.is_addition());
-        }
+        assert_eq!(true, events[0].is_addition());
 
         // The chunks should just not get compacted since the result would be beyond the num_rows
         // threshold, and therefore will never be split either since there will never be a chunk
@@ -1012,11 +1023,21 @@ mod tests {
         let chunks = (0..10).map(|_| build_chunk(1)).collect_vec();
 
         let mut prev_chunk: Option> = None;
+        let mut is_first_insert = true;
         for chunk in chunks {
             let mut events = store.insert_chunk(&chunk).unwrap();
-            assert_eq!(1, events.len());
+            if is_first_insert {
+                assert_eq!(2, events.len());
+                assert!(
+                    events[1].is_schema_addition(),
+                    "the first write should emit a schema addition for newly seen columns"
+                );
+                is_first_insert = false;
+            } else {
+                assert_eq!(1, events.len());
+            }
 
-            let event = events.pop().unwrap();
+            let event = events.remove(0);
             let event = event.to_addition().unwrap();
             assert_eq!(chunk.id(), event.chunk_before_processing.id());
 
diff --git a/crates/store/re_chunk_store/src/store.rs b/crates/store/re_chunk_store/src/store.rs
index 06bd0f888aee..722f7288bc41 100644
--- a/crates/store/re_chunk_store/src/store.rs
+++ b/crates/store/re_chunk_store/src/store.rs
@@ -339,15 +339,21 @@ pub struct ColumnMetadataState {
     /// This is purely additive: once false, it will always be false. Even in case of garbage
     /// collection.
     pub is_semantically_empty: bool,
+
+    /// Whether this column has ever been written as static data.
+    ///
+    /// Starts as `false` and flips to `true` once static data is observed. Never goes back.
+    pub is_static: bool,
 }
 
 impl re_byte_size::SizeBytes for ColumnMetadataState {
     fn heap_size_bytes(&self) -> u64 {
         let Self {
             is_semantically_empty,
+            is_static,
         } = self;
 
-        is_semantically_empty.heap_size_bytes()
+        is_semantically_empty.heap_size_bytes() + is_static.heap_size_bytes()
     }
 }
 
@@ -888,6 +894,7 @@ impl ChunkStore {
     ) -> Option {
         let ColumnMetadataState {
             is_semantically_empty,
+            is_static: _,
         } = self
             .schema
             .lookup_column_metadata_state(entity_path, component)?;
diff --git a/crates/store/re_chunk_store/src/store_schema.rs b/crates/store/re_chunk_store/src/store_schema.rs
index 4c5704a01537..698845fa49c0 100644
--- a/crates/store/re_chunk_store/src/store_schema.rs
+++ b/crates/store/re_chunk_store/src/store_schema.rs
@@ -40,7 +40,7 @@ impl re_byte_size::SizeBytes for ColumnMetadataEntry {
     }
 }
 
-use crate::ChunkStoreEvent;
+use crate::{ChunkComponentMeta, ChunkMeta, ChunkStoreEvent};
 
 // ---
 
@@ -82,6 +82,8 @@ pub struct StoreSchema {
     components_per_entity: IntMap,
 
     // TODO(grtlr): Can we slim this map down by getting rid of `ComponentIdentifier`-level here?
+    // Ideally, we'd even merge this with the above fields. We are currently storing a lot of
+    // redundant information.
     per_column_metadata: IntMap>,
 }
 
@@ -199,31 +201,139 @@ impl StoreSchema {
         }
     }
 
+    /// Update per-entity component set and per-column metadata for a single component.
+    ///
+    /// Returns `Some(ChunkComponentMeta)` when a schema event should be emitted,
+    /// i.e. when the column is genuinely new or `is_static` transitions from `false` to `true`.
+    fn update_column_metadata(
+        &mut self,
+        col_descr: &ComponentColumnDescriptor,
+    ) -> Option {
+        let ComponentColumnDescriptor {
+            entity_path,
+            component,
+            is_static,
+            is_semantically_empty,
+            store_datatype: _,
+            component_type: _,
+            archetype: _,
+            is_tombstone: _,
+        } = col_descr;
+        let descriptor = col_descr.component_descriptor();
+        let inner_datatype = col_descr.inner_datatype();
+        let metadata_state = ColumnMetadataState {
+            is_semantically_empty: *is_semantically_empty,
+            is_static: *is_static,
+        };
+
+        let key = schema_component_key(col_descr);
+        self.components
+            .entry(key)
+            .and_modify(|existing| {
+                existing.is_static |= is_static;
+                existing.is_semantically_empty &= is_semantically_empty;
+            })
+            .or_insert_with(|| col_descr.clone());
+
+        let is_new = self
+            .components_per_entity
+            .entry(entity_path.clone())
+            .or_default()
+            .insert(*component);
+
+        let prev_is_static = self
+            .per_column_metadata
+            .get(entity_path)
+            .and_then(|per_id| per_id.get(component))
+            .map(|e| e.metadata_state.is_static);
+
+        let entry = self
+            .per_column_metadata
+            .entry(entity_path.clone())
+            .or_default()
+            .entry(*component)
+            .and_modify(|e| {
+                if e.datatype != inner_datatype {
+                    // TODO(grtlr): If we encounter two different data types, we should split the chunk.
+                    // More information: https://github.com/rerun-io/rerun/pull/10082#discussion_r2140549340
+                    re_log::warn_once!(
+                        "Datatype of column {} in {entity_path} has changed from {} to {inner_datatype}",
+                        e.descriptor,
+                        e.datatype,
+                    );
+                    e.datatype = inner_datatype.clone();
+                }
+                e.metadata_state.is_static |= is_static;
+                e.metadata_state.is_semantically_empty &= is_semantically_empty;
+            })
+            .or_insert_with(|| ColumnMetadataEntry {
+                descriptor: descriptor.clone(),
+                metadata_state,
+                datatype: inner_datatype.clone(),
+            });
+
+        let new_is_static = entry.metadata_state.is_static;
+        let static_changed = prev_is_static.is_some_and(|prev| !prev && new_is_static);
+
+        if is_new || static_changed {
+            Some(ChunkComponentMeta {
+                descriptor: descriptor.clone(),
+                inner_arrow_datatype: Some(inner_datatype.clone()),
+                has_data: !entry.metadata_state.is_semantically_empty,
+                is_static: new_is_static,
+            })
+        } else {
+            None
+        }
+    }
+
     // --- Updating via events ---
 
     /// Update the schema from store events.
     ///
     /// This processes addition events (both physical chunk additions and virtual
-    /// manifest additions). Deletion events are ignored since the schema is purely additive.
-    pub fn on_events(&mut self, events: &[ChunkStoreEvent]) {
+    /// manifest additions). Deletion events and schema column addition events are
+    /// ignored since the schema is purely additive and schema events are output, not input.
+    ///
+    /// Returns newly discovered entity/component pairs grouped by entity.
+    pub fn on_events(&mut self, events: &[ChunkStoreEvent]) -> Vec {
         re_tracing::profile_function!();
 
+        let mut all_new: nohash_hasher::IntMap> =
+            Default::default();
+
         for event in events {
             match &event.diff {
                 crate::ChunkStoreDiff::Addition(add) => {
-                    self.on_chunk_addition(&add.chunk_after_processing);
+                    for new_col in self.on_chunk_addition(&add.chunk_after_processing) {
+                        all_new
+                            .entry(add.chunk_after_processing.entity_path().clone())
+                            .or_default()
+                            .push(new_col);
+                    }
                 }
                 crate::ChunkStoreDiff::VirtualAddition(vadd) => {
-                    self.on_rrd_manifest(&vadd.rrd_manifest);
+                    for (entity_path, new_cols) in self.on_rrd_manifest(&vadd.rrd_manifest) {
+                        all_new.entry(entity_path).or_default().extend(new_cols);
+                    }
                 }
-                crate::ChunkStoreDiff::Deletion(_) => {
-                    // Schema is purely additive — deletions are ignored.
+                crate::ChunkStoreDiff::Deletion(_) | crate::ChunkStoreDiff::SchemaAddition(_) => {
+                    // Schema is purely additive — deletions and schema column addition events are ignored.
                 }
             }
         }
+
+        all_new
+            .into_iter()
+            .map(|(entity_path, components)| ChunkMeta {
+                entity_path,
+                components,
+            })
+            .collect()
     }
 
-    fn on_chunk_addition(&mut self, chunk: &re_chunk::Chunk) {
+    /// Returns [`ChunkComponentMeta`] for each genuinely new component column.
+    fn on_chunk_addition(&mut self, chunk: &re_chunk::Chunk) -> Vec {
         let is_static = chunk.is_static();
 
         // Update time type registry
@@ -241,16 +351,13 @@ impl StoreSchema {
 
         let entity_path = chunk.entity_path();
 
+        let mut new_columns = Vec::new();
+
         // Update component columns and per-entity component sets
         for column in chunk.components().values() {
             let descriptor = &column.descriptor;
             let component = descriptor.component;
 
-            self.components_per_entity
-                .entry(entity_path.clone())
-                .or_default()
-                .insert(component);
-
             let is_semantically_empty =
                 re_arrow_util::is_list_array_semantically_empty(&column.list_array);
 
@@ -272,45 +379,19 @@ impl StoreSchema {
                 is_semantically_empty,
             };
 
-            let key = schema_component_key(&col_descr);
-            self.components
-                .entry(key)
-                .and_modify(|existing| {
-                    // Additive updates: once static, always static; once non-empty, always non-empty
-                    existing.is_static |= is_static;
-                    existing.is_semantically_empty &= is_semantically_empty;
-                })
-                .or_insert(col_descr);
-
-            // Update per-column metadata
-            let entry = self
-                .per_column_metadata
-                .entry(entity_path.clone())
-                .or_default()
-                .entry(component)
-                .or_insert_with(|| ColumnMetadataEntry {
-                    descriptor: descriptor.clone(),
-                    metadata_state: ColumnMetadataState {
-                        is_semantically_empty: true,
-                    },
-                    datatype: column.list_array.value_type().clone(),
-                });
-            if entry.datatype != column.list_array.value_type() {
-                // TODO(grtlr): If we encounter two different data types, we should split the chunk.
-                // More information: https://github.com/rerun-io/rerun/pull/10082#discussion_r2140549340
-                re_log::warn!(
-                    "Datatype of column {} in {entity_path} has changed from {} to {}",
-                    entry.descriptor,
-                    entry.datatype,
-                    column.list_array.value_type()
-                );
-                entry.datatype = column.list_array.value_type().clone();
+            if let Some(meta) = self.update_column_metadata(&col_descr) {
+                new_columns.push(meta);
             }
-            entry.metadata_state.is_semantically_empty &= is_semantically_empty;
         }
+
+        new_columns
     }
 
-    fn on_rrd_manifest(&mut self, rrd_manifest: &re_log_encoding::RrdManifest) {
+    /// Returns newly inserted columns grouped by entity path.
+    fn on_rrd_manifest(
+        &mut self,
+        rrd_manifest: &re_log_encoding::RrdManifest,
+    ) -> Vec<(EntityPath, Vec)> {
         let sorbet_schema = rrd_manifest.recording_schema();
 
         // Update time type registry
@@ -319,51 +400,20 @@ impl StoreSchema {
                 .insert(descr.timeline_name(), descr.timeline().typ());
         }
 
+        let mut new_per_entity: nohash_hasher::IntMap> =
+            Default::default();
+
         // Update component columns and per-entity component sets
         for descr in sorbet_schema.columns.component_columns() {
-            let component = descr.component;
-            let entity_path = &descr.entity_path;
-
-            self.components_per_entity
-                .entry(entity_path.clone())
-                .or_default()
-                .insert(component);
-
-            let key = schema_component_key(descr);
-            self.components
-                .entry(key)
-                .and_modify(|existing| {
-                    existing.is_static |= descr.is_static;
-                    existing.is_semantically_empty &= descr.is_semantically_empty;
-                })
-                .or_insert_with(|| descr.clone());
-
-            // Update per-column metadata
-            let inner_datatype = descr.inner_datatype();
-            let previous = self
-                .per_column_metadata
-                .entry(entity_path.clone())
-                .or_default()
-                .insert(
-                    component,
-                    ColumnMetadataEntry {
-                        descriptor: descr.component_descriptor(),
-                        metadata_state: ColumnMetadataState {
-                            is_semantically_empty: descr.is_semantically_empty,
-                        },
-                        datatype: inner_datatype.clone(),
-                    },
-                );
-
-            if let Some(previous) = previous
-                && previous.datatype != inner_datatype
-            {
-                re_log::warn_once!(
-                    "Component '{component}' on entity '{entity_path}' changed type from {} to {inner_datatype}",
-                    previous.datatype,
-                );
+            if let Some(meta) = self.update_column_metadata(descr) {
+                new_per_entity
+                    .entry(descr.entity_path.clone())
+                    .or_default()
+                    .push(meta);
             }
         }
+
+        new_per_entity.into_iter().collect()
     }
 
     /// Remove all data for a given entity path.
diff --git a/crates/store/re_chunk_store/src/writes.rs b/crates/store/re_chunk_store/src/writes.rs
index e26e7213d4de..acbeba9151d9 100644
--- a/crates/store/re_chunk_store/src/writes.rs
+++ b/crates/store/re_chunk_store/src/writes.rs
@@ -24,7 +24,7 @@ impl ChunkStore {
     ///
     /// All queries will return partial results until the missing physical data gets loaded in.
     #[must_use = "The chunk store events should be handled"]
-    pub fn insert_rrd_manifest(&mut self, rrd_manifest: Arc) -> ChunkStoreEvent {
+    pub fn insert_rrd_manifest(&mut self, rrd_manifest: Arc) -> Vec {
         re_tracing::profile_function!();
 
         let Self {
@@ -147,13 +147,28 @@ impl ChunkStore {
             diff: ChunkStoreDiff::virtual_addition(rrd_manifest),
         };
 
-        self.schema.on_events(std::slice::from_ref(&event));
+        let new_columns = self.schema.on_events(std::slice::from_ref(&event));
+
+        let mut events = vec![event];
+
+        if !new_columns.is_empty() {
+            events.push(ChunkStoreEvent {
+                store_id: self.id.clone(),
+                store_generation: self.generation(),
+                event_id: self
+                    .event_id
+                    .fetch_add(1, std::sync::atomic::Ordering::Relaxed),
+                diff: ChunkStoreDiff::SchemaAddition(crate::ChunkStoreDiffSchemaAddition {
+                    new_columns,
+                }),
+            });
+        }
 
         if self.config.enable_changelog {
-            Self::on_events(std::slice::from_ref(&event));
+            Self::on_events(&events);
         }
 
-        event
+        events
     }
 
     /// Inserts a [`Chunk`] in the store.
@@ -172,7 +187,7 @@ impl ChunkStore {
 
         let diffs = self.insert_chunk_impl(chunk, ChunkDirectLineageReport::Volatile)?;
 
-        let events: Vec<_> = diffs
+        let mut events: Vec<_> = diffs
             .into_iter()
             .map(|diff| ChunkStoreEvent {
                 store_id: self.id.clone(),
@@ -184,7 +199,20 @@ impl ChunkStore {
             })
             .collect();
 
-        self.schema.on_events(&events);
+        let new_columns = self.schema.on_events(&events);
+
+        if !new_columns.is_empty() {
+            events.push(ChunkStoreEvent {
+                store_id: self.id.clone(),
+                store_generation: self.generation(),
+                event_id: self
+                    .event_id
+                    .fetch_add(1, std::sync::atomic::Ordering::Relaxed),
+                diff: ChunkStoreDiff::SchemaAddition(crate::ChunkStoreDiffSchemaAddition {
+                    new_columns,
+                }),
+            });
+        }
 
         if self.config.enable_changelog {
             Self::on_events(&events);
@@ -1033,7 +1061,7 @@ impl ChunkStore {
 
 #[cfg(test)]
 mod tests {
-    use std::collections::BTreeMap;
+    use std::collections::{BTreeMap, BTreeSet};
 
     use re_chunk::{TimeInt, TimePoint, Timeline};
     use re_log_types::example_components::{MyColor, MyLabel, MyPoint, MyPoints};
@@ -1367,18 +1395,48 @@ mod tests {
         let chunk4 = Arc::new(chunk4);
 
         let events = store.insert_chunk(&chunk1)?;
+        assert_eq!(events.len(), 2);
+        assert_eq!(events[0].delta_chunk().unwrap().id(), chunk1.id());
         assert!(
-            events.len() == 1
-                && events[0].delta_chunk().unwrap().id() == chunk1.id()
-                && matches!(events[0].diff, ChunkStoreDiff::Addition(_)),
-            "the first write should result in the addition of chunk1 and nothing else"
+            events[0].is_addition(),
+            "the first write should result in the addition of chunk1"
+        );
+        // chunk1 introduces 3 new components on this entity: points, colors, labels.
+        let schema_add = match &events[1].diff {
+            ChunkStoreDiff::SchemaAddition(sa) => sa,
+            other => panic!("expected SchemaAddition, got {other:?}"),
+        };
+        assert_eq!(schema_add.new_columns.len(), 1);
+        assert_eq!(schema_add.new_columns[0].entity_path, entity_path);
+        let new_descriptors: BTreeSet<_> = schema_add.new_columns[0]
+            .components
+            .iter()
+            .map(|c| c.descriptor.clone())
+            .collect();
+        assert_eq!(
+            new_descriptors,
+            BTreeSet::from([
+                MyPoints::descriptor_points(),
+                MyPoints::descriptor_colors(),
+                MyPoints::descriptor_labels(),
+            ]),
+            "points, colors, labels"
         );
+        // All should be is_static since chunk1 is static.
+        for comp in &schema_add.new_columns[0].components {
+            assert!(
+                comp.is_static,
+                "{} should be is_static after a static insert",
+                comp.descriptor
+            );
+        }
 
         let events = store.insert_chunk(&chunk2)?;
+        // chunk2 only has points and colors which already exist — no new schema columns.
+        assert_eq!(events.len(), 1);
+        assert_eq!(events[0].delta_chunk().unwrap().id(), chunk2.id());
         assert!(
-            events.len() == 1
-                && events[0].delta_chunk().unwrap().id() == chunk2.id()
-                && matches!(events[0].diff, ChunkStoreDiff::Addition(_)),
+            events[0].is_addition(),
             "the second write should result in the addition of chunk2 and nothing else"
         );
 
@@ -1396,13 +1454,13 @@ mod tests {
         }
 
         let events = store.insert_chunk(&chunk3)?;
+        assert_eq!(events.len(), 2);
+        assert_eq!(events[0].delta_chunk().unwrap().id(), chunk3.id());
+        assert!(events[0].is_addition());
+        assert_eq!(events[1].delta_chunk().unwrap().id(), chunk1.id());
         assert!(
-            events.len() == 2
-                && events[0].delta_chunk().unwrap().id() == chunk3.id()
-                && matches!(events[0].diff, ChunkStoreDiff::Addition(_))
-                && events[1].delta_chunk().unwrap().id() == chunk1.id()
-                && matches!(events[1].diff, ChunkStoreDiff::Deletion(_)),
-            "the third write should result in the addition of chunk3 _and_ the deletion of the now fully overwritten chunk1"
+            events[1].is_deletion(),
+            "the third write should result in the addition of chunk3 and the deletion of chunk1"
         );
 
         let stats_after = store.stats();
@@ -1440,6 +1498,206 @@ mod tests {
         Ok(())
     }
 
+    /// Temporal data first, then static: `is_static` should transition and re-emit a `SchemaAddition`.
+    #[test]
+    fn schema_temporal_then_static() -> anyhow::Result<()> {
+        re_log::setup_logging();
+
+        let mut store = ChunkStore::new(
+            re_log_types::StoreId::random(re_log_types::StoreKind::Recording, "test_app"),
+            Default::default(),
+        );
+
+        let entity_path = EntityPath::from("this/that");
+        let points = &[MyPoint::new(1.0, 1.0)];
+
+        // Temporal insert: new component, is_static = false.
+        let events = store.insert_chunk(&Arc::new(
+            Chunk::builder(entity_path.clone())
+                .with_component_batches(
+                    RowId::new(),
+                    [(Timeline::new_sequence("frame"), 1)],
+                    [(MyPoints::descriptor_points(), points as _)],
+                )
+                .build()?,
+        ))?;
+        assert_eq!(events.len(), 2);
+        assert!(events[0].is_addition());
+        let schema_add = match &events[1].diff {
+            ChunkStoreDiff::SchemaAddition(sa) => sa,
+            other => panic!("expected SchemaAddition, got {other:?}"),
+        };
+        assert!(!schema_add.new_columns[0].components[0].is_static);
+
+        // Static insert: same component, triggers is_static transition.
+        let events = store.insert_chunk(&Arc::new(
+            Chunk::builder(entity_path.clone())
+                .with_component_batches(
+                    RowId::new(),
+                    TimePoint::STATIC,
+                    [(MyPoints::descriptor_points(), points as _)],
+                )
+                .build()?,
+        ))?;
+        assert_eq!(events.len(), 2);
+        assert!(events[0].is_addition());
+        let schema_add = match &events[1].diff {
+            ChunkStoreDiff::SchemaAddition(sa) => sa,
+            other => panic!("expected SchemaAddition for is_static transition, got {other:?}"),
+        };
+        assert!(
+            schema_add.new_columns[0].components[0].is_static,
+            "component should now be is_static"
+        );
+
+        // Another temporal insert: no further transition.
+        let events = store.insert_chunk(&Arc::new(
+            Chunk::builder(entity_path.clone())
+                .with_component_batches(
+                    RowId::new(),
+                    [(Timeline::new_sequence("frame"), 2)],
+                    [(MyPoints::descriptor_points(), points as _)],
+                )
+                .build()?,
+        ))?;
+        assert!(
+            !events.iter().any(|e| e.is_schema_addition()),
+            "no SchemaAddition after transition already happened"
+        );
+
+        Ok(())
+    }
+
+    /// `insert_rrd_manifest` should emit a `SchemaAddition` with the manifest's columns.
+    #[test]
+    fn schema_addition_from_manifest() -> anyhow::Result<()> {
+        re_log::setup_logging();
+
+        let store_id =
+            re_log_types::StoreId::random(re_log_types::StoreKind::Recording, "test_app");
+        let mut store = ChunkStore::new(store_id.clone(), Default::default());
+
+        let entity_path = EntityPath::from("this/that");
+        let tl = Timeline::new_sequence("frame");
+        let point = MyPoint::new(1.0, 1.0);
+
+        let chunks: Vec> = [10, 20]
+            .into_iter()
+            .map(|t| {
+                Arc::new(
+                    Chunk::builder(entity_path.clone())
+                        .with_component_batch(
+                            RowId::new(),
+                            TimePoint::from_iter([(tl, t)]),
+                            (MyPoints::descriptor_points(), &[point] as _),
+                        )
+                        .build()
+                        .unwrap(),
+                )
+            })
+            .collect();
+
+        let rrd_manifest = re_log_encoding::RrdManifest::build_in_memory_from_chunks(
+            store_id,
+            chunks.iter().map(|c| &**c),
+        )?;
+
+        let events = store.insert_rrd_manifest(rrd_manifest);
+        assert_eq!(events.len(), 2);
+        assert!(events[0].is_virtual_addition());
+        let schema_add = match &events[1].diff {
+            ChunkStoreDiff::SchemaAddition(sa) => sa,
+            other => panic!("expected SchemaAddition, got {other:?}"),
+        };
+        assert_eq!(schema_add.new_columns.len(), 1);
+        assert_eq!(schema_add.new_columns[0].entity_path, entity_path);
+        assert!(!schema_add.new_columns[0].components.is_empty());
+
+        // Inserting the same manifest again should NOT emit a second SchemaAddition.
+        let rrd_manifest2 = re_log_encoding::RrdManifest::build_in_memory_from_chunks(
+            re_log_types::StoreId::random(re_log_types::StoreKind::Recording, "test_app"),
+            chunks.iter().map(|c| &**c),
+        )?;
+        let events2 = store.insert_rrd_manifest(rrd_manifest2);
+        assert!(
+            !events2.iter().any(|e| e.is_schema_addition()),
+            "re-inserting a manifest with the same columns should not emit SchemaAddition"
+        );
+
+        Ok(())
+    }
+
+    /// Manifest with temporal data followed by manifest with static data:
+    /// `is_static` should transition and re-emit a `SchemaAddition`.
+    #[test]
+    fn schema_static_transition_from_manifest() -> anyhow::Result<()> {
+        re_log::setup_logging();
+
+        let store_id =
+            re_log_types::StoreId::random(re_log_types::StoreKind::Recording, "test_app");
+        let mut store = ChunkStore::new(store_id.clone(), Default::default());
+
+        let entity_path = EntityPath::from("this/that");
+        let tl = Timeline::new_sequence("frame");
+        let point = MyPoint::new(1.0, 1.0);
+
+        // First manifest: temporal-only data.
+        let temporal_chunk = Arc::new(
+            Chunk::builder(entity_path.clone())
+                .with_component_batch(
+                    RowId::new(),
+                    TimePoint::from_iter([(tl, 10)]),
+                    (MyPoints::descriptor_points(), &[point] as _),
+                )
+                .build()?,
+        );
+        let manifest_temporal = re_log_encoding::RrdManifest::build_in_memory_from_chunks(
+            store_id.clone(),
+            std::iter::once(&*temporal_chunk),
+        )?;
+
+        let events = store.insert_rrd_manifest(manifest_temporal);
+        assert_eq!(events.len(), 2);
+        assert!(events[0].is_virtual_addition());
+        let schema_add = match &events[1].diff {
+            ChunkStoreDiff::SchemaAddition(sa) => sa,
+            other => panic!("expected SchemaAddition, got {other:?}"),
+        };
+        assert!(
+            !schema_add.new_columns[0].components[0].is_static,
+            "first manifest is temporal-only"
+        );
+
+        // Second manifest: same component but with static data.
+        let static_chunk = Arc::new(
+            Chunk::builder(entity_path.clone())
+                .with_component_batch(
+                    RowId::new(),
+                    TimePoint::STATIC,
+                    (MyPoints::descriptor_points(), &[point] as _),
+                )
+                .build()?,
+        );
+        let manifest_static = re_log_encoding::RrdManifest::build_in_memory_from_chunks(
+            store_id,
+            std::iter::once(&*static_chunk),
+        )?;
+
+        let events = store.insert_rrd_manifest(manifest_static);
+        assert_eq!(events.len(), 2);
+        assert!(events[0].is_virtual_addition());
+        let schema_add = match &events[1].diff {
+            ChunkStoreDiff::SchemaAddition(sa) => sa,
+            other => panic!("expected SchemaAddition for is_static transition, got {other:?}"),
+        };
+        assert!(
+            schema_add.new_columns[0].components[0].is_static,
+            "component should now be is_static after static manifest"
+        );
+
+        Ok(())
+    }
+
     #[test]
     fn row_id_min_overwrites() -> anyhow::Result<()> {
         re_log::setup_logging();
diff --git a/crates/store/re_entity_db/src/data_meta_per_timeline.rs b/crates/store/re_entity_db/src/data_meta_per_timeline.rs
index 737ffd560ab8..f2dfa6aa5766 100644
--- a/crates/store/re_entity_db/src/data_meta_per_timeline.rs
+++ b/crates/store/re_entity_db/src/data_meta_per_timeline.rs
@@ -140,6 +140,7 @@ impl DataMetaPerTimeline {
                     }
                 }
             }
+            ChunkStoreDiff::SchemaAddition(_) => {}
         }
     }
 
@@ -373,9 +374,9 @@ mod tests {
         let (_, rrd_manifest) = build_manifest_chunks(&entity, tl, &[10, 20, 30], &store_id);
 
         // Insert the manifest virtually.
-        let event = store.insert_rrd_manifest(rrd_manifest.clone());
+        let events = store.insert_rrd_manifest(rrd_manifest.clone());
         manifest_index.append(rrd_manifest).unwrap();
-        meta.on_events(&manifest_index, &store, &[event]);
+        meta.on_events(&manifest_index, &store, &events);
 
         // Virtual rows should be counted.
         assert_eq!(meta.row_count_for_timeline(tl.name()), 3);
@@ -394,10 +395,10 @@ mod tests {
         let (chunks, rrd_manifest) = build_manifest_chunks(&entity, tl, &[10, 20, 30], &store_id);
 
         // Load virtually first.
-        let event = store.insert_rrd_manifest(rrd_manifest.clone());
+        let events = store.insert_rrd_manifest(rrd_manifest.clone());
         manifest_index.append(rrd_manifest).unwrap();
-        manifest_index.on_events(&store, std::slice::from_ref(&event));
-        meta.on_events(&manifest_index, &store, std::slice::from_ref(&event));
+        manifest_index.on_events(&store, &events);
+        meta.on_events(&manifest_index, &store, &events);
 
         assert_eq!(meta.row_count_for_timeline(tl.name()), 3);
 
@@ -455,9 +456,9 @@ mod tests {
         )
         .unwrap();
 
-        let event = store.insert_rrd_manifest(rrd_manifest.clone());
+        let events = store.insert_rrd_manifest(rrd_manifest.clone());
         manifest_index.append(rrd_manifest).unwrap();
-        meta.on_events(&manifest_index, &store, &[event]);
+        meta.on_events(&manifest_index, &store, &events);
 
         assert_eq!(meta.row_count_for_timeline(tl.name()), 4);
     }
diff --git a/crates/store/re_entity_db/src/entity_db.rs b/crates/store/re_entity_db/src/entity_db.rs
index 17346aa2495a..a0b2169a2325 100644
--- a/crates/store/re_entity_db/src/entity_db.rs
+++ b/crates/store/re_entity_db/src/entity_db.rs
@@ -724,22 +724,25 @@ impl EntityDb {
         self.entity_path_from_hash.contains_key(&entity_path.hash())
     }
 
-    pub fn add_rrd_manifest_message(&mut self, rrd_manifest: Arc) -> ChunkStoreEvent {
+    pub fn add_rrd_manifest_message(
+        &mut self,
+        rrd_manifest: Arc,
+    ) -> Vec {
         re_tracing::profile_function!();
 
-        let event = self
+        let events = self
             .storage_engine
             .write()
             .store()
             .insert_rrd_manifest(rrd_manifest.clone());
 
-        self.on_store_events(std::slice::from_ref(&event));
+        self.on_store_events(&events);
 
         if let Err(err) = self.rrd_manifest_index.append(rrd_manifest) {
             re_log::error!("Failed to append RRD manifest: {err}");
         }
 
-        event
+        events
     }
 
     /// Mark the RRD manifest as complete (all parts have been received).
diff --git a/crates/store/re_entity_db/src/rrd_manifest_index.rs b/crates/store/re_entity_db/src/rrd_manifest_index.rs
index ecf6fe9691d7..6cf747036268 100644
--- a/crates/store/re_entity_db/src/rrd_manifest_index.rs
+++ b/crates/store/re_entity_db/src/rrd_manifest_index.rs
@@ -453,7 +453,7 @@ impl RrdManifestIndex {
                     self.mark_roots_as(store, &del.chunk.id(), LoadState::Unloaded);
                 }
 
-                ChunkStoreDiff::VirtualAddition(_) => {}
+                ChunkStoreDiff::VirtualAddition(_) | ChunkStoreDiff::SchemaAddition(_) => {}
             }
         }
     }
diff --git a/crates/store/re_query/src/cache.rs b/crates/store/re_query/src/cache.rs
index f5df4c66b050..806803e82a62 100644
--- a/crates/store/re_query/src/cache.rs
+++ b/crates/store/re_query/src/cache.rs
@@ -551,6 +551,7 @@ impl ChunkStoreSubscriber for QueryCache {
                         }
                     }
                 }
+                ChunkStoreDiff::SchemaAddition(_) => {} // Nothing to do here.
             }
         }
 
diff --git a/crates/store/re_query/src/latest_at.rs b/crates/store/re_query/src/latest_at.rs
index 6c468a254121..825b7b88b042 100644
--- a/crates/store/re_query/src/latest_at.rs
+++ b/crates/store/re_query/src/latest_at.rs
@@ -1205,7 +1205,7 @@ mod tests {
         let mut cache = QueryCache::new(store.clone());
 
         // The store is now aware that there is a virtual tombstone pending somewhere, and so should be the cache.
-        cache.on_events(&[store.write().insert_rrd_manifest(rrd_manifest)]);
+        cache.on_events(&store.write().insert_rrd_manifest(rrd_manifest));
 
         // Load the physical data into the store, but not the tombstone.
         cache.on_events(
diff --git a/crates/store/re_tf/src/transform_resolution_cache/cache.rs b/crates/store/re_tf/src/transform_resolution_cache/cache.rs
index 5ee35927775e..345acbe4496f 100644
--- a/crates/store/re_tf/src/transform_resolution_cache/cache.rs
+++ b/crates/store/re_tf/src/transform_resolution_cache/cache.rs
@@ -234,6 +234,8 @@ impl TransformResolutionCache {
                         self.remove_chunk(&deletion.chunk, aspects);
                     }
                 }
+
+                re_chunk_store::ChunkStoreDiff::SchemaAddition(_) => {}
             }
         }
     }
diff --git a/crates/viewer/re_time_panel/src/recursive_chunks_per_timeline_subscriber.rs b/crates/viewer/re_time_panel/src/recursive_chunks_per_timeline_subscriber.rs
index a327ce51b95b..7ae1931c75b7 100644
--- a/crates/viewer/re_time_panel/src/recursive_chunks_per_timeline_subscriber.rs
+++ b/crates/viewer/re_time_panel/src/recursive_chunks_per_timeline_subscriber.rs
@@ -191,7 +191,7 @@ impl PerStoreChunkSubscriber for PathRecursiveChunksPerTimelineStoreSubscriber {
                     self.remove_chunk(&del.chunk);
                 }
 
-                ChunkStoreDiff::VirtualAddition(_) => {}
+                ChunkStoreDiff::VirtualAddition(_) | ChunkStoreDiff::SchemaAddition(_) => {}
             }
         }
     }
diff --git a/crates/viewer/re_viewer/src/app.rs b/crates/viewer/re_viewer/src/app.rs
index 1baee20e817b..ecf1374e5319 100644
--- a/crates/viewer/re_viewer/src/app.rs
+++ b/crates/viewer/re_viewer/src/app.rs
@@ -2501,12 +2501,12 @@ impl App {
             match msg {
                 DataSourceMessage::RrdManifest(store_id, rrd_manifest) => {
                     let entity_db = store_hub.entity_db_entry(&store_id);
-                    let store_event = entity_db.add_rrd_manifest_message(rrd_manifest);
+                    let store_events = entity_db.add_rrd_manifest_message(rrd_manifest);
 
                     if let Some((entity_db, cache)) =
                         store_hub.entity_db_and_cache(&store_id, &self.view_class_registry)
                     {
-                        cache.on_store_events(&[store_event], entity_db);
+                        cache.on_store_events(&store_events, entity_db);
                     }
                 }
 
diff --git a/crates/viewer/re_viewer_context/src/cache/video_stream_cache.rs b/crates/viewer/re_viewer_context/src/cache/video_stream_cache.rs
index b4fb00ee89c7..59394dd777dd 100644
--- a/crates/viewer/re_viewer_context/src/cache/video_stream_cache.rs
+++ b/crates/viewer/re_viewer_context/src/cache/video_stream_cache.rs
@@ -276,7 +276,7 @@ impl VideoStreamCache {
                 handle_deletion(entity_db, timeline, video_data, &del.chunk, known_ranges)
             }
 
-            ChunkStoreDiff::VirtualAddition(_) => Ok(()),
+            ChunkStoreDiff::VirtualAddition(_) | ChunkStoreDiff::SchemaAddition(_) => Ok(()),
         };
 
         let encoding_details_changed = encoding_details_before != video_data.encoding_details;
diff --git a/crates/viewer/re_viewer_context/src/view/visualizer_entity_subscriber.rs b/crates/viewer/re_viewer_context/src/view/visualizer_entity_subscriber.rs
index 1692ea1b0a96..6445d12543b6 100644
--- a/crates/viewer/re_viewer_context/src/view/visualizer_entity_subscriber.rs
+++ b/crates/viewer/re_viewer_context/src/view/visualizer_entity_subscriber.rs
@@ -232,7 +232,7 @@ fn process_entity_components(
     re_chunk_store::ChunkMeta {
         entity_path,
         components,
-    }: re_chunk_store::ChunkMeta,
+    }: &re_chunk_store::ChunkMeta,
 ) {
     let VisualizerEntityConfig {
         relevant_archetype,
@@ -287,7 +287,7 @@ fn process_entity_components(
             let mut has_any_datatype = false;
 
             for c in components {
-                if !constraint.allow_static_data() && c.is_static_only {
+                if !constraint.allow_static_data() && c.is_static {
                     continue;
                 }
 
@@ -305,7 +305,7 @@ fn process_entity_components(
                     has_any_datatype = true;
 
                     store_mapping.add_visualizability_reason(
-                        &entity_path,
+                        entity_path,
                         visualizer,
                         VisualizableReason::SingleRequiredComponentMatch(
                             SingleRequiredComponentMatch {
@@ -359,7 +359,7 @@ fn process_entity_components(
                     let buffer_matches = state.all_buffer_matches.clone();
                     let format_components = state.all_formats_matches.clone();
                     store_mapping.add_visualizability_reason(
-                        &entity_path,
+                        entity_path,
                         visualizer,
                         VisualizableReason::BufferAndFormatMatch(BufferAndFormatMatch {
                             buffer_target: constraint.buffer_target(),
@@ -387,14 +387,14 @@ impl VisualizerEntitySubscriber {
         // Process manifest (virtual additions).
         if let Some(manifest) = entity_db.rrd_manifest_index().manifest() {
             for meta in re_chunk_store::ChunkMeta::from_manifest(manifest) {
-                process_entity_components(&self.config, &mut self.mapping, &store_id, meta);
+                process_entity_components(&self.config, &mut self.mapping, &store_id, &meta);
             }
         }
 
         // Process existing physical chunks.
         for chunk in store.iter_physical_chunks() {
             let meta = re_chunk_store::ChunkMeta::from_chunk(chunk);
-            process_entity_components(&self.config, &mut self.mapping, &store_id, meta);
+            process_entity_components(&self.config, &mut self.mapping, &store_id, &meta);
         }
     }
 
@@ -408,20 +408,8 @@ impl VisualizerEntitySubscriber {
 
         for event in events {
             match &event.diff {
-                re_chunk_store::ChunkStoreDiff::Addition(add) => {
-                    // This is a purely additive datastructure, and it doesn't keep track of actual chunks,
-                    // just the bits of data that are of actual interest.
-                    // Therefore, the meta of the delta chunk is all we need, always.
-
-                    process_entity_components(
-                        &self.config,
-                        &mut self.mapping,
-                        &event.store_id,
-                        add.chunk_meta(),
-                    );
-                }
-                re_chunk_store::ChunkStoreDiff::VirtualAddition(virtual_add) => {
-                    for meta in virtual_add.chunk_metas() {
+                re_chunk_store::ChunkStoreDiff::SchemaAddition(schema_add) => {
+                    for meta in &schema_add.new_columns {
                         process_entity_components(
                             &self.config,
                             &mut self.mapping,
@@ -430,8 +418,12 @@ impl VisualizerEntitySubscriber {
                         );
                     }
                 }
-                re_chunk_store::ChunkStoreDiff::Deletion(_) => {
-                    // Not handling deletions here yet.
+                re_chunk_store::ChunkStoreDiff::Addition(..)
+                | re_chunk_store::ChunkStoreDiff::Deletion(_)
+                | re_chunk_store::ChunkStoreDiff::VirtualAddition(..) => {
+                    // Our heuristics are additive and only change when new data is added to the store.
+                    // For now we don't look at the component data to inform the recommendations for
+                    // visualizers and view spawning, so looking at additions to the schema is enough.
                 }
             }
         }
@@ -442,13 +434,9 @@ impl VisualizerEntitySubscriber {
 mod tests {
     use super::*;
     use crate::BufferAndFormatConstraint;
-    use arrow::array::ArrayRef;
-    use re_chunk::{Chunk, RowId};
     use re_chunk_store::{
-        ChunkDirectLineageReport, ChunkStoreDiff, ChunkStoreDiffAddition, ChunkStoreEvent,
-        ChunkStoreGeneration,
+        ChunkStoreDiff, ChunkStoreDiffSchemaAddition, ChunkStoreEvent, ChunkStoreGeneration,
     };
-    use re_log_types::TimePoint;
     use re_sdk_types::ComponentDescriptor;
 
     const BUFFER_CTYPE: &str = "test.components.Buffer";
@@ -492,36 +480,30 @@ mod tests {
         }
     }
 
-    /// Build a minimal chunk with the given entity path and component columns.
-    ///
-    /// Each entry is `(descriptor, arrow_datatype)` — a single-element array of the
-    /// given type is created as data so that `has_data` is `true`.
-    fn make_chunk(
+    /// Build a `ChunkStoreEvent` for a schema addition with only the specified columns.
+    fn schema_addition_event(
+        store_id: &StoreId,
         entity: &EntityPath,
         columns: &[(ComponentDescriptor, arrow::datatypes::DataType)],
-    ) -> Arc {
-        let row = columns.iter().map(|(desc, dt)| {
-            let array: ArrayRef = arrow::array::new_null_array(dt, 1);
-            (desc.clone(), array)
-        });
-        Arc::new(
-            Chunk::builder(entity.clone())
-                .with_row(RowId::new(), TimePoint::default(), row)
-                .build()
-                .expect("failed to build test chunk"),
-        )
-    }
-
-    /// Wrap a chunk into a single `ChunkStoreEvent` (addition).
-    fn addition_event(store_id: &StoreId, chunk: Arc) -> ChunkStoreEvent {
+    ) -> ChunkStoreEvent {
+        let meta = re_chunk_store::ChunkMeta {
+            entity_path: entity.clone(),
+            components: columns
+                .iter()
+                .map(|(desc, dt)| re_chunk_store::ChunkComponentMeta {
+                    descriptor: desc.clone(),
+                    inner_arrow_datatype: Some(dt.clone()),
+                    has_data: true,
+                    is_static: false,
+                })
+                .collect(),
+        };
         ChunkStoreEvent {
             store_id: store_id.clone(),
             store_generation: ChunkStoreGeneration::default(),
             event_id: 0,
-            diff: ChunkStoreDiff::Addition(ChunkStoreDiffAddition {
-                chunk_before_processing: Arc::clone(&chunk),
-                chunk_after_processing: chunk,
-                direct_lineage: ChunkDirectLineageReport::Volatile,
+            diff: ChunkStoreDiff::SchemaAddition(ChunkStoreDiffSchemaAddition {
+                new_columns: vec![meta],
             }),
         }
     }
@@ -557,21 +539,18 @@ mod tests {
         let entity: EntityPath = "/test/entity".into();
         let mut sub = test_subscriber_with_buffer_and_format_constraint();
 
-        let chunk = make_chunk(
-            &entity,
-            &[
-                (
-                    descriptor("buf", Some(BUFFER_CTYPE)),
-                    BufferAndFormatConstraint::buffer_arrow_datatype(),
-                ),
-                (
-                    descriptor("fmt", Some(FORMAT_CTYPE)),
-                    arrow::datatypes::DataType::UInt32,
-                ),
-            ],
-        );
+        let columns = [
+            (
+                descriptor("buf", Some(BUFFER_CTYPE)),
+                BufferAndFormatConstraint::buffer_arrow_datatype(),
+            ),
+            (
+                descriptor("fmt", Some(FORMAT_CTYPE)),
+                arrow::datatypes::DataType::UInt32,
+            ),
+        ];
 
-        sub.on_events(&[addition_event(&store_id, chunk)]);
+        sub.on_events(&[schema_addition_event(&store_id, &entity, &columns)]);
 
         let m = expect_buffer_and_format_visualizable(&sub, &entity);
         assert_eq!(m.buffer_matches.len(), 1);
@@ -587,31 +566,25 @@ mod tests {
         let store_id = test_store_id();
         let entity: EntityPath = "/test/entity".into();
 
-        let buffer_chunk = make_chunk(
-            &entity,
-            &[(
-                descriptor("buf", Some(BUFFER_CTYPE)),
-                BufferAndFormatConstraint::buffer_arrow_datatype(),
-            )],
-        );
-        let format_chunk = make_chunk(
-            &entity,
-            &[(
-                descriptor("fmt", Some(FORMAT_CTYPE)),
-                arrow::datatypes::DataType::UInt32,
-            )],
-        );
+        let buffer_columns = [(
+            descriptor("buf", Some(BUFFER_CTYPE)),
+            BufferAndFormatConstraint::buffer_arrow_datatype(),
+        )];
+        let format_columns = [(
+            descriptor("fmt", Some(FORMAT_CTYPE)),
+            arrow::datatypes::DataType::UInt32,
+        )];
 
-        for (first_chunk, second_chunk) in [
-            (buffer_chunk.clone(), format_chunk.clone()),
-            (format_chunk.clone(), buffer_chunk.clone()),
+        for (first, second) in [
+            (&buffer_columns[..], &format_columns[..]),
+            (&format_columns[..], &buffer_columns[..]),
         ] {
             let mut sub = test_subscriber_with_buffer_and_format_constraint();
 
-            sub.on_events(&[addition_event(&store_id, first_chunk)]);
+            sub.on_events(&[schema_addition_event(&store_id, &entity, first)]);
             assert_not_visualizable(&sub, &entity);
 
-            sub.on_events(&[addition_event(&store_id, second_chunk)]);
+            sub.on_events(&[schema_addition_event(&store_id, &entity, second)]);
             expect_buffer_and_format_visualizable(&sub, &entity);
         }
     }
@@ -623,20 +596,17 @@ mod tests {
         let mut sub = test_subscriber_with_buffer_and_format_constraint();
 
         // Buffer has the right arrow type but wrong semantic type → PhysicalDatatypeOnly.
-        let chunk = make_chunk(
-            &entity,
-            &[
-                (
-                    descriptor("buf", Some("other.components.Blob")),
-                    BufferAndFormatConstraint::buffer_arrow_datatype(),
-                ),
-                (
-                    descriptor("fmt", Some(FORMAT_CTYPE)),
-                    arrow::datatypes::DataType::UInt32,
-                ),
-            ],
-        );
-        sub.on_events(&[addition_event(&store_id, chunk)]);
+        let columns = [
+            (
+                descriptor("buf", Some("other.components.Blob")),
+                BufferAndFormatConstraint::buffer_arrow_datatype(),
+            ),
+            (
+                descriptor("fmt", Some(FORMAT_CTYPE)),
+                arrow::datatypes::DataType::UInt32,
+            ),
+        ];
+        sub.on_events(&[schema_addition_event(&store_id, &entity, &columns)]);
 
         let m = expect_buffer_and_format_visualizable(&sub, &entity);
         assert!(matches!(
@@ -652,20 +622,17 @@ mod tests {
         let mut sub = test_subscriber_with_buffer_and_format_constraint();
 
         // Buffer matches, but format has wrong semantic type.
-        let chunk = make_chunk(
-            &entity,
-            &[
-                (
-                    descriptor("buf", Some(BUFFER_CTYPE)),
-                    BufferAndFormatConstraint::buffer_arrow_datatype(),
-                ),
-                (
-                    descriptor("fmt", Some("wrong.components.Format")),
-                    arrow::datatypes::DataType::UInt32,
-                ),
-            ],
-        );
-        sub.on_events(&[addition_event(&store_id, chunk)]);
+        let columns = [
+            (
+                descriptor("buf", Some(BUFFER_CTYPE)),
+                BufferAndFormatConstraint::buffer_arrow_datatype(),
+            ),
+            (
+                descriptor("fmt", Some("wrong.components.Format")),
+                arrow::datatypes::DataType::UInt32,
+            ),
+        ];
+        sub.on_events(&[schema_addition_event(&store_id, &entity, &columns)]);
         assert_not_visualizable(&sub, &entity);
     }
 
@@ -676,20 +643,17 @@ mod tests {
         let mut sub = test_subscriber_with_buffer_and_format_constraint();
 
         // Neither buffer nor format arrow types match.
-        let chunk = make_chunk(
-            &entity,
-            &[
-                (
-                    descriptor("buf", Some(BUFFER_CTYPE)),
-                    arrow::datatypes::DataType::Float64,
-                ),
-                (
-                    descriptor("fmt", Some(FORMAT_CTYPE)),
-                    arrow::datatypes::DataType::Float64,
-                ),
-            ],
-        );
-        sub.on_events(&[addition_event(&store_id, chunk)]);
+        let columns = [
+            (
+                descriptor("buf", Some(BUFFER_CTYPE)),
+                arrow::datatypes::DataType::Float64,
+            ),
+            (
+                descriptor("fmt", Some(FORMAT_CTYPE)),
+                arrow::datatypes::DataType::Float64,
+            ),
+        ];
+        sub.on_events(&[schema_addition_event(&store_id, &entity, &columns)]);
         assert_not_visualizable(&sub, &entity);
     }
 
@@ -700,31 +664,25 @@ mod tests {
         let mut sub = test_subscriber_with_buffer_and_format_constraint();
 
         // Event 1: first buffer + format.
-        let chunk1 = make_chunk(
-            &entity,
-            &[
-                (
-                    descriptor("buf1", Some(BUFFER_CTYPE)),
-                    BufferAndFormatConstraint::buffer_arrow_datatype(),
-                ),
-                (
-                    descriptor("fmt", Some(FORMAT_CTYPE)),
-                    arrow::datatypes::DataType::UInt32,
-                ),
-            ],
-        );
-        sub.on_events(&[addition_event(&store_id, chunk1)]);
+        let columns1 = [
+            (
+                descriptor("buf1", Some(BUFFER_CTYPE)),
+                BufferAndFormatConstraint::buffer_arrow_datatype(),
+            ),
+            (
+                descriptor("fmt", Some(FORMAT_CTYPE)),
+                arrow::datatypes::DataType::UInt32,
+            ),
+        ];
+        sub.on_events(&[schema_addition_event(&store_id, &entity, &columns1)]);
         expect_buffer_and_format_visualizable(&sub, &entity);
 
         // Event 2: second buffer arrives.
-        let chunk2 = make_chunk(
-            &entity,
-            &[(
-                descriptor("buf2", Some("other.components.Blob")),
-                BufferAndFormatConstraint::buffer_arrow_datatype(),
-            )],
-        );
-        sub.on_events(&[addition_event(&store_id, chunk2)]);
+        let columns2 = [(
+            descriptor("buf2", Some("other.components.Blob")),
+            BufferAndFormatConstraint::buffer_arrow_datatype(),
+        )];
+        sub.on_events(&[schema_addition_event(&store_id, &entity, &columns2)]);
 
         // Both buffer matches should be visible.
         let m = expect_buffer_and_format_visualizable(&sub, &entity);
@@ -746,40 +704,34 @@ mod tests {
         let mut sub = test_subscriber_with_buffer_and_format_constraint();
 
         // Event 1: first buffer + first format.
-        let chunk1 = make_chunk(
-            &entity,
-            &[
-                (
-                    descriptor("buf1", Some(BUFFER_CTYPE)),
-                    BufferAndFormatConstraint::buffer_arrow_datatype(),
-                ),
-                (
-                    descriptor("fmt1", Some(FORMAT_CTYPE)),
-                    arrow::datatypes::DataType::UInt32,
-                ),
-            ],
-        );
-        sub.on_events(&[addition_event(&store_id, chunk1)]);
+        let columns1 = [
+            (
+                descriptor("buf1", Some(BUFFER_CTYPE)),
+                BufferAndFormatConstraint::buffer_arrow_datatype(),
+            ),
+            (
+                descriptor("fmt1", Some(FORMAT_CTYPE)),
+                arrow::datatypes::DataType::UInt32,
+            ),
+        ];
+        sub.on_events(&[schema_addition_event(&store_id, &entity, &columns1)]);
 
         let m = expect_buffer_and_format_visualizable(&sub, &entity);
         assert_eq!(m.buffer_matches.len(), 1);
         assert_eq!(m.format_matches.len(), 1);
 
         // Event 2: second buffer + second format.
-        let chunk2 = make_chunk(
-            &entity,
-            &[
-                (
-                    descriptor("buf2", Some("other.components.Blob")),
-                    BufferAndFormatConstraint::buffer_arrow_datatype(),
-                ),
-                (
-                    descriptor("fmt2", Some(FORMAT_CTYPE)),
-                    arrow::datatypes::DataType::UInt32,
-                ),
-            ],
-        );
-        sub.on_events(&[addition_event(&store_id, chunk2)]);
+        let columns2 = [
+            (
+                descriptor("buf2", Some("other.components.Blob")),
+                BufferAndFormatConstraint::buffer_arrow_datatype(),
+            ),
+            (
+                descriptor("fmt2", Some(FORMAT_CTYPE)),
+                arrow::datatypes::DataType::UInt32,
+            ),
+        ];
+        sub.on_events(&[schema_addition_event(&store_id, &entity, &columns2)]);
 
         // Both buffers and both formats should be tracked in a single entry.
         let m = expect_buffer_and_format_visualizable(&sub, &entity);
@@ -825,17 +777,14 @@ mod tests {
             .into(),
         );
 
-        let chunk = make_chunk(
-            &entity,
-            &[
-                (descriptor("data", None), struct_dt),
-                (
-                    descriptor("fmt", Some(FORMAT_CTYPE)),
-                    arrow::datatypes::DataType::UInt32,
-                ),
-            ],
-        );
-        sub.on_events(&[addition_event(&store_id, chunk)]);
+        let columns = [
+            (descriptor("data", None), struct_dt),
+            (
+                descriptor("fmt", Some(FORMAT_CTYPE)),
+                arrow::datatypes::DataType::UInt32,
+            ),
+        ];
+        sub.on_events(&[schema_addition_event(&store_id, &entity, &columns)]);
 
         let m = expect_buffer_and_format_visualizable(&sub, &entity);
         let data_match = m

From 299969cddc89bcbecde2ea184ab0f47e6fb849ae Mon Sep 17 00:00:00 2001
From: Emil Ernerfeldt 
Date: Tue, 17 Mar 2026 15:07:12 +0100
Subject: [PATCH 160/513] Fix slow resizing of table columns

* Closes
https://linear.app/rerun/issue/RR-4079/improve-resize-speed-for-egui-table-due-to-limitations
* Uses https://github.com/rerun-io/egui_table/pull/53

Source-Ref: c5017f8f3d41f279e5ec7383286dfe99beb188c7
---
 Cargo.lock | 3 +--
 Cargo.toml | 2 +-
 2 files changed, 2 insertions(+), 3 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index b82588fee96a..894d61e5dc34 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3370,8 +3370,7 @@ dependencies = [
 [[package]]
 name = "egui_table"
 version = "0.7.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6f2cf21af68301c187bfd9c89a35f13bf2cbbcc78587f6d7ba3c5b36259337ba"
+source = "git+https://github.com/rerun-io/egui_table?branch=main#d1c33b24af4b273f300120b6107a0e099c30ae69"
 dependencies = [
  "egui",
  "serde",
diff --git a/Cargo.toml b/Cargo.toml
index c2a176633767..c393fc4b962c 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -889,7 +889,7 @@ emath = { git = "https://github.com/emilk/egui.git", branch = "main" }
 # egui_tiles = { git = "https://github.com/rerun-io/egui_tiles", branch = "emilk/update-egui" }
 # egui_tiles = { path = "../egui_tiles" }
 
-# egui_table = { git = "https://github.com/rerun-io/egui_table", branch = "main" }
+egui_table = { git = "https://github.com/rerun-io/egui_table", branch = "main" }
 # egui_table = { path = "../egui_table" }
 
 # egui_dnd = { git = "https://github.com/rerun-io/hello_egui.git", branch = "emilk/egui-0.33.0" }

From 5d74d6bc4875ef6273ebeb930cebf043e03371cd Mon Sep 17 00:00:00 2001
From: Antoine Beyeler <49431240+abey79@users.noreply.github.com>
Date: Tue, 17 Mar 2026 15:07:57 +0100
Subject: [PATCH 161/513] Fix typoOo

Title

Source-Ref: 174d538a49027983ac3ce8eb27cf5f2c8736bda6
---
 crates/store/re_entity_db/src/rrd_manifest_index.rs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/crates/store/re_entity_db/src/rrd_manifest_index.rs b/crates/store/re_entity_db/src/rrd_manifest_index.rs
index 6cf747036268..a4450fc1e659 100644
--- a/crates/store/re_entity_db/src/rrd_manifest_index.rs
+++ b/crates/store/re_entity_db/src/rrd_manifest_index.rs
@@ -411,7 +411,7 @@ impl RrdManifestIndex {
         let num_root_chunks = self.root_chunks.len();
         if 10_000 < num_root_chunks {
             re_log::warn!(
-                "There are {} rooot chunks in this recording. Consider running `rerun rrd compact` on the original.",
+                "There are {} root chunks in this recording. Consider running `rerun rrd compact` on the original.",
                 re_format::format_uint(num_root_chunks)
             );
         }

From f6e73a843ed6e657fe6f530f48c9b078aff256df Mon Sep 17 00:00:00 2001
From: Isse 
Date: Tue, 17 Mar 2026 14:31:16 +0100
Subject: [PATCH 162/513] Clear dead undo reflection points

### Related

* Closes RR-4068

### What

Undo could panic in debug mode because of undo inflection points and
their associated frame id not increasing monotonically.

Source-Ref: c440116ce627001debfa563ec7d6d0678c1cc448
---
 crates/viewer/re_viewer_context/src/undo.rs | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/crates/viewer/re_viewer_context/src/undo.rs b/crates/viewer/re_viewer_context/src/undo.rs
index 6ef0ea9c3cb3..6e1a480e0cc1 100644
--- a/crates/viewer/re_viewer_context/src/undo.rs
+++ b/crates/viewer/re_viewer_context/src/undo.rs
@@ -133,6 +133,11 @@ impl BlueprintUndoState {
                 AbsoluteTimeRange::new(first_dropped_event_time, re_chunk::TimeInt::MAX),
             );
 
+            // Also remove inflection points after the current time,
+            // so they don't stick around with stale frame numbers.
+            self.inflection_points
+                .split_off(&last_kept_event_time.inc());
+
             re_log::trace!("{} chunks affected when clearing redo buffer", events.len());
         }
     }
@@ -157,6 +162,12 @@ impl BlueprintUndoState {
 
         // Nothing is happening - remember this as a time to undo to.
         let time = max_blueprint_time(blueprint_db);
+
+        // Blueprint GC can remove data, causing max_blueprint_time to decrease.
+        // Remove any stale inflection points above the current max time,
+        // since that data no longer exists in the store.
+        self.inflection_points.split_off(&time.inc());
+
         let inserted = self.inflection_points.insert(time, frame_nr).is_none();
         if inserted {
             re_log::trace!("Inserted new inflection point at {time:?}");

From 9c7511c2595a5c3fb0f345d1683526f45cdfb39e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?G=C3=A1bor=20Gyebn=C3=A1r?= 
Date: Tue, 17 Mar 2026 14:40:58 +0100
Subject: [PATCH 163/513] Disables `cli_command_invoked` event on startup for
 conversion commands
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

### Related

Closes RR-4040

### What

`cli_command_invoked` analítics event won't be sent for conversion
commands once the CLI starts.

Source-Ref: 74224cdea392714dd4d511228f77ecd53d5ef4c6
---
 crates/top/rerun/src/commands/entrypoint.rs | 26 +++++----------------
 1 file changed, 6 insertions(+), 20 deletions(-)

diff --git a/crates/top/rerun/src/commands/entrypoint.rs b/crates/top/rerun/src/commands/entrypoint.rs
index c8c275cfeaa1..cffdae9fdc10 100644
--- a/crates/top/rerun/src/commands/entrypoint.rs
+++ b/crates/top/rerun/src/commands/entrypoint.rs
@@ -664,7 +664,6 @@ where
 
     use clap::Parser as _;
     let mut args = Args::parse_from(args);
-
     #[cfg(feature = "analytics")]
     record_cli_command_analytics(&args);
 
@@ -1754,11 +1753,9 @@ fn record_cli_command_analytics(args: &Args) {
         Some(Command::Manual) => ("man", None),
 
         #[cfg(feature = "data_loaders")]
-        Some(Command::Mcap(cmd)) => {
-            let subcommand = match cmd {
-                McapCommands::Convert(_) => "convert",
-            };
-            ("mcap", Some(subcommand))
+        Some(Command::Mcap(_cmd)) => {
+            // TODO(RR-4073): Re-enable analytics for MCAP commands.
+            return;
         }
 
         Some(Command::Download(_)) => ("download", None),
@@ -1766,20 +1763,9 @@ fn record_cli_command_analytics(args: &Args) {
         #[cfg(feature = "native_viewer")]
         Some(Command::Reset) => ("reset", None),
 
-        Some(Command::Rrd(cmd)) => {
-            let subcommand = match cmd {
-                RrdCommands::Compact(_) => "compact",
-                RrdCommands::Compare(_) => "compare",
-                RrdCommands::Filter(_) => "filter",
-                RrdCommands::Split(_) => "split",
-                RrdCommands::Merge(_) => "merge",
-                RrdCommands::Migrate(_) => "migrate",
-                RrdCommands::Print(_) => "print",
-                RrdCommands::Route(_) => "route",
-                RrdCommands::Stats(_) => "stats",
-                RrdCommands::Verify(_) => "verify",
-            };
-            ("rrd", Some(subcommand))
+        Some(Command::Rrd(_cmd)) => {
+            // TODO(RR-4073): Re-enable analytics for RRD commands.
+            return;
         }
 
         #[cfg(feature = "oss_server")]

From 2f6d88f6f223ff26b4fe53eb68e61c7b99093dd6 Mon Sep 17 00:00:00 2001
From: Andreas Reich 
Date: Tue, 17 Mar 2026 15:51:06 +0100
Subject: [PATCH 164/513] Limit amount of heuristic lineseries from arbitrary
 sources

Limits spam of visualizers on some scenes a bit (this isn't a perfect
fix, more of a mitigation).
We still show all the indicated components (i.e. scalars with Rerun
semantics), but limit the amount of other things.

We do so without cutting down on recommendations, but instead
distinguishing explicitly what we want to spawn and what we want to
"just" recommend. Everywhere outside the time series view this is the
same for now.

---------

Source-Ref: 04c91abc99768a400e61797884d7f2ae25190786
Co-authored-by: Claude Opus 4.6 
---
 .../re_selection_panel/src/selection_panel.rs | 14 ++--
 .../re_selection_panel/src/visualizer_ui.rs   | 17 ++--
 crates/viewer/re_view_time_series/src/lib.rs  |  1 +
 .../re_view_time_series/src/view_class.rs     | 44 +++++++---
 .../tests/automatic_mapping.rs                | 68 +++++++++++++++
 .../re_viewer_context/src/view/view_class.rs  | 82 +++++++++++++++++--
 .../re_viewer_context/src/view/view_query.rs  |  4 +-
 .../src/view_contents.rs                      |  2 +-
 8 files changed, 197 insertions(+), 35 deletions(-)

diff --git a/crates/viewer/re_selection_panel/src/selection_panel.rs b/crates/viewer/re_selection_panel/src/selection_panel.rs
index 69bc6e8ef1f7..de0a6a06547b 100644
--- a/crates/viewer/re_selection_panel/src/selection_panel.rs
+++ b/crates/viewer/re_selection_panel/src/selection_panel.rs
@@ -641,7 +641,7 @@ fn build_add_visualizer_menu_options(
         };
 
         let options =
-            collect_add_visualizer_options_for_entity(data_result, recommended_visualizers);
+            collect_add_visualizer_options_for_entity(data_result, &recommended_visualizers);
         if !options.is_empty() {
             result.push(AddVisualizerOption {
                 entity_path,
@@ -655,12 +655,14 @@ fn build_add_visualizer_menu_options(
 
 fn collect_add_visualizer_options_for_entity(
     data_result: &DataResult,
-    recommended_visualizers: RecommendedVisualizers,
+    recommended_visualizers: &RecommendedVisualizers,
 ) -> Vec {
     let existing_visualizers = &data_result.visualizer_instructions;
 
     let mut visualizer_options = vec![];
-    for (view_system_id, recommended_mappings_per_view_system) in recommended_visualizers.0 {
+    for (view_system_id, recommended_mappings_per_view_system) in
+        recommended_visualizers.all_recommendations()
+    {
         for recommended_mappings in recommended_mappings_per_view_system {
             let Some(display_name) = recommended_mappings.display_name() else {
                 continue;
@@ -668,13 +670,13 @@ fn collect_add_visualizer_options_for_entity(
 
             // Check if there is already a visualizer that covers this recommendation.
             let is_already_visualized = existing_visualizers.iter().any(|visualizer| {
-                visualizer.visualizer_type == view_system_id
+                visualizer.visualizer_type == *view_system_id
                     && recommended_mappings.is_covered_by(&visualizer.component_mappings)
             });
 
             visualizer_options.push(AddVisualizerOptionPerEntity {
-                view_system_id,
-                recommended_mappings,
+                view_system_id: *view_system_id,
+                recommended_mappings: recommended_mappings.clone(),
                 display_name,
                 is_already_visualized,
             });
diff --git a/crates/viewer/re_selection_panel/src/visualizer_ui.rs b/crates/viewer/re_selection_panel/src/visualizer_ui.rs
index 621a83d877d9..2c7d09399929 100644
--- a/crates/viewer/re_selection_panel/src/visualizer_ui.rs
+++ b/crates/viewer/re_selection_panel/src/visualizer_ui.rs
@@ -777,7 +777,7 @@ fn extract_recommended_source_options(
         viewer_ctx.indicated_entities_per_visualizer,
     );
     if let Some(recommended_mappings) = recommended_visualizers
-        .0
+        .all_recommendations()
         .get(&mapping_ctx.instruction.visualizer_type)
     {
         let recommended: Vec<_> = recommended_mappings
@@ -1025,10 +1025,12 @@ fn menu_add_new_visualizer(
         ctx.viewer_ctx.indicated_entities_per_visualizer,
     );
 
-    let (recommended, other): (Vec<_>, Vec<_>) = available_visualizers
-        .iter()
-        .copied()
-        .partition(|vis| recommended_visualizers.0.contains_key(vis));
+    let (recommended, other): (Vec<_>, Vec<_>) =
+        available_visualizers.iter().copied().partition(|vis| {
+            recommended_visualizers
+                .all_recommendations()
+                .contains_key(vis)
+        });
 
     // Don't show categorization if either group is empty.
     let show_sections = !recommended.is_empty() && !other.is_empty();
@@ -1142,7 +1144,10 @@ fn component_mappings_for_new_visualizer(
         &entity_visualizers,
         ctx.viewer_ctx.indicated_entities_per_visualizer,
     );
-    let component_mapping_recommendations = recommended_visualizers.0.get(visualizer_type).cloned();
+    let component_mapping_recommendations = recommended_visualizers
+        .all_recommendations()
+        .get(visualizer_type)
+        .cloned();
 
     // Chain in all possible mappings.
     let visualizable_reason = entity_visualizers
diff --git a/crates/viewer/re_view_time_series/src/lib.rs b/crates/viewer/re_view_time_series/src/lib.rs
index 4de423263d50..7d684da6c6f3 100644
--- a/crates/viewer/re_view_time_series/src/lib.rs
+++ b/crates/viewer/re_view_time_series/src/lib.rs
@@ -27,6 +27,7 @@ pub use view_class::TimeSeriesView;
 /// This limit is NOT applied when the scalar component has an identity mapping,
 /// since in that case the user explicitly logged `Scalars` data and knows how many series to expect.
 pub(crate) const MAX_NUM_SERIES_FOR_REMAPPED_SCALARS: usize = 100;
+pub const MAX_NUM_NON_INDICATED_RECOMMENDED_VISUALIZERS_PER_ENTITY: usize = 4;
 pub(crate) const MAX_NUM_ITEMS_IN_PLOT_LEGEND_BEFORE_HIDDEN: usize = 20;
 
 /// Computes a deterministic, globally unique ID for the plot based on the ID of the view
diff --git a/crates/viewer/re_view_time_series/src/view_class.rs b/crates/viewer/re_view_time_series/src/view_class.rs
index 0775e259ffa0..df82ba2268b8 100644
--- a/crates/viewer/re_view_time_series/src/view_class.rs
+++ b/crates/viewer/re_view_time_series/src/view_class.rs
@@ -30,11 +30,11 @@ use re_viewport_blueprint::ViewProperty;
 use smallvec::SmallVec;
 use vec1::Vec1;
 
-use crate::PlotSeriesKind;
 use crate::line_visualizer_system::SeriesLinesSystem;
 use crate::naming::{SeriesInfo, SeriesNamesContext};
 use crate::point_visualizer_system::SeriesPointsSystem;
 use crate::util::data_result_time_range;
+use crate::{MAX_NUM_NON_INDICATED_RECOMMENDED_VISUALIZERS_PER_ENTITY, PlotSeriesKind};
 
 // ---
 
@@ -284,7 +284,7 @@ impl ViewClass for TimeSeriesView {
                 .map(|(visualizer, reason)| (*visualizer, *reason))
                 .collect();
 
-        let mut recommended = RecommendedVisualizers(
+        let mut recommended = RecommendedVisualizers::new(
             available_visualizers
                 .iter()
                 .filter_map(|(visualizer, reason)| {
@@ -308,18 +308,32 @@ impl ViewClass for TimeSeriesView {
         );
 
         // If there were no other visualizers, but the SeriesLineSystem is available, use it.
-        if recommended.0.is_empty()
+        if recommended.all_recommendations().is_empty()
             && let Some(series_line_visualizable_reason) =
                 available_visualizers.get(&SeriesLinesSystem::identifier())
         {
-            let all_mappings: Vec =
+            let mut mappings_with_auto_spawn: Vec =
                 all_scalar_mappings(series_line_visualizable_reason)
                     .map(|(component, source)| RecommendedMappings::new(component, source))
                     .collect();
-            if let Ok(mappings) = Vec1::try_from_vec(all_mappings) {
-                recommended
-                    .0
-                    .insert(SeriesLinesSystem::identifier(), mappings);
+
+            // Not all automatic recommendations should be spawned by default, since that can lead to
+            // a huge number of visualizers being spawned for a single entity.
+            let recommendation_only = if mappings_with_auto_spawn.len()
+                > MAX_NUM_NON_INDICATED_RECOMMENDED_VISUALIZERS_PER_ENTITY
+            {
+                mappings_with_auto_spawn
+                    .split_off(MAX_NUM_NON_INDICATED_RECOMMENDED_VISUALIZERS_PER_ENTITY)
+            } else {
+                Vec::new()
+            };
+
+            // First add mappings with auto spawn since they should show up higher in the list.
+            if let Ok(mappings) = Vec1::try_from_vec(mappings_with_auto_spawn) {
+                recommended.insert(SeriesLinesSystem::identifier(), mappings, true);
+            }
+            if let Ok(mappings) = Vec1::try_from_vec(recommendation_only) {
+                recommended.insert(SeriesLinesSystem::identifier(), mappings, false);
             }
         }
 
@@ -360,7 +374,9 @@ impl ViewClass for TimeSeriesView {
                 let recommended = if let Ok(mappings) =
                     vec1::Vec1::try_from_vec(all_scalar_mappings_for(matches))
                 {
-                    RecommendedVisualizers(std::iter::once((series_line_id, mappings)).collect())
+                    RecommendedVisualizers::new(
+                        std::iter::once((series_line_id, mappings)).collect(),
+                    )
                 } else {
                     return None;
                 };
@@ -1515,7 +1531,7 @@ mod tests {
             &indicated,
         );
 
-        assert!(result.0.is_empty());
+        assert!(result.all_recommendations().is_empty());
     }
 
     /// `SeriesLinesSystem` should be recommended when the datatype is a recommended one, even if not indicated.
@@ -1542,8 +1558,12 @@ mod tests {
             &indicated,
         );
 
-        assert!(result.0.contains_key(&SeriesLinesSystem::identifier()));
-        let mappings = &result.0[&SeriesLinesSystem::identifier()];
+        assert!(
+            result
+                .all_recommendations()
+                .contains_key(&SeriesLinesSystem::identifier())
+        );
+        let mappings = &result.all_recommendations()[&SeriesLinesSystem::identifier()];
         assert_eq!(mappings.len(), 1);
         assert!(
             mappings[0].contains_mapping_for_component(&Scalars::descriptor_scalars().component)
diff --git a/crates/viewer/re_view_time_series/tests/automatic_mapping.rs b/crates/viewer/re_view_time_series/tests/automatic_mapping.rs
index 6ca0b12c7101..b3d7c67246d5 100644
--- a/crates/viewer/re_view_time_series/tests/automatic_mapping.rs
+++ b/crates/viewer/re_view_time_series/tests/automatic_mapping.rs
@@ -328,6 +328,51 @@ fn setup_store(test_context: &mut TestContext) {
         });
     }
 
+    // Scenario 14: Entity with many custom Float64 components (more than the auto-spawn limit)
+    // Expected: Only the first MAX_NUM_NON_INDICATED_RECOMMENDED_VISUALIZERS_PER_ENTITY (4)
+    // should be auto-spawned as visualizer instructions. The rest are still recommended
+    // but not auto-spawned.
+    for i in 0..10 {
+        test_context.log_entity("entity_many_custom_floats", |builder| {
+            builder.with_archetype_auto_row(
+                [(timeline, i)],
+                &DynamicArchetype::new("custom")
+                    .with_component_from_data(
+                        "comp_a",
+                        Arc::new(Float64Array::from(vec![i as f64])),
+                    )
+                    .with_component_from_data(
+                        "comp_b",
+                        Arc::new(Float64Array::from(vec![i as f64 * 2.0])),
+                    )
+                    .with_component_from_data(
+                        "comp_c",
+                        Arc::new(Float64Array::from(vec![i as f64 * 3.0])),
+                    )
+                    .with_component_from_data(
+                        "comp_d",
+                        Arc::new(Float64Array::from(vec![i as f64 * 4.0])),
+                    )
+                    .with_component_from_data(
+                        "comp_e",
+                        Arc::new(Float64Array::from(vec![i as f64 * 5.0])),
+                    )
+                    .with_component_from_data(
+                        "comp_f",
+                        Arc::new(Float64Array::from(vec![i as f64 * 6.0])),
+                    )
+                    .with_component_from_data(
+                        "comp_g",
+                        Arc::new(Float64Array::from(vec![i as f64 * 7.0])),
+                    )
+                    .with_component_from_data(
+                        "comp_h",
+                        Arc::new(Float64Array::from(vec![i as f64 * 8.0])),
+                    ),
+            )
+        });
+    }
+
     test_context.set_active_timeline(*timeline.name());
 }
 
@@ -598,4 +643,27 @@ fn check_visualizer_instructions(test_context: &TestContext, view_id: ViewId) {
             "Expected selector `.a[].c` to access Float64 field (c) within list of structs; Uint32 field (b) is never recommended"
         );
     }
+
+    // Scenario 14: Entity with many custom Float64 components (8 total, auto-spawn limit is 4)
+    // Expected: Only the first 4 (by priority order) are auto-spawned as visualizer instructions.
+    // The entity has no indicated components, so it goes through the fallback path which
+    // limits auto-spawned visualizers to MAX_NUM_NON_INDICATED_RECOMMENDED_VISUALIZERS_PER_ENTITY.
+    {
+        let instructions = visualizers_for(data_result_tree, "entity_many_custom_floats");
+        assert_eq!(
+            instructions.len(),
+            re_view_time_series::MAX_NUM_NON_INDICATED_RECOMMENDED_VISUALIZERS_PER_ENTITY,
+            "Should auto-spawn only {} visualizers out of 8, got: {instructions:#?}",
+            re_view_time_series::MAX_NUM_NON_INDICATED_RECOMMENDED_VISUALIZERS_PER_ENTITY,
+        );
+
+        // All auto-spawned instructions should be SeriesLines visualizers with source component mappings.
+        for instruction in instructions {
+            let (component, _selector) = source_component_for(instruction);
+            assert!(
+                component.starts_with("custom:comp_"),
+                "Expected custom component mapping, got: {component}"
+            );
+        }
+    }
 }
diff --git a/crates/viewer/re_viewer_context/src/view/view_class.rs b/crates/viewer/re_viewer_context/src/view/view_class.rs
index cb49439555cf..dc9f3bde828a 100644
--- a/crates/viewer/re_viewer_context/src/view/view_class.rs
+++ b/crates/viewer/re_viewer_context/src/view/view_class.rs
@@ -1,5 +1,6 @@
 use std::collections::BTreeMap;
 
+use itertools::Itertools as _;
 use nohash_hasher::IntSet;
 use re_chunk_store::MissingChunkReporter;
 use re_log_types::EntityPath;
@@ -28,18 +29,35 @@ pub enum ViewClassLayoutPriority {
     High,
 }
 
-pub struct RecommendedVisualizers(
-    /// A _stable_ mapping for which visualizers can visualize which components (with optional selectors).
-    pub BTreeMap>,
-);
+/// Recommended visualizers for an entity, split into all recommendations and the subset to auto-spawn.
+///
+/// `auto_spawned` is always a subset of `all_recommendations`.
+/// By default, all recommendations are also auto-spawned.
+pub struct RecommendedVisualizers {
+    /// All recommended visualizers (used for UI purposes like "add visualizer" menus).
+    all_recommendations: BTreeMap>,
+
+    /// The subset of [`Self::all_recommendations`] that should be automatically spawned.
+    auto_spawned: BTreeMap>,
+}
 
 impl RecommendedVisualizers {
+    /// Creates a new [`RecommendedVisualizers`] where all recommendations are auto-spawned.
+    pub fn new(
+        recommended_and_auto_spawned: BTreeMap>,
+    ) -> Self {
+        Self {
+            auto_spawned: recommended_and_auto_spawned.clone(),
+            all_recommendations: recommended_and_auto_spawned,
+        }
+    }
+
     pub fn empty() -> Self {
-        Self(Default::default())
+        Self::new(Default::default())
     }
 
     pub fn default(visualizer: ViewSystemIdentifier) -> Self {
-        Self(std::iter::once((visualizer, Default::default())).collect())
+        Self::new(std::iter::once((visualizer, Default::default())).collect())
     }
 
     pub fn default_many(visualizers: impl IntoIterator) -> Self {
@@ -47,7 +65,55 @@ impl RecommendedVisualizers {
             .into_iter()
             .map(|v| (v, Default::default()))
             .collect();
-        Self(recommended)
+        Self::new(recommended)
+    }
+
+    /// Inserts visualizer recommendations, merging with any existing mappings for the same visualizer.
+    ///
+    /// Duplicate mappings are removed.
+    /// If `auto_spawn` is true, this recommendation will also spawn a view.
+    pub fn insert(
+        &mut self,
+        visualizer: ViewSystemIdentifier,
+        mappings: Vec1,
+        auto_spawn: bool,
+    ) {
+        if auto_spawn {
+            Self::extend_mappings_unique(&mut self.auto_spawned, visualizer, mappings.clone());
+        }
+        Self::extend_mappings_unique(&mut self.all_recommendations, visualizer, mappings);
+    }
+
+    /// All recommended visualizers, including those not auto-spawned.
+    pub fn all_recommendations(
+        &self,
+    ) -> &BTreeMap> {
+        &self.all_recommendations
+    }
+
+    /// Consumes self and returns the auto-spawned recommendations.
+    ///
+    /// Auto-spanned recommendations are always a subset of all recommendations.
+    pub fn into_auto_spawned(self) -> BTreeMap> {
+        self.auto_spawned
+    }
+
+    fn extend_mappings_unique(
+        map: &mut BTreeMap>,
+        visualizer: ViewSystemIdentifier,
+        mappings: Vec1,
+    ) {
+        match map.entry(visualizer) {
+            std::collections::btree_map::Entry::Occupied(mut entry) => {
+                let existing = entry.get_mut();
+                existing.extend(mappings);
+                *existing = Vec1::try_from_vec(existing.iter().cloned().unique().collect())
+                    .expect("There was already at least one mapping.");
+            }
+            std::collections::btree_map::Entry::Vacant(entry) => {
+                entry.insert(mappings);
+            }
+        }
     }
 }
 
@@ -179,7 +245,7 @@ pub trait ViewClass: Send + Sync {
             })
             .collect();
 
-        RecommendedVisualizers(recommended)
+        RecommendedVisualizers::new(recommended)
     }
 
     /// Custom UI and add-visualizer options for the "Visualizers" section in the selection panel.
diff --git a/crates/viewer/re_viewer_context/src/view/view_query.rs b/crates/viewer/re_viewer_context/src/view/view_query.rs
index b74160c76d96..ae44d97d7af1 100644
--- a/crates/viewer/re_viewer_context/src/view/view_query.rs
+++ b/crates/viewer/re_viewer_context/src/view/view_query.rs
@@ -16,7 +16,7 @@ use crate::{
 };
 
 /// [`VisualizerComponentMapping`] but without the target.
-#[derive(Clone, Debug, PartialEq, Eq)]
+#[derive(Clone, Debug, PartialEq, Eq, Hash)]
 pub enum VisualizerComponentSource {
     /// See [`ComponentSourceKind::SourceComponent`].
     SourceComponent {
@@ -97,7 +97,7 @@ pub type VisualizerComponentMappings = BTreeMap {
                     self.indicated_entities_per_visualizer,
                 );
                 node.data_result.visualizer_instructions = recommended_visualizers
-                    .0
+                    .into_auto_spawned()
                     .into_iter()
                     .flat_map(|(visualizer_type, mappings_per_visualizer_type)| {
                         mappings_per_visualizer_type

From b69f797116fddb7e7cf3844c2895d9d3912a9f1f Mon Sep 17 00:00:00 2001
From: Lucas Meurer 
Date: Tue, 17 Mar 2026 17:04:28 +0100
Subject: [PATCH 165/513] Fix vulnerability in enforce branch name action
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

### Related

This would have allowed someone to run any shell command via branch name
injection and to exfiltrate the github token with all permissions 😬

Source-Ref: 60c78f2e1d4c607c5b404d68406e3b409b4b29c5
---
 .github/workflows/enforce_branch_name.yml | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)

diff --git a/.github/workflows/enforce_branch_name.yml b/.github/workflows/enforce_branch_name.yml
index 85035330e710..c9a021834aea 100644
--- a/.github/workflows/enforce_branch_name.yml
+++ b/.github/workflows/enforce_branch_name.yml
@@ -4,16 +4,22 @@ on:
   pull_request_target:
     types: [opened, reopened, synchronize]
 
+permissions:
+  issues: write
+
 jobs:
   check-source-branch:
     runs-on: ubuntu-latest
     steps:
       - name: Check PR source branch
+        env:
+          IS_FORK: ${{ github.event.pull_request.head.repo.fork }}
+          HEAD_REF: ${{ github.event.pull_request.head.ref }}
         run: |
           # Check if PR is from a fork
-          if [[ "${{ github.event.pull_request.head.repo.fork }}" == "true" ]]; then
+          if [[ "$IS_FORK" == "true" ]]; then
             # Check if PR is from the master/main branch of a fork
-            if [[ "${{ github.event.pull_request.head.ref }}" == "master" || "${{ github.event.pull_request.head.ref }}" == "main" ]]; then
+            if [[ "$HEAD_REF" == "master" || "$HEAD_REF" == "main" ]]; then
               echo "ERROR: Pull requests from the master/main branch of forks are not allowed, because it prevents maintainers from contributing to your PR"
               echo "Please create a feature branch in your fork and submit the PR from that branch instead."
               exit 1

From 900887fdcb866ffcd50ded1af41e517b0edd5fc8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?G=C3=A1bor=20Gyebn=C3=A1r?= 
Date: Tue, 17 Mar 2026 17:14:33 +0100
Subject: [PATCH 166/513] Adds cancel button to login flow

### Related

Closes RR-4044

### What

Adds a "cancel" button that shuts down the callback server.
The other login button (in the "Add server" modal) already has a cancel
button.

image

Source-Ref: 40defa91e6b7715c06e0806ac1361ccc940d7586
---
 crates/viewer/re_redap_browser/src/servers.rs | 25 ++++++++++++++++++-
 1 file changed, 24 insertions(+), 1 deletion(-)

diff --git a/crates/viewer/re_redap_browser/src/servers.rs b/crates/viewer/re_redap_browser/src/servers.rs
index 3fec75ca37d0..65a1ae9d6974 100644
--- a/crates/viewer/re_redap_browser/src/servers.rs
+++ b/crates/viewer/re_redap_browser/src/servers.rs
@@ -308,9 +308,24 @@ fn error_ui(
 
                     ui.add_space(8.0);
                     if has_active_login_flow {
-                        ui.horizontal(|ui| {
+                        ui.horizontal_centered(|ui| {
+                            // 4.0 = Size::Small vertical padding
+                            let cancel_button_height =
+                                ui.text_style_height(&egui::TextStyle::Button) + 2.0 * 4.0;
+                            ui.set_min_height(cancel_button_height);
                             ui.loading_indicator("Waiting for login");
                             ui.label("Waiting for login…");
+                            ui.add_space(8.0);
+                            if ui
+                                .add(
+                                    re_ui::ReButton::new(("Cancel", &icons::CLOSE))
+                                        .small()
+                                        .primary(),
+                                )
+                                .clicked()
+                            {
+                                send_crossbeam(ctx.command_sender, Command::CancelLoginFlow).ok();
+                            }
                         });
                     } else {
                         ui.horizontal(|ui| {
@@ -514,6 +529,9 @@ pub enum Command {
 
     /// Use the stored account credentials for a server and refresh.
     UseStoredCredentials(re_uri::Origin),
+
+    /// Cancel the active inline login flow.
+    CancelLoginFlow,
 }
 
 impl std::fmt::Debug for Command {
@@ -541,6 +559,7 @@ impl std::fmt::Debug for Command {
             Self::UseStoredCredentials(origin) => {
                 f.debug_tuple("UseStoredCredentials").field(origin).finish()
             }
+            Self::CancelLoginFlow => write!(f, "CancelLoginFlow"),
         }
     }
 }
@@ -727,6 +746,10 @@ impl RedapServers {
                 connection_registry.set_credentials(&origin, re_redap_client::Credentials::Stored);
                 send_crossbeam(&self.command_sender, Command::RefreshCollection(origin)).ok();
             }
+
+            Command::CancelLoginFlow => {
+                self.inline_login_flow = None;
+            }
         }
     }
 

From 62df5aea1db7459ec3036a37fb7dc55e44164f6c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?G=C3=A1bor=20Gyebn=C3=A1r?= 
Date: Tue, 17 Mar 2026 17:35:24 +0100
Subject: [PATCH 167/513] Uses Lavapipe on Macos CI runners

### Related

Ref RR-3339

### What

Uses Lavapipe as the stoftware renderer on Macos CI runners.

As we use Lavapipe on Linux and Windows runners, we can now have ground
truth image snapshots of our UI and remove or lower os-specific
threshold settings.

---------

Source-Ref: 551dcec39702a157b6ecb68565e501ed3d38d986
Co-authored-by: Claude Opus 4.6 
---
 CONTRIBUTING.md                               |   8 +-
 crates/viewer/re_test_context/src/lib.rs      |   2 +-
 crates/viewer/re_ui/src/testing.rs            |  35 +--
 crates/viewer/re_ui/tests/help_ui_test.rs     |   8 +-
 crates/viewer/re_viewer/tests/app_kittest.rs  |  18 ++
 scripts/ci/setup_software_rasterizer.py       | 236 ++++--------------
 .../visualizer_instruction_errors_test.rs     |   2 -
 7 files changed, 78 insertions(+), 231 deletions(-)

diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 71141da8aa6c..89d258010be8 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -213,12 +213,8 @@ For best practices & unexpected sources of image differences refer to the [egui_
 
 Just like for drawing the viewer itself, drawing for comparison tests requires a `wgpu` compatible driver.
 As of writing comparison tests are only run via Vulkan & Metal.
-For CI / headless environments we a recent version `llvmpipe` for software rendering on Linux & Windows.
-On MacOS we use [`SwiftShader`](https://github.com/google/swiftshader/).
-
-⚠️ Unfortunately, `SwiftShader`'s MSAA & texture filtering differs drastically from `llvmpipe` and
-other native renderers which is why we use a lot higher comparison thresholds on Mac.
--> **DO NOT** use images generated on MacOS CI as reference image, prefer those produced by our Linux runner.
+For CI / headless environments we use lavapipe (`llvmpipe`) for software rendering on all platforms.
+On macOS, we use a custom static build from [`rerun-io/lavapipe-build`](https://github.com/rerun-io/lavapipe-build).
 
 For details on how to set this up refer to the [CI setup](./.github/workflows/reusable_checks_rust.yml).
 
diff --git a/crates/viewer/re_test_context/src/lib.rs b/crates/viewer/re_test_context/src/lib.rs
index d1a133809ef9..a8d475b76d3e 100644
--- a/crates/viewer/re_test_context/src/lib.rs
+++ b/crates/viewer/re_test_context/src/lib.rs
@@ -81,7 +81,7 @@ pub struct TestContext {
     command_sender: CommandSender,
     command_receiver: CommandReceiver,
 
-    egui_render_state: Mutex>,
+    pub egui_render_state: Mutex>,
     called_setup_kittest_for_rendering: AtomicBool,
 }
 
diff --git a/crates/viewer/re_ui/src/testing.rs b/crates/viewer/re_ui/src/testing.rs
index 3e03c6a66a63..281ea7940458 100644
--- a/crates/viewer/re_ui/src/testing.rs
+++ b/crates/viewer/re_ui/src/testing.rs
@@ -27,24 +27,11 @@ pub fn new_harness(option: TestOptions, size: impl Into) -> HarnessBuil
         .with_options(options)
 }
 
-fn use_lenient_macos_ci_thresholds() -> bool {
-    // TODO(andreas): As of writing SwiftShader gets MSAA wrong and has drastically different texture filtering.
-    // TODO(andreas): A more straight forward check for swiftshader would be better here.
-    // TODO(#12450): Investigate whether we can run lavapipe instead.
-    cfg!(target_os = "macos") && std::env::var("CI").is_ok()
-}
-
 pub fn default_snapshot_options_for_ui() -> SnapshotOptions {
-    if use_lenient_macos_ci_thresholds() {
-        // SwiftShader has drastically different texture filtering it seems.
-        SnapshotOptions::default()
-            .failed_pixel_count_threshold(80)
-            .threshold(1.0)
-    } else {
-        SnapshotOptions::default().failed_pixel_count_threshold(
-            10, // we sometimes have a few wrong pixels in text rendering in egui for unknown reasons
-        )
-    }
+    // TODO(aedm): allow zero on CI and warn users if they generate snapshots on GPU
+    SnapshotOptions::default().failed_pixel_count_threshold(
+        10, // we sometimes have a few wrong pixels in text rendering in egui for unknown reasons
+    )
 }
 
 pub fn default_snapshot_options_for_3d(viewport_size: Vec2) -> SnapshotOptions {
@@ -53,19 +40,11 @@ pub fn default_snapshot_options_for_3d(viewport_size: Vec2) -> SnapshotOptions {
     // How many depend on the size of the image.
     let num_total_pixels = viewport_size.x * viewport_size.y;
 
-    let lenient_macos_ci_thresholds = use_lenient_macos_ci_thresholds();
-    let broken_pixels_fraction = if lenient_macos_ci_thresholds {
-        1.0 / 100.0
-    } else {
-        0.04 / 100.0
-    };
+    let broken_pixels_fraction = 0.04 / 100.0;
     let max_broken_pixels = (num_total_pixels * broken_pixels_fraction).round() as usize;
 
-    let threshold = if lenient_macos_ci_thresholds {
-        2.5
-    } else {
-        1.0 // Need a bit higher than the default to accommodate for various filtering artifacts, typically caused by the grid shader.
-    };
+    // Need a bit higher than the default to accommodate for various filtering artifacts, typically caused by the grid shader.
+    let threshold = 1.0;
 
     SnapshotOptions::default()
         .threshold(threshold)
diff --git a/crates/viewer/re_ui/tests/help_ui_test.rs b/crates/viewer/re_ui/tests/help_ui_test.rs
index 4dce82fe1da2..04e2ee60da94 100644
--- a/crates/viewer/re_ui/tests/help_ui_test.rs
+++ b/crates/viewer/re_ui/tests/help_ui_test.rs
@@ -6,7 +6,7 @@ use re_ui::{Help, IconText, MouseButtonText, UiExt as _, icons};
 
 #[test]
 fn test_help() {
-    let mut _snapshot_results = SnapshotResults::new();
+    let mut snapshot_results = SnapshotResults::new();
 
     // We show different shortcuts based on the OS
     for os in [OperatingSystem::Windows, OperatingSystem::Mac] {
@@ -58,10 +58,6 @@ fn test_help() {
 
         harness.try_run_realtime().ok();
 
-        // TODO(#12450): Keeps failing randomly on swiftshader.
-        #[cfg(not(target_os = "macos"))]
-        {
-            _snapshot_results.add(harness.try_snapshot(format!("help_{os:?}")));
-        }
+        snapshot_results.add(harness.try_snapshot(format!("help_{os:?}")));
     }
 }
diff --git a/crates/viewer/re_viewer/tests/app_kittest.rs b/crates/viewer/re_viewer/tests/app_kittest.rs
index 33fcef32eb79..0c064dbf01f1 100644
--- a/crates/viewer/re_viewer/tests/app_kittest.rs
+++ b/crates/viewer/re_viewer/tests/app_kittest.rs
@@ -98,3 +98,21 @@ fn colormap_selector_ui() {
     harness.run();
     harness.snapshot("colormap_selector_open");
 }
+
+#[test]
+fn ci_runners_use_software_rendering() {
+    if std::env::var("CI").is_ok() {
+        let test_context = TestContext::new();
+        let _viewer = test_context.setup_kittest_for_rendering_3d([200.0, 100.0]);
+        let render_state_guard = test_context.egui_render_state.lock();
+        let render_state = render_state_guard.as_ref().unwrap();
+        assert_eq!(
+            render_state.adapter.get_info().device_type,
+            wgpu::DeviceType::Cpu
+        );
+        assert_eq!(
+            render_state.adapter.get_info().backend,
+            wgpu::Backend::Vulkan
+        );
+    }
+}
diff --git a/scripts/ci/setup_software_rasterizer.py b/scripts/ci/setup_software_rasterizer.py
index 483ac57f34a9..3a74754ce2a5 100644
--- a/scripts/ci/setup_software_rasterizer.py
+++ b/scripts/ci/setup_software_rasterizer.py
@@ -6,19 +6,16 @@
 In fact we're the exact same Mesa builds that wgpu produces,
 see https://github.com/gfx-rs/ci-build
 
-For macOS, we use SwiftShader instead of lavapipe. SwiftShader is Google's software Vulkan implementation
-that provides better compatibility with macOS.
 Using a software rasterizer avoids GPU-related flakiness on CI runners which we hit quite often when
 running many tests in parallel - we got spurious timeouts and failure to find graphics devices,
 the cause of these issues is unknown. See https://github.com/rerun-io/rerun/issues/11359.
-(Since SwiftShader is Apache 2.0 licensed, we can host the binaries ourselves, which speeds up the whole process.)
 
-TODO(#12450): Investigate whether we can run lavapipe instead.
+All platforms use lavapipe (Mesa's software Vulkan implementation).
+For macOS, we use a custom static build from https://github.com/rerun-io/lavapipe-build.
 """
 
 from __future__ import annotations
 
-import json
 import os
 import platform
 import shutil
@@ -35,13 +32,11 @@
 # Corresponds to https://github.com/gfx-rs/ci-build/releases
 CI_BINARY_BUILD = "build19"
 
-# SwiftShader version for macOS software rasterization
-# This corresponds to the Chrome version from which the binaries were extracted
-SWIFTSHADER_VERSION = "144.0.7559.60"
-
-# GCloud bucket for SwiftShader binaries
-SWIFTSHADER_GCLOUD_BUCKET = "rerun-test-assets"
-SWIFTSHADER_GCLOUD_PATH = f"swiftshader/{SWIFTSHADER_VERSION}"
+# Lavapipe build for macOS from https://github.com/rerun-io/lavapipe-build
+LAVAPIPE_MACOS_VERSION = "v0.4.0"
+LAVAPIPE_MACOS_URL = (
+    f"https://github.com/rerun-io/lavapipe-build/releases/download/{LAVAPIPE_MACOS_VERSION}/lavapipe-macos-arm64.tar.gz"
+)
 
 CARGO_TARGET_DIR = Path(os.environ.get("CARGO_TARGET_DIR", "target"))
 
@@ -221,170 +216,52 @@ def setup_lavapipe_for_windows() -> dict[str, str]:
     return env_vars
 
 
-def extract_swiftshader_from_chrome() -> tuple[Path, str] | None:
-    """
-    Extract SwiftShader binaries from a local (!) Chrome installation.
-
-    Chrome bundles SwiftShader (Apache 2.0 license) as its software Vulkan implementation.
-
-    This is a helper function for maintainers to upload new SwiftShader versions.
-    Run this script with --upload-swiftshader to extract from Chrome and upload.
-
-    Returns:
-        Tuple of (Path to libvk_swiftshader.dylib, Chrome version string), or None if Chrome is not found.
-    """
-    chrome_paths = [
-        "/Applications/Google Chrome.app",
-        "/Applications/Chromium.app",
-    ]
-
-    for chrome_path in chrome_paths:
-        chrome_base = Path(chrome_path)
-        if not chrome_base.exists():
-            continue
-
-        # Find the latest version directory
-        framework_path = chrome_base / "Contents/Frameworks"
-        if "Chrome" in chrome_path:
-            framework_path = framework_path / "Google Chrome Framework.framework/Versions"
-        else:
-            framework_path = framework_path / "Chromium Framework.framework/Versions"
-
-        if not framework_path.exists():
-            continue
-
-        # Find the latest version (skip symlinks like "Current")
-        versions = [d for d in framework_path.iterdir() if d.is_dir() and not d.is_symlink()]
-        if not versions:
-            continue
-
-        latest_version = max(versions, key=lambda p: p.name)
-        swiftshader_lib = latest_version / "Libraries/libvk_swiftshader.dylib"
-
-        if swiftshader_lib.exists():
-            version_str = latest_version.name
-            print(f"Found SwiftShader in {chrome_path}, version {version_str}")
-            print(f"Library path: {swiftshader_lib}")
-            return swiftshader_lib, version_str
-
-    return None
-
-
-def upload_swiftshader_to_gcloud(lib_path: Path, version: str) -> None:
-    """
-    Upload SwiftShader binary to GCloud storage.
-
-    This is a helper function for maintainers to upload new SwiftShader versions.
-    Run this script with --upload-swiftshader to extract from Chrome and upload.
-
-    SwiftShader is Apache 2.0 licensed, so we can redistribute the binaries.
-    See: https://github.com/google/swiftshader/blob/HEAD/LICENSE.txt
-
-    Args:
-        lib_path: Path to the libvk_swiftshader.dylib file to upload
-        version: Chrome version string (e.g. "143.0.7499.193")
-    """
-    try:
-        from google.cloud import storage
-    except ImportError:
-        print("ERROR: google-cloud-storage is not installed.")
-        print("Install it with: pip install google-cloud-storage")
-        sys.exit(1)
-
-    blob_path = f"swiftshader/{version}/libvk_swiftshader.dylib"
-    gcloud_url = f"gs://{SWIFTSHADER_GCLOUD_BUCKET}/{blob_path}"
-
-    print(f"\nUploading {lib_path} to {gcloud_url}")
-    try:
-        client = storage.Client("rerun-open")
-        bucket = client.bucket(SWIFTSHADER_GCLOUD_BUCKET)
-        blob = bucket.blob(blob_path)
-
-        print(f"Uploading to bucket '{SWIFTSHADER_GCLOUD_BUCKET}' at path '{blob_path}'…")
-        blob.upload_from_filename(str(lib_path))
-
-        print(f"✓ Successfully uploaded to {gcloud_url}")
-
-        if version != SWIFTSHADER_VERSION:
-            print(f"\nNext step: Update SWIFTSHADER_VERSION = '{version}' in this script to use the new version in CI.")
-    except Exception as e:
-        print(f"✗ Upload failed: {e}")
-        print("Make sure you're authenticated with: gcloud auth application-default login")
-        sys.exit(1)
-
-
-def setup_swiftshader_for_macos() -> dict[str, str]:
-    """
-    Sets up SwiftShader software rasterizer for macOS.
-
-    SwiftShader is Google's software Vulkan implementation (Apache 2.0 licensed).
-    We use it for CI testing to avoid GPU-related flakiness on macOS runners.
-
-    This function:
-    1. Downloads libvk_swiftshader.dylib from GCloud storage
-    2. Creates a Vulkan ICD (Installable Client Driver) JSON file pointing to the library
-    3. Sets VK_DRIVER_FILES environment variable to use SwiftShader
-
-    Note: The Vulkan SDK must be installed separately (for the loader and vulkaninfo).
-
-    Returns:
-        Dictionary of environment variables to set.
-    """
-    print("Setting up SwiftShader for macOS…")
-
-    try:
-        from google.cloud import storage
-    except ImportError:
-        print("ERROR: google-cloud-storage is not installed.")
-        print("Install it with: pip install google-cloud-storage")
-        sys.exit(1)
-
-    # Create directory for SwiftShader
-    swiftshader_dir = Path.home() / "swiftshader"
-    swiftshader_dir.mkdir(exist_ok=True)
-
-    swiftshader_lib = swiftshader_dir / "libvk_swiftshader.dylib"
+def setup_lavapipe_for_macos() -> dict[str, str]:
+    """Sets up lavapipe mesa driver for macOS (arm64)."""
+    print("Setting up lavapipe for macOS…")
 
-    # Download from GCloud
-    blob_path = f"{SWIFTSHADER_GCLOUD_PATH}/libvk_swiftshader.dylib"
-    gcloud_url = f"gs://{SWIFTSHADER_GCLOUD_BUCKET}/{blob_path}"
-    print(f"Downloading SwiftShader from {gcloud_url}…")
-
-    try:
-        client = storage.Client("rerun-open")
-        bucket = client.bucket(SWIFTSHADER_GCLOUD_BUCKET)
-        blob = bucket.blob(blob_path)
-
-        if not blob.exists():
-            print(f"✗ SwiftShader binary not found at {gcloud_url}")
-            print("If you're a maintainer, upload it with:")
-            print(f"  python {__file__} --upload-swiftshader")
-            sys.exit(1)
-
-        blob.download_to_filename(str(swiftshader_lib))
-        print(f"✓ Downloaded SwiftShader to {swiftshader_lib}")
-
-    except Exception as e:
-        print(f"✗ GCloud download failed: {e}")
-        sys.exit(1)
-
-    # Create ICD JSON file
-    # The ICD file tells the Vulkan loader where to find the driver implementation.
-    icd_json = {
-        "file_format_version": "1.0.0",
-        "ICD": {"library_path": str(swiftshader_lib.resolve()), "api_version": "1.3.0"},
-    }
-
-    icd_json_path = swiftshader_dir / "vk_swiftshader_icd.json"
-
-    with open(icd_json_path, "w", encoding="utf-8") as f:
-        json.dump(icd_json, f, indent=2)
+    # Download lavapipe build (self-contained with bundled dependencies)
+    run([
+        "curl",
+        "-L",
+        "--retry",
+        "5",
+        LAVAPIPE_MACOS_URL,
+        "-o",
+        "lavapipe-macos-arm64.tar.gz",
+    ])
 
-    print(f"✓ Created ICD file at {icd_json_path}")
+    # Extract
+    run(["tar", "xzf", "lavapipe-macos-arm64.tar.gz"])
+
+    lavapipe_dir = Path(os.getcwd()) / "lavapipe-macos-arm64"
+    icd_json_path = lavapipe_dir / "lvp_icd.aarch64.json"
+
+    # The Vulkan SDK's loader may be incompatible with lavapipe. Replace it with
+    # Homebrew's vulkan-loader which is built against the same Vulkan headers as Mesa.
+    print("Installing Homebrew vulkan-loader for compatibility with lavapipe…")
+    run(["/opt/homebrew/bin/brew", "install", "vulkan-loader"])
+
+    # Replace the Vulkan SDK's loader with the Homebrew one in all search paths.
+    # DYLD_LIBRARY_PATH alone is unreliable on macOS due to SIP stripping it.
+    run(["sudo", "mkdir", "-p", "/usr/local/lib"])
+    for lib_name in ["libvulkan.dylib", "libvulkan.1.dylib"]:
+        homebrew_path = f"/opt/homebrew/lib/{lib_name}"
+        if Path(homebrew_path).exists():
+            run(["sudo", "ln", "-sf", homebrew_path, f"/usr/local/lib/{lib_name}"])
+            # Also overwrite in the Vulkan SDK lib directory
+            vulkan_sdk_path = os.environ.get("VULKAN_SDK")
+            if vulkan_sdk_path:
+                sdk_lib = f"{vulkan_sdk_path}/lib/{lib_name}"
+                if Path(sdk_lib).exists() or Path(f"{vulkan_sdk_path}/lib").is_dir():
+                    print(f"Replacing {sdk_lib} with symlink to {homebrew_path}")
+                    run(["sudo", "ln", "-sf", homebrew_path, sdk_lib])
 
     # Set environment variables
     env_vars = {
         "VK_DRIVER_FILES": str(icd_json_path.resolve()),
+        # Clear VK_LAYER_PATH to prevent validation layer crashes with lavapipe.
+        "VK_LAYER_PATH": "",
     }
     set_environment_variables(env_vars)
 
@@ -424,26 +301,9 @@ def check_for_vulkan_sdk() -> None:
 
 
 def main() -> None:
-    # Handle --upload-swiftshader flag for maintainers
-    if len(sys.argv) > 1 and sys.argv[1] == "--upload-swiftshader":
-        if sys.platform != "darwin":
-            print("ERROR: --upload-swiftshader is only supported on macOS")
-            sys.exit(1)
-
-        print("Extracting SwiftShader from local Chrome and uploading to GCloud…")
-        result = extract_swiftshader_from_chrome()
-        if result is None:
-            print("ERROR: Could not find SwiftShader in Chrome installation")
-            sys.exit(1)
-
-        lib_path, version = result
-        upload_swiftshader_to_gcloud(lib_path, version)
-        return
-
     # We only use Vulkan software rasterizers right now.
     check_for_vulkan_sdk()
 
-    # Normal setup
     if os.name == "nt" and platform.machine() == "AMD64":
         # Note that we could also use WARP, the DX12 software rasterizer.
         # (wgpu tests with both llvmpip and WARP)
@@ -455,7 +315,7 @@ def main() -> None:
         env_vars = setup_lavapipe_for_linux()
         vulkan_info(env_vars)
     elif os.name == "posix" and sys.platform == "darwin":
-        env_vars = setup_swiftshader_for_macos()
+        env_vars = setup_lavapipe_for_macos()
         vulkan_info(env_vars)
     else:
         raise ValueError(f"Unsupported OS / architecture: {os.name} / {platform.machine()}")
diff --git a/tests/rust/re_integration_test/tests/visualizer_instruction_errors_test.rs b/tests/rust/re_integration_test/tests/visualizer_instruction_errors_test.rs
index 28fb2f3de986..152b4bda0d32 100644
--- a/tests/rust/re_integration_test/tests/visualizer_instruction_errors_test.rs
+++ b/tests/rust/re_integration_test/tests/visualizer_instruction_errors_test.rs
@@ -172,7 +172,5 @@ pub async fn series_count_exceeds_max() {
 
     harness.click_label("View errors");
 
-    // TODO(#12450): macOS CI uses SwiftShader which produces images too different from the reference.
-    #[cfg(not(target_os = "macos"))]
     harness.snapshot_app("series_count_exceeds_max");
 }

From 426fcfc2d699a7e721ed5d36e8b44eadeb737921 Mon Sep 17 00:00:00 2001
From: Michael Grupp 
Date: Tue, 17 Mar 2026 18:18:43 +0100
Subject: [PATCH 168/513] Load URDF from `/robot_description` ROS 2 string
 topics in MCAP

### Related

Closes https://linear.app/rerun/issue/RR-4047

### What

Automatically attempts to load URDF from a ROS 2 `/robot_description`
topic when converting or loading MCAP files.

ROS 2 recordings typically contain a `/robot_description` topic
(`std_msgs/msg/String`) published by
robot_state_publisher with the full URDF XML. Previously this was logged
as a plain text string. Now we
parse it as URDF and emit 3D visualization chunks (primitive geometry,
coordinate frames, materials) by
reusing the existing URDF file loader.

Adds a `urdf` option to the MCAP CLI to toggle this behavior.

Note: Transformations are expected to be part of the MCAP in this
loading path (as opposed to the regular URDF file loader, which also
sends the static transforms from the file).

Internal change: the URDF loader methods now accept functions/closures
for emitting chunks instead of only a send channel. This is necessary to
allow the flexibility needed for this additional use case.

 ### Testing

- Tested with a ROS 2 MCAP file containing a /robot_description topic

### Compatibility

No breaking changes anticipated. MCAP files without robot_description
topics should be unaffected.

---------

Source-Ref: c178df1e5b266b06d0f6e0e15cfd62c2213f6095
Co-authored-by: Isaac Blankenau <119615777+iblnkn@users.noreply.github.com>
---
 .../re_data_loader/src/loader_mcap/loader.rs  |  13 ++
 .../re_data_loader/src/loader_mcap/mod.rs     |   1 +
 .../src/loader_mcap/robot_description.rs      |  77 ++++++++
 .../re_data_loader/src/loader_urdf/mod.rs     | 186 +++++++++---------
 .../loader_urdf/robot_description_parser.rs   |  30 +++
 crates/top/rerun/src/commands/mcap/mod.rs     |   9 +-
 .../mcap/cli-reference.md                     |   7 +-
 .../mcap/decoders-explained.md                |  10 +
 .../howto/logging-and-ingestion/mcap.md       |   4 +
 9 files changed, 243 insertions(+), 94 deletions(-)
 create mode 100644 crates/store/re_data_loader/src/loader_mcap/robot_description.rs
 create mode 100644 crates/store/re_data_loader/src/loader_urdf/robot_description_parser.rs

diff --git a/crates/store/re_data_loader/src/loader_mcap/loader.rs b/crates/store/re_data_loader/src/loader_mcap/loader.rs
index 9021c6c84362..d2a160ff68cf 100644
--- a/crates/store/re_data_loader/src/loader_mcap/loader.rs
+++ b/crates/store/re_data_loader/src/loader_mcap/loader.rs
@@ -299,6 +299,19 @@ pub fn load_mcap(
         .plan(mcap, &summary)?
         .run(mcap, &summary, time_type, &mut send_chunk)?;
 
+    // Extract URDF from robot_description topics and convert to 3D visualization chunks.
+    // Non-fatal: errors here should never prevent the rest of the MCAP from loading.
+    // TODO(michael): make the URDF extraction a proper decoder.
+    if selected_decoders.contains(&DecoderIdentifier::from("urdf"))
+        && let Err(err) = super::robot_description::extract_urdf_from_robot_descriptions(
+            mcap,
+            &summary,
+            &mut send_chunk,
+        )
+    {
+        re_log::warn_once!("Failed to extract URDF from robot_description topics: {err}");
+    }
+
     Ok(())
 }
 
diff --git a/crates/store/re_data_loader/src/loader_mcap/mod.rs b/crates/store/re_data_loader/src/loader_mcap/mod.rs
index 5cf888fcc55e..17fdb70a2e86 100644
--- a/crates/store/re_data_loader/src/loader_mcap/mod.rs
+++ b/crates/store/re_data_loader/src/loader_mcap/mod.rs
@@ -1,6 +1,7 @@
 //! Rerun dataloader for MCAP files.
 
 mod loader;
+mod robot_description;
 
 /// Lens implementations for transforming various third-party data formats into Rerun components.
 pub mod lenses;
diff --git a/crates/store/re_data_loader/src/loader_mcap/robot_description.rs b/crates/store/re_data_loader/src/loader_mcap/robot_description.rs
new file mode 100644
index 000000000000..8ee593694f77
--- /dev/null
+++ b/crates/store/re_data_loader/src/loader_mcap/robot_description.rs
@@ -0,0 +1,77 @@
+use std::collections::HashMap;
+
+/// Minimal ROS2 String message for CDR deserialization.
+#[derive(serde::Deserialize)]
+struct RosString {
+    data: String,
+}
+
+/// Scans the MCAP for `robot_description` topics carrying `std_msgs/msg/String`,
+/// extracts the URDF XML, and emits 3D visualization chunks.
+///
+/// Note that transforms are not extracted from the URDF in this context.
+/// These are expected to be present in the MCAP as separate transform messages.
+///
+/// TODO(michael): this could be implemented as an `re_mcap` decoder if the
+/// core URDF parsing logic is moved to a separate crate outside of `re_data_loader`.
+pub(crate) fn extract_urdf_from_robot_descriptions(
+    mcap_bytes: &[u8],
+    summary: &mcap::Summary,
+    emit: &mut dyn FnMut(re_chunk::Chunk),
+) -> anyhow::Result<()> {
+    let robot_desc_channels: Vec = summary
+        .channels
+        .values()
+        .filter(|channel| {
+            channel.topic.contains("robot_description")
+                && channel.schema.as_ref().is_some_and(|schema| {
+                    schema.name == "std_msgs/msg/String" && schema.encoding == "ros2msg"
+                })
+        })
+        .map(|channel| channel.id)
+        .collect();
+
+    if robot_desc_channels.is_empty() {
+        return Ok(());
+    }
+
+    re_log::debug!(
+        "Found {} robot_description channel(s), scanning messages…",
+        robot_desc_channels.len()
+    );
+
+    let mut urdf_by_channel: HashMap = HashMap::new();
+
+    for msg in mcap::MessageStream::new(mcap_bytes)? {
+        let msg = msg?;
+        if robot_desc_channels.contains(&msg.channel.id)
+            && let Ok(decoded) = re_mcap::cdr::try_decode_message::(&msg.data)
+        {
+            urdf_by_channel.insert(msg.channel.id, decoded.data);
+        }
+    }
+
+    for urdf_xml in urdf_by_channel.into_values() {
+        match crate::loader_urdf::build_urdf_chunks_from_xml(
+            &urdf_xml,
+            &None,
+            &re_log_types::TimePoint::STATIC,
+            false,
+        ) {
+            Ok(chunks) => {
+                re_log::debug!(
+                    "URDF extraction produced {} chunks from robot_description.",
+                    chunks.len()
+                );
+                for chunk in chunks {
+                    emit(chunk);
+                }
+            }
+            Err(err) => {
+                re_log::warn_once!("Failed to parse URDF from robot_description topic: {err}");
+            }
+        }
+    }
+
+    Ok(())
+}
diff --git a/crates/store/re_data_loader/src/loader_urdf/mod.rs b/crates/store/re_data_loader/src/loader_urdf/mod.rs
index 60184b8c5fce..98c8a147b143 100644
--- a/crates/store/re_data_loader/src/loader_urdf/mod.rs
+++ b/crates/store/re_data_loader/src/loader_urdf/mod.rs
@@ -1,15 +1,16 @@
 //! Rerun data loader and utilities for URDF files.
 
 pub mod joint_transform;
+mod robot_description_parser;
 mod urdf_tree;
+pub(crate) use robot_description_parser::build_urdf_chunks_from_xml;
 pub use urdf_tree::UrdfTree;
 
 use std::path::{Path, PathBuf};
 
 use anyhow::{Context as _, bail};
 use crossbeam::channel::Sender;
-use re_chunk::{ChunkBuilder, ChunkId, EntityPath, RowId, TimePoint};
-use re_log_types::StoreId;
+use re_chunk::{Chunk, ChunkBuilder, ChunkId, EntityPath, RowId, TimePoint};
 use re_sdk_types::archetypes::{Asset3D, CoordinateFrame, InstancePoses3D, Transform3D};
 use re_sdk_types::components::Color;
 use re_sdk_types::datatypes::{Rgba32, Vec3D};
@@ -25,28 +26,19 @@ fn is_urdf_file(path: impl AsRef) -> bool {
         .is_some_and(|ext| ext.eq_ignore_ascii_case("urdf"))
 }
 
-fn send_chunk_builder(
-    tx: &Sender,
-    store_id: &StoreId,
-    chunk: ChunkBuilder,
-) -> anyhow::Result<()> {
-    re_quota_channel::send_crossbeam(
-        tx,
-        LoadedData::Chunk(UrdfDataLoader.name(), store_id.clone(), chunk.build()?),
-    )?;
+fn emit_chunk_builder(emit: &mut dyn FnMut(Chunk), chunk: ChunkBuilder) -> anyhow::Result<()> {
+    emit(chunk.build()?);
     Ok(())
 }
 
-fn send_archetype(
-    tx: &Sender,
-    store_id: &StoreId,
+fn emit_archetype(
+    emit: &mut dyn FnMut(Chunk),
     entity_path: EntityPath,
     timepoint: &TimePoint,
     archetype: &impl AsComponents,
 ) -> anyhow::Result<()> {
-    send_chunk_builder(
-        tx,
-        store_id,
+    emit_chunk_builder(
+        emit,
         ChunkBuilder::new(ChunkId::new(), entity_path).with_archetype(
             RowId::new(),
             timepoint.clone(),
@@ -80,16 +72,32 @@ impl DataLoader for UrdfDataLoader {
         let robot = urdf_rs::read_file(&filepath)
             .with_context(|| format!("Path: {}", filepath.display()))?;
 
-        log_robot(
+        let store_id = settings.opened_store_id_or_recommended();
+        let mut send_error = None;
+        let mut emit = |chunk| {
+            if send_error.is_none() {
+                send_error = re_quota_channel::send_crossbeam(
+                    &tx,
+                    LoadedData::Chunk(Self.name(), store_id.clone(), chunk),
+                )
+                .err();
+            }
+        };
+
+        emit_robot(
+            &mut emit,
             robot,
-            &filepath,
-            &tx,
-            &settings.opened_store_id_or_recommended(),
+            filepath.parent().map(|path| path.to_path_buf()),
             &settings.entity_path_prefix,
             &settings.timepoint.clone().unwrap_or_default(),
+            true,
         )
         .with_context(|| "Failed to load URDF file!")?;
 
+        if let Some(err) = send_error {
+            return Err(anyhow::anyhow!(err.to_string()).into());
+        }
+
         Ok(())
     }
 
@@ -109,56 +117,68 @@ impl DataLoader for UrdfDataLoader {
         let robot = urdf_rs::read_from_string(&String::from_utf8_lossy(&contents))
             .with_context(|| format!("Path: {}", filepath.display()))?;
 
-        log_robot(
+        let store_id = settings.opened_store_id_or_recommended();
+        let mut send_error = None;
+        let mut emit = |chunk| {
+            if send_error.is_none() {
+                send_error = re_quota_channel::send_crossbeam(
+                    &tx,
+                    LoadedData::Chunk(Self.name(), store_id.clone(), chunk),
+                )
+                .err();
+            }
+        };
+
+        emit_robot(
+            &mut emit,
             robot,
-            &filepath,
-            &tx,
-            &settings.opened_store_id_or_recommended(),
+            filepath.parent().map(|path| path.to_path_buf()),
             &settings.entity_path_prefix,
             &settings.timepoint.clone().unwrap_or_default(),
+            true,
         )
         .with_context(|| "Failed to load URDF file!")?;
 
+        if let Some(err) = send_error {
+            return Err(anyhow::anyhow!(err.to_string()).into());
+        }
+
         Ok(())
     }
 }
 
-fn log_robot(
+pub(crate) fn emit_robot(
+    emit: &mut dyn FnMut(Chunk),
     robot: urdf_rs::Robot,
-    filepath: &Path,
-    tx: &Sender,
-    store_id: &StoreId,
+    urdf_dir: Option,
     entity_path_prefix: &Option,
     timepoint: &TimePoint,
+    include_joint_transforms: bool,
 ) -> anyhow::Result<()> {
-    let urdf_dir = filepath.parent().map(|path| path.to_path_buf());
-
     let urdf_tree = UrdfTree::new(robot, urdf_dir, entity_path_prefix.clone())
         .with_context(|| "Failed to build URDF tree!")?;
 
     // The robot's root coordinate frame_id.
-    send_archetype(
-        tx,
-        store_id,
+    emit_archetype(
+        emit,
         urdf_tree.log_paths.root.clone(),
         timepoint,
         &CoordinateFrame::update_fields().with_frame(urdf_tree.root().name.clone()),
     )?;
 
-    let transforms = walk_tree(&urdf_tree, tx, store_id, timepoint, &urdf_tree.root().name)?;
+    let transforms = walk_tree(emit, &urdf_tree, timepoint, &urdf_tree.root().name)?;
 
-    // Send all transforms as rows in a single chunk.
-    if !transforms.is_empty() {
-        send_static_transforms_batch(tx, store_id, &urdf_tree.log_paths.transforms, &transforms)?;
+    // Emit all transforms as rows in a single chunk.
+    if include_joint_transforms && !transforms.is_empty() {
+        emit_static_transforms_batch(emit, &urdf_tree.log_paths.transforms, &transforms)?;
     }
 
     Ok(())
 }
 
 fn walk_tree(
+    emit: &mut dyn FnMut(Chunk),
     urdf_tree: &UrdfTree,
-    tx: &Sender,
-    store_id: &StoreId,
     timepoint: &TimePoint,
     link_name: &str,
 ) -> anyhow::Result> {
@@ -167,7 +187,7 @@ fn walk_tree(
         .with_context(|| format!("Link {link_name:?} missing from map"))?;
     re_log::debug_assert_eq!(link_name, link.name);
 
-    log_link(urdf_tree, tx, store_id, timepoint, link)?;
+    emit_link(urdf_tree, timepoint, link, emit)?;
 
     let Some(joints) = urdf_tree.get_children(link_name) else {
         // if there's no more joints connecting this link to anything else we've reached the end of this branch.
@@ -179,8 +199,7 @@ fn walk_tree(
         joint_transforms_for_link.push(get_joint_transform(joint));
 
         // Recurse
-        let mut child_transforms =
-            walk_tree(urdf_tree, tx, store_id, timepoint, &joint.child.link)?;
+        let mut child_transforms = walk_tree(emit, urdf_tree, timepoint, &joint.child.link)?;
         joint_transforms_for_link.append(&mut child_transforms);
     }
 
@@ -205,13 +224,12 @@ fn get_joint_transform(joint: &Joint) -> Transform3D {
     transform_from_pose(origin, parent.link.clone(), child.link.clone())
 }
 
-/// Send a batch of static transforms as a single chunk.
+/// Emit a batch of static transforms as a single chunk.
 ///
 /// We always do this statically for URDF, because this allows users to override them later
 /// on any other transform entity of their choice.
-fn send_static_transforms_batch(
-    tx: &Sender,
-    store_id: &StoreId,
+fn emit_static_transforms_batch(
+    emit: &mut dyn FnMut(Chunk),
     transforms_path: &EntityPath,
     transforms: &[Transform3D],
 ) -> anyhow::Result<()> {
@@ -221,7 +239,7 @@ fn send_static_transforms_batch(
         chunk = chunk.with_archetype(RowId::new(), TimePoint::STATIC, transform);
     }
 
-    send_chunk_builder(tx, store_id, chunk)
+    emit_chunk_builder(emit, chunk)
 }
 
 fn transform_from_pose(
@@ -254,25 +272,22 @@ fn instance_poses_from_pose(origin: &urdf_rs::Pose, scale: Option) -> Ins
     poses
 }
 
-fn send_instance_pose_with_frame(
-    tx: &Sender,
-    store_id: &StoreId,
+fn emit_instance_pose_with_frame(
+    emit: &mut dyn FnMut(Chunk),
     entity_path: EntityPath,
     timepoint: &TimePoint,
     origin: &urdf_rs::Pose,
     parent_frame: String,
     scale: Option,
 ) -> anyhow::Result<()> {
-    send_archetype(
-        tx,
-        store_id,
+    emit_archetype(
+        emit,
         entity_path.clone(),
         timepoint,
         &instance_poses_from_pose(origin, scale),
     )?;
-    send_archetype(
-        tx,
-        store_id,
+    emit_archetype(
+        emit,
         entity_path,
         timepoint,
         &CoordinateFrame::update_fields().with_frame(parent_frame),
@@ -289,12 +304,11 @@ fn extract_instance_scale(geometry: &Geometry) -> Option {
     }
 }
 
-fn log_link(
+fn emit_link(
     urdf_tree: &UrdfTree,
-    tx: &Sender,
-    store_id: &StoreId,
     timepoint: &TimePoint,
     link: &urdf_rs::Link,
+    emit: &mut dyn FnMut(Chunk),
 ) -> anyhow::Result<()> {
     let urdf_rs::Link {
         name: link_name,
@@ -323,9 +337,8 @@ fn log_link(
 
         // A visual geometry has no frame ID of its own and has a constant pose,
         // so we attach it to the link using an instance pose.
-        send_instance_pose_with_frame(
-            tx,
-            store_id,
+        emit_instance_pose_with_frame(
+            emit,
             visual_entity_path.clone(),
             timepoint,
             origin,
@@ -333,10 +346,9 @@ fn log_link(
             instance_scale,
         )?;
 
-        log_geometry(
+        emit_geometry(
+            emit,
             urdf_tree,
-            tx,
-            store_id,
             visual_entity_path,
             geometry,
             material,
@@ -356,9 +368,8 @@ fn log_link(
 
         // A collision geometry has no frame ID of its own and has a constant pose,
         // so we attach it to the link using an instance pose.
-        send_instance_pose_with_frame(
-            tx,
-            store_id,
+        emit_instance_pose_with_frame(
+            emit,
             collision_entity_path.clone(),
             timepoint,
             origin,
@@ -366,10 +377,9 @@ fn log_link(
             instance_scale,
         )?;
 
-        log_geometry(
+        emit_geometry(
+            emit,
             urdf_tree,
-            tx,
-            store_id,
             collision_entity_path.clone(),
             geometry,
             None,
@@ -379,9 +389,8 @@ fn log_link(
         if false {
             // TODO(michael): consider hiding collision geometries by default.
             // TODO(#6541): the viewer should respect the `Visible` component.
-            send_chunk_builder(
-                tx,
-                store_id,
+            emit_chunk_builder(
+                emit,
                 ChunkBuilder::new(ChunkId::new(), collision_entity_path).with_component_batch(
                     RowId::new(),
                     timepoint.clone(),
@@ -434,10 +443,9 @@ fn load_ros_resource(
     }
 }
 
-fn log_geometry(
+fn emit_geometry(
+    emit: &mut dyn FnMut(Chunk),
     urdf_tree: &UrdfTree,
-    tx: &Sender,
-    store_id: &StoreId,
     entity_path: EntityPath,
     geometry: &Geometry,
     material: Option<&urdf_rs::Material>,
@@ -464,14 +472,13 @@ fn log_geometry(
                 }
             }
 
-            send_archetype(tx, store_id, entity_path, timepoint, &asset3d)?;
+            emit_archetype(emit, entity_path, timepoint, &asset3d)?;
         }
         Geometry::Box {
             size: Vec3([x, y, z]),
         } => {
-            send_archetype(
-                tx,
-                store_id,
+            emit_archetype(
+                emit,
                 entity_path,
                 timepoint,
                 &re_sdk_types::archetypes::Boxes3D::from_sizes([Vec3D::new(
@@ -482,9 +489,8 @@ fn log_geometry(
         }
         Geometry::Cylinder { radius, length } => {
             // URDF and Rerun both use Z as the main axis
-            send_archetype(
-                tx,
-                store_id,
+            emit_archetype(
+                emit,
                 entity_path,
                 timepoint,
                 &re_sdk_types::archetypes::Cylinders3D::from_lengths_and_radii(
@@ -496,9 +502,8 @@ fn log_geometry(
         }
         Geometry::Capsule { radius, length } => {
             // URDF and Rerun both use Z as the main axis
-            send_archetype(
-                tx,
-                store_id,
+            emit_archetype(
+                emit,
                 entity_path,
                 timepoint,
                 &re_sdk_types::archetypes::Capsules3D::from_lengths_and_radii(
@@ -509,9 +514,8 @@ fn log_geometry(
             )?;
         }
         Geometry::Sphere { radius } => {
-            send_archetype(
-                tx,
-                store_id,
+            emit_archetype(
+                emit,
                 entity_path,
                 timepoint,
                 &re_sdk_types::archetypes::Ellipsoids3D::from_radii([*radius as f32])
diff --git a/crates/store/re_data_loader/src/loader_urdf/robot_description_parser.rs b/crates/store/re_data_loader/src/loader_urdf/robot_description_parser.rs
new file mode 100644
index 000000000000..96f6c568b47e
--- /dev/null
+++ b/crates/store/re_data_loader/src/loader_urdf/robot_description_parser.rs
@@ -0,0 +1,30 @@
+//! Utilities for parsing URDF XML from strings, like e.g. a ROS `robot_description` topic.
+
+use re_chunk::EntityPath;
+use re_log_types::TimePoint;
+
+/// Parses URDF XML and returns the chunks emitted by Rerun's built-in URDF loader.
+///
+/// `include_joint_transforms` controls whether static joint transforms from the URDF
+/// are emitted in addition to the robot geometry.
+pub(crate) fn build_urdf_chunks_from_xml(
+    urdf_xml: &str,
+    entity_path_prefix: &Option,
+    timepoint: &TimePoint,
+    include_joint_transforms: bool,
+) -> anyhow::Result> {
+    let robot = urdf_rs::read_from_string(urdf_xml)?;
+
+    let mut chunks = Vec::new();
+
+    super::emit_robot(
+        &mut |chunk| chunks.push(chunk),
+        robot,
+        None,
+        entity_path_prefix,
+        timepoint,
+        include_joint_transforms,
+    )?;
+
+    Ok(chunks)
+}
diff --git a/crates/top/rerun/src/commands/mcap/mod.rs b/crates/top/rerun/src/commands/mcap/mod.rs
index 89251fa1714a..5f05f040f5aa 100644
--- a/crates/top/rerun/src/commands/mcap/mod.rs
+++ b/crates/top/rerun/src/commands/mcap/mod.rs
@@ -21,8 +21,13 @@ fn possible_timeline_types() -> impl clap::builder::TypedValueParser {
 }
 
 fn possible_decoders() -> clap::builder::PossibleValuesParser {
-    static DECODER_IDS: std::sync::LazyLock> =
-        std::sync::LazyLock::new(|| DecoderRegistry::all_builtin(true).all_identifiers());
+    static DECODER_IDS: std::sync::LazyLock> = std::sync::LazyLock::new(|| {
+        let mut ids = DecoderRegistry::all_builtin(true).all_identifiers();
+        ids.push("urdf".to_owned());
+        ids.sort();
+        ids.dedup();
+        ids
+    });
     clap::builder::PossibleValuesParser::new(
         DECODER_IDS.iter().map(String::as_str).collect::>(),
     )
diff --git a/docs/content/concepts/logging-and-ingestion/mcap/cli-reference.md b/docs/content/concepts/logging-and-ingestion/mcap/cli-reference.md
index 14fafb40db5a..6973773de9bd 100644
--- a/docs/content/concepts/logging-and-ingestion/mcap/cli-reference.md
+++ b/docs/content/concepts/logging-and-ingestion/mcap/cli-reference.md
@@ -50,6 +50,9 @@ rerun mcap convert input.mcap -d protobuf -d stats -o output.rrd
 # Use only ROS2 semantic interpretation for robotics data
 rerun mcap convert input.mcap -d ros2msg -o output.rrd
 
+# Add robot geometry from ROS robot_description topics
+rerun mcap convert input.mcap -d ros2msg -d urdf -o output.rrd
+
 # Combine multiple decoders for comprehensive data access
 rerun mcap convert input.mcap -d ros2msg -d raw -d recording_info -o output.rrd
 ```
@@ -63,6 +66,7 @@ Decoding:
 - **`metadata`**: Extract metadata records into RRD `__properties`, if present
 - **`protobuf`**: Decode protobuf messages using into generic Arrow data without Rerun visualization components
 - **`recording_info`**: Extract recording session metadata
+- **`urdf`**: Use Rerun's built-in URDF loader when a ROS 2 `/robot_description` topic is present
 
 Semantic:
 - **`foxglove`**: Semantic interpretation of Foxglove Protobuf messages
@@ -83,8 +87,9 @@ rerun mcap convert input.mcap \
     -d stats \
     -d metadata \
     -d protobuf \
+    -d recording_info \
+    -d urdf \
     -d ros2msg \
     -d foxglove \
-    -d recording_info \
     -o output.rrd
 ```
diff --git a/docs/content/concepts/logging-and-ingestion/mcap/decoders-explained.md b/docs/content/concepts/logging-and-ingestion/mcap/decoders-explained.md
index 8b85b621abe1..e2c0759ae222 100644
--- a/docs/content/concepts/logging-and-ingestion/mcap/decoders-explained.md
+++ b/docs/content/concepts/logging-and-ingestion/mcap/decoders-explained.md
@@ -45,6 +45,12 @@ The `raw` decoder preserves the original message bytes without any interpretatio
 
 The `recording_info` decoder extracts metadata about the recording session and capture context, creating metadata entities with information about recording timestamps, source system details, and capture software versions.
 
+## The URDF option
+
+The `urdf` option uses Rerun's built-in URDF loader if there is a ROS 2 string topic named `/robot_description`, logging the robot model as static 3D geometry. In this MCAP workflow, joint transforms are not loaded from the URDF itself; they are expected to come from TF topics in the MCAP (e.g. `/tf` or `/tf_static`).
+
+For general information about how to load URDF files, see [here](../../../howto/logging-and-ingestion/urdf.md).
+
 ## Decoder selection and performance
 
 ### Selecting decoders
@@ -57,6 +63,9 @@ rerun mcap convert input.mcap -d protobuf -d stats -o output.rrd
 
 # Use multiple decoders for different perspectives
 rerun mcap convert input.mcap -d ros2msg -d raw -d recording_info -o output.rrd
+
+# Add robot geometry from ROS robot_description topics
+rerun mcap convert input.mcap -d ros2msg -d urdf -o output.rrd
 ```
 
 ## Accessing decoder data
@@ -66,6 +75,7 @@ Each decoder creates different types of components on entity paths (derived from
 - Data from the `ros2msg` decoder and supported Foxglove messages appears as native Rerun visualization archetypes (see [here](message-formats.md#overview) for an overview)
 - Other data from the `protobuf` or `ros2_reflection` decoders appears as structured components that can be queried by field name or manually added to certain views ([example](message-formats.md#example-timeseries-plot-for-custom-message-scalars))
 - Data from the `raw` decoder appears as blob components containing the original message bytes
+- Data from the `urdf` option appears as static 3D robot geometry loaded from the ROS 2 `/robot_description` topic
 - Metadata from `schema`, `stats`, and `recording_info` decoders appears as dedicated metadata entities
 
 For more information on querying data and working with archetypes, see the [Data Queries documentation](../../../howto/query-and-transform/get-data-out.md).
diff --git a/docs/content/howto/logging-and-ingestion/mcap.md b/docs/content/howto/logging-and-ingestion/mcap.md
index 220d9bb24a93..a805a065e646 100644
--- a/docs/content/howto/logging-and-ingestion/mcap.md
+++ b/docs/content/howto/logging-and-ingestion/mcap.md
@@ -66,6 +66,7 @@ Each layer extracts different types of information from the MCAP source and each
 - **`ros2msg`**: Provides semantic conversion of common ROS2 message types into Rerun's visualization components
 - **`ros2_reflection`**: Automatically decodes ROS2 messages using reflection
 - **`recording_info`**: Extracts recording metadata such as message counts, start time, and session information
+- **`urdf`**: Uses Rerun's built-in URDF loader when a ROS 2 `/robot_description` string topic is present
 
 By default, Rerun analyzes an MCAP file to determine which decoders are active to provide the most comprehensive view of your data, while avoiding duplication.
 You can also choose to activate only specific decoders that are relevant to your use case.
@@ -78,6 +79,9 @@ rerun mcap convert input.mcap -d protobuf -d stats -o output.rrd
 
 # Use multiple decoders for different perspectives
 rerun mcap convert input.mcap -d ros2msg -d raw -d recording_info -o output.rrd
+
+# Add robot geometry from robot_description topics
+rerun mcap convert input.mcap -d ros2msg -d urdf -o output.rrd
 ```
 
 For a detailed explanation of how each decoder works and when to use them, see [Decoders Explained](../../concepts/logging-and-ingestion/mcap/decoders-explained.md).

From 351a8e2c5d21052c7a4a09b3a23af47cc9331fb1 Mon Sep 17 00:00:00 2001
From: Isse 
Date: Tue, 17 Mar 2026 19:40:36 +0100
Subject: [PATCH 169/513] Add back collapsing time gaps

### Related

- Closes RR-4046

### What

These are now based on simpler heuristics than before.

Before:
- Based on time histogram for finding gaps.
- Each row in every chunk inserted into the time histogram.
- Looking for gaps to collapse every frame.

Here:
- Only done for chunks with a rrd manifest.
- Based on chunk ranges for chunks with lower duration than 10
seconds/sequences. Each row for chunks larger than 20% of the timeline
range.
- Calculated first time when we've loaded the whole rrd manifest, and
then also when all chunks larger than 20% of the timeline range are
loaded.

Source-Ref: d98a6619f33d9669e5e94fbe67cc3cb1fd79b1f6
---
 Cargo.lock                                    |   1 +
 crates/store/re_entity_db/Cargo.toml          |   1 +
 crates/store/re_entity_db/src/entity_db.rs    |   7 +
 .../re_entity_db/src/rrd_manifest_index.rs    |  43 +++++
 .../collapsed_time_ranges.rs                  | 153 ++++++++++++++++++
 crates/viewer/re_test_context/src/lib.rs      |   1 +
 crates/viewer/re_time_panel/src/time_panel.rs |   9 +-
 .../time_panel_partially_unloaded_chunks.png  |   4 +-
 .../snapshots/time_panel_two_sections.png     |   4 +-
 .../re_time_panel/tests/time_panel_tests.rs   |  60 ++++++-
 10 files changed, 277 insertions(+), 6 deletions(-)
 create mode 100644 crates/store/re_entity_db/src/rrd_manifest_index/collapsed_time_ranges.rs

diff --git a/Cargo.lock b/Cargo.lock
index 894d61e5dc34..d4425ac70a67 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -9036,6 +9036,7 @@ dependencies = [
  "static_assertions",
  "tap",
  "thiserror 2.0.18",
+ "vec1",
  "web-time",
 ]
 
diff --git a/crates/store/re_entity_db/Cargo.toml b/crates/store/re_entity_db/Cargo.toml
index c6dbd2d07553..554634586a23 100644
--- a/crates/store/re_entity_db/Cargo.toml
+++ b/crates/store/re_entity_db/Cargo.toml
@@ -59,6 +59,7 @@ serde = { workspace = true, features = ["derive", "rc"], optional = true }
 static_assertions.workspace = true
 tap.workspace = true
 thiserror.workspace = true
+vec1.workspace = true
 web-time.workspace = true
 
 [dev-dependencies]
diff --git a/crates/store/re_entity_db/src/entity_db.rs b/crates/store/re_entity_db/src/entity_db.rs
index a0b2169a2325..be6f505e1920 100644
--- a/crates/store/re_entity_db/src/entity_db.rs
+++ b/crates/store/re_entity_db/src/entity_db.rs
@@ -649,6 +649,13 @@ impl EntityDb {
         self.storage_engine.read().store().time_range(timeline)
     }
 
+    /// Data time ranges for gap collapsing in the time panel.
+    ///
+    /// Only available for redap connections with manifests.
+    pub fn data_time_ranges_for(&self, timeline: &TimelineName) -> Option<&[AbsoluteTimeRange]> {
+        self.rrd_manifest_index.data_time_ranges_for(timeline)
+    }
+
     /// Returns the total number of temporal rows on the given timeline across all entities.
     pub fn num_temporal_rows_on_timeline(&self, timeline: &TimelineName) -> u64 {
         self.data_meta_per_timeline.row_count_for_timeline(timeline)
diff --git a/crates/store/re_entity_db/src/rrd_manifest_index.rs b/crates/store/re_entity_db/src/rrd_manifest_index.rs
index a4450fc1e659..08dc188b73a9 100644
--- a/crates/store/re_entity_db/src/rrd_manifest_index.rs
+++ b/crates/store/re_entity_db/src/rrd_manifest_index.rs
@@ -16,6 +16,7 @@ use crate::chunk_requests::ChunkBatchRequest;
 pub use crate::chunk_requests::{ChunkPromise, ChunkRequests, RequestInfo};
 
 mod chunk_prioritizer;
+mod collapsed_time_ranges;
 mod sorted_temporal_chunks;
 mod time_range_merger;
 
@@ -170,6 +171,10 @@ pub struct RrdManifestIndex {
     /// Full time range per timeline
     timelines: BTreeMap,
 
+    /// Cached data time ranges per timeline, used for gap collapsing in the time panel.
+    /// Computed from chunk time ranges when the manifest is complete.
+    data_time_ranges: BTreeMap>,
+
     pub entity_tree: crate::EntityTree,
     entity_has_static_data: IntSet,
 
@@ -415,6 +420,8 @@ impl RrdManifestIndex {
                 re_format::format_uint(num_root_chunks)
             );
         }
+
+        self.data_time_ranges = collapsed_time_ranges::compute_data_time_ranges(&self.root_chunks);
     }
 
     /// The manifest as it currently stands.
@@ -459,6 +466,8 @@ impl RrdManifestIndex {
     }
 
     fn mark_roots_as(&mut self, store: &ChunkStore, chunk_id: &ChunkId, new_state: LoadState) {
+        re_tracing::profile_function!();
+
         let store_kind = store.id().kind();
 
         let root_chunk_ids = store.find_root_chunks(chunk_id);
@@ -469,6 +478,9 @@ impl RrdManifestIndex {
                 "Added chunk that was not part of the chunk index",
             );
         } else {
+            // Track which timelines had a large chunk transition to loaded
+            let mut timelines_to_recalculate: Vec = Vec::new();
+
             for chunk_id in root_chunk_ids {
                 if let Some(chunk_info) = self.root_chunks.get_mut(&chunk_id) {
                     let old_state = chunk_info.state;
@@ -495,6 +507,16 @@ impl RrdManifestIndex {
                             }
                         }
                     }
+
+                    // When a large chunk gets loaded, recalculate data ranges for its timelines
+                    if new_state == LoadState::FullyLoaded && old_state != LoadState::FullyLoaded {
+                        timelines_to_recalculate.extend(
+                            collapsed_time_ranges::should_recalculate_for_chunk(
+                                chunk_info,
+                                &self.timelines,
+                            ),
+                        );
+                    }
                 } else {
                     warn_when_editing_recording(
                         store_kind,
@@ -502,9 +524,27 @@ impl RrdManifestIndex {
                     );
                 }
             }
+
+            for timeline_name in timelines_to_recalculate {
+                if let Some(ranges) = collapsed_time_ranges::calculate_data_ranges_for_timeline(
+                    &self.root_chunks,
+                    &self.timelines,
+                    store,
+                    &timeline_name,
+                ) {
+                    self.data_time_ranges.insert(timeline_name, ranges);
+                }
+            }
         }
     }
 
+    /// Data time ranges for the given timeline, used for gap collapsing in the time panel.
+    ///
+    /// Returns `None` if the manifest is not yet complete or if there are no ranges.
+    pub fn data_time_ranges_for(&self, timeline: &TimelineName) -> Option<&[AbsoluteTimeRange]> {
+        self.data_time_ranges.get(timeline).map(|v| v.as_slice())
+    }
+
     /// When do we have data on this timeline?
     pub fn timeline_range(&self, timeline: &TimelineName) -> Option {
         self.timelines.get(timeline).copied()
@@ -701,6 +741,7 @@ impl re_byte_size::SizeBytes for RrdManifestIndex {
             root_chunks: virtual_chunks,
             chunk_prioritizer,
             timelines,
+            data_time_ranges,
             full_uncompressed_size: _,
             manifest_complete: _,
         } = self;
@@ -713,6 +754,7 @@ impl re_byte_size::SizeBytes for RrdManifestIndex {
             + virtual_chunks.heap_size_bytes()
             + chunk_prioritizer.heap_size_bytes()
             + timelines.heap_size_bytes()
+            + data_time_ranges.heap_size_bytes()
     }
 }
 
@@ -731,6 +773,7 @@ impl MemUsageTreeCapture for RrdManifestIndex {
             root_chunks: virtual_chunks,
             chunk_prioritizer,
             timelines,
+            data_time_ranges: _,
             full_uncompressed_size: _,
             manifest_complete: _,
         } = self;
diff --git a/crates/store/re_entity_db/src/rrd_manifest_index/collapsed_time_ranges.rs b/crates/store/re_entity_db/src/rrd_manifest_index/collapsed_time_ranges.rs
new file mode 100644
index 000000000000..2f29fb05abd3
--- /dev/null
+++ b/crates/store/re_entity_db/src/rrd_manifest_index/collapsed_time_ranges.rs
@@ -0,0 +1,153 @@
+use std::collections::BTreeMap;
+
+use ahash::HashMap;
+use re_chunk::{ChunkId, TimelineName};
+use re_chunk_store::ChunkStore;
+use re_log_types::AbsoluteTimeRange;
+
+use super::RootChunkInfo;
+
+/// Chunks spanning more than 20% of the full timeline are considered "large"
+/// and will be scanned for internal gaps once loaded.
+fn large_chunk_threshold(timeline_range: AbsoluteTimeRange) -> u64 {
+    (timeline_range.abs_length() / 5).max(10)
+}
+
+/// Sort ranges by start time and merge overlapping/adjacent ones.
+fn merge_and_sort_ranges(ranges: &[AbsoluteTimeRange]) -> Vec {
+    let Some(sorted) = vec1::Vec1::try_from_vec({
+        let mut v = ranges.to_vec();
+        v.sort_by_key(|r| r.min.as_i64());
+        v
+    })
+    .ok() else {
+        return Vec::new();
+    };
+
+    let (first, rest) = sorted.split_off_first();
+    let mut merged = vec1::vec1![first];
+    for range in rest {
+        let last = merged.last_mut();
+        if range.min.as_i64() <= last.max.as_i64() + 1 {
+            if range.max.as_i64() > last.max.as_i64() {
+                last.max = range.max;
+            }
+        } else {
+            merged.push(range);
+        }
+    }
+
+    merged.into()
+}
+
+/// Split a time column into sub-ranges at gaps larger than `gap_threshold`.
+fn split_time_column_at_gaps(
+    time_column: &re_chunk::TimeColumn,
+    gap_threshold: u64,
+) -> Vec {
+    let times = time_column.times_raw();
+    if times.len() < 2 || !time_column.is_sorted() {
+        return vec![time_column.time_range()];
+    }
+
+    let mut ranges = Vec::new();
+    let mut start = times[0];
+    let mut prev = times[0];
+
+    for &t in ×[1..] {
+        if prev.abs_diff(t) > gap_threshold {
+            ranges.push(AbsoluteTimeRange::new(start, prev));
+            start = t;
+        }
+        prev = t;
+    }
+    ranges.push(AbsoluteTimeRange::new(start, prev));
+    ranges
+}
+
+/// Compute data time ranges from manifest chunk ranges.
+///
+/// This merges all chunk time ranges per timeline to detect gaps between chunks.
+/// Chunks spanning large durations are tracked for recalculation when they get loaded.
+pub fn compute_data_time_ranges(
+    root_chunks: &HashMap,
+) -> BTreeMap> {
+    re_tracing::profile_function!();
+
+    let mut ranges_per_timeline: BTreeMap> = BTreeMap::new();
+
+    for chunk_info in root_chunks.values() {
+        for (timeline_name, temporal_info) in &chunk_info.temporals {
+            ranges_per_timeline
+                .entry(*timeline_name)
+                .or_default()
+                .push(temporal_info.time_range);
+        }
+    }
+
+    let mut result = BTreeMap::new();
+    for (timeline_name, ranges) in &ranges_per_timeline {
+        let merged = merge_and_sort_ranges(ranges);
+        result.insert(*timeline_name, merged);
+    }
+    result
+}
+
+/// Refine data time ranges for a timeline by scanning all loaded physical chunks
+/// for internal gaps within large time ranges.
+pub fn calculate_data_ranges_for_timeline(
+    root_chunks: &HashMap,
+    timelines: &BTreeMap,
+    store: &ChunkStore,
+    timeline_name: &TimelineName,
+) -> Option> {
+    re_tracing::profile_function!();
+
+    let &timeline_range = timelines.get(timeline_name)?;
+    let threshold = large_chunk_threshold(timeline_range);
+
+    let mut all_ranges: Vec = Vec::new();
+
+    // Loaded chunks: scan for internal gaps in large ones
+    for chunk in store.physical_chunks() {
+        if let Some(time_col) = chunk.timelines().get(timeline_name) {
+            let range = time_col.time_range();
+            let duration = range.abs_length();
+            if duration > threshold {
+                all_ranges.extend(split_time_column_at_gaps(time_col, threshold));
+            } else {
+                all_ranges.push(range);
+            }
+        }
+    }
+
+    // Unloaded chunks: include their manifest ranges as-is
+    for chunk_info in root_chunks.values() {
+        if !chunk_info.is_fully_loaded()
+            && let Some(temporal_info) = chunk_info.temporals.get(timeline_name)
+        {
+            all_ranges.push(temporal_info.time_range);
+        }
+    }
+
+    Some(merge_and_sort_ranges(&all_ranges))
+}
+
+/// Check if a newly loaded chunk is "large" enough to warrant recalculating data ranges
+/// for its timelines.
+pub fn should_recalculate_for_chunk(
+    chunk_info: &RootChunkInfo,
+    timelines: &BTreeMap,
+) -> Vec {
+    let mut result = Vec::new();
+    for (timeline_name, temporal_info) in &chunk_info.temporals {
+        if let Some(&timeline_range) = timelines.get(timeline_name) {
+            let threshold = large_chunk_threshold(timeline_range);
+            let duration = temporal_info.time_range.abs_length();
+            if duration > threshold {
+                result.push(*timeline_name);
+            }
+        }
+    }
+    result
+}
diff --git a/crates/viewer/re_test_context/src/lib.rs b/crates/viewer/re_test_context/src/lib.rs
index a8d475b76d3e..c809eabb9fa1 100644
--- a/crates/viewer/re_test_context/src/lib.rs
+++ b/crates/viewer/re_test_context/src/lib.rs
@@ -562,6 +562,7 @@ impl TestContext {
         let store_hub = self.store_hub.get_mut();
         let active_recording = store_hub.entity_db_mut(&self.recording_store_id).unwrap();
         active_recording.add_rrd_manifest_message(rrd_manifest);
+        active_recording.mark_rrd_manifest_complete();
 
         // Pretend like we are connected to a real redap server:
         active_recording.data_source = Some(re_log_channel::LogSource::RedapGrpcStream {
diff --git a/crates/viewer/re_time_panel/src/time_panel.rs b/crates/viewer/re_time_panel/src/time_panel.rs
index c3a1aa3c6d26..5ff6fa21ea70 100644
--- a/crates/viewer/re_time_panel/src/time_panel.rs
+++ b/crates/viewer/re_time_panel/src/time_panel.rs
@@ -1669,7 +1669,14 @@ fn initialize_time_ranges_ui(
     if let Some(time_type) = store_ctx.time_ctrl.time_type()
         && let Some(full_timeline_range) = store_ctx.db.time_range_for(timeline)
     {
-        let timeline_axis = TimelineAxis::new(time_type, &[full_timeline_range]);
+        let data_ranges = store_ctx.db.data_time_ranges_for(timeline);
+        let timeline_axis = if let Some(ranges) = data_ranges
+            && !ranges.is_empty()
+        {
+            TimelineAxis::new(time_type, ranges)
+        } else {
+            TimelineAxis::new(time_type, &[full_timeline_range])
+        };
         time_view = time_view.or_else(|| Some(view_everything(&x_range, &timeline_axis)));
         time_range.extend(timeline_axis.ranges);
     }
diff --git a/crates/viewer/re_time_panel/tests/snapshots/time_panel_partially_unloaded_chunks.png b/crates/viewer/re_time_panel/tests/snapshots/time_panel_partially_unloaded_chunks.png
index 468f4fd1f30c..6d2e38bb0e19 100644
--- a/crates/viewer/re_time_panel/tests/snapshots/time_panel_partially_unloaded_chunks.png
+++ b/crates/viewer/re_time_panel/tests/snapshots/time_panel_partially_unloaded_chunks.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:7ded2817a215499e16d0bf1a2109a06f1e10047bbb29ca771497c4fc9910ff38
-size 34605
+oid sha256:acc1c4873ce3ecef388e710979bdd155a36121b68891f86d117e9d0cef3bee72
+size 34408
diff --git a/crates/viewer/re_time_panel/tests/snapshots/time_panel_two_sections.png b/crates/viewer/re_time_panel/tests/snapshots/time_panel_two_sections.png
index e5007b83fe6e..ec5defc8d9fe 100644
--- a/crates/viewer/re_time_panel/tests/snapshots/time_panel_two_sections.png
+++ b/crates/viewer/re_time_panel/tests/snapshots/time_panel_two_sections.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:ef7d8815d5f73a3b35b70545110beff6af6f443743b0b2df7d12af4c739b1fd8
-size 38602
+oid sha256:d8d8b75ea8bbac5c3eb05189475aa40cb4f3f773b06238373e6cc341105f7862
+size 38587
diff --git a/crates/viewer/re_time_panel/tests/time_panel_tests.rs b/crates/viewer/re_time_panel/tests/time_panel_tests.rs
index 4f8dfc742282..44573df53b82 100644
--- a/crates/viewer/re_time_panel/tests/time_panel_tests.rs
+++ b/crates/viewer/re_time_panel/tests/time_panel_tests.rs
@@ -2,7 +2,7 @@
 #![expect(clippy::unwrap_used)] // Fine for tests
 
 use re_chunk::Chunk;
-use re_chunk_store::{LatestAtQuery, RowId};
+use re_chunk_store::{ChunkStoreConfig, LatestAtQuery, RowId};
 use re_entity_db::InstancePath;
 use re_log_types::example_components::{MyPoint, MyPoints};
 use re_log_types::{EntityPath, TimeInt, TimePoint, TimeReal, TimeType, Timeline, build_frame_nr};
@@ -13,6 +13,64 @@ use re_time_panel::TimePanel;
 use re_viewer_context::{CollapseScope, TimeControlCommand, TimeView, blueprint_timeline};
 use re_viewport_blueprint::ViewportBlueprint;
 
+/// Creates chunks with two groups of frames separated by a gap:
+/// entity/0 at frames [10, 11, 12, 15, 18, 100, 102, 104]
+/// entity/1 at frames [11, 12, 13, 16, 19, 101, 103, 105]
+fn create_sparse_chunks() -> Vec {
+    let points1 = MyPoint::from_iter(0..1);
+    let mut chunks = Vec::new();
+    for i in 0..2i64 {
+        let entity_path: EntityPath = format!("/entity/{i}").into();
+        for frame in [10, 11, 12, 15, 18, 100, 102, 104].map(|f| f + i) {
+            let chunk = Chunk::builder(entity_path.clone())
+                .with_sparse_component_batches(
+                    RowId::new(),
+                    [build_frame_nr(frame)],
+                    [(MyPoints::descriptor_points(), Some(&points1 as _))],
+                )
+                .build()
+                .unwrap();
+            chunks.push(chunk);
+        }
+    }
+    chunks
+}
+
+#[test]
+pub fn time_panel_two_sections() {
+    TimePanel::ensure_registered_subscribers();
+
+    let mut test_context = TestContext::new_with_store_info_and_config(
+        re_log_types::StoreInfo::testing(),
+        ChunkStoreConfig::COMPACTION_DISABLED,
+    );
+
+    let chunks = create_sparse_chunks();
+
+    let rrd_manifest = re_log_encoding::RrdManifest::build_in_memory_from_chunks(
+        test_context.active_store_id(),
+        chunks.iter(),
+    )
+    .unwrap();
+
+    test_context.add_rrd_manifest(rrd_manifest);
+    test_context.add_chunks(chunks.into_iter());
+    test_context.set_active_timeline("frame_nr");
+
+    let mut snapshot_results = SnapshotResults::new();
+    run_time_panel_and_save_snapshot(
+        &test_context,
+        TimePanel::default(),
+        "time_panel_two_sections",
+        &mut snapshot_results,
+        &RunOptions {
+            height: 300.0,
+            expand_all: false,
+            mark_chunks_used_or_missing: vec![],
+        },
+    );
+}
+
 #[test]
 pub fn time_panel_dense_data() {
     TimePanel::ensure_registered_subscribers();

From dfc733cced0c17945cfa2996134dcc52467c9d43 Mon Sep 17 00:00:00 2001
From: Andreas Reich 
Date: Wed, 18 Mar 2026 09:34:04 +0100
Subject: [PATCH 170/513] Remove old mostly outdated design docs

Nobody maintained these.
We have now some more internal docs on overarching design that replaces
this.

Source-Ref: 909a9f5b6b56133b6097d8c283ddcdc304cf21ea
---
 design/batching.md            | 1022 ---------------------------------
 design/component_datatypes.md |   48 --
 design/space_views.md         |  170 ------
 design/spatial_transforms.md  |   34 --
 4 files changed, 1274 deletions(-)
 delete mode 100644 design/batching.md
 delete mode 100644 design/component_datatypes.md
 delete mode 100644 design/space_views.md
 delete mode 100644 design/spatial_transforms.md

diff --git a/design/batching.md b/design/batching.md
deleted file mode 100644
index ee33fd1a95c1..000000000000
--- a/design/batching.md
+++ /dev/null
@@ -1,1022 +0,0 @@
-- Start Date: 2023-03-18
-- RFC PR: [rerun-io/rerun#1610](https://github.com/rerun-io/rerun/pull/1610)
-- Tracking Issue: [rerun-io/rerun#1619](https://github.com/rerun-io/rerun/issues/1619)
-
-# End-to-end batching
-
-A design proposal for end-to-end data batches, from their creation on the client SDK and all the way until their end of life (GC).
-
-This redesign and the major changes involved also present an opportunity to address some of the long-standing design flaws in the datastore.
-
-- Where are we today?
-- Where do we want to go and why?
-- How do we get there?
-- What does the future look like beyond batching?
-- Try and keep note of all the good ideas that came up during discussions around batching
-- Gather all the information needed for a future technical blog post about the datastore
-
-TL;DR: A big braindump that covers a lot of the discussions and design thoughts that have been thrown around during the last few weeks, just to make sure it doesn't all get lost to time… I'm sure I've missed most of it though.
-
----
-
-Status: proposal
-
-- [Why](#why)
-- [Status quo](#status-quo)
-  * [Creation](#creation)
-  * [Transport](#transport)
-  * [Storage](#storage)
-  * [Write path](#write-path)
-  * [Read path](#read-path)
-    + [LatestAt (random-access like)](#latestat--random-access-like-)
-    + [Range (timeseries like)](#range--timeseries-like-)
-  * [Garbage Collection](#garbage-collection)
-  * [Save-to-disk](#save-to-disk)
-- [Proposal](#proposal)
-  * [Creation](#creation-1)
-  * [Transport](#transport-1)
-  * [Storage](#storage-1)
-  * [Write path](#write-path-1)
-  * [Read path](#read-path-1)
-    + [LatestAt (random-access like)](#latestat--random-access-like--1)
-    + [Range (timeseries like)](#range--timeseries-like--1)
-  * [Garbage Collection](#garbage-collection-1)
-  * [Save-to-disk](#save-to-disk-1)
-- [Implementation plan](#implementation-plan)
-- [Future work](#future-work)
-  * [Bucket compaction making a return](#bucket-compaction-making-a-return)
-  * [Dedicated storage for timeseries](#dedicated-storage-for-timeseries)
-  * [Recursive clears native to the store](#recursive-clears-native-to-the-store)
-  * [Native file format for writing the store to disk](#native-file-format-for-writing-the-store-to-disk)
-  * [Derived components & components converter mappings](#derived-components---components-converter-mappings)
-  * [Don't send full schemas of known/builtin components over the wire](#don-t-send-full-schemas-of-known-builtin-components-over-the-wire)
-  * [Post-GC latest-at correctness](#post-gc-latest-at-correctness)
-  * [Optimize linear backwards walk](#optimize-linear-backwards-walk)
-  * [Drop-after semantics (undo/redo)](#drop-after-semantics--undo-redo-)
-  * [Write our own arrow2-convert](#write-our-own-arrow2-convert)
-  * [The DataStore is its own server](#the-datastore-is-its-own-server)
-  * [Data might be a reference to an external storage system](#data-might-be-a-reference-to-an-external-storage-system)
-- [Q&A](#q-a)
-  * [What is the relationship between "component instances" and "instance keys"?](#what-is-the-relationship-between--component-instances--and--instance-keys--)
-  * [Are there any "special" components?](#are-there-any--special--components-)
-
----
-
-## Why
-
-The proposed implementation aims to address several issues and provide numerous benefits, including:
-- A significant reduction in the space overhead of `LogMsg`s during transport, in memory, and on disk.
-- Resolution of splat issues that currently require dedicated events/rows to function.
-- Resolution of the dreaded `MsgId` mismatch issues.
-- Replacement of the current hackish and partially broken garbage collection mechanism with a more viable one.
-- Should massively improve the speed of range queries (even more so for our scenes that span the entire time-range, e.g.: text, plot…).
-- Should (likely) vastly improve the loading speed of .rrd files (even more so on the web).
-
-Finally, these changes are expected to significantly simplify the DataStore codebase by completely eliminating component tables and their corresponding buckets.
-
-## Status quo
-
-This section describes the current state of things as of 2023-03-20.
-
-The data goes through several distinct stages during its lifetime:
-- Creation
-- Transport
-- Storage
-- Write path
-- Read path
-- GC
-- Save-to-disk
-
-### Creation
-
-At present, the client is limited to creating a single event at a time, corresponding to a single row of data. Each row contains N components, each of which can hold M instances for a given entity across P timelines.
-
-To begin the process, the SDK creates a `ComponentBundle`, which can be thought of as a data cell within a dataframe. This `ComponentBundle` is essentially a list of values for a specific component type. Keep in mind we only ever work with lists, rather than individual values.
-From this point forward, the individual values in these lists are referred to as "component instances" (:warning: "component instances" != "instance keys").
-
-```rust
-pub struct ComponentBundle {
-    /// The name of the Component, used as column name in the table `Field`.
-    name: ComponentName,
-
-    /// The Component payload `Array`.
-    value: ListArray,
-}
-```
-
-These `ComponentBundle`s are then packed into a `MsgBundle`:
-```rust
-pub struct MsgBundle {
-    /// A unique id per [`crate::LogMsg`].
-    pub msg_id: MsgId,
-    pub entity_path: EntityPath,
-    pub time_point: TimePoint,
-    pub components: Vec,
-}
-```
-which corresponds to _1 event_, i.e. 1 row's worth of data for 1 entity in N timelines.
-This event is uniquely identified with a `MsgId`, which is a `TUID` under the hood (wall-clock UID).
-
-The number of component instances for all columns, or components, in a given row is determined by examining the number of instances for the first entry in the `components` list. However, this approach has a significant flaw: all components must have the same number of instances.
-This requirement creates a situation where splats, with a single instance, and clears, with no instances, must be sent in separate `MsgBundle`s.
-
-As part of packing the `MsgBundle`, we convert the `MsgId` itself into a `ComponentBundle` by cloning it as many times as necessary to match the number of instances. We do this because we need the `MsgId` information later for garbage collection purposes.
-However, this approach presents a challenge for clears, which have zero instances. As a result, messages that contain zero instances cannot be garbage collected as of today.
-
-### Transport
-
-In general, data is transmitted as an Arrow table, where each row (of which there is only ever a single one at present) represents a multi-timeline event. Each column can be either a component or a timepoint, and each cell contains a list of component instances for a given component type.
-
-However, in practice, timepoints and components are stored separately within the chunk. This separation facilitates the identification and extraction of the time data, which receives special treatment in later stages (as discussed in the following sections).
-
-In concrete terms, the SDK transforms the `MsgBundle` into an `ArrowMsg`, which is ready to be serialized and sent over the wire:
-```rust
-pub struct ArrowMsg {
-    /// A unique id per [`crate::LogMsg`].
-    pub msg_id: MsgId,
-
-    /// Arrow schema
-    pub schema: arrow2::Schema,
-
-    /// Arrow chunk
-    pub chunk: arrow2::Chunk>,
-}
-```
-
-Taking a closer look at the `arrow2::Schema` of the `chunk` gives the complete story:
-```rust
-Schema {
-    fields: [
-        Field {
-            name: "timelines",
-            data_type: List(
-                Field {
-                    name: "item",
-                    data_type: Struct([
-                        Field { name: "timeline", data_type: Utf8, is_nullable: false, metadata: {} },
-                        Field { name: "type", data_type: UInt8, is_nullable: false, metadata: {} },
-                        Field { name: "time", data_type: Int64, is_nullable: false, metadata: {} },
-                    ]),
-                    is_nullable: true,
-                    metadata: {},
-                },
-            ),
-            is_nullable: false,
-            metadata: {},
-        },
-        Field {
-            name: "components",
-            data_type: Struct([
-                Field {
-                    name: "rerun.text_entry",
-                    data_type: List(
-                        Field {
-                            name: "item",
-                            data_type: Struct([
-                                Field { name: "body", data_type: Utf8, is_nullable: false, metadata: {} },
-                                Field { name: "level", data_type: Utf8, is_nullable: true, metadata: {} },
-                            ]),
-                            is_nullable: true,
-                            metadata: {},
-                        },
-                    ),
-                    is_nullable: false,
-                    metadata: {},
-                },
-                Field {
-                    name: "rerun.msg_id",
-                    data_type: List(
-                        Field {
-                            name: "item",
-                            data_type: Struct([
-                                Field { name: "time_ns", data_type: UInt64, is_nullable: false, metadata: {} },
-                                Field { name: "inc", data_type: UInt64, is_nullable: false, metadata: {} },
-                            ]),
-                            is_nullable: true,
-                            metadata: {},
-                        },
-                    ),
-                    is_nullable: false,
-                    metadata: {},
-                },
-            ]),
-            is_nullable: false,
-            metadata: {},
-        },
-    ],
-    metadata: {
-        "RERUN:entity_path": "logs/seg_demo_log",
-    },
-}
-```
-
-There are several important points to note:
-- The entity path is actually passed in the schema metadata.
-- This model already allows for batching, aside from the entity path just mentioned.
-- We're always sending the complete schemas of our builtin components, even though they are already known to the server by definition.
-- The actual instance keys can be omitted, in which case they'll be auto-generated on the server.
-- Notice the extra `msg_id` column!
-
-### Storage
-
-The data is actually stored in two places:
-- in `EntityDb`, where every raw `LogMsg` is kept around so that it can later be saved to disk,
-    - Due to the lack of batching, the size of the data sitting in `EntityDb` is actually completely dwarfed by the size of the schema metadata.
-- in the `DataStore`, where the data is stripped down into parts and indexed as needed for our latest-at semantics.
-    - That's the origin of the `MsgId` mismatch problem.
-
-In the store, indices are stored as tables on a per-timeline per-entity basis. To facilitate garbage collection _and_ set an upper bound on index sorting costs, these tables are further split into buckets based on both space and time thresholds:
-```
-IndexTable {
-    timeline: frame_nr
-    entity: this/that
-    size: 3 buckets for a total of 256 B across 8 total rows
-    buckets: [
-        IndexBucket {
-            index time bound: >= #0
-            size: 96 B across 2 rows
-                - frame_nr: from #41 to #41 (all inclusive)
-            data (sorted=true):
-            +----------+---------------+--------------+--------------------+
-            | frame_nr | rerun.point2d | rerun.rect2d | rerun.instance_key |
-            +----------+---------------+--------------+--------------------+
-            | 41       | 1             |              | 2                  |
-            | 41       |               | 3            | 2                  |
-            +----------+---------------+--------------+--------------------+
-        }
-        IndexBucket {
-            index time bound: >= #42
-            size: 96 B across 2 rows
-                - frame_nr: from #42 to #42 (all inclusive)
-            data (sorted=true):
-            +----------+---------------+--------------+--------------------+
-            | frame_nr | rerun.point2d | rerun.rect2d | rerun.instance_key |
-            +----------+---------------+--------------+--------------------+
-            | 42       |               | 1            | 2                  |
-            | 42       | 2             |              | 2                  |
-            +----------+---------------+--------------+--------------------+
-        }
-        IndexBucket {
-            index time bound: >= #43
-            size: 64 B across 2 rows
-                - frame_nr: from #43 to #44 (all inclusive)
-            data (sorted=true):
-            +----------+---------------+--------------+--------------------+
-            | frame_nr | rerun.point2d | rerun.rect2d | rerun.instance_key |
-            +----------+---------------+--------------+--------------------+
-            | 43       |               | 4            | 2                  |
-            | 44       | 3             |              | 2                  |
-            +----------+---------------+--------------+--------------------+
-        }
-    ]
-}
-```
-Note that, although the tables are bucketed, garbage collection of indices is actually entirely disabled today because the whole GC story is broken (see below).
-
-Components are stored on a per-component basis: i.e. all timelines and all entities share the same component storage.
-Like indices, they are split further into buckets (using both space and time thresholds), once again to facilitate garbage collection:
-```
-ComponentTable {
-    name: rerun.point2d
-    size: 2 buckets for a total of 96 B across 4 total rows
-    buckets: [
-        ComponentBucket {
-            size: 64 B across 3 rows
-            row range: from 0 to 0 (all inclusive)
-            archived: true
-            time ranges:
-                - log_time: from 19:37:35.713798Z to 19:37:35.713798Z (all inclusive)
-                - frame_nr: from #41 to #42 (all inclusive)
-            +-------------------------------------------------------------------+
-            | rerun.point2d                                                     |
-            +-------------------------------------------------------------------+
-            | []                                                                |
-            | [{x: 2.4033058, y: 8.535466}, {x: 4.051945, y: 7.6194324}         |
-            | [{x: 1.4975989, y: 6.17476}, {x: 2.4128711, y: 1.853013}          |
-            +-------------------------------------------------------------------+
-        }
-        ComponentBucket {
-            size: 32 B across 1 rows
-            row range: from 3 to 3 (all inclusive)
-            archived: false
-            time ranges:
-                - frame_nr: from #44 to #44 (all inclusive)
-            +-------------------------------------------------------------------+
-            | rerun.point2d                                                     |
-            +-------------------------------------------------------------------+
-            | [{x: 0.6296742, y: 6.7517242}, {x: 2.3393118, y: 8.770799}        |
-            +-------------------------------------------------------------------+
-        }
-    ]
-}
-```
-(The space thresholds don't actually work today due to the hacks we do in the GC implementation to work around `MsgId` mismatches)
-
-Storing data in both `EntityDb` and the component tables can lead to a significant increase in memory usage if not managed carefully, effectively doubling the storage requirements. Therefore, bucket compaction is currently disabled, leaving some performance on the table.
-
-Overall, this storage architecture maps well to our latest-at query semantics, but quite poorly to our range/timeseries semantics (see read path section below).
-
-The index buckets in the `DataStore` hold references to specific rows in the component tables, where the actual data is stored.
-At first, this may seem reasonable, but it's not the most efficient approach: Arrow data is already reference counted, so we're essentially referencing a set of references. This leads to a significant and expensive issue on the read path, particularly for range queries, as discussed below.
-
-### Write path
-
-The write path is fairly straightforward, with some complications arising from having to support timeless data and automatic generation of instance keys (we won't delve into those as they have no impact on batching).
-
-First, each component (i.e., column) is inserted into the currently active component table, which generates a set of globally unique and stable row numbers.
-
-Next, we retrieve or create the appropriate index based on the `EntityPath` and `Timeline` parameters. Using binary search, we locate the correct bucket and insert the row numbers.
-That's also when bucket splitting happen, which is its own can of worms, but is completely orthogonal to batching concerns.
-
-We also maintain an additional index that maps `MsgId`s to timepoints, which is crucial for multi-timeline views like the text view.
-
-### Read path
-
-#### LatestAt (random-access like)
-
-Once again, aside from timeless considerations, latest-at queries are nothing too surprising: it's mostly a matter of grabbing the appropriate index and binsearching for the right bucket.
-
-There are two subtleties though:
-1. Finding the right data might involve linearly walking backwards (across all buckets in the worst case).
-2. The result of the query is not the data itself, but rather the row numbers at which the data can be found in the component tables.
-
-This second subtlety has important implications. To actually retrieve the data, the caller needs to perform an extra `get` request, which involves a binsearch through the component tables (and it gets costly).
-
-#### Range (timeseries like)
-
-While range queries have some surprisingly tricky semantics (especially around the intersection of timeless and temporal data), operationally they behave pretty much like latest-at queries: grabbing the right index, binsearching for the right bucket, and starting iteration from there.
-
-However, the fact that we return row numbers instead of the actual data itself can have significant performance implications when it comes to range queries.
-For example, if you need to iterate through 100k values, you would need to run 100k `get` requests, which would require 100k binsearches in the component tables. This can be extremely costly and is a major reason why our ranged query scenes quickly become unusable as the dataset grows.
-
-### Garbage collection
-
-The current garbage collection mechanism was put together as a quick fix for the `MsgId`-mismatch issue, and it is largely unreliable.
-
-The algorithm works as follows: it finds the oldest component bucket based on the insertion order from the datastore, which doesn't make much semantic sense, and drops it. Then, it drops all component buckets that roughly cover the same time range. Finally, it returns all the `MsgId`s to the Viewer so that it can in turn clear its own data structures.
-This process is repeated in a loop until a sufficient amount of data has been dropped.
-
-Beyond these hacks, the logic in and of itself is fundamentally broken right now. Consider the following log calls:
-```python
-log_color("some/entity", frame_nr=0, [{255, 0, 0, 255}])
-log_point("some/entity", frame_nr=1, [{1.0, 1.0}])
-log_point("some/entity", frame_nr=2, [{2.0, 2.0}])
-log_point("some/entity", frame_nr=3, [{3.0, 3.0}])
-log_point("some/entity", frame_nr=4, [{4.0, 4.0}])
-log_point("some/entity", frame_nr=5, [{5.0, 5.0}])
-```
-
-Querying for `LatestAt("some/entity", ("frame_nr", 5))` will unsurprisingly yield a red point at `(5.0, 5.0)`.
-
-Now, consider what happens after running a GC that drops 50% of the data, leaving us with:
-```python
-log_point("some/entity", frame_nr=3, [{3.0, 3.0}])
-log_point("some/entity", frame_nr=4, [{4.0, 4.0}])
-log_point("some/entity", frame_nr=5, [{5.0, 5.0}])
-```
-
-Querying for `LatestAt("some/entity", ("frame_nr", 5))` will now yield a point at `(5.0, 5.0)` with whatever is currently defined as the default color, rather than red. This is just plain wrong.
-
-This happens because the GC blindly drops data rather than doing the correct thing: compacting what gets dropped into a latest-at kind of state and keeping that around for future queries.
-
-### Save-to-disk
-
-The current store cannot be dumped to disk, we rely on `EntityDb` to store all incoming `LogMsg`s and dump them to disk as-is if the user decides to save the recording.
-
----
-
-## Proposal
-
-The proposed design involves significant changes at every stage of the data lifecycle.
-
-### Creation
-
-The main difference is of course that the client can now accumulate events (i.e., rows) in a local table before sending them to the server.
-In practice this process of accumulation is handled behind the scenes by the SDK, and driven by both time and space thresholds ("accumulate at most 10MiB of raw data for no more than 50ms").
-
-To reflect the fact that we're passing tables of data around, I suggest we update the terminology.
-The current terms `ComponentBundle` and `MsgBundle` are vague, so let's use more descriptive terms instead:
-* `DataCell`: a uniform list of values for a given component type from a single log call.
-* `DataRow`: an event, a list of cells associated with an event ID, entity path, timepoint, and number of instances. Corresponds to a single SDK log call.
-* `DataTable`: a batch; a list of rows associated with a batch ID.
-
-Juggling between native and Arrow data interchangeably can be a cumbersome task in our current implementation. While we have some helper functions to facilitate this, the process is not as smooth as it could be.
-This is partly due to limitations in `arrow2-convert`, but also because some of our APIs are simply not optimized for this use case (yet).
-
-So, considering all the reasons above, here are all the new types involved.
-
-`DataCell`, which roughly fills the role of our current `ComponentBundle`:
-```rust
-/// A cell's worth of data, i.e. a uniform list of values for a given component type: `[C]`.
-pub struct DataCell {
-    /// Name of the component type used in this cell.
-    //
-    // TODO(cmc): We should consider storing this information within the values array itself, rather than
-    // outside of it. Arrow has the concept of extensions specifically for storing type metadata, but
-    // we have had some issues with it in the past. This is an opportunity to revisit and improve upon
-    // that implementation.
-    name: ComponentName,
-
-    /// A uniformly typed list of values for the given component type.
-    ///
-    /// Includes the data, its schema and probably soon the component metadata (e.g. the `ComponentName`).
-    values: Box,
-}
-
-impl DataCell {
-    /// Builds a new `DataCell` out of a uniform list of native component values.
-    pub fn from_native(values: Vec) -> Self { /* … */ }
-
-    /// Builds a new `DataCell` from an arrow array.
-    //
-    // TODO(cmc): We shouldn't have to specify the component name separately, this should be part of the
-    // metadata by using an extension.
-    pub fn from_arrow(name: ComponentName, values: Box) -> Self  { /* … */ }
-
-    /// Builds an empty `DataCell` from an arrow datatype.
-    //
-    // TODO(cmc): We shouldn't have to specify the component name separately, this should be part of the
-    // metadata by using an extension.
-    pub fn from_datatype(name: ComponentName, datatype: DataType) -> Self  { /* … */ }
-
-    /// Builds an empty `DataCell` from a component type.
-    //
-    // TODO(cmc): do keep in mind there's a future not too far away where components become a
-    // `(component, type)` tuple kinda thing.
-    pub fn from_component() -> Self  { /* … */ }
-
-    /// Returns the contents of the cell as an arrow array.
-    pub fn as_arrow(&self) -> Box { /* … */ }
-
-    /// Returns the contents of the cell as vector of native components.
-    //
-    // TODO(cmc): We could potentially keep the original native component values if the cell was created
-    // using `from_native`.
-    pub fn as_components(&self) -> Vec { /* … */ }
-}
-
-// TODO(cmc): Some convenient `From` implementations etc
-```
-(The "arrow extension" thing that is mentioned a lot in the comments above is [this](https://docs.rs/arrow2/latest/arrow2/datatypes/enum.DataType.html#variant.Extension).)
-
-`DataRow`, which fills the shoes of today's `MsgBundle`:
-```rust
-/// A row's worth of data, i.e. an event: a list of [`DataCell`]s associated with an auto-generated
-/// [`EventId`], a user-specified [`TimePoint`] and [`EntityPath`], and an expected number of
-/// instances.
-pub struct DataRow {
-    /// Auto-generated [`TUID`], uniquely identifying this event and keeping track of the client's
-    /// wall-clock.
-    event_id: EventId,
-
-    /// User-specified [`TimePoint`] for this event.
-    timepoint: TimePoint,
-
-    /// User-specified [`EntityPath`] for this event.
-    entity_path: EntityPath,
-
-    /// The expected number of values (== component instances) in each cell.
-    ///
-    /// Each cell must have either:
-    /// - 0 instance (clear),
-    /// - 1 instance (splat),
-    /// - `num_instances` instances (standard).
-    num_instances: u32,
-
-    /// The actual cells (== columns).
-    cells: Vec,
-}
-
-impl DataRow {
-    /// Builds a new `DataRow` out of a list of [`DataCell`]s.
-    pub fn from_cells(
-        timepoint: TimePoint,
-        entity_path: EntityPath,
-        num_instances: u32,
-        cells: Vec,
-    ) -> Self { /* … */ }
-
-    /// Append a cell to an existing row.
-    ///
-    /// Returns an error if the cell is not compatible with the row, e.g.:
-    /// - Trying to append a cell which contains neither `0`, `1` or `num_instances`.
-    /// - Trying to append the same component type more than once.
-    /// - Etc.
-    pub fn append_cell(&mut self, cell: DataCell) -> Result<()> { /* … */ }
-}
-
-// TODO(cmc): Some convenient `From` implementations etc
-```
-
-And finally `DataTable`, which is where the batching happens:
-```rust
-/// An entire table's worth of data, i.e. a batch: a list of [`DataRow`]s associated with an auto-generated
-/// [`BatchId`].
-struct DataTable {
-    /// Auto-generated [`TUID`], uniquely identifying this batch of data and keeping track of the
-    /// client's wall-clock.
-    batch_id: BatchId,
-
-    /// The entire column of [`EventId`]s.
-    event_id: Vec,
-
-    /// The entire column of [`TimePoint`]s.
-    timepoint: Vec,
-
-    /// The entire column of [`EntityPath`]s.
-    entity_path: Vec,
-
-    /// The entire column of `num_instances`.
-    num_instances: Vec,
-
-    /// All the rows for all the component columns.
-    ///
-    /// The cells are optional since not all rows will have data for every single component (i.e. the table is sparse).
-    rows: HashMap>>,
-}
-
-impl DataTable {
-    /// Builds a new `DataTable` out of a list of [`DataRow`]s.
-    pub fn from_rows(rows: Vec) -> Self { /* … */ }
-
-    /// Append a row to an existing table.
-    ///
-    /// Returns an error if the row is not compatible with the table.
-    pub fn append_row(&mut self, row: DataRow) -> Result<()> { /* … */ }
-}
-
-// TODO(cmc): Some convenient `From` implementations etc
-```
-
-These datastructures should get rid of all the issues that plague clears, splats and everything that ensue from `MsgId` mismatch issues.
-
-The SDK accumulates cells into rows into tables until either the space or time thresholds are reached, at which point the batch is ready for transport.
-
-Note that `DataCell`, `DataRow`, `DataTable` are all temporary constructs to help with the creation of data batches, they are not what gets sent over the wire (although `DataCell` pre-serializes its data as it is much more convenient to erase component data before passing it around).
-
-Only when a `DataTable` gets transformed into a `ArrowMsg` does serialization actually happen.
-`ArrowMsg` is what gets sent over the wire.
-
-### Transport
-
-`ArrowMsg` stays roughly the same in spirit: it's the fully serialized Arrow representation of a `DataTable`:
-```rust
-pub struct ArrowMsg {
-    /// Auto-generated [`TUID`], uniquely identifying this batch of data and keeping track of the
-    /// client's wall-clock.
-    pub batch_id: BatchId,
-
-    /// The schema for the entire table.
-    pub schema: arrow2::Schema,
-
-    /// The data for the entire table.
-    pub chunk: arrow2::Chunk>,
-}
-```
-
-The new schema is expected to look like this (-ish):
-```rust
-Schema {
-    fields: [
-        Field {
-            name: "event_id",
-            data_type: List(
-                Field {
-                    name: "item",
-                    data_type: Struct([
-                        Field { name: "time_ns", data_type: UInt64, is_nullable: false, metadata: {} },
-                        Field { name: "inc", data_type: UInt64, is_nullable: false, metadata: {} },
-                    ]),
-                    is_nullable: true,
-                    metadata: { "rerun.kind": "event_id" },
-                },
-            ),
-            is_nullable: false,
-            metadata: {},
-        },
-        // TODO(cmc): not the right type but you get the idea
-        Field {
-            name: "num_instances",
-            data_type: List(
-                Field {
-                    name: "item",
-                    data_type: UInt32,
-                    is_nullable: false,
-                    metadata: { "rerun.kind": "num_instances" },
-                },
-            ),
-            is_nullable: false,
-            metadata: {},
-        },
-        // TODO(cmc): not the right type but you get the idea
-        Field {
-            name: "entity_path",
-            data_type: List(
-                Field {
-                    name: "item",
-                    data_type: Utf8,
-                    is_nullable: false,
-                    metadata: { "rerun.kind": "entity_path" }
-                },
-            ),
-            is_nullable: false,
-            metadata: {},
-        },
-        Field {
-            name: "timepoint",
-            data_type: List(
-                Field {
-                    name: "item",
-                    data_type: Struct([
-                        Field { name: "timeline", data_type: Utf8, is_nullable: false, metadata: {} },
-                        Field { name: "type", data_type: UInt8, is_nullable: false, metadata: {} },
-                        Field { name: "time", data_type: Int64, is_nullable: false, metadata: {} },
-                    ]),
-                    is_nullable: true,
-                    metadata: {},
-                },
-            ),
-            is_nullable: false,
-            metadata: {},
-        },
-        Field {
-            name: "components",
-            data_type: Struct([
-                Field {
-                    name: "text_entry",
-                    data_type: List(
-                        Field {
-                            name: "item",
-                            data_type: Struct([
-                                Field { name: "body", data_type: Utf8, is_nullable: false, metadata: {} },
-                                Field { name: "level", data_type: Utf8, is_nullable: true, metadata: {} },
-                            ]),
-                            is_nullable: true,
-                            metadata: {
-                                "rerun.kind": "component".
-                                "rerun.component": "rerun.text_entry",
-                            },
-                        },
-                    ),
-                    is_nullable: false,
-                    metadata: {},
-                },
-            ]),
-            is_nullable: false,
-            metadata: {},
-        },
-    ],
-    metadata: {
-        "rerun.batch_id": "",
-    },
-}
-```
-
-The one major difference here is that `event_id`, `entity_path` and `num_instances` join `timepoints` in having dedicated, top-level columns.
-This is important as, like timepoints, these will have to be handled separately (and deserialized into native types!) in order to drive some of the logic in the store.
-
-Lastly, we inject the `BatchId` as metadata.
-`BatchId` isn't used for any logic yet, but comes in very handy for debug purposes.
-
-At this point we might want to sort the batch by `(event_id, entity_path)`, which will greatly improve data locality once it sits in the store (see storage section below).
-
-That's also an opportunity to pre-compact the data: if two rows share the same timepoints with different components, we could potentially merge them together… that's a bit more controversial though as it means either dropping some `EventId`s, or supporting multiple `EventId`s for a single event.
-
-One last thing that needs to be taken care of before actually sending the data is compression / dictionary-encoding of some kind.
-We already have `zstd` in place for that.
-
-### Storage
-
-One of the major change storage-wise is the complete removal of component tables: index tables now reference the Arrow data directly.
-With the new design, the Arrow buffers now store multiple rows of data. To reference a specific row, each index row must point to _a unit-length slice_ in a shared batch of Arrow data.
-
-That is the reason why sorting the batch on the client's end improves performance: it improves data locality in the store by making the shared batches follow the layout of the final buckets more closely.
-
-Assuming the following syntax for Arrow slices: `ArrowSlice()`, indices should now look roughly like the following, sorted by the timeline (`frame_nr`):
-```
-IndexTable {
-    timeline: frame_nr
-    entity: this/that
-    size: 3 buckets for a total of 256 B across 8 total rows
-    buckets: [
-        IndexBucket {
-            index time bound: >= #0
-            size: 96 B across 2 rows
-                - frame_nr: from #41 to #41 (all inclusive)
-            data (sorted=true):
-            +----------+----------+---------------+-------------------+-------------------+------------------------+
-            | event_id | frame_nr | num_instances | rerun.point2d     | rerun.rect2d      | rerun.instance_key     |
-            +----------+----------+---------------+-------------------+-------------------+------------------------+
-            | 1        | 41       | 2             | ArrowSlice(1,33)  |                   | ArrowSlice(0, 0)       |
-            | 2        | 41       | 2             |                   | ArrowSlice(1,25)  | ArrowSlice(0, 0)       |
-            +----------+----------+---------------+-------------------+-------------------+------------------------+
-        }
-        IndexBucket {
-            index time bound: >= #42
-            size: 96 B across 2 rows
-                - frame_nr: from #42 to #42 (all inclusive)
-            data (sorted=true):
-            +----------+----------+---------------+-------------------+-------------------+------------------------+
-            | event_id | frame_nr | num_instances | rerun.point2d     | rerun.rect2d      | rerun.instance_key     |
-            +----------+----------+---------------+-------------------+-------------------+------------------------+
-            | 3        | 42       | 2             |                   | ArrowSlice(2,25)  | ArrowSlice(0, 0)       |
-            | 4        | 42       | 2             | ArrowSlice(2,33)  |                   | ArrowSlice(0, 0)       |
-            +----------+----------+---------------+-------------------+-------------------+------------------------+
-        }
-        IndexBucket {
-            index time bound: >= #43
-            size: 64 B across 2 rows
-                - frame_nr: from #43 to #44 (all inclusive)
-            data (sorted=true):
-            +----------+----------+---------------+-------------------+-------------------+------------------------+
-            | event_id | frame_nr | num_instances | rerun.point2d     | rerun.rect2d      | rerun.instance_key     |
-            +----------+----------+---------------+-------------------+-------------------+------------------------+
-            | 6        | 43       | 2             |                   | ArrowSlice(3,25)  | ArrowSlice(0, 0)       |
-            | 5        | 44       | 2             | ArrowSlice(3,33)  |                   | ArrowSlice(0, 0)       |
-            +----------+----------+---------------+-------------------+-------------------+------------------------+
-        }
-    ]
-}
-```
-
-Worth noticing:
-- `event_id` and `num_instances` are deserialized and stored natively, as they play a crucial role in many storage and query features.
-- In this example, `rerun.instance_key` consistently references the same slice of Arrow data. This is because they are auto-generated in this case.
-
-In addition to storing the indices themselves, we also require a bunch of auxiliary datastructures.
-
-First, we need to keep track of all `EventId`s currently present in the store, in `event_id` order (remember, these are time-based (clients' wall-clocks)!).
-This will replace the existing `chronological_msg_ids` in `EntityDb` (which is currently in insertion-order-as-seen-from-the-viewer, which isn't too great).
-We need this because some operations like GC and save-to-disk require to pick an arbitrary ordering to get going, and `event_id` is our best bet for now.
-
-Second, we need to map `EventId`s to index rows for GC purposes: `HashMap>`.
-
-Finally, we need to map `EventId`s to `TimePoint`s: `HashMap`.
-This is something we already have today and that is needed for e.g. text log views.
-
-Overall this is a much simpler design, and while it still isn't optimal for timeseries-like queries (i.e. range), it should already quite the bump in performance for those.
-
-### Write path
-
-For each row in the batch, we create a bunch of unit-length Arrow slices that point to the right place in the shared buffer, and then it's all pretty much the same as before.
-
-The major difference is we now directly store Arrow buffers (which are really Arrow slices under the hood) rather than indices into component tables.
-Everything else is the same: get (or create) the appropriate index (`EntityPath` + `Timeline`), find the right bucket using a binsearch, and insert those Arrow slices
-
-We also actually deserialize the `event_id` and `num_instances` columns into native types as we're going to need those for the store to function:
-- `event_id`s are needed to maintain our auxiliary datastructures (GC, save-to-file)
-- `num_instances` are needed for `re_query` to be able to do its job
-
-### Read path
-
-The major difference is we now directly return Arrow buffers (which are really Arrow slices under the hood), which means `get` queries are gone… which means latest-at queries should get a bit faster and range queries should get much faster.
-
-Everything else is the same: grab the right index (`EntityPath` + `Timeline`), binsearch for the right bucket, walk backwards if you need to, and you're done.
-
-An important difference is we're now returning the expected number of instances as part of the result, which means `re_query` doesn't have to guess anymore and can actually apply clears and splats appropriately.
-
-#### LatestAt (random-access like)
-
-Nothing specific to add to the above.
-
-#### Range (timeseries like)
-
-Nothing specific to add to the above.
-
-### Garbage collection
-
-The garbage collector is the system undergoing the most changes.
-
-We want to garbage collect in `event_id` order (reminder: `EventId` is a `TUID`, i.e. its order is based on the clients's wall-clocks).
-
-- We iterate over all `EventId`s in their natural order
-- For every one of them:
-    - Find all index rows that match this `event_id` and replace the Arrow slice with a tombstone, this has 2 effects:
-        1. This decrements the internal refcount of the overall Arrow buffer, which might deallocate it if this happens to be the last one standing
-        2. This lets the read path knows it should ignore this row (and maybe not look any further?)
-    - Check if the bucket now only contains tombstones, and drop it entirely if that's the case
-    - Remove the freshly dropped `event_id` from all our auxiliary datastructures
-    - Measure how much memory has been reclaimed (if any!) and decide whether we should continue with the next `event_id` on the list
-- Return all the time ranges that have been dropped to the main app so that it can update the timeline widget appropriately
-
-### Save-to-disk
-
-The store is now in charge of saving to disk, i.e. we do _not_ store raw `LogMsg`s in the main app anymore.
-
-The dumping process is very similar to how the new GC works:
-- We iterate over all `EventId`s in their natural order
-- For every one of them:
-    - Find all index rows that match this `event_id` and:
-        1. Merge their timepoints (the rest of the data must be identical!)
-        2. Reconstruct a `LogMsg` using the data from the row
-- Return all these `LogMsg`s to the main save-to-disk function
-
-Doing the above results in something functional and technically correct.. but there's a catch: we're losing all the original batching, which means re-loading this .rrd file will have poorer performance than when exploring the original recording.
-Not only we do not want to lose the original batching, ideally we would want to improve on it, i.e. batch even more aggressively when we're writing to disk!
-
-We want an extra step in there: accumulate rows of data until we reach a given size, and then craft a single `LogMsg` out of that.
-This will make the resulting .rrd file both faster to load (there's some fixed overhead for each `LogMsg`, especially on web…) and faster to explore (improved cache locality in the store).
-
-## Implementation plan
-
-A _lot_ of things are gonna change, so we really want A) to avoid crazy large PRs that are a pain to review and B) to be able detect regressions (both correctness and performance) early on so they don't turn into long & painful investigations.
-
-Nothing set in stone obviously, but the following steps seem like a good start (roughly 1 step == 1 PR).
-This entire section can pretty much be used verbatim as a tracking issue.
-
-1. Implement all the needed tests & benchmarks
-We need to be able to check for regressions at every step, so make sure we have all the tests and benchmarks we need for that.
-We should already be 95% of the way there at this point.
-
-1. Move `DataStore` sanity checks and formatting tools to separate files
-`store.rs` is supposed to be the place where one can get an overview of all the datastructures involved in the store, except it has slowly become a mess over time and is now pretty much unreadable.
-
-1. Replace `MsgBundle` & `ComponentBundle` with the new types (`DataCell`, `DataRow`, `DataTable`, `EventId`, `BatchId`…)
-No actual batching features nor any kind of behavior changes of any sort: just define the new types and use them everywhere.
-
-1. Pass entity path as a column rather than as metadata
-Replace the current entity_path that is passed in the metadata map with an actual column instead. This will also requires us to make `EntityPath` a proper Arrow datatype (..datatype, not component!!).
-
-1. Implement explicit number of instances
-Introduce a new column for `num_instances`, integrate it in the store index and expose it in the store APIs.
-
-1. Fix splats all around (rs sdk, py sdk, re_query…)
-Update the SDKs and `re_query` to properly make use of the new explicit `num_instances`.
-
-1. Get rid of component buckets altogether
-Update the store implementation to remove component tables, remove the `get` APIs, introduce slicing on the write path, etc. Still no batching in sight!
-
-1. Implement the coalescing/accumulation logic in the SDK
-Add the required logic/thread/timers/whatever-else in the SDKs to accumulate data and just send it all as many `LogMsg`s (i.e. no batching yet).
-
-1. Implement full-on batching
-End-to-end: transport, storage, the whole shebang.
-
-1. Sort the batch before sending (`(event_id, entity_path)`)
-Keep that in its own PR to keep track of the benchmarks.
-
-1. Implement new GC
-The complete implementation; should close all existing GC issues.
-
-1. Dump directly from the store into an rrd file
-No rebatching yet, just dump every event in its own `LogMsg`.
-
-1. Remove `LogMsg`s from `EntityDb`
-We shouldn't need to keep track of events outside the store past this point: clean it all up.
-Reminder: the timeline widget keeps track of timepoints directly, not events.
-
-1. Rebatch aggressively while dumping to disk
-
-1. Use Arrow extension types to carry around component names
-
-1. Drop `log_time`
-We currently store the logging time twice: once in the `MsgId` (soon `EventId`) and once injected by the SDK (and they don't even match!).
-We could just not inject the `log_time`, and instead derive a `log_time` column on the server using the timestamp in the `EventId`; especially since we probably want an auto-derived `ingestion_time` anyway.
-The timestamp in `EventId` is not going away: it is what defines our global ordering!
-
-- Turn all of the above into a tracking issue
-- Get to work :>
-
-
-
-## Future work
-
-Future work that we're expecting to happen in the mid-term and that we should keep in mind while making changes to the datastore design, so we don't end up completely stuck later on.
-
-- Might or might not be related to batching
-- No particular order
-
-### Bucket compaction making a return
-
-Basically aggressive rebatching on the loaded data.
-
-### Dedicated storage for timeseries
-
-While our store design nicely matches latest-at semantics, it's pretty horrible when it comes to range/timeseries-like semantics.
-It gets even worse for timeseries of simple scalars.
-
-At some point we're gonna want to have a fully dedicated storage & query path for scalar timeseries.
-
-### Recursive clears native to the store
-
-Recursive clears are currently handled in `EntityDb`, which is an issue for (at least) two reasons:
-- Once we start saving the store in a native format, rather than a collection of `LogMsg`, we'll lose the recursive clears when dumping then reloading the recording.
-- The recursive clears aren't even arrowified yet.
-
-### Native file format for writing the store to disk
-
-We currently store recordings as a stream of `LogMsg`s, which is nice for some purposes and awful for others.
-
-While we still want to have the ability to dump the store as a stream of `LogMsg`, in the future we will need a native format that allows streaming data in and out of the disk as needed.
-
-### Derived components & components converter mappings
-
-We'd like to have components be generic over their datatype at some point, and be able to register conversion routines to map from `(component A, type B)` to `(component C, type D)`.
-
-### Don't send full schemas of known/builtin components over the wire
-
-Builtin components have fixed schemas, sending that information everytime is wasteful.
-
-### Post-GC latest-at correctness
-
-The current garbage collector is factually wrong; consider the following state:
-```
-+----------+----------+---------------+-------------------+-------------------+------------------------+
-| event_id | frame_nr | num_instances | rerun.point2d     | rerun.rect2d      | rerun.instance_key     |
-+----------+----------+---------------+-------------------+-------------------+------------------------+
-| 1        | 40       | 2             | ArrowSlice(1,33)  |                   | ArrowSlice(0, 0)       |
-| 2        | 41       | 2             |                   | ArrowSlice(1,25)  | ArrowSlice(0, 0)       |
-+----------+----------+---------------+-------------------+-------------------+------------------------+
-```
-
-A `LatestAt(("frame_nr", 42))` query would yield `[rerun.point2d=ArrowSlice(1,33), rerun.rect2d=ArrowSlice(1,25)]`.
-
-Now let's say we run a GC with `AtLeast(0.30)` ("collect at least 30%"), we end up with the following state:
-```
-+----------+----------+---------------+-------------------+-------------------+------------------------+
-| event_id | frame_nr | num_instances | rerun.point2d     | rerun.rect2d      | rerun.instance_key     |
-+----------+----------+---------------+-------------------+-------------------+------------------------+
-| 2        | 41       | 2             |                   | ArrowSlice(1,25)  | ArrowSlice(0, 0)       |
-+----------+----------+---------------+-------------------+-------------------+------------------------+
-```
-
-A `LatestAt(("frame_nr", 42))` query would now yield `[rerun.rect2d=ArrowSlice(1,25)]`, i.e. `rerun.point2d` would now fallback to its default value, rather than whatever value was in `ArrowSlice(1,33)`.
-
-When garbage collecting, we _have to_ keep track of the compacted latest-at state that would have been there otherwise.
-
-### Optimize linear backwards walk
-
-Although this has been fixed in the trivial case (the component is not present at all), this can still be an issue in others.
-The classic solution is some kind of bitmap index.
-
-### Drop-after semantics (undo/redo)
-
-If we decide to retain the UI's undo/redo state in the store, we will require a method to discard all data from a certain point in time and beyond.
-
-I.e. a GC request of the form `DropAfter(("frame_nr", 41))`, rather than the other way around that our GC already supports.
-
-### Write our own arrow2-convert
-
-`arrow2-convert` has a bunch of shortcomings that are likely going to push us to write our own struct-to-arrow mapper in the future, and as such we shouldn't restrain ourselves based on `arrow2-convert`'s design.
-
-### The DataStore is its own server
-
-The datastore won't run as part of the Viewer forever.
-
-### Data might be a reference to an external storage system
-
-At some point we're going need the ability for components to refer to data that reside out of the store (e.g. a component `VideoFrame` which is merely a URI pointing to a video file "somewhere").
-
-### Non-integer instance keys
-
-Instance keys currently only support `u32` as their datatype. Maybe at some point we'll want to support others..?
-
-### Streamed serialization
-
-Don't waste compute & memory on creating large `arrow::Chunk`s from cells, instead serialize them independently in a streaming fashion.
-
-## Q&A
-
-### What is the relationship between "component instances" and "instance keys"?
-
-> It seems like we do treat instance keys like any other component, which means each individual instance key is actually a component instance, no?
-
-The terminology is very subtle.
-
-- `InstanceKey` is indeed a component, and so it is always passed as a list, which we colloquially refer to as "the instance keys".
-- a "component instance", or just "instance", is the name we give to any single value in a component cell:
-```
-[C, C, …]
- ^  ^
- |  |
- |  |
- |  instance #2
- |
- instance #1
-```
-
-So, a cell of `InstanceKey`s is indeed made up of component instances, but "instance keys" and "component instances" are two different things.
-
-> each individual instance key is actually a component instance, no?
-
-Yes, each individual instance key is a component instance, but not every component instance is necessarily an `InstanceKey`.
-
-### Are there any "special" components?
-
-> Does `DataRow::cells` include the "instance key" component then? Are there any other special components?
-
-The following list is all the types that are treated separately because they need to be deserialized and stored natively in the `DataStore` since they drive its behavior:
-```rust
-event_id: Vec,
-timepoint: Vec,
-entity_path: Vec,
-num_instances: Vec,
-```
-None of these are components however, they are merely Arrow datatypes.
-
-Everything else is just a component, and as such is passed as a `DataCell`.
-
-Components are completely opaque blobs from the store's PoV; they cannot dictate its behavior since they aren't even deserialized.
-This includes `InstanceKey`s, which are just returned to `re_query` as-is.
-
-The one special thing about instance keys is that they are auto-generated server-side if they are missing; but even then, once they are generated they are still treated as opaque blobs from the store's PoV.
diff --git a/design/component_datatypes.md b/design/component_datatypes.md
deleted file mode 100644
index 3b7fb1bfdac9..000000000000
--- a/design/component_datatypes.md
+++ /dev/null
@@ -1,48 +0,0 @@
-# 2023-03-15 component type conversions
-Status: pre-proposal (not ready to be implemented)
-
-## Summary
-Each _Component_ can be represented by many different _Datatypes_.
-
-Every piece of data is associated with a `(component datatype)` tuple.
-
-In the store we can have multiple columns of datatypes for each component.
-
-Examples of `(component datatype)`:
-```
-(point2      [f16; 2])
-(point3      [f32; 3])
-(label       utf8)
-(transform   mat4_f32)
-(box2        box2_min_max_f32)
-(box2        box2_min_size_u32)
-(tensor      tensor_dense_v1)
-(tensor      jpeg)
-```
-
-Both components and datatypes have namespaced names.
-
-## Converters
-We have a plugin-system for converting between from one `(comp, datatype)` to another.
-
-The converters can match any `(comp, datatype)` pattern to any other, including wildcards.
-For instance, `(*, vec3f16) -> (*, vec3f32)` and `(point2, vec2f32) -> (point3, vec3f32)`.
-
-The plugin may specify if it should be on-the-fly, memoized, or write-back.
-
-## Advantages
-
-We can use this for:
-  * compression (`Jpeg -> Tensor`)
-  * different representation (`Mat4_f32` or `(Vec3_f32, Quat_XYZW_f32)`)
-  * save space (store scalar as `u16`, or `f32`, or `f64`, …)
-  * versioning (`Tensor_v2 -> Tensor_v3`)
-
-### Open questions:
-How do we handle Jpegs? Are they a separate datatype for the `Tensor` component: `(Tensor Jpeg)`, or are they a separate component type: `(Jpeg [u8])` ?
-
-How do we handle the transform-graph of conversions? What if there are cycles? What if there are two different paths from one `(component datatype)` tuple to another?
-
-#### What naming convention should we use for datatypes?
-* Rust style: `[u8]`, `[f32; 3]` etc ?
-* `quat_xyzw_f32` or `xyzw_f32` ?
diff --git a/design/space_views.md b/design/space_views.md
deleted file mode 100644
index a5f2ea85bedd..000000000000
--- a/design/space_views.md
+++ /dev/null
@@ -1,170 +0,0 @@
-# Views
-Status: Mostly implemented.
-
-
-## What are views
-Views visualize a Data Blueprint, i.e. a set of entities with given properties.
-They are represented as freely arrangeable tiles in the Viewport.
-Most Views are interactive, allowing their data to be explored freely.
-
-
-## Properties of a view
-All properties are saved as part of the blueprint.
-
-Changing discards Space View State:
-* Space View Class
-* root entity path
-
-Freely mutable:
-* name
-* positioning within layout
-* class specific properties
-* Data Blueprint
-
-## Root entity path
-* root of the transform hierarchy (if any is used)
-* may govern heuristics
-* available at various stages of UI drawing & system execution build-up (see below)
-
-
-## View state
-In addition to blueprint stored data, a view has a class specific `SpaceViewState`
-which stored ephemeral state that is not persisted as part of the blueprint.
-This is typically used for animation/transition state.
-
-⚠️ As of writing, we're using this also for state that *should* be persisted and needs to be moved to
-blueprint components.
-
-## View class
-Each Space View refers to an immutable Space View Class, implemented by `SpaceViewClass`.
-It defines:
-* which data it can display and how it is displayed
-* how it is interacted with
-* what properties are read from the blueprint store and how they are exposed in the UI
-
-### What view classes are there?
-Space View differ only in class when they are **fundamentally different** in the way they display data.
-Naturally, this means that there are only ever very few distinct Space View classes.
-
-As of writing we have:
-* Spatial
-* Bar Chart
-* Tensor
-* Text
-* Text Document
-* Time Series
-
-#### Future view class distinction
-
-The fundamental difference between different views lies in the kinds of axes a view has.
-- Data Table (currently text views) have rows and columns with text
-- Text Log have rows with logs sorted in time (not 100% sure this is fundamentally different than Data Table)
-- Spatial 2D has two orthogonal axes with defined spatial relationships
-- Spatial 3D has three orthogonal axes with defined spatial relationships
-- Time Series has one time axis and one numeric axis
-- Rich Text is a rich text document (linear in top to bottom with wraparound in horizontal)
-
-##### On merging bar chart with spatial 2D
-It might take some time to get the Archetype Queries + defaults expressive and easy to use enough that it makes sense to merge bar chart with spatial 2D. Right now we have the state that the bar chart view takes a single 1-D tensor and draws a bar chart with x-axis = tensor indices and y-axis = tensor values. It draws boxes with width 1, centered on integers in x, y-min = 0 and y-max = tensor value.
-
-With the right set of primitives a user should be able to manually build a bar chart in a spatial 2D view. For example they might want a stacked bar chart. Talking about bringing in 3D into a bar chart doesn't likely make sense since there probably doesn't exist a camera projection that maps between 3D and the tensor indices axis (x).
-
-One could imagine that we would have heuristics that generate a Data Blueprint for boxes that creates a bar chart from 1-D tensors.
-
-##### On why 2D and 3D views shouldn't be the same
-In the early prototype 2D and 3D Views were separate since they would use different
-renderers - 3D Views were driven by `three-d`, 2D Views by egui directly.
-With the advent or `re_renderer`, this distinction was no longer necessary and indeed a hindrance.
-Like most modern renderer, `re_renderer` does not distinguish 2D and 3D rendering at a fundamental level
-(albeit we might add some limited awareness in the future) since shader, hardware acceleration and
-data structures are all fundamentally the same.
-
-If the root of a 2D Space View has a camera projection we can have a defined way of displaying any 3D content.
-Therefore, all 3D content can be displayed in a 2D Space View.
-
-Vice versa, if an entity in a 3D space defines camera intrinsics, any 2D contents under it can be previewed
-in 3D space. Again, there is no point in putting a limit on what is displayed there.
-
-However, they are more different from a users point of view.
-
-First of all 3D data is only viewable in 2D if combined with a suitable projection (could be through perspective projection or by dropping the data of one dimension). The fact that 2D views are rendered in a 3D pipeline using some kind of pseudo depth (draw order) and an implicitly defined orthographic camera, is not top of mind to me as a user. This is something you need considerable exposure to graphics or 3D computer vision to experience as immediately obvious.
-
-Second, the expectations around how to navigate a 2D visualization are quite different from how I expect to navigate a 3D visualization.
-
-### Registering
-Registration happens on startup in the Viewer owned `SpaceViewClassRegistry`.
-The Viewer registers all builtin Space View Classes and users may add new types at any point in time.
-
-
-### Systems
-
-Space View systems are the primary means how a Space View processes entities.
-All Space View systems are instantiated and executed every frame.
-Each System operates on a statically defined set of archetypes.
-Execution is allowed to store arbitrary state for the duration of the frame.
-
-For the moment we have a simple two step framework:
-
-#### `ViewContextSystem`
-Instantiation happens before `ViewPartSystem` and can not emit drawables, only set internal state.
-The results are available during `ViewPartSystem` execution as well as `SpaceViewClass` drawing.
-
-This is used e.g. to prepare the transform tree.
-Each `ViewPartSystem` that knows about this `TransformContext` can then use it to look up transforms.
-
-#### `ViewPartSystem`
-Gathers data from the store and emits `re_renderer` draw data for later use in the `SpaceViewClass`'s ui/drawing method.
-
-For convenience, it provides a `data() -> &Any` method to make it easy to expose results other than `re_renderer` draw data
-in a generic fashion.
-
-Example:
-The `Points2DPart` queries the `Points2D` archetype upon execution and produces as a result `re_renderer::PointCloudDrawData`.
-Since points can have UI labels, it also stores `UiLabel` in its own state which the view class of `ui`
-can read out via `Points2DPart::data()` to draw UI labels.
-
-Note on naming:
-`ViewPartSystem` was called `ScenePart` in earlier versions since it formed a _part_ of a per-frame built-up _Scene_.
-We discarded _Scene_ since in most applications scenes are permanent and not per-frame.
-However, we determined that they still make up the essential parts of a `SpaceViewClass`.
-Their behavior is a match to what in ECS implementations is referred to as a System -
-i.e. an object or function that queries a set of components (an Archetype) and executes some logic as a result.
-
-### Registration
-Registration is done via `SpaceViewSystemRegistry` which `SpaceViewClassRegistry` stores for each class.
-View classes can register their built-in systems upon their own registration via their `on_register` method.
-As with view classes themselves, new systems may be added at runtime.
-
-### Frame lifecycle
-* `SpaceViewClass::prepare_ui`
-* default create all registered `ViewContextSystem` into a `ViewContextCollection`
-* execute all `ViewContextSystem`
-* default create all registered `ViewPartSystem` into a `ViewPartCollection`
-* execute all `ViewPartSystem`, giving read access to the `ViewContextSystem`
-  * this produces a list or `re_renderer` draw data
-* pass all previously assembled objects as read-only into `SpaceViewClass::ui`
-  * here the actual rendering via egui happens
-    * this typically requires iterating over all `ViewPartSystem` and extract some data either in a generic fashion via `ViewPartSystem::data` or with knowledge of the concrete `ViewPartSystem` types
-  * currently, we also pass in all `re_renderer` data since the build up of the `re_renderer` view via `ViewBuilder` is not (yet?) unified
-
-### View class registry
-Despite being few in numbers, Views Classes are registered on startup.
-This is desirable since:
-* forces decoupling from other aspects of the Viewer (Viewer should be composable)
-* allows for user defined views
-
-
-![Overview diagram of how the basic traits related to each other](https://github.com/rerun-io/rerun/assets/1220815/ffdb1cdf-7efe-47a0-ac38-30262d770e69)
-
-
-#### User defined view classes
-Rust developers can use the Class Registry to register their own Space View types.
-We do *not* expect this to be a common workflow, but more of a last resort / highest level
-extensibility hooks.
-
-These user defined Views have no limitations over built-in Views and are able
-to completely reimplement existing Views if desired.
-
-In the future A more common extension point will be to add custom systems to an existing Space View
-emitting re_renderer drawables.
-(TODO(andreas): We're lacking API hooks and an example for this!)
diff --git a/design/spatial_transforms.md b/design/spatial_transforms.md
deleted file mode 100644
index fe0f1ec0459d..000000000000
--- a/design/spatial_transforms.md
+++ /dev/null
@@ -1,34 +0,0 @@
-# Spatial transforms
-
-Spatial transforms are transforms that apply the spatial 2D & 3D views.
-This includes affine 2D/3D transforms as well as camera projections.
-
-Any transform component that is logged a path `parent/entity` it describes the
-transform between `parent` to `parent/entity`.
-
-
-## Topology
-We infer a `SpatialTopology` from these transforms.
-As we get more information about a scene the topology changes, but all changes are irreversible.
-In practical terms this means that once a pinhole is logged we'll always assume that everything under
-the entity path of that camera is in 2D space.
-
-The spatial topology is used to determine which (spatial) visualizers can be used in which contents.
-A 2D visualizer can only ever be applied to an entity when there is a valid transformation
-along the path of the entity to the view's origin.
-
-Examples for invalid transformation paths are:
-* mismatched start space
-  * 2D content can not be added to a 3D space and vice versa
-* several projections (a pinhole can only be applied once)
-* explicit space disconnect
-
-## `DisconnectedTransform` (former `DisconnectedSpace`)
-Disconnected transform is a special transform that forbids any transformation path
-from an entity to its parent and vice versa.
-As such it creates an explicit break in the topology.
-
-Like any other topological break, it is permanent. Once logged, the resulting subspaces can no longer be fused.
-
-## Null transforms
-Null transforms are handled like identity transforms, the same as not logging a transform at all.

From 580e91377904480891cd822c427f514e5d7d8e80 Mon Sep 17 00:00:00 2001
From: Michael Grupp 
Date: Wed, 18 Mar 2026 09:34:28 +0100
Subject: [PATCH 171/513] Fix URDF loader applying fallback albedo factor to
 non-primitive meshes

Fixes a bug recently introduced in #1173 (**only on main**), where
textured URDFs showed up as white.

We should only have the fallback to white in `material_color()` for
primitives like box/cylinder/etc and not in the broader
`material_albedo_factor()` that is also used for textured
`Geometry::Mesh`.

# on main

grafik

# fixed

grafik

Closes https://linear.app/rerun/issue/RR-4118

Source-Ref: 47451052b674eac035693f082ee15d8ab963b112
---
 .../store/re_data_loader/src/loader_urdf/mod.rs   | 15 +++++++++------
 1 file changed, 9 insertions(+), 6 deletions(-)

diff --git a/crates/store/re_data_loader/src/loader_urdf/mod.rs b/crates/store/re_data_loader/src/loader_urdf/mod.rs
index 98c8a147b143..27b99b39f8cc 100644
--- a/crates/store/re_data_loader/src/loader_urdf/mod.rs
+++ b/crates/store/re_data_loader/src/loader_urdf/mod.rs
@@ -456,9 +456,12 @@ fn emit_geometry(
             use re_sdk_types::components::MediaType;
 
             let mesh_bytes = load_ros_resource(urdf_tree.urdf_dir.as_ref(), filename)?;
-            let asset3d =
-                Asset3D::from_file_contents(mesh_bytes, MediaType::guess_from_path(filename))
-                    .with_albedo_factor(material_albedo_factor(material));
+            let mut asset3d =
+                Asset3D::from_file_contents(mesh_bytes, MediaType::guess_from_path(filename));
+
+            if let Some(albedo_factor) = material_albedo_factor(material) {
+                asset3d = asset3d.with_albedo_factor(albedo_factor);
+            }
 
             if let Some(material) = material {
                 let urdf_rs::Material {
@@ -528,10 +531,11 @@ fn emit_geometry(
 
 /// Extracts the RGBA color from a URDF material. Falls back to white if no color is specified.
 fn material_color(material: Option<&urdf_rs::Material>) -> Color {
-    Color::new(material_albedo_factor(material))
+    Color::new(material_albedo_factor(material).unwrap_or(Rgba32::WHITE))
 }
 
-fn material_albedo_factor(material: Option<&urdf_rs::Material>) -> Rgba32 {
+/// Extracts the URDF material color for mesh albedo, if one is explicitly specified.
+fn material_albedo_factor(material: Option<&urdf_rs::Material>) -> Option {
     material
         .and_then(|material| material.color.as_ref())
         .map(|color| {
@@ -542,7 +546,6 @@ fn material_albedo_factor(material: Option<&urdf_rs::Material>) -> Rgba32 {
             // TODO(emilk): is this linear or sRGB?
             Rgba32::from_linear_unmultiplied_rgba_f32(*r as f32, *g as f32, *b as f32, *a as f32)
         })
-        .unwrap_or(Rgba32::WHITE)
 }
 
 fn quat_from_rpy(rpy: &[f64; 3]) -> glam::Quat {

From c96297b11d28d99c0ba1ee1056af9e0d22446cfb Mon Sep 17 00:00:00 2001
From: Isse 
Date: Wed, 18 Mar 2026 10:28:31 +0100
Subject: [PATCH 172/513] Store entity tree in the chunk store

### What

Store the entity in the chunk store instead of in the rrd manifest
index.

Source-Ref: 6133f50f1d6a7c07d70fce6cbeb8abc934f16dfb
---
 .../re_chunk_store/src/drop_time_range.rs     |  20 +-
 .../store/re_chunk_store/src/entity_tree.rs   | 183 +++++++++++
 crates/store/re_chunk_store/src/gc.rs         |  20 +-
 crates/store/re_chunk_store/src/lib.rs        |   2 +
 crates/store/re_chunk_store/src/query.rs      |  15 +-
 crates/store/re_chunk_store/src/store.rs      |  61 ++++
 .../store/re_chunk_store/src/store_schema.rs  |  30 +-
 crates/store/re_chunk_store/src/writes.rs     |  54 +---
 .../src/data_meta_per_timeline.rs             |  12 +-
 crates/store/re_entity_db/src/entity_db.rs    | 113 +++----
 crates/store/re_entity_db/src/entity_tree.rs  | 303 ------------------
 crates/store/re_entity_db/src/lib.rs          |   3 +-
 .../re_entity_db/src/rrd_manifest_index.rs    |  20 +-
 .../sorted_temporal_chunks.rs                 |  13 +-
 .../src/actions/collapse_expand_all.rs        |   5 +-
 .../re_context_menu/src/collapse_expand.rs    |   8 +-
 .../re_data_ui/src/component_path_ui.rs       |   2 +-
 crates/viewer/re_data_ui/src/item_ui.rs       |  11 +-
 .../src/view_entity_picker.rs                 |   3 +-
 .../src/visible_time_range_ui.rs              |   3 +-
 .../re_time_panel/src/streams_tree_data.rs    |   3 +-
 crates/viewer/re_view_spatial/src/view_2d.rs  |   3 +-
 crates/viewer/re_view_spatial/src/view_3d.rs  |   2 +-
 .../re_view_spatial/tests/bgr_images.rs       |   4 +-
 .../re_view_spatial/tests/blueprint_2d.rs     |   4 +-
 .../tests/latest_at_partial_updates.rs        |   4 +-
 .../tests/visible_time_range.rs               |   6 +-
 crates/viewer/re_viewport/src/viewport_ui.rs  |   3 +-
 .../viewer/re_viewport_blueprint/src/view.rs  |   3 +-
 .../src/view_contents.rs                      |   8 +-
 .../src/view_properties.rs                    |   5 +-
 31 files changed, 425 insertions(+), 501 deletions(-)
 create mode 100644 crates/store/re_chunk_store/src/entity_tree.rs
 delete mode 100644 crates/store/re_entity_db/src/entity_tree.rs

diff --git a/crates/store/re_chunk_store/src/drop_time_range.rs b/crates/store/re_chunk_store/src/drop_time_range.rs
index 7a4af57ae761..2400deab77aa 100644
--- a/crates/store/re_chunk_store/src/drop_time_range.rs
+++ b/crates/store/re_chunk_store/src/drop_time_range.rs
@@ -4,7 +4,7 @@ use re_chunk::{ChunkId, TimelineName};
 use re_log::debug_assert;
 use re_log_types::AbsoluteTimeRange;
 
-use crate::{ChunkStore, ChunkStoreEvent};
+use crate::{ChunkStore, ChunkStoreDiff, ChunkStoreEvent};
 
 impl ChunkStore {
     /// Drop all events that are in the given range on the given timeline.
@@ -123,8 +123,7 @@ impl ChunkStore {
         // ------------------
         // Apply the changes:
 
-        let generation = self.generation();
-        let mut events: Vec = vec![];
+        let mut deletion_diffs: Vec = vec![];
 
         for chunk in chunks_to_drop {
             let dels = if deep_removal {
@@ -132,18 +131,11 @@ impl ChunkStore {
             } else {
                 self.remove_chunks_shallow(vec![chunk], None)
             };
-
-            for del in dels {
-                events.push(ChunkStoreEvent {
-                    store_id: self.id.clone(),
-                    store_generation: generation.clone(),
-                    event_id: self
-                        .event_id
-                        .fetch_add(1, std::sync::atomic::Ordering::Relaxed),
-                    diff: del.into(),
-                });
-            }
+            deletion_diffs.extend(dels.into_iter().map(ChunkStoreDiff::from));
         }
+
+        let mut events = self.finalize_events(deletion_diffs);
+
         for mut chunk in new_chunks {
             chunk.sort_if_unsorted();
             #[expect(clippy::unwrap_used)] // The chunk came from the store, so it should be fine
diff --git a/crates/store/re_chunk_store/src/entity_tree.rs b/crates/store/re_chunk_store/src/entity_tree.rs
new file mode 100644
index 000000000000..1cf7f18ebc10
--- /dev/null
+++ b/crates/store/re_chunk_store/src/entity_tree.rs
@@ -0,0 +1,183 @@
+use std::collections::BTreeMap;
+
+use re_log_types::{EntityPath, EntityPathPart};
+
+// ----------------------------------------------------------------------------
+
+/// A recursive tree structure that maintains the entity hierarchy.
+///
+/// The tree contains a list of subtrees, and so on recursively.
+#[derive(Debug, Clone)]
+pub struct EntityTree {
+    /// Full path prefix to the root of this (sub)tree.
+    pub path: EntityPath,
+
+    /// Direct descendants of this (sub)tree.
+    pub children: BTreeMap,
+}
+
+impl Default for EntityTree {
+    fn default() -> Self {
+        Self::root()
+    }
+}
+
+impl EntityTree {
+    pub fn root() -> Self {
+        Self::new(EntityPath::root())
+    }
+
+    pub fn new(path: EntityPath) -> Self {
+        Self {
+            path,
+            children: Default::default(),
+        }
+    }
+
+    /// Has no child entities.
+    pub fn is_leaf(&self) -> bool {
+        self.children.is_empty()
+    }
+
+    pub fn on_new_entity(&mut self, entity_path: &EntityPath) {
+        re_tracing::profile_function!();
+
+        // Book-keeping for each level in the hierarchy:
+        let mut tree = self;
+        for (i, part) in entity_path.iter().enumerate() {
+            tree = tree
+                .children
+                .entry(part.clone())
+                .or_insert_with(|| Self::new(entity_path.as_slice()[..=i].into()));
+        }
+    }
+
+    pub fn subtree(&self, path: &EntityPath) -> Option<&Self> {
+        fn subtree_recursive<'tree>(
+            this: &'tree EntityTree,
+            path: &[EntityPathPart],
+        ) -> Option<&'tree EntityTree> {
+            match path {
+                [] => Some(this),
+                [first, rest @ ..] => {
+                    let child = this.children.get(first)?;
+                    subtree_recursive(child, rest)
+                }
+            }
+        }
+
+        subtree_recursive(self, path.as_slice())
+    }
+
+    /// Invokes visitor for `self` and all children recursively.
+    pub fn visit_children_recursively(&self, mut visitor: impl FnMut(&EntityPath)) {
+        fn visit(this: &EntityTree, visitor: &mut impl FnMut(&EntityPath)) {
+            visitor(&this.path);
+            for child in this.children.values() {
+                visit(child, visitor);
+            }
+        }
+
+        visit(self, &mut visitor);
+    }
+
+    /// Removes leaf entities that have no children and for which `entity_has_data` returns false.
+    ///
+    /// This is called after store deletions to keep the tree in sync with the actual data.
+    pub fn prune_empty_entities(&mut self, entity_has_data: &impl Fn(&EntityPath) -> bool) {
+        self.children.retain(|_, child| {
+            child.prune_empty_entities(entity_has_data);
+            let has_children = !child.children.is_empty();
+            let has_data = entity_has_data(&child.path);
+            has_children || has_data
+        });
+    }
+
+    /// Invokes the `predicate` for `self` and all children recursively,
+    /// returning the _first_ entity for which the `predicate` returns `true`.
+    ///
+    /// Note that this function has early return semantics, meaning if multiple
+    /// entities would return `true`, only the first is returned.
+    /// The entities are yielded in order of their entity paths.
+    pub fn find_first_child_recursive(
+        &self,
+        mut predicate: impl FnMut(&EntityPath) -> bool,
+    ) -> Option<&Self> {
+        fn visit<'a>(
+            this: &'a EntityTree,
+            predicate: &mut impl FnMut(&EntityPath) -> bool,
+        ) -> Option<&'a EntityTree> {
+            if predicate(&this.path) {
+                return Some(this);
+            }
+
+            for child in this.children.values() {
+                if let Some(subtree) = visit(child, predicate) {
+                    // Early return
+                    return Some(subtree);
+                }
+            }
+
+            None
+        }
+
+        visit(self, &mut predicate)
+    }
+}
+
+impl re_byte_size::SizeBytes for EntityTree {
+    fn heap_size_bytes(&self) -> u64 {
+        let Self { path, children } = self;
+        path.heap_size_bytes() + children.heap_size_bytes()
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn prune_removes_empty_leaves() {
+        let mut tree = EntityTree::root();
+        let parent: EntityPath = "parent".into();
+        let child: EntityPath = "parent/child".into();
+        let grandchild: EntityPath = "parent/child/grandchild".into();
+
+        tree.on_new_entity(&grandchild);
+
+        assert!(tree.subtree(&parent).is_some());
+        assert!(tree.subtree(&child).is_some());
+        assert!(tree.subtree(&grandchild).is_some());
+
+        // Only grandchild has data
+        tree.prune_empty_entities(&|path| *path == grandchild);
+        assert!(tree.subtree(&parent).is_some());
+        assert!(tree.subtree(&child).is_some());
+        assert!(tree.subtree(&grandchild).is_some());
+
+        // No entity has data, all should be pruned
+        tree.prune_empty_entities(&|_| false);
+        assert!(tree.subtree(&parent).is_none());
+        assert!(tree.subtree(&child).is_none());
+        assert!(tree.subtree(&grandchild).is_none());
+        assert!(tree.children.is_empty());
+    }
+
+    #[test]
+    fn prune_keeps_parents_with_children() {
+        let mut tree = EntityTree::root();
+        let parent: EntityPath = "parent".into();
+        let child_a: EntityPath = "parent/a".into();
+        let child_b: EntityPath = "parent/b".into();
+
+        tree.on_new_entity(&child_a);
+        tree.on_new_entity(&child_b);
+
+        // Only child_b has data -- parent and child_a have no data
+        // but parent should stay because child_b is still there
+        tree.prune_empty_entities(&|path| *path == child_b);
+        assert!(tree.subtree(&parent).is_some());
+        assert!(tree.subtree(&child_a).is_none());
+        assert!(tree.subtree(&child_b).is_some());
+    }
+}
diff --git a/crates/store/re_chunk_store/src/gc.rs b/crates/store/re_chunk_store/src/gc.rs
index 35ab5b15f9f1..b4245a43aaad 100644
--- a/crates/store/re_chunk_store/src/gc.rs
+++ b/crates/store/re_chunk_store/src/gc.rs
@@ -236,25 +236,9 @@ impl ChunkStore {
             "GC done"
         );
 
-        let events: Vec<_> = diffs
-            .into_iter()
-            .map(|diff| ChunkStoreEvent {
-                store_id: self.id.clone(),
-                store_generation: self.generation(),
-                event_id: self
-                    .event_id
-                    .fetch_add(1, std::sync::atomic::Ordering::Relaxed),
-                diff,
-            })
-            .collect();
-        if cfg!(debug_assertions) {
-            let any_event_other_than_deletion = events.iter().any(|e| !e.is_deletion());
-            assert!(!any_event_other_than_deletion);
-        }
+        re_log::debug_assert!(diffs.iter().all(|d| d.is_deletion()));
 
-        if self.config.enable_changelog {
-            Self::on_events(&events);
-        }
+        let events = self.finalize_events(diffs);
 
         (events, stats_before - stats_after)
     }
diff --git a/crates/store/re_chunk_store/src/lib.rs b/crates/store/re_chunk_store/src/lib.rs
index e0add817381a..f39fc329e7ca 100644
--- a/crates/store/re_chunk_store/src/lib.rs
+++ b/crates/store/re_chunk_store/src/lib.rs
@@ -16,6 +16,7 @@
 
 mod dataframe;
 mod drop_time_range;
+pub mod entity_tree;
 mod events;
 mod gc;
 mod lineage;
@@ -43,6 +44,7 @@ pub use self::dataframe::{
     Index, IndexRange, IndexValue, QueryExpression, SparseFillStrategy, StaticColumnSelection,
     ViewContentsSelector,
 };
+pub use self::entity_tree::EntityTree;
 pub use self::events::{
     ChunkComponentMeta, ChunkMeta, ChunkStoreDiff, ChunkStoreDiffAddition, ChunkStoreDiffDeletion,
     ChunkStoreDiffSchemaAddition, ChunkStoreDiffVirtualAddition, ChunkStoreEvent,
diff --git a/crates/store/re_chunk_store/src/query.rs b/crates/store/re_chunk_store/src/query.rs
index b3735b6c23db..e58b6b768da7 100644
--- a/crates/store/re_chunk_store/src/query.rs
+++ b/crates/store/re_chunk_store/src/query.rs
@@ -366,7 +366,20 @@ impl ChunkStore {
             || self.entity_has_physical_temporal_data_on_timeline(entity_path, timeline)
     }
 
-    /// Check whether an entity has any physical static data or any temporal data on any timeline.
+    /// Check whether an entity has any indexed data, physical or virtual.
+    ///
+    /// Returns true if the entity has any static or temporal chunk IDs,
+    /// regardless of whether those chunks are currently loaded in memory.
+    ///
+    /// An entity path can exist in the schema/entity tree but return `false` here
+    /// if all of its chunks have been removed by garbage collection or otherwise removed.
+    #[inline]
+    pub fn entity_has_data(&self, entity_path: &EntityPath) -> bool {
+        self.static_chunk_ids_per_entity.contains_key(entity_path)
+            || self.temporal_chunk_ids_per_entity.contains_key(entity_path)
+    }
+
+    /// Check whether an entity has any physical data.
     ///
     /// This is different from checking if the entity has any component, it also ensures
     /// that some _data_ currently exists in the store for this entity.
diff --git a/crates/store/re_chunk_store/src/store.rs b/crates/store/re_chunk_store/src/store.rs
index 722f7288bc41..a41c8adffddc 100644
--- a/crates/store/re_chunk_store/src/store.rs
+++ b/crates/store/re_chunk_store/src/store.rs
@@ -834,6 +834,67 @@ impl ChunkStore {
         &self.config
     }
 
+    /// The hierarchical tree of all entities registered in the store.
+    #[inline]
+    pub fn entity_tree(&self) -> &crate::EntityTree {
+        self.schema.entity_tree()
+    }
+
+    /// Prunes leaf entities from the entity tree that have no indexed data.
+    ///
+    /// Called after store deletions to keep the tree in sync with actual data.
+    fn prune_entity_tree(&mut self) {
+        let static_ids = &self.static_chunk_ids_per_entity;
+        let temporal_ids = &self.temporal_chunk_ids_per_entity;
+        self.schema.prune_entity_tree(&|path| {
+            static_ids.contains_key(path) || temporal_ids.contains_key(path)
+        });
+    }
+
+    /// Converts diffs into store events, prunes the entity tree if there are deletions,
+    /// and notifies subscribers.
+    pub(crate) fn finalize_events(
+        &mut self,
+        diffs: impl IntoIterator,
+    ) -> Vec {
+        let mut events: Vec<_> = diffs
+            .into_iter()
+            .map(|diff| crate::ChunkStoreEvent {
+                store_id: self.id.clone(),
+                store_generation: self.generation(),
+                event_id: self
+                    .event_id
+                    .fetch_add(1, std::sync::atomic::Ordering::Relaxed),
+                diff,
+            })
+            .collect();
+
+        let new_columns = self.schema.on_events(&events);
+
+        if !new_columns.is_empty() {
+            events.push(crate::ChunkStoreEvent {
+                store_id: self.id.clone(),
+                store_generation: self.generation(),
+                event_id: self
+                    .event_id
+                    .fetch_add(1, std::sync::atomic::Ordering::Relaxed),
+                diff: crate::ChunkStoreDiff::SchemaAddition(crate::ChunkStoreDiffSchemaAddition {
+                    new_columns,
+                }),
+            });
+        }
+
+        if events.iter().any(|e| e.is_deletion()) {
+            self.prune_entity_tree();
+        }
+
+        if self.config.enable_changelog {
+            Self::on_events(&events);
+        }
+
+        events
+    }
+
     /// Iterate over all *physical* chunks in the store, in ascending [`ChunkId`] order.
     #[inline]
     pub fn iter_physical_chunks(&self) -> impl Iterator> + '_ {
diff --git a/crates/store/re_chunk_store/src/store_schema.rs b/crates/store/re_chunk_store/src/store_schema.rs
index 698845fa49c0..7b4d5d40d06e 100644
--- a/crates/store/re_chunk_store/src/store_schema.rs
+++ b/crates/store/re_chunk_store/src/store_schema.rs
@@ -67,9 +67,9 @@ fn schema_component_key(descr: &ComponentColumnDescriptor) -> SchemaComponentKey
 
 /// Incrementally maintained store schema.
 ///
-/// Contains [`ChunkColumnDescriptors`] and per-entity component sets.
+/// Contains [`ChunkColumnDescriptors`], per-entity component sets, and the entity tree.
 /// Updated via [`Self::on_events`] when chunks are inserted or RRD manifests are ingested.
-/// Never affected by garbage collection.
+/// The schema itself is purely additive, but the entity tree is pruned on deletions.
 #[derive(Debug, Clone, Default)]
 pub struct StoreSchema {
     /// The _latest_ [`TimeType`] for each timeline name.
@@ -85,9 +85,20 @@ pub struct StoreSchema {
     // Ideally, we'd even merge this with the above fields. We are currently storing a lot of
     // redundant information.
     per_column_metadata: IntMap>,
+
+    /// Hierarchical tree of all entities that have been registered in the store.
+    ///
+    /// Entities are pruned on deletions but not during GC.
+    entity_tree: crate::EntityTree,
 }
 
 impl StoreSchema {
+    /// The hierarchical tree of all entities registered in the store.
+    #[inline]
+    pub fn entity_tree(&self) -> &crate::EntityTree {
+        &self.entity_tree
+    }
+
     /// Retrieve all timelines in the store.
     #[inline]
     pub fn timelines(&self) -> BTreeMap {
@@ -350,6 +361,7 @@ impl StoreSchema {
         }
 
         let entity_path = chunk.entity_path();
+        self.entity_tree.on_new_entity(entity_path);
 
         let mut new_columns = Vec::new();
 
@@ -400,6 +412,11 @@ impl StoreSchema {
                 .insert(descr.timeline_name(), descr.timeline().typ());
         }
 
+        // Update entity tree
+        for entity in sorbet_schema.all_entities() {
+            self.entity_tree.on_new_entity(entity);
+        }
+
         let mut new_per_entity: nohash_hasher::IntMap> =
             Default::default();
 
@@ -424,6 +441,13 @@ impl StoreSchema {
         self.components_per_entity.remove(entity_path);
         self.per_column_metadata.remove(entity_path);
     }
+
+    /// Prunes leaf entities from the entity tree that have no indexed data.
+    ///
+    /// Called after store deletions to keep the tree in sync with actual data.
+    pub fn prune_entity_tree(&mut self, entity_has_data: &impl Fn(&EntityPath) -> bool) {
+        self.entity_tree.prune_empty_entities(entity_has_data);
+    }
 }
 
 impl SizeBytes for StoreSchema {
@@ -433,11 +457,13 @@ impl SizeBytes for StoreSchema {
             components,
             components_per_entity,
             per_column_metadata,
+            entity_tree,
         } = self;
 
         time_type_registry.heap_size_bytes()
             + components.heap_size_bytes()
             + components_per_entity.heap_size_bytes()
             + per_column_metadata.heap_size_bytes()
+            + entity_tree.heap_size_bytes()
     }
 }
diff --git a/crates/store/re_chunk_store/src/writes.rs b/crates/store/re_chunk_store/src/writes.rs
index acbeba9151d9..11700c879dfc 100644
--- a/crates/store/re_chunk_store/src/writes.rs
+++ b/crates/store/re_chunk_store/src/writes.rs
@@ -186,39 +186,7 @@ impl ChunkStore {
         }
 
         let diffs = self.insert_chunk_impl(chunk, ChunkDirectLineageReport::Volatile)?;
-
-        let mut events: Vec<_> = diffs
-            .into_iter()
-            .map(|diff| ChunkStoreEvent {
-                store_id: self.id.clone(),
-                store_generation: self.generation(),
-                event_id: self
-                    .event_id
-                    .fetch_add(1, std::sync::atomic::Ordering::Relaxed),
-                diff,
-            })
-            .collect();
-
-        let new_columns = self.schema.on_events(&events);
-
-        if !new_columns.is_empty() {
-            events.push(ChunkStoreEvent {
-                store_id: self.id.clone(),
-                store_generation: self.generation(),
-                event_id: self
-                    .event_id
-                    .fetch_add(1, std::sync::atomic::Ordering::Relaxed),
-                diff: ChunkStoreDiff::SchemaAddition(crate::ChunkStoreDiffSchemaAddition {
-                    new_columns,
-                }),
-            });
-        }
-
-        if self.config.enable_changelog {
-            Self::on_events(&events);
-        }
-
-        Ok(events)
+        Ok(self.finalize_events(diffs))
     }
 
     fn insert_chunk_impl(
@@ -949,10 +917,8 @@ impl ChunkStore {
 
         self.gc_id += 1; // close enough
 
-        let generation = self.generation();
-
         let Self {
-            id,
+            id: _,
             config: _,
             schema,
             physical_chunks_per_chunk_id: chunks_per_chunk_id,
@@ -969,7 +935,7 @@ impl ChunkStore {
             queried_chunk_id_tracker: _,
             insert_id: _,
             gc_id: _,
-            event_id,
+            event_id: _,
         } = self;
 
         schema.drop_entity(entity_path);
@@ -1039,23 +1005,13 @@ impl ChunkStore {
                 *temporal_physical_chunks_stats -= ChunkStoreChunkStats::from_chunk(chunk);
             });
 
-        let events: Vec<_> = dropped_static_chunks
+        let diffs: Vec<_> = dropped_static_chunks
             .into_iter()
             .chain(dropped_temporal_chunks)
             .map(ChunkStoreDiff::deletion)
-            .map(|diff| ChunkStoreEvent {
-                store_id: id.clone(),
-                store_generation: generation.clone(),
-                event_id: event_id.fetch_add(1, std::sync::atomic::Ordering::Relaxed),
-                diff,
-            })
             .collect();
 
-        if self.config.enable_changelog {
-            Self::on_events(&events);
-        }
-
-        events
+        self.finalize_events(diffs)
     }
 }
 
diff --git a/crates/store/re_entity_db/src/data_meta_per_timeline.rs b/crates/store/re_entity_db/src/data_meta_per_timeline.rs
index f2dfa6aa5766..b0197446c008 100644
--- a/crates/store/re_entity_db/src/data_meta_per_timeline.rs
+++ b/crates/store/re_entity_db/src/data_meta_per_timeline.rs
@@ -375,7 +375,9 @@ mod tests {
 
         // Insert the manifest virtually.
         let events = store.insert_rrd_manifest(rrd_manifest.clone());
-        manifest_index.append(rrd_manifest).unwrap();
+        manifest_index
+            .append(rrd_manifest, store.entity_tree())
+            .unwrap();
         meta.on_events(&manifest_index, &store, &events);
 
         // Virtual rows should be counted.
@@ -396,7 +398,9 @@ mod tests {
 
         // Load virtually first.
         let events = store.insert_rrd_manifest(rrd_manifest.clone());
-        manifest_index.append(rrd_manifest).unwrap();
+        manifest_index
+            .append(rrd_manifest, store.entity_tree())
+            .unwrap();
         manifest_index.on_events(&store, &events);
         meta.on_events(&manifest_index, &store, &events);
 
@@ -457,7 +461,9 @@ mod tests {
         .unwrap();
 
         let events = store.insert_rrd_manifest(rrd_manifest.clone());
-        manifest_index.append(rrd_manifest).unwrap();
+        manifest_index
+            .append(rrd_manifest, store.entity_tree())
+            .unwrap();
         meta.on_events(&manifest_index, &store, &events);
 
         assert_eq!(meta.row_count_for_timeline(tl.name()), 4);
diff --git a/crates/store/re_entity_db/src/entity_db.rs b/crates/store/re_entity_db/src/entity_db.rs
index be6f505e1920..a56b8eff35b1 100644
--- a/crates/store/re_entity_db/src/entity_db.rs
+++ b/crates/store/re_entity_db/src/entity_db.rs
@@ -5,7 +5,6 @@ use std::{
     sync::Arc,
 };
 
-use itertools::Itertools as _;
 use nohash_hasher::IntMap;
 use re_byte_size::{MemUsageNode, MemUsageTree, MemUsageTreeCapture, SizeBytes as _};
 use re_chunk::{
@@ -233,11 +232,6 @@ impl EntityDb {
         }
     }
 
-    #[inline]
-    pub fn tree(&self) -> &crate::EntityTree {
-        &self.rrd_manifest_index.entity_tree
-    }
-
     /// Formats the entity tree into a human-readable text representation with component schema information.
     pub fn format_with_components(&self) -> String {
         let mut text = String::new();
@@ -245,30 +239,31 @@ impl EntityDb {
         let storage_engine = self.storage_engine();
         let store = storage_engine.store();
 
-        self.tree().visit_children_recursively(|entity_path| {
-            if entity_path.is_root() {
-                return;
-            }
-            let depth = entity_path.len() - 1;
-            let indent = "  ".repeat(depth);
-            text.push_str(&format!("{indent}{entity_path}\n"));
-            let Some(components) = store.schema().all_components_for_entity(entity_path) else {
-                return;
-            };
-            for &component in components {
-                let component_indent = "  ".repeat(depth + 1);
-                if let Some((component_type, datatype)) =
-                    store.schema().lookup_component_type(entity_path, component)
-                {
-                    let name = component_type
-                        .map_or_else(|| component.to_string(), |ct| ct.short_name().to_owned());
-                    text.push_str(&format!("{component_indent}{name}: {datatype}\n"));
-                } else {
-                    // Fallback to component identifier
-                    text.push_str(&format!("{component_indent}{component}\n"));
+        store
+            .entity_tree()
+            .visit_children_recursively(|entity_path| {
+                if entity_path.is_root() {
+                    return;
                 }
-            }
-        });
+                let depth = entity_path.len() - 1;
+                let indent = "  ".repeat(depth);
+                text.push_str(&format!("{indent}{entity_path}\n"));
+                let Some(components) = store.schema().all_components_for_entity(entity_path) else {
+                    return;
+                };
+                for &component in components {
+                    let component_indent = "  ".repeat(depth + 1);
+                    if let Some((component_type, datatype)) =
+                        store.schema().lookup_component_type(entity_path, component)
+                    {
+                        let name = component_type
+                            .map_or_else(|| component.to_string(), |ct| ct.short_name().to_owned());
+                        text.push_str(&format!("{component_indent}{name}: {datatype}\n"));
+                    } else {
+                        text.push_str(&format!("{component_indent}{component}\n"));
+                    }
+                }
+            });
         text
     }
 
@@ -721,7 +716,12 @@ impl EntityDb {
     /// Returns `true` also for entities higher up in the hierarchy.
     #[inline]
     pub fn is_known_entity(&self, entity_path: &EntityPath) -> bool {
-        self.tree().subtree(entity_path).is_some()
+        self.storage_engine
+            .read()
+            .store()
+            .entity_tree()
+            .subtree(entity_path)
+            .is_some()
     }
 
     /// If you log `world/points`, then that is a logged entity, but `world` is not,
@@ -745,7 +745,9 @@ impl EntityDb {
 
         self.on_store_events(&events);
 
-        if let Err(err) = self.rrd_manifest_index.append(rrd_manifest) {
+        let engine = self.storage_engine.read();
+        let entity_tree = engine.store().entity_tree();
+        if let Err(err) = self.rrd_manifest_index.append(rrd_manifest, entity_tree) {
             re_log::error!("Failed to append RRD manifest: {err}");
         }
 
@@ -859,29 +861,6 @@ impl EntityDb {
             engine.store(),
             store_events,
         );
-
-        self.rrd_manifest_index
-            .entity_tree
-            .on_store_additions(store_events.iter().filter_map(|e| e.to_addition()));
-
-        let dels = store_events
-            .iter()
-            .filter_map(|e| e.to_deletion())
-            .collect_vec();
-
-        // It is possible for writes to trigger deletions: specifically in the case of
-        // overwritten static data leading to dangling chunks.
-        let entity_paths_with_deletions =
-            dels.iter().map(|e| e.chunk.entity_path().clone()).collect();
-
-        {
-            re_tracing::profile_scope!("on_store_deletions");
-            self.rrd_manifest_index.entity_tree.on_store_deletions(
-                &engine,
-                &entity_paths_with_deletions,
-                &dels,
-            );
-        }
     }
 
     pub fn set_store_info(&mut self, store_info: SetStoreInfo) {
@@ -1011,10 +990,14 @@ impl EntityDb {
 
         let mut to_drop = vec![entity_path.clone()];
 
-        if let Some(tree) = self.tree().subtree(entity_path) {
-            tree.visit_children_recursively(|path| {
-                to_drop.push(path.clone());
-            });
+        {
+            let storage_engine = self.storage_engine();
+            let tree = storage_engine.store().entity_tree();
+            if let Some(subtree) = tree.subtree(entity_path) {
+                subtree.visit_children_recursively(|path| {
+                    to_drop.push(path.clone());
+                });
+            }
         }
 
         for entity_path in to_drop {
@@ -1144,13 +1127,13 @@ impl EntityDb {
     ///
     /// This excludes temporal data.
     pub fn subtree_stats_static(
-        &self,
         engine: &StorageEngineReadGuard<'_>,
         entity_path: &EntityPath,
     ) -> ChunkStoreChunkStats {
         re_tracing::profile_function!();
 
-        let Some(subtree) = self.tree().subtree(entity_path) else {
+        let entity_tree = engine.store().entity_tree();
+        let Some(subtree) = entity_tree.subtree(entity_path) else {
             return Default::default();
         };
 
@@ -1166,14 +1149,14 @@ impl EntityDb {
     ///
     /// This excludes static data.
     pub fn subtree_stats_on_timeline(
-        &self,
         engine: &StorageEngineReadGuard<'_>,
         entity_path: &EntityPath,
         timeline: &TimelineName,
     ) -> ChunkStoreChunkStats {
         re_tracing::profile_function!();
 
-        let Some(subtree) = self.tree().subtree(entity_path) else {
+        let entity_tree = engine.store().entity_tree();
+        let Some(subtree) = entity_tree.subtree(entity_path) else {
             return Default::default();
         };
 
@@ -1196,7 +1179,8 @@ impl EntityDb {
     ) -> bool {
         re_tracing::profile_function!();
 
-        let Some(subtree) = self.tree().subtree(entity_path) else {
+        let entity_tree = engine.store().entity_tree();
+        let Some(subtree) = entity_tree.subtree(entity_path) else {
             return false;
         };
 
@@ -1222,7 +1206,8 @@ impl EntityDb {
     ) -> bool {
         re_tracing::profile_function!();
 
-        let Some(subtree) = self.tree().subtree(entity_path) else {
+        let entity_tree = engine.store().entity_tree();
+        let Some(subtree) = entity_tree.subtree(entity_path) else {
             return false;
         };
 
diff --git a/crates/store/re_entity_db/src/entity_tree.rs b/crates/store/re_entity_db/src/entity_tree.rs
deleted file mode 100644
index b7c325089c50..000000000000
--- a/crates/store/re_entity_db/src/entity_tree.rs
+++ /dev/null
@@ -1,303 +0,0 @@
-use std::collections::BTreeMap;
-
-use nohash_hasher::IntSet;
-use re_chunk_store::{
-    ChunkStoreDiffAddition, ChunkStoreDiffDeletion, ChunkStoreEvent, ChunkStoreSubscriber,
-};
-use re_log_types::{EntityPath, EntityPathPart};
-use re_query::StorageEngineReadGuard;
-
-// ----------------------------------------------------------------------------
-
-/// A recursive, manually updated [`ChunkStoreSubscriber`] that maintains the entity hierarchy.
-///
-/// The tree contains a list of subtrees, and so on recursively.
-#[derive(Debug, Clone)]
-pub struct EntityTree {
-    /// Full path prefix to the root of this (sub)tree.
-    pub path: EntityPath,
-
-    /// Direct descendants of this (sub)tree.
-    pub children: BTreeMap,
-}
-
-impl Default for EntityTree {
-    fn default() -> Self {
-        Self::root()
-    }
-}
-
-// NOTE: This is only to let people know that this is in fact a [`ChunkStoreSubscriber`], so they A) don't try
-// to implement it on their own and B) don't try to register it.
-impl ChunkStoreSubscriber for EntityTree {
-    fn name(&self) -> String {
-        "rerun.store_subscribers.EntityTree".into()
-    }
-
-    fn as_any(&self) -> &dyn std::any::Any {
-        self
-    }
-
-    fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
-        self
-    }
-
-    #[expect(clippy::unimplemented)]
-    fn on_events(&mut self, _events: &[ChunkStoreEvent]) {
-        unimplemented!(
-            r"EntityTree view is maintained manually, see `EntityTree::on_store_{{additions|deletions}}`"
-        );
-    }
-}
-
-impl EntityTree {
-    pub fn root() -> Self {
-        Self::new(EntityPath::root())
-    }
-
-    pub fn new(path: EntityPath) -> Self {
-        Self {
-            path,
-            children: Default::default(),
-        }
-    }
-
-    /// Has no child entities.
-    pub fn is_leaf(&self) -> bool {
-        self.children.is_empty()
-    }
-
-    /// Returns `true` if this entity has no children and no data.
-    ///
-    /// Checking for the absence of data is neither costly nor totally free: do it a few hundreds or
-    /// thousands times a frame and it will absolutely kill framerate.
-    /// Don't blindly call this on every existing entity every frame: use [`ChunkStoreEvent`]s to make
-    /// sure anything changed at all first.
-    pub fn check_is_empty(&self, engine: &StorageEngineReadGuard<'_>) -> bool {
-        self.children.is_empty() && !engine.store().entity_has_physical_data(&self.path)
-    }
-
-    /// Updates the [`EntityTree`] by applying a batch of [`ChunkStoreDiffAddition`]s, adding any
-    /// new entities to the tree.
-    pub fn on_store_additions<'a>(
-        &mut self,
-        events: impl Iterator,
-    ) {
-        re_tracing::profile_function!();
-        for event in events {
-            self.on_store_addition(event);
-        }
-    }
-
-    fn on_store_addition(&mut self, event: &ChunkStoreDiffAddition) {
-        self.on_new_entity(event.delta_chunk().entity_path());
-    }
-
-    pub fn on_new_entity(&mut self, entity_path: &EntityPath) {
-        re_tracing::profile_function!();
-
-        // Book-keeping for each level in the hierarchy:
-        let mut tree = self;
-        for (i, part) in entity_path.iter().enumerate() {
-            tree = tree
-                .children
-                .entry(part.clone())
-                .or_insert_with(|| Self::new(entity_path.as_slice()[..=i].into()));
-        }
-    }
-
-    /// Updates the [`EntityTree`] by removing any entities which have no data and no children.
-    pub fn on_store_deletions(
-        &mut self,
-        engine: &StorageEngineReadGuard<'_>,
-        entity_paths_with_deletions: &IntSet,
-        events: &[&ChunkStoreDiffDeletion],
-    ) {
-        // NOTE: no re_tracing here because this is a recursive function
-        if entity_paths_with_deletions.is_empty() {
-            return; // early-out
-        }
-
-        // We don't actually use the events for anything, we just want to
-        // have a direct dependency on the chunk store which must have
-        // produced them by the time this function was called.
-        let _ = events;
-
-        self.children.retain(|_, entity| {
-            // this is placed first, because we'll only know if the child entity is empty after telling it to clear itself.
-            entity.on_store_deletions(engine, entity_paths_with_deletions, events);
-
-            let has_children = || !entity.children.is_empty();
-            // Checking for lack of data is not free, so make sure there is any reason to believe
-            // that any relevant data has changed first.
-            let has_recursive_deletion_events = || {
-                entity_paths_with_deletions
-                    .iter()
-                    .any(|removed_entity_path| removed_entity_path.starts_with(&entity.path))
-            };
-            let has_data = || engine.store().entity_has_physical_data(&entity.path);
-
-            let should_be_removed =
-                !has_children() && (has_recursive_deletion_events() && !has_data());
-
-            !should_be_removed
-        });
-    }
-
-    pub fn subtree(&self, path: &EntityPath) -> Option<&Self> {
-        fn subtree_recursive<'tree>(
-            this: &'tree EntityTree,
-            path: &[EntityPathPart],
-        ) -> Option<&'tree EntityTree> {
-            match path {
-                [] => Some(this),
-                [first, rest @ ..] => {
-                    let child = this.children.get(first)?;
-                    subtree_recursive(child, rest)
-                }
-            }
-        }
-
-        subtree_recursive(self, path.as_slice())
-    }
-
-    /// Invokes visitor for `self` and all children recursively.
-    pub fn visit_children_recursively(&self, mut visitor: impl FnMut(&EntityPath)) {
-        fn visit(this: &EntityTree, visitor: &mut impl FnMut(&EntityPath)) {
-            visitor(&this.path);
-            for child in this.children.values() {
-                visit(child, visitor);
-            }
-        }
-
-        visit(self, &mut visitor);
-    }
-
-    /// Invokes the `predicate` for `self` and all children recursively,
-    /// returning the _first_ entity for which the `predicate` returns `true`.
-    ///
-    /// Note that this function has early return semantics, meaning if multiple
-    /// entities would return `true`, only the first is returned.
-    /// The entities are yielded in order of their entity paths.
-    pub fn find_first_child_recursive(
-        &self,
-        mut predicate: impl FnMut(&EntityPath) -> bool,
-    ) -> Option<&Self> {
-        fn visit<'a>(
-            this: &'a EntityTree,
-            predicate: &mut impl FnMut(&EntityPath) -> bool,
-        ) -> Option<&'a EntityTree> {
-            if predicate(&this.path) {
-                return Some(this);
-            }
-
-            for child in this.children.values() {
-                if let Some(subtree) = visit(child, predicate) {
-                    // Early return
-                    return Some(subtree);
-                }
-            }
-
-            None
-        }
-
-        visit(self, &mut predicate)
-    }
-}
-
-impl re_byte_size::SizeBytes for EntityTree {
-    fn heap_size_bytes(&self) -> u64 {
-        let Self { path, children } = self;
-        path.heap_size_bytes() + children.heap_size_bytes()
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use std::sync::Arc;
-
-    use re_chunk::{Chunk, RowId};
-    use re_log_types::example_components::{MyPoint, MyPoints};
-    use re_log_types::{EntityPath, StoreId, TimePoint, Timeline};
-
-    use crate::EntityDb;
-
-    #[test]
-    fn deleting_descendants() -> anyhow::Result<()> {
-        re_log::setup_logging();
-
-        let mut db = EntityDb::new(StoreId::random(
-            re_log_types::StoreKind::Recording,
-            "test_app",
-        ));
-
-        let timeline_frame = Timeline::new_sequence("frame");
-
-        let entity_path_parent: EntityPath = "parent".into();
-        let entity_path_child: EntityPath = "parent/child1".into();
-        let entity_path_grandchild: EntityPath = "parent/child1/grandchild".into();
-
-        assert!(db.tree().check_is_empty(&db.storage_engine()));
-
-        {
-            let row_id = RowId::new();
-            let timepoint = TimePoint::from_iter([(timeline_frame, 10)]);
-            let point = MyPoint::new(1.0, 2.0);
-            let chunk = Chunk::builder(entity_path_grandchild.clone())
-                .with_component_batches(
-                    row_id,
-                    timepoint,
-                    [(MyPoints::descriptor_points(), &[point] as _)],
-                )
-                .build()?;
-
-            db.add_chunk(&Arc::new(chunk))?;
-        }
-
-        {
-            let parent = db
-                .tree()
-                .find_first_child_recursive(|entity_path| *entity_path == entity_path_parent)
-                .unwrap();
-            let child = db
-                .tree()
-                .find_first_child_recursive(|entity_path| *entity_path == entity_path_child)
-                .unwrap();
-            let grandchild = db
-                .tree()
-                .find_first_child_recursive(|entity_path| *entity_path == entity_path_grandchild)
-                .unwrap();
-
-            assert_eq!(1, parent.children.len());
-            assert_eq!(1, child.children.len());
-            assert_eq!(0, grandchild.children.len());
-
-            assert!(!db.tree().check_is_empty(&db.storage_engine()));
-            assert!(!parent.check_is_empty(&db.storage_engine()));
-            assert!(!child.check_is_empty(&db.storage_engine()));
-            assert!(!grandchild.check_is_empty(&db.storage_engine()));
-        }
-
-        let store_events = db.gc(&re_chunk_store::GarbageCollectionOptions::gc_everything());
-        db.on_store_events(&store_events);
-
-        {
-            let parent = db
-                .tree()
-                .find_first_child_recursive(|entity_path| *entity_path == entity_path_parent);
-            let child = db
-                .tree()
-                .find_first_child_recursive(|entity_path| *entity_path == entity_path_child);
-            let grandchild = db
-                .tree()
-                .find_first_child_recursive(|entity_path| *entity_path == entity_path_grandchild);
-
-            assert!(db.tree().check_is_empty(&db.storage_engine()));
-            assert!(parent.is_none());
-            assert!(child.is_none());
-            assert!(grandchild.is_none());
-        }
-
-        Ok(())
-    }
-}
diff --git a/crates/store/re_entity_db/src/lib.rs b/crates/store/re_entity_db/src/lib.rs
index 65c8f354d6d1..063dc4b1b5ee 100644
--- a/crates/store/re_entity_db/src/lib.rs
+++ b/crates/store/re_entity_db/src/lib.rs
@@ -7,7 +7,6 @@
 mod chunk_requests;
 mod data_meta_per_timeline;
 pub mod entity_db;
-pub mod entity_tree;
 mod ingestion_statistics;
 mod instance_path;
 mod rrd_manifest_index;
@@ -19,7 +18,6 @@ mod versioned_instance_path;
 pub use re_log_types::{EntityPath, EntityPathPart, TimeInt, Timeline};
 
 pub use self::entity_db::{DEFAULT_GC_TIME_BUDGET, EntityDb};
-pub use self::entity_tree::EntityTree;
 pub use self::ingestion_statistics::{IngestionStatistics, LatencySnapshot, LatencyStats};
 pub use self::instance_path::{InstancePath, InstancePathHash};
 pub use self::rrd_manifest_index::{
@@ -27,6 +25,7 @@ pub use self::rrd_manifest_index::{
 };
 pub use self::store_bundle::{StoreBundle, StoreLoadError};
 pub use self::versioned_instance_path::{VersionedInstancePath, VersionedInstancePathHash};
+pub use re_chunk_store::EntityTree;
 
 pub mod external {
     pub use {re_chunk_store, re_query};
diff --git a/crates/store/re_entity_db/src/rrd_manifest_index.rs b/crates/store/re_entity_db/src/rrd_manifest_index.rs
index 08dc188b73a9..90226e16aa4f 100644
--- a/crates/store/re_entity_db/src/rrd_manifest_index.rs
+++ b/crates/store/re_entity_db/src/rrd_manifest_index.rs
@@ -175,7 +175,6 @@ pub struct RrdManifestIndex {
     /// Computed from chunk time ranges when the manifest is complete.
     data_time_ranges: BTreeMap>,
 
-    pub entity_tree: crate::EntityTree,
     entity_has_static_data: IntSet,
 
     full_uncompressed_size: u64,
@@ -188,11 +187,14 @@ impl std::fmt::Debug for RrdManifestIndex {
 }
 
 impl RrdManifestIndex {
-    pub fn append(&mut self, delta: Arc) -> CodecResult<()> {
+    pub fn append(
+        &mut self,
+        delta: Arc,
+        entity_tree: &re_chunk_store::EntityTree,
+    ) -> CodecResult<()> {
         re_tracing::profile_function!();
 
         self.update_timeline_stats(&delta);
-        self.update_entity_tree(&delta);
         self.update_entity_static_data(&delta);
         self.chunk_prioritizer.on_rrd_manifest(&delta);
 
@@ -246,7 +248,7 @@ impl RrdManifestIndex {
         };
 
         self.sorted_chunks =
-            SortedTemporalChunks::new(&self.entity_tree, new_full_manifest.temporal_map());
+            SortedTemporalChunks::new(entity_tree, new_full_manifest.temporal_map());
 
         self.manifest = Some(new_full_manifest);
 
@@ -285,12 +287,6 @@ impl RrdManifestIndex {
         }
     }
 
-    fn update_entity_tree(&mut self, manifest: &RrdManifest) {
-        for entity in manifest.recording_schema().all_entities() {
-            self.entity_tree.on_new_entity(entity);
-        }
-    }
-
     fn update_entity_static_data(&mut self, manifest: &RrdManifest) {
         for entity in manifest.static_map().keys() {
             self.entity_has_static_data.insert(entity.clone());
@@ -734,7 +730,6 @@ impl re_byte_size::SizeBytes for RrdManifestIndex {
 
         let Self {
             entity_has_static_data,
-            entity_tree,
             manifest,
             sorted_chunks,
             loaded_ranges,
@@ -747,7 +742,6 @@ impl re_byte_size::SizeBytes for RrdManifestIndex {
         } = self;
 
         entity_has_static_data.heap_size_bytes()
-            + entity_tree.heap_size_bytes()
             + manifest.heap_size_bytes()
             + sorted_chunks.heap_size_bytes()
             + loaded_ranges.heap_size_bytes()
@@ -766,7 +760,6 @@ impl MemUsageTreeCapture for RrdManifestIndex {
 
         let Self {
             entity_has_static_data,
-            entity_tree,
             sorted_chunks,
             loaded_ranges,
             manifest,
@@ -784,7 +777,6 @@ impl MemUsageTreeCapture for RrdManifestIndex {
             "entity_has_static_data",
             entity_has_static_data.total_size_bytes(),
         );
-        node.add("entity_tree", entity_tree.total_size_bytes());
         node.add("sorted_chunks", sorted_chunks.total_size_bytes());
         node.add("loaded_ranges", loaded_ranges.total_size_bytes());
         node.add("manifest", manifest.total_size_bytes());
diff --git a/crates/store/re_entity_db/src/rrd_manifest_index/sorted_temporal_chunks.rs b/crates/store/re_entity_db/src/rrd_manifest_index/sorted_temporal_chunks.rs
index 7e0578ab387d..5bc8d656444c 100644
--- a/crates/store/re_entity_db/src/rrd_manifest_index/sorted_temporal_chunks.rs
+++ b/crates/store/re_entity_db/src/rrd_manifest_index/sorted_temporal_chunks.rs
@@ -17,6 +17,7 @@ use std::collections::BTreeMap;
 
 use nohash_hasher::IntMap;
 use re_chunk::{ChunkId, TimelineName};
+use re_chunk_store::EntityTree;
 use re_log_types::{AbsoluteTimeRange, EntityPathHash};
 
 /// Summary information about a chunk for display/query purposes.
@@ -122,7 +123,7 @@ impl re_byte_size::SizeBytes for SortedTemporalChunks {
 
 impl SortedTemporalChunks {
     pub fn new(
-        entity_tree: &crate::EntityTree,
+        entity_tree: &EntityTree,
         native_temporal_map: &re_log_encoding::RrdManifestTemporalMap,
     ) -> Self {
         re_tracing::profile_function!();
@@ -137,7 +138,7 @@ impl SortedTemporalChunks {
     // TODO(emilk): handle incremental ingestion
     fn update(
         &mut self,
-        entity_tree: &crate::EntityTree,
+        entity_tree: &EntityTree,
         native_temporal_map: &re_log_encoding::RrdManifestTemporalMap,
     ) {
         re_tracing::profile_function!();
@@ -168,14 +169,14 @@ impl SortedTemporalChunks {
         }
 
         /// Bottom-up entity traversal
-        fn visit(current: &crate::EntityTree, visitor: &mut impl FnMut(&crate::EntityTree)) {
+        fn visit(current: &EntityTree, visitor: &mut impl FnMut(&EntityTree)) {
             for child in current.children.values() {
                 visit(child, visitor);
             }
             visitor(current);
         }
 
-        visit(entity_tree, &mut |node: &crate::EntityTree| {
+        visit(entity_tree, &mut |node: &EntityTree| {
             for per_entity in self.per_timeline.values_mut() {
                 // Collect all chunks from direct children which now already includes
                 // their descendants and components
@@ -261,8 +262,8 @@ mod tests {
     use re_chunk::EntityPath;
     use re_log_types::TimeInt;
 
-    fn make_entity_tree(paths: &[&EntityPath]) -> crate::EntityTree {
-        let mut tree = crate::EntityTree::root();
+    fn make_entity_tree(paths: &[&EntityPath]) -> EntityTree {
+        let mut tree = EntityTree::root();
         for path in paths {
             tree.on_new_entity(path);
         }
diff --git a/crates/viewer/re_context_menu/src/actions/collapse_expand_all.rs b/crates/viewer/re_context_menu/src/actions/collapse_expand_all.rs
index 2cf4c1a8eb50..d00f7effc428 100644
--- a/crates/viewer/re_context_menu/src/actions/collapse_expand_all.rs
+++ b/crates/viewer/re_context_menu/src/actions/collapse_expand_all.rs
@@ -47,8 +47,9 @@ impl ContextMenuAction for CollapseExpandAllAction {
             //TODO(ab): for DataResult, walk the data result tree instead!
             Item::DataResult(data_result) => ctx
                 .viewer_context
-                .recording()
-                .tree()
+                .recording_engine()
+                .store()
+                .entity_tree()
                 .subtree(&data_result.instance_path.entity_path)
                 .is_some_and(|subtree| !subtree.is_leaf()),
         }
diff --git a/crates/viewer/re_context_menu/src/collapse_expand.rs b/crates/viewer/re_context_menu/src/collapse_expand.rs
index 27ddae57c4f0..44b120862ab4 100644
--- a/crates/viewer/re_context_menu/src/collapse_expand.rs
+++ b/crates/viewer/re_context_menu/src/collapse_expand.rs
@@ -59,7 +59,9 @@ pub fn collapse_expand_data_result(
 ) {
     //TODO(ab): here we should in principle walk the DataResult tree instead of the entity tree
     // but the current API isn't super ergonomic.
-    let Some(subtree) = ctx.recording().tree().subtree(&instance_path.entity_path) else {
+    let recording_engine = ctx.recording_engine();
+    let tree = recording_engine.store().entity_tree();
+    let Some(subtree) = tree.subtree(&instance_path.entity_path) else {
         return;
     };
 
@@ -77,7 +79,9 @@ pub fn collapse_expand_instance_path(
     scope: CollapseScope,
     expand: bool,
 ) {
-    let Some(subtree) = db.tree().subtree(&instance_path.entity_path) else {
+    let db_engine = db.storage_engine();
+    let tree = db_engine.store().entity_tree();
+    let Some(subtree) = tree.subtree(&instance_path.entity_path) else {
         return;
     };
 
diff --git a/crates/viewer/re_data_ui/src/component_path_ui.rs b/crates/viewer/re_data_ui/src/component_path_ui.rs
index 6094b2d1580a..8d5bb025029d 100644
--- a/crates/viewer/re_data_ui/src/component_path_ui.rs
+++ b/crates/viewer/re_data_ui/src/component_path_ui.rs
@@ -26,7 +26,7 @@ impl DataUi for ComponentPath {
                 hits,
             }
             .data_ui(ctx, ui, ui_layout);
-        } else if ctx.db.tree().subtree(&entity_path).is_some() {
+        } else if engine.store().entity_tree().subtree(&entity_path).is_some() {
             let any_missing_chunks = !results.missing_virtual.is_empty();
 
             // TODO(RR-3670): figure out how to handle missing chunks
diff --git a/crates/viewer/re_data_ui/src/item_ui.rs b/crates/viewer/re_data_ui/src/item_ui.rs
index b1ffbf47ede8..39e5eefed438 100644
--- a/crates/viewer/re_data_ui/src/item_ui.rs
+++ b/crates/viewer/re_data_ui/src/item_ui.rs
@@ -298,8 +298,8 @@ fn entity_tree_stats_ui(
 
     let (static_stats, timeline_stats) = if include_subtree {
         (
-            db.subtree_stats_static(&engine, &tree.path),
-            db.subtree_stats_on_timeline(&engine, &tree.path, timeline),
+            re_entity_db::EntityDb::subtree_stats_static(&engine, &tree.path),
+            re_entity_db::EntityDb::subtree_stats_on_timeline(&engine, &tree.path, timeline),
         )
     } else {
         (
@@ -514,7 +514,12 @@ pub fn instance_hover_card_ui(
     // Then we can move the size view into `data_ui`.
 
     if instance_path.instance.is_all() {
-        if let Some(subtree) = ctx.db.tree().subtree(&instance_path.entity_path) {
+        let db_engine = ctx.db.storage_engine();
+        if let Some(subtree) = db_engine
+            .store()
+            .entity_tree()
+            .subtree(&instance_path.entity_path)
+        {
             entity_tree_stats_ui(ui, &ctx.timeline_name(), ctx.db, subtree, include_subtree);
         }
     } else {
diff --git a/crates/viewer/re_selection_panel/src/view_entity_picker.rs b/crates/viewer/re_selection_panel/src/view_entity_picker.rs
index e3e255c2602b..b708698d22c1 100644
--- a/crates/viewer/re_selection_panel/src/view_entity_picker.rs
+++ b/crates/viewer/re_selection_panel/src/view_entity_picker.rs
@@ -85,7 +85,8 @@ fn add_entities_ui(
 ) {
     re_tracing::profile_function!();
 
-    let tree = &ctx.recording().tree();
+    let recording_engine = ctx.recording_engine();
+    let tree = recording_engine.store().entity_tree();
     let query_result = ctx.lookup_query_result(view.id);
     let entity_path_filter = view.contents.entity_path_filter();
     let entities_add_info = create_entity_add_info(ctx, tree, view, query_result);
diff --git a/crates/viewer/re_selection_panel/src/visible_time_range_ui.rs b/crates/viewer/re_selection_panel/src/visible_time_range_ui.rs
index da7e2ef70382..2c946c11e40e 100644
--- a/crates/viewer/re_selection_panel/src/visible_time_range_ui.rs
+++ b/crates/viewer/re_selection_panel/src/visible_time_range_ui.rs
@@ -23,9 +23,10 @@ pub fn visible_time_range_ui_for_view(
         return;
     }
 
+    let engine = ctx.store_context.blueprint.storage_engine();
     let property_path = entity_path_for_view_property(
         view.id,
-        ctx.store_context.blueprint.tree(),
+        engine.store().entity_tree(),
         re_sdk_types::blueprint::archetypes::VisibleTimeRanges::name(),
     );
 
diff --git a/crates/viewer/re_time_panel/src/streams_tree_data.rs b/crates/viewer/re_time_panel/src/streams_tree_data.rs
index fb5c44275515..2a21243238c0 100644
--- a/crates/viewer/re_time_panel/src/streams_tree_data.rs
+++ b/crates/viewer/re_time_panel/src/streams_tree_data.rs
@@ -33,8 +33,9 @@ impl StreamsTreeData {
 
         let mut hierarchy = Vec::default();
         let mut hierarchy_highlights = PathRanges::default();
+        let db_engine = db.storage_engine();
         let root_data = EntityData::from_entity_tree_and_filter(
-            db.tree(),
+            db_engine.store().entity_tree(),
             filter_matcher,
             &mut hierarchy,
             &mut hierarchy_highlights,
diff --git a/crates/viewer/re_view_spatial/src/view_2d.rs b/crates/viewer/re_view_spatial/src/view_2d.rs
index 087485fc409f..bce2c48db3c0 100644
--- a/crates/viewer/re_view_spatial/src/view_2d.rs
+++ b/crates/viewer/re_view_spatial/src/view_2d.rs
@@ -347,7 +347,8 @@ fn recommended_views_with_image_splits(
 ) {
     re_tracing::profile_function!();
 
-    let tree = ctx.recording().tree();
+    let engine = ctx.recording_engine();
+    let tree = engine.store().entity_tree();
 
     let Some(subtree) = tree.subtree(recommended_origin) else {
         re_log::debug_warn_once!("Ancestor of entity not found in entity tree.");
diff --git a/crates/viewer/re_view_spatial/src/view_3d.rs b/crates/viewer/re_view_spatial/src/view_3d.rs
index 96a3e08109a2..00b1c5411da5 100644
--- a/crates/viewer/re_view_spatial/src/view_3d.rs
+++ b/crates/viewer/re_view_spatial/src/view_3d.rs
@@ -387,7 +387,7 @@ impl ViewClass for SpatialView3D {
                 // Is there a nicer way for this or do we want a visualizer for view coordinates anyways?
                 // There's also a strong argument to be made that ViewCoordinates implies a 3D space, thus changing the SpacialTopology accordingly!
                 let engine = ctx.recording_engine();
-                ctx.recording().tree().visit_children_recursively(|path| {
+                engine.store().entity_tree().visit_children_recursively(|path| {
                     if let Some(components) = engine.schema().all_components_for_entity(path)
                         && components.iter().any(|&component| {
                             archetypes::Pinhole::all_components().iter().any(|c| c.component == component)
diff --git a/crates/viewer/re_view_spatial/tests/bgr_images.rs b/crates/viewer/re_view_spatial/tests/bgr_images.rs
index c09202f5c6fc..332129c990ed 100644
--- a/crates/viewer/re_view_spatial/tests/bgr_images.rs
+++ b/crates/viewer/re_view_spatial/tests/bgr_images.rs
@@ -37,9 +37,11 @@ fn run_bgr_test(
     let view_id = test_context.setup_viewport_blueprint(|ctx, blueprint| {
         let view =
             ViewBlueprint::new_with_root_wildcard(re_view_spatial::SpatialView2D::identifier());
+        let engine = ctx.store_context.blueprint.storage_engine();
+        let blueprint_tree = engine.store().entity_tree();
         let property_path = re_viewport_blueprint::entity_path_for_view_property(
             view.id,
-            ctx.store_context.blueprint.tree(),
+            blueprint_tree,
             re_sdk_types::blueprint::archetypes::Background::name(),
         );
         ctx.save_blueprint_archetype(
diff --git a/crates/viewer/re_view_spatial/tests/blueprint_2d.rs b/crates/viewer/re_view_spatial/tests/blueprint_2d.rs
index d33dc5b10382..3beee9f39647 100644
--- a/crates/viewer/re_view_spatial/tests/blueprint_2d.rs
+++ b/crates/viewer/re_view_spatial/tests/blueprint_2d.rs
@@ -94,9 +94,11 @@ fn setup_blueprint(
     test_context.setup_viewport_blueprint(|ctx, blueprint| {
         let view = ViewBlueprint::new_with_root_wildcard(SpatialView2D::identifier());
 
+        let engine = ctx.store_context.blueprint.storage_engine();
+        let blueprint_tree = engine.store().entity_tree();
         let property_path = re_viewport_blueprint::entity_path_for_view_property(
             view.id,
-            ctx.store_context.blueprint.tree(),
+            blueprint_tree,
             re_sdk_types::blueprint::archetypes::VisualBounds2D::name(),
         );
         ctx.save_blueprint_archetype(
diff --git a/crates/viewer/re_view_spatial/tests/latest_at_partial_updates.rs b/crates/viewer/re_view_spatial/tests/latest_at_partial_updates.rs
index 5b81927f62b4..bc7b7f560d30 100644
--- a/crates/viewer/re_view_spatial/tests/latest_at_partial_updates.rs
+++ b/crates/viewer/re_view_spatial/tests/latest_at_partial_updates.rs
@@ -86,9 +86,11 @@ fn setup_blueprint(test_context: &mut TestContext) -> ViewId {
     test_context.setup_viewport_blueprint(|ctx, blueprint| {
         let view = ViewBlueprint::new_with_root_wildcard(SpatialView2D::identifier());
 
+        let engine = ctx.store_context.blueprint.storage_engine();
+        let blueprint_tree = engine.store().entity_tree();
         let property_path = re_viewport_blueprint::entity_path_for_view_property(
             view.id,
-            ctx.store_context.blueprint.tree(),
+            blueprint_tree,
             re_sdk_types::blueprint::archetypes::VisualBounds2D::name(),
         );
         ctx.save_blueprint_archetype(
diff --git a/crates/viewer/re_view_spatial/tests/visible_time_range.rs b/crates/viewer/re_view_spatial/tests/visible_time_range.rs
index 76b0dbafd70b..bde46911c836 100644
--- a/crates/viewer/re_view_spatial/tests/visible_time_range.rs
+++ b/crates/viewer/re_view_spatial/tests/visible_time_range.rs
@@ -343,9 +343,11 @@ fn setup_blueprint(
         ));
 
         // Set the bounds such that the points are fully visible, that way we get more pixels contributing to the output.
+        let engine = ctx.store_context.blueprint.storage_engine();
+        let blueprint_tree = engine.store().entity_tree();
         let property_path = re_viewport_blueprint::entity_path_for_view_property(
             view_id,
-            ctx.store_context.blueprint.tree(),
+            blueprint_tree,
             re_sdk_types::blueprint::archetypes::VisualBounds2D::name(),
         );
         ctx.save_blueprint_archetype(
@@ -363,7 +365,7 @@ fn setup_blueprint(
                 re_sdk_types::blueprint::archetypes::VisibleTimeRanges::new([time_range]);
             let property_path = re_viewport_blueprint::entity_path_for_view_property(
                 view_id,
-                ctx.store_context.blueprint.tree(),
+                blueprint_tree,
                 re_sdk_types::blueprint::archetypes::VisibleTimeRanges::name(),
             );
 
diff --git a/crates/viewer/re_viewport/src/viewport_ui.rs b/crates/viewer/re_viewport/src/viewport_ui.rs
index aa238ad55291..6593173cc3db 100644
--- a/crates/viewer/re_viewport/src/viewport_ui.rs
+++ b/crates/viewer/re_viewport/src/viewport_ui.rs
@@ -261,9 +261,10 @@ impl ViewportUi {
         view_blueprint: &ViewBlueprint,
         entities: &[EntityPath],
     ) -> bool {
+        let recording_engine = ctx.recording_engine();
         let add_info = create_entity_add_info(
             ctx,
-            ctx.recording().tree(),
+            recording_engine.store().entity_tree(),
             view_blueprint,
             ctx.lookup_query_result(view_blueprint.id),
         );
diff --git a/crates/viewer/re_viewport_blueprint/src/view.rs b/crates/viewer/re_viewport_blueprint/src/view.rs
index 348f587a00c8..8904f3126109 100644
--- a/crates/viewer/re_viewport_blueprint/src/view.rs
+++ b/crates/viewer/re_viewport_blueprint/src/view.rs
@@ -248,7 +248,8 @@ impl ViewBlueprint {
 
         // Create pending write operations to duplicate the entire subtree
         // TODO(jleibs): This should be a helper somewhere.
-        if let Some(tree) = blueprint.tree().subtree(¤t_path) {
+        let bp_engine = blueprint.storage_engine();
+        if let Some(tree) = bp_engine.store().entity_tree().subtree(¤t_path) {
             tree.visit_children_recursively(|path| {
                 let sub_path: EntityPath = new_path
                     .iter()
diff --git a/crates/viewer/re_viewport_blueprint/src/view_contents.rs b/crates/viewer/re_viewport_blueprint/src/view_contents.rs
index e39db95d742b..767b2502c0fe 100644
--- a/crates/viewer/re_viewport_blueprint/src/view_contents.rs
+++ b/crates/viewer/re_viewport_blueprint/src/view_contents.rs
@@ -315,7 +315,7 @@ impl ViewContents {
         let root_handle = {
             re_tracing::profile_scope!("add_entity_tree_to_data_results_recursive");
             executor.add_entity_tree_to_data_results_recursive(
-                ctx.recording.tree(),
+                ctx.recording.storage_engine().store().entity_tree(),
                 &mut data_results,
                 &mut num_matching_entities,
                 &mut num_visualized_entities,
@@ -550,8 +550,10 @@ impl DataQueryPropertyResolver<'_> {
                 // the index in the low 64 bits of the UUID. We extract that index to
                 // build a map from index → existing instruction id.
                 let mut known_ids: IntMap = Default::default();
-                if let Some(subtree) = blueprint
-                    .tree()
+                let bp_engine = blueprint.storage_engine();
+                if let Some(subtree) = bp_engine
+                    .store()
+                    .entity_tree()
                     .subtree(&node.data_result.override_base_path)
                 {
                     for child_tree in subtree.children.values() {
diff --git a/crates/viewer/re_viewport_blueprint/src/view_properties.rs b/crates/viewer/re_viewport_blueprint/src/view_properties.rs
index f30c7f52f739..32b58d48738d 100644
--- a/crates/viewer/re_viewport_blueprint/src/view_properties.rs
+++ b/crates/viewer/re_viewport_blueprint/src/view_properties.rs
@@ -1,5 +1,6 @@
 use arrow::array::ArrayRef;
 use re_chunk::ComponentIdentifier;
+use re_chunk_store::EntityTree;
 use re_chunk_store::LatestAtQuery;
 use re_entity_db::EntityDb;
 use re_entity_db::external::re_query::LatestAtResults;
@@ -7,7 +8,6 @@ use re_log_types::EntityPath;
 use re_sdk_types::{
     Archetype, ArchetypeName, ComponentBatch, ComponentDescriptor, DeserializationError,
 };
-use re_viewer_context::external::re_entity_db::EntityTree;
 use re_viewer_context::{
     BlueprintContext, ComponentFallbackError, QueryContext, ViewContext, ViewId,
     ViewSystemExecutionError, ViewerContext,
@@ -76,8 +76,9 @@ impl ViewProperty {
         archetype_name: ArchetypeName,
         component_descrs: Vec,
     ) -> Self {
+        let engine = blueprint_db.storage_engine();
         let blueprint_store_path =
-            entity_path_for_view_property(view_id, blueprint_db.tree(), archetype_name);
+            entity_path_for_view_property(view_id, engine.store().entity_tree(), archetype_name);
 
         let query_results = blueprint_db.latest_at(
             &blueprint_query,

From ea4283e4bf85b670e170513ed1c4f843bfbb514a Mon Sep 17 00:00:00 2001
From: Isse 
Date: Wed, 18 Mar 2026 10:30:08 +0100
Subject: [PATCH 173/513] Improve handling of texture creation errors.

### Related

- Closes RR-4076

### What

- Adds missing `DedupableError` for wgpu resource errors, which caused
the warn logs in the issue.
- Special cases `TextureCreationError` to not log errors for them.
- Renames the previous `DebugLabel` to `Label` and makes them exist in
release mode as well.

Source-Ref: 74907a83a273d262336dc0a68f35a3445429e86f
---
 .../src/allocator/data_texture_source.rs      |  4 +-
 .../src/allocator/uniform_buffer_fill.rs      |  8 +-
 crates/viewer/re_renderer/src/context.rs      |  5 +-
 crates/viewer/re_renderer/src/debug_label.rs  | 90 -------------------
 .../re_renderer/src/draw_phases/outlines.rs   | 18 ++--
 .../src/draw_phases/picking_layer.rs          | 11 ++-
 .../re_renderer/src/draw_phases/screenshot.rs |  8 +-
 .../src/error_handling/error_tracker.rs       | 17 +++-
 .../src/error_handling/wgpu_core_error.rs     | 24 ++++-
 crates/viewer/re_renderer/src/importer/dae.rs |  4 +-
 crates/viewer/re_renderer/src/importer/stl.rs |  4 +-
 crates/viewer/re_renderer/src/label.rs        | 56 ++++++++++++
 crates/viewer/re_renderer/src/lib.rs          |  4 +-
 .../re_renderer/src/line_drawable_builder.rs  |  4 +-
 crates/viewer/re_renderer/src/mesh.rs         |  6 +-
 .../re_renderer/src/point_cloud_builder.rs    |  4 +-
 .../viewer/re_renderer/src/renderer/lines.rs  |  6 +-
 .../re_renderer/src/renderer/point_cloud.rs   |  8 +-
 .../image_data_to_texture.rs                  | 18 ++--
 .../src/resource_managers/yuv_converter.rs    |  2 +-
 crates/viewer/re_renderer/src/view_builder.rs | 10 +--
 .../wgpu_resources/bind_group_layout_pool.rs  |  6 +-
 .../src/wgpu_resources/bind_group_pool.rs     |  6 +-
 .../src/wgpu_resources/buffer_pool.rs         |  6 +-
 .../wgpu_resources/pipeline_layout_pool.rs    |  6 +-
 .../wgpu_resources/render_pipeline_pool.rs    |  6 +-
 .../src/wgpu_resources/sampler_pool.rs        |  6 +-
 .../src/wgpu_resources/shader_module_pool.rs  |  8 +-
 .../src/wgpu_resources/texture_pool.rs        |  8 +-
 .../re_view_spatial/src/picking_ui_pixel.rs   |  2 +-
 .../viewer/re_view_spatial/src/proc_mesh.rs   |  2 +-
 .../visualizers/utilities/proc_mesh_vis.rs    |  4 +-
 .../viewer/re_view_tensor/src/view_class.rs   |  2 +-
 .../re_viewer_context/src/gpu_bridge/mod.rs   |  2 +-
 34 files changed, 185 insertions(+), 190 deletions(-)
 delete mode 100644 crates/viewer/re_renderer/src/debug_label.rs
 create mode 100644 crates/viewer/re_renderer/src/label.rs

diff --git a/crates/viewer/re_renderer/src/allocator/data_texture_source.rs b/crates/viewer/re_renderer/src/allocator/data_texture_source.rs
index 9f2fbfb2a997..46f506c16541 100644
--- a/crates/viewer/re_renderer/src/allocator/data_texture_source.rs
+++ b/crates/viewer/re_renderer/src/allocator/data_texture_source.rs
@@ -3,7 +3,7 @@ use re_log::debug_assert;
 
 use super::{CpuWriteGpuReadBuffer, CpuWriteGpuReadError};
 use crate::wgpu_resources::{self, GpuTexture};
-use crate::{DebugLabel, RenderContext};
+use crate::{Label, RenderContext};
 
 #[derive(thiserror::Error, Debug, PartialEq, Eq)]
 pub enum DataTextureSourceWriteError {
@@ -330,7 +330,7 @@ impl<'a, T: Pod + Send + Sync> DataTextureSource<'a, T> {
     pub fn finish(
         self,
         texture_format: wgpu::TextureFormat,
-        texture_label: impl Into,
+        texture_label: impl Into
Release notes

Sourced from geopandas's releases.

Version 1.1.2

What's Changed

Bug fixes:

  • Fix an issue that caused an error in GeoDataFrame.from_features when there is no properties field (#3599).
  • Fix read_file and to_file errors (#3682)
  • Fix read_parquet with to_pandas_kwargs for complex (list/struct) arrow types (#3640)
  • value_counts on GeoSeries now preserves CRS in index (#3669)
  • Fix f-string placeholders appearing in error messages when pyogrio cannot be imported (#3682).
  • Fix read_parquet with to_pandas_kwargs for complex (list/struct) arrow types (#3640).
  • .to_json now provides a clearer error message when called on a GeoDataFrame without an active geometry column (#3648).
  • Calling del gdf["geometry"] now will downcast to a pd.DataFrame if there are no geometry columns left in the dataframe (#3648).
  • Fix SQL injection in to_postgis via geometry column name (#3681).

Full Changelog: https://github.com/geopandas/geopandas/compare/v1.1.1...v1.1.2