1use std::convert::TryFrom;
9
10use fbxscii::{ElementAmphitheatre, ElementHandle};
11
12use crate::document::{
13 Document, DocumentLoader, DocumentParseError, ImportSettings, LazyObject,
14 ObjectPropertyConnection, Property, PropertyDetails, PropertyParseError, Template,
15};
16
17pub const LOWEST_SUPPORTED_VERSION: u32 = 7100;
19pub const UPPER_SUPPORTED_VERSION: u32 = 7400;
21
22fn read_header(
26 amphitheatre: &ElementAmphitheatre,
27 document: &mut Document,
28 settings: ImportSettings,
29) -> Result<(), DocumentParseError> {
30 let header_extension = amphitheatre.get_handle_by_key("FBXHeaderExtension");
31 if header_extension.is_none() {
32 return Err(DocumentParseError::RequiredElementNotFound(
33 "FBXHeaderExtension".to_string(),
34 ));
35 }
36 let header_extension = header_extension.unwrap();
37
38 let version_element = header_extension.first_child_by_key("FBXVersion").ok_or(
40 DocumentParseError::RequiredElementNotFound("FBXVersion".to_string()),
41 )?;
42 document.fbx_version = version_element
43 .try_into()
44 .map_err(DocumentParseError::ElementParseError)?;
45
46 if document.fbx_version < LOWEST_SUPPORTED_VERSION {
48 return Err(DocumentParseError::UnsupportedVersion(
49 document.fbx_version,
50 None,
51 ));
52 }
53 if document.fbx_version > UPPER_SUPPORTED_VERSION && settings.strict {
54 return Err(DocumentParseError::UnsupportedVersion(
55 document.fbx_version,
56 Some("Turn off strict mode to import this version.".to_string()),
57 ));
58 }
59
60 document.creator = header_extension
62 .first_child_by_key("Creator")
63 .ok_or(DocumentParseError::RequiredElementNotFound(
64 "Creator".to_string(),
65 ))?
66 .try_into()
67 .map_err(DocumentParseError::ElementParseError)?;
68
69 let creation_date_element = header_extension
71 .first_child_by_key("CreationTimeStamp")
72 .ok_or(DocumentParseError::RequiredElementNotFound(
73 "CreationTimeStamp".to_string(),
74 ))?;
75 let year = creation_date_element
76 .first_child_by_key("Year")
77 .ok_or(DocumentParseError::RequiredElementNotFound(
78 "Year".to_string(),
79 ))?
80 .try_into()
81 .map_err(DocumentParseError::ElementParseError)?;
82 let month = creation_date_element
83 .first_child_by_key("Month")
84 .ok_or(DocumentParseError::RequiredElementNotFound(
85 "Month".to_string(),
86 ))?
87 .try_into()
88 .map_err(DocumentParseError::ElementParseError)?;
89 let day = creation_date_element
90 .first_child_by_key("Day")
91 .ok_or(DocumentParseError::RequiredElementNotFound(
92 "Day".to_string(),
93 ))?
94 .try_into()
95 .map_err(DocumentParseError::ElementParseError)?;
96 let hour = creation_date_element
97 .first_child_by_key("Hour")
98 .ok_or(DocumentParseError::RequiredElementNotFound(
99 "Hour".to_string(),
100 ))?
101 .try_into()
102 .map_err(DocumentParseError::ElementParseError)?;
103 let minute = creation_date_element
104 .first_child_by_key("Minute")
105 .ok_or(DocumentParseError::RequiredElementNotFound(
106 "Minute".to_string(),
107 ))?
108 .try_into()
109 .map_err(DocumentParseError::ElementParseError)?;
110 let second = creation_date_element
111 .first_child_by_key("Second")
112 .ok_or(DocumentParseError::RequiredElementNotFound(
113 "Second".to_string(),
114 ))?
115 .try_into()
116 .map_err(DocumentParseError::ElementParseError)?;
117 let millisecond = creation_date_element
118 .first_child_by_key("Millisecond")
119 .ok_or(DocumentParseError::RequiredElementNotFound(
120 "Millisecond".to_string(),
121 ))?
122 .try_into()
123 .map_err(DocumentParseError::ElementParseError)?;
124 document.creation_date = [year, month, day, hour, minute, second, millisecond];
125 Ok(())
126}
127
128fn read_definitions(
133 amphitheatre: &ElementAmphitheatre,
134 document: &mut Document,
135) -> Result<(), DocumentParseError> {
136 let definition_handle_opt = amphitheatre.get_handle_by_key("Definitions");
137 if definition_handle_opt.is_none() {
138 return Ok(());
139 }
140 let definition_handle = definition_handle_opt.unwrap();
141 let object_type_handles = definition_handle.children_by_key("ObjectType");
142 for object_type_handle in object_type_handles {
143 if !object_type_handle.has_children() {
144 continue;
145 }
146
147 let object_tokens = object_type_handle.tokens();
148 if object_tokens.is_empty() {
149 continue;
150 }
151 let object_name = &object_tokens[0];
152 let property_template_handles = object_type_handle.children_by_key("PropertyTemplate");
153 for property_template_handle in property_template_handles {
154 if !property_template_handle.has_children() {
155 continue;
156 }
157 let property_tokens = property_template_handle.tokens();
158 if property_tokens.is_empty() {
159 continue;
160 }
161 let property_name = &property_tokens[0];
162 let property_table_handle_opt =
163 property_template_handle.first_child_by_key("Properties70");
164 if let Some(property_table_handle) = property_table_handle_opt {
165 let template_name = format!("{}.{}", object_name, property_name);
166 document
167 .default_template_by_object_type
168 .entry(object_name.to_string())
169 .or_insert_with(|| template_name.clone());
170 let template = document
171 .templates
172 .entry(template_name.clone())
173 .or_insert_with(Template::default);
174 for property_detail in property_table_handle.children() {
175 match PropertyDetails::try_from(property_detail) {
176 Ok(property_details) => {
177 template.insert(property_details.name, property_details.property);
178 }
179 Err(e) => {
180 log::debug!(
181 target: "fbx_dom",
182 "skipped Properties70 default-template entry (template={}, error={})",
183 template_name,
184 e
185 );
186 }
187 }
188 }
189 }
190 }
191 }
192
193 Ok(())
194}
195
196fn read_global_settings(
198 amphitheatre: &ElementAmphitheatre,
199 document: &mut Document,
200) -> Result<(), DocumentParseError> {
201 let global_settings_handle_opt = amphitheatre.get_handle_by_key("GlobalSettings");
202 if global_settings_handle_opt.is_none() {
203 return Ok(());
204 }
205 let global_settings_handle = global_settings_handle_opt.unwrap();
206 let property_table_handle_opt = global_settings_handle.first_child_by_key("Properties70");
207 if let Some(property_table_handle) = property_table_handle_opt {
208 for property_detail in property_table_handle.children() {
209 let property_details: PropertyDetails = property_detail
210 .try_into()
211 .map_err(DocumentParseError::PropertyParseError)?;
212 document
213 .global_settings
214 .insert(property_details.name, property_details.property);
215 }
216 }
217 Ok(())
218}
219
220fn read_connections(
229 amphitheatre: &ElementAmphitheatre,
230 document: &mut Document,
231) -> Result<(), DocumentParseError> {
232 let connections_handle_opt = amphitheatre.get_handle_by_key("Connections");
233 if connections_handle_opt.is_none() {
234 return Ok(());
235 }
236 let connections_handle = connections_handle_opt.unwrap();
237 for connection_handle in connections_handle.children() {
238 let connection_tokens = connection_handle.tokens();
239 if connection_tokens.len() < 2 {
240 continue;
241 }
242 let connection_type = &connection_tokens[0];
243 match connection_type.as_str() {
244 "OO" => {
245 if connection_tokens.len() != 3 {
246 continue;
247 }
248 let Ok(src) = connection_tokens[1].parse::<u64>() else {
249 continue;
250 };
251 let Ok(dest) = connection_tokens[2].parse::<u64>() else {
252 continue;
253 };
254 document
255 .object_connections
256 .entry(src)
257 .or_insert(Vec::new())
258 .push(dest);
259 }
260 "OP" => {
261 if connection_tokens.len() != 4 {
262 continue;
263 }
264 let Ok(src) = connection_tokens[1].parse::<u64>() else {
265 continue;
266 };
267 let Ok(dest) = connection_tokens[2].parse::<u64>() else {
268 continue;
269 };
270 let property = connection_tokens[3].to_string();
271 document
272 .object_property_connections
273 .entry(src)
274 .or_insert(Vec::new())
275 .push(ObjectPropertyConnection { dest, property });
276 }
277 "PP" => {
278 if connection_tokens.len() != 5 {
279 continue;
280 }
281 let Ok(src) = connection_tokens[1].parse::<u64>() else {
282 continue;
283 };
284 let src_property = connection_tokens[2].to_string();
285 let Ok(dest) = connection_tokens[3].parse::<u64>() else {
286 continue;
287 };
288 let dest_property = connection_tokens[4].to_string();
289 document
290 .object_to_source_properties
291 .entry(src)
292 .or_insert(Vec::new())
293 .push(src_property.clone());
294 document
295 .property_connections
296 .entry(ObjectPropertyConnection {
297 dest: src,
298 property: src_property,
299 })
300 .or_insert(Vec::new())
301 .push(ObjectPropertyConnection {
302 dest,
303 property: dest_property,
304 });
305 }
306 _ => continue,
307 }
308 }
309 Ok(())
310}
311
312fn read_objects(
316 amphitheatre: &ElementAmphitheatre,
317 document: &mut Document,
318) -> Result<(), DocumentParseError> {
319 let objects_handle_opt = amphitheatre.get_handle_by_key("Objects");
320 if objects_handle_opt.is_none() {
321 return Ok(());
322 }
323 let objects_handle = objects_handle_opt.unwrap();
324 for object_handle in objects_handle.children() {
325 let object_tokens = object_handle.tokens();
326 if object_tokens.len() != 3 {
327 continue;
328 }
329 let object_index = object_tokens[0].parse::<u64>();
330 if object_index.is_err() {
331 continue;
332 }
333 let object_index = object_index.unwrap();
334
335 let object = LazyObject {
336 name: object_tokens[1].to_string(),
337 type_name: object_handle.key().to_string(),
338 class_name: object_tokens[2].to_string(),
339 element_index: object_handle.index(),
340 };
341 document.objects.insert(object_index, object);
342 }
343 Ok(())
344}
345
346impl<'a> TryFrom<ElementHandle<'a>> for PropertyDetails {
350 type Error = PropertyParseError;
351
352 fn try_from(handle: ElementHandle<'a>) -> Result<Self, PropertyParseError> {
353 let tokens = handle.tokens();
354 if tokens.len() < 2 {
355 return Err(PropertyParseError::InvalidTokenLength(tokens.len(), None));
356 }
357 let property_name = &tokens[0];
358 let property_type = &tokens[1];
359 let property = match property_type.as_str() {
360 "KString" => {
361 if tokens.len() != 5 {
362 return Err(PropertyParseError::InvalidTokenLength(
363 tokens.len(),
364 Some(property_type.to_string()),
365 ));
366 }
367 Property::String(tokens[4].to_string())
368 }
369 "bool" | "Bool" => {
370 if tokens.len() != 5 {
371 return Err(PropertyParseError::InvalidTokenLength(
372 tokens.len(),
373 Some(property_type.to_string()),
374 ));
375 }
376 let Ok(val) = tokens[4].parse::<i32>() else {
377 return Err(PropertyParseError::TokenParseError(
378 property_type.to_string(),
379 tokens[4].to_string(),
380 ));
381 };
382 Property::Bool(val != 0)
383 }
384 "int" | "Int" | "enum" | "Enum" | "Integer" => {
385 if tokens.len() != 5 {
386 return Err(PropertyParseError::InvalidTokenLength(
387 tokens.len(),
388 Some(property_type.to_string()),
389 ));
390 }
391 let Ok(val) = tokens[4].parse::<i32>() else {
392 return Err(PropertyParseError::TokenParseError(
393 property_type.to_string(),
394 tokens[4].to_string(),
395 ));
396 };
397 Property::Int(val)
398 }
399 "ULongLong" => {
400 if tokens.len() != 5 {
401 return Err(PropertyParseError::InvalidTokenLength(
402 tokens.len(),
403 Some(property_type.to_string()),
404 ));
405 }
406 let Ok(val) = tokens[4].parse::<u64>() else {
407 return Err(PropertyParseError::TokenParseError(
408 property_type.to_string(),
409 tokens[4].to_string(),
410 ));
411 };
412 Property::ULongLong(val)
413 }
414 "KTime" => {
415 if tokens.len() != 5 {
416 return Err(PropertyParseError::InvalidTokenLength(
417 tokens.len(),
418 Some(property_type.to_string()),
419 ));
420 }
421 let Ok(val) = tokens[4].parse::<i64>() else {
422 return Err(PropertyParseError::TokenParseError(
423 property_type.to_string(),
424 tokens[4].to_string(),
425 ));
426 };
427 Property::ILongLong(val)
428 }
429 "double" | "Number" | "float" | "Float" | "FieldOfView" | "UnitScaleFactor" => {
430 if tokens.len() != 5 {
431 return Err(PropertyParseError::InvalidTokenLength(
432 tokens.len(),
433 Some(property_type.to_string()),
434 ));
435 }
436 let Ok(val) = tokens[4].parse::<f32>() else {
437 return Err(PropertyParseError::TokenParseError(
438 property_type.to_string(),
439 tokens[4].to_string(),
440 ));
441 };
442 Property::Float(val)
443 }
444 "Vector3D" | "Vector" | "Color" | "ColorRGB" | "Lcl Translation" | "Lcl Rotation"
445 | "Lcl Scaling" => {
446 if tokens.len() != 7 {
447 return Err(PropertyParseError::InvalidTokenLength(
448 tokens.len(),
449 Some(property_type.to_string()),
450 ));
451 }
452 let Ok(x) = tokens[4].parse::<f32>() else {
453 return Err(PropertyParseError::TokenParseError(
454 property_type.to_string(),
455 tokens[4].to_string(),
456 ));
457 };
458 let Ok(y) = tokens[5].parse::<f32>() else {
459 return Err(PropertyParseError::TokenParseError(
460 property_type.to_string(),
461 tokens[5].to_string(),
462 ));
463 };
464 let Ok(z) = tokens[6].parse::<f32>() else {
465 return Err(PropertyParseError::TokenParseError(
466 property_type.to_string(),
467 tokens[6].to_string(),
468 ));
469 };
470 Property::Vec3([x, y, z])
471 }
472 "ColorAndAlpha" => {
473 if tokens.len() != 8 {
474 return Err(PropertyParseError::InvalidTokenLength(
475 tokens.len(),
476 Some(property_type.to_string()),
477 ));
478 }
479 let r = tokens[4].parse::<f32>().unwrap_or(0.0);
480 let g = tokens[5].parse::<f32>().unwrap_or(0.0);
481 let b = tokens[6].parse::<f32>().unwrap_or(0.0);
482 let a = tokens[7].parse::<f32>().unwrap_or(0.0);
483 Property::Vec4([r, g, b, a])
484 }
485 value => return Err(PropertyParseError::MissingPropertyType(value.to_string())),
486 };
487 Ok(PropertyDetails {
488 name: property_name.to_string(),
489 property,
490 })
491 }
492}
493
494impl DocumentLoader for ElementAmphitheatre {
495 fn load_into_document(
497 self,
498 document: &mut Document,
499 settings: ImportSettings,
500 ) -> Result<(), DocumentParseError> {
501 read_header(&self, document, settings)?;
502 read_definitions(&self, document)?;
503 read_global_settings(&self, document)?;
504
505 read_objects(&self, document)?;
506 read_connections(&self, document)?;
507 document.object_element_amphitheatre = self;
508 Ok(())
509 }
510}
511
512#[cfg(test)]
513mod tests {
514
515 use std::convert::TryFrom;
516 use std::io::BufReader;
517
518 use fbxscii::{ElementAmphitheatre, ElementHandle, Parser, Tokenizer};
519
520 use crate::document::{
521 Document, DocumentParseError, ImportSettings, ObjectPropertyConnection, Property,
522 PropertyDetails, PropertyParseError,
523 };
524
525 fn minimal_ascii_fbx_7200(global_properties70_body: &str, tail: &str) -> String {
527 format!(
528 r#"; test
529FBXHeaderExtension: {{
530 FBXHeaderVersion: 1003
531 FBXVersion: 7200
532 CreationTimeStamp: {{
533 Version: 1000
534 Year: 2012
535 Month: 6
536 Day: 28
537 Hour: 16
538 Minute: 32
539 Second: 53
540 Millisecond: 433
541 }}
542 Creator: "test"
543}}
544GlobalSettings: {{
545 Properties70: {{
546{global_properties70_body}
547 }}
548}}
549{tail}"#
550 )
551 }
552
553 fn load_arena(src: &str) -> ElementAmphitheatre {
555 let tokenizer = Tokenizer::new(BufReader::new(src.as_bytes()));
556 let parser = Parser::new(tokenizer);
557 parser.load().expect("parse ASCII FBX")
558 }
559
560 fn first_p_child_properties70(arena: &ElementAmphitheatre) -> ElementHandle<'_> {
562 let gs = arena
563 .get_handle_by_key("GlobalSettings")
564 .expect("GlobalSettings");
565 let p70 = gs.first_child_by_key("Properties70").expect("Properties70");
566 p70.children().next().expect("at least one P row")
567 }
568
569 #[test]
570 fn test_header_parse() {
571 let test_document = r#"
572FBXHeaderExtension: {
573 FBXHeaderVersion: 1003
574 FBXVersion: 7300
575 CreationTimeStamp: {
576 Version: 1000
577 Year: 2012
578 Month: 6
579 Day: 28
580 Hour: 16
581 Minute: 32
582 Second: 53
583 Millisecond: 433
584 }
585 Creator: "FBX SDK/FBX Plugins version 2013.1"
586}"#;
587 let tokenizer = Tokenizer::new(BufReader::new(test_document.as_bytes()));
588 let parser = Parser::new(tokenizer);
589 let document = Document::from_parser(parser, ImportSettings::default()).unwrap();
590 assert_eq!(document.fbx_version, 7300);
591 assert_eq!(document.creator, "FBX SDK/FBX Plugins version 2013.1");
592 assert_eq!(document.creation_date, [2012, 6, 28, 16, 32, 53, 433]);
593 }
594
595 #[test]
596 fn test_empty_document_parse() {
597 let test_document = "";
598 let tokenizer = Tokenizer::new(BufReader::new(test_document.as_bytes()));
599 let parser = Parser::new(tokenizer);
600 let document = Document::from_parser(parser, ImportSettings::default());
601 assert!(document.is_err());
602 assert_eq!(
603 document.unwrap_err(),
604 DocumentParseError::RequiredElementNotFound("FBXHeaderExtension".to_string())
605 );
606 }
607
608 #[test]
609 fn connections_pp_ascii_populates_property_maps() {
610 let src = minimal_ascii_fbx_7200(
611 r#" P: "UpAxis", "int", "", "",1
612"#,
613 r#"Definitions: {
614 ObjectType: "Geometry" {
615 PropertyTemplate: "FbxMesh" {
616 Properties70: {
617 P: "UnitScaleFactor", "double", "", "",1
618 }
619 }
620 }
621}
622Objects: {
623 Geometry: 101, "A", "Mesh" {
624 }
625 Geometry: 202, "B", "Mesh" {
626 }
627}
628Connections: {
629 C: "PP",101,"SrcProp",202,"DstProp"
630}
631"#,
632 );
633 let tokenizer = Tokenizer::new(BufReader::new(src.as_bytes()));
634 let parser = Parser::new(tokenizer);
635 let document = Document::from_parser(parser, ImportSettings::default()).unwrap();
636
637 let obj = document.object_by_index(101).expect("object 101");
638 assert_eq!(obj.pp_source_property_names(), &["SrcProp".to_string()]);
639 assert_eq!(
640 obj.pp_targets((101, "SrcProp")),
641 Some(
642 &[ObjectPropertyConnection {
643 dest: 202,
644 property: "DstProp".to_string(),
645 }][..],
646 )
647 );
648 }
649
650 #[test]
651 fn connections_pp_ascii_appends_multiple_destinations_for_same_source() {
652 let src = minimal_ascii_fbx_7200(
653 r#" P: "UpAxis", "int", "", "",1
654"#,
655 r#"Definitions: {
656 ObjectType: "Geometry" {
657 PropertyTemplate: "FbxMesh" {
658 Properties70: {
659 P: "UnitScaleFactor", "double", "", "",1
660 }
661 }
662 }
663}
664Objects: {
665 Geometry: 101, "A", "Mesh" {
666 }
667 Geometry: 202, "B", "Mesh" {
668 }
669 Geometry: 303, "C", "Mesh" {
670 }
671}
672Connections: {
673 C: "PP",101,"SrcProp",202,"DstA"
674 C: "PP",101,"SrcProp",303,"DstB"
675}
676"#,
677 );
678 let tokenizer = Tokenizer::new(BufReader::new(src.as_bytes()));
679 let parser = Parser::new(tokenizer);
680 let document = Document::from_parser(parser, ImportSettings::default()).unwrap();
681
682 let obj = document.object_by_index(101).expect("object 101");
683 assert_eq!(
685 obj.pp_source_property_names(),
686 &["SrcProp".to_string(), "SrcProp".to_string()]
687 );
688 let targets = obj.pp_targets((101, "SrcProp")).expect("PP targets");
689 assert_eq!(targets.len(), 2);
690 assert!(targets.contains(&ObjectPropertyConnection {
691 dest: 202,
692 property: "DstA".to_string(),
693 }));
694 assert!(targets.contains(&ObjectPropertyConnection {
695 dest: 303,
696 property: "DstB".to_string(),
697 }));
698 }
699
700 #[test]
701 fn property_details_try_from_int_kstring_and_vector3d() {
702 let src = minimal_ascii_fbx_7200(
703 r#" P: "UpAxis", "int", "", "",2
704 P: "LayerName", "KString", "", "", "hello"
705 P: "Point", "Vector3D", "Vector", "",0,1,2
706"#,
707 "",
708 );
709 let arena = load_arena(&src);
710 let gs = arena.get_handle_by_key("GlobalSettings").unwrap();
711 let p70 = gs.first_child_by_key("Properties70").unwrap();
712 let mut it = p70.children();
713 let d0: PropertyDetails = PropertyDetails::try_from(it.next().unwrap()).unwrap();
714 assert_eq!(d0.name, "UpAxis");
715 assert_eq!(d0.property, Property::Int(2));
716 let d1: PropertyDetails = PropertyDetails::try_from(it.next().unwrap()).unwrap();
717 assert_eq!(d1.name, "LayerName");
718 assert_eq!(d1.property, Property::String("hello".to_string()));
719 let d2: PropertyDetails = PropertyDetails::try_from(it.next().unwrap()).unwrap();
720 assert_eq!(d2.name, "Point");
721 assert_eq!(d2.property, Property::Vec3([0.0, 1.0, 2.0]));
722 }
723
724 #[test]
725 fn property_details_try_from_missing_property_type_object() {
726 let src = minimal_ascii_fbx_7200(
727 r#" P: "SourceObject", "object", "", ""
728"#,
729 "",
730 );
731 let arena = load_arena(&src);
732 let p = first_p_child_properties70(&arena);
733 let err = PropertyDetails::try_from(p).unwrap_err();
734 assert_eq!(
735 err,
736 PropertyParseError::MissingPropertyType("object".to_string())
737 );
738 }
739
740 #[test]
741 fn property_details_try_from_invalid_token_length() {
742 let src = minimal_ascii_fbx_7200(
743 r#" P: "Short", "int"
744"#,
745 "",
746 );
747 let arena = load_arena(&src);
748 let p = first_p_child_properties70(&arena);
749 let err = PropertyDetails::try_from(p).unwrap_err();
750 assert_eq!(
751 err,
752 PropertyParseError::InvalidTokenLength(2, Some("int".to_string()))
753 );
754 }
755
756 #[test]
757 fn property_details_try_from_token_parse_error() {
758 let src = minimal_ascii_fbx_7200(
759 r#" P: "BadInt", "int", "", "", "not_an_int"
760"#,
761 "",
762 );
763 let arena = load_arena(&src);
764 let p = first_p_child_properties70(&arena);
765 let err = PropertyDetails::try_from(p).unwrap_err();
766 assert_eq!(
767 err,
768 PropertyParseError::TokenParseError("int".to_string(), "not_an_int".to_string())
769 );
770 }
771}