Skip to main content

fbx_dom/objects/
mod.rs

1//! Typed FBX object wrappers aligned with Assimp’s [`FBXDocument`](https://github.com/assimp/assimp/blob/master/code/AssetLib/FBX/FBXDocument.h)
2//! and [`LazyObject::Get`](https://github.com/assimp/assimp/blob/master/code/AssetLib/FBX/FBXDocument.cpp)-style dispatch.
3//!
4//! ## Dispatch (`fbx_object_tag` / [`ClassifiedFbxObject`])
5//!
6//! - **`type_name`** is the FBX object class family (`Geometry`, `Model`, `Material`, …).
7//! - **`class_name`** narrows within the family (`Mesh`, `Camera`, `Skin`, …).
8//! - Materials, textures, video, and animation types are keyed by **`type_name` only** so varied
9//!   SDK `class_name` strings still classify.
10//! - Unknown `Geometry` / `NodeAttribute` / `Deformer` classes become explicit `Unknown*` variants
11//!   instead of the generic [`ClassifiedFbxObject::Unknown`].
12//!
13//! Each concrete type is a newtype over [`crate::OwnedObject`] (or a small struct) with
14//! [`TryFrom<OwnedObject>`] for narrowing. Prefer [`ClassifiedFbxObject::try_from`] when the kind is
15//! not known upfront.
16
17mod animation_curve;
18mod animation_curve_node;
19mod animation_layer;
20mod animation_stack;
21mod blend_shape;
22mod blend_shape_channel;
23mod camera;
24mod camera_switcher;
25mod cluster;
26mod extract;
27mod global_settings;
28mod layered_texture;
29mod light;
30mod limb_node;
31mod line_geometry;
32mod material;
33mod mesh_geometry;
34mod model;
35mod null_node;
36mod shape_geometry;
37mod skin;
38mod texture;
39mod video;
40
41pub use animation_curve::AnimationCurve;
42pub use animation_curve_node::AnimationCurveNode;
43pub use animation_layer::AnimationLayer;
44pub use animation_stack::AnimationStack;
45pub use blend_shape::BlendShape;
46pub use blend_shape_channel::BlendShapeChannel;
47pub use camera::Camera;
48pub use camera_switcher::CameraSwitcher;
49pub use cluster::Cluster;
50pub use extract::AttrExtractor;
51pub use extract::AttrExtractorExt;
52pub use extract::AttrExtractorParseExt;
53pub use global_settings::OwnedGlobalSettings;
54pub use layered_texture::LayeredTexture;
55pub use light::Light;
56pub use light::LightDecay;
57pub use light::LightType;
58pub use limb_node::LimbNode;
59pub use line_geometry::LineGeometry;
60pub use material::Material;
61pub use mesh_geometry::MeshGeometry;
62pub use model::Model;
63pub use model::ModelRotationOrder;
64pub use model::ModelTransformInheritance;
65pub use null_node::NullNode;
66pub use shape_geometry::ShapeGeometry;
67pub use skin::Skin;
68pub use texture::Texture;
69pub use video::Video;
70
71use crate::OwnedObject;
72
73/// Borrowed polymorphic NodeAttribute reference (replacement for inheritance-style base).
74#[derive(Clone, Copy, Debug, PartialEq)]
75pub enum NodeAttributeRef<'a> {
76    Camera(&'a Camera),
77    CameraSwitcher(&'a CameraSwitcher),
78    Light(&'a Light),
79    NullNode(&'a NullNode),
80    LimbNode(&'a LimbNode),
81    Unknown(&'a OwnedObject),
82}
83
84impl<'a> NodeAttributeRef<'a> {
85    pub fn inner(self) -> &'a OwnedObject {
86        match self {
87            NodeAttributeRef::Camera(x) => x.inner(),
88            NodeAttributeRef::CameraSwitcher(x) => x.inner(),
89            NodeAttributeRef::Light(x) => x.inner(),
90            NodeAttributeRef::NullNode(x) => x.inner(),
91            NodeAttributeRef::LimbNode(x) => x.inner(),
92            NodeAttributeRef::Unknown(x) => x,
93        }
94    }
95}
96
97/// Borrowed polymorphic `Geometry` reference for incoming `Geometry -> Model` `OO` links.
98#[derive(Clone, Copy, Debug, PartialEq)]
99pub enum ModelGeometryRef<'a> {
100    Mesh(&'a MeshGeometry),
101    Line(&'a LineGeometry),
102    Shape(&'a ShapeGeometry),
103    Unknown(&'a OwnedObject),
104}
105
106impl<'a> ModelGeometryRef<'a> {
107    pub fn inner(self) -> &'a OwnedObject {
108        match self {
109            ModelGeometryRef::Mesh(x) => x.inner(),
110            ModelGeometryRef::Line(x) => x.inner(),
111            ModelGeometryRef::Shape(x) => x.inner(),
112            ModelGeometryRef::Unknown(x) => x,
113        }
114    }
115}
116
117// --- `type_name` / `class_name` pairs (Assimp `LazyObject::Get` dispatch) -----------------------
118
119pub const MODEL_TYPE_NAME: &str = "Model";
120pub const MODEL_IK_EFFECTOR_CLASS_NAME: &str = "IKEffector";
121pub const MODEL_FK_EFFECTOR_CLASS_NAME: &str = "FKEffector";
122
123pub const GEOMETRY_TYPE_NAME: &str = "Geometry";
124pub const GEOMETRY_MESH_CLASS_NAME: &str = "Mesh";
125pub const GEOMETRY_LINE_CLASS_NAME: &str = "Line";
126pub const GEOMETRY_SHAPE_CLASS_NAME: &str = "Shape";
127
128pub const NODE_ATTRIBUTE_TYPE_NAME: &str = "NodeAttribute";
129pub const NODE_ATTRIBUTE_CAMERA_CLASS_NAME: &str = "Camera";
130pub const NODE_ATTRIBUTE_CAMERA_SWITCHER_CLASS_NAME: &str = "CameraSwitcher";
131pub const NODE_ATTRIBUTE_LIGHT_CLASS_NAME: &str = "Light";
132pub const NODE_ATTRIBUTE_NULL_CLASS_NAME: &str = "Null";
133pub const NODE_ATTRIBUTE_LIMB_NODE_CLASS_NAME: &str = "LimbNode";
134
135pub const MATERIAL_TYPE_NAME: &str = "Material";
136pub const MATERIAL_CLASS_NAME: &str = "Material";
137
138pub const TEXTURE_TYPE_NAME: &str = "Texture";
139pub const TEXTURE_CLASS_NAME: &str = "Texture";
140
141pub const LAYERED_TEXTURE_TYPE_NAME: &str = "LayeredTexture";
142pub const LAYERED_TEXTURE_CLASS_NAME: &str = "LayeredTexture";
143
144pub const VIDEO_TYPE_NAME: &str = "Video";
145pub const VIDEO_CLASS_NAME: &str = "Video";
146
147pub const DEFORMER_TYPE_NAME: &str = "Deformer";
148pub const DEFORMER_CLUSTER_CLASS_NAME: &str = "Cluster";
149pub const DEFORMER_SKIN_CLASS_NAME: &str = "Skin";
150pub const DEFORMER_BLEND_SHAPE_CLASS_NAME: &str = "BlendShape";
151pub const DEFORMER_BLEND_SHAPE_CHANNEL_CLASS_NAME: &str = "BlendShapeChannel";
152
153pub const ANIMATION_STACK_TYPE_NAME: &str = "AnimationStack";
154pub const ANIMATION_STACK_CLASS_NAME: &str = "AnimationStack";
155
156pub const ANIMATION_LAYER_TYPE_NAME: &str = "AnimationLayer";
157pub const ANIMATION_LAYER_CLASS_NAME: &str = "AnimationLayer";
158
159pub const ANIMATION_CURVE_TYPE_NAME: &str = "AnimationCurve";
160pub const ANIMATION_CURVE_CLASS_NAME: &str = "AnimationCurve";
161
162pub const ANIMATION_CURVE_NODE_TYPE_NAME: &str = "AnimationCurveNode";
163pub const ANIMATION_CURVE_NODE_CLASS_NAME: &str = "AnimationCurveNode";
164
165/// Why [`TryFrom`]`<`[`OwnedObject`]`>` failed for a typed FBX wrapper.
166#[derive(Debug, Clone, PartialEq, Eq)]
167pub enum FbxTryFromReason {
168    /// `(type_name, class_name)` does not match the target wrapper (factory: `FbxTypeMismatch::wrong_object_kind`).
169    WrongObjectKind {
170        expected: String,
171        got_type_name: String,
172        got_class_name: String,
173    },
174    /// A required non-`Properties70` child (FBX element under the object) was missing.
175    MissingAttribute { name: String },
176    /// A child was present but had no usable value or failed to parse.
177    InvalidAttributeFormat { name: String, detail: String },
178}
179
180/// Returned when [`TryFrom`]`<`[`OwnedObject`]`>` fails for a typed FBX wrapper.
181#[derive(Debug, PartialEq)]
182pub struct FbxTypeMismatch {
183    pub object: OwnedObject,
184    pub reason: FbxTryFromReason,
185}
186
187impl FbxTypeMismatch {
188    fn new(o: OwnedObject, reason: FbxTryFromReason) -> FbxTypeMismatch {
189        FbxTypeMismatch { object: o, reason }
190    }
191
192    pub(crate) fn wrong_object_kind(o: OwnedObject, expected: String) -> FbxTypeMismatch {
193        let reason = FbxTryFromReason::WrongObjectKind {
194            expected,
195            got_type_name: o.type_name.clone(),
196            got_class_name: o.class_name.clone(),
197        };
198        FbxTypeMismatch { object: o, reason }
199    }
200}
201
202/// Internal discriminant for [`fbx_object_tag`].
203#[derive(Clone, Copy, Debug, Eq, PartialEq)]
204pub(crate) enum FbxObjectTag {
205    Model,
206    MeshGeometry,
207    LineGeometry,
208    ShapeGeometry,
209    UnknownGeometry,
210    Camera,
211    CameraSwitcher,
212    Light,
213    NullNode,
214    LimbNode,
215    UnknownNodeAttribute,
216    Material,
217    Texture,
218    LayeredTexture,
219    Video,
220    Cluster,
221    Skin,
222    BlendShape,
223    BlendShapeChannel,
224    UnknownDeformer,
225    AnimationStack,
226    AnimationLayer,
227    AnimationCurve,
228    AnimationCurveNode,
229    /// Unsupported `Model` kinds (e.g. IK/FK effectors) or any object not mapped above.
230    Unknown,
231}
232
233/// Map [`OwnedObject::type_name`] and [`OwnedObject::class_name`] to a dispatch tag.
234///
235/// `Material`, `Texture`, `LayeredTexture`, `Video`, and animation objects are keyed by
236/// `type_name` only (SDK exports vary `class_name`). `Geometry` / `NodeAttribute` / `Deformer`
237/// use `class_name` for known Assimp kinds; anything else under those types gets an explicit
238/// `Unknown*` tag instead of falling through to a generic [`FbxObjectTag::Unknown`].
239pub(crate) fn fbx_object_tag(o: &OwnedObject) -> FbxObjectTag {
240    let ty = o.type_name.as_str();
241    let cls = o.class_name.as_str();
242
243    match ty {
244        MODEL_TYPE_NAME => {
245            if cls == MODEL_IK_EFFECTOR_CLASS_NAME || cls == MODEL_FK_EFFECTOR_CLASS_NAME {
246                FbxObjectTag::Unknown
247            } else {
248                FbxObjectTag::Model
249            }
250        }
251
252        GEOMETRY_TYPE_NAME => match cls {
253            GEOMETRY_MESH_CLASS_NAME => FbxObjectTag::MeshGeometry,
254            GEOMETRY_LINE_CLASS_NAME => FbxObjectTag::LineGeometry,
255            GEOMETRY_SHAPE_CLASS_NAME => FbxObjectTag::ShapeGeometry,
256            _ => FbxObjectTag::UnknownGeometry,
257        },
258
259        NODE_ATTRIBUTE_TYPE_NAME => match cls {
260            NODE_ATTRIBUTE_CAMERA_CLASS_NAME => FbxObjectTag::Camera,
261            NODE_ATTRIBUTE_CAMERA_SWITCHER_CLASS_NAME => FbxObjectTag::CameraSwitcher,
262            NODE_ATTRIBUTE_LIGHT_CLASS_NAME => FbxObjectTag::Light,
263            NODE_ATTRIBUTE_NULL_CLASS_NAME => FbxObjectTag::NullNode,
264            NODE_ATTRIBUTE_LIMB_NODE_CLASS_NAME => FbxObjectTag::LimbNode,
265            _ => FbxObjectTag::UnknownNodeAttribute,
266        },
267
268        MATERIAL_TYPE_NAME => FbxObjectTag::Material,
269        TEXTURE_TYPE_NAME => FbxObjectTag::Texture,
270        LAYERED_TEXTURE_TYPE_NAME => FbxObjectTag::LayeredTexture,
271        VIDEO_TYPE_NAME => FbxObjectTag::Video,
272
273        DEFORMER_TYPE_NAME => match cls {
274            DEFORMER_CLUSTER_CLASS_NAME => FbxObjectTag::Cluster,
275            DEFORMER_SKIN_CLASS_NAME => FbxObjectTag::Skin,
276            DEFORMER_BLEND_SHAPE_CLASS_NAME => FbxObjectTag::BlendShape,
277            DEFORMER_BLEND_SHAPE_CHANNEL_CLASS_NAME => FbxObjectTag::BlendShapeChannel,
278            _ => FbxObjectTag::UnknownDeformer,
279        },
280
281        ANIMATION_STACK_TYPE_NAME => FbxObjectTag::AnimationStack,
282        ANIMATION_LAYER_TYPE_NAME => FbxObjectTag::AnimationLayer,
283        ANIMATION_CURVE_TYPE_NAME => FbxObjectTag::AnimationCurve,
284        ANIMATION_CURVE_NODE_TYPE_NAME => FbxObjectTag::AnimationCurveNode,
285
286        _ => FbxObjectTag::Unknown,
287    }
288}
289
290impl TryFrom<OwnedObject> for ClassifiedFbxObject {
291    type Error = FbxTypeMismatch;
292
293    fn try_from(o: OwnedObject) -> Result<Self, Self::Error> {
294        match fbx_object_tag(&o) {
295            FbxObjectTag::Model => Ok(ClassifiedFbxObject::Model(Model::try_from(o)?)),
296            FbxObjectTag::MeshGeometry => Ok(ClassifiedFbxObject::MeshGeometry(
297                MeshGeometry::try_from(o)?,
298            )),
299            FbxObjectTag::LineGeometry => Ok(ClassifiedFbxObject::LineGeometry(
300                LineGeometry::try_from(o)?,
301            )),
302            FbxObjectTag::ShapeGeometry => Ok(ClassifiedFbxObject::ShapeGeometry(
303                ShapeGeometry::try_from(o)?,
304            )),
305            FbxObjectTag::UnknownGeometry => Ok(ClassifiedFbxObject::UnknownGeometry(o)),
306            FbxObjectTag::Camera => Ok(ClassifiedFbxObject::Camera(Camera::try_from(o)?)),
307            FbxObjectTag::CameraSwitcher => Ok(ClassifiedFbxObject::CameraSwitcher(
308                CameraSwitcher::try_from(o)?,
309            )),
310            FbxObjectTag::Light => Ok(ClassifiedFbxObject::Light(Light::try_from(o)?)),
311            FbxObjectTag::NullNode => Ok(ClassifiedFbxObject::NullNode(NullNode::try_from(o)?)),
312            FbxObjectTag::LimbNode => Ok(ClassifiedFbxObject::LimbNode(LimbNode::try_from(o)?)),
313            FbxObjectTag::UnknownNodeAttribute => Ok(ClassifiedFbxObject::UnknownNodeAttribute(o)),
314            FbxObjectTag::Material => Ok(ClassifiedFbxObject::Material(Material::try_from(o)?)),
315            FbxObjectTag::Texture => Ok(ClassifiedFbxObject::Texture(Texture::try_from(o)?)),
316            FbxObjectTag::LayeredTexture => Ok(ClassifiedFbxObject::LayeredTexture(
317                LayeredTexture::try_from(o)?,
318            )),
319            FbxObjectTag::Video => Ok(ClassifiedFbxObject::Video(Video::try_from(o)?)),
320            FbxObjectTag::Cluster => Ok(ClassifiedFbxObject::Cluster(Cluster::try_from(o)?)),
321            FbxObjectTag::Skin => Ok(ClassifiedFbxObject::Skin(Skin::try_from(o)?)),
322            FbxObjectTag::BlendShape => {
323                Ok(ClassifiedFbxObject::BlendShape(BlendShape::try_from(o)?))
324            }
325            FbxObjectTag::BlendShapeChannel => Ok(ClassifiedFbxObject::BlendShapeChannel(
326                BlendShapeChannel::try_from(o)?,
327            )),
328            FbxObjectTag::UnknownDeformer => Ok(ClassifiedFbxObject::UnknownDeformer(o)),
329            FbxObjectTag::AnimationStack => Ok(ClassifiedFbxObject::AnimationStack(
330                AnimationStack::try_from(o)?,
331            )),
332            FbxObjectTag::AnimationLayer => Ok(ClassifiedFbxObject::AnimationLayer(
333                AnimationLayer::try_from(o)?,
334            )),
335            FbxObjectTag::AnimationCurve => Ok(ClassifiedFbxObject::AnimationCurve(
336                AnimationCurve::try_from(o)?,
337            )),
338            FbxObjectTag::AnimationCurveNode => Ok(ClassifiedFbxObject::AnimationCurveNode(
339                AnimationCurveNode::try_from(o)?,
340            )),
341            FbxObjectTag::Unknown => Ok(ClassifiedFbxObject::Unknown(o)),
342        }
343    }
344}
345
346/// Success result of classifying an [`OwnedObject`]: a concrete wrapper or an explicit unknown bucket.
347///
348/// Use [`ClassifiedFbxObject::inner`] to recover the underlying [`OwnedObject`] and connections.
349#[derive(Debug, PartialEq)]
350pub enum ClassifiedFbxObject {
351    Model(Model),
352    MeshGeometry(MeshGeometry),
353    LineGeometry(LineGeometry),
354    ShapeGeometry(ShapeGeometry),
355    Camera(Camera),
356    CameraSwitcher(CameraSwitcher),
357    Light(Light),
358    NullNode(NullNode),
359    LimbNode(LimbNode),
360    Material(Material),
361    Texture(Texture),
362    LayeredTexture(LayeredTexture),
363    Video(Video),
364    Cluster(Cluster),
365    Skin(Skin),
366    BlendShape(BlendShape),
367    BlendShapeChannel(BlendShapeChannel),
368    AnimationStack(AnimationStack),
369    AnimationLayer(AnimationLayer),
370    AnimationCurve(AnimationCurve),
371    AnimationCurveNode(AnimationCurveNode),
372    /// `Deformer` object with unhandled class name.
373    UnknownDeformer(OwnedObject),
374    /// `Geometry` object with unhandled class name.
375    UnknownGeometry(OwnedObject),
376    /// `NodeAttribute` object with unhandled class name.
377    UnknownNodeAttribute(OwnedObject),
378    /// Any `Objects` row not mapped to a known Assimp DOM class pair.
379    Unknown(OwnedObject),
380}
381
382impl ClassifiedFbxObject {
383    pub fn object_index(&self) -> u64 {
384        self.inner().object_index
385    }
386
387    pub fn inner(&self) -> &OwnedObject {
388        match self {
389            ClassifiedFbxObject::Model(x) => x.inner(),
390            ClassifiedFbxObject::MeshGeometry(x) => x.inner(),
391            ClassifiedFbxObject::LineGeometry(x) => x.inner(),
392            ClassifiedFbxObject::ShapeGeometry(x) => x.inner(),
393            ClassifiedFbxObject::Camera(x) => x.inner(),
394            ClassifiedFbxObject::CameraSwitcher(x) => x.inner(),
395            ClassifiedFbxObject::Light(x) => x.inner(),
396            ClassifiedFbxObject::NullNode(x) => x.inner(),
397            ClassifiedFbxObject::LimbNode(x) => x.inner(),
398            ClassifiedFbxObject::Material(x) => x.inner(),
399            ClassifiedFbxObject::Texture(x) => x.inner(),
400            ClassifiedFbxObject::LayeredTexture(x) => x.inner(),
401            ClassifiedFbxObject::Video(x) => x.inner(),
402            ClassifiedFbxObject::Cluster(x) => x.inner(),
403            ClassifiedFbxObject::Skin(x) => x.inner(),
404            ClassifiedFbxObject::BlendShape(x) => x.inner(),
405            ClassifiedFbxObject::BlendShapeChannel(x) => x.inner(),
406            ClassifiedFbxObject::AnimationStack(x) => x.inner(),
407            ClassifiedFbxObject::AnimationLayer(x) => x.inner(),
408            ClassifiedFbxObject::AnimationCurve(x) => x.inner(),
409            ClassifiedFbxObject::AnimationCurveNode(x) => x.inner(),
410            ClassifiedFbxObject::UnknownDeformer(o) => o,
411            ClassifiedFbxObject::UnknownGeometry(o) => o,
412            ClassifiedFbxObject::UnknownNodeAttribute(o) => o,
413            ClassifiedFbxObject::Unknown(o) => o,
414        }
415    }
416}