Skip to main content

fbx_dom/objects/
camera_switcher.rs

1//! FBX `NodeAttribute` / `CameraSwitcher` — Assimp [`CameraSwitcher`](https://github.com/assimp/assimp/blob/master/code/AssetLib/FBX/FBXNodeAttribute.cpp).
2//!
3//! Unlike Assimp (optional `CameraId` / `CameraName` / `CameraIndexName`), we require all three
4//! child elements with a single value token each.
5
6use std::collections::HashMap;
7use std::convert::TryFrom;
8
9use fbxscii::ElementAttribute;
10
11use crate::{OwnedObject, Property, objects::AttrExtractorExt};
12
13use super::{FbxObjectTag, FbxTryFromReason, FbxTypeMismatch, fbx_object_tag};
14
15const CAMERA_ID: &str = "CameraId";
16const CAMERA_NAME: &str = "CameraName";
17const CAMERA_INDEX_NAME: &str = "CameraIndexName";
18
19#[derive(Debug, PartialEq)]
20pub struct CameraSwitcher {
21    object: OwnedObject,
22    pub camera_id: i32,
23    pub camera_name: String,
24    pub camera_index_name: String,
25}
26
27impl CameraSwitcher {
28    pub fn inner(&self) -> &OwnedObject {
29        &self.object
30    }
31
32    pub fn into_inner(self) -> OwnedObject {
33        self.object
34    }
35
36    /// Temporary bridge to Assimp-style camera switcher properties until typed accessors are added.
37    pub fn properties(&self) -> &HashMap<String, Property> {
38        &self.object.properties
39    }
40
41    pub fn property(&self, name: &str) -> Option<&Property> {
42        self.object.properties.get(name)
43    }
44}
45
46fn parse_camera_switcher_fields(
47    attrs: &HashMap<String, ElementAttribute>,
48) -> Result<(i32, String, String), FbxTryFromReason> {
49    let id_tok = attrs.require_token(&CAMERA_ID)?;
50    let camera_id =
51        id_tok
52            .parse::<i32>()
53            .map_err(|e| FbxTryFromReason::InvalidAttributeFormat {
54                name: CAMERA_ID.to_string(),
55                detail: e.to_string(),
56            })?;
57
58    let camera_name = attrs.require_token(&CAMERA_NAME)?.to_string();
59    let camera_index_name = attrs.require_token(&CAMERA_INDEX_NAME)?.to_string();
60
61    Ok((camera_id, camera_name, camera_index_name))
62}
63
64impl TryFrom<OwnedObject> for CameraSwitcher {
65    type Error = FbxTypeMismatch;
66
67    fn try_from(o: OwnedObject) -> Result<Self, Self::Error> {
68        if fbx_object_tag(&o) != FbxObjectTag::CameraSwitcher {
69            return Err(FbxTypeMismatch::wrong_object_kind(
70                o,
71                "CameraSwitcher".to_string(),
72            ));
73        }
74
75        match parse_camera_switcher_fields(&o.attributes) {
76            Ok((camera_id, camera_name, camera_index_name)) => Ok(CameraSwitcher {
77                object: o,
78                camera_id,
79                camera_name,
80                camera_index_name,
81            }),
82            Err(reason) => Err(FbxTypeMismatch { object: o, reason }),
83        }
84    }
85}
86
87#[cfg(test)]
88mod tests {
89    use super::*;
90    use crate::objects::{NODE_ATTRIBUTE_CAMERA_SWITCHER_CLASS_NAME, NODE_ATTRIBUTE_TYPE_NAME};
91    use fbxscii::LeafAttribute;
92    use std::collections::HashMap;
93
94    fn leaf(tokens: &[&str]) -> ElementAttribute {
95        ElementAttribute::Leaf(Box::new(LeafAttribute {
96            key: String::new(),
97            tokens: tokens.iter().map(|s| (*s).to_string()).collect(),
98        }))
99    }
100
101    fn owned_camera_switcher(attrs: HashMap<String, ElementAttribute>) -> OwnedObject {
102        OwnedObject {
103            object_index: 42,
104            name: "Switcher".into(),
105            type_name: NODE_ATTRIBUTE_TYPE_NAME.into(),
106            class_name: NODE_ATTRIBUTE_CAMERA_SWITCHER_CLASS_NAME.into(),
107            properties: HashMap::new(),
108            attributes: attrs,
109            connected_object_ids: vec![],
110            object_property_targets: vec![],
111            pp_property_targets: HashMap::new(),
112        }
113    }
114
115    #[test]
116    fn camera_switcher_try_from_ok() {
117        let mut attrs = HashMap::new();
118        attrs.insert(CAMERA_ID.into(), leaf(&["7"]));
119        attrs.insert(CAMERA_NAME.into(), leaf(&["MainCam"]));
120        attrs.insert(CAMERA_INDEX_NAME.into(), leaf(&["IndexA"]));
121        let o = owned_camera_switcher(attrs);
122        let cs = CameraSwitcher::try_from(o).unwrap();
123        assert_eq!(cs.camera_id, 7);
124        assert_eq!(cs.camera_name, "MainCam");
125        assert_eq!(cs.camera_index_name, "IndexA");
126    }
127
128    #[test]
129    fn camera_switcher_missing_attr() {
130        let mut attrs = HashMap::new();
131        attrs.insert(CAMERA_ID.into(), leaf(&["1"]));
132        attrs.insert(CAMERA_NAME.into(), leaf(&["X"]));
133        let o = owned_camera_switcher(attrs);
134        let err = CameraSwitcher::try_from(o).unwrap_err();
135        assert!(matches!(
136            err.reason,
137            FbxTryFromReason::MissingAttribute {
138                name: ref n,
139            }
140            if n == CAMERA_INDEX_NAME
141        ));
142    }
143
144    #[test]
145    fn camera_switcher_wrong_kind() {
146        let o = OwnedObject {
147            object_index: 1,
148            name: "g".into(),
149            type_name: "Geometry".into(),
150            class_name: "Mesh".into(),
151            properties: HashMap::new(),
152            attributes: HashMap::new(),
153            connected_object_ids: vec![],
154            object_property_targets: vec![],
155            pp_property_targets: HashMap::new(),
156        };
157        let err = CameraSwitcher::try_from(o).unwrap_err();
158        assert!(matches!(
159            err.reason,
160            FbxTryFromReason::WrongObjectKind { expected: ref e, ..}
161            if e == "CameraSwitcher"
162        ));
163    }
164
165    #[test]
166    fn property_accessors_return_owned_object_properties() {
167        let mut attrs = HashMap::new();
168        attrs.insert(CAMERA_ID.into(), leaf(&["7"]));
169        attrs.insert(CAMERA_NAME.into(), leaf(&["MainCam"]));
170        attrs.insert(CAMERA_INDEX_NAME.into(), leaf(&["IndexA"]));
171
172        let mut o = owned_camera_switcher(attrs);
173        o.properties
174            .insert("SomeFlag".to_string(), Property::Bool(true));
175
176        let cs = CameraSwitcher::try_from(o).unwrap();
177        assert_eq!(cs.property("SomeFlag"), Some(&Property::Bool(true)));
178        assert_eq!(cs.properties().len(), 1);
179    }
180}