diff --git a/crates/store/re_types/definitions/rerun/archetypes/asset3d.fbs b/crates/store/re_types/definitions/rerun/archetypes/asset3d.fbs
index 974c1b4ea43b..1c1dd87a6362 100644
--- a/crates/store/re_types/definitions/rerun/archetypes/asset3d.fbs
+++ b/crates/store/re_types/definitions/rerun/archetypes/asset3d.fbs
@@ -9,7 +9,7 @@ namespace rerun.archetypes;
/// \example archetypes/asset3d_simple title="Simple 3D asset" image="https://static.rerun.io/asset3d_simple/af238578188d3fd0de3e330212120e2842a8ddb2/1200w.png"
/// \example archetypes/asset3d_out_of_tree !api title="3D asset with out-of-tree transform"
table Asset3D (
- "attr.rust.derive": "PartialEq",
+ "attr.rust.derive": "PartialEq, Eq",
"attr.docs.category": "Spatial 3D",
"attr.docs.view_types": "Spatial3DView, Spatial2DView: if logged above active projection"
) {
@@ -34,8 +34,8 @@ table Asset3D (
// --- Optional ---
- /// An out-of-tree transform.
+ /// If enabled, any transform (components part of the [archetypes.Transform3D] archetype) on this entity will not affect its children.
///
- /// Applies a transformation to the asset itself without impacting its children.
- transform: rerun.components.OutOfTreeTransform3D ("attr.rerun.component_optional", nullable, order: 3000);
+ /// It will however, still be affected by transforms on its parents.
+ out_of_tree_transform: rerun.components.OutOfTreeTransform ("attr.rerun.component_optional", nullable, order: 3000);
}
diff --git a/crates/store/re_types/definitions/rerun/archetypes/ellipsoids.fbs b/crates/store/re_types/definitions/rerun/archetypes/ellipsoids.fbs
index 5e1e7b5a990f..8e4dba977b60 100644
--- a/crates/store/re_types/definitions/rerun/archetypes/ellipsoids.fbs
+++ b/crates/store/re_types/definitions/rerun/archetypes/ellipsoids.fbs
@@ -6,7 +6,7 @@ namespace rerun.archetypes;
///
/// This archetype is for ellipsoids or spheres whose size is a key part of the data
/// (e.g. a bounding sphere).
-/// For points whose radii are for the sake of visualization, use `Points3D` instead.
+/// For points whose radii are for the sake of visualization, use [archetypes.Points3D] instead.
///
/// Currently, ellipsoids are always rendered as wireframes.
/// Opaque and transparent rendering will be supported later.
@@ -53,7 +53,7 @@ table Ellipsoids (
/// Optional text labels for the ellipsoids.
labels: [rerun.components.Text] ("attr.rerun.component_optional", nullable, order: 3200);
- /// Optional `ClassId`s for the ellipsoids.
+ /// Optional [components.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: 3300);
diff --git a/crates/store/re_types/definitions/rerun/archetypes/transform3d.fbs b/crates/store/re_types/definitions/rerun/archetypes/transform3d.fbs
index bcd18db06dbb..e7eff6621704 100644
--- a/crates/store/re_types/definitions/rerun/archetypes/transform3d.fbs
+++ b/crates/store/re_types/definitions/rerun/archetypes/transform3d.fbs
@@ -10,17 +10,20 @@ namespace rerun.archetypes;
///
/// Each transform component can be listed multiple times, but transform tree propagation is only possible
/// if there's only one instance for each transform component.
-/// TODO(#6831): write more about the exact interaction with the to be written `OutOfTreeTransform` component.
+/// However, support for arrays of transform components depends on the visualizer/archetype
+/// and many only support a single instance of each transform component.
+/// To force disabling transform propagation ("out of tree transformation"), use the [components.OutOfTreeTransform] component.
///
/// \example archetypes/transform3d_simple title="Variety of 3D transforms" image="https://static.rerun.io/transform3d_simple/141368b07360ce3fcb1553079258ae3f42bdb9ac/1200w.png"
/// \example archetypes/transform3d_hierarchy title="Transform hierarchy" image="https://static.rerun.io/transform_hierarchy/cb7be7a5a31fcb2efc02ba38e434849248f87554/1200w.png"
-// TODO(#6831): provide example with out of tree transform.
table Transform3D (
"attr.rust.derive": "Default, PartialEq",
"attr.rust.generate_field_info",
"attr.docs.category": "Spatial 3D",
"attr.docs.view_types": "Spatial3DView, Spatial2DView: if logged above active projection"
) {
+ // --- transform components
+
/// Translation vectors.
translation: [rerun.components.Translation3D] ("attr.rerun.component_optional", nullable, order: 1100);
diff --git a/crates/store/re_types/definitions/rerun/components.fbs b/crates/store/re_types/definitions/rerun/components.fbs
index 06093a49364f..e6d161aa6739 100644
--- a/crates/store/re_types/definitions/rerun/components.fbs
+++ b/crates/store/re_types/definitions/rerun/components.fbs
@@ -29,7 +29,7 @@ include "./components/marker_size.fbs";
include "./components/media_type.fbs";
include "./components/name.fbs";
include "./components/opacity.fbs";
-include "./components/out_of_tree_transform3d.fbs";
+include "./components/out_of_tree_transform.fbs";
include "./components/pinhole_projection.fbs";
include "./components/position2d.fbs";
include "./components/position3d.fbs";
diff --git a/crates/store/re_types/definitions/rerun/components/out_of_tree_transform.fbs b/crates/store/re_types/definitions/rerun/components/out_of_tree_transform.fbs
new file mode 100644
index 000000000000..2897a553da42
--- /dev/null
+++ b/crates/store/re_types/definitions/rerun/components/out_of_tree_transform.fbs
@@ -0,0 +1,17 @@
+namespace rerun.components;
+
+/// If out of tree transform is enabled, a transform does not participate in the transform hierarchy.
+///
+/// This means transforms on this entity do not affect children.
+/// It will however, still be affected by transforms on its parents.
+///
+/// This is automatically enabled if any of the transform components are present multiple times.
+/// Setting this to false for a transform that has multiple instances of the same transform component,
+/// will result in an error.
+struct OutOfTreeTransform (
+ "attr.docs.unreleased",
+ "attr.rust.derive": "Default, PartialEq, Eq, Copy"
+) {
+ /// Whether the out of tree transform mode is enabled.
+ enabled: rerun.datatypes.Bool (order: 100);
+}
diff --git a/crates/store/re_types/definitions/rerun/components/out_of_tree_transform3d.fbs b/crates/store/re_types/definitions/rerun/components/out_of_tree_transform3d.fbs
deleted file mode 100644
index 79c5a80f5508..000000000000
--- a/crates/store/re_types/definitions/rerun/components/out_of_tree_transform3d.fbs
+++ /dev/null
@@ -1,13 +0,0 @@
-namespace rerun.components;
-
-// ---
-
-/// An out-of-tree affine transform between two 3D spaces, represented in a given direction.
-///
-/// "Out-of-tree" means that the transform only affects its own entity: children don't inherit from it.
-table OutOfTreeTransform3D (
- "attr.rust.derive": "Default, PartialEq"
-) {
- /// Representation of the transform.
- repr: rerun.datatypes.Transform3D (order: 100);
-}
diff --git a/crates/store/re_types/definitions/rerun/components/transform_relation.fbs b/crates/store/re_types/definitions/rerun/components/transform_relation.fbs
index 94543ceb28ab..2deaef624dca 100644
--- a/crates/store/re_types/definitions/rerun/components/transform_relation.fbs
+++ b/crates/store/re_types/definitions/rerun/components/transform_relation.fbs
@@ -6,14 +6,14 @@ enum TransformRelation: byte (
) {
/// The transform describes how to transform into the parent entity's space.
///
- /// E.g. a translation of (0, 1, 0) with this `TransformRelation` logged at `parent/child` means
+ /// E.g. a translation of (0, 1, 0) with this [components.TransformRelation] logged at `parent/child` means
/// that from the point of view of `parent`, `parent/child` is translated 1 unit along `parent`'s Y axis.
/// From perspective of `parent/child`, the `parent` entity is translated -1 unit along `parent/child`'s Y axis.
ParentFromChild(default),
/// The transform describes how to transform into the child entity's space.
///
- /// E.g. a translation of (0, 1, 0) with this `TransformRelation` logged at `parent/child` means
+ /// E.g. a translation of (0, 1, 0) with this [components.TransformRelation] logged at `parent/child` means
/// that from the point of view of `parent`, `parent/child` is translated -1 unit along `parent`'s Y axis.
/// From perspective of `parent/child`, the `parent` entity is translated 1 unit along `parent/child`'s Y axis.
ChildFromParent,
diff --git a/crates/store/re_types/src/archetypes/asset3d.rs b/crates/store/re_types/src/archetypes/asset3d.rs
index 1c34dd174190..019dc7ab372a 100644
--- a/crates/store/re_types/src/archetypes/asset3d.rs
+++ b/crates/store/re_types/src/archetypes/asset3d.rs
@@ -51,7 +51,7 @@ use ::re_types_core::{DeserializationError, DeserializationResult};
///
///
///
-#[derive(Clone, Debug, PartialEq)]
+#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Asset3D {
/// The asset's bytes.
pub blob: crate::components::Blob,
@@ -68,10 +68,10 @@ pub struct Asset3D {
/// If it cannot guess, it won't be able to render the asset.
pub media_type: Option,
- /// An out-of-tree transform.
+ /// If enabled, any transform (components part of the [`archetypes::Transform3D`][crate::archetypes::Transform3D] archetype) on this entity will not affect its children.
///
- /// Applies a transformation to the asset itself without impacting its children.
- pub transform: Option,
+ /// It will however, still be affected by transforms on its parents.
+ pub out_of_tree_transform: Option,
}
impl ::re_types_core::SizeBytes for Asset3D {
@@ -79,14 +79,14 @@ impl ::re_types_core::SizeBytes for Asset3D {
fn heap_size_bytes(&self) -> u64 {
self.blob.heap_size_bytes()
+ self.media_type.heap_size_bytes()
- + self.transform.heap_size_bytes()
+ + self.out_of_tree_transform.heap_size_bytes()
}
#[inline]
fn is_pod() -> bool {
::is_pod()
&& >::is_pod()
- && >::is_pod()
+ && >::is_pod()
}
}
@@ -102,7 +102,7 @@ static RECOMMENDED_COMPONENTS: once_cell::sync::Lazy<[ComponentName; 2usize]> =
});
static OPTIONAL_COMPONENTS: once_cell::sync::Lazy<[ComponentName; 1usize]> =
- once_cell::sync::Lazy::new(|| ["rerun.components.OutOfTreeTransform3D".into()]);
+ once_cell::sync::Lazy::new(|| ["rerun.components.OutOfTreeTransform".into()]);
static ALL_COMPONENTS: once_cell::sync::Lazy<[ComponentName; 4usize]> =
once_cell::sync::Lazy::new(|| {
@@ -110,7 +110,7 @@ static ALL_COMPONENTS: once_cell::sync::Lazy<[ComponentName; 4usize]> =
"rerun.components.Blob".into(),
"rerun.components.MediaType".into(),
"rerun.components.Asset3DIndicator".into(),
- "rerun.components.OutOfTreeTransform3D".into(),
+ "rerun.components.OutOfTreeTransform".into(),
]
});
@@ -193,10 +193,10 @@ impl ::re_types_core::Archetype for Asset3D {
} else {
None
};
- let transform =
- if let Some(array) = arrays_by_name.get("rerun.components.OutOfTreeTransform3D") {
- ::from_arrow_opt(&**array)
- .with_context("rerun.archetypes.Asset3D#transform")?
+ let out_of_tree_transform =
+ if let Some(array) = arrays_by_name.get("rerun.components.OutOfTreeTransform") {
+ ::from_arrow_opt(&**array)
+ .with_context("rerun.archetypes.Asset3D#out_of_tree_transform")?
.into_iter()
.next()
.flatten()
@@ -206,7 +206,7 @@ impl ::re_types_core::Archetype for Asset3D {
Ok(Self {
blob,
media_type,
- transform,
+ out_of_tree_transform,
})
}
}
@@ -221,7 +221,7 @@ impl ::re_types_core::AsComponents for Asset3D {
self.media_type
.as_ref()
.map(|comp| (comp as &dyn ComponentBatch).into()),
- self.transform
+ self.out_of_tree_transform
.as_ref()
.map(|comp| (comp as &dyn ComponentBatch).into()),
]
@@ -238,7 +238,7 @@ impl Asset3D {
Self {
blob: blob.into(),
media_type: None,
- transform: None,
+ out_of_tree_transform: None,
}
}
@@ -258,15 +258,15 @@ impl Asset3D {
self
}
- /// An out-of-tree transform.
+ /// If enabled, any transform (components part of the [`archetypes::Transform3D`][crate::archetypes::Transform3D] archetype) on this entity will not affect its children.
///
- /// Applies a transformation to the asset itself without impacting its children.
+ /// It will however, still be affected by transforms on its parents.
#[inline]
- pub fn with_transform(
+ pub fn with_out_of_tree_transform(
mut self,
- transform: impl Into,
+ out_of_tree_transform: impl Into,
) -> Self {
- self.transform = Some(transform.into());
+ self.out_of_tree_transform = Some(out_of_tree_transform.into());
self
}
}
diff --git a/crates/store/re_types/src/archetypes/asset3d_ext.rs b/crates/store/re_types/src/archetypes/asset3d_ext.rs
index ad11edb9ba5b..e24149d2a982 100644
--- a/crates/store/re_types/src/archetypes/asset3d_ext.rs
+++ b/crates/store/re_types/src/archetypes/asset3d_ext.rs
@@ -36,7 +36,7 @@ impl Asset3D {
Self {
blob: contents.into(),
media_type,
- transform: None,
+ out_of_tree_transform: None,
}
}
}
diff --git a/crates/store/re_types/src/archetypes/ellipsoids.rs b/crates/store/re_types/src/archetypes/ellipsoids.rs
index f0842e113556..77efdc86abf8 100644
--- a/crates/store/re_types/src/archetypes/ellipsoids.rs
+++ b/crates/store/re_types/src/archetypes/ellipsoids.rs
@@ -22,7 +22,7 @@ use ::re_types_core::{DeserializationError, DeserializationResult};
///
/// This archetype is for ellipsoids or spheres whose size is a key part of the data
/// (e.g. a bounding sphere).
-/// For points whose radii are for the sake of visualization, use `Points3D` instead.
+/// For points whose radii are for the sake of visualization, use [`archetypes::Points3D`][crate::archetypes::Points3D] instead.
///
/// Currently, ellipsoids are always rendered as wireframes.
/// Opaque and transparent rendering will be supported later.
@@ -55,7 +55,7 @@ pub struct Ellipsoids {
/// Optional text labels for the ellipsoids.
pub labels: Option>,
- /// Optional `ClassId`s for the ellipsoids.
+ /// Optional [`components::ClassId`][crate::components::ClassId]s for the ellipsoids.
///
/// The class ID provides colors and labels if not specified explicitly.
pub class_ids: Option>,
@@ -402,7 +402,7 @@ impl Ellipsoids {
self
}
- /// Optional `ClassId`s for the ellipsoids.
+ /// Optional [`components::ClassId`][crate::components::ClassId]s for the ellipsoids.
///
/// The class ID provides colors and labels if not specified explicitly.
#[inline]
diff --git a/crates/store/re_types/src/archetypes/transform3d.rs b/crates/store/re_types/src/archetypes/transform3d.rs
index a9ffbb6630f2..3a1f03cfec4b 100644
--- a/crates/store/re_types/src/archetypes/transform3d.rs
+++ b/crates/store/re_types/src/archetypes/transform3d.rs
@@ -27,7 +27,9 @@ use ::re_types_core::{DeserializationError, DeserializationResult};
///
/// Each transform component can be listed multiple times, but transform tree propagation is only possible
/// if there's only one instance for each transform component.
-/// TODO(#6831): write more about the exact interaction with the to be written `OutOfTreeTransform` component.
+/// However, support for arrays of transform components depends on the visualizer/archetype
+/// and many only support a single instance of each transform component.
+/// To force disabling transform propagation ("out of tree transformation"), use the [`components::OutOfTreeTransform`][crate::components::OutOfTreeTransform] component.
///
/// ## Examples
///
diff --git a/crates/store/re_types/src/components/.gitattributes b/crates/store/re_types/src/components/.gitattributes
index ab6662dfba01..82163cc9dde7 100644
--- a/crates/store/re_types/src/components/.gitattributes
+++ b/crates/store/re_types/src/components/.gitattributes
@@ -30,7 +30,7 @@ media_type.rs linguist-generated=true
mod.rs linguist-generated=true
name.rs linguist-generated=true
opacity.rs linguist-generated=true
-out_of_tree_transform3d.rs linguist-generated=true
+out_of_tree_transform.rs linguist-generated=true
pinhole_projection.rs linguist-generated=true
position2d.rs linguist-generated=true
position3d.rs linguist-generated=true
diff --git a/crates/store/re_types/src/components/mod.rs b/crates/store/re_types/src/components/mod.rs
index d52e2550b0f0..c06ec638c907 100644
--- a/crates/store/re_types/src/components/mod.rs
+++ b/crates/store/re_types/src/components/mod.rs
@@ -50,7 +50,8 @@ mod name;
mod name_ext;
mod opacity;
mod opacity_ext;
-mod out_of_tree_transform3d;
+mod out_of_tree_transform;
+mod out_of_tree_transform_ext;
mod pinhole_projection;
mod pinhole_projection_ext;
mod position2d;
@@ -132,7 +133,7 @@ pub use self::marker_size::MarkerSize;
pub use self::media_type::MediaType;
pub use self::name::Name;
pub use self::opacity::Opacity;
-pub use self::out_of_tree_transform3d::OutOfTreeTransform3D;
+pub use self::out_of_tree_transform::OutOfTreeTransform;
pub use self::pinhole_projection::PinholeProjection;
pub use self::position2d::Position2D;
pub use self::position3d::Position3D;
diff --git a/crates/store/re_types/src/components/out_of_tree_transform3d.rs b/crates/store/re_types/src/components/out_of_tree_transform.rs
similarity index 55%
rename from crates/store/re_types/src/components/out_of_tree_transform3d.rs
rename to crates/store/re_types/src/components/out_of_tree_transform.rs
index ab1ca3b068cb..f61d65b43b5e 100644
--- a/crates/store/re_types/src/components/out_of_tree_transform3d.rs
+++ b/crates/store/re_types/src/components/out_of_tree_transform.rs
@@ -1,5 +1,5 @@
// 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/out_of_tree_transform3d.fbs".
+// Based on "crates/store/re_types/definitions/rerun/components/out_of_tree_transform.fbs".
#![allow(unused_imports)]
#![allow(unused_parens)]
@@ -18,16 +18,21 @@ use ::re_types_core::SerializationResult;
use ::re_types_core::{ComponentBatch, MaybeOwnedComponentBatch};
use ::re_types_core::{DeserializationError, DeserializationResult};
-/// **Component**: An out-of-tree affine transform between two 3D spaces, represented in a given direction.
+/// **Component**: If out of tree transform is enabled, a transform does not participate in the transform hierarchy.
///
-/// "Out-of-tree" means that the transform only affects its own entity: children don't inherit from it.
-#[derive(Clone, Debug, Default, PartialEq)]
-pub struct OutOfTreeTransform3D(
- /// Representation of the transform.
- pub crate::datatypes::Transform3D,
+/// This means transforms on this entity do not affect children.
+/// It will however, still be affected by transforms on its parents.
+///
+/// This is automatically enabled if any of the transform components are present multiple times.
+/// Setting this to false for a transform that has multiple instances of the same transform component,
+/// will result in an error.
+#[derive(Clone, Debug, Default, PartialEq, Eq, Copy)]
+pub struct OutOfTreeTransform(
+ /// Whether the out of tree transform mode is enabled.
+ pub crate::datatypes::Bool,
);
-impl ::re_types_core::SizeBytes for OutOfTreeTransform3D {
+impl ::re_types_core::SizeBytes for OutOfTreeTransform {
#[inline]
fn heap_size_bytes(&self) -> u64 {
self.0.heap_size_bytes()
@@ -35,52 +40,52 @@ impl ::re_types_core::SizeBytes for OutOfTreeTransform3D {
#[inline]
fn is_pod() -> bool {
- ::is_pod()
+ ::is_pod()
}
}
-impl> From for OutOfTreeTransform3D {
+impl> From for OutOfTreeTransform {
fn from(v: T) -> Self {
Self(v.into())
}
}
-impl std::borrow::Borrow for OutOfTreeTransform3D {
+impl std::borrow::Borrow for OutOfTreeTransform {
#[inline]
- fn borrow(&self) -> &crate::datatypes::Transform3D {
+ fn borrow(&self) -> &crate::datatypes::Bool {
&self.0
}
}
-impl std::ops::Deref for OutOfTreeTransform3D {
- type Target = crate::datatypes::Transform3D;
+impl std::ops::Deref for OutOfTreeTransform {
+ type Target = crate::datatypes::Bool;
#[inline]
- fn deref(&self) -> &crate::datatypes::Transform3D {
+ fn deref(&self) -> &crate::datatypes::Bool {
&self.0
}
}
-impl std::ops::DerefMut for OutOfTreeTransform3D {
+impl std::ops::DerefMut for OutOfTreeTransform {
#[inline]
- fn deref_mut(&mut self) -> &mut crate::datatypes::Transform3D {
+ fn deref_mut(&mut self) -> &mut crate::datatypes::Bool {
&mut self.0
}
}
-::re_types_core::macros::impl_into_cow!(OutOfTreeTransform3D);
+::re_types_core::macros::impl_into_cow!(OutOfTreeTransform);
-impl ::re_types_core::Loggable for OutOfTreeTransform3D {
+impl ::re_types_core::Loggable for OutOfTreeTransform {
type Name = ::re_types_core::ComponentName;
#[inline]
fn name() -> Self::Name {
- "rerun.components.OutOfTreeTransform3D".into()
+ "rerun.components.OutOfTreeTransform".into()
}
#[inline]
fn arrow_datatype() -> arrow2::datatypes::DataType {
- crate::datatypes::Transform3D::arrow_datatype()
+ crate::datatypes::Bool::arrow_datatype()
}
fn to_arrow_opt<'a>(
@@ -89,7 +94,7 @@ impl ::re_types_core::Loggable for OutOfTreeTransform3D {
where
Self: Clone + 'a,
{
- crate::datatypes::Transform3D::to_arrow_opt(data.into_iter().map(|datum| {
+ crate::datatypes::Bool::to_arrow_opt(data.into_iter().map(|datum| {
datum.map(|datum| match datum.into() {
::std::borrow::Cow::Borrowed(datum) => ::std::borrow::Cow::Borrowed(&datum.0),
::std::borrow::Cow::Owned(datum) => ::std::borrow::Cow::Owned(datum.0),
@@ -103,7 +108,7 @@ impl ::re_types_core::Loggable for OutOfTreeTransform3D {
where
Self: Sized,
{
- crate::datatypes::Transform3D::from_arrow_opt(arrow_data)
+ crate::datatypes::Bool::from_arrow_opt(arrow_data)
.map(|v| v.into_iter().map(|v| v.map(Self)).collect())
}
}
diff --git a/crates/store/re_types/src/components/out_of_tree_transform_ext.rs b/crates/store/re_types/src/components/out_of_tree_transform_ext.rs
new file mode 100644
index 000000000000..abb31828a784
--- /dev/null
+++ b/crates/store/re_types/src/components/out_of_tree_transform_ext.rs
@@ -0,0 +1,9 @@
+use super::OutOfTreeTransform;
+
+impl OutOfTreeTransform {
+ /// Enabled out of tree transform.
+ pub const ENABLED: Self = Self(crate::datatypes::Bool(true));
+
+ /// Disabled out of tree transform.
+ pub const DISABLED: Self = Self(crate::datatypes::Bool(false));
+}
diff --git a/crates/store/re_types/src/components/transform_relation.rs b/crates/store/re_types/src/components/transform_relation.rs
index 5af660a8ecc0..aa14638e7873 100644
--- a/crates/store/re_types/src/components/transform_relation.rs
+++ b/crates/store/re_types/src/components/transform_relation.rs
@@ -23,7 +23,7 @@ use ::re_types_core::{DeserializationError, DeserializationResult};
pub enum TransformRelation {
/// The transform describes how to transform into the parent entity's space.
///
- /// E.g. a translation of (0, 1, 0) with this `TransformRelation` logged at `parent/child` means
+ /// E.g. a translation of (0, 1, 0) with this [`components::TransformRelation`][crate::components::TransformRelation] logged at `parent/child` means
/// that from the point of view of `parent`, `parent/child` is translated 1 unit along `parent`'s Y axis.
/// From perspective of `parent/child`, the `parent` entity is translated -1 unit along `parent/child`'s Y axis.
#[default]
@@ -31,7 +31,7 @@ pub enum TransformRelation {
/// The transform describes how to transform into the child entity's space.
///
- /// E.g. a translation of (0, 1, 0) with this `TransformRelation` logged at `parent/child` means
+ /// E.g. a translation of (0, 1, 0) with this [`components::TransformRelation`][crate::components::TransformRelation] logged at `parent/child` means
/// that from the point of view of `parent`, `parent/child` is translated -1 unit along `parent`'s Y axis.
/// From perspective of `parent/child`, the `parent` entity is translated 1 unit along `parent/child`'s Y axis.
ChildFromParent = 2,
@@ -47,10 +47,10 @@ impl ::re_types_core::reflection::Enum for TransformRelation {
fn docstring_md(self) -> &'static str {
match self {
Self::ParentFromChild => {
- "The transform describes how to transform into the parent entity's space.\n\nE.g. a translation of (0, 1, 0) with this `TransformRelation` logged at `parent/child` means\nthat from the point of view of `parent`, `parent/child` is translated 1 unit along `parent`'s Y axis.\nFrom perspective of `parent/child`, the `parent` entity is translated -1 unit along `parent/child`'s Y axis."
+ "The transform describes how to transform into the parent entity's space.\n\nE.g. a translation of (0, 1, 0) with this [`components::TransformRelation`][crate::components::TransformRelation] logged at `parent/child` means\nthat from the point of view of `parent`, `parent/child` is translated 1 unit along `parent`'s Y axis.\nFrom perspective of `parent/child`, the `parent` entity is translated -1 unit along `parent/child`'s Y axis."
}
Self::ChildFromParent => {
- "The transform describes how to transform into the child entity's space.\n\nE.g. a translation of (0, 1, 0) with this `TransformRelation` logged at `parent/child` means\nthat from the point of view of `parent`, `parent/child` is translated -1 unit along `parent`'s Y axis.\nFrom perspective of `parent/child`, the `parent` entity is translated 1 unit along `parent/child`'s Y axis."
+ "The transform describes how to transform into the child entity's space.\n\nE.g. a translation of (0, 1, 0) with this [`components::TransformRelation`][crate::components::TransformRelation] logged at `parent/child` means\nthat from the point of view of `parent`, `parent/child` is translated -1 unit along `parent`'s Y axis.\nFrom perspective of `parent/child`, the `parent` entity is translated 1 unit along `parent/child`'s Y axis."
}
}
}
diff --git a/crates/store/re_types/tests/asset3d.rs b/crates/store/re_types/tests/asset3d.rs
index 113b6c6e34f0..d2a347f1fb77 100644
--- a/crates/store/re_types/tests/asset3d.rs
+++ b/crates/store/re_types/tests/asset3d.rs
@@ -1,11 +1,7 @@
-use std::f32::consts::TAU;
-
use re_types::{
archetypes::Asset3D,
- components::{Blob, MediaType, OutOfTreeTransform3D},
- datatypes::{
- Angle, Rotation3D, RotationAxisAngle, Scale3D, TranslationRotationScale3D, Utf8, Vec3D,
- },
+ components::{Blob, MediaType},
+ datatypes::Utf8,
Archetype as _, AsComponents as _,
};
@@ -16,29 +12,11 @@ fn roundtrip() {
let expected = Asset3D {
blob: Blob(BYTES.to_vec().into()),
media_type: Some(MediaType(Utf8(MediaType::GLTF.into()))),
- transform: Some(OutOfTreeTransform3D(
- re_types::datatypes::Transform3D::TranslationRotationScale(
- TranslationRotationScale3D {
- translation: Some(Vec3D([1.0, 2.0, 3.0])),
- rotation: Some(Rotation3D::AxisAngle(RotationAxisAngle {
- axis: Vec3D([0.2, 0.2, 0.8]),
- angle: Angle::from_radians(0.5 * TAU),
- })),
- scale: Some(Scale3D::Uniform(42.0)),
- from_parent: true,
- },
- ),
- )), //
+ out_of_tree_transform: Some(re_types::components::OutOfTreeTransform::ENABLED),
};
- let arch = Asset3D::from_file_contents(BYTES.to_vec(), Some(MediaType::gltf())).with_transform(
- re_types::datatypes::Transform3D::from_translation_rotation_scale(
- [1.0, 2.0, 3.0],
- RotationAxisAngle::new([0.2, 0.2, 0.8], Angle::from_radians(0.5 * TAU)),
- 42.0,
- )
- .from_parent(),
- );
+ let arch = Asset3D::from_file_contents(BYTES.to_vec(), Some(MediaType::gltf()))
+ .with_out_of_tree_transform(true);
similar_asserts::assert_eq!(expected, arch);
// let expected_extensions: HashMap<_, _> = [
diff --git a/crates/top/rerun/src/sdk.rs b/crates/top/rerun/src/sdk.rs
index 8c91db5388a0..7bc05387c8d3 100644
--- a/crates/top/rerun/src/sdk.rs
+++ b/crates/top/rerun/src/sdk.rs
@@ -27,7 +27,7 @@ mod prelude {
pub use re_chunk::ChunkTimeline;
pub use re_types::components::{
AlbedoFactor, Color, FillMode, HalfSize2D, HalfSize3D, LineStrip2D, LineStrip3D, MediaType,
- OutOfTreeTransform3D, Position2D, Position3D, Radius, Scale3D, Text, TextLogLevel,
+ OutOfTreeTransform, Position2D, Position3D, Radius, Scale3D, Text, TextLogLevel,
TransformRelation, TriangleIndices, Vector2D, Vector3D,
};
pub use re_types::datatypes::{
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 c3bb679819e1..79a185606d3d 100644
--- a/crates/viewer/re_data_ui/src/component_ui_registry.rs
+++ b/crates/viewer/re_data_ui/src/component_ui_registry.rs
@@ -22,7 +22,6 @@ pub fn create_component_ui_registry() -> ComponentUiRegistry {
add_to_registry::(&mut registry);
add_to_registry::(&mut registry);
add_to_registry::(&mut registry);
- add_to_registry::(&mut registry);
add_to_registry::(&mut registry);
add_to_registry::(&mut registry);
diff --git a/crates/viewer/re_data_ui/src/transform3d.rs b/crates/viewer/re_data_ui/src/transform3d.rs
index ac62199d3097..65e89ca80d4a 100644
--- a/crates/viewer/re_data_ui/src/transform3d.rs
+++ b/crates/viewer/re_data_ui/src/transform3d.rs
@@ -45,20 +45,6 @@ impl DataUi for re_types::components::Transform3D {
}
}
-impl DataUi for re_types::components::OutOfTreeTransform3D {
- #[inline]
- fn data_ui(
- &self,
- ctx: &ViewerContext<'_>,
- ui: &mut egui::Ui,
- ui_layout: UiLayout,
- query: &re_chunk_store::LatestAtQuery,
- db: &re_entity_db::EntityDb,
- ) {
- re_types::components::Transform3D(self.0).data_ui(ctx, ui, ui_layout, query, db);
- }
-}
-
impl DataUi for Transform3D {
#[allow(clippy::only_used_in_recursion)]
fn data_ui(
diff --git a/crates/viewer/re_space_view_spatial/src/contexts/mod.rs b/crates/viewer/re_space_view_spatial/src/contexts/mod.rs
index 709b93c2dd53..e79a0677c4b3 100644
--- a/crates/viewer/re_space_view_spatial/src/contexts/mod.rs
+++ b/crates/viewer/re_space_view_spatial/src/contexts/mod.rs
@@ -5,7 +5,7 @@ mod transform_context;
pub use annotation_context::AnnotationSceneContext;
pub use depth_offsets::EntityDepthOffsets;
use re_types::SpaceViewClassIdentifier;
-pub use transform_context::TransformContext;
+pub use transform_context::{fallback_for_out_of_tree_transform, TransformContext, TransformInfo};
// -----------------------------------------------------------------------------
@@ -15,6 +15,7 @@ use re_viewer_context::{Annotations, SpaceViewClassRegistryError};
/// Context objects for a single entity in a spatial scene.
pub struct SpatialSceneEntityContext<'a> {
pub world_from_entity: glam::Affine3A,
+ pub transform_info: &'a TransformInfo,
pub depth_offset: DepthOffset,
pub annotations: std::sync::Arc,
diff --git a/crates/viewer/re_space_view_spatial/src/contexts/transform_context.rs b/crates/viewer/re_space_view_spatial/src/contexts/transform_context.rs
index 75ec284b0fad..3f93cdbca768 100644
--- a/crates/viewer/re_space_view_spatial/src/contexts/transform_context.rs
+++ b/crates/viewer/re_space_view_spatial/src/contexts/transform_context.rs
@@ -6,20 +6,38 @@ use re_space_view::DataResultQuery as _;
use re_types::{
archetypes::Pinhole,
components::{
- DisconnectedSpace, ImagePlaneDistance, PinholeProjection, RotationAxisAngle, RotationQuat,
- Scale3D, Transform3D, TransformMat3x3, TransformRelation, Translation3D, ViewCoordinates,
+ DisconnectedSpace, ImagePlaneDistance, OutOfTreeTransform, PinholeProjection,
+ RotationAxisAngle, RotationQuat, Scale3D, Transform3D, TransformMat3x3, TransformRelation,
+ Translation3D, ViewCoordinates,
},
ComponentNameSet, Loggable as _,
};
use re_viewer_context::{IdentifiedViewSystem, ViewContext, ViewContextSystem};
-use crate::visualizers::image_view_coordinates;
+use crate::visualizers::{entity_iterator::clamped_or_nothing, image_view_coordinates};
-#[derive(Clone)]
-struct TransformInfo {
+#[derive(Clone, Debug)]
+pub struct TransformInfo {
/// The transform from the entity to the reference space.
+ ///
+ /// Does not include out-of-tree transforms!
pub reference_from_entity: glam::Affine3A,
+ /// Like [`TransformInfo::reference_from_entity`], but if this entity has a pinhole camera, it won't affect the transform.
+ ///
+ /// Normally, the transform we compute for an entity with a pinhole transform places all objects
+ /// in front (defined by view coordinates) of the camera with a given image plane distance.
+ /// In some cases like drawing the lines for a frustum or arrows for the 3D transform, this is not the desired transformation.
+ ///
+ /// TODO(andreas): This a lot of overhead for when we don't need it (which is most of the time!).
+ ///
+ /// TODO(#2663, #1025): Going forward we should have separate transform hierarchies for 2D (i.e. projected) and 3D,
+ /// which would remove the need for this.
+ pub reference_from_entity_ignoring_3d_from_2d_pinhole: glam::Affine3A,
+
+ /// Optional list of out of tree transforms that are applied to the instances of this entity.
+ pub out_of_tree_transforms: Vec,
+
/// The pinhole camera ancestor of this entity if any.
///
/// None indicates that this entity is under the eye camera with no Pinhole camera in-between.
@@ -27,6 +45,17 @@ struct TransformInfo {
pub parent_pinhole: Option,
}
+impl Default for TransformInfo {
+ fn default() -> Self {
+ Self {
+ reference_from_entity: glam::Affine3A::IDENTITY,
+ reference_from_entity_ignoring_3d_from_2d_pinhole: glam::Affine3A::IDENTITY,
+ out_of_tree_transforms: Vec::new(),
+ parent_pinhole: None,
+ }
+ }
+}
+
#[derive(Clone, Copy)]
enum UnreachableTransformReason {
/// More than one pinhole camera between this and the reference space.
@@ -98,12 +127,10 @@ impl ViewContextSystem for TransformContext {
debug_assert_transform_field_order(ctx.viewer_ctx.reflection);
- let entity_tree = ctx.recording().tree();
-
self.space_origin = query.space_origin.clone();
// Find the entity path tree for the root.
- let Some(mut current_tree) = &entity_tree.subtree(query.space_origin) else {
+ let Some(current_tree) = &ctx.recording().tree().subtree(query.space_origin) else {
// It seems the space path is not part of the object tree!
// This happens frequently when the viewer remembers space views from a previous run that weren't shown yet.
// Naturally, in this case we don't have any transforms yet.
@@ -119,11 +146,32 @@ impl ViewContextSystem for TransformContext {
current_tree,
ctx.recording(),
&time_query,
- glam::Affine3A::IDENTITY,
- &None, // Ignore potential pinhole camera at the root of the space view, since it regarded as being "above" this root.
+ // Ignore potential pinhole camera at the root of the space view, since it regarded as being "above" this root.
+ TransformInfo::default(),
);
// Walk up from the reference to the highest reachable parent.
+ self.gather_parent_transforms(ctx, query, current_tree, &time_query);
+ }
+
+ fn as_any(&self) -> &dyn std::any::Any {
+ self
+ }
+}
+
+impl TransformContext {
+ /// Gather transforms for everything _above_ the root.
+ fn gather_parent_transforms<'a>(
+ &mut self,
+ ctx: &'a ViewContext<'a>,
+ query: &re_viewer_context::ViewQuery<'_>,
+ mut current_tree: &'a EntityTree,
+ time_query: &LatestAtQuery,
+ ) {
+ re_tracing::profile_function!();
+
+ let entity_tree = ctx.recording().tree();
+
let mut encountered_pinhole = None;
let mut reference_from_ancestor = glam::Affine3A::IDENTITY;
while let Some(parent_path) = current_tree.path.parent() {
@@ -139,10 +187,10 @@ impl ViewContextSystem for TransformContext {
// Note that the transform at the reference is the first that needs to be inverted to "break out" of its hierarchy.
// Generally, the transform _at_ a node isn't relevant to it's children, but only to get to its parent in turn!
- match transform_at(
+ let new_transform = match transform_at(
current_tree,
ctx.recording(),
- &time_query,
+ time_query,
// TODO(#1025): See comment in transform_at. This is a workaround for precision issues
// and the fact that there is no meaningful image plane distance for 3D->2D views.
|_| 500.0,
@@ -153,33 +201,63 @@ impl ViewContextSystem for TransformContext {
Some((parent_tree.path.clone(), unreachable_reason));
break;
}
- Ok(None) => {}
- Ok(Some(parent_from_child)) => {
- reference_from_ancestor *= parent_from_child.inverse();
+ Ok((transform_at_entity, entity_from_2d_pinhole_content)) => {
+ let mut new_transform = TransformInfo {
+ reference_from_entity: reference_from_ancestor,
+ reference_from_entity_ignoring_3d_from_2d_pinhole: reference_from_ancestor,
+ out_of_tree_transforms: Vec::new(),
+ parent_pinhole: encountered_pinhole.clone(),
+ };
+
+ // Need to take care of the fact that we're walking the other direction of the tree here compared to `gather_descendants_transforms`!
+ if let Some(entity_from_2d_pinhole_content) = entity_from_2d_pinhole_content {
+ debug_assert!(encountered_pinhole.as_ref() == Some(¤t_tree.path));
+ new_transform.reference_from_entity *=
+ entity_from_2d_pinhole_content.0.inverse();
+ }
+
+ match transform_at_entity {
+ TransformsAtEntity::None => {}
+ TransformsAtEntity::TreeTransform(child_from_entity) => {
+ let entity_from_child = child_from_entity.inverse();
+ new_transform.reference_from_entity *= entity_from_child;
+ new_transform.reference_from_entity_ignoring_3d_from_2d_pinhole *=
+ entity_from_child;
+ }
+ TransformsAtEntity::OutOfTreeTransforms(out_of_tree_transforms) => {
+ new_transform.out_of_tree_transforms = out_of_tree_transforms
+ .iter()
+ .map(|&t| t.inverse())
+ .collect();
+ }
+ }
+
+ // If we're going up the tree and encounter a pinhole, we need to apply it, it means that our reference is 2D.
+ // So it's not actually a "3d_from_2d" but rather a "2d_from_3d", consequently
+ // `reference_from_entity_ignoring_3d_from_2d_pinhole`is always the same as `reference_from_entity`.
+ new_transform.reference_from_entity_ignoring_3d_from_2d_pinhole =
+ new_transform.reference_from_entity;
+
+ new_transform
}
- }
+ };
- // (skip over everything at and under `current_tree` automatically)
+ reference_from_ancestor = new_transform.reference_from_entity;
+
+ // (this skips over everything at and under `current_tree` automatically)
self.gather_descendants_transforms(
ctx,
query,
parent_tree,
ctx.recording(),
- &time_query,
- reference_from_ancestor,
- &encountered_pinhole,
+ time_query,
+ new_transform,
);
current_tree = parent_tree;
}
}
- fn as_any(&self) -> &dyn std::any::Any {
- self
- }
-}
-
-impl TransformContext {
#[allow(clippy::too_many_arguments)]
fn gather_descendants_transforms(
&mut self,
@@ -188,24 +266,22 @@ impl TransformContext {
subtree: &EntityTree,
entity_db: &EntityDb,
query: &LatestAtQuery,
- reference_from_entity: glam::Affine3A,
- encountered_pinhole: &Option,
+ transform: TransformInfo,
) {
+ let encountered_pinhole = transform.parent_pinhole.clone();
+ let reference_from_parent = transform.reference_from_entity;
+ let reference_from_parent_ignoring_3d_from_2d_pinhole =
+ transform.reference_from_entity_ignoring_3d_from_2d_pinhole;
match self.transform_per_entity.entry(subtree.path.clone()) {
std::collections::hash_map::Entry::Occupied(_) => {
return;
}
std::collections::hash_map::Entry::Vacant(e) => {
- e.insert(TransformInfo {
- reference_from_entity,
- parent_pinhole: encountered_pinhole.clone(),
- });
+ e.insert(transform);
}
}
for child_tree in subtree.children.values() {
- let mut encountered_pinhole = encountered_pinhole.clone();
-
let lookup_image_plane = |p: &_| {
let query_result = ctx.viewer_ctx.lookup_query_result(view_query.space_view_id);
@@ -223,7 +299,8 @@ impl TransformContext {
.into()
};
- let reference_from_child = match transform_at(
+ let mut encountered_pinhole = encountered_pinhole.clone();
+ let new_transform = match transform_at(
child_tree,
entity_db,
query,
@@ -235,17 +312,44 @@ impl TransformContext {
.push((child_tree.path.clone(), unreachable_reason));
continue;
}
- Ok(None) => reference_from_entity,
- Ok(Some(child_from_parent)) => reference_from_entity * child_from_parent,
+
+ Ok((transform_at_entity, entity_from_2d_pinhole_content)) => {
+ let mut new_transform = TransformInfo {
+ reference_from_entity: reference_from_parent,
+ reference_from_entity_ignoring_3d_from_2d_pinhole:
+ reference_from_parent_ignoring_3d_from_2d_pinhole,
+ out_of_tree_transforms: Vec::new(),
+ parent_pinhole: encountered_pinhole.clone(),
+ };
+
+ match transform_at_entity {
+ TransformsAtEntity::None => {}
+ TransformsAtEntity::TreeTransform(parent_from_entity) => {
+ new_transform.reference_from_entity *= parent_from_entity;
+ new_transform.reference_from_entity_ignoring_3d_from_2d_pinhole *=
+ parent_from_entity;
+ }
+ TransformsAtEntity::OutOfTreeTransforms(out_of_tree_transforms) => {
+ new_transform.out_of_tree_transforms = out_of_tree_transforms;
+ }
+ }
+
+ if let Some(entity_from_2d_pinhole_content) = entity_from_2d_pinhole_content {
+ debug_assert!(encountered_pinhole.as_ref() == Some(&child_tree.path));
+ new_transform.reference_from_entity *= entity_from_2d_pinhole_content.0;
+ }
+
+ new_transform
+ }
};
+
self.gather_descendants_transforms(
ctx,
view_query,
child_tree,
entity_db,
query,
- reference_from_child,
- &encountered_pinhole,
+ new_transform,
);
}
}
@@ -254,51 +358,11 @@ impl TransformContext {
&self.space_origin
}
- /// Retrieves the transform of on entity from its local system to the space of the reference.
+ /// Retrieves transform information for a given entity.
///
- /// Returns None if the path is not reachable.
- pub fn reference_from_entity(&self, ent_path: &EntityPath) -> Option {
- self.transform_per_entity
- .get(ent_path)
- .map(|i| i.reference_from_entity)
- }
-
- /// Like [`Self::reference_from_entity`], but if `ent_path` has a pinhole camera, it won't affect the transform.
- ///
- /// Normally, the transform we compute for an entity with a pinhole transform places all objects
- /// in front (defined by view coordinates) of the camera with a given image plane distance.
- /// In some cases like drawing the lines for a frustum or arrows for the 3D transform, this is not the desired transformation.
- /// Returns None if the path is not reachable.
- ///
- /// TODO(#2663, #1025): Going forward we should have separate transform hierarchies for 2D (i.e. projected) and 3D,
- /// which would remove the need for this.
- pub fn reference_from_entity_ignoring_pinhole(
- &self,
- ent_path: &EntityPath,
- entity_db: &EntityDb,
- query: &LatestAtQuery,
- ) -> Option {
- let transform_info = self.transform_per_entity.get(ent_path)?;
- if let (true, Some(parent)) = (
- transform_info.parent_pinhole.as_ref() == Some(ent_path),
- ent_path.parent(),
- ) {
- self.reference_from_entity(&parent).map(|t| {
- t * get_parent_from_child_transform(ent_path, entity_db, query).unwrap_or_default()
- })
- } else {
- Some(transform_info.reference_from_entity)
- }
- }
-
- /// Retrieves the ancestor (or self) pinhole under which this entity sits.
- ///
- /// None indicates either that the entity does not exist in this hierarchy or that this entity is under the eye camera with no Pinhole camera in-between.
- /// Some indicates that the entity is under a pinhole camera at the given entity path that is not at the root of the space view.
- pub fn parent_pinhole(&self, ent_path: &EntityPath) -> Option<&EntityPath> {
- self.transform_per_entity
- .get(ent_path)
- .and_then(|i| i.parent_pinhole.as_ref())
+ /// Returns `None` if it's not reachable from the view's origin.
+ pub fn transform_info_for_entity(&self, ent_path: &EntityPath) -> Option<&TransformInfo> {
+ self.transform_per_entity.get(ent_path)
}
}
@@ -341,11 +405,87 @@ But they are instead ordered like this:\n{actual_order:?}"
#[cfg(not(debug_assertions))]
fn debug_assert_transform_field_order(_: &re_types::reflection::Reflection) {}
-fn get_parent_from_child_transform(
+fn assemble_transform(
+ translation: Option<&Translation3D>,
+ rotation_quat: Option<&RotationQuat>,
+ rotation_axis_angle: Option<&RotationAxisAngle>,
+ scale: Option<&Scale3D>,
+ mat3x3: Option<&TransformMat3x3>,
+ transform_relation: TransformRelation,
+) -> glam::Affine3A {
+ // Order see `debug_assert_transform_field_order`
+ let mut transform = glam::Affine3A::IDENTITY;
+
+ if let Some(translation) = translation {
+ transform = glam::Affine3A::from(*translation);
+ }
+ if let Some(rotation_quat) = rotation_quat {
+ transform *= glam::Affine3A::from(*rotation_quat);
+ }
+ if let Some(rotation_axis_angle) = rotation_axis_angle {
+ transform *= glam::Affine3A::from(*rotation_axis_angle);
+ }
+ if let Some(scale) = scale {
+ transform *= glam::Affine3A::from(*scale);
+ }
+ if let Some(mat3x3) = mat3x3 {
+ transform *= glam::Affine3A::from(*mat3x3);
+ }
+
+ if transform_relation == TransformRelation::ChildFromParent {
+ transform = transform.inverse();
+ }
+
+ transform
+}
+
+/// Utility representing the possible resolved transforms at an entity.
+enum TransformsAtEntity {
+ None,
+ TreeTransform(glam::Affine3A),
+ OutOfTreeTransforms(Vec),
+}
+
+struct ObjFrom2DPinholeContent(glam::Affine3A);
+
+impl TransformsAtEntity {
+ fn is_none(&self) -> bool {
+ matches!(self, Self::None)
+ }
+}
+
+/// Utility method to implement fallback provider of `OutOfTreeTransform`.
+pub fn fallback_for_out_of_tree_transform(ctx: &re_viewer_context::QueryContext<'_>) -> bool {
+ let result = ctx.recording().latest_at(
+ ctx.query,
+ ctx.target_entity_path,
+ [
+ Transform3D::name(),
+ Translation3D::name(),
+ RotationAxisAngle::name(),
+ RotationQuat::name(),
+ Scale3D::name(),
+ TransformMat3x3::name(),
+ ],
+ );
+ if result.components.is_empty() {
+ return false;
+ }
+
+ result
+ .components
+ .values()
+ .map(|result| result.num_instances())
+ .max()
+ .unwrap_or(0)
+ > 1
+}
+
+fn query_and_resolve_transforms_at_entity(
entity_path: &EntityPath,
entity_db: &EntityDb,
query: &LatestAtQuery,
-) -> Option {
+) -> TransformsAtEntity {
let resolver = entity_db.resolver();
// TODO(#6743): Doesn't take into account overrides.
let result = entity_db.latest_at(
@@ -359,38 +499,84 @@ fn get_parent_from_child_transform(
Scale3D::name(),
TransformMat3x3::name(),
TransformRelation::name(),
+ OutOfTreeTransform::name(),
],
);
if result.components.is_empty() {
- return None;
+ return TransformsAtEntity::None;
}
- // Order is specified by order of components in the Transform3D archetype.
- // See `has_transform_expected_order`
- let mut transform = glam::Affine3A::IDENTITY;
- if let Some(translation) = result.get_instance::(resolver, 0) {
- transform *= glam::Affine3A::from(translation);
+ let translations = result.get_slice::(resolver).unwrap_or(&[]);
+ let scales = result.get_slice::(resolver).unwrap_or(&[]);
+ let rotation_quats = result.get_slice::(resolver).unwrap_or(&[]);
+ let rotation_axis_angles = result
+ .get_slice::(resolver)
+ .unwrap_or(&[]);
+ let mat3x3 = result.get_slice::(resolver).unwrap_or(&[]);
+
+ let max_count = translations
+ .len()
+ .max(scales.len())
+ .max(rotation_quats.len())
+ .max(rotation_axis_angles.len())
+ .max(mat3x3.len());
+
+ if max_count == 0 {
+ return TransformsAtEntity::None;
}
- if let Some(rotation) = result.get_instance::(resolver, 0) {
- transform *= glam::Affine3A::from(rotation);
- }
- if let Some(rotation) = result.get_instance::(resolver, 0) {
- transform *= glam::Affine3A::from(rotation);
- }
- if let Some(scale) = result.get_instance::(resolver, 0) {
- transform *= glam::Affine3A::from(scale);
- }
- if let Some(mat3x3) = result.get_instance::(resolver, 0) {
- transform *= glam::Affine3A::from(mat3x3);
+
+ // Default out of tree transform to true if any of the transform components are set more than once.
+ let out_of_tree = result
+ .get_instance::(resolver, 0)
+ .map_or(max_count > 1, |c| *c.0);
+
+ if !out_of_tree && max_count > 1 {
+ re_log::error!(
+ "Entity {:?} has multiple instances of transform components, but OutOfTreeTransform is set to false.
+Propagating multiple transforms to children is not supported",
+ entity_path
+ );
+ return TransformsAtEntity::None;
}
let transform_relation = result
.get_instance::(resolver, 0)
.unwrap_or_default();
- if transform_relation == TransformRelation::ChildFromParent {
- Some(transform.inverse())
+
+ if out_of_tree {
+ // Order is specified by order of components in the Transform3D archetype.
+ // See `has_transform_expected_order`
+
+ let mut out_of_tree_transforms = Vec::with_capacity(max_count);
+
+ let mut iter_scales = clamped_or_nothing(scales, max_count);
+ let mut iter_rotation_quats = clamped_or_nothing(rotation_quats, max_count);
+ let mut iter_rotation_axis_angles = clamped_or_nothing(rotation_axis_angles, max_count);
+ let mut iter_translations = clamped_or_nothing(translations, max_count);
+ let mut iter_mat3x3 = clamped_or_nothing(mat3x3, max_count);
+
+ for _ in 0..max_count {
+ out_of_tree_transforms.push(assemble_transform(
+ iter_translations.next(),
+ iter_rotation_quats.next(),
+ iter_rotation_axis_angles.next(),
+ iter_scales.next(),
+ iter_mat3x3.next(),
+ transform_relation,
+ ));
+ }
+
+ TransformsAtEntity::OutOfTreeTransforms(out_of_tree_transforms)
} else {
- Some(transform)
+ // Fast path for no out of tree transforms.
+ TransformsAtEntity::TreeTransform(assemble_transform(
+ translations.first(),
+ rotation_quats.first(),
+ rotation_axis_angles.first(),
+ scales.first(),
+ mat3x3.first(),
+ transform_relation,
+ ))
}
// TODO(#6831): Should add a unit test to this method once all variants are in.
@@ -420,23 +606,24 @@ fn transform_at(
query: &LatestAtQuery,
pinhole_image_plane_distance: impl Fn(&EntityPath) -> f32,
encountered_pinhole: &mut Option,
-) -> Result, UnreachableTransformReason> {
+) -> Result<(TransformsAtEntity, Option), UnreachableTransformReason> {
re_tracing::profile_function!();
let entity_path = &subtree.path;
-
- let pinhole = get_cached_pinhole(entity_path, entity_db, query);
- if pinhole.is_some() {
+ let transforms_at_entity =
+ query_and_resolve_transforms_at_entity(entity_path, entity_db, query);
+
+ // Special pinhole handling.
+ // TODO(#1025): We should start a proper 2D subspace here instead of making up a 2D -> 3D transform.
+ let pinhole_transform = if let Some((image_from_camera, camera_xyz)) =
+ get_cached_pinhole(entity_path, entity_db, query)
+ {
if encountered_pinhole.is_some() {
return Err(UnreachableTransformReason::NestedPinholeCameras);
} else {
*encountered_pinhole = Some(entity_path.clone());
}
- }
- let transform3d = get_parent_from_child_transform(entity_path, entity_db, query);
-
- let pinhole = pinhole.map(|(image_from_camera, camera_xyz)| {
// Everything under a pinhole camera is a 2D projection, thus doesn't actually have a proper 3D representation.
// Our visualization interprets this as looking at a 2D image plane from a single point (the pinhole).
@@ -457,9 +644,11 @@ fn transform_at(
// Our interpretation of the pinhole camera implies that the axis semantics, i.e. ViewCoordinates,
// determine how the image plane is oriented.
// (see also `CamerasPart` where the frustum lines are set up)
- let world_from_image_plane3d = camera_xyz.from_other(&image_view_coordinates());
+ let obj_from_image_plane3d = camera_xyz.from_other(&image_view_coordinates());
- glam::Affine3A::from_mat3(world_from_image_plane3d) * image_plane3d_from_2d_content
+ Some(ObjFrom2DPinholeContent(
+ glam::Affine3A::from_mat3(obj_from_image_plane3d) * image_plane3d_from_2d_content,
+ ))
// Above calculation is nice for a certain kind of visualizing a projected image plane,
// but the image plane distance is arbitrary and there might be other, better visualizations!
@@ -471,23 +660,19 @@ fn transform_at(
// should have infinite depth!
// The inverse of this matrix *is* working for this, but quickly runs into precision issues.
// See also `ui_2d.rs#setup_target_config`
- });
-
- let is_disconnect_space = || {
- entity_db
- .latest_at_component::(entity_path, query)
- .map_or(false, |res| **res.value)
+ } else {
+ None
};
// If there is any other transform, we ignore `DisconnectedSpace`.
- if transform3d.is_some() || pinhole.is_some() {
- Ok(Some(
- transform3d.unwrap_or(glam::Affine3A::IDENTITY)
- * pinhole.unwrap_or(glam::Affine3A::IDENTITY),
- ))
- } else if is_disconnect_space() {
+ if transforms_at_entity.is_none()
+ && pinhole_transform.is_none()
+ && entity_db
+ .latest_at_component::(entity_path, query)
+ .map_or(false, |res| **res.value)
+ {
Err(UnreachableTransformReason::DisconnectedSpace)
} else {
- Ok(None)
+ Ok((transforms_at_entity, pinhole_transform))
}
}
diff --git a/crates/viewer/re_space_view_spatial/src/mesh_cache.rs b/crates/viewer/re_space_view_spatial/src/mesh_cache.rs
index 5c0722b43272..ea765e9886e9 100644
--- a/crates/viewer/re_space_view_spatial/src/mesh_cache.rs
+++ b/crates/viewer/re_space_view_spatial/src/mesh_cache.rs
@@ -24,7 +24,10 @@ pub struct MeshCache(ahash::HashMap>>);
/// Either a [`re_types::archetypes::Asset3D`] or [`re_types::archetypes::Mesh3D`] to be cached.
#[derive(Debug, Clone, Copy)]
pub enum AnyMesh<'a> {
- Asset(&'a re_types::archetypes::Asset3D),
+ Asset {
+ blob: &'a re_types::components::Blob,
+ media_type: Option<&'a re_types::components::MediaType>,
+ },
Mesh {
mesh: &'a re_types::archetypes::Mesh3D,
diff --git a/crates/viewer/re_space_view_spatial/src/mesh_loader.rs b/crates/viewer/re_space_view_spatial/src/mesh_loader.rs
index df0a01b68c11..8bbc5b0d5389 100644
--- a/crates/viewer/re_space_view_spatial/src/mesh_loader.rs
+++ b/crates/viewer/re_space_view_spatial/src/mesh_loader.rs
@@ -1,10 +1,6 @@
use itertools::Itertools;
use re_renderer::{resource_managers::ResourceLifeTime, RenderContext, Rgba32Unmul};
-use re_types::{
- archetypes::{Asset3D, Mesh3D},
- components::MediaType,
- datatypes::TensorBuffer,
-};
+use re_types::{archetypes::Mesh3D, components::MediaType, datatypes::TensorBuffer};
use crate::mesh_cache::AnyMesh;
@@ -26,7 +22,9 @@ impl LoadedMesh {
) -> anyhow::Result {
// TODO(emilk): load CpuMesh in background thread.
match mesh {
- AnyMesh::Asset(asset3d) => Self::load_asset3d(name, asset3d, render_ctx),
+ AnyMesh::Asset { blob, media_type } => {
+ Self::load_asset3d(name, blob, media_type, render_ctx)
+ }
AnyMesh::Mesh { mesh, texture_key } => {
Ok(Self::load_mesh3d(name, mesh, texture_key, render_ctx)?)
}
@@ -68,18 +66,13 @@ impl LoadedMesh {
fn load_asset3d(
name: String,
- asset3d: &Asset3D,
+ blob: &re_types::components::Blob,
+ media_type: Option<&re_types::components::MediaType>,
render_ctx: &RenderContext,
) -> anyhow::Result {
re_tracing::profile_function!();
- let Asset3D {
- blob,
- media_type,
- transform: _,
- } = asset3d;
-
- let media_type = MediaType::or_guess_from_data(media_type.clone(), blob.as_slice())
+ let media_type = MediaType::or_guess_from_data(media_type.cloned(), blob.as_slice())
.ok_or_else(|| anyhow::anyhow!("couldn't guess media type"))?;
let slf = Self::load_asset3d_parts(name, &media_type, blob.as_slice(), render_ctx)?;
diff --git a/crates/viewer/re_space_view_spatial/src/visualizers/assets3d.rs b/crates/viewer/re_space_view_spatial/src/visualizers/assets3d.rs
index 2c7904873d99..6db6fe583f40 100644
--- a/crates/viewer/re_space_view_spatial/src/visualizers/assets3d.rs
+++ b/crates/viewer/re_space_view_spatial/src/visualizers/assets3d.rs
@@ -1,22 +1,22 @@
use re_chunk_store::RowId;
use re_log_types::{hash::Hash64, Instance, TimeInt};
-use re_query::range_zip_1x2;
+use re_query::range_zip_1x1;
use re_renderer::renderer::MeshInstance;
use re_renderer::RenderContext;
use re_types::{
archetypes::Asset3D,
- components::{Blob, MediaType, OutOfTreeTransform3D},
+ components::{Blob, MediaType, OutOfTreeTransform},
};
use re_viewer_context::{
ApplicableEntities, IdentifiedViewSystem, QueryContext, SpaceViewSystemExecutionError,
- ViewContext, ViewContextCollection, ViewQuery, VisualizableEntities, VisualizableFilterContext,
- VisualizerQueryInfo, VisualizerSystem,
+ TypedComponentFallbackProvider, ViewContext, ViewContextCollection, ViewQuery,
+ VisualizableEntities, VisualizableFilterContext, VisualizerQueryInfo, VisualizerSystem,
};
use super::{filter_visualizable_3d_entities, SpatialViewVisualizerData};
use crate::{
- contexts::SpatialSceneEntityContext,
+ contexts::{fallback_for_out_of_tree_transform, SpatialSceneEntityContext},
instance_hash_conversions::picking_layer_id_from_instance_path_hash,
mesh_cache::{AnyMesh, MeshCache, MeshCacheKey},
view_kind::SpatialSpaceViewKind,
@@ -37,7 +37,6 @@ struct Asset3DComponentData<'a> {
blob: &'a Blob,
media_type: Option<&'a MediaType>,
- transform: Option<&'a OutOfTreeTransform3D>,
}
// NOTE: Do not put profile scopes in these methods. They are called for all entities and all
@@ -54,14 +53,6 @@ impl Asset3DVisualizer {
let entity_path = ctx.target_entity_path;
for data in data {
- let mesh = Asset3D {
- blob: data.blob.clone(),
- media_type: data.media_type.cloned(),
-
- // NOTE: Don't even try to cache the transform!
- transform: None,
- };
-
let primary_row_id = data.index.1;
let picking_instance_hash = re_entity_db::InstancePathHash::entity_all(entity_path);
let outline_mask_ids = ent_context.highlight.index_outline_mask(Instance::ALL);
@@ -77,7 +68,10 @@ impl Asset3DVisualizer {
query_result_hash: Hash64::ZERO,
media_type: data.media_type.cloned(),
},
- AnyMesh::Asset(&mesh),
+ AnyMesh::Asset {
+ blob: data.blob,
+ media_type: data.media_type,
+ },
render_ctx,
)
});
@@ -85,10 +79,13 @@ impl Asset3DVisualizer {
if let Some(mesh) = mesh {
re_tracing::profile_scope!("mesh instances");
- let world_from_pose = ent_context.world_from_entity
- * data
- .transform
- .map_or(glam::Affine3A::IDENTITY, |t| t.0.into());
+ let world_from_pose = if let Some(entity_from_out_of_tree) =
+ ent_context.transform_info.out_of_tree_transforms.first()
+ {
+ ent_context.world_from_entity * (*entity_from_out_of_tree)
+ } else {
+ ent_context.world_from_entity
+ };
instances.extend(mesh.mesh_instances.iter().map(move |mesh_instance| {
let pose_from_mesh = mesh_instance.world_from_mesh;
@@ -159,21 +156,15 @@ impl VisualizerSystem for Asset3DVisualizer {
};
let media_types = results.get_or_empty_dense(resolver)?;
- let transforms = results.get_or_empty_dense(resolver)?;
- let data = range_zip_1x2(
- blobs.range_indexed(),
- media_types.range_indexed(),
- transforms.range_indexed(),
- )
- .filter_map(|(&index, blobs, media_types, transforms)| {
- blobs.first().map(|blob| Asset3DComponentData {
- index,
- blob,
- media_type: media_types.and_then(|media_types| media_types.first()),
- transform: transforms.and_then(|transforms| transforms.first()),
- })
- });
+ let data = range_zip_1x1(blobs.range_indexed(), media_types.range_indexed())
+ .filter_map(|(&index, blobs, media_types)| {
+ blobs.first().map(|blob| Asset3DComponentData {
+ index,
+ blob,
+ media_type: media_types.and_then(|media_types| media_types.first()),
+ })
+ });
self.process_data(ctx, render_ctx, &mut instances, spatial_ctx, data);
Ok(())
@@ -202,4 +193,10 @@ impl VisualizerSystem for Asset3DVisualizer {
}
}
-re_viewer_context::impl_component_fallback_provider!(Asset3DVisualizer => []);
+impl TypedComponentFallbackProvider for Asset3DVisualizer {
+ fn fallback_for(&self, ctx: &QueryContext<'_>) -> OutOfTreeTransform {
+ fallback_for_out_of_tree_transform(ctx).into()
+ }
+}
+
+re_viewer_context::impl_component_fallback_provider!(Asset3DVisualizer => [OutOfTreeTransform]);
diff --git a/crates/viewer/re_space_view_spatial/src/visualizers/cameras.rs b/crates/viewer/re_space_view_spatial/src/visualizers/cameras.rs
index 1075f0f3ee43..fef514f071f8 100644
--- a/crates/viewer/re_space_view_spatial/src/visualizers/cameras.rs
+++ b/crates/viewer/re_space_view_spatial/src/visualizers/cameras.rs
@@ -3,7 +3,7 @@ use re_log_types::Instance;
use re_renderer::renderer::LineStripFlags;
use re_types::{
archetypes::Pinhole,
- components::{ImagePlaneDistance, Transform3D, ViewCoordinates},
+ components::{ImagePlaneDistance, ViewCoordinates},
};
use re_viewer_context::{
ApplicableEntities, DataResult, IdentifiedViewSystem, QueryContext, SpaceViewOutlineMasks,
@@ -52,7 +52,6 @@ impl CamerasVisualizer {
transforms: &TransformContext,
data_result: &DataResult,
pinhole: &Pinhole,
- transform_at_entity: Option,
pinhole_view_coordinates: ViewCoordinates,
entity_highlight: &SpaceViewOutlineMasks,
) {
@@ -74,42 +73,26 @@ impl CamerasVisualizer {
return;
}
- // We need special handling to find the 3D transform for drawing the
- // frustum itself. The transform that would otherwise be in the
- // transform context might include both a rigid transform and a pinhole. This
- // makes sense, since if there's an image logged here one would expect
- // both the rigid and the pinhole to apply, but here we're only interested
- // in the rigid transform at this entity path, excluding the pinhole
- // portion (we handle the pinhole separately later).
- let world_from_camera_rigid = {
- // Start with the transform to the entity parent, if it exists
- let world_from_parent = ent_path
- .parent()
- .and_then(|parent_path| transforms.reference_from_entity(&parent_path))
- .unwrap_or(glam::Affine3A::IDENTITY);
-
- // Then combine it with the transform at the entity itself, if there is one.
- if let Some(transform_at_entity) = transform_at_entity {
- world_from_parent * transform_at_entity.into_parent_from_child_transform()
- } else {
- world_from_parent
- }
+ // The camera transform does not include the pinhole transform.
+ let Some(transform_info) = transforms.transform_info_for_entity(ent_path) else {
+ return;
};
+ let world_from_camera = transform_info.reference_from_entity_ignoring_3d_from_2d_pinhole;
// If this transform is not representable as an `IsoTransform` we can't display it yet.
// This would happen if the camera is under another camera or under a transform with non-uniform scale.
- let Some(world_from_camera_rigid_iso) =
- re_math::IsoTransform::from_mat4(&world_from_camera_rigid.into())
+ let Some(world_from_camera_iso) =
+ re_math::IsoTransform::from_mat4(&world_from_camera.into())
else {
return;
};
- debug_assert!(world_from_camera_rigid_iso.is_finite());
+ debug_assert!(world_from_camera_iso.is_finite());
self.space_cameras.push(SpaceCamera3D {
ent_path: ent_path.clone(),
pinhole_view_coordinates,
- world_from_camera: world_from_camera_rigid_iso,
+ world_from_camera: world_from_camera_iso,
pinhole: Some(pinhole.clone()),
picture_plane_distance: frustum_length,
});
@@ -163,8 +146,7 @@ impl CamerasVisualizer {
// The frustum is setup as a RDF frustum, but if the view coordinates are not RDF,
// we need to reorient the displayed frustum so that we indicate the correct orientation in the 3D world space.
.world_from_obj(
- world_from_camera_rigid
- * glam::Affine3A::from_mat3(pinhole_view_coordinates.from_rdf()),
+ world_from_camera * glam::Affine3A::from_mat3(pinhole_view_coordinates.from_rdf()),
)
.outline_mask_ids(entity_highlight.overall)
.picking_object_id(instance_layer_id.object);
@@ -183,7 +165,7 @@ impl CamerasVisualizer {
self.data.add_bounding_box_from_points(
ent_path.hash(),
std::iter::once(glam::Vec3::ZERO),
- world_from_camera_rigid,
+ world_from_camera,
);
}
}
@@ -232,10 +214,6 @@ impl VisualizerSystem for CamerasVisualizer {
transforms,
data_result,
&pinhole,
- // TODO(#5607): what should happen if the promise is still pending?
- ctx.recording()
- .latest_at_component::(&data_result.entity_path, &time_query)
- .map(|c| c.value),
pinhole.camera_xyz.unwrap_or(ViewCoordinates::RDF), // TODO(#2641): This should come from archetype
entity_highlight,
);
diff --git a/crates/viewer/re_space_view_spatial/src/visualizers/depth_images.rs b/crates/viewer/re_space_view_spatial/src/visualizers/depth_images.rs
index ceae1b8e1254..f9e0ccb59a82 100644
--- a/crates/viewer/re_space_view_spatial/src/visualizers/depth_images.rs
+++ b/crates/viewer/re_space_view_spatial/src/visualizers/depth_images.rs
@@ -17,7 +17,7 @@ use re_viewer_context::{
};
use crate::{
- contexts::{SpatialSceneEntityContext, TransformContext},
+ contexts::SpatialSceneEntityContext,
query_pinhole_legacy,
view_kind::SpatialSpaceViewKind,
visualizers::{filter_visualizable_2d_entities, SIZE_BOOST_IN_POINTS_FOR_POINT_OUTLINES},
@@ -53,7 +53,6 @@ impl DepthImageVisualizer {
&mut self,
ctx: &QueryContext<'_>,
depth_clouds: &mut Vec,
- transforms: &TransformContext,
ent_context: &SpatialSceneEntityContext<'_>,
images: impl Iterator- ,
) {
@@ -76,7 +75,7 @@ impl DepthImageVisualizer {
image.colormap = Some(image.colormap.unwrap_or_else(|| self.fallback_for(ctx)));
if is_3d_view {
- if let Some(parent_pinhole_path) = transforms.parent_pinhole(entity_path) {
+ if let Some(parent_pinhole_path) = &ent_context.transform_info.parent_pinhole {
let fill_ratio = fill_ratio.unwrap_or_default();
// NOTE: we don't pass in `world_from_obj` because this corresponds to the
@@ -84,7 +83,6 @@ impl DepthImageVisualizer {
// What we want are the extrinsics of the depth camera!
match Self::process_entity_view_as_depth_cloud(
ctx,
- transforms,
ent_context,
&image,
entity_path,
@@ -142,10 +140,8 @@ impl DepthImageVisualizer {
}
}
- #[allow(clippy::too_many_arguments)]
fn process_entity_view_as_depth_cloud(
ctx: &QueryContext<'_>,
- transforms: &TransformContext,
ent_context: &SpatialSceneEntityContext<'_>,
image: &ImageInfo,
ent_path: &EntityPath,
@@ -162,14 +158,9 @@ impl DepthImageVisualizer {
};
// Place the cloud at the pinhole's location. Note that this means we ignore any 2D transforms that might be there.
- let world_from_view = transforms.reference_from_entity_ignoring_pinhole(
- parent_pinhole_path,
- ctx.recording(),
- ctx.query,
- );
- let Some(world_from_view) = world_from_view else {
- anyhow::bail!("Couldn't fetch pinhole extrinsics at {parent_pinhole_path:?}");
- };
+ let world_from_view = ent_context
+ .transform_info
+ .reference_from_entity_ignoring_3d_from_2d_pinhole;
let world_from_rdf = world_from_view
* glam::Affine3A::from_mat3(
intrinsics
@@ -255,7 +246,6 @@ impl VisualizerSystem for DepthImageVisualizer {
};
let mut depth_clouds = Vec::new();
- let transforms = context_systems.get::
()?;
super::entity_iterator::process_archetype::(
ctx,
@@ -314,13 +304,7 @@ impl VisualizerSystem for DepthImageVisualizer {
},
);
- self.process_depth_image_data(
- ctx,
- &mut depth_clouds,
- transforms,
- spatial_ctx,
- &mut data,
- );
+ self.process_depth_image_data(ctx, &mut depth_clouds, spatial_ctx, &mut data);
Ok(())
},
diff --git a/crates/viewer/re_space_view_spatial/src/visualizers/transform3d_arrows.rs b/crates/viewer/re_space_view_spatial/src/visualizers/transform3d_arrows.rs
index e25cc399ec18..da408d4b5e8c 100644
--- a/crates/viewer/re_space_view_spatial/src/visualizers/transform3d_arrows.rs
+++ b/crates/viewer/re_space_view_spatial/src/visualizers/transform3d_arrows.rs
@@ -70,13 +70,12 @@ impl VisualizerSystem for Transform3DArrowsVisualizer {
for data_result in query.iter_visible_data_results(ctx, Self::identifier()) {
// Use transform without potential pinhole, since we don't want to visualize image-space coordinates.
- let Some(world_from_obj) = transforms.reference_from_entity_ignoring_pinhole(
- &data_result.entity_path,
- ctx.recording(),
- &latest_at_query,
- ) else {
+ let Some(transform_info) =
+ transforms.transform_info_for_entity(&data_result.entity_path)
+ else {
continue;
};
+ let world_from_obj = transform_info.reference_from_entity_ignoring_3d_from_2d_pinhole;
let results = data_result
.latest_at_with_blueprint_resolved_data::(ctx, &latest_at_query);
diff --git a/crates/viewer/re_space_view_spatial/src/visualizers/utilities/entity_iterator.rs b/crates/viewer/re_space_view_spatial/src/visualizers/utilities/entity_iterator.rs
index facb42d77ebf..554650ca6fc7 100644
--- a/crates/viewer/re_space_view_spatial/src/visualizers/utilities/entity_iterator.rs
+++ b/crates/viewer/re_space_view_spatial/src/visualizers/utilities/entity_iterator.rs
@@ -160,24 +160,23 @@ where
let system_identifier = System::identifier();
for data_result in query.iter_visible_data_results(ctx, system_identifier) {
- // The transform that considers pinholes only makes sense if this is a 3D space-view
- let world_from_entity =
- if view_ctx.space_view_class_identifier() == SpatialSpaceView3D::identifier() {
- transforms.reference_from_entity(&data_result.entity_path)
- } else {
- transforms.reference_from_entity_ignoring_pinhole(
- &data_result.entity_path,
- ctx.recording(),
- &latest_at,
- )
- };
-
- let Some(world_from_entity) = world_from_entity else {
+ let Some(transform_info) = transforms.transform_info_for_entity(&data_result.entity_path)
+ else {
continue;
};
+
+ // The transform that considers pinholes only makes sense if this is a 3D space-view
+ let world_from_entity = transform_info.reference_from_entity;
+ if view_ctx.space_view_class_identifier() == SpatialSpaceView3D::identifier() {
+ transform_info.reference_from_entity
+ } else {
+ transform_info.reference_from_entity_ignoring_3d_from_2d_pinhole
+ };
+
let depth_offset_key = (system_identifier, data_result.entity_path.hash());
let entity_context = SpatialSceneEntityContext {
world_from_entity,
+ transform_info,
depth_offset: depth_offsets
.per_entity_and_visualizer
.get(&depth_offset_key)
diff --git a/crates/viewer/re_viewer/src/reflection/mod.rs b/crates/viewer/re_viewer/src/reflection/mod.rs
index 4c0a6860fdda..eeda1862a3a3 100644
--- a/crates/viewer/re_viewer/src/reflection/mod.rs
+++ b/crates/viewer/re_viewer/src/reflection/mod.rs
@@ -441,10 +441,10 @@ fn generate_component_reflection() -> Result::name(),
+ ::name(),
ComponentReflection {
- docstring_md: "An out-of-tree affine transform between two 3D spaces, represented in a given direction.\n\n\"Out-of-tree\" means that the transform only affects its own entity: children don't inherit from it.",
- placeholder: Some(OutOfTreeTransform3D::default().to_arrow()?),
+ docstring_md: "If out of tree transform is enabled, a transform does not participate in the transform hierarchy.\n\nThis means transforms on this entity do not affect children.\nIt will however, still be affected by transforms on its parents.\n\nThis is automatically enabled if any of the transform components are present multiple times.\nSetting this to false for a transform that has multiple instances of the same transform component,\nwill result in an error.",
+ placeholder: Some(OutOfTreeTransform::default().to_arrow()?),
},
),
(
@@ -658,7 +658,7 @@ fn generate_archetype_reflection() -> ArchetypeReflectionMap {
ArchetypeName::new("rerun.archetypes.Transform3D"),
ArchetypeReflection {
display_name: "Transform 3D",
- docstring_md: "A transform between two 3D spaces, i.e. a pose.\n\nFrom the point of view of the entity's coordinate system,\nall components are applied in the inverse order they are listed here.\nE.g. if both a translation and a max3x3 transform are present,\nthe 3x3 matrix is applied first, followed by the translation.\n\nEach transform component can be listed multiple times, but transform tree propagation is only possible\nif there's only one instance for each transform component.\nTODO(#6831): write more about the exact interaction with the to be written `OutOfTreeTransform` component.\n\n## Examples\n\n### Variety of 3D transforms\n```ignore\nuse std::f32::consts::TAU;\n\nfn main() -> Result<(), Box> {\n let rec = rerun::RecordingStreamBuilder::new(\"rerun_example_transform3d\").spawn()?;\n\n let arrow = rerun::Arrows3D::from_vectors([(0.0, 1.0, 0.0)]).with_origins([(0.0, 0.0, 0.0)]);\n\n rec.log(\"base\", &arrow)?;\n\n rec.log(\n \"base/translated\",\n &rerun::Transform3D::from_translation([1.0, 0.0, 0.0]),\n )?;\n\n rec.log(\"base/translated\", &arrow)?;\n\n rec.log(\n \"base/rotated_scaled\",\n &rerun::Transform3D::from_rotation_scale(\n rerun::RotationAxisAngle::new([0.0, 0.0, 1.0], rerun::Angle::from_radians(TAU / 8.0)),\n rerun::Scale3D::from(2.0),\n ),\n )?;\n\n rec.log(\"base/rotated_scaled\", &arrow)?;\n\n Ok(())\n}\n```\n\n\n \n \n \n \n \n \n \n\n### Transform hierarchy\n```ignore\nfn main() -> Result<(), Box> {\n let rec = rerun::RecordingStreamBuilder::new(\"rerun_example_transform3d_hierarchy\").spawn()?;\n\n // TODO(#5521): log two space views as in the python example\n\n rec.set_time_seconds(\"sim_time\", 0.0);\n\n // Planetary motion is typically in the XY plane.\n rec.log_static(\"/\", &rerun::ViewCoordinates::RIGHT_HAND_Z_UP)?;\n\n // Setup points, all are in the center of their own space:\n rec.log(\n \"sun\",\n &rerun::Points3D::new([[0.0, 0.0, 0.0]])\n .with_radii([1.0])\n .with_colors([rerun::Color::from_rgb(255, 200, 10)]),\n )?;\n rec.log(\n \"sun/planet\",\n &rerun::Points3D::new([[0.0, 0.0, 0.0]])\n .with_radii([0.4])\n .with_colors([rerun::Color::from_rgb(40, 80, 200)]),\n )?;\n rec.log(\n \"sun/planet/moon\",\n &rerun::Points3D::new([[0.0, 0.0, 0.0]])\n .with_radii([0.15])\n .with_colors([rerun::Color::from_rgb(180, 180, 180)]),\n )?;\n\n // Draw fixed paths where the planet & moon move.\n let d_planet = 6.0;\n let d_moon = 3.0;\n let angles = (0..=100).map(|i| i as f32 * 0.01 * std::f32::consts::TAU);\n let circle: Vec<_> = angles.map(|angle| [angle.sin(), angle.cos()]).collect();\n rec.log(\n \"sun/planet_path\",\n &rerun::LineStrips3D::new([rerun::LineStrip3D::from_iter(\n circle\n .iter()\n .map(|p| [p[0] * d_planet, p[1] * d_planet, 0.0]),\n )]),\n )?;\n rec.log(\n \"sun/planet/moon_path\",\n &rerun::LineStrips3D::new([rerun::LineStrip3D::from_iter(\n circle.iter().map(|p| [p[0] * d_moon, p[1] * d_moon, 0.0]),\n )]),\n )?;\n\n // Movement via transforms.\n for i in 0..(6 * 120) {\n let time = i as f32 / 120.0;\n rec.set_time_seconds(\"sim_time\", time);\n let r_moon = time * 5.0;\n let r_planet = time * 2.0;\n\n rec.log(\n \"sun/planet\",\n &rerun::Transform3D::from_translation_rotation(\n [r_planet.sin() * d_planet, r_planet.cos() * d_planet, 0.0],\n rerun::RotationAxisAngle {\n axis: [1.0, 0.0, 0.0].into(),\n angle: rerun::Angle::from_degrees(20.0),\n },\n ),\n )?;\n rec.log(\n \"sun/planet/moon\",\n &rerun::Transform3D::from_translation([\n r_moon.cos() * d_moon,\n r_moon.sin() * d_moon,\n 0.0,\n ])\n .with_relation(rerun::TransformRelation::ChildFromParent),\n )?;\n }\n\n Ok(())\n}\n```\n\n\n \n \n \n \n \n \n ",
+ docstring_md: "A transform between two 3D spaces, i.e. a pose.\n\nFrom the point of view of the entity's coordinate system,\nall components are applied in the inverse order they are listed here.\nE.g. if both a translation and a max3x3 transform are present,\nthe 3x3 matrix is applied first, followed by the translation.\n\nEach transform component can be listed multiple times, but transform tree propagation is only possible\nif there's only one instance for each transform component.\nHowever, support for arrays of transform components depends on the visualizer/archetype\nand many only support a single instance of each transform component.\nTo force disabling transform propagation (\"out of tree transformation\"), use the [`components.OutOfTreeTransform`](https://rerun.io/docs/reference/types/components/out_of_tree_transform?speculative-link) component.\n\n## Examples\n\n### Variety of 3D transforms\n```ignore\nuse std::f32::consts::TAU;\n\nfn main() -> Result<(), Box> {\n let rec = rerun::RecordingStreamBuilder::new(\"rerun_example_transform3d\").spawn()?;\n\n let arrow = rerun::Arrows3D::from_vectors([(0.0, 1.0, 0.0)]).with_origins([(0.0, 0.0, 0.0)]);\n\n rec.log(\"base\", &arrow)?;\n\n rec.log(\n \"base/translated\",\n &rerun::Transform3D::from_translation([1.0, 0.0, 0.0]),\n )?;\n\n rec.log(\"base/translated\", &arrow)?;\n\n rec.log(\n \"base/rotated_scaled\",\n &rerun::Transform3D::from_rotation_scale(\n rerun::RotationAxisAngle::new([0.0, 0.0, 1.0], rerun::Angle::from_radians(TAU / 8.0)),\n rerun::Scale3D::from(2.0),\n ),\n )?;\n\n rec.log(\"base/rotated_scaled\", &arrow)?;\n\n Ok(())\n}\n```\n\n\n \n \n \n \n \n \n \n\n### Transform hierarchy\n```ignore\nfn main() -> Result<(), Box> {\n let rec = rerun::RecordingStreamBuilder::new(\"rerun_example_transform3d_hierarchy\").spawn()?;\n\n // TODO(#5521): log two space views as in the python example\n\n rec.set_time_seconds(\"sim_time\", 0.0);\n\n // Planetary motion is typically in the XY plane.\n rec.log_static(\"/\", &rerun::ViewCoordinates::RIGHT_HAND_Z_UP)?;\n\n // Setup points, all are in the center of their own space:\n rec.log(\n \"sun\",\n &rerun::Points3D::new([[0.0, 0.0, 0.0]])\n .with_radii([1.0])\n .with_colors([rerun::Color::from_rgb(255, 200, 10)]),\n )?;\n rec.log(\n \"sun/planet\",\n &rerun::Points3D::new([[0.0, 0.0, 0.0]])\n .with_radii([0.4])\n .with_colors([rerun::Color::from_rgb(40, 80, 200)]),\n )?;\n rec.log(\n \"sun/planet/moon\",\n &rerun::Points3D::new([[0.0, 0.0, 0.0]])\n .with_radii([0.15])\n .with_colors([rerun::Color::from_rgb(180, 180, 180)]),\n )?;\n\n // Draw fixed paths where the planet & moon move.\n let d_planet = 6.0;\n let d_moon = 3.0;\n let angles = (0..=100).map(|i| i as f32 * 0.01 * std::f32::consts::TAU);\n let circle: Vec<_> = angles.map(|angle| [angle.sin(), angle.cos()]).collect();\n rec.log(\n \"sun/planet_path\",\n &rerun::LineStrips3D::new([rerun::LineStrip3D::from_iter(\n circle\n .iter()\n .map(|p| [p[0] * d_planet, p[1] * d_planet, 0.0]),\n )]),\n )?;\n rec.log(\n \"sun/planet/moon_path\",\n &rerun::LineStrips3D::new([rerun::LineStrip3D::from_iter(\n circle.iter().map(|p| [p[0] * d_moon, p[1] * d_moon, 0.0]),\n )]),\n )?;\n\n // Movement via transforms.\n for i in 0..(6 * 120) {\n let time = i as f32 / 120.0;\n rec.set_time_seconds(\"sim_time\", time);\n let r_moon = time * 5.0;\n let r_planet = time * 2.0;\n\n rec.log(\n \"sun/planet\",\n &rerun::Transform3D::from_translation_rotation(\n [r_planet.sin() * d_planet, r_planet.cos() * d_planet, 0.0],\n rerun::RotationAxisAngle {\n axis: [1.0, 0.0, 0.0].into(),\n angle: rerun::Angle::from_degrees(20.0),\n },\n ),\n )?;\n rec.log(\n \"sun/planet/moon\",\n &rerun::Transform3D::from_translation([\n r_moon.cos() * d_moon,\n r_moon.sin() * d_moon,\n 0.0,\n ])\n .with_relation(rerun::TransformRelation::ChildFromParent),\n )?;\n }\n\n Ok(())\n}\n```\n\n\n \n \n \n \n \n \n ",
fields: vec) is no longer
+represented by a component wrapping `Transform3D`, but as a separate [`OutOfTreeTransform`](https://rerun.io/docs/reference/types/components/out_of_tree_transform#speculative-link)
+component which merely stores whether transforms logged at this entity path should affect their children.
+If `OutOfTreeTransform` is enabled, any transform component will only affect the entity itself, not its children.
+If arrays of transform components are logged on a single entity, `OutOfTreeTransform` will be enabled by default, otherwise it is disabled by default.
+⚠️ This means it is no longer possible to have different transforms in-tree and out-of-tree on the same entity. If you want to do this, you need to log the transforms on separate entities.
#### Python
The `Transform3D` archetype no longer has a `transform` argument. Use one of the other arguments instead.
-TODO(andreas): Not true as of writing. but should be true at the time or release!
Before:
```python
@@ -84,11 +87,16 @@ rr.log("myentity", rr.Transform3D(translation=Vec3D([1, 2, 3]), relation=rr.Tran
```
-TODO(andreas): code example
-
-
-TODO(andreas): Talk about OutOfTreeTransform
-TODO(andreas): … and Asset3D specifically
+Out of tree transforms are now regular transforms plus an `OutOfTreeTransform` component:
+Before:
+```python
+rr.log("world/asset", rr.Asset3D(path=asset_path, transform=translation))
+```
+After:
+```python
+rr.log("world/asset", rr.Asset3D(path=asset_path, out_of_tree_transform=True))
+rr.log("world/asset", rr.Transform3D(translation=translation))
+```
#### C++
@@ -119,18 +127,27 @@ an empty archetype instead that you can populate (e.g. `rerun::Transform3D().wit
Scale is no longer an enum datatype but a component with a 3D vec:
Before:
-```rust
-let scale_uniform = rerun::Scale3D::Uniform(2.0);
-let scale_y = rerun::Scale3D::ThreeD([1.0, 2.0, 1.0]);
+```cpp
+auto scale_uniform = rerun::Scale3D::Uniform(2.0);
+auto scale_y = rerun::Scale3D::ThreeD([1.0, 2.0, 1.0]);
```
After:
-```rust
-let scale_uniform = rerun::Scale3D::uniform(2.0);
-let scale_y = rerun::Scale3D::from([1.0, 2.0, 1.0]);
+```cpp
+auto scale_uniform = rerun::Scale3D::uniform(2.0f);
+auto scale_y = rerun::Scale3D(1.0f, 2.0f, 1.0f);
+```
+
+Out of tree transforms are now regular transforms plus an `OutOfTreeTransform` component:
+Before:
+```cpp
+rec.log("world/asset", rerun::Asset3D::from_file(path).value_or_throw().with_transform(translation));
+```
+After:
+```cpp
+rec.log("world/asset", rerun::Asset3D::from_file(path).value_or_throw().with_out_of_tree_transform(true));
+rec.log("world/asset", rerun::Transform3D::from_translation(translation));
```
-TODO(andreas): Talk about OutOfTreeTransform
-TODO(andreas): … and Asset3D specifically
#### Rust
`rerun::archetypes::Transform3D` no longer has a `new`, use other factory methods instead, e.g. `from_translation_rotation_scale` or `from_mat3x3`
@@ -152,7 +169,7 @@ impl From for rerun::components::Transform3D {
fn from(transform: GltfTransform) -> Self {
rerun::components::Transform3D::from_translation_rotation_scale(
transform.t,
- rerun::datatypes::Quaternion::from_xyzw(transform.r),
+ rerun::Quaternion::from_xyzw(transform.r),
transform.s,
)
}
@@ -164,13 +181,12 @@ impl From for rerun::Transform3D {
fn from(transform: GltfTransform) -> Self {
rerun::Transform3D::from_translation_rotation_scale(
transform.t,
- rerun::datatypes::Quaternion::from_xyzw(transform.r),
+ rerun::Quaternion::from_xyzw(transform.r),
transform.s,
)
}
}
```
-TODO(andreas): Quaternion in above snippet is likely to change as well.
Since all aspects of the transform archetypes are now granular, they can be chained with `with_` functions:
```rust
@@ -181,5 +197,16 @@ Note that the order of the method calls does _not_ affect the order in which tra
`rerun::Transform3D::IDENTITY` has been removed, sue `rerun::Transform3D::default()` to start out with
an empty archetype instead that you can populate (e.g. `rerun::Transform3D::default().with_mat3x3(rerun::datatypes::Mat3x3::IDENTITY)`).
-TODO(andreas): Talk about OutOfTreeTransform
-TODO(andreas): … and Asset3D specifically
+
+Out of tree transforms are now regular transforms plus an `OutOfTreeTransform` component:
+Before:
+```rust
+rec.log("world/asset", &rerun::Asset3D::from_file(path)?
+ .with_transform(rerun::OutOfTreeTransform3D::from_translation(translation))
+)?;
+```
+After:
+```rust
+rec.log("world/asset", &rerun::Asset3D::from_file(path)?.with_out_of_tree_transform(true))?;
+rec.log("world/asset", &rerun::Transform3D::from_translation(translation))?;
+```
diff --git a/docs/content/reference/types/archetypes/asset3d.md b/docs/content/reference/types/archetypes/asset3d.md
index 880727f1cfd1..dd7cd355c999 100644
--- a/docs/content/reference/types/archetypes/asset3d.md
+++ b/docs/content/reference/types/archetypes/asset3d.md
@@ -13,7 +13,7 @@ See also [`archetypes.Mesh3D`](https://rerun.io/docs/reference/types/archetypes/
**Recommended**: [`MediaType`](../components/media_type.md)
-**Optional**: [`OutOfTreeTransform3D`](../components/out_of_tree_transform3d.md)
+**Optional**: [`OutOfTreeTransform`](../components/out_of_tree_transform.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 196cf8a60b62..edad8d372d5f 100644
--- a/docs/content/reference/types/archetypes/ellipsoids.md
+++ b/docs/content/reference/types/archetypes/ellipsoids.md
@@ -7,7 +7,7 @@ title: "Ellipsoids"
This archetype is for ellipsoids or spheres whose size is a key part of the data
(e.g. a bounding sphere).
-For points whose radii are for the sake of visualization, use `Points3D` instead.
+For points whose radii are for the sake of visualization, use [`archetypes.Points3D`](https://rerun.io/docs/reference/types/archetypes/points3d) instead.
Currently, ellipsoids are always rendered as wireframes.
Opaque and transparent rendering will be supported later.
diff --git a/docs/content/reference/types/archetypes/transform3d.md b/docs/content/reference/types/archetypes/transform3d.md
index 73a40c593262..97b709eb9483 100644
--- a/docs/content/reference/types/archetypes/transform3d.md
+++ b/docs/content/reference/types/archetypes/transform3d.md
@@ -12,7 +12,9 @@ the 3x3 matrix is applied first, followed by the translation.
Each transform component can be listed multiple times, but transform tree propagation is only possible
if there's only one instance for each transform component.
-TODO(#6831): write more about the exact interaction with the to be written `OutOfTreeTransform` component.
+However, support for arrays of transform components depends on the visualizer/archetype
+and many only support a single instance of each transform component.
+To force disabling transform propagation ("out of tree transformation"), use the [`components.OutOfTreeTransform`](https://rerun.io/docs/reference/types/components/out_of_tree_transform?speculative-link) component.
## Components
diff --git a/docs/content/reference/types/components.md b/docs/content/reference/types/components.md
index c7e5090e2211..005ebfd7f486 100644
--- a/docs/content/reference/types/components.md
+++ b/docs/content/reference/types/components.md
@@ -42,7 +42,7 @@ on [Entities and Components](../../concepts/entity-component.md).
* [`MediaType`](components/media_type.md): A standardized media type (RFC2046, formerly known as MIME types), encoded as a string.
* [`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).
-* [`OutOfTreeTransform3D`](components/out_of_tree_transform3d.md): An out-of-tree affine transform between two 3D spaces, represented in a given direction.
+* [`OutOfTreeTransform`](components/out_of_tree_transform.md): If out of tree transform is enabled, a transform does not participate in the transform hierarchy.
* [`PinholeProjection`](components/pinhole_projection.md): Camera projection, from image coordinates to view coordinates.
* [`Position2D`](components/position2d.md): A position in 2D space.
* [`Position3D`](components/position3d.md): A position in 3D space.
diff --git a/docs/content/reference/types/components/.gitattributes b/docs/content/reference/types/components/.gitattributes
index a54341eb5dbf..dedd18b6816f 100644
--- a/docs/content/reference/types/components/.gitattributes
+++ b/docs/content/reference/types/components/.gitattributes
@@ -30,7 +30,7 @@ marker_size.md linguist-generated=true
media_type.md linguist-generated=true
name.md linguist-generated=true
opacity.md linguist-generated=true
-out_of_tree_transform3d.md linguist-generated=true
+out_of_tree_transform.md linguist-generated=true
pinhole_projection.md linguist-generated=true
position2d.md linguist-generated=true
position3d.md linguist-generated=true
diff --git a/docs/content/reference/types/components/out_of_tree_transform.md b/docs/content/reference/types/components/out_of_tree_transform.md
new file mode 100644
index 000000000000..0920a038ad2c
--- /dev/null
+++ b/docs/content/reference/types/components/out_of_tree_transform.md
@@ -0,0 +1,27 @@
+---
+title: "OutOfTreeTransform"
+---
+
+
+If out of tree transform is enabled, a transform does not participate in the transform hierarchy.
+
+This means transforms on this entity do not affect children.
+It will however, still be affected by transforms on its parents.
+
+This is automatically enabled if any of the transform components are present multiple times.
+Setting this to false for a transform that has multiple instances of the same transform component,
+will result in an error.
+
+## Fields
+
+* enabled: [`Bool`](../datatypes/bool.md)
+
+## API reference links
+ * 🌊 [C++ API docs for `OutOfTreeTransform`](https://ref.rerun.io/docs/cpp/stable/structrerun_1_1components_1_1OutOfTreeTransform.html?speculative-link)
+ * 🐍 [Python API docs for `OutOfTreeTransform`](https://ref.rerun.io/docs/python/stable/common/components?speculative-link#rerun.components.OutOfTreeTransform)
+ * 🦀 [Rust API docs for `OutOfTreeTransform`](https://docs.rs/rerun/latest/rerun/components/struct.OutOfTreeTransform.html?speculative-link)
+
+
+## Used by
+
+* [`Asset3D`](../archetypes/asset3d.md)
diff --git a/docs/content/reference/types/components/out_of_tree_transform3d.md b/docs/content/reference/types/components/out_of_tree_transform3d.md
deleted file mode 100644
index ca8ec12b7174..000000000000
--- a/docs/content/reference/types/components/out_of_tree_transform3d.md
+++ /dev/null
@@ -1,22 +0,0 @@
----
-title: "OutOfTreeTransform3D"
----
-
-
-An out-of-tree affine transform between two 3D spaces, represented in a given direction.
-
-"Out-of-tree" means that the transform only affects its own entity: children don't inherit from it.
-
-## Fields
-
-* repr: [`Transform3D`](../datatypes/transform3d.md)
-
-## API reference links
- * 🌊 [C++ API docs for `OutOfTreeTransform3D`](https://ref.rerun.io/docs/cpp/stable/structrerun_1_1components_1_1OutOfTreeTransform3D.html)
- * 🐍 [Python API docs for `OutOfTreeTransform3D`](https://ref.rerun.io/docs/python/stable/common/components#rerun.components.OutOfTreeTransform3D)
- * 🦀 [Rust API docs for `OutOfTreeTransform3D`](https://docs.rs/rerun/latest/rerun/components/struct.OutOfTreeTransform3D.html)
-
-
-## Used by
-
-* [`Asset3D`](../archetypes/asset3d.md)
diff --git a/docs/content/reference/types/datatypes/bool.md b/docs/content/reference/types/datatypes/bool.md
index c1edda7aa2a3..2bf776aee0ee 100644
--- a/docs/content/reference/types/datatypes/bool.md
+++ b/docs/content/reference/types/datatypes/bool.md
@@ -19,3 +19,4 @@ A single boolean.
* [`ClearIsRecursive`](../components/clear_is_recursive.md)
* [`DisconnectedSpace`](../components/disconnected_space.md)
+* [`OutOfTreeTransform`](../components/out_of_tree_transform.md?speculative-link)
diff --git a/docs/content/reference/types/datatypes/transform3d.md b/docs/content/reference/types/datatypes/transform3d.md
index 970ccd641ce2..443132c49da3 100644
--- a/docs/content/reference/types/datatypes/transform3d.md
+++ b/docs/content/reference/types/datatypes/transform3d.md
@@ -17,5 +17,4 @@ Representation of a 3D affine transform.
## Used by
-* [`OutOfTreeTransform3D`](../components/out_of_tree_transform3d.md)
* [`Transform3D`](../components/transform3d.md)
diff --git a/docs/snippets/all/archetypes/asset3d_out_of_tree.cpp b/docs/snippets/all/archetypes/asset3d_out_of_tree.cpp
index 4158d9364e8e..81d1f7cae42d 100644
--- a/docs/snippets/all/archetypes/asset3d_out_of_tree.cpp
+++ b/docs/snippets/all/archetypes/asset3d_out_of_tree.cpp
@@ -18,7 +18,10 @@ int main(int argc, char** argv) {
rec.log_static("world", rerun::ViewCoordinates::RIGHT_HAND_Z_UP); // Set an up-axis
rec.set_time_sequence("frame", 0);
- rec.log("world/asset", rerun::Asset3D::from_file(path).value_or_throw());
+ rec.log(
+ "world/asset",
+ rerun::Asset3D::from_file(path).value_or_throw().with_out_of_tree_transform(true)
+ );
// Those points will not be affected by their parent's out-of-tree transform!
rec.log(
"world/asset/points",
@@ -29,11 +32,9 @@ int main(int argc, char** argv) {
rec.set_time_sequence("frame", i);
// Modify the asset's out-of-tree transform: this will not affect its children (i.e. the points)!
- const auto translation =
- rerun::TranslationRotationScale3D({0.0, 0.0, static_cast(i) - 10.0f});
rec.log(
"world/asset",
- rerun::Collection(rerun::OutOfTreeTransform3D(translation))
+ rerun::Transform3D::from_translation({0.0, 0.0, static_cast(i) - 10.0f})
);
}
}
diff --git a/docs/snippets/all/archetypes/asset3d_out_of_tree.py b/docs/snippets/all/archetypes/asset3d_out_of_tree.py
index 35dd272dfcb6..48d75d5fb54b 100644
--- a/docs/snippets/all/archetypes/asset3d_out_of_tree.py
+++ b/docs/snippets/all/archetypes/asset3d_out_of_tree.py
@@ -4,8 +4,6 @@
import numpy as np
import rerun as rr
-from rerun.components import OutOfTreeTransform3DBatch
-from rerun.datatypes import TranslationRotationScale3D
if len(sys.argv) < 2:
print(f"Usage: {sys.argv[0]} ")
@@ -16,7 +14,7 @@
rr.log("world", rr.ViewCoordinates.RIGHT_HAND_Z_UP, static=True) # Set an up-axis
rr.set_time_sequence("frame", 0)
-rr.log("world/asset", rr.Asset3D(path=sys.argv[1]))
+rr.log("world/asset", rr.Asset3D(path=sys.argv[1], out_of_tree_transform=True))
# Those points will not be affected by their parent's out-of-tree transform!
rr.log(
"world/asset/points",
@@ -27,5 +25,4 @@
for i in range(1, 20):
rr.set_time_sequence("frame", i)
- translation = TranslationRotationScale3D(translation=[0, 0, i - 10.0])
- rr.log_components("world/asset", [OutOfTreeTransform3DBatch(translation)])
+ rr.log("world/asset", rr.Transform3D(translation=[0, 0, i - 10.0]))
diff --git a/docs/snippets/all/archetypes/asset3d_out_of_tree.rs b/docs/snippets/all/archetypes/asset3d_out_of_tree.rs
index 8673eeb4a73d..51726cb65cc4 100644
--- a/docs/snippets/all/archetypes/asset3d_out_of_tree.rs
+++ b/docs/snippets/all/archetypes/asset3d_out_of_tree.rs
@@ -16,7 +16,10 @@ fn main() -> anyhow::Result<()> {
rec.log_static("world", &rerun::ViewCoordinates::RIGHT_HAND_Z_UP)?; // Set an up-axis
rec.set_time_sequence("frame", 0);
- rec.log("world/asset", &rerun::Asset3D::from_file(path)?)?;
+ rec.log(
+ "world/asset",
+ &rerun::Asset3D::from_file(path)?.with_out_of_tree_transform(true),
+ )?;
// Those points will not be affected by their parent's out-of-tree transform!
rec.log(
"world/asset/points",
@@ -27,12 +30,9 @@ fn main() -> anyhow::Result<()> {
rec.set_time_sequence("frame", i);
// Modify the asset's out-of-tree transform: this will not affect its children (i.e. the points)!
- let translation =
- rerun::TranslationRotationScale3D::from_translation([0.0, 0.0, i as f32 - 10.0]);
- rec.log_component_batches(
+ rec.log(
"world/asset",
- false,
- [&rerun::OutOfTreeTransform3D::from(translation) as _],
+ &rerun::Transform3D::from_translation([0.0, 0.0, i as f32 - 10.0]),
)?;
}
diff --git a/examples/python/signed_distance_fields/signed_distance_fields/__main__.py b/examples/python/signed_distance_fields/signed_distance_fields/__main__.py
index 108ab74e3b75..17adac1a5357 100755
--- a/examples/python/signed_distance_fields/signed_distance_fields/__main__.py
+++ b/examples/python/signed_distance_fields/signed_distance_fields/__main__.py
@@ -89,11 +89,8 @@ def log_mesh(path: Path, mesh: Trimesh) -> None:
scale = bs2.scale / bs1.scale
center = bs2.center - bs1.center * scale
- mesh3d = rr.Asset3D(path=path)
- mesh3d.transform = rr.OutOfTreeTransform3DBatch(
- rr.datatypes.TranslationRotationScale3D(translation=center, scale=scale)
- )
- rr.log("world/mesh", mesh3d)
+ rr.log("world/mesh", rr.Asset3D(path=path))
+ rr.log("world/mesh", rr.Transform3D(translation=center, scale=scale))
def log_sampled_sdf(points: npt.NDArray[np.float32], sdf: npt.NDArray[np.float32]) -> None:
diff --git a/rerun_cpp/src/rerun.hpp b/rerun_cpp/src/rerun.hpp
index 261d1c7ef43e..5a82929634e9 100644
--- a/rerun_cpp/src/rerun.hpp
+++ b/rerun_cpp/src/rerun.hpp
@@ -38,7 +38,6 @@ namespace rerun {
using components::LineStrip2D;
using components::LineStrip3D;
using components::MediaType;
- using components::OutOfTreeTransform3D;
using components::Position2D;
using components::Position3D;
using components::Radius;
diff --git a/rerun_cpp/src/rerun/archetypes/asset3d.cpp b/rerun_cpp/src/rerun/archetypes/asset3d.cpp
index 0a02b9aec381..fe08a849cb2b 100644
--- a/rerun_cpp/src/rerun/archetypes/asset3d.cpp
+++ b/rerun_cpp/src/rerun/archetypes/asset3d.cpp
@@ -26,8 +26,8 @@ namespace rerun {
RR_RETURN_NOT_OK(result.error);
cells.push_back(std::move(result.value));
}
- if (archetype.transform.has_value()) {
- auto result = DataCell::from_loggable(archetype.transform.value());
+ if (archetype.out_of_tree_transform.has_value()) {
+ auto result = DataCell::from_loggable(archetype.out_of_tree_transform.value());
RR_RETURN_NOT_OK(result.error);
cells.push_back(std::move(result.value));
}
diff --git a/rerun_cpp/src/rerun/archetypes/asset3d.hpp b/rerun_cpp/src/rerun/archetypes/asset3d.hpp
index 22131f95f6e9..5f2da8108128 100644
--- a/rerun_cpp/src/rerun/archetypes/asset3d.hpp
+++ b/rerun_cpp/src/rerun/archetypes/asset3d.hpp
@@ -7,7 +7,7 @@
#include "../compiler_utils.hpp"
#include "../components/blob.hpp"
#include "../components/media_type.hpp"
-#include "../components/out_of_tree_transform3d.hpp"
+#include "../components/out_of_tree_transform.hpp"
#include "../data_cell.hpp"
#include "../indicator_component.hpp"
#include "../result.hpp"
@@ -66,10 +66,10 @@ namespace rerun::archetypes {
/// If it cannot guess, it won't be able to render the asset.
std::optional media_type;
- /// An out-of-tree transform.
+ /// If enabled, any transform (components part of the `archetypes::Transform3D` archetype) on this entity will not affect its children.
///
- /// Applies a transformation to the asset itself without impacting its children.
- std::optional transform;
+ /// It will however, still be affected by transforms on its parents.
+ std::optional out_of_tree_transform;
public:
static constexpr const char IndicatorComponentName[] = "rerun.components.Asset3DIndicator";
@@ -128,11 +128,13 @@ namespace rerun::archetypes {
RR_WITH_MAYBE_UNINITIALIZED_DISABLED(return std::move(*this);)
}
- /// An out-of-tree transform.
+ /// If enabled, any transform (components part of the `archetypes::Transform3D` archetype) on this entity will not affect its children.
///
- /// Applies a transformation to the asset itself without impacting its children.
- Asset3D with_transform(rerun::components::OutOfTreeTransform3D _transform) && {
- transform = std::move(_transform);
+ /// It will however, still be affected by transforms on its parents.
+ Asset3D with_out_of_tree_transform(
+ rerun::components::OutOfTreeTransform _out_of_tree_transform
+ ) && {
+ out_of_tree_transform = std::move(_out_of_tree_transform);
// See: https://github.com/rerun-io/rerun/issues/4027
RR_WITH_MAYBE_UNINITIALIZED_DISABLED(return std::move(*this);)
}
diff --git a/rerun_cpp/src/rerun/archetypes/ellipsoids.hpp b/rerun_cpp/src/rerun/archetypes/ellipsoids.hpp
index 6451374d5a3d..ca97934ff798 100644
--- a/rerun_cpp/src/rerun/archetypes/ellipsoids.hpp
+++ b/rerun_cpp/src/rerun/archetypes/ellipsoids.hpp
@@ -27,7 +27,7 @@ namespace rerun::archetypes {
///
/// This archetype is for ellipsoids or spheres whose size is a key part of the data
/// (e.g. a bounding sphere).
- /// For points whose radii are for the sake of visualization, use `Points3D` instead.
+ /// For points whose radii are for the sake of visualization, use `archetypes::Points3D` instead.
///
/// Currently, ellipsoids are always rendered as wireframes.
/// Opaque and transparent rendering will be supported later.
@@ -59,7 +59,7 @@ namespace rerun::archetypes {
/// Optional text labels for the ellipsoids.
std::optional> labels;
- /// Optional `ClassId`s for the ellipsoids.
+ /// Optional `components::ClassId`s for the ellipsoids.
///
/// The class ID provides colors and labels if not specified explicitly.
std::optional> class_ids;
@@ -155,7 +155,7 @@ namespace rerun::archetypes {
RR_WITH_MAYBE_UNINITIALIZED_DISABLED(return std::move(*this);)
}
- /// Optional `ClassId`s for the ellipsoids.
+ /// Optional `components::ClassId`s for the ellipsoids.
///
/// The class ID provides colors and labels if not specified explicitly.
Ellipsoids with_class_ids(Collection _class_ids) && {
diff --git a/rerun_cpp/src/rerun/archetypes/transform3d.hpp b/rerun_cpp/src/rerun/archetypes/transform3d.hpp
index 7cb518cd8cb9..aea90a1c81b9 100644
--- a/rerun_cpp/src/rerun/archetypes/transform3d.hpp
+++ b/rerun_cpp/src/rerun/archetypes/transform3d.hpp
@@ -33,7 +33,9 @@ namespace rerun::archetypes {
///
/// Each transform component can be listed multiple times, but transform tree propagation is only possible
/// if there's only one instance for each transform component.
- /// TODO(#6831): write more about the exact interaction with the to be written `OutOfTreeTransform` component.
+ /// However, support for arrays of transform components depends on the visualizer/archetype
+ /// and many only support a single instance of each transform component.
+ /// To force disabling transform propagation ("out of tree transformation"), use the `components::OutOfTreeTransform` component.
///
/// ## Examples
///
diff --git a/rerun_cpp/src/rerun/components.hpp b/rerun_cpp/src/rerun/components.hpp
index fd92e24cdec3..86083cffe588 100644
--- a/rerun_cpp/src/rerun/components.hpp
+++ b/rerun_cpp/src/rerun/components.hpp
@@ -31,7 +31,7 @@
#include "components/media_type.hpp"
#include "components/name.hpp"
#include "components/opacity.hpp"
-#include "components/out_of_tree_transform3d.hpp"
+#include "components/out_of_tree_transform.hpp"
#include "components/pinhole_projection.hpp"
#include "components/position2d.hpp"
#include "components/position3d.hpp"
diff --git a/rerun_cpp/src/rerun/components/.gitattributes b/rerun_cpp/src/rerun/components/.gitattributes
index 3b125ebb1e59..5768075edbfa 100644
--- a/rerun_cpp/src/rerun/components/.gitattributes
+++ b/rerun_cpp/src/rerun/components/.gitattributes
@@ -40,7 +40,7 @@ marker_size.hpp linguist-generated=true
media_type.hpp linguist-generated=true
name.hpp linguist-generated=true
opacity.hpp linguist-generated=true
-out_of_tree_transform3d.hpp linguist-generated=true
+out_of_tree_transform.hpp linguist-generated=true
pinhole_projection.hpp linguist-generated=true
position2d.hpp linguist-generated=true
position3d.hpp linguist-generated=true
diff --git a/rerun_cpp/src/rerun/components/out_of_tree_transform.hpp b/rerun_cpp/src/rerun/components/out_of_tree_transform.hpp
new file mode 100644
index 000000000000..659013e619a1
--- /dev/null
+++ b/rerun_cpp/src/rerun/components/out_of_tree_transform.hpp
@@ -0,0 +1,69 @@
+// 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/out_of_tree_transform.fbs".
+
+#pragma once
+
+#include "../datatypes/bool.hpp"
+#include "../result.hpp"
+
+#include
+#include
+
+namespace rerun::components {
+ /// **Component**: If out of tree transform is enabled, a transform does not participate in the transform hierarchy.
+ ///
+ /// This means transforms on this entity do not affect children.
+ /// It will however, still be affected by transforms on its parents.
+ ///
+ /// This is automatically enabled if any of the transform components are present multiple times.
+ /// Setting this to false for a transform that has multiple instances of the same transform component,
+ /// will result in an error.
+ struct OutOfTreeTransform {
+ /// Whether the out of tree transform mode is enabled.
+ rerun::datatypes::Bool enabled;
+
+ public:
+ OutOfTreeTransform() = default;
+
+ OutOfTreeTransform(rerun::datatypes::Bool enabled_) : enabled(enabled_) {}
+
+ OutOfTreeTransform& operator=(rerun::datatypes::Bool enabled_) {
+ enabled = enabled_;
+ return *this;
+ }
+
+ OutOfTreeTransform(bool value_) : enabled(value_) {}
+
+ OutOfTreeTransform& operator=(bool value_) {
+ enabled = value_;
+ return *this;
+ }
+
+ /// Cast to the underlying Bool datatype
+ operator rerun::datatypes::Bool() const {
+ return enabled;
+ }
+ };
+} // namespace rerun::components
+
+namespace rerun {
+ static_assert(sizeof(rerun::datatypes::Bool) == sizeof(components::OutOfTreeTransform));
+
+ /// \private
+ template <>
+ struct Loggable {
+ static constexpr const char Name[] = "rerun.components.OutOfTreeTransform";
+
+ /// Returns the arrow data type this type corresponds to.
+ static const std::shared_ptr& arrow_datatype() {
+ return Loggable::arrow_datatype();
+ }
+
+ /// Serializes an array of `rerun::components::OutOfTreeTransform` into an arrow array.
+ static Result> to_arrow(
+ const components::OutOfTreeTransform* instances, size_t num_instances
+ ) {
+ return Loggable::to_arrow(&instances->enabled, num_instances);
+ }
+ };
+} // namespace rerun
diff --git a/rerun_cpp/src/rerun/components/out_of_tree_transform3d.hpp b/rerun_cpp/src/rerun/components/out_of_tree_transform3d.hpp
deleted file mode 100644
index 131fb9dfe56a..000000000000
--- a/rerun_cpp/src/rerun/components/out_of_tree_transform3d.hpp
+++ /dev/null
@@ -1,73 +0,0 @@
-// 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/out_of_tree_transform3d.fbs".
-
-#pragma once
-
-#include "../datatypes/transform3d.hpp"
-#include "../datatypes/translation_rotation_scale3d.hpp"
-#include "../result.hpp"
-
-#include
-#include
-
-namespace rerun::components {
- /// **Component**: An out-of-tree affine transform between two 3D spaces, represented in a given direction.
- ///
- /// "Out-of-tree" means that the transform only affects its own entity: children don't inherit from it.
- struct OutOfTreeTransform3D {
- /// Representation of the transform.
- rerun::datatypes::Transform3D repr;
-
- public:
- OutOfTreeTransform3D() = default;
-
- OutOfTreeTransform3D(rerun::datatypes::Transform3D repr_) : repr(repr_) {}
-
- OutOfTreeTransform3D& operator=(rerun::datatypes::Transform3D repr_) {
- repr = repr_;
- return *this;
- }
-
- OutOfTreeTransform3D(rerun::datatypes::TranslationRotationScale3D TranslationRotationScale_)
- : repr(TranslationRotationScale_) {}
-
- OutOfTreeTransform3D& operator=(
- rerun::datatypes::TranslationRotationScale3D TranslationRotationScale_
- ) {
- repr = TranslationRotationScale_;
- return *this;
- }
-
- /// Cast to the underlying Transform3D datatype
- operator rerun::datatypes::Transform3D() const {
- return repr;
- }
- };
-} // namespace rerun::components
-
-namespace rerun {
- static_assert(
- sizeof(rerun::datatypes::Transform3D) == sizeof(components::OutOfTreeTransform3D)
- );
-
- /// \private
- template <>
- struct Loggable {
- static constexpr const char Name[] = "rerun.components.OutOfTreeTransform3D";
-
- /// Returns the arrow data type this type corresponds to.
- static const std::shared_ptr& arrow_datatype() {
- return Loggable::arrow_datatype();
- }
-
- /// Serializes an array of `rerun::components::OutOfTreeTransform3D` into an arrow array.
- static Result> to_arrow(
- const components::OutOfTreeTransform3D* instances, size_t num_instances
- ) {
- return Loggable::to_arrow(
- &instances->repr,
- num_instances
- );
- }
- };
-} // namespace rerun
diff --git a/rerun_cpp/src/rerun/components/transform_relation.hpp b/rerun_cpp/src/rerun/components/transform_relation.hpp
index 1a76de63a617..87ceb3e9b731 100644
--- a/rerun_cpp/src/rerun/components/transform_relation.hpp
+++ b/rerun_cpp/src/rerun/components/transform_relation.hpp
@@ -20,14 +20,14 @@ namespace rerun::components {
/// The transform describes how to transform into the parent entity's space.
///
- /// E.g. a translation of (0, 1, 0) with this `TransformRelation` logged at `parent/child` means
+ /// E.g. a translation of (0, 1, 0) with this `components::TransformRelation` logged at `parent/child` means
/// that from the point of view of `parent`, `parent/child` is translated 1 unit along `parent`'s Y axis.
/// From perspective of `parent/child`, the `parent` entity is translated -1 unit along `parent/child`'s Y axis.
ParentFromChild = 1,
/// The transform describes how to transform into the child entity's space.
///
- /// E.g. a translation of (0, 1, 0) with this `TransformRelation` logged at `parent/child` means
+ /// E.g. a translation of (0, 1, 0) with this `components::TransformRelation` logged at `parent/child` means
/// that from the point of view of `parent`, `parent/child` is translated -1 unit along `parent`'s Y axis.
/// From perspective of `parent/child`, the `parent` entity is translated 1 unit along `parent/child`'s Y axis.
ChildFromParent = 2,
diff --git a/rerun_py/rerun_sdk/rerun/__init__.py b/rerun_py/rerun_sdk/rerun/__init__.py
index 906fc4db983c..2a63264cc313 100644
--- a/rerun_py/rerun_sdk/rerun/__init__.py
+++ b/rerun_py/rerun_sdk/rerun/__init__.py
@@ -76,8 +76,6 @@
from .components import (
AlbedoFactor as AlbedoFactor,
MediaType as MediaType,
- OutOfTreeTransform3D as OutOfTreeTransform3D,
- OutOfTreeTransform3DBatch as OutOfTreeTransform3DBatch,
Radius as Radius,
Scale3D as Scale3D,
TensorDimensionIndexSelection as TensorDimensionIndexSelection,
diff --git a/rerun_py/rerun_sdk/rerun/archetypes/asset3d.py b/rerun_py/rerun_sdk/rerun/archetypes/asset3d.py
index abe22e53df63..562bd378737a 100644
--- a/rerun_py/rerun_sdk/rerun/archetypes/asset3d.py
+++ b/rerun_py/rerun_sdk/rerun/archetypes/asset3d.py
@@ -59,7 +59,7 @@ def __attrs_clear__(self) -> None:
self.__attrs_init__(
blob=None, # type: ignore[arg-type]
media_type=None, # type: ignore[arg-type]
- transform=None, # type: ignore[arg-type]
+ out_of_tree_transform=None, # type: ignore[arg-type]
)
@classmethod
@@ -95,14 +95,14 @@ def _clear(cls) -> Asset3D:
#
# (Docstring intentionally commented out to hide this field from the docs)
- transform: components.OutOfTreeTransform3DBatch | None = field(
+ out_of_tree_transform: components.OutOfTreeTransformBatch | None = field(
metadata={"component": "optional"},
default=None,
- converter=components.OutOfTreeTransform3DBatch._optional, # type: ignore[misc]
+ converter=components.OutOfTreeTransformBatch._optional, # type: ignore[misc]
)
- # An out-of-tree transform.
+ # If enabled, any transform (components part of the [`archetypes.Transform3D`][rerun.archetypes.Transform3D] archetype) on this entity will not affect its children.
#
- # Applies a transformation to the asset itself without impacting its children.
+ # It will however, still be affected by transforms on its parents.
#
# (Docstring intentionally commented out to hide this field from the docs)
diff --git a/rerun_py/rerun_sdk/rerun/archetypes/asset3d_ext.py b/rerun_py/rerun_sdk/rerun/archetypes/asset3d_ext.py
index 92e7882d510d..7558ffcd0458 100644
--- a/rerun_py/rerun_sdk/rerun/archetypes/asset3d_ext.py
+++ b/rerun_py/rerun_sdk/rerun/archetypes/asset3d_ext.py
@@ -35,7 +35,7 @@ def __init__(
path: str | pathlib.Path | None = None,
contents: datatypes.BlobLike | None = None,
media_type: datatypes.Utf8Like | None = None,
- transform: datatypes.Transform3DLike | None = None,
+ out_of_tree_transform: datatypes.BoolLike | None = None,
):
"""
Create a new instance of the Asset3D archetype.
@@ -63,10 +63,11 @@ def __init__(
or the viewer will try to guess from the contents (magic header).
If the media type cannot be guessed, the viewer won't be able to render the asset.
- transform:
- An out-of-tree transform.
+ out_of_tree_transform:
+ If enabled, any transform (components part of the [`archetypes.Transform3D`][rerun.archetypes.Transform3D]
+ on this entity will not affect its children.
- Applies a transformation to the asset itself without impacting its children.
+ It will however, still be affected by transforms on its parents.
"""
@@ -81,7 +82,7 @@ def __init__(
if media_type is None:
media_type = guess_media_type(str(path))
- self.__attrs_init__(blob=blob, media_type=media_type, transform=transform)
+ self.__attrs_init__(blob=blob, media_type=media_type, out_of_tree_transform=out_of_tree_transform)
return
self.__attrs_clear__()
diff --git a/rerun_py/rerun_sdk/rerun/archetypes/ellipsoids.py b/rerun_py/rerun_sdk/rerun/archetypes/ellipsoids.py
index 953c72fa7838..a1957435307f 100644
--- a/rerun_py/rerun_sdk/rerun/archetypes/ellipsoids.py
+++ b/rerun_py/rerun_sdk/rerun/archetypes/ellipsoids.py
@@ -23,7 +23,7 @@ class Ellipsoids(EllipsoidsExt, Archetype):
This archetype is for ellipsoids or spheres whose size is a key part of the data
(e.g. a bounding sphere).
- For points whose radii are for the sake of visualization, use `Points3D` instead.
+ For points whose radii are for the sake of visualization, use [`archetypes.Points3D`][rerun.archetypes.Points3D] instead.
Currently, ellipsoids are always rendered as wireframes.
Opaque and transparent rendering will be supported later.
@@ -124,7 +124,7 @@ def _clear(cls) -> Ellipsoids:
default=None,
converter=components.ClassIdBatch._optional, # type: ignore[misc]
)
- # Optional `ClassId`s for the ellipsoids.
+ # Optional [`components.ClassId`][rerun.components.ClassId]s for the ellipsoids.
#
# The class ID provides colors and labels if not specified explicitly.
#
diff --git a/rerun_py/rerun_sdk/rerun/archetypes/transform3d.py b/rerun_py/rerun_sdk/rerun/archetypes/transform3d.py
index b6041e852f8c..9d4e2b43ea1c 100644
--- a/rerun_py/rerun_sdk/rerun/archetypes/transform3d.py
+++ b/rerun_py/rerun_sdk/rerun/archetypes/transform3d.py
@@ -28,7 +28,9 @@ class Transform3D(Transform3DExt, Archetype):
Each transform component can be listed multiple times, but transform tree propagation is only possible
if there's only one instance for each transform component.
- TODO(#6831): write more about the exact interaction with the to be written `OutOfTreeTransform` component.
+ However, support for arrays of transform components depends on the visualizer/archetype
+ and many only support a single instance of each transform component.
+ To force disabling transform propagation ("out of tree transformation"), use the [`components.OutOfTreeTransform`][rerun.components.OutOfTreeTransform] component.
Examples
--------
diff --git a/rerun_py/rerun_sdk/rerun/components/.gitattributes b/rerun_py/rerun_sdk/rerun/components/.gitattributes
index 97a701b83707..d17306be7082 100644
--- a/rerun_py/rerun_sdk/rerun/components/.gitattributes
+++ b/rerun_py/rerun_sdk/rerun/components/.gitattributes
@@ -31,7 +31,7 @@ marker_size.py linguist-generated=true
media_type.py linguist-generated=true
name.py linguist-generated=true
opacity.py linguist-generated=true
-out_of_tree_transform3d.py linguist-generated=true
+out_of_tree_transform.py linguist-generated=true
pinhole_projection.py linguist-generated=true
position2d.py linguist-generated=true
position3d.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 85d82926cc28..639bbc808f66 100644
--- a/rerun_py/rerun_sdk/rerun/components/__init__.py
+++ b/rerun_py/rerun_sdk/rerun/components/__init__.py
@@ -55,7 +55,7 @@
from .media_type import MediaType, MediaTypeBatch, MediaTypeType
from .name import Name, NameBatch, NameType
from .opacity import Opacity, OpacityBatch, OpacityType
-from .out_of_tree_transform3d import OutOfTreeTransform3D, OutOfTreeTransform3DBatch, OutOfTreeTransform3DType
+from .out_of_tree_transform import OutOfTreeTransform, OutOfTreeTransformBatch, OutOfTreeTransformType
from .pinhole_projection import PinholeProjection, PinholeProjectionBatch, PinholeProjectionType
from .position2d import Position2D, Position2DBatch, Position2DType
from .position3d import Position3D, Position3DBatch, Position3DType
@@ -203,9 +203,9 @@
"Opacity",
"OpacityBatch",
"OpacityType",
- "OutOfTreeTransform3D",
- "OutOfTreeTransform3DBatch",
- "OutOfTreeTransform3DType",
+ "OutOfTreeTransform",
+ "OutOfTreeTransformBatch",
+ "OutOfTreeTransformType",
"PinholeProjection",
"PinholeProjectionBatch",
"PinholeProjectionType",
diff --git a/rerun_py/rerun_sdk/rerun/components/out_of_tree_transform.py b/rerun_py/rerun_sdk/rerun/components/out_of_tree_transform.py
new file mode 100644
index 000000000000..f7dd562a455e
--- /dev/null
+++ b/rerun_py/rerun_sdk/rerun/components/out_of_tree_transform.py
@@ -0,0 +1,45 @@
+# 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/out_of_tree_transform.fbs".
+
+# You can extend this class by creating a "OutOfTreeTransformExt" class in "out_of_tree_transform_ext.py".
+
+from __future__ import annotations
+
+from .. import datatypes
+from .._baseclasses import (
+ ComponentBatchMixin,
+ ComponentMixin,
+)
+
+__all__ = ["OutOfTreeTransform", "OutOfTreeTransformBatch", "OutOfTreeTransformType"]
+
+
+class OutOfTreeTransform(datatypes.Bool, ComponentMixin):
+ """
+ **Component**: If out of tree transform is enabled, a transform does not participate in the transform hierarchy.
+
+ This means transforms on this entity do not affect children.
+ It will however, still be affected by transforms on its parents.
+
+ This is automatically enabled if any of the transform components are present multiple times.
+ Setting this to false for a transform that has multiple instances of the same transform component,
+ will result in an error.
+ """
+
+ _BATCH_TYPE = None
+ # You can define your own __init__ function as a member of OutOfTreeTransformExt in out_of_tree_transform_ext.py
+
+ # Note: there are no fields here because OutOfTreeTransform delegates to datatypes.Bool
+ pass
+
+
+class OutOfTreeTransformType(datatypes.BoolType):
+ _TYPE_NAME: str = "rerun.components.OutOfTreeTransform"
+
+
+class OutOfTreeTransformBatch(datatypes.BoolBatch, ComponentBatchMixin):
+ _ARROW_TYPE = OutOfTreeTransformType()
+
+
+# This is patched in late to avoid circular dependencies.
+OutOfTreeTransform._BATCH_TYPE = OutOfTreeTransformBatch # type: ignore[assignment]
diff --git a/rerun_py/rerun_sdk/rerun/components/out_of_tree_transform3d.py b/rerun_py/rerun_sdk/rerun/components/out_of_tree_transform3d.py
deleted file mode 100644
index 26de7e765666..000000000000
--- a/rerun_py/rerun_sdk/rerun/components/out_of_tree_transform3d.py
+++ /dev/null
@@ -1,40 +0,0 @@
-# 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/out_of_tree_transform3d.fbs".
-
-# You can extend this class by creating a "OutOfTreeTransform3DExt" class in "out_of_tree_transform3d_ext.py".
-
-from __future__ import annotations
-
-from .. import datatypes
-from .._baseclasses import (
- ComponentBatchMixin,
- ComponentMixin,
-)
-
-__all__ = ["OutOfTreeTransform3D", "OutOfTreeTransform3DBatch", "OutOfTreeTransform3DType"]
-
-
-class OutOfTreeTransform3D(datatypes.Transform3D, ComponentMixin):
- """
- **Component**: An out-of-tree affine transform between two 3D spaces, represented in a given direction.
-
- "Out-of-tree" means that the transform only affects its own entity: children don't inherit from it.
- """
-
- _BATCH_TYPE = None
- # You can define your own __init__ function as a member of OutOfTreeTransform3DExt in out_of_tree_transform3d_ext.py
-
- # Note: there are no fields here because OutOfTreeTransform3D delegates to datatypes.Transform3D
- pass
-
-
-class OutOfTreeTransform3DType(datatypes.Transform3DType):
- _TYPE_NAME: str = "rerun.components.OutOfTreeTransform3D"
-
-
-class OutOfTreeTransform3DBatch(datatypes.Transform3DBatch, ComponentBatchMixin):
- _ARROW_TYPE = OutOfTreeTransform3DType()
-
-
-# This is patched in late to avoid circular dependencies.
-OutOfTreeTransform3D._BATCH_TYPE = OutOfTreeTransform3DBatch # type: ignore[assignment]
diff --git a/rerun_py/rerun_sdk/rerun/components/transform_relation.py b/rerun_py/rerun_sdk/rerun/components/transform_relation.py
index 71ec11127c6c..e9bead75f441 100644
--- a/rerun_py/rerun_sdk/rerun/components/transform_relation.py
+++ b/rerun_py/rerun_sdk/rerun/components/transform_relation.py
@@ -34,7 +34,7 @@ class TransformRelation(Enum):
"""
The transform describes how to transform into the parent entity's space.
- E.g. a translation of (0, 1, 0) with this `TransformRelation` logged at `parent/child` means
+ E.g. a translation of (0, 1, 0) with this [`components.TransformRelation`][rerun.components.TransformRelation] logged at `parent/child` means
that from the point of view of `parent`, `parent/child` is translated 1 unit along `parent`'s Y axis.
From perspective of `parent/child`, the `parent` entity is translated -1 unit along `parent/child`'s Y axis.
"""
@@ -43,7 +43,7 @@ class TransformRelation(Enum):
"""
The transform describes how to transform into the child entity's space.
- E.g. a translation of (0, 1, 0) with this `TransformRelation` logged at `parent/child` means
+ E.g. a translation of (0, 1, 0) with this [`components.TransformRelation`][rerun.components.TransformRelation] logged at `parent/child` means
that from the point of view of `parent`, `parent/child` is translated -1 unit along `parent`'s Y axis.
From perspective of `parent/child`, the `parent` entity is translated 1 unit along `parent/child`'s Y axis.
"""
diff --git a/rerun_py/tests/unit/test_asset3d.py b/rerun_py/tests/unit/test_asset3d.py
index 24ed86b448a2..a1b0457d5c34 100644
--- a/rerun_py/tests/unit/test_asset3d.py
+++ b/rerun_py/tests/unit/test_asset3d.py
@@ -26,16 +26,4 @@ def test_asset3d() -> None:
for asset in assets:
assert asset.blob.as_arrow_array() == rr.components.BlobBatch(blob_comp).as_arrow_array()
assert asset.media_type == rr.components.MediaTypeBatch(rr.components.MediaType.GLB)
- assert asset.transform is None
-
-
-def test_asset3d_transform() -> None:
- asset = rr.Asset3D(path=CUBE_FILEPATH, transform=rr.datatypes.TranslationRotationScale3D(translation=[1, 2, 3]))
-
- assert asset.transform is not None
- assert (
- asset.transform.as_arrow_array()
- == rr.components.OutOfTreeTransform3DBatch(
- rr.datatypes.TranslationRotationScale3D(translation=[1, 2, 3])
- ).as_arrow_array()
- )
+ assert asset.out_of_tree_transform is None