diff --git a/.github/workflows/contrib_rerun_py.yml b/.github/workflows/contrib_rerun_py.yml index 93ac0b767e52..ab742d368e9a 100644 --- a/.github/workflows/contrib_rerun_py.yml +++ b/.github/workflows/contrib_rerun_py.yml @@ -56,9 +56,6 @@ jobs: pixi-version: v0.25.0 environments: wheel-test-min - - name: Run Python unit-tests - run: pixi run -e wheel-test-min py-test - - name: Run e2e test run: pixi run -e wheel-test-min RUST_LOG=debug scripts/run_python_e2e_test.py --no-build # rerun-sdk is already built and installed diff --git a/crates/store/re_types/definitions/rerun/archetypes/boxes3d.fbs b/crates/store/re_types/definitions/rerun/archetypes/boxes3d.fbs index b1c42744a833..97b430877b37 100644 --- a/crates/store/re_types/definitions/rerun/archetypes/boxes3d.fbs +++ b/crates/store/re_types/definitions/rerun/archetypes/boxes3d.fbs @@ -34,14 +34,17 @@ table Boxes3D ( /// Optional radii for the lines that make up the boxes. radii: [rerun.components.Radius] ("attr.rerun.component_optional", nullable, order: 3000); + /// Optionally choose whether the boxes are drawn with lines or solid. + fill_mode: rerun.components.FillMode ("attr.rerun.component_optional", nullable, order: 3100); + /// Optional text labels for the boxes. /// /// If there's a single label present, it will be placed at the center of the entity. /// Otherwise, each instance will have its own label. - labels: [rerun.components.Text] ("attr.rerun.component_optional", nullable, order: 3100); + labels: [rerun.components.Text] ("attr.rerun.component_optional", nullable, order: 3200); /// Optional [components.ClassId]s for the boxes. /// /// The [components.ClassId] provides colors and labels if not specified explicitly. - class_ids: [rerun.components.ClassId] ("attr.rerun.component_optional", nullable, order: 3200); + class_ids: [rerun.components.ClassId] ("attr.rerun.component_optional", nullable, order: 3300); } diff --git a/crates/store/re_types/definitions/rerun/archetypes/ellipsoids.fbs b/crates/store/re_types/definitions/rerun/archetypes/ellipsoids.fbs index b051bea57430..5e1e7b5a990f 100644 --- a/crates/store/re_types/definitions/rerun/archetypes/ellipsoids.fbs +++ b/crates/store/re_types/definitions/rerun/archetypes/ellipsoids.fbs @@ -47,11 +47,14 @@ table Ellipsoids ( /// Optional radii for the lines used when the ellipsoid is rendered as a wireframe. line_radii: [rerun.components.Radius] ("attr.rerun.component_optional", nullable, order: 3000); + /// Optionally choose whether the ellipsoids are drawn with lines or solid. + fill_mode: rerun.components.FillMode ("attr.rerun.component_optional", nullable, order: 3100); + /// Optional text labels for the ellipsoids. - labels: [rerun.components.Text] ("attr.rerun.component_optional", nullable, order: 3100); + labels: [rerun.components.Text] ("attr.rerun.component_optional", nullable, order: 3200); /// Optional `ClassId`s for the ellipsoids. /// /// The class ID provides colors and labels if not specified explicitly. - class_ids: [rerun.components.ClassId] ("attr.rerun.component_optional", nullable, order: 3200); + class_ids: [rerun.components.ClassId] ("attr.rerun.component_optional", nullable, order: 3300); } diff --git a/crates/store/re_types/definitions/rerun/components.fbs b/crates/store/re_types/definitions/rerun/components.fbs index 331527356a79..06093a49364f 100644 --- a/crates/store/re_types/definitions/rerun/components.fbs +++ b/crates/store/re_types/definitions/rerun/components.fbs @@ -14,6 +14,7 @@ include "./components/colormap.fbs"; include "./components/depth_meter.fbs"; include "./components/disconnected_space.fbs"; include "./components/draw_order.fbs"; +include "./components/fill_mode.fbs"; include "./components/fill_ratio.fbs"; include "./components/gamma_correction.fbs"; include "./components/half_size2d.fbs"; diff --git a/crates/store/re_types/definitions/rerun/components/fill_mode.fbs b/crates/store/re_types/definitions/rerun/components/fill_mode.fbs new file mode 100644 index 000000000000..08642668ef25 --- /dev/null +++ b/crates/store/re_types/definitions/rerun/components/fill_mode.fbs @@ -0,0 +1,16 @@ +namespace rerun.components; + +/// How a geometric shape is drawn and colored. +enum FillMode: byte( + "attr.docs.unreleased" +) { + /// Lines are drawn around the edges of the shape. + /// + /// The interior (2D) or surface (3D) are not drawn. + Wireframe (default), + + /// The interior (2D) or surface (3D) is filled with a single color. + /// + /// Lines are not drawn. + Solid, +} diff --git a/crates/store/re_types/src/archetypes/boxes3d.rs b/crates/store/re_types/src/archetypes/boxes3d.rs index 85aa7c59f1ed..2797a7840720 100644 --- a/crates/store/re_types/src/archetypes/boxes3d.rs +++ b/crates/store/re_types/src/archetypes/boxes3d.rs @@ -44,6 +44,7 @@ use ::re_types_core::{DeserializationError, DeserializationResult}; /// rerun::Color::from_rgb(0, 255, 0), /// rerun::Color::from_rgb(0, 0, 255), /// ]) +/// .with_fill_mode(rerun::FillMode::Solid) /// .with_labels(["red", "green", "blue"]), /// )?; /// @@ -76,6 +77,9 @@ pub struct Boxes3D { /// Optional radii for the lines that make up the boxes. pub radii: Option>, + /// Optionally choose whether the boxes are drawn with lines or solid. + pub fill_mode: Option, + /// Optional text labels for the boxes. /// /// If there's a single label present, it will be placed at the center of the entity. @@ -96,6 +100,7 @@ impl ::re_types_core::SizeBytes for Boxes3D { + self.rotations.heap_size_bytes() + self.colors.heap_size_bytes() + self.radii.heap_size_bytes() + + self.fill_mode.heap_size_bytes() + self.labels.heap_size_bytes() + self.class_ids.heap_size_bytes() } @@ -107,6 +112,7 @@ impl ::re_types_core::SizeBytes for Boxes3D { && >>::is_pod() && >>::is_pod() && >>::is_pod() + && >::is_pod() && >>::is_pod() && >>::is_pod() } @@ -125,16 +131,17 @@ static RECOMMENDED_COMPONENTS: once_cell::sync::Lazy<[ComponentName; 4usize]> = ] }); -static OPTIONAL_COMPONENTS: once_cell::sync::Lazy<[ComponentName; 3usize]> = +static OPTIONAL_COMPONENTS: once_cell::sync::Lazy<[ComponentName; 4usize]> = once_cell::sync::Lazy::new(|| { [ "rerun.components.Radius".into(), + "rerun.components.FillMode".into(), "rerun.components.Text".into(), "rerun.components.ClassId".into(), ] }); -static ALL_COMPONENTS: once_cell::sync::Lazy<[ComponentName; 8usize]> = +static ALL_COMPONENTS: once_cell::sync::Lazy<[ComponentName; 9usize]> = once_cell::sync::Lazy::new(|| { [ "rerun.components.HalfSize3D".into(), @@ -143,14 +150,15 @@ static ALL_COMPONENTS: once_cell::sync::Lazy<[ComponentName; 8usize]> = "rerun.components.Color".into(), "rerun.components.Boxes3DIndicator".into(), "rerun.components.Radius".into(), + "rerun.components.FillMode".into(), "rerun.components.Text".into(), "rerun.components.ClassId".into(), ] }); impl Boxes3D { - /// The total number of components in the archetype: 1 required, 4 recommended, 3 optional - pub const NUM_COMPONENTS: usize = 8usize; + /// The total number of components in the archetype: 1 required, 4 recommended, 4 optional + pub const NUM_COMPONENTS: usize = 9usize; } /// Indicator component for the [`Boxes3D`] [`::re_types_core::Archetype`] @@ -265,6 +273,15 @@ impl ::re_types_core::Archetype for Boxes3D { } else { None }; + let fill_mode = if let Some(array) = arrays_by_name.get("rerun.components.FillMode") { + ::from_arrow_opt(&**array) + .with_context("rerun.archetypes.Boxes3D#fill_mode")? + .into_iter() + .next() + .flatten() + } else { + None + }; let labels = if let Some(array) = arrays_by_name.get("rerun.components.Text") { Some({ ::from_arrow_opt(&**array) @@ -295,6 +312,7 @@ impl ::re_types_core::Archetype for Boxes3D { rotations, colors, radii, + fill_mode, labels, class_ids, }) @@ -320,6 +338,9 @@ impl ::re_types_core::AsComponents for Boxes3D { self.radii .as_ref() .map(|comp_batch| (comp_batch as &dyn ComponentBatch).into()), + self.fill_mode + .as_ref() + .map(|comp| (comp as &dyn ComponentBatch).into()), self.labels .as_ref() .map(|comp_batch| (comp_batch as &dyn ComponentBatch).into()), @@ -345,6 +366,7 @@ impl Boxes3D { rotations: None, colors: None, radii: None, + fill_mode: None, labels: None, class_ids: None, } @@ -390,6 +412,13 @@ impl Boxes3D { self } + /// Optionally choose whether the boxes are drawn with lines or solid. + #[inline] + pub fn with_fill_mode(mut self, fill_mode: impl Into) -> Self { + self.fill_mode = Some(fill_mode.into()); + self + } + /// Optional text labels for the boxes. /// /// If there's a single label present, it will be placed at the center of the entity. diff --git a/crates/store/re_types/src/archetypes/ellipsoids.rs b/crates/store/re_types/src/archetypes/ellipsoids.rs index 8f3fc89f3920..f0842e113556 100644 --- a/crates/store/re_types/src/archetypes/ellipsoids.rs +++ b/crates/store/re_types/src/archetypes/ellipsoids.rs @@ -49,6 +49,9 @@ pub struct Ellipsoids { /// Optional radii for the lines used when the ellipsoid is rendered as a wireframe. pub line_radii: Option>, + /// Optionally choose whether the ellipsoids are drawn with lines or solid. + pub fill_mode: Option, + /// Optional text labels for the ellipsoids. pub labels: Option>, @@ -66,6 +69,7 @@ impl ::re_types_core::SizeBytes for Ellipsoids { + self.rotations.heap_size_bytes() + self.colors.heap_size_bytes() + self.line_radii.heap_size_bytes() + + self.fill_mode.heap_size_bytes() + self.labels.heap_size_bytes() + self.class_ids.heap_size_bytes() } @@ -77,6 +81,7 @@ impl ::re_types_core::SizeBytes for Ellipsoids { && >>::is_pod() && >>::is_pod() && >>::is_pod() + && >::is_pod() && >>::is_pod() && >>::is_pod() } @@ -95,16 +100,17 @@ static RECOMMENDED_COMPONENTS: once_cell::sync::Lazy<[ComponentName; 4usize]> = ] }); -static OPTIONAL_COMPONENTS: once_cell::sync::Lazy<[ComponentName; 3usize]> = +static OPTIONAL_COMPONENTS: once_cell::sync::Lazy<[ComponentName; 4usize]> = once_cell::sync::Lazy::new(|| { [ "rerun.components.Radius".into(), + "rerun.components.FillMode".into(), "rerun.components.Text".into(), "rerun.components.ClassId".into(), ] }); -static ALL_COMPONENTS: once_cell::sync::Lazy<[ComponentName; 8usize]> = +static ALL_COMPONENTS: once_cell::sync::Lazy<[ComponentName; 9usize]> = once_cell::sync::Lazy::new(|| { [ "rerun.components.HalfSize3D".into(), @@ -113,14 +119,15 @@ static ALL_COMPONENTS: once_cell::sync::Lazy<[ComponentName; 8usize]> = "rerun.components.Color".into(), "rerun.components.EllipsoidsIndicator".into(), "rerun.components.Radius".into(), + "rerun.components.FillMode".into(), "rerun.components.Text".into(), "rerun.components.ClassId".into(), ] }); impl Ellipsoids { - /// The total number of components in the archetype: 1 required, 4 recommended, 3 optional - pub const NUM_COMPONENTS: usize = 8usize; + /// The total number of components in the archetype: 1 required, 4 recommended, 4 optional + pub const NUM_COMPONENTS: usize = 9usize; } /// Indicator component for the [`Ellipsoids`] [`::re_types_core::Archetype`] @@ -235,6 +242,15 @@ impl ::re_types_core::Archetype for Ellipsoids { } else { None }; + let fill_mode = if let Some(array) = arrays_by_name.get("rerun.components.FillMode") { + ::from_arrow_opt(&**array) + .with_context("rerun.archetypes.Ellipsoids#fill_mode")? + .into_iter() + .next() + .flatten() + } else { + None + }; let labels = if let Some(array) = arrays_by_name.get("rerun.components.Text") { Some({ ::from_arrow_opt(&**array) @@ -265,6 +281,7 @@ impl ::re_types_core::Archetype for Ellipsoids { rotations, colors, line_radii, + fill_mode, labels, class_ids, }) @@ -290,6 +307,9 @@ impl ::re_types_core::AsComponents for Ellipsoids { self.line_radii .as_ref() .map(|comp_batch| (comp_batch as &dyn ComponentBatch).into()), + self.fill_mode + .as_ref() + .map(|comp| (comp as &dyn ComponentBatch).into()), self.labels .as_ref() .map(|comp_batch| (comp_batch as &dyn ComponentBatch).into()), @@ -315,6 +335,7 @@ impl Ellipsoids { rotations: None, colors: None, line_radii: None, + fill_mode: None, labels: None, class_ids: None, } @@ -364,6 +385,13 @@ impl Ellipsoids { self } + /// Optionally choose whether the ellipsoids are drawn with lines or solid. + #[inline] + pub fn with_fill_mode(mut self, fill_mode: impl Into) -> Self { + self.fill_mode = Some(fill_mode.into()); + self + } + /// Optional text labels for the ellipsoids. #[inline] pub fn with_labels( diff --git a/crates/store/re_types/src/components/.gitattributes b/crates/store/re_types/src/components/.gitattributes index 35018e8aa5e8..ab6662dfba01 100644 --- a/crates/store/re_types/src/components/.gitattributes +++ b/crates/store/re_types/src/components/.gitattributes @@ -14,6 +14,7 @@ colormap.rs linguist-generated=true depth_meter.rs linguist-generated=true disconnected_space.rs linguist-generated=true draw_order.rs linguist-generated=true +fill_mode.rs linguist-generated=true fill_ratio.rs linguist-generated=true gamma_correction.rs linguist-generated=true half_size2d.rs linguist-generated=true diff --git a/crates/store/re_types/src/components/fill_mode.rs b/crates/store/re_types/src/components/fill_mode.rs new file mode 100644 index 000000000000..5dd188759d79 --- /dev/null +++ b/crates/store/re_types/src/components/fill_mode.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_types/definitions/rerun/components/fill_mode.fbs". + +#![allow(unused_imports)] +#![allow(unused_parens)] +#![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)] + +use ::re_types_core::external::arrow2; +use ::re_types_core::ComponentName; +use ::re_types_core::SerializationResult; +use ::re_types_core::{ComponentBatch, MaybeOwnedComponentBatch}; +use ::re_types_core::{DeserializationError, DeserializationResult}; + +/// **Component**: How a geometric shape is drawn and colored. +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Default)] +pub enum FillMode { + /// Lines are drawn around the edges of the shape. + /// + /// The interior (2D) or surface (3D) are not drawn. + #[default] + Wireframe = 1, + + /// The interior (2D) or surface (3D) is filled with a single color. + /// + /// Lines are not drawn. + Solid = 2, +} + +impl ::re_types_core::reflection::Enum for FillMode { + #[inline] + fn variants() -> &'static [Self] { + &[Self::Wireframe, Self::Solid] + } + + #[inline] + fn docstring_md(self) -> &'static str { + match self { + Self::Wireframe => { + "Lines are drawn around the edges of the shape.\n\nThe interior (2D) or surface (3D) are not drawn." + } + Self::Solid => { + "The interior (2D) or surface (3D) is filled with a single color.\n\nLines are not drawn." + } + } + } +} + +impl ::re_types_core::SizeBytes for FillMode { + #[inline] + fn heap_size_bytes(&self) -> u64 { + 0 + } + + #[inline] + fn is_pod() -> bool { + true + } +} + +impl std::fmt::Display for FillMode { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Wireframe => write!(f, "Wireframe"), + Self::Solid => write!(f, "Solid"), + } + } +} + +::re_types_core::macros::impl_into_cow!(FillMode); + +impl ::re_types_core::Loggable for FillMode { + type Name = ::re_types_core::ComponentName; + + #[inline] + fn name() -> Self::Name { + "rerun.components.FillMode".into() + } + + #[inline] + fn arrow_datatype() -> arrow2::datatypes::DataType { + #![allow(clippy::wildcard_imports)] + use arrow2::datatypes::*; + DataType::Union( + std::sync::Arc::new(vec![ + Field::new("_null_markers", DataType::Null, true), + Field::new("Wireframe", DataType::Null, true), + Field::new("Solid", DataType::Null, true), + ]), + Some(std::sync::Arc::new(vec![0i32, 1i32, 2i32])), + UnionMode::Sparse, + ) + } + + fn to_arrow_opt<'a>( + data: impl IntoIterator>>>, + ) -> SerializationResult> + where + Self: Clone + 'a, + { + #![allow(clippy::wildcard_imports)] + use ::re_types_core::{Loggable as _, ResultExt as _}; + use arrow2::{array::*, datatypes::*}; + Ok({ + // Sparse Arrow union + let data: Vec<_> = data + .into_iter() + .map(|datum| { + let datum: Option<::std::borrow::Cow<'a, Self>> = datum.map(Into::into); + datum + }) + .collect(); + let num_variants = 2usize; + let types = data + .iter() + .map(|a| match a.as_deref() { + None => 0, + Some(value) => *value as i8, + }) + .collect(); + let fields: Vec<_> = + std::iter::repeat(NullArray::new(DataType::Null, data.len()).boxed()) + .take(1 + num_variants) + .collect(); + UnionArray::new(Self::arrow_datatype(), types, fields, None).boxed() + }) + } + + fn from_arrow_opt( + arrow_data: &dyn arrow2::array::Array, + ) -> DeserializationResult>> + where + Self: Sized, + { + #![allow(clippy::wildcard_imports)] + use ::re_types_core::{Loggable as _, ResultExt as _}; + use arrow2::{array::*, buffer::*, datatypes::*}; + Ok({ + let arrow_data = 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.FillMode")?; + let arrow_data_types = arrow_data.types(); + arrow_data_types + .iter() + .map(|typ| match typ { + 0 => Ok(None), + 1 => Ok(Some(Self::Wireframe)), + 2 => Ok(Some(Self::Solid)), + _ => Err(DeserializationError::missing_union_arm( + Self::arrow_datatype(), + "", + *typ as _, + )), + }) + .collect::>>() + .with_context("rerun.components.FillMode")? + }) + } +} diff --git a/crates/store/re_types/src/components/mod.rs b/crates/store/re_types/src/components/mod.rs index fe677d12c0a3..d52e2550b0f0 100644 --- a/crates/store/re_types/src/components/mod.rs +++ b/crates/store/re_types/src/components/mod.rs @@ -22,6 +22,7 @@ mod disconnected_space; mod disconnected_space_ext; mod draw_order; mod draw_order_ext; +mod fill_mode; mod fill_ratio; mod fill_ratio_ext; mod gamma_correction; @@ -116,6 +117,7 @@ pub use self::colormap::Colormap; pub use self::depth_meter::DepthMeter; pub use self::disconnected_space::DisconnectedSpace; pub use self::draw_order::DrawOrder; +pub use self::fill_mode::FillMode; pub use self::fill_ratio::FillRatio; pub use self::gamma_correction::GammaCorrection; pub use self::half_size2d::HalfSize2D; diff --git a/crates/store/re_types/tests/box3d.rs b/crates/store/re_types/tests/box3d.rs index bfc3e33c33fd..fe4d431dc89a 100644 --- a/crates/store/re_types/tests/box3d.rs +++ b/crates/store/re_types/tests/box3d.rs @@ -28,6 +28,7 @@ fn roundtrip() { components::Radius::from(42.0), // components::Radius::from(43.0), ]), + fill_mode: Some(components::FillMode::Solid), labels: Some(vec![ "hello".into(), // "friend".into(), // @@ -49,6 +50,7 @@ fn roundtrip() { ]) .with_colors([0xAA0000CC, 0x00BB00DD]) .with_radii([42.0, 43.0]) + .with_fill_mode(components::FillMode::Solid) .with_labels(["hello", "friend"]) .with_class_ids([126, 127]); similar_asserts::assert_eq!(expected, arch); @@ -58,6 +60,7 @@ fn roundtrip() { ("centers", vec!["rerun.components.Position2D"]), ("colors", vec!["rerun.components.Color"]), ("radii", vec!["rerun.components.Radius"]), + ("fill_mode", vec!["rerun.components.FillMode"]), ("labels", vec!["rerun.components.Label"]), ("draw_order", vec!["rerun.components.DrawOrder"]), ("class_ids", vec!["rerun.components.ClassId"]), diff --git a/crates/top/rerun/src/sdk.rs b/crates/top/rerun/src/sdk.rs index c8ba61d467d3..8c91db5388a0 100644 --- a/crates/top/rerun/src/sdk.rs +++ b/crates/top/rerun/src/sdk.rs @@ -26,7 +26,7 @@ mod prelude { // Also import any component or datatype that has a unique name: pub use re_chunk::ChunkTimeline; pub use re_types::components::{ - AlbedoFactor, Color, HalfSize2D, HalfSize3D, LineStrip2D, LineStrip3D, MediaType, + AlbedoFactor, Color, FillMode, HalfSize2D, HalfSize3D, LineStrip2D, LineStrip3D, MediaType, OutOfTreeTransform3D, Position2D, Position3D, Radius, Scale3D, Text, TextLogLevel, TransformRelation, TriangleIndices, Vector2D, Vector3D, }; diff --git a/crates/viewer/re_edit_ui/src/lib.rs b/crates/viewer/re_edit_ui/src/lib.rs index 9d013b1309c7..d3fc507ad1c7 100644 --- a/crates/viewer/re_edit_ui/src/lib.rs +++ b/crates/viewer/re_edit_ui/src/lib.rs @@ -24,8 +24,9 @@ use re_types::{ }, components::{ AggregationPolicy, AlbedoFactor, AxisLength, ChannelDataType, Color, ColorModel, Colormap, - DepthMeter, DrawOrder, FillRatio, GammaCorrection, ImagePlaneDistance, MagnificationFilter, - MarkerSize, Name, Opacity, Scale3D, StrokeWidth, Text, TransformRelation, Translation3D, + DepthMeter, DrawOrder, FillMode, FillRatio, GammaCorrection, ImagePlaneDistance, + MagnificationFilter, MarkerSize, Name, Opacity, Scale3D, StrokeWidth, Text, + TransformRelation, Translation3D, }, Loggable as _, }; @@ -71,6 +72,7 @@ pub fn register_editors(registry: &mut re_viewer_context::ComponentUiRegistry) { colormap_edit_or_view_ui(ctx.render_ctx, ui, value) }); + // TODO(#6974): Enums editors trivial and always the same, provide them automatically! 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); @@ -78,6 +80,7 @@ pub fn register_editors(registry: &mut 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_space_view_spatial/src/proc_mesh.rs b/crates/viewer/re_space_view_spatial/src/proc_mesh.rs index 2551d15f04b4..a976a3ebc249 100644 --- a/crates/viewer/re_space_view_spatial/src/proc_mesh.rs +++ b/crates/viewer/re_space_view_spatial/src/proc_mesh.rs @@ -1,8 +1,16 @@ +//! Procedurally-generated meshes for rendering objects that are +//! specified geometrically, and have nontrivial numbers of vertices each, +//! such as a sphere or cylinder. + use std::sync::Arc; use ahash::HashSet; -use glam::Vec3; +use glam::{uvec3, vec3, Vec3}; +use itertools::Itertools as _; +use smallvec::smallvec; +use re_renderer::mesh; +use re_renderer::resource_managers::{GpuMeshHandle, ResourceManagerError}; use re_renderer::RenderContext; use re_viewer_context::Cache; @@ -10,16 +18,38 @@ use re_viewer_context::Cache; /// Description of a mesh that can be procedurally generated. /// -/// Obtain the actual mesh by passing this to [`WireframeCache`]. -/// In the future, it will be possible to produce solid triangle meshes too. +/// Obtain the actual mesh by passing this to [`WireframeCache`] or [`SolidCache`]. #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] pub enum ProcMeshKey { + /// A unit cube, centered; its bounds are ±0.5. + Cube, + /// A sphere of unit radius. + /// + /// The resulting mesh may be scaled to represent spheres and ellipsoids + /// of other sizes. Sphere { subdivisions: usize }, } +impl ProcMeshKey { + /// Returns the bounding box which can be computed from the mathematical shape, + /// without regard for its exact approximation as a mesh. + pub fn simple_bounding_box(&self) -> re_math::BoundingBox { + match self { + Self::Sphere { subdivisions: _ } => { + // sphere’s radius is 1, so its size is 2 + re_math::BoundingBox::from_center_size(Vec3::splat(0.0), Vec3::splat(2.0)) + } + Self::Cube => { + re_math::BoundingBox::from_center_size(Vec3::splat(0.0), Vec3::splat(1.0)) + } + } + } +} + /// A renderable mesh generated from a [`ProcMeshKey`] by the [`WireframeCache`], /// which is to be drawn as lines rather than triangles. +#[derive(Debug)] pub struct WireframeMesh { pub bbox: re_math::BoundingBox, @@ -33,6 +63,19 @@ pub struct WireframeMesh { pub line_strips: Vec>, } +/// A renderable mesh generated from a [`ProcMeshKey`] by the [`SolidCache`], +/// which is to be drawn as triangles rather than lines. +/// +/// This type is cheap to clone. +#[derive(Debug, Clone)] +pub struct SolidMesh { + pub bbox: re_math::BoundingBox, + + /// Mesh to render. Note that its colors are set to black, so that the + /// `MeshInstance::additive_tint` can be used to set the color per instance. + pub gpu_mesh: GpuMeshHandle, +} + // ---------------------------------------------------------------------------- /// Cache for the computation of wireframe meshes from [`ProcMeshKey`]s. @@ -79,10 +122,50 @@ impl Cache for WireframeCache { /// Generate a wireframe mesh without caching. fn generate_wireframe(key: &ProcMeshKey, render_ctx: &RenderContext) -> WireframeMesh { + re_tracing::profile_function!(); + // In the future, render_ctx will be used to allocate GPU memory for the mesh. _ = render_ctx; match *key { + ProcMeshKey::Cube => { + let corners = [ + vec3(-0.5, -0.5, -0.5), + vec3(-0.5, -0.5, 0.5), + vec3(-0.5, 0.5, -0.5), + vec3(-0.5, 0.5, 0.5), + vec3(0.5, -0.5, -0.5), + vec3(0.5, -0.5, 0.5), + vec3(0.5, 0.5, -0.5), + vec3(0.5, 0.5, 0.5), + ]; + let line_strips: Vec> = vec![ + // bottom: + vec![ + // bottom loop + corners[0b000], + corners[0b001], + corners[0b011], + corners[0b010], + corners[0b000], + // joined to top loop + corners[0b100], + corners[0b101], + corners[0b111], + corners[0b110], + corners[0b100], + ], + // remaining side edges + vec![corners[0b001], corners[0b101]], + vec![corners[0b010], corners[0b110]], + vec![corners[0b011], corners[0b111]], + ]; + WireframeMesh { + bbox: key.simple_bounding_box(), + vertex_count: line_strips.iter().map(|v| v.len()).sum(), + line_strips, + } + } ProcMeshKey::Sphere { subdivisions } => { let subdiv = hexasphere::shapes::IcoSphere::new(subdivisions, |_| ()); @@ -128,8 +211,7 @@ fn generate_wireframe(key: &ProcMeshKey, render_ctx: &RenderContext) -> Wirefram .collect() }; WireframeMesh { - // sphere’s radius is 1, so its size is 2 - bbox: re_math::BoundingBox::from_center_size(Vec3::splat(0.0), Vec3::splat(2.0)), + bbox: key.simple_bounding_box(), vertex_count: line_strips.iter().map(|v| v.len()).sum(), line_strips, } @@ -137,4 +219,142 @@ fn generate_wireframe(key: &ProcMeshKey, render_ctx: &RenderContext) -> Wirefram } } -// TODO(kpreid): A solid (rather than wireframe) mesh cache implementation should live here. +// ---------------------------------------------------------------------------- + +/// Cache for the computation of triangle meshes from [`ProcMeshKey`]s that depict the +/// shape as a solid object. +#[derive(Default)] +pub struct SolidCache(ahash::HashMap>); + +impl SolidCache { + pub fn entry(&mut self, key: ProcMeshKey, render_ctx: &RenderContext) -> Option { + re_tracing::profile_function!(); + + self.0 + .entry(key) + .or_insert_with(|| { + re_log::debug!("Generating mesh {key:?}…"); + + match generate_solid(&key, render_ctx) { + Ok(mesh) => Some(mesh), + Err(err) => { + re_log::warn!( + "Failed to generate mesh {key:?}: {}", + re_error::format_ref(&err) + ); + None + } + } + }) + .clone() + } +} + +impl Cache for SolidCache { + fn begin_frame(&mut self) {} + + fn purge_memory(&mut self) { + self.0.clear(); + } + + fn as_any_mut(&mut self) -> &mut dyn std::any::Any { + self + } +} + +/// Generate a solid triangle mesh without caching. +fn generate_solid( + key: &ProcMeshKey, + render_ctx: &RenderContext, +) -> Result { + re_tracing::profile_function!(); + + let mesh: mesh::Mesh = match *key { + ProcMeshKey::Cube => { + let mut mg = re_math::MeshGen::new(); + mg.push_cube(Vec3::splat(0.5), re_math::IsoTransform::IDENTITY); + + let num_vertices = mg.positions.len(); + + let triangle_indices: Vec = mg + .indices + .into_iter() + .tuples() + .map(|(i1, i2, i3)| uvec3(i1, i2, i3)) + .collect(); + let materials = materials_for_uncolored_mesh(render_ctx, triangle_indices.len()); + + mesh::Mesh { + label: format!("{key:?}").into(), + materials, + triangle_indices, + vertex_positions: mg.positions, + vertex_normals: mg.normals, + // Colors are black so that the instance `additive_tint` can set per-instance color. + vertex_colors: vec![re_renderer::Rgba32Unmul::BLACK; num_vertices], + vertex_texcoords: vec![glam::Vec2::ZERO; num_vertices], + } + } + ProcMeshKey::Sphere { subdivisions } => { + let subdiv = hexasphere::shapes::IcoSphere::new(subdivisions, |_| ()); + + let vertex_positions: Vec = + subdiv.raw_points().iter().map(|&p| p.into()).collect(); + // A unit sphere's normals are its positions. + let vertex_normals = vertex_positions.clone(); + let num_vertices = vertex_positions.len(); + + let triangle_indices = subdiv.get_all_indices(); + let triangle_indices: Vec = triangle_indices + .into_iter() + .tuples() + .map(|(i1, i2, i3)| glam::uvec3(i1, i2, i3)) + .collect(); + + let materials = materials_for_uncolored_mesh(render_ctx, triangle_indices.len()); + + mesh::Mesh { + label: format!("{key:?}").into(), + + // bytemuck is re-grouping the indices into triples without realloc + triangle_indices, + + vertex_positions, + vertex_normals, + // Colors are black so that the instance `additive_tint` can set per-instance color. + vertex_colors: vec![re_renderer::Rgba32Unmul::BLACK; num_vertices], + vertex_texcoords: vec![glam::Vec2::ZERO; num_vertices], + + materials, + } + } + }; + + mesh.sanity_check()?; + + let gpu_mesh = render_ctx.mesh_manager.write().create( + render_ctx, + &mesh, + re_renderer::resource_managers::ResourceLifeTime::LongLived, + )?; + + Ok(SolidMesh { + bbox: key.simple_bounding_box(), + gpu_mesh, + }) +} + +fn materials_for_uncolored_mesh( + render_ctx: &RenderContext, + num_triangles: usize, +) -> smallvec::SmallVec<[mesh::Material; 1]> { + smallvec![mesh::Material { + label: "default material".into(), + index_range: 0..(num_triangles * 3) as u32, + albedo: render_ctx + .texture_manager_2d + .white_texture_unorm_handle() + .clone(), + albedo_factor: re_renderer::Rgba::BLACK, + }] +} diff --git a/crates/viewer/re_space_view_spatial/src/visualizers/boxes3d.rs b/crates/viewer/re_space_view_spatial/src/visualizers/boxes3d.rs index 1533dfbe9b49..bcbd75e16a46 100644 --- a/crates/viewer/re_space_view_spatial/src/visualizers/boxes3d.rs +++ b/crates/viewer/re_space_view_spatial/src/visualizers/boxes3d.rs @@ -1,9 +1,13 @@ +use re_entity_db::InstancePathHash; use re_log_types::Instance; -use re_query::range_zip_1x7; -use re_renderer::{LineDrawableBuilder, PickingLayerInstanceId}; +use re_renderer::{ + renderer::MeshInstance, LineDrawableBuilder, PickingLayerInstanceId, RenderContext, +}; use re_types::{ archetypes::Boxes3D, - components::{ClassId, Color, HalfSize3D, KeypointId, Position3D, Radius, Rotation3D, Text}, + components::{ + ClassId, Color, FillMode, HalfSize3D, KeypointId, Position3D, Radius, Rotation3D, Text, + }, }; use re_viewer_context::{ auto_color_for_entity_path, ApplicableEntities, IdentifiedViewSystem, QueryContext, @@ -12,7 +16,11 @@ use re_viewer_context::{ VisualizerQueryInfo, VisualizerSystem, }; -use crate::{contexts::SpatialSceneEntityContext, view_kind::SpatialSpaceViewKind}; +use crate::{ + contexts::SpatialSceneEntityContext, + instance_hash_conversions::picking_layer_id_from_instance_path_hash, proc_mesh, + view_kind::SpatialSpaceViewKind, +}; use super::{ entity_iterator::clamped_or, filter_visualizable_3d_entities, @@ -35,14 +43,17 @@ impl Default for Boxes3DVisualizer { // 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 Boxes3DVisualizer { + #[allow(clippy::too_many_arguments)] fn process_data<'a>( &mut self, ctx: &QueryContext<'_>, line_builder: &mut LineDrawableBuilder<'_>, + mesh_instances: &mut Vec, query: &ViewQuery<'_>, ent_context: &SpatialSceneEntityContext<'_>, data: impl Iterator>, - ) { + render_ctx: &RenderContext, + ) -> Result<(), SpaceViewSystemExecutionError> { let entity_path = ctx.target_entity_path; for data in data { @@ -78,32 +89,64 @@ impl Boxes3DVisualizer { let centers = clamped_or(data.centers, &Position3D::ZERO); let rotations = clamped_or(data.rotations, &Rotation3D::IDENTITY); - for (i, (half_size, ¢er, rotation, radius, &color)) in + for (instance_index, (half_size, ¢er, rotation, radius, &color)) in itertools::izip!(data.half_sizes, centers, rotations, radii, &colors).enumerate() { - obj_space_bounding_box.extend(half_size.box_min(center)); - obj_space_bounding_box.extend(half_size.box_max(center)); - - let center = center.into(); - - let box3d = line_batch - .add_box_outline_from_transform( - glam::Affine3A::from_scale_rotation_translation( - glam::Vec3::from(*half_size) * 2.0, - rotation.0.into(), - center, - ), - ) - .color(color) - .radius(radius) - .picking_instance_id(PickingLayerInstanceId(i as _)); - - if let Some(outline_mask_ids) = ent_context - .highlight - .instances - .get(&Instance::from(i as u64)) - { - box3d.outline_mask_ids(*outline_mask_ids); + let instance = Instance::from(instance_index as u64); + // Transform from a centered unit cube to this box in the entity's + // coordinate system. + let entity_from_mesh = glam::Affine3A::from_scale_rotation_translation( + glam::Vec3::from(*half_size) * 2.0, + rotation.0.into(), + center.into(), + ); + + let proc_mesh_key = proc_mesh::ProcMeshKey::Cube; + + obj_space_bounding_box = obj_space_bounding_box.union( + // We must perform this transform to fully account for the per-instance + // transform, which is separate from the entity's transform. + proc_mesh_key + .simple_bounding_box() + .transform_affine3(&entity_from_mesh), + ); + + match data.fill_mode { + FillMode::Wireframe => { + let box3d = line_batch + .add_box_outline_from_transform(entity_from_mesh) + .color(color) + .radius(radius) + .picking_instance_id(PickingLayerInstanceId(instance_index as _)); + + if let Some(outline_mask_ids) = + ent_context.highlight.instances.get(&instance) + { + box3d.outline_mask_ids(*outline_mask_ids); + } + } + FillMode::Solid => { + let Some(solid_mesh) = + ctx.viewer_ctx.cache.entry(|c: &mut proc_mesh::SolidCache| { + c.entry(proc_mesh_key, render_ctx) + }) + else { + return Err(SpaceViewSystemExecutionError::DrawDataCreationError( + "Failed to allocate solid mesh".into(), + )); + }; + + mesh_instances.push(MeshInstance { + gpu_mesh: solid_mesh.gpu_mesh, + mesh: None, + world_from_mesh: entity_from_mesh, + outline_mask_ids: ent_context.highlight.index_outline_mask(instance), + picking_layer_id: picking_layer_id_from_instance_path_hash( + InstancePathHash::instance(entity_path, instance), + ), + additive_tint: color, + }); + } } } @@ -135,6 +178,8 @@ impl Boxes3DVisualizer { )); } } + + Ok(()) } } @@ -152,6 +197,8 @@ struct Boxes3DComponentData<'a> { labels: &'a [Text], keypoint_ids: &'a [KeypointId], class_ids: &'a [ClassId], + + fill_mode: FillMode, } impl IdentifiedViewSystem for Boxes3DVisualizer { @@ -187,6 +234,13 @@ impl VisualizerSystem for Boxes3DVisualizer { let mut line_builder = LineDrawableBuilder::new(render_ctx); line_builder.radius_boost_in_ui_points_for_outlines(SIZE_BOOST_IN_POINTS_FOR_LINE_OUTLINES); + // Collects solid (that is, triangles rather than wireframe) instances to be drawn. + // + // Why do we draw solid surfaces using instanced meshes and wireframes using instances? + // No good reason; only, those are the types of renderers that have been implemented so far. + // This code should be revisited with an eye on performance. + let mut solid_instances: Vec = Vec::new(); + super::entity_iterator::process_archetype::( ctx, view_query, @@ -210,19 +264,34 @@ impl VisualizerSystem for Boxes3DVisualizer { return Ok(()); } - // Each box consists of 12 independent lines with 2 vertices each. - line_builder.reserve_strips(num_boxes * 12)?; - line_builder.reserve_vertices(num_boxes * 12 * 2)?; - let centers = results.get_or_empty_dense(resolver)?; let rotations = results.get_or_empty_dense(resolver)?; let colors = results.get_or_empty_dense(resolver)?; + let fill_mode = results.get_or_empty_dense(resolver)?; let radii = results.get_or_empty_dense(resolver)?; let labels = results.get_or_empty_dense(resolver)?; let class_ids = results.get_or_empty_dense(resolver)?; let keypoint_ids = results.get_or_empty_dense(resolver)?; - let data = range_zip_1x7( + // fill mode is currently a non-repeated component + let fill_mode: FillMode = fill_mode + .range_indexed() + .next() + .and_then(|(_, fill_modes)| fill_modes.first().copied()) + .unwrap_or_default(); + + match fill_mode { + FillMode::Wireframe => { + // Each box consists of 12 independent lines with 2 vertices each. + line_builder.reserve_strips(num_boxes * 12)?; + line_builder.reserve_vertices(num_boxes * 12 * 2)?; + } + FillMode::Solid => { + // No lines. + } + } + + let data = re_query::range_zip_1x7( half_sizes.range_indexed(), centers.range_indexed(), rotations.range_indexed(), @@ -250,6 +319,8 @@ impl VisualizerSystem for Boxes3DVisualizer { rotations: rotations.unwrap_or_default(), colors: colors.unwrap_or_default(), radii: radii.unwrap_or_default(), + // fill mode is currently a non-repeated component + fill_mode, labels: labels.unwrap_or_default(), class_ids: class_ids.unwrap_or_default(), keypoint_ids: keypoint_ids.unwrap_or_default(), @@ -257,13 +328,38 @@ impl VisualizerSystem for Boxes3DVisualizer { }, ); - self.process_data(ctx, &mut line_builder, view_query, spatial_ctx, data); + self.process_data( + ctx, + &mut line_builder, + &mut solid_instances, + view_query, + spatial_ctx, + data, + render_ctx, + )?; Ok(()) }, )?; - Ok(vec![(line_builder.into_draw_data()?.into())]) + let wireframe_draw_data: re_renderer::QueueableDrawData = + line_builder.into_draw_data()?.into(); + + let solid_draw_data: Option = + match re_renderer::renderer::MeshDrawData::new(render_ctx, &solid_instances) { + Ok(draw_data) => Some(draw_data.into()), + Err(err) => { + re_log::error_once!( + "Failed to create mesh draw data from mesh instances: {err}" + ); + None + } + }; + + Ok([solid_draw_data, Some(wireframe_draw_data)] + .into_iter() + .flatten() + .collect()) } fn data(&self) -> Option<&dyn std::any::Any> { diff --git a/crates/viewer/re_space_view_spatial/src/visualizers/ellipsoids.rs b/crates/viewer/re_space_view_spatial/src/visualizers/ellipsoids.rs index 48b7bc3f7d70..b5a6fbdcd9ac 100644 --- a/crates/viewer/re_space_view_spatial/src/visualizers/ellipsoids.rs +++ b/crates/viewer/re_space_view_spatial/src/visualizers/ellipsoids.rs @@ -1,10 +1,13 @@ use re_entity_db::{EntityPath, InstancePathHash}; use re_log_types::Instance; -use re_query::range_zip_1x7; -use re_renderer::{LineDrawableBuilder, PickingLayerInstanceId, RenderContext}; +use re_renderer::{ + renderer::MeshInstance, LineDrawableBuilder, PickingLayerInstanceId, RenderContext, +}; use re_types::{ archetypes::Ellipsoids, - components::{ClassId, Color, HalfSize3D, KeypointId, Position3D, Radius, Rotation3D, Text}, + components::{ + ClassId, Color, FillMode, HalfSize3D, KeypointId, Position3D, Radius, Rotation3D, Text, + }, }; use re_viewer_context::{ auto_color_for_entity_path, ApplicableEntities, IdentifiedViewSystem, QueryContext, @@ -15,7 +18,8 @@ use re_viewer_context::{ use crate::{ contexts::SpatialSceneEntityContext, - proc_mesh::{ProcMeshKey, WireframeCache}, + instance_hash_conversions::picking_layer_id_from_instance_path_hash, + proc_mesh, view_kind::SpatialSpaceViewKind, visualizers::{UiLabel, UiLabelTarget}, }; @@ -71,10 +75,12 @@ impl EllipsoidsVisualizer { }) } + #[allow(clippy::too_many_arguments)] fn process_data<'a>( &mut self, ctx: &QueryContext<'_>, line_builder: &mut LineDrawableBuilder<'_>, + mesh_instances: &mut Vec, query: &ViewQuery<'_>, ent_context: &SpatialSceneEntityContext<'_>, data: impl Iterator>, @@ -127,12 +133,15 @@ impl EllipsoidsVisualizer { .outline_mask_ids(ent_context.highlight.overall) .picking_object_id(re_renderer::PickingLayerObjectId(entity_path.hash64())); - let mut bounding_box = re_math::BoundingBox::NOTHING; + let mut obj_space_bounding_box = re_math::BoundingBox::NOTHING; - for (i, (half_size, ¢er, rotation, radius, color)) in + for (instance_index, (half_size, ¢er, rotation, radius, color)) in itertools::izip!(data.half_sizes, centers, rotations, radii, colors).enumerate() { - let transform = glam::Affine3A::from_scale_rotation_translation( + let instance = Instance::from(instance_index as u64); + // Transform from a centered unit sphere to this ellipsoid in the entity's + // coordinate system. + let entity_from_mesh = glam::Affine3A::from_scale_rotation_translation( glam::Vec3::from(*half_size), rotation.0.into(), center.into(), @@ -141,40 +150,78 @@ impl EllipsoidsVisualizer { // TODO(kpreid): subdivisions should be configurable, and possibly dynamic based on // either world size or screen size (depending on application). let subdivisions = 2; - - let Some(sphere_mesh) = ctx.viewer_ctx.cache.entry(|c: &mut WireframeCache| { - c.entry(ProcMeshKey::Sphere { subdivisions }, render_ctx) - }) else { - // TODO(kpreid): Should this be just returning nothing instead? - // If we do, there won't be any error report, just missing data. - - return Err(SpaceViewSystemExecutionError::DrawDataCreationError( - "Failed to allocate wireframe mesh".into(), - )); - }; - - bounding_box = bounding_box.union(sphere_mesh.bbox.transform_affine3(&transform)); - - for strip in &sphere_mesh.line_strips { - let box3d = line_batch - .add_strip(strip.iter().map(|&point| transform.transform_point3(point))) - .color(color) - .radius(radius) - .picking_instance_id(PickingLayerInstanceId(i as _)); - - if let Some(outline_mask_ids) = ent_context - .highlight - .instances - .get(&Instance::from(i as u64)) - { - box3d.outline_mask_ids(*outline_mask_ids); + let proc_mesh_key = proc_mesh::ProcMeshKey::Sphere { subdivisions }; + + match data.fill_mode { + FillMode::Wireframe => { + let Some(wireframe_mesh) = + ctx.viewer_ctx + .cache + .entry(|c: &mut proc_mesh::WireframeCache| { + c.entry(proc_mesh_key, render_ctx) + }) + else { + return Err(SpaceViewSystemExecutionError::DrawDataCreationError( + "Failed to allocate wireframe mesh".into(), + )); + }; + + obj_space_bounding_box = obj_space_bounding_box + .union(wireframe_mesh.bbox.transform_affine3(&entity_from_mesh)); + + for strip in &wireframe_mesh.line_strips { + let strip_builder = line_batch + .add_strip( + strip + .iter() + .map(|&point| entity_from_mesh.transform_point3(point)), + ) + .color(color) + .radius(radius) + .picking_instance_id(PickingLayerInstanceId(instance_index as _)); + + 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 Some(solid_mesh) = + ctx.viewer_ctx.cache.entry(|c: &mut proc_mesh::SolidCache| { + c.entry(proc_mesh_key, render_ctx) + }) + else { + return Err(SpaceViewSystemExecutionError::DrawDataCreationError( + "Failed to allocate solid mesh".into(), + )); + }; + + obj_space_bounding_box = obj_space_bounding_box + .union(solid_mesh.bbox.transform_affine3(&entity_from_mesh)); + + mesh_instances.push(MeshInstance { + gpu_mesh: solid_mesh.gpu_mesh, + mesh: None, + world_from_mesh: entity_from_mesh, + outline_mask_ids: ent_context.highlight.index_outline_mask(instance), + picking_layer_id: picking_layer_id_from_instance_path_hash( + InstancePathHash::instance(entity_path, instance), + ), + additive_tint: color, + }); } } } self.0.add_bounding_box( entity_path.hash(), - bounding_box, + obj_space_bounding_box, ent_context.world_from_entity, ); } @@ -197,6 +244,8 @@ struct EllipsoidsComponentData<'a> { labels: &'a [Text], keypoint_ids: &'a [KeypointId], class_ids: &'a [ClassId], + + fill_mode: FillMode, } impl IdentifiedViewSystem for EllipsoidsVisualizer { @@ -234,6 +283,9 @@ impl VisualizerSystem for EllipsoidsVisualizer { let mut line_builder = LineDrawableBuilder::new(render_ctx); line_builder.radius_boost_in_ui_points_for_outlines(SIZE_BOOST_IN_POINTS_FOR_LINE_OUTLINES); + // Collects solid (that is, triangles rather than wireframe) instances to be drawn. + let mut solid_instances: Vec = Vec::new(); + super::entity_iterator::process_archetype::( ctx, view_query, @@ -266,16 +318,18 @@ impl VisualizerSystem for EllipsoidsVisualizer { let rotations = results.get_or_empty_dense(resolver)?; let colors = results.get_or_empty_dense(resolver)?; let line_radii = results.get_or_empty_dense(resolver)?; + let fill_mode = results.get_or_empty_dense(resolver)?; let labels = results.get_or_empty_dense(resolver)?; let class_ids = results.get_or_empty_dense(resolver)?; let keypoint_ids = results.get_or_empty_dense(resolver)?; - let data = range_zip_1x7( + let data = re_query::range_zip_1x8( half_sizes.range_indexed(), centers.range_indexed(), rotations.range_indexed(), colors.range_indexed(), line_radii.range_indexed(), + fill_mode.range_indexed(), labels.range_indexed(), class_ids.range_indexed(), keypoint_ids.range_indexed(), @@ -288,6 +342,7 @@ impl VisualizerSystem for EllipsoidsVisualizer { rotations, colors, line_radii, + fill_mode, labels, class_ids, keypoint_ids, @@ -298,6 +353,12 @@ impl VisualizerSystem for EllipsoidsVisualizer { rotations: rotations.unwrap_or_default(), colors: colors.unwrap_or_default(), line_radii: line_radii.unwrap_or_default(), + // fill mode is currently a non-repeated component + fill_mode: fill_mode + .unwrap_or_default() + .first() + .copied() + .unwrap_or_default(), labels: labels.unwrap_or_default(), class_ids: class_ids.unwrap_or_default(), keypoint_ids: keypoint_ids.unwrap_or_default(), @@ -308,6 +369,7 @@ impl VisualizerSystem for EllipsoidsVisualizer { self.process_data( ctx, &mut line_builder, + &mut solid_instances, view_query, spatial_ctx, data, @@ -318,7 +380,24 @@ impl VisualizerSystem for EllipsoidsVisualizer { }, )?; - Ok(vec![(line_builder.into_draw_data()?.into())]) + let wireframe_draw_data: re_renderer::QueueableDrawData = + line_builder.into_draw_data()?.into(); + + let solid_draw_data: Option = + match re_renderer::renderer::MeshDrawData::new(render_ctx, &solid_instances) { + Ok(draw_data) => Some(draw_data.into()), + Err(err) => { + re_log::error_once!( + "Failed to create mesh draw data from mesh instances: {err}" + ); + None + } + }; + + Ok([solid_draw_data, Some(wireframe_draw_data)] + .into_iter() + .flatten() + .collect()) } fn data(&self) -> Option<&dyn std::any::Any> { diff --git a/crates/viewer/re_viewer/src/reflection/mod.rs b/crates/viewer/re_viewer/src/reflection/mod.rs index 7265920beed3..4c0a6860fdda 100644 --- a/crates/viewer/re_viewer/src/reflection/mod.rs +++ b/crates/viewer/re_viewer/src/reflection/mod.rs @@ -335,6 +335,13 @@ fn generate_component_reflection() -> Result::name(), + ComponentReflection { + docstring_md: "How a geometric shape is drawn and colored.", + placeholder: Some(FillMode::default().to_arrow()?), + }, + ), ( ::name(), ComponentReflection { diff --git a/docs/content/reference/types/archetypes/boxes3d.md b/docs/content/reference/types/archetypes/boxes3d.md index 60c997849804..344704145f9e 100644 --- a/docs/content/reference/types/archetypes/boxes3d.md +++ b/docs/content/reference/types/archetypes/boxes3d.md @@ -11,7 +11,7 @@ title: "Boxes3D" **Recommended**: [`Position3D`](../components/position3d.md), [`Rotation3D`](../components/rotation3d.md), [`Color`](../components/color.md) -**Optional**: [`Radius`](../components/radius.md), [`Text`](../components/text.md), [`ClassId`](../components/class_id.md) +**Optional**: [`Radius`](../components/radius.md), [`FillMode`](../components/fill_mode.md), [`Text`](../components/text.md), [`ClassId`](../components/class_id.md) ## Shown in * [Spatial3DView](../views/spatial3d_view.md) diff --git a/docs/content/reference/types/archetypes/ellipsoids.md b/docs/content/reference/types/archetypes/ellipsoids.md index c03c7d7ee13a..196cf8a60b62 100644 --- a/docs/content/reference/types/archetypes/ellipsoids.md +++ b/docs/content/reference/types/archetypes/ellipsoids.md @@ -18,7 +18,7 @@ Opaque and transparent rendering will be supported later. **Recommended**: [`Position3D`](../components/position3d.md), [`Rotation3D`](../components/rotation3d.md), [`Color`](../components/color.md) -**Optional**: [`Radius`](../components/radius.md), [`Text`](../components/text.md), [`ClassId`](../components/class_id.md) +**Optional**: [`Radius`](../components/radius.md), [`FillMode`](../components/fill_mode.md), [`Text`](../components/text.md), [`ClassId`](../components/class_id.md) ## Shown in * [Spatial3DView](../views/spatial3d_view.md) diff --git a/docs/content/reference/types/components.md b/docs/content/reference/types/components.md index 2b6ab9934ac5..c7e5090e2211 100644 --- a/docs/content/reference/types/components.md +++ b/docs/content/reference/types/components.md @@ -27,6 +27,7 @@ on [Entities and Components](../../concepts/entity-component.md). * [`DepthMeter`](components/depth_meter.md): The world->depth map scaling factor. * [`DisconnectedSpace`](components/disconnected_space.md): Spatially disconnect this entity from its parent. * [`DrawOrder`](components/draw_order.md): Draw order of 2D elements. Higher values are drawn on top of lower values. +* [`FillMode`](components/fill_mode.md): How a geometric shape is drawn and colored. * [`FillRatio`](components/fill_ratio.md): How much a primitive fills out the available space. * [`GammaCorrection`](components/gamma_correction.md): A gamma correction value to be used with a scalar value or color. * [`HalfSize2D`](components/half_size2d.md): Half-size (radius) of a 2D box. diff --git a/docs/content/reference/types/components/.gitattributes b/docs/content/reference/types/components/.gitattributes index 57a0ce5cfa2e..a54341eb5dbf 100644 --- a/docs/content/reference/types/components/.gitattributes +++ b/docs/content/reference/types/components/.gitattributes @@ -15,6 +15,7 @@ colormap.md linguist-generated=true depth_meter.md linguist-generated=true disconnected_space.md linguist-generated=true draw_order.md linguist-generated=true +fill_mode.md linguist-generated=true fill_ratio.md linguist-generated=true gamma_correction.md linguist-generated=true half_size2d.md linguist-generated=true diff --git a/docs/content/reference/types/components/fill_mode.md b/docs/content/reference/types/components/fill_mode.md new file mode 100644 index 000000000000..d128a3ee76af --- /dev/null +++ b/docs/content/reference/types/components/fill_mode.md @@ -0,0 +1,22 @@ +--- +title: "FillMode" +--- + + +How a geometric shape is drawn and colored. + +## Variants + +* Wireframe +* Solid + +## API reference links + * 🌊 [C++ API docs for `FillMode`](https://ref.rerun.io/docs/cpp/stable/namespacererun_1_1components.html?speculative-link) + * 🐍 [Python API docs for `FillMode`](https://ref.rerun.io/docs/python/stable/common/components?speculative-link#rerun.components.FillMode) + * 🦀 [Rust API docs for `FillMode`](https://docs.rs/rerun/latest/rerun/components/enum.FillMode.html?speculative-link) + + +## Used by + +* [`Boxes3D`](../archetypes/boxes3d.md) +* [`Ellipsoids`](../archetypes/ellipsoids.md?speculative-link) diff --git a/docs/snippets/all/archetypes/box3d_batch.cpp b/docs/snippets/all/archetypes/box3d_batch.cpp index 1bb32e2ef276..f54144ea472a 100644 --- a/docs/snippets/all/archetypes/box3d_batch.cpp +++ b/docs/snippets/all/archetypes/box3d_batch.cpp @@ -24,6 +24,7 @@ int main() { rerun::Rgba32(0, 255, 0), rerun::Rgba32(0, 0, 255), }) + .with_fill_mode(rerun::FillMode::Solid) .with_labels({"red", "green", "blue"}) ); } diff --git a/docs/snippets/all/archetypes/box3d_batch.py b/docs/snippets/all/archetypes/box3d_batch.py index 272d895b371a..ae6964e5a59c 100644 --- a/docs/snippets/all/archetypes/box3d_batch.py +++ b/docs/snippets/all/archetypes/box3d_batch.py @@ -17,6 +17,7 @@ ], radii=0.025, colors=[(255, 0, 0), (0, 255, 0), (0, 0, 255)], + fill_mode="solid", labels=["red", "green", "blue"], ), ) diff --git a/docs/snippets/all/archetypes/box3d_batch.rs b/docs/snippets/all/archetypes/box3d_batch.rs index 4bd135abcf1a..54c6e4e07dda 100644 --- a/docs/snippets/all/archetypes/box3d_batch.rs +++ b/docs/snippets/all/archetypes/box3d_batch.rs @@ -20,6 +20,7 @@ fn main() -> Result<(), Box> { rerun::Color::from_rgb(0, 255, 0), rerun::Color::from_rgb(0, 0, 255), ]) + .with_fill_mode(rerun::FillMode::Solid) .with_labels(["red", "green", "blue"]), )?; diff --git a/docs/snippets/all/archetypes/ellipsoid_batch.cpp b/docs/snippets/all/archetypes/ellipsoid_batch.cpp index dd0340520810..68f8a372908e 100644 --- a/docs/snippets/all/archetypes/ellipsoid_batch.cpp +++ b/docs/snippets/all/archetypes/ellipsoid_batch.cpp @@ -34,5 +34,6 @@ int main() { rerun::Rgba32(0, 0, 0), rerun::Rgba32(0, 0, 0), }) + .with_fill_mode(rerun::FillMode::Solid) ); } diff --git a/docs/snippets/all/archetypes/ellipsoid_batch.py b/docs/snippets/all/archetypes/ellipsoid_batch.py index b8c1aa3db06b..b83f5637ab9d 100644 --- a/docs/snippets/all/archetypes/ellipsoid_batch.py +++ b/docs/snippets/all/archetypes/ellipsoid_batch.py @@ -31,5 +31,6 @@ (0, 0, 0), (0, 0, 0), ], + fill_mode="solid", ), ) diff --git a/docs/snippets/all/archetypes/ellipsoid_batch.rs b/docs/snippets/all/archetypes/ellipsoid_batch.rs index b6f02af7e1d0..838fac677769 100644 --- a/docs/snippets/all/archetypes/ellipsoid_batch.rs +++ b/docs/snippets/all/archetypes/ellipsoid_batch.rs @@ -30,7 +30,8 @@ fn main() -> Result<(), Box> { rerun::Color::from_rgb(255, 255, 255), rerun::Color::from_rgb(0, 0, 0), rerun::Color::from_rgb(0, 0, 0), - ]), + ]) + .with_fill_mode(rerun::FillMode::Solid), )?; Ok(()) diff --git a/rerun_cpp/src/rerun.hpp b/rerun_cpp/src/rerun.hpp index 2d7220ed5031..261d1c7ef43e 100644 --- a/rerun_cpp/src/rerun.hpp +++ b/rerun_cpp/src/rerun.hpp @@ -32,6 +32,7 @@ namespace rerun { // Also import any component or datatype that has a unique name: using components::AlbedoFactor; using components::Color; + using components::FillMode; using components::HalfSize2D; using components::HalfSize3D; using components::LineStrip2D; diff --git a/rerun_cpp/src/rerun/archetypes/boxes3d.cpp b/rerun_cpp/src/rerun/archetypes/boxes3d.cpp index 6e4ec3433d85..18d0e9695954 100644 --- a/rerun_cpp/src/rerun/archetypes/boxes3d.cpp +++ b/rerun_cpp/src/rerun/archetypes/boxes3d.cpp @@ -14,7 +14,7 @@ namespace rerun { ) { using namespace archetypes; std::vector cells; - cells.reserve(8); + cells.reserve(9); { auto result = DataCell::from_loggable(archetype.half_sizes); @@ -41,6 +41,11 @@ namespace rerun { RR_RETURN_NOT_OK(result.error); cells.push_back(std::move(result.value)); } + if (archetype.fill_mode.has_value()) { + auto result = DataCell::from_loggable(archetype.fill_mode.value()); + RR_RETURN_NOT_OK(result.error); + cells.push_back(std::move(result.value)); + } if (archetype.labels.has_value()) { auto result = DataCell::from_loggable(archetype.labels.value()); RR_RETURN_NOT_OK(result.error); diff --git a/rerun_cpp/src/rerun/archetypes/boxes3d.hpp b/rerun_cpp/src/rerun/archetypes/boxes3d.hpp index 7bbf1c4f3dcf..0a40990d5a74 100644 --- a/rerun_cpp/src/rerun/archetypes/boxes3d.hpp +++ b/rerun_cpp/src/rerun/archetypes/boxes3d.hpp @@ -7,6 +7,7 @@ #include "../compiler_utils.hpp" #include "../components/class_id.hpp" #include "../components/color.hpp" +#include "../components/fill_mode.hpp" #include "../components/half_size3d.hpp" #include "../components/position3d.hpp" #include "../components/radius.hpp" @@ -54,6 +55,7 @@ namespace rerun::archetypes { /// rerun::Rgba32(0, 255, 0), /// rerun::Rgba32(0, 0, 255), /// }) + /// .with_fill_mode(rerun::FillMode::Solid) /// .with_labels({"red", "green", "blue"}) /// ); /// } @@ -74,6 +76,9 @@ namespace rerun::archetypes { /// Optional radii for the lines that make up the boxes. std::optional> radii; + /// Optionally choose whether the boxes are drawn with lines or solid. + std::optional fill_mode; + /// Optional text labels for the boxes. /// /// If there's a single label present, it will be placed at the center of the entity. @@ -175,6 +180,13 @@ namespace rerun::archetypes { RR_WITH_MAYBE_UNINITIALIZED_DISABLED(return std::move(*this);) } + /// Optionally choose whether the boxes are drawn with lines or solid. + Boxes3D with_fill_mode(rerun::components::FillMode _fill_mode) && { + fill_mode = std::move(_fill_mode); + // See: https://github.com/rerun-io/rerun/issues/4027 + RR_WITH_MAYBE_UNINITIALIZED_DISABLED(return std::move(*this);) + } + /// Optional text labels for the boxes. /// /// If there's a single label present, it will be placed at the center of the entity. diff --git a/rerun_cpp/src/rerun/archetypes/ellipsoids.cpp b/rerun_cpp/src/rerun/archetypes/ellipsoids.cpp index 6baf7232881c..cc4109b1a12b 100644 --- a/rerun_cpp/src/rerun/archetypes/ellipsoids.cpp +++ b/rerun_cpp/src/rerun/archetypes/ellipsoids.cpp @@ -14,7 +14,7 @@ namespace rerun { ) { using namespace archetypes; std::vector cells; - cells.reserve(8); + cells.reserve(9); { auto result = DataCell::from_loggable(archetype.half_sizes); @@ -41,6 +41,11 @@ namespace rerun { RR_RETURN_NOT_OK(result.error); cells.push_back(std::move(result.value)); } + if (archetype.fill_mode.has_value()) { + auto result = DataCell::from_loggable(archetype.fill_mode.value()); + RR_RETURN_NOT_OK(result.error); + cells.push_back(std::move(result.value)); + } if (archetype.labels.has_value()) { auto result = DataCell::from_loggable(archetype.labels.value()); RR_RETURN_NOT_OK(result.error); diff --git a/rerun_cpp/src/rerun/archetypes/ellipsoids.hpp b/rerun_cpp/src/rerun/archetypes/ellipsoids.hpp index e22d516be6bd..6451374d5a3d 100644 --- a/rerun_cpp/src/rerun/archetypes/ellipsoids.hpp +++ b/rerun_cpp/src/rerun/archetypes/ellipsoids.hpp @@ -7,6 +7,7 @@ #include "../compiler_utils.hpp" #include "../components/class_id.hpp" #include "../components/color.hpp" +#include "../components/fill_mode.hpp" #include "../components/half_size3d.hpp" #include "../components/position3d.hpp" #include "../components/radius.hpp" @@ -52,6 +53,9 @@ namespace rerun::archetypes { /// Optional radii for the lines used when the ellipsoid is rendered as a wireframe. std::optional> line_radii; + /// Optionally choose whether the ellipsoids are drawn with lines or solid. + std::optional fill_mode; + /// Optional text labels for the ellipsoids. std::optional> labels; @@ -137,6 +141,13 @@ namespace rerun::archetypes { RR_WITH_MAYBE_UNINITIALIZED_DISABLED(return std::move(*this);) } + /// Optionally choose whether the ellipsoids are drawn with lines or solid. + Ellipsoids with_fill_mode(rerun::components::FillMode _fill_mode) && { + fill_mode = std::move(_fill_mode); + // See: https://github.com/rerun-io/rerun/issues/4027 + RR_WITH_MAYBE_UNINITIALIZED_DISABLED(return std::move(*this);) + } + /// Optional text labels for the ellipsoids. Ellipsoids with_labels(Collection _labels) && { labels = std::move(_labels); diff --git a/rerun_cpp/src/rerun/components.hpp b/rerun_cpp/src/rerun/components.hpp index 56668bc637f8..fd92e24cdec3 100644 --- a/rerun_cpp/src/rerun/components.hpp +++ b/rerun_cpp/src/rerun/components.hpp @@ -16,6 +16,7 @@ #include "components/depth_meter.hpp" #include "components/disconnected_space.hpp" #include "components/draw_order.hpp" +#include "components/fill_mode.hpp" #include "components/fill_ratio.hpp" #include "components/gamma_correction.hpp" #include "components/half_size2d.hpp" diff --git a/rerun_cpp/src/rerun/components/.gitattributes b/rerun_cpp/src/rerun/components/.gitattributes index 042f2627d074..3b125ebb1e59 100644 --- a/rerun_cpp/src/rerun/components/.gitattributes +++ b/rerun_cpp/src/rerun/components/.gitattributes @@ -20,6 +20,8 @@ colormap.hpp linguist-generated=true depth_meter.hpp linguist-generated=true disconnected_space.hpp linguist-generated=true draw_order.hpp linguist-generated=true +fill_mode.cpp linguist-generated=true +fill_mode.hpp linguist-generated=true fill_ratio.hpp linguist-generated=true gamma_correction.hpp linguist-generated=true half_size2d.hpp linguist-generated=true diff --git a/rerun_cpp/src/rerun/components/fill_mode.cpp b/rerun_cpp/src/rerun/components/fill_mode.cpp new file mode 100644 index 000000000000..bc96f19799eb --- /dev/null +++ b/rerun_cpp/src/rerun/components/fill_mode.cpp @@ -0,0 +1,61 @@ +// DO NOT EDIT! This file was auto-generated by crates/build/re_types_builder/src/codegen/cpp/mod.rs +// Based on "crates/store/re_types/definitions/rerun/components/fill_mode.fbs". + +#include "fill_mode.hpp" + +#include +#include + +namespace rerun { + const std::shared_ptr& Loggable::arrow_datatype() { + static const auto datatype = arrow::sparse_union({ + arrow::field("_null_markers", arrow::null(), true, nullptr), + arrow::field("Wireframe", arrow::null(), true), + arrow::field("Solid", arrow::null(), true), + }); + return datatype; + } + + Result> Loggable::to_arrow( + const components::FillMode* 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::SparseUnionBuilder* builder, const components::FillMode* 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/fill_mode.hpp b/rerun_cpp/src/rerun/components/fill_mode.hpp new file mode 100644 index 000000000000..c13ced274c83 --- /dev/null +++ b/rerun_cpp/src/rerun/components/fill_mode.hpp @@ -0,0 +1,56 @@ +// DO NOT EDIT! This file was auto-generated by crates/build/re_types_builder/src/codegen/cpp/mod.rs +// Based on "crates/store/re_types/definitions/rerun/components/fill_mode.fbs". + +#pragma once + +#include "../result.hpp" + +#include +#include + +namespace arrow { + class Array; + class DataType; + class SparseUnionBuilder; +} // namespace arrow + +namespace rerun::components { + /// **Component**: How a geometric shape is drawn and colored. + enum class FillMode : uint8_t { + + /// Lines are drawn around the edges of the shape. + /// + /// The interior (2D) or surface (3D) are not drawn. + Wireframe = 1, + + /// The interior (2D) or surface (3D) is filled with a single color. + /// + /// Lines are not drawn. + Solid = 2, + }; +} // namespace rerun::components + +namespace rerun { + template + struct Loggable; + + /// \private + template <> + struct Loggable { + static constexpr const char Name[] = "rerun.components.FillMode"; + + /// Returns the arrow data type this type corresponds to. + static const std::shared_ptr& arrow_datatype(); + + /// Serializes an array of `rerun::components::FillMode` into an arrow array. + static Result> to_arrow( + const components::FillMode* instances, size_t num_instances + ); + + /// Fills an arrow array builder with an array of this type. + static rerun::Error fill_arrow_array_builder( + arrow::SparseUnionBuilder* builder, const components::FillMode* elements, + size_t num_elements + ); + }; +} // namespace rerun diff --git a/rerun_py/rerun_sdk/rerun/archetypes/boxes3d.py b/rerun_py/rerun_sdk/rerun/archetypes/boxes3d.py index 1989506ee9fb..babb131aab04 100644 --- a/rerun_py/rerun_sdk/rerun/archetypes/boxes3d.py +++ b/rerun_py/rerun_sdk/rerun/archetypes/boxes3d.py @@ -42,6 +42,7 @@ class Boxes3D(Boxes3DExt, Archetype): ], radii=0.025, colors=[(255, 0, 0), (0, 255, 0), (0, 0, 255)], + fill_mode="solid", labels=["red", "green", "blue"], ), ) @@ -68,6 +69,7 @@ def __attrs_clear__(self) -> None: rotations=None, # type: ignore[arg-type] colors=None, # type: ignore[arg-type] radii=None, # type: ignore[arg-type] + fill_mode=None, # type: ignore[arg-type] labels=None, # type: ignore[arg-type] class_ids=None, # type: ignore[arg-type] ) @@ -123,6 +125,15 @@ def _clear(cls) -> Boxes3D: # # (Docstring intentionally commented out to hide this field from the docs) + fill_mode: components.FillModeBatch | None = field( + metadata={"component": "optional"}, + default=None, + converter=components.FillModeBatch._optional, # type: ignore[misc] + ) + # Optionally choose whether the boxes are drawn with lines or solid. + # + # (Docstring intentionally commented out to hide this field from the docs) + labels: components.TextBatch | None = field( metadata={"component": "optional"}, default=None, diff --git a/rerun_py/rerun_sdk/rerun/archetypes/boxes3d_ext.py b/rerun_py/rerun_sdk/rerun/archetypes/boxes3d_ext.py index 7048ce103dbf..ce48e1a2bc76 100644 --- a/rerun_py/rerun_sdk/rerun/archetypes/boxes3d_ext.py +++ b/rerun_py/rerun_sdk/rerun/archetypes/boxes3d_ext.py @@ -4,7 +4,7 @@ import numpy as np -from .. import datatypes +from .. import components, datatypes from ..error_utils import _send_warning_or_raise, catch_and_log_exceptions @@ -21,6 +21,7 @@ def __init__( rotations: datatypes.Rotation3DArrayLike | None = None, colors: datatypes.Rgba32ArrayLike | None = None, radii: datatypes.Float32ArrayLike | None = None, + fill_mode: components.FillMode | None = None, labels: datatypes.Utf8ArrayLike | None = None, class_ids: datatypes.ClassIdArrayLike | None = None, ) -> None: @@ -45,6 +46,8 @@ def __init__( Optional colors for the boxes. radii: Optional radii for the lines that make up the boxes. + fill_mode: + Optionally choose whether the boxes are drawn with lines or solid. labels: Optional text labels for the boxes. class_ids: @@ -81,6 +84,7 @@ def __init__( rotations=rotations, colors=colors, radii=radii, + fill_mode=fill_mode, labels=labels, class_ids=class_ids, ) diff --git a/rerun_py/rerun_sdk/rerun/archetypes/ellipsoids.py b/rerun_py/rerun_sdk/rerun/archetypes/ellipsoids.py index 7a228344874d..953c72fa7838 100644 --- a/rerun_py/rerun_sdk/rerun/archetypes/ellipsoids.py +++ b/rerun_py/rerun_sdk/rerun/archetypes/ellipsoids.py @@ -39,6 +39,7 @@ def __attrs_clear__(self) -> None: rotations=None, # type: ignore[arg-type] colors=None, # type: ignore[arg-type] line_radii=None, # type: ignore[arg-type] + fill_mode=None, # type: ignore[arg-type] labels=None, # type: ignore[arg-type] class_ids=None, # type: ignore[arg-type] ) @@ -100,6 +101,15 @@ def _clear(cls) -> Ellipsoids: # # (Docstring intentionally commented out to hide this field from the docs) + fill_mode: components.FillModeBatch | None = field( + metadata={"component": "optional"}, + default=None, + converter=components.FillModeBatch._optional, # type: ignore[misc] + ) + # Optionally choose whether the ellipsoids are drawn with lines or solid. + # + # (Docstring intentionally commented out to hide this field from the docs) + labels: components.TextBatch | None = field( metadata={"component": "optional"}, default=None, diff --git a/rerun_py/rerun_sdk/rerun/archetypes/ellipsoids_ext.py b/rerun_py/rerun_sdk/rerun/archetypes/ellipsoids_ext.py index aa8a9e9b0da5..750e6a2c6927 100644 --- a/rerun_py/rerun_sdk/rerun/archetypes/ellipsoids_ext.py +++ b/rerun_py/rerun_sdk/rerun/archetypes/ellipsoids_ext.py @@ -4,7 +4,7 @@ import numpy as np -from .. import datatypes +from .. import components, datatypes from ..error_utils import _send_warning_or_raise, catch_and_log_exceptions @@ -20,6 +20,7 @@ def __init__( rotations: datatypes.Rotation3DArrayLike | None = None, colors: datatypes.Rgba32ArrayLike | None = None, line_radii: datatypes.Float32ArrayLike | None = None, + fill_mode: components.FillMode | None = None, labels: datatypes.Utf8ArrayLike | None = None, class_ids: datatypes.ClassIdArrayLike | None = None, ) -> None: @@ -42,6 +43,8 @@ def __init__( Optional colors for the ellipsoids. line_radii: Optional radii for the lines that make up the ellipsoids. + fill_mode: + Optionally choose whether the ellipsoids are drawn with lines or solid. labels: Optional text labels for the ellipsoids. class_ids: @@ -66,6 +69,7 @@ def __init__( rotations=rotations, colors=colors, line_radii=line_radii, + fill_mode=fill_mode, labels=labels, class_ids=class_ids, ) diff --git a/rerun_py/rerun_sdk/rerun/components/.gitattributes b/rerun_py/rerun_sdk/rerun/components/.gitattributes index 19b26b523b0e..97a701b83707 100644 --- a/rerun_py/rerun_sdk/rerun/components/.gitattributes +++ b/rerun_py/rerun_sdk/rerun/components/.gitattributes @@ -16,6 +16,7 @@ colormap.py linguist-generated=true depth_meter.py linguist-generated=true disconnected_space.py linguist-generated=true draw_order.py linguist-generated=true +fill_mode.py linguist-generated=true fill_ratio.py linguist-generated=true gamma_correction.py linguist-generated=true half_size2d.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 e398376d2fdd..85d82926cc28 100644 --- a/rerun_py/rerun_sdk/rerun/components/__init__.py +++ b/rerun_py/rerun_sdk/rerun/components/__init__.py @@ -34,6 +34,7 @@ from .depth_meter import DepthMeter, DepthMeterBatch, DepthMeterType from .disconnected_space import DisconnectedSpace, DisconnectedSpaceBatch, DisconnectedSpaceType from .draw_order import DrawOrder, DrawOrderBatch, DrawOrderType +from .fill_mode import FillMode, FillModeArrayLike, FillModeBatch, FillModeLike, FillModeType from .fill_ratio import FillRatio, FillRatioBatch, FillRatioType from .gamma_correction import GammaCorrection, GammaCorrectionBatch, GammaCorrectionType from .half_size2d import HalfSize2D, HalfSize2DBatch, HalfSize2DType @@ -147,6 +148,11 @@ "DrawOrder", "DrawOrderBatch", "DrawOrderType", + "FillMode", + "FillModeArrayLike", + "FillModeBatch", + "FillModeLike", + "FillModeType", "FillRatio", "FillRatioBatch", "FillRatioType", diff --git a/rerun_py/rerun_sdk/rerun/components/fill_mode.py b/rerun_py/rerun_sdk/rerun/components/fill_mode.py new file mode 100644 index 000000000000..df3c91c5cf6c --- /dev/null +++ b/rerun_py/rerun_sdk/rerun/components/fill_mode.py @@ -0,0 +1,101 @@ +# DO NOT EDIT! This file was auto-generated by crates/build/re_types_builder/src/codegen/python/mod.rs +# Based on "crates/store/re_types/definitions/rerun/components/fill_mode.fbs". + +# You can extend this class by creating a "FillModeExt" class in "fill_mode_ext.py". + +from __future__ import annotations + +from typing import Literal, Sequence, Union + +import pyarrow as pa + +from .._baseclasses import ( + BaseBatch, + BaseExtensionType, + ComponentBatchMixin, +) + +__all__ = ["FillMode", "FillModeArrayLike", "FillModeBatch", "FillModeLike", "FillModeType"] + + +from enum import Enum + + +class FillMode(Enum): + """**Component**: How a geometric shape is drawn and colored.""" + + Wireframe = 1 + """ + Lines are drawn around the edges of the shape. + + The interior (2D) or surface (3D) are not drawn. + """ + + Solid = 2 + """ + The interior (2D) or surface (3D) is filled with a single color. + + Lines are not drawn. + """ + + +FillModeLike = Union[FillMode, Literal["wireframe", "solid"]] +FillModeArrayLike = Union[FillModeLike, Sequence[FillModeLike]] + + +class FillModeType(BaseExtensionType): + _TYPE_NAME: str = "rerun.components.FillMode" + + def __init__(self) -> None: + pa.ExtensionType.__init__( + self, + pa.sparse_union([ + pa.field("_null_markers", pa.null(), nullable=True, metadata={}), + pa.field("Wireframe", pa.null(), nullable=True, metadata={}), + pa.field("Solid", pa.null(), nullable=True, metadata={}), + ]), + self._TYPE_NAME, + ) + + +class FillModeBatch(BaseBatch[FillModeArrayLike], ComponentBatchMixin): + _ARROW_TYPE = FillModeType() + + @staticmethod + def _native_to_pa_array(data: FillModeArrayLike, data_type: pa.DataType) -> pa.Array: + if isinstance(data, (FillMode, int, str)): + data = [data] + + types: list[int] = [] + + for value in data: + if value is None: + types.append(0) + elif isinstance(value, FillMode): + types.append(value.value) # Actual enum value + elif isinstance(value, int): + types.append(value) # By number + elif isinstance(value, str): + if hasattr(FillMode, value): + types.append(FillMode[value].value) # fast path + elif value.lower() == "wireframe": + types.append(FillMode.Wireframe.value) + elif value.lower() == "solid": + types.append(FillMode.Solid.value) + else: + raise ValueError(f"Unknown FillMode kind: {value}") + else: + raise ValueError(f"Unknown FillMode kind: {value}") + + buffers = [ + None, + pa.array(types, type=pa.int8()).buffers()[1], + ] + children = (1 + 2) * [pa.nulls(len(data))] + + return pa.UnionArray.from_buffers( + type=data_type, + length=len(data), + buffers=buffers, + children=children, + )