diff --git a/crates/re_arrow_store/src/arrow_util.rs b/crates/re_arrow_store/src/arrow_util.rs index ef119bd51b31..44bf32b5bbd8 100644 --- a/crates/re_arrow_store/src/arrow_util.rs +++ b/crates/re_arrow_store/src/arrow_util.rs @@ -233,109 +233,64 @@ fn test_clean_for_polars_nomodify() { assert_eq!(cell.as_arrow_ref(), &*cleaned); } -#[test] -fn test_clean_for_polars_modify() { - use re_log_types::{DataCell, Pinhole, Transform}; - // transforms are a nice pathological type with both Unions and FixedSizeLists - let transforms = vec![Transform::Pinhole(Pinhole { - image_from_cam: [[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]].into(), - resolution: None, - })]; +#[cfg(test)] +mod tests { + use arrow2::datatypes::{DataType, Field, UnionMode}; + use arrow2_convert::{ArrowDeserialize, ArrowField, ArrowSerialize}; + use re_log_types::{component_types::Vec3D, Component, DataCell}; - let cell: DataCell = transforms.try_into().unwrap(); - assert_eq!( - *cell.datatype(), - DataType::Union( - vec![ - Field::new("Unknown", DataType::Boolean, false), - Field::new( - "Rigid3", - DataType::Struct(vec![ - Field::new( - "rotation", - DataType::FixedSizeList( - Box::new(Field::new("item", DataType::Float32, false)), - 4 - ), - false - ), - Field::new( - "translation", - DataType::FixedSizeList( - Box::new(Field::new("item", DataType::Float32, false)), - 3 - ), - false - ) - ]), - false - ), - Field::new( - "Pinhole", - DataType::Struct(vec![ - Field::new( - "image_from_cam", - DataType::FixedSizeList( - Box::new(Field::new("item", DataType::Float32, false)), - 9 - ), - false, - ), - Field::new( - "resolution", - DataType::FixedSizeList( - Box::new(Field::new("item", DataType::Float32, false)), - 2 - ), - true, - ), - ]), - false - ) - ], - None, - UnionMode::Dense - ), - ); + use crate::ArrayExt; - let cleaned = cell.as_arrow_ref().clean_for_polars(); + #[derive(Clone, Copy, Debug, PartialEq, ArrowField, ArrowSerialize, ArrowDeserialize)] + #[arrow_field(type = "dense")] + enum TestComponentWithUnionAndFixedSizeList { + Bool(bool), + Vec3D(Vec3D), + } - assert_eq!( - *cleaned.data_type(), - DataType::Struct(vec![ - Field::new("Unknown", DataType::Boolean, false), - Field::new( - "Rigid3", - DataType::Struct(vec![ - Field::new( - "rotation", - DataType::List(Box::new(Field::new("item", DataType::Float32, false)),), - false - ), + impl Component for TestComponentWithUnionAndFixedSizeList { + fn name() -> re_log_types::ComponentName { + "test_component_with_union_and_fixed_size_list".into() + } + } + + #[test] + fn test_clean_for_polars_modify() { + // Pick a type with both Unions and FixedSizeLists + let elements = vec![TestComponentWithUnionAndFixedSizeList::Bool(false)]; + + let cell: DataCell = elements.try_into().unwrap(); + assert_eq!( + *cell.datatype(), + DataType::Union( + vec![ + Field::new("Bool", DataType::Boolean, false), Field::new( - "translation", - DataType::List(Box::new(Field::new("item", DataType::Float32, false)),), + "Vec3D", + DataType::FixedSizeList( + Box::new(Field::new("item", DataType::Float32, false)), + 3 + ), false ) - ]), - false - ), - Field::new( - "Pinhole", - DataType::Struct(vec![ - Field::new( - "image_from_cam", - DataType::List(Box::new(Field::new("item", DataType::Float32, false))), - false, - ), - Field::new( - "resolution", - DataType::List(Box::new(Field::new("item", DataType::Float32, false))), - true, - ), - ]), - false + ], + None, + UnionMode::Dense ) - ],), - ); + ); + + let cleaned = cell.as_arrow_ref().clean_for_polars(); + + assert_eq!( + *cleaned.data_type(), + DataType::Struct(vec![ + Field::new("Bool", DataType::Boolean, false), + Field::new( + "Vec3D", + DataType::List(Box::new(Field::new("item", DataType::Float32, false))), + false + ) + ],) + ); + } } diff --git a/crates/re_data_ui/src/component_ui_registry.rs b/crates/re_data_ui/src/component_ui_registry.rs index f1a37eed5eda..e140cc781120 100644 --- a/crates/re_data_ui/src/component_ui_registry.rs +++ b/crates/re_data_ui/src/component_ui_registry.rs @@ -51,6 +51,7 @@ pub fn create_component_ui_registry() -> ComponentUiRegistry { add::(&mut registry); // add::(&mut registry); // add::(&mut registry); + add::(&mut registry); // add::(&mut registry); // add::(&mut registry); add::(&mut registry); @@ -59,7 +60,7 @@ pub fn create_component_ui_registry() -> ComponentUiRegistry { // add::(&mut registry); add::(&mut registry); add::(&mut registry); - add::(&mut registry); + add::(&mut registry); add::(&mut registry); add::(&mut registry); add::(&mut registry); diff --git a/crates/re_data_ui/src/data.rs b/crates/re_data_ui/src/data.rs index db0f8ca940eb..ec879d726e7d 100644 --- a/crates/re_data_ui/src/data.rs +++ b/crates/re_data_ui/src/data.rs @@ -1,10 +1,8 @@ use egui::Vec2; use re_format::format_f32; -use re_log_types::{ - component_types::ColorRGBA, - component_types::{LineStrip2D, LineStrip3D, Mat3x3, Rect2D, Vec2D, Vec3D, Vec4D}, - Pinhole, Rigid3, Transform, ViewCoordinates, +use re_log_types::component_types::{ + ColorRGBA, LineStrip2D, LineStrip3D, Mat3x3, Rect2D, Vec2D, Vec3D, Vec4D, ViewCoordinates, }; use re_viewer_context::{UiVerbosity, ViewerContext}; @@ -53,24 +51,6 @@ impl DataUi for ColorRGBA { } } -impl DataUi for Transform { - fn data_ui( - &self, - ctx: &mut ViewerContext<'_>, - ui: &mut egui::Ui, - verbosity: UiVerbosity, - query: &re_arrow_store::LatestAtQuery, - ) { - match self { - Transform::Unknown => { - ui.label("Unknown transform"); - } - Transform::Rigid3(rigid3) => rigid3.data_ui(ctx, ui, verbosity, query), - Transform::Pinhole(pinhole) => pinhole.data_ui(ctx, ui, verbosity, query), - } - } -} - impl DataUi for ViewCoordinates { fn data_ui( &self, @@ -90,90 +70,6 @@ impl DataUi for ViewCoordinates { } } -impl DataUi for Rigid3 { - #[allow(clippy::only_used_in_recursion)] - fn data_ui( - &self, - ctx: &mut ViewerContext<'_>, - ui: &mut egui::Ui, - verbosity: UiVerbosity, - query: &re_arrow_store::LatestAtQuery, - ) { - match verbosity { - UiVerbosity::Small => { - ui.label("Rigid 3D transform").on_hover_ui(|ui| { - self.data_ui(ctx, ui, UiVerbosity::All, query); - }); - } - - UiVerbosity::All | UiVerbosity::Reduced => { - let pose = self.parent_from_child(); // TODO(emilk): which one to show? - let rotation = pose.rotation(); - let translation = pose.translation(); - - ui.vertical(|ui| { - ui.label("Rigid 3D transform:"); - ui.indent("rigid3", |ui| { - egui::Grid::new("rigid3").num_columns(2).show(ui, |ui| { - ui.label("rotation"); - ui.monospace(format!("{rotation:?}")); - ui.end_row(); - - ui.label("translation"); - ui.monospace(format!("{translation:?}")); - ui.end_row(); - }); - }); - }); - } - } - } -} - -impl DataUi for Pinhole { - fn data_ui( - &self, - ctx: &mut ViewerContext<'_>, - ui: &mut egui::Ui, - verbosity: UiVerbosity, - query: &re_arrow_store::LatestAtQuery, - ) { - match verbosity { - UiVerbosity::Small => { - ui.label("Pinhole transform").on_hover_ui(|ui| { - self.data_ui(ctx, ui, UiVerbosity::All, query); - }); - } - - UiVerbosity::All | UiVerbosity::Reduced => { - let Pinhole { - image_from_cam: image_from_view, - resolution, - } = self; - - ui.vertical(|ui| { - ui.label("Pinhole transform:"); - ui.indent("pinole", |ui| { - ui.horizontal(|ui| { - ui.label("resolution:"); - if let Some(re_log_types::component_types::Vec2D([x, y])) = resolution { - ui.monospace(format!("{x}x{y}")); - } else { - ui.weak("(none)"); - } - }); - - ui.label("image from view:"); - ui.indent("image_from_view", |ui| { - image_from_view.data_ui(ctx, ui, verbosity, query); - }); - }); - }); - } - } - } -} - impl DataUi for Mat3x3 { fn data_ui( &self, diff --git a/crates/re_data_ui/src/lib.rs b/crates/re_data_ui/src/lib.rs index 4216db834ac0..ba2012aa6a32 100644 --- a/crates/re_data_ui/src/lib.rs +++ b/crates/re_data_ui/src/lib.rs @@ -17,6 +17,8 @@ mod instance_path; mod item; pub mod item_ui; mod log_msg; +mod pinhole; +mod transform3d; pub use crate::image::{ show_zoomed_image_region, show_zoomed_image_region_area_outline, diff --git a/crates/re_data_ui/src/pinhole.rs b/crates/re_data_ui/src/pinhole.rs new file mode 100644 index 000000000000..9abd37689da1 --- /dev/null +++ b/crates/re_data_ui/src/pinhole.rs @@ -0,0 +1,48 @@ +use re_log_types::component_types::Pinhole; +use re_viewer_context::{UiVerbosity, ViewerContext}; + +use crate::DataUi; + +impl DataUi for Pinhole { + fn data_ui( + &self, + ctx: &mut ViewerContext<'_>, + ui: &mut egui::Ui, + verbosity: UiVerbosity, + query: &re_arrow_store::LatestAtQuery, + ) { + match verbosity { + UiVerbosity::Small => { + ui.label("Pinhole transform").on_hover_ui(|ui| { + self.data_ui(ctx, ui, UiVerbosity::All, query); + }); + } + + UiVerbosity::All | UiVerbosity::Reduced => { + let Pinhole { + image_from_cam: image_from_view, + resolution, + } = self; + + ui.vertical(|ui| { + ui.label("Pinhole transform:"); + ui.indent("pinole", |ui| { + ui.horizontal(|ui| { + ui.label("resolution:"); + if let Some(re_log_types::component_types::Vec2D([x, y])) = resolution { + ui.monospace(format!("{x}x{y}")); + } else { + ui.weak("(none)"); + } + }); + + ui.label("image from view:"); + ui.indent("image_from_view", |ui| { + image_from_view.data_ui(ctx, ui, verbosity, query); + }); + }); + }); + } + } + } +} diff --git a/crates/re_data_ui/src/transform3d.rs b/crates/re_data_ui/src/transform3d.rs new file mode 100644 index 000000000000..6ff02ec9fcfc --- /dev/null +++ b/crates/re_data_ui/src/transform3d.rs @@ -0,0 +1,196 @@ +use re_log_types::component_types::{ + Angle, Rotation3D, RotationAxisAngle, Scale3D, Transform3D, Transform3DRepr, + TranslationAndMat3, TranslationRotationScale3D, +}; +use re_viewer_context::{UiVerbosity, ViewerContext}; + +use crate::DataUi; + +impl DataUi for Transform3D { + #[allow(clippy::only_used_in_recursion)] + fn data_ui( + &self, + ctx: &mut ViewerContext<'_>, + ui: &mut egui::Ui, + verbosity: UiVerbosity, + query: &re_arrow_store::LatestAtQuery, + ) { + match verbosity { + UiVerbosity::Small => { + // TODO(andreas): Preview some information instead of just a label with hover ui. + ui.label("3D transform").on_hover_ui(|ui| { + self.data_ui(ctx, ui, UiVerbosity::All, query); + }); + } + + UiVerbosity::All | UiVerbosity::Reduced => { + let dir_string = if self.from_parent { + "parent ➡ child" + } else { + "child ➡ parent" + }; + + ui.vertical(|ui| { + ui.label("3D transform"); + ui.indent("transform_repr", |ui| { + ui.label(dir_string); + self.transform.data_ui(ctx, ui, verbosity, query); + }); + }); + } + } + } +} + +impl DataUi for Transform3DRepr { + #[allow(clippy::only_used_in_recursion)] + fn data_ui( + &self, + ctx: &mut ViewerContext<'_>, + ui: &mut egui::Ui, + verbosity: UiVerbosity, + query: &re_arrow_store::LatestAtQuery, + ) { + match verbosity { + UiVerbosity::Small => { + ui.label("3D transform").on_hover_ui(|ui| { + self.data_ui(ctx, ui, UiVerbosity::All, query); + }); + } + + UiVerbosity::All | UiVerbosity::Reduced => match self { + Transform3DRepr::TranslationAndMat3(translation_matrix) => { + translation_matrix.data_ui(ctx, ui, verbosity, query); + } + Transform3DRepr::TranslationRotationScale(translation_rotation_scale) => { + translation_rotation_scale.data_ui(ctx, ui, verbosity, query); + } + }, + } + } +} + +impl DataUi for TranslationRotationScale3D { + fn data_ui( + &self, + ctx: &mut ViewerContext<'_>, + ui: &mut egui::Ui, + verbosity: UiVerbosity, + query: &re_arrow_store::LatestAtQuery, + ) { + let TranslationRotationScale3D { + translation, + rotation, + scale, + } = self; + + egui::Grid::new("translation_rotation_scale") + .num_columns(2) + .show(ui, |ui| { + // Unlike Rotation/Scale, we don't have a value that indicates that nothing was logged. + // We still skip zero translations though since they are typically not logged explicitly. + if let Some(translation) = translation { + ui.label("translation"); + translation.data_ui(ctx, ui, verbosity, query); + ui.end_row(); + } + + if let Some(rotation) = rotation { + ui.label("rotation"); + rotation.data_ui(ctx, ui, verbosity, query); + ui.end_row(); + } + + if let Some(scale) = scale { + ui.label("scale"); + scale.data_ui(ctx, ui, verbosity, query); + ui.end_row(); + } + }); + } +} + +impl DataUi for Rotation3D { + fn data_ui( + &self, + ctx: &mut ViewerContext<'_>, + ui: &mut egui::Ui, + verbosity: UiVerbosity, + query: &re_arrow_store::LatestAtQuery, + ) { + match self { + Rotation3D::Quaternion(q) => { + // TODO(andreas): Better formatting for quaternions. + ui.label(format!("{q:?}")); + } + Rotation3D::AxisAngle(RotationAxisAngle { axis, angle }) => { + egui::Grid::new("axis_angle").num_columns(2).show(ui, |ui| { + ui.label("axis"); + axis.data_ui(ctx, ui, verbosity, query); + ui.end_row(); + + ui.label("angle"); + match angle { + Angle::Radians(v) => { + ui.label(format!("{}rad", re_format::format_f32(*v))); + } + Angle::Degrees(v) => { + // TODO(andreas): Convert to arc minutes/seconds for very small angles. + // That code should be in re_format! + ui.label(format!("{}°", re_format::format_f32(*v),)); + } + } + ui.end_row(); + }); + } + } + } +} + +impl DataUi for Scale3D { + fn data_ui( + &self, + ctx: &mut ViewerContext<'_>, + ui: &mut egui::Ui, + verbosity: UiVerbosity, + query: &re_arrow_store::LatestAtQuery, + ) { + match self { + Scale3D::Uniform(scale) => { + ui.label(re_format::format_f32(*scale)); + } + Scale3D::ThreeD(v) => { + v.data_ui(ctx, ui, verbosity, query); + } + } + } +} + +impl DataUi for TranslationAndMat3 { + fn data_ui( + &self, + ctx: &mut ViewerContext<'_>, + ui: &mut egui::Ui, + verbosity: UiVerbosity, + query: &re_arrow_store::LatestAtQuery, + ) { + let TranslationAndMat3 { + translation, + matrix, + } = self; + + egui::Grid::new("translation_and_mat3") + .num_columns(2) + .show(ui, |ui| { + if let Some(translation) = translation { + ui.label("translation"); + translation.data_ui(ctx, ui, verbosity, query); + ui.end_row(); + } + + ui.label("matrix"); + matrix.data_ui(ctx, ui, verbosity, query); + ui.end_row(); + }); + } +} diff --git a/crates/re_log_types/src/component_types/disconnected_space.rs b/crates/re_log_types/src/component_types/disconnected_space.rs new file mode 100644 index 000000000000..07102ec26d57 --- /dev/null +++ b/crates/re_log_types/src/component_types/disconnected_space.rs @@ -0,0 +1,36 @@ +use arrow2_convert::{ArrowDeserialize, ArrowField, ArrowSerialize}; + +use crate::Component; + +/// Specifies that the entity path at which this is logged is disconnected from its parent. +/// +/// If a transform or pinhole is logged on the same path, this component will be ignored. +/// +/// This is useful for specifying that a subgraph is independent of the rest of the scene. +/// +/// This component is a "mono-component". See [the crate level docs](crate) for details. +#[derive(Copy, Clone, Debug, ArrowField, ArrowSerialize, ArrowDeserialize)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +#[arrow_field(transparent)] +#[repr(transparent)] +pub struct DisconnectedSpace(bool); + +impl DisconnectedSpace { + #[inline] + pub fn new() -> Self { + Self(false) + } +} + +impl Default for DisconnectedSpace { + fn default() -> Self { + Self::new() + } +} + +impl Component for DisconnectedSpace { + #[inline] + fn name() -> crate::ComponentName { + "rerun.disconnected_space".into() + } +} diff --git a/crates/re_log_types/src/component_types/draw_order.rs b/crates/re_log_types/src/component_types/draw_order.rs index 66d3d69fc5e3..f7b5c197ba03 100644 --- a/crates/re_log_types/src/component_types/draw_order.rs +++ b/crates/re_log_types/src/component_types/draw_order.rs @@ -10,6 +10,8 @@ use crate::Component; /// /// Draw order for entities with the same draw order is generally undefined. /// +/// This component is a "mono-component". See [the crate level docs](crate) for details. +/// /// ``` /// use re_log_types::component_types::DrawOrder; /// use arrow2_convert::field::ArrowField; diff --git a/crates/re_log_types/src/component_types/mat.rs b/crates/re_log_types/src/component_types/mat.rs index 303f0c6bf9d9..973cabcb7754 100644 --- a/crates/re_log_types/src/component_types/mat.rs +++ b/crates/re_log_types/src/component_types/mat.rs @@ -27,6 +27,14 @@ use super::Vec3D; #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct Mat3x3([Vec3D; 3]); +impl Mat3x3 { + pub const IDENTITY: Mat3x3 = Mat3x3([ + Vec3D([1.0, 0.0, 0.0]), + Vec3D([0.0, 1.0, 0.0]), + Vec3D([0.0, 0.0, 1.0]), + ]); +} + impl std::ops::Index for Mat3x3 where Idx: std::slice::SliceIndex<[Vec3D]>, diff --git a/crates/re_log_types/src/component_types/mod.rs b/crates/re_log_types/src/component_types/mod.rs index 2eae0fc191a7..bd681e05ee6b 100644 --- a/crates/re_log_types/src/component_types/mod.rs +++ b/crates/re_log_types/src/component_types/mod.rs @@ -22,6 +22,7 @@ mod class_id; mod color; pub mod context; pub mod coordinates; +mod disconnected_space; mod draw_order; mod instance_key; mod keypoint_id; @@ -29,6 +30,7 @@ mod label; mod linestrip; mod mat; mod mesh3d; +mod pinhole; mod point; mod quaternion; mod radius; @@ -38,7 +40,7 @@ mod size; mod tensor; mod text_box; mod text_entry; -mod transform; +mod transform3d; mod vec; pub use arrow::Arrow3D; @@ -47,6 +49,7 @@ pub use class_id::ClassId; pub use color::ColorRGBA; pub use context::{AnnotationContext, AnnotationInfo, ClassDescription}; pub use coordinates::ViewCoordinates; +pub use disconnected_space::DisconnectedSpace; pub use draw_order::DrawOrder; pub use instance_key::InstanceKey; pub use keypoint_id::KeypointId; @@ -54,6 +57,7 @@ pub use label::Label; pub use linestrip::{LineStrip2D, LineStrip3D}; pub use mat::Mat3x3; pub use mesh3d::{EncodedMesh3D, Mesh3D, MeshFormat, MeshId, RawMesh3D}; +pub use pinhole::Pinhole; pub use point::{Point2D, Point3D}; pub use quaternion::Quaternion; pub use radius::Radius; @@ -68,24 +72,29 @@ pub use tensor::{ pub use tensor::{TensorImageLoadError, TensorImageSaveError}; pub use text_box::TextBox; pub use text_entry::TextEntry; -pub use transform::{Pinhole, Rigid3, Transform}; +pub use transform3d::{ + Angle, Rotation3D, RotationAxisAngle, Scale3D, Transform3D, Transform3DRepr, + TranslationAndMat3, TranslationRotationScale3D, +}; pub use vec::{Vec2D, Vec3D, Vec4D}; lazy_static! { //TODO(john): use a run-time type registry - static ref FIELDS: [Field; 27] = [ + static ref FIELDS: [Field; 29] = [ ::field(), ::field(), ::field(), ::field(), ::field(), ::field(), + ::field(), ::field(), ::field(),