├── .editorconfig ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE-APACHE.txt ├── LICENSE-MIT.txt ├── README.md ├── examples ├── dump-pull-parser-events.rs └── load-tree.rs ├── src ├── lib.rs ├── low.rs ├── low │ ├── fbx_header.rs │ ├── v7400.rs │ ├── v7400 │ │ ├── array_attribute.rs │ │ ├── attribute.rs │ │ ├── attribute │ │ │ ├── type_.rs │ │ │ └── value.rs │ │ ├── fbx_footer.rs │ │ ├── node_header.rs │ │ └── special_attribute.rs │ └── version.rs ├── pull_parser.rs ├── pull_parser │ ├── any.rs │ ├── any │ │ └── error.rs │ ├── error.rs │ ├── error │ │ ├── data.rs │ │ ├── operation.rs │ │ └── warning.rs │ ├── position.rs │ ├── reader.rs │ ├── v7400.rs │ ├── v7400 │ │ ├── attribute.rs │ │ ├── attribute │ │ │ ├── array.rs │ │ │ ├── iter.rs │ │ │ ├── loader.rs │ │ │ ├── loaders.rs │ │ │ └── loaders │ │ │ │ ├── direct.rs │ │ │ │ ├── single.rs │ │ │ │ └── type_.rs │ │ ├── event.rs │ │ ├── parser.rs │ │ └── read.rs │ └── version.rs ├── tree.rs ├── tree │ ├── any.rs │ ├── any │ │ └── error.rs │ ├── v7400.rs │ └── v7400 │ │ ├── error.rs │ │ ├── loader.rs │ │ ├── macros.rs │ │ ├── node.rs │ │ └── node │ │ ├── data.rs │ │ ├── handle.rs │ │ └── name.rs ├── writer.rs └── writer │ ├── v7400.rs │ └── v7400 │ ├── binary.rs │ └── binary │ ├── attributes.rs │ ├── attributes │ └── array.rs │ ├── error.rs │ ├── footer.rs │ └── macros.rs └── tests ├── missing-and-extra-node-end-marker.rs ├── v7400 ├── mod.rs └── writer.rs ├── write-and-parse-v7400-binary.rs └── write-tree-and-parse-v7400-binary.rs /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | # Use rustfmt (not editorconfig) to format Rust sources. 4 | 5 | [*.{toml,yaml,yml}] 6 | charset = utf-8 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.toml] 12 | indent_size = 4 13 | 14 | [*.{yaml,yml}] 15 | indent_size = 2 16 | indent_style = space 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | cache: cargo 3 | rust: 4 | - stable 5 | - beta 6 | - 1.52.0 7 | jobs: 8 | include: 9 | - rust: 1.52.0 10 | env: TEST_MINIMAL_VERSIONS=1 11 | - rust: 1.52.0 12 | env: LINT=1 13 | before_install: 14 | - | 15 | if [ "${LINT:-0}" -ne 0 ] ; then 16 | rustup component add clippy rustfmt 17 | cargo clippy --version 18 | cargo fmt --version 19 | fi 20 | - | 21 | if [ "${TEST_MINIMAL_VERSIONS:-0}" -ne 0 ] ; then 22 | rustup install nightly 23 | fi 24 | before_script: 25 | # Use dependencies with minimal versions. 26 | - | 27 | if [ "${TEST_MINIMAL_VERSIONS:-0}" -ne 0 ] ; then 28 | cargo +nightly update -Z minimal-versions 29 | fi 30 | script: 31 | - if [ "${LINT:-0}" -eq 0 ] ; then cargo build --verbose --workspace --all-features && cargo test --verbose --workspace --all-features ; fi 32 | # Fail if the code is correctly formatted. 33 | - if [ "${LINT:-0}" -ne 0 ] ; then cargo fmt --all -- --check ; fi 34 | # Fail if the code has warnings. 35 | - if [ "${LINT:-0}" -ne 0 ] ; then cargo clippy --all-features -- --deny warnings ; fi 36 | notifications: 37 | email: false 38 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fbxcel" 3 | version = "0.9.0" 4 | authors = ["YOSHIOKA Takuma "] 5 | edition = "2021" 6 | rust-version = "1.56" 7 | license = "MIT OR Apache-2.0" 8 | readme = "README.md" 9 | description = "Excellent FBX library" 10 | repository = "https://github.com/lo48576/fbxcel" 11 | keywords = ["FBX", "3D", "model"] 12 | categories = ["parser-implementations"] 13 | 14 | [package.metadata.docs.rs] 15 | all-features = true 16 | # See . 17 | # 18 | # Didn't create `docsrs` feature, since this (`#[doc(cfg(feature = ...))]`) 19 | # depends on nightly feature and it prevents `cargo doc --all-features` from 20 | # running with stable rust toolchain. 21 | # See 22 | # for unstable `#[doc(cfg(...))]` attribute. 23 | rustdoc-args = ["--cfg", "docsrs"] 24 | 25 | [features] 26 | default = [] 27 | 28 | tree = ["indextree", "string-interner"] 29 | writer = [] 30 | 31 | [dependencies] 32 | indextree = { version = "4", optional = true } 33 | libflate = "1.0.1" 34 | log = "0.4.4" 35 | string-interner = { version = "0.14.0", optional = true, default-features = false, features = ["backends", "inline-more", "std"] } 36 | 37 | [dev-dependencies] 38 | env_logger = "0.9.0" 39 | 40 | [badges] 41 | maintenance = { status = "passively-maintained" } 42 | travis-ci = { repository = "lo48576/fbxcel" } 43 | 44 | [[example]] 45 | name = "dump-pull-parser-events" 46 | 47 | [[example]] 48 | name = "load-tree" 49 | required-features = ["tree"] 50 | -------------------------------------------------------------------------------- /LICENSE-APACHE.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /LICENSE-MIT.txt: -------------------------------------------------------------------------------- 1 | Copyright 2018-2019 YOSHIOKA Takuma 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # fbxcel 2 | 3 | [![Build Status](https://travis-ci.com/lo48576/fbxcel.svg?branch=develop)](https://travis-ci.com/lo48576/fbxcel) 4 | [![Latest version](https://img.shields.io/crates/v/fbxcel.svg)](https://crates.io/crates/fbxcel) 5 | [![Documentation](https://docs.rs/fbxcel/badge.svg)](https://docs.rs/fbxcel) 6 | ![Minimum rustc version: 1.56](https://img.shields.io/badge/rustc-1.56+-lightgray.svg) 7 | 8 | `fbxcel` is an FBX library for Rust programming language. 9 | 10 | `fbxcel` is relatively low-level library. 11 | If you want to interpret and render FBX data, you need another library or need to do it yourself. 12 | ([`fbxcel-dom`](https://github.com/lo48576/fbxcel-dom) is incomplete and currently unmaintained, 13 | but it can help you know what kind of tasks are needed to interpret FBX data.) 14 | 15 | ## Features 16 | 17 | * Pull parser for FBX binary (`pull_parser` module) 18 | + FBX 7.4 and 7.5 is explicitly supported. 19 | * Writer for FBX binary (`writer` module) 20 | + FBX 7.4 and 7.5 is explicitly supported. 21 | + This is optional and enabled by `writer` feature. 22 | * Types and functions for low-level FBX tree access 23 | + This is optional and enabled by `tree` feature. 24 | + Provides arena-based tree type. 25 | 26 | ### FBX versions 27 | 28 | * FBX 6 or below is not supported. 29 | * FBX 7.0 to 7.3 is not explicitly supported, but you can try FBX 7.4 feature to load them. 30 | * FBX 7.4 and 7.5 is supported. 31 | 32 | ### FBX format 33 | 34 | Only FBX binary format is supported. 35 | 36 | Currently there is no plan to support FBX ASCII format. 37 | 38 | 39 | ## Rust version 40 | 41 | Latest stable compiler (currently 1.52) is supported. 42 | 43 | ## License 44 | 45 | Licensed under either of 46 | 47 | * Apache License, Version 2.0, ([LICENSE-APACHE.txt](LICENSE-APACHE.txt) or 48 | ) 49 | * MIT license ([LICENSE-MIT.txt](LICENSE-MIT.txt) or 50 | ) 51 | 52 | at your option. 53 | 54 | ### Contribution 55 | 56 | Unless you explicitly state otherwise, any contribution intentionally submitted 57 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be 58 | dual licensed as above, without any additional terms or conditions. 59 | -------------------------------------------------------------------------------- /examples/dump-pull-parser-events.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | fs::File, 3 | io::{self, BufReader}, 4 | path::PathBuf, 5 | }; 6 | 7 | use fbxcel::pull_parser::{self, any::AnyParser}; 8 | 9 | fn main() { 10 | env_logger::init(); 11 | 12 | let path = match std::env::args_os().nth(1) { 13 | Some(v) => PathBuf::from(v), 14 | None => { 15 | eprintln!("Usage: dump-pull-parser-events "); 16 | std::process::exit(1); 17 | } 18 | }; 19 | let file = File::open(path).expect("Failed to open file"); 20 | let reader = BufReader::new(file); 21 | 22 | match AnyParser::from_seekable_reader(reader).expect("Failed to create parser") { 23 | AnyParser::V7400(mut parser) => { 24 | let version = parser.fbx_version(); 25 | println!("FBX version: {}.{}", version.major(), version.minor()); 26 | parser.set_warning_handler(|w, pos| { 27 | eprintln!("WARNING: {} (pos={:?})", w, pos); 28 | Ok(()) 29 | }); 30 | dump_fbx_7400(parser).expect("Failed to parse FBX file"); 31 | } 32 | parser => panic!( 33 | "Unsupported by this example: fbx_version={:?}", 34 | parser.fbx_version() 35 | ), 36 | } 37 | } 38 | 39 | fn indent(depth: usize) { 40 | print!("{:depth$}", "", depth = depth * 4); 41 | } 42 | 43 | fn dump_fbx_7400( 44 | mut parser: pull_parser::v7400::Parser, 45 | ) -> pull_parser::Result<()> { 46 | let mut depth = 0; 47 | 48 | /// Dump format of node attributes. 49 | enum AttrsDumpFormat { 50 | /// Type only. 51 | Type, 52 | /// Value for primitive types, length for array, binary, and string. 53 | Length, 54 | /// Values for all types. 55 | /// 56 | /// Not recommended because the output might be quite large. 57 | Full, 58 | } 59 | 60 | let attrs_dump_format = match std::env::var("DUMP_ATTRIBUTES").as_ref().map(AsRef::as_ref) { 61 | Ok("length") => AttrsDumpFormat::Length, 62 | Ok("full") => AttrsDumpFormat::Full, 63 | _ => AttrsDumpFormat::Type, 64 | }; 65 | 66 | loop { 67 | use self::pull_parser::v7400::*; 68 | 69 | match parser.next_event()? { 70 | Event::StartNode(start) => { 71 | indent(depth); 72 | println!("Node start: {:?}", start.name()); 73 | depth += 1; 74 | 75 | let attrs = start.attributes(); 76 | match attrs_dump_format { 77 | AttrsDumpFormat::Type => dump_v7400_attributes_type(depth, attrs)?, 78 | AttrsDumpFormat::Length => dump_v7400_attributes_length(depth, attrs)?, 79 | AttrsDumpFormat::Full => dump_v7400_attributes_full(depth, attrs)?, 80 | } 81 | } 82 | Event::EndNode => { 83 | depth -= 1; 84 | indent(depth); 85 | println!("Node end"); 86 | } 87 | Event::EndFbx(footer_res) => { 88 | println!("FBX end"); 89 | match footer_res { 90 | Ok(footer) => println!("footer: {:#?}", footer), 91 | Err(e) => println!("footer has an error: {:?}", e), 92 | } 93 | break; 94 | } 95 | } 96 | } 97 | 98 | Ok(()) 99 | } 100 | 101 | fn dump_v7400_attributes_length( 102 | depth: usize, 103 | mut attrs: pull_parser::v7400::Attributes<'_, R>, 104 | ) -> pull_parser::Result<()> 105 | where 106 | R: io::Read, 107 | { 108 | use fbxcel::{ 109 | low::v7400::AttributeValue, pull_parser::v7400::attribute::loaders::DirectLoader, 110 | }; 111 | 112 | while let Some(attr) = attrs.load_next(DirectLoader)? { 113 | let type_ = attr.type_(); 114 | indent(depth); 115 | match attr { 116 | AttributeValue::Bool(_) => println!("Attribute: {:?}", attr), 117 | AttributeValue::I16(_) => println!("Attribute: {:?}", attr), 118 | AttributeValue::I32(_) => println!("Attribute: {:?}", attr), 119 | AttributeValue::I64(_) => println!("Attribute: {:?}", attr), 120 | AttributeValue::F32(_) => println!("Attribute: {:?}", attr), 121 | AttributeValue::F64(_) => println!("Attribute: {:?}", attr), 122 | AttributeValue::ArrBool(v) => println!("Attribute: type={:?}, len={}", type_, v.len()), 123 | AttributeValue::ArrI32(v) => println!("Attribute: type={:?}, len={}", type_, v.len()), 124 | AttributeValue::ArrI64(v) => println!("Attribute: type={:?}, len={}", type_, v.len()), 125 | AttributeValue::ArrF32(v) => println!("Attribute: type={:?}, len={}", type_, v.len()), 126 | AttributeValue::ArrF64(v) => println!("Attribute: type={:?}, len={}", type_, v.len()), 127 | AttributeValue::Binary(v) => println!("Attribute: type={:?}, len={}", type_, v.len()), 128 | AttributeValue::String(v) => println!("Attribute: type={:?}, len={}", type_, v.len()), 129 | } 130 | } 131 | 132 | Ok(()) 133 | } 134 | 135 | fn dump_v7400_attributes_type( 136 | depth: usize, 137 | mut attrs: pull_parser::v7400::Attributes<'_, R>, 138 | ) -> pull_parser::Result<()> 139 | where 140 | R: io::Read, 141 | { 142 | use self::pull_parser::v7400::attribute::loaders::TypeLoader; 143 | 144 | while let Some(type_) = attrs.load_next(TypeLoader).unwrap() { 145 | indent(depth); 146 | println!("Attribute: {:?}", type_); 147 | } 148 | 149 | Ok(()) 150 | } 151 | 152 | fn dump_v7400_attributes_full( 153 | depth: usize, 154 | mut attrs: pull_parser::v7400::Attributes<'_, R>, 155 | ) -> pull_parser::Result<()> 156 | where 157 | R: io::Read, 158 | { 159 | use fbxcel::{ 160 | low::v7400::AttributeValue, pull_parser::v7400::attribute::loaders::DirectLoader, 161 | }; 162 | 163 | while let Some(attr) = attrs.load_next(DirectLoader)? { 164 | let type_ = attr.type_(); 165 | indent(depth); 166 | match attr { 167 | AttributeValue::Bool(_) => println!("Attribute: {:?}", attr), 168 | AttributeValue::I16(_) => println!("Attribute: {:?}", attr), 169 | AttributeValue::I32(_) => println!("Attribute: {:?}", attr), 170 | AttributeValue::I64(_) => println!("Attribute: {:?}", attr), 171 | AttributeValue::F32(_) => println!("Attribute: {:?}", attr), 172 | AttributeValue::F64(_) => println!("Attribute: {:?}", attr), 173 | AttributeValue::ArrBool(v) => println!( 174 | "Attribute: type={:?}, len={}, value={:?}", 175 | type_, 176 | v.len(), 177 | v 178 | ), 179 | AttributeValue::ArrI32(v) => println!( 180 | "Attribute: type={:?}, len={}, value={:?}", 181 | type_, 182 | v.len(), 183 | v 184 | ), 185 | AttributeValue::ArrI64(v) => println!( 186 | "Attribute: type={:?}, len={}, value={:?}", 187 | type_, 188 | v.len(), 189 | v 190 | ), 191 | AttributeValue::ArrF32(v) => println!( 192 | "Attribute: type={:?}, len={}, value={:?}", 193 | type_, 194 | v.len(), 195 | v 196 | ), 197 | AttributeValue::ArrF64(v) => println!( 198 | "Attribute: type={:?}, len={}, value={:?}", 199 | type_, 200 | v.len(), 201 | v 202 | ), 203 | AttributeValue::Binary(v) => println!( 204 | "Attribute: type={:?}, len={}, value={:?}", 205 | type_, 206 | v.len(), 207 | v 208 | ), 209 | AttributeValue::String(v) => println!( 210 | "Attribute: type={:?}, len={}, value={:?}", 211 | type_, 212 | v.len(), 213 | v 214 | ), 215 | } 216 | } 217 | 218 | Ok(()) 219 | } 220 | -------------------------------------------------------------------------------- /examples/load-tree.rs: -------------------------------------------------------------------------------- 1 | use std::{fs::File, io::BufReader, path::PathBuf}; 2 | 3 | use fbxcel::tree::any::AnyTree; 4 | 5 | pub fn main() { 6 | env_logger::init(); 7 | 8 | let path = match std::env::args_os().nth(1) { 9 | Some(v) => PathBuf::from(v), 10 | None => { 11 | eprintln!("Usage: load-tree "); 12 | std::process::exit(1); 13 | } 14 | }; 15 | let file = File::open(path).expect("Failed to open file"); 16 | let reader = BufReader::new(file); 17 | 18 | match AnyTree::from_seekable_reader(reader).expect("Failed to load tree") { 19 | AnyTree::V7400(fbx_version, tree, footer) => { 20 | println!("FBX version = {:#?}", fbx_version); 21 | println!("tree = {:#?}", tree); 22 | println!("footer = {:#?}", footer); 23 | } 24 | _ => panic!("FBX version unsupported by this example"), 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! The excellent FBX library. 2 | //! 3 | //! [`low`] module provides low-level data types such as FBX header, node 4 | //! attribute value, etc. 5 | //! 6 | //! [`pull_parser`] module provides pull parser for FBX binary format. 7 | //! ASCII format is not supported. 8 | //! 9 | #![cfg_attr(feature = "tree", doc = "[`tree`] ")] 10 | #![cfg_attr(not(feature = "tree"), doc = "`tree` ")] 11 | //! module provides tree types, which allow users to access FBX data as 12 | //! tree, not as stream of parser events. 13 | //! To use `tree` module, enable `tree` feature. 14 | //! 15 | #![cfg_attr(feature = "writer", doc = "[`writer`] ")] 16 | #![cfg_attr(not(feature = "writer"), doc = "`writer` ")] 17 | //! module provides writer types. 18 | //! To use `writer` module, enable `writer` feature. 19 | #![cfg_attr(docsrs, feature(doc_cfg))] 20 | #![forbid(unsafe_code)] 21 | #![warn(missing_docs)] 22 | #![warn(clippy::missing_docs_in_private_items)] 23 | 24 | pub mod low; 25 | pub mod pull_parser; 26 | #[cfg(feature = "tree")] 27 | #[cfg_attr(docsrs, doc(cfg(feature = "tree")))] 28 | pub mod tree; 29 | #[cfg(feature = "writer")] 30 | #[cfg_attr(docsrs, doc(cfg(feature = "writer")))] 31 | pub mod writer; 32 | -------------------------------------------------------------------------------- /src/low.rs: -------------------------------------------------------------------------------- 1 | //! Low-level or primitive data types for FBX binary. 2 | 3 | #[cfg(feature = "writer")] 4 | #[cfg_attr(docsrs, doc(cfg(feature = "writer")))] 5 | pub(crate) use self::fbx_header::MAGIC; 6 | pub use self::{ 7 | fbx_header::{FbxHeader, HeaderError}, 8 | version::FbxVersion, 9 | }; 10 | 11 | mod fbx_header; 12 | pub mod v7400; 13 | mod version; 14 | -------------------------------------------------------------------------------- /src/low/fbx_header.rs: -------------------------------------------------------------------------------- 1 | //! FBX binary header. 2 | 3 | use std::{error, fmt, io}; 4 | 5 | use log::info; 6 | 7 | use crate::{low::FbxVersion, pull_parser::ParserVersion}; 8 | 9 | /// Magic binary length. 10 | const MAGIC_LEN: usize = 23; 11 | 12 | /// Magic binary. 13 | pub(crate) const MAGIC: &[u8; MAGIC_LEN] = b"Kaydara FBX Binary \x00\x1a\x00"; 14 | 15 | /// Header read error. 16 | #[derive(Debug)] 17 | pub enum HeaderError { 18 | /// I/O error. 19 | Io(io::Error), 20 | /// Magic binary is not detected. 21 | MagicNotDetected, 22 | } 23 | 24 | impl error::Error for HeaderError {} 25 | 26 | impl fmt::Display for HeaderError { 27 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 28 | match self { 29 | HeaderError::Io(e) => e.fmt(f), 30 | HeaderError::MagicNotDetected => f.write_str("FBX magic binary is not detected"), 31 | } 32 | } 33 | } 34 | 35 | impl From for HeaderError { 36 | #[inline] 37 | fn from(e: io::Error) -> Self { 38 | HeaderError::Io(e) 39 | } 40 | } 41 | 42 | /// FBX binary header. 43 | /// 44 | /// This type represents a binary header for all supported versions of FBX. 45 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 46 | pub struct FbxHeader { 47 | /// FBX version. 48 | version: FbxVersion, 49 | } 50 | 51 | impl FbxHeader { 52 | /// Reads an FBX header from the given reader. 53 | pub fn load(mut reader: impl io::Read) -> Result { 54 | // Check magic. 55 | let mut magic_buf = [0u8; MAGIC_LEN]; 56 | reader.read_exact(&mut magic_buf)?; 57 | if magic_buf != *MAGIC { 58 | return Err(HeaderError::MagicNotDetected); 59 | } 60 | 61 | // Read FBX version. 62 | let version = { 63 | let mut buf = [0_u8; 4]; 64 | reader.read_exact(&mut buf)?; 65 | u32::from_le_bytes(buf) 66 | }; 67 | info!("FBX header is detected, version={}", version); 68 | 69 | Ok(FbxHeader { 70 | version: FbxVersion::new(version), 71 | }) 72 | } 73 | 74 | /// Returns FBX version. 75 | #[inline] 76 | #[must_use] 77 | pub fn version(self) -> FbxVersion { 78 | self.version 79 | } 80 | 81 | /// Returns FBX parser version. 82 | /// 83 | /// Returns `None` if no parser supports the FBX version. 84 | #[inline] 85 | #[must_use] 86 | pub fn parser_version(self) -> Option { 87 | ParserVersion::from_fbx_version(self.version()) 88 | } 89 | 90 | /// Returns header length in bytes. 91 | #[inline] 92 | #[must_use] 93 | pub(crate) const fn len(self) -> usize { 94 | /// FBX version length. 95 | const VERSION_LEN: usize = 4; 96 | 97 | MAGIC_LEN + VERSION_LEN 98 | } 99 | } 100 | 101 | #[cfg(test)] 102 | mod tests { 103 | use super::*; 104 | use std::io::{Cursor, Read}; 105 | 106 | #[test] 107 | fn header_ok() { 108 | let raw_header = b"Kaydara FBX Binary \x00\x1a\x00\xe8\x1c\x00\x00"; 109 | let mut cursor = Cursor::new(raw_header); 110 | let header = FbxHeader::load(cursor.by_ref()).expect("Should never fail"); 111 | assert_eq!( 112 | header.version(), 113 | FbxVersion::new(7400), 114 | "Header and version should be detected correctly" 115 | ); 116 | assert_eq!( 117 | cursor.position() as usize, 118 | raw_header.len(), 119 | "Header should be read completely" 120 | ); 121 | } 122 | 123 | #[test] 124 | fn magic_ng() { 125 | let wrong_header = b"Kaydara FBX Binary \x00\xff\x00\xe8\x1c\x00\x00"; 126 | let mut cursor = Cursor::new(wrong_header); 127 | // `HeaderError` may contain `io::Error` and is not comparable. 128 | assert!( 129 | matches!( 130 | FbxHeader::load(cursor.by_ref()), 131 | Err(HeaderError::MagicNotDetected) 132 | ), 133 | "Invalid magic should be reported by `MagicNotDetected`" 134 | ); 135 | assert!( 136 | (cursor.position() as usize) < wrong_header.len(), 137 | "Header should not be read too much if the magic is not detected" 138 | ); 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /src/low/v7400.rs: -------------------------------------------------------------------------------- 1 | //! Low-level or primitive data types for FBX 7.4 and compatible versions. 2 | 3 | pub use self::{ 4 | array_attribute::ArrayAttributeEncoding, 5 | attribute::{type_::AttributeType, value::AttributeValue}, 6 | fbx_footer::FbxFooter, 7 | }; 8 | pub(crate) use self::{ 9 | array_attribute::ArrayAttributeHeader, node_header::NodeHeader, 10 | special_attribute::SpecialAttributeHeader, 11 | }; 12 | 13 | mod array_attribute; 14 | mod attribute; 15 | mod fbx_footer; 16 | mod node_header; 17 | mod special_attribute; 18 | -------------------------------------------------------------------------------- /src/low/v7400/array_attribute.rs: -------------------------------------------------------------------------------- 1 | //! Low-level data types related to array type node attributes. 2 | 3 | use std::io; 4 | 5 | use crate::pull_parser::{ 6 | error::{Compression, DataError}, 7 | v7400::FromReader, 8 | Error as ParserError, 9 | }; 10 | 11 | /// Array attribute encoding. 12 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 13 | pub enum ArrayAttributeEncoding { 14 | /// Direct values. 15 | Direct, 16 | /// Zlib compression. 17 | /// 18 | /// Zlib compression with header. 19 | Zlib, 20 | } 21 | 22 | impl ArrayAttributeEncoding { 23 | /// Creates a new `ArrayEncoding` from the given raw value. 24 | #[inline] 25 | #[must_use] 26 | pub(crate) fn from_u32(v: u32) -> Option { 27 | match v { 28 | 0 => Some(ArrayAttributeEncoding::Direct), 29 | 1 => Some(ArrayAttributeEncoding::Zlib), 30 | _ => None, 31 | } 32 | } 33 | 34 | /// Returns the raw value. 35 | #[cfg(feature = "writer")] 36 | #[cfg_attr(docsrs, doc(cfg(feature = "writer")))] 37 | #[inline] 38 | #[must_use] 39 | pub(crate) fn to_u32(self) -> u32 { 40 | match self { 41 | ArrayAttributeEncoding::Direct => 0, 42 | ArrayAttributeEncoding::Zlib => 1, 43 | } 44 | } 45 | } 46 | 47 | impl From for Compression { 48 | // Panics if the encoding is `Direct` (i.e. not compressed). 49 | #[inline] 50 | fn from(v: ArrayAttributeEncoding) -> Self { 51 | match v { 52 | ArrayAttributeEncoding::Direct => unreachable!( 53 | "Data with `ArrayEncoding::Direct` should not cause (de)compression error" 54 | ), 55 | ArrayAttributeEncoding::Zlib => Compression::Zlib, 56 | } 57 | } 58 | } 59 | 60 | impl FromReader for ArrayAttributeEncoding { 61 | fn from_reader(reader: &mut impl io::Read) -> Result { 62 | let raw_encoding = u32::from_reader(reader)?; 63 | let encoding = ArrayAttributeEncoding::from_u32(raw_encoding) 64 | .ok_or(DataError::InvalidArrayAttributeEncoding(raw_encoding))?; 65 | Ok(encoding) 66 | } 67 | } 68 | 69 | /// A header type for array-type attributes. 70 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 71 | pub(crate) struct ArrayAttributeHeader { 72 | /// Number of elements. 73 | pub(crate) elements_count: u32, 74 | /// Encoding. 75 | pub(crate) encoding: ArrayAttributeEncoding, 76 | /// Elements length in bytes. 77 | pub(crate) bytelen: u32, 78 | } 79 | 80 | impl FromReader for ArrayAttributeHeader { 81 | fn from_reader(reader: &mut impl io::Read) -> Result { 82 | let elements_count = u32::from_reader(reader)?; 83 | let encoding = ArrayAttributeEncoding::from_reader(reader)?; 84 | let bytelen = u32::from_reader(reader)?; 85 | 86 | Ok(Self { 87 | elements_count, 88 | encoding, 89 | bytelen, 90 | }) 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/low/v7400/attribute.rs: -------------------------------------------------------------------------------- 1 | //! Node attribute. 2 | 3 | pub(crate) mod type_; 4 | pub(crate) mod value; 5 | -------------------------------------------------------------------------------- /src/low/v7400/attribute/type_.rs: -------------------------------------------------------------------------------- 1 | //! Node attribute type. 2 | 3 | use std::io; 4 | 5 | use crate::pull_parser::{error::DataError, v7400::FromReader, Error as ParserError}; 6 | 7 | /// Node attribute type. 8 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 9 | pub enum AttributeType { 10 | /// Single `bool`. 11 | Bool, 12 | /// Single `i16`. 13 | I16, 14 | /// Single `i32`. 15 | I32, 16 | /// Single `i64`. 17 | I64, 18 | /// Single `f32`. 19 | F32, 20 | /// Single `f64`. 21 | F64, 22 | /// Array of `bool`. 23 | ArrBool, 24 | /// Array of `i32`. 25 | ArrI32, 26 | /// Array of `i64`. 27 | ArrI64, 28 | /// Array of `f32`. 29 | ArrF32, 30 | /// Array of `f64`. 31 | ArrF64, 32 | /// Binary. 33 | Binary, 34 | /// UTF-8 string. 35 | String, 36 | } 37 | 38 | impl AttributeType { 39 | /// Creates an `AttributeType` from the given type code. 40 | #[must_use] 41 | pub(crate) fn from_type_code(code: u8) -> Option { 42 | match code { 43 | b'C' => Some(AttributeType::Bool), 44 | b'Y' => Some(AttributeType::I16), 45 | b'I' => Some(AttributeType::I32), 46 | b'L' => Some(AttributeType::I64), 47 | b'F' => Some(AttributeType::F32), 48 | b'D' => Some(AttributeType::F64), 49 | b'b' => Some(AttributeType::ArrBool), 50 | b'i' => Some(AttributeType::ArrI32), 51 | b'l' => Some(AttributeType::ArrI64), 52 | b'f' => Some(AttributeType::ArrF32), 53 | b'd' => Some(AttributeType::ArrF64), 54 | b'R' => Some(AttributeType::Binary), 55 | b'S' => Some(AttributeType::String), 56 | _ => None, 57 | } 58 | } 59 | 60 | /// Returns the type code. 61 | #[cfg(feature = "writer")] 62 | #[cfg_attr(docsrs, doc(cfg(feature = "writer")))] 63 | #[must_use] 64 | pub(crate) fn type_code(self) -> u8 { 65 | match self { 66 | AttributeType::Bool => b'C', 67 | AttributeType::I16 => b'Y', 68 | AttributeType::I32 => b'I', 69 | AttributeType::I64 => b'L', 70 | AttributeType::F32 => b'F', 71 | AttributeType::F64 => b'D', 72 | AttributeType::ArrBool => b'b', 73 | AttributeType::ArrI32 => b'i', 74 | AttributeType::ArrI64 => b'l', 75 | AttributeType::ArrF32 => b'f', 76 | AttributeType::ArrF64 => b'd', 77 | AttributeType::Binary => b'R', 78 | AttributeType::String => b'S', 79 | } 80 | } 81 | } 82 | 83 | impl FromReader for AttributeType { 84 | fn from_reader(reader: &mut impl io::Read) -> Result { 85 | let type_code = u8::from_reader(reader)?; 86 | let attr_type = Self::from_type_code(type_code) 87 | .ok_or(DataError::InvalidAttributeTypeCode(type_code))?; 88 | Ok(attr_type) 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/low/v7400/attribute/value.rs: -------------------------------------------------------------------------------- 1 | //! Node attribute value. 2 | 3 | use crate::low::v7400::AttributeType; 4 | 5 | /// Node attribute value. 6 | /// 7 | /// To get a value of the specific type easily, use `get_*()` or 8 | /// `get_*_or_type()` method. 9 | /// 10 | /// * `get_*()` returns `Option<_>`. 11 | /// + If a value of the expected type available, returns `Some(_)`. 12 | /// + If not, returns `None`. 13 | /// * `get_*_or_type()` returns `Result<_, AttributeType>`. 14 | /// + If a value of the expected type available, returns `Ok(_)`. 15 | /// + If not, returns `Ok(ty)` where `ty` is value type (same value as 16 | /// returned by [`type_`][`Self::type_()`] method. 17 | #[derive(Debug, Clone, PartialEq)] 18 | pub enum AttributeValue { 19 | /// Single `bool`. 20 | Bool(bool), 21 | /// Single `i16`. 22 | I16(i16), 23 | /// Single `i32`. 24 | I32(i32), 25 | /// Single `i64`. 26 | I64(i64), 27 | /// Single `f32`. 28 | F32(f32), 29 | /// Single `f64`. 30 | F64(f64), 31 | /// Array of `bool`. 32 | ArrBool(Vec), 33 | /// Array of `i32`. 34 | ArrI32(Vec), 35 | /// Array of `i64`. 36 | ArrI64(Vec), 37 | /// Array of `f32`. 38 | ArrF32(Vec), 39 | /// Array of `f64`. 40 | ArrF64(Vec), 41 | /// UTF-8 string. 42 | String(String), 43 | /// Binary. 44 | Binary(Vec), 45 | } 46 | 47 | /// Implement direct value getters. 48 | macro_rules! impl_val_getter { 49 | ($variant:ident, $ty_ret:ty, $opt_getter:ident, $opt_doc:expr, $res_getter:ident, $res_doc:expr,) => { 50 | #[doc = $opt_doc] 51 | #[inline] 52 | #[must_use] 53 | pub fn $opt_getter(&self) -> Option<$ty_ret> { 54 | match self { 55 | AttributeValue::$variant(v) => Some(*v), 56 | _ => None, 57 | } 58 | } 59 | 60 | #[doc = $res_doc] 61 | pub fn $res_getter(&self) -> Result<$ty_ret, AttributeType> { 62 | match self { 63 | AttributeValue::$variant(v) => Ok(*v), 64 | _ => Err(self.type_()), 65 | } 66 | } 67 | }; 68 | } 69 | 70 | /// Implement value reference getters. 71 | macro_rules! impl_ref_getter { 72 | ($variant:ident, $ty_ret:ty, $opt_getter:ident, $opt_doc:expr, $res_getter:ident, $res_doc:expr,) => { 73 | #[doc = $opt_doc] 74 | #[inline] 75 | #[must_use] 76 | pub fn $opt_getter(&self) -> Option<&$ty_ret> { 77 | match self { 78 | AttributeValue::$variant(v) => Some(v), 79 | _ => None, 80 | } 81 | } 82 | 83 | #[doc = $res_doc] 84 | pub fn $res_getter(&self) -> Result<&$ty_ret, AttributeType> { 85 | match self { 86 | AttributeValue::$variant(v) => Ok(v), 87 | _ => Err(self.type_()), 88 | } 89 | } 90 | }; 91 | } 92 | 93 | impl AttributeValue { 94 | /// Returns the value type. 95 | #[must_use] 96 | pub fn type_(&self) -> AttributeType { 97 | match self { 98 | AttributeValue::Bool(_) => AttributeType::Bool, 99 | AttributeValue::I16(_) => AttributeType::I16, 100 | AttributeValue::I32(_) => AttributeType::I32, 101 | AttributeValue::I64(_) => AttributeType::I64, 102 | AttributeValue::F32(_) => AttributeType::F32, 103 | AttributeValue::F64(_) => AttributeType::F64, 104 | AttributeValue::ArrBool(_) => AttributeType::ArrBool, 105 | AttributeValue::ArrI32(_) => AttributeType::ArrI32, 106 | AttributeValue::ArrI64(_) => AttributeType::ArrI64, 107 | AttributeValue::ArrF32(_) => AttributeType::ArrF32, 108 | AttributeValue::ArrF64(_) => AttributeType::ArrF64, 109 | AttributeValue::String(_) => AttributeType::String, 110 | AttributeValue::Binary(_) => AttributeType::Binary, 111 | } 112 | } 113 | 114 | impl_val_getter! { 115 | Bool, 116 | bool, 117 | get_bool, 118 | "Returns the the inner `bool` value, if available.", 119 | get_bool_or_type, 120 | "Returns the the inner `bool` value, if available.\n\nReturns `Err(type)` on type mismatch.", 121 | } 122 | 123 | impl_val_getter! { 124 | I16, 125 | i16, 126 | get_i16, 127 | "Returns the the inner `i16` value, if available.", 128 | get_i16_or_type, 129 | "Returns the the inner `i16` value, if available.\n\nReturns `Err(type)` on type mismatch.", 130 | } 131 | 132 | impl_val_getter! { 133 | I32, 134 | i32, 135 | get_i32, 136 | "Returns the the inner `i32` value, if available.", 137 | get_i32_or_type, 138 | "Returns the the inner `i32` value, if available.\n\nReturns `Err(type)` on type mismatch.", 139 | } 140 | 141 | impl_val_getter! { 142 | I64, 143 | i64, 144 | get_i64, 145 | "Returns the the inner `i64` value, if available.", 146 | get_i64_or_type, 147 | "Returns the the inner `i64` value, if available.\n\nReturns `Err(type)` on type mismatch.", 148 | } 149 | 150 | impl_val_getter! { 151 | F32, 152 | f32, 153 | get_f32, 154 | "Returns the the inner `f32` value, if available.", 155 | get_f32_or_type, 156 | "Returns the the inner `f32` value, if available.\n\nReturns `Err(type)` on type mismatch.", 157 | } 158 | 159 | impl_val_getter! { 160 | F64, 161 | f64, 162 | get_f64, 163 | "Returns the the inner `f64` value, if available.", 164 | get_f64_or_type, 165 | "Returns the the inner `f64` value, if available.\n\nReturns `Err(type)` on type mismatch.", 166 | } 167 | 168 | impl_ref_getter! { 169 | ArrBool, 170 | [bool], 171 | get_arr_bool, 172 | "Returns the reference to the inner `bool` slice, if available.", 173 | get_arr_bool_or_type, 174 | "Returns the reference to the inner `bool` slice, if available.\n\nReturns `Err(type)` on type mismatch.", 175 | } 176 | 177 | impl_ref_getter! { 178 | ArrI32, 179 | [i32], 180 | get_arr_i32, 181 | "Returns the reference to the inner `i32` slice, if available.", 182 | get_arr_i32_or_type, 183 | "Returns the reference to the inner `i32` slice, if available.\n\nReturns `Err(type)` on type mismatch.", 184 | } 185 | 186 | impl_ref_getter! { 187 | ArrI64, 188 | [i64], 189 | get_arr_i64, 190 | "Returns the reference to the inner `i64` slice, if available.", 191 | get_arr_i64_or_type, 192 | "Returns the reference to the inner `i64` slice, if available.\n\nReturns `Err(type)` on type mismatch.", 193 | } 194 | 195 | impl_ref_getter! { 196 | ArrF32, 197 | [f32], 198 | get_arr_f32, 199 | "Returns the reference to the inner `f32` slice, if available.", 200 | get_arr_f32_or_type, 201 | "Returns the reference to the inner `f32` slice, if available.\n\nReturns `Err(type)` on type mismatch.", 202 | } 203 | 204 | impl_ref_getter! { 205 | ArrF64, 206 | [f64], 207 | get_arr_f64, 208 | "Returns the reference to the inner `f64` slice, if available.", 209 | get_arr_f64_or_type, 210 | "Returns the reference to the inner `f64` slice, if available.\n\nReturns `Err(type)` on type mismatch.", 211 | } 212 | 213 | impl_ref_getter! { 214 | String, 215 | str, 216 | get_string, 217 | "Returns the reference to the inner string slice, if available.", 218 | get_string_or_type, 219 | "Returns the reference to the inner string slice, if available.\n\nReturns `Err(type)` on type mismatch.", 220 | } 221 | 222 | impl_ref_getter! { 223 | Binary, 224 | [u8], 225 | get_binary, 226 | "Returns the reference to the inner binary data, if available.", 227 | get_binary_or_type, 228 | "Returns the reference to the inner binary data, if available.\n\nReturns `Err(type)` on type mismatch.", 229 | } 230 | 231 | /// Compares attribute values strictly. 232 | /// 233 | /// "Strictly" means, `f32` and `f64` values are compared bitwise. 234 | pub fn strict_eq(&self, other: &Self) -> bool { 235 | use AttributeValue::*; 236 | 237 | match (self, other) { 238 | (Bool(l), Bool(r)) => l == r, 239 | (I16(l), I16(r)) => l == r, 240 | (I32(l), I32(r)) => l == r, 241 | (I64(l), I64(r)) => l == r, 242 | (F32(l), F32(r)) => l.to_bits() == r.to_bits(), 243 | (F64(l), F64(r)) => l.to_bits() == r.to_bits(), 244 | (ArrBool(l), ArrBool(r)) => l == r, 245 | (ArrI32(l), ArrI32(r)) => l == r, 246 | (ArrI64(l), ArrI64(r)) => l == r, 247 | (ArrF32(l), ArrF32(r)) => l 248 | .iter() 249 | .map(|v| v.to_bits()) 250 | .eq(r.iter().map(|v| v.to_bits())), 251 | (ArrF64(l), ArrF64(r)) => l 252 | .iter() 253 | .map(|v| v.to_bits()) 254 | .eq(r.iter().map(|v| v.to_bits())), 255 | (Binary(l), Binary(r)) => l == r, 256 | (String(l), String(r)) => l == r, 257 | _ => false, 258 | } 259 | } 260 | } 261 | 262 | /// Implement `From` trait. 263 | macro_rules! impl_from { 264 | (direct: $ty:ty, $variant:ident) => { 265 | impl From<$ty> for AttributeValue { 266 | #[inline] 267 | fn from(v: $ty) -> Self { 268 | AttributeValue::$variant(v.into()) 269 | } 270 | } 271 | }; 272 | (map: $ty:ty, $variant:ident, $arg:ident, $v:expr) => { 273 | impl From<$ty> for AttributeValue { 274 | #[inline] 275 | fn from($arg: $ty) -> Self { 276 | AttributeValue::$variant($v) 277 | } 278 | } 279 | }; 280 | } 281 | 282 | impl_from! { direct: bool, Bool } 283 | impl_from! { direct: i16, I16 } 284 | impl_from! { direct: i32, I32 } 285 | impl_from! { direct: i64, I64 } 286 | impl_from! { direct: f32, F32 } 287 | impl_from! { direct: f64, F64 } 288 | impl_from! { direct: Vec, ArrBool } 289 | impl_from! { direct: Vec, ArrI32 } 290 | impl_from! { direct: Vec, ArrI64 } 291 | impl_from! { direct: Vec, ArrF32 } 292 | impl_from! { direct: Vec, ArrF64 } 293 | impl_from! { direct: Vec, Binary } 294 | impl_from! { direct: String, String } 295 | impl_from! { map: &[bool], ArrBool, v, v.to_owned() } 296 | impl_from! { map: &[i32], ArrI32, v, v.to_owned() } 297 | impl_from! { map: &[i64], ArrI64, v, v.to_owned() } 298 | impl_from! { map: &[f32], ArrF32, v, v.to_owned() } 299 | impl_from! { map: &[f64], ArrF64, v, v.to_owned() } 300 | impl_from! { map: &[u8], Binary, v, v.to_owned() } 301 | impl_from! { map: &str, String, v, v.to_owned() } 302 | -------------------------------------------------------------------------------- /src/low/v7400/fbx_footer.rs: -------------------------------------------------------------------------------- 1 | //! FBX 7.4 footer. 2 | 3 | use std::io::{self, Read}; 4 | 5 | use log::debug; 6 | 7 | use crate::{ 8 | low::FbxVersion, 9 | pull_parser::{ 10 | error::DataError, 11 | v7400::{FromParser, Parser}, 12 | Error as ParserError, SyntacticPosition, Warning, 13 | }, 14 | }; 15 | 16 | /// FBX 7.4 footer. 17 | /// 18 | /// Data contained in a FBX 7.4 footer is not useful for normal usage. 19 | /// Most of users can safely ignore the footer. 20 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 21 | pub struct FbxFooter { 22 | /// Unknown (semirandom) 16-bytes data. 23 | /// 24 | /// This field is expected to have prescribed upper 4 bits, i.e. the field 25 | /// is `fx bx ax 0x dx cx dx 6x bx 7x fx 8x 1x fx 2x 7x` if the FBX data is 26 | /// exported from official SDK. 27 | /// 28 | /// Note that third party exporter will use completely random data. 29 | pub unknown1: [u8; 16], 30 | /// Padding length. 31 | /// 32 | /// Padding is `padding_len` `0`s. 33 | /// `padding_len >= 0 && padding <= 15` should hold. 34 | /// 35 | /// Note that third party exporter will not use correct padding length. 36 | pub padding_len: u8, 37 | /// Unknown 4-bytes data. 38 | /// 39 | /// This is expected to be `[0u8; 4]`. 40 | pub unknown2: [u8; 4], 41 | /// FBX version. 42 | /// 43 | /// This is expected to be same as the version in header. 44 | pub fbx_version: FbxVersion, 45 | /// Unknown 16-bytes data. 46 | /// 47 | /// This is expected to be `[0xf8, 0x5a, 0x8c, 0x6a, 0xde, 0xf5, 0xd9, 0x7e, 48 | /// 0xec, 0xe9, 0x0c, 0xe3, 0x75, 0x8f, 0x29, 0x0b]`. 49 | pub unknown3: [u8; 16], 50 | } 51 | 52 | impl FromParser for FbxFooter { 53 | fn read_from_parser(parser: &mut Parser) -> Result 54 | where 55 | R: io::Read, 56 | { 57 | let start_pos = parser.reader().position(); 58 | 59 | // Read unknown field 1. 60 | let unknown1 = { 61 | /// Expected upper 4-bits of the unknown field 1. 62 | const EXPECTED: [u8; 16] = [ 63 | 0xf0, 0xb0, 0xa0, 0x00, 0xd0, 0xc0, 0xd0, 0x60, 0xb0, 0x70, 0xf0, 0x80, 0x10, 0xf0, 64 | 0x20, 0x70, 65 | ]; 66 | let mut buf = [0u8; 16]; 67 | parser.reader().read_exact(&mut buf)?; 68 | 69 | for (byte, expected) in buf.iter().zip(&EXPECTED) { 70 | if (byte & 0xf0) != *expected { 71 | let pos = SyntacticPosition { 72 | byte_pos: parser.reader().position() - 16, 73 | component_byte_pos: start_pos, 74 | node_path: Vec::new(), 75 | attribute_index: None, 76 | }; 77 | parser.warn(Warning::UnexpectedFooterFieldValue, pos)?; 78 | break; 79 | } 80 | } 81 | 82 | buf 83 | }; 84 | 85 | // Read padding, following 144-bytes zeroes, unknown field 2, FBX 86 | // version, and unknown field 3. 87 | let (padding_len, unknown2, version, unknown3) = { 88 | let buf_start_pos = parser.reader().position(); 89 | 90 | // Expected padding length. 91 | let expected_padding_len = (buf_start_pos.wrapping_neg() & 0x0f) as usize; 92 | debug!( 93 | "Current position = {}, expected padding length = {}", 94 | buf_start_pos, expected_padding_len 95 | ); 96 | 97 | /// Buffer length to load footer partially. 98 | // Padding (min 0) + unknown2 (4) + version (4) + zeroes (120) 99 | // + unknown3 (16) = 144. 100 | const BUF_LEN: usize = 144; 101 | let mut buf = [0u8; BUF_LEN]; 102 | parser.reader().read_exact(&mut buf)?; 103 | 104 | // First, get the beginning position of unknown field 3, 105 | // because it is expected to be starting with a non-zero byte. 106 | let unknown3_pos = { 107 | /// Start offset of search of unknown field 3. 108 | const SEARCH_OFFSET: usize = BUF_LEN - 16; 109 | let pos = &buf[SEARCH_OFFSET..] 110 | .iter() 111 | .position(|&v| v != 0) 112 | .ok_or(DataError::BrokenFbxFooter)?; 113 | SEARCH_OFFSET + pos 114 | }; 115 | 116 | let padding_len = unknown3_pos & 0x0f; 117 | assert!(padding_len < 16); 118 | assert_eq!(unknown3_pos, padding_len + 128); 119 | let padding = &buf[..padding_len]; 120 | let mut unknown2 = [0u8; 4]; 121 | unknown2.copy_from_slice(&buf[padding_len..(padding_len + 4)]); 122 | let version = u32::from_le_bytes( 123 | buf[(padding_len + 4)..(padding_len + 8)] 124 | .try_into() 125 | .expect("the slice must be 4 bytes"), 126 | ); 127 | let zeroes_120 = &buf[(padding_len + 8)..(padding_len + 128)]; 128 | let unknown3_part = &buf[(padding_len + 128)..]; 129 | 130 | // Check that the padding has only zeroes. 131 | if !padding.iter().all(|&v| v == 0) { 132 | return Err(DataError::BrokenFbxFooter.into()); 133 | } 134 | 135 | // Check that the unknown field 2 has only zeroes. 136 | if unknown2 != [0u8; 4] { 137 | return Err(DataError::BrokenFbxFooter.into()); 138 | } 139 | 140 | // Check that the FBX version is same as the FBX header. 141 | let version = FbxVersion::new(version); 142 | if version != parser.fbx_version() { 143 | // Version mismatch. 144 | return Err(DataError::BrokenFbxFooter.into()); 145 | } 146 | 147 | // Check that there are 120-bytes zeroes. 148 | if !zeroes_120.iter().all(|&v| v == 0) { 149 | return Err(DataError::BrokenFbxFooter.into()); 150 | } 151 | 152 | // Check that the unknown field 3 has expected pattern. 153 | /// Expected value of unknown field 3. 154 | const UNKNOWN3_EXPECTED: [u8; 16] = [ 155 | 0xf8, 0x5a, 0x8c, 0x6a, 0xde, 0xf5, 0xd9, 0x7e, 0xec, 0xe9, 0x0c, 0xe3, 0x75, 0x8f, 156 | 0x29, 0x0b, 157 | ]; 158 | let mut unknown3 = [0u8; 16]; 159 | unknown3[0..unknown3_part.len()].copy_from_slice(unknown3_part); 160 | parser 161 | .reader() 162 | .read_exact(&mut unknown3[unknown3_part.len()..])?; 163 | if unknown3 != UNKNOWN3_EXPECTED { 164 | return Err(DataError::BrokenFbxFooter.into()); 165 | } 166 | 167 | // If the execution comes here, footer may have no error. 168 | // Emit warning if necessary. 169 | 170 | // Check if the padding has correct length. 171 | if padding_len != expected_padding_len { 172 | let pos = SyntacticPosition { 173 | byte_pos: buf_start_pos, 174 | component_byte_pos: start_pos, 175 | node_path: Vec::new(), 176 | attribute_index: None, 177 | }; 178 | parser.warn( 179 | Warning::InvalidFooterPaddingLength(expected_padding_len, padding_len), 180 | pos, 181 | )?; 182 | } 183 | 184 | (padding_len, unknown2, version, unknown3) 185 | }; 186 | 187 | Ok(Self { 188 | unknown1, 189 | padding_len: padding_len as u8, 190 | unknown2, 191 | fbx_version: version, 192 | unknown3, 193 | }) 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /src/low/v7400/node_header.rs: -------------------------------------------------------------------------------- 1 | //! Node header. 2 | 3 | use std::io; 4 | 5 | use crate::pull_parser::{ 6 | v7400::{FromParser, Parser}, 7 | Error as ParserError, 8 | }; 9 | 10 | /// Node header. 11 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 12 | pub(crate) struct NodeHeader { 13 | /// End offset of the node. 14 | pub(crate) end_offset: u64, 15 | /// The number of the node attributes. 16 | pub(crate) num_attributes: u64, 17 | /// Length of the node attributes in bytes. 18 | pub(crate) bytelen_attributes: u64, 19 | /// Length of the node name in bytes. 20 | pub(crate) bytelen_name: u8, 21 | } 22 | 23 | impl NodeHeader { 24 | /// Checks whether the entry indicates end of a node. 25 | #[inline] 26 | #[must_use] 27 | pub(crate) fn is_node_end(&self) -> bool { 28 | self.end_offset == 0 29 | && self.num_attributes == 0 30 | && self.bytelen_attributes == 0 31 | && self.bytelen_name == 0 32 | } 33 | 34 | /// Returns node end marker. 35 | #[cfg(feature = "writer")] 36 | #[cfg_attr(docsrs, doc(cfg(feature = "writer")))] 37 | #[inline] 38 | #[must_use] 39 | pub(crate) fn node_end() -> Self { 40 | Self { 41 | end_offset: 0, 42 | num_attributes: 0, 43 | bytelen_attributes: 0, 44 | bytelen_name: 0, 45 | } 46 | } 47 | } 48 | 49 | impl FromParser for NodeHeader { 50 | fn read_from_parser(parser: &mut Parser) -> Result 51 | where 52 | R: io::Read, 53 | { 54 | let (end_offset, num_attributes, bytelen_attributes) = if parser.fbx_version().raw() < 7500 55 | { 56 | let eo = u64::from(parser.parse::()?); 57 | let na = u64::from(parser.parse::()?); 58 | let ba = u64::from(parser.parse::()?); 59 | (eo, na, ba) 60 | } else { 61 | let eo = parser.parse::()?; 62 | let na = parser.parse::()?; 63 | let ba = parser.parse::()?; 64 | (eo, na, ba) 65 | }; 66 | let bytelen_name = parser.parse::()?; 67 | 68 | Ok(Self { 69 | end_offset, 70 | num_attributes, 71 | bytelen_attributes, 72 | bytelen_name, 73 | }) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/low/v7400/special_attribute.rs: -------------------------------------------------------------------------------- 1 | //! Low-level data types for binary and stirng type node attributes. 2 | 3 | use std::io; 4 | 5 | use crate::pull_parser::{v7400::FromReader, Error as ParserError}; 6 | 7 | /// A header type for array-type attributes. 8 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 9 | pub(crate) struct SpecialAttributeHeader { 10 | /// Elements length in bytes. 11 | pub(crate) bytelen: u32, 12 | } 13 | 14 | impl FromReader for SpecialAttributeHeader { 15 | fn from_reader(reader: &mut impl io::Read) -> Result { 16 | let bytelen = u32::from_reader(reader)?; 17 | 18 | Ok(Self { bytelen }) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/low/version.rs: -------------------------------------------------------------------------------- 1 | //! FBX version type. 2 | 3 | /// FBX version. 4 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 5 | pub struct FbxVersion(u32); 6 | 7 | impl FbxVersion { 8 | /// Version 7.4. 9 | pub const V7_4: Self = FbxVersion(7400); 10 | 11 | /// Version 7.5. 12 | pub const V7_5: Self = FbxVersion(7500); 13 | 14 | /// Creates a new `FbxVersion`. 15 | #[inline] 16 | #[must_use] 17 | pub(crate) const fn new(version: u32) -> Self { 18 | FbxVersion(version) 19 | } 20 | 21 | /// Returns the raw value. 22 | /// 23 | /// For example, `7400` for FBX 7.4. 24 | #[inline] 25 | #[must_use] 26 | pub(crate) const fn raw(self) -> u32 { 27 | self.0 28 | } 29 | 30 | /// Returns the major version. 31 | /// 32 | /// For example, `7` for FBX 7.4. 33 | #[inline] 34 | #[must_use] 35 | pub const fn major(self) -> u32 { 36 | self.raw() / 1000 37 | } 38 | 39 | /// Returns the minor version. 40 | /// 41 | /// For example, `4` for FBX 7.4. 42 | #[inline] 43 | #[must_use] 44 | pub const fn minor(self) -> u32 { 45 | (self.raw() % 1000) / 100 46 | } 47 | 48 | /// Returns a tuple of the major and minor verison. 49 | /// 50 | /// For example, `(7, 4)` for FBX 7.4. 51 | #[inline] 52 | #[must_use] 53 | pub const fn major_minor(self) -> (u32, u32) { 54 | let major = self.major(); 55 | let minor = self.minor(); 56 | (major, minor) 57 | } 58 | } 59 | 60 | #[cfg(test)] 61 | mod tests { 62 | use super::*; 63 | 64 | #[test] 65 | fn version() { 66 | let major = 7; 67 | let minor = 4; 68 | let raw = major * 1000 + minor * 100; 69 | let ver = FbxVersion(raw); 70 | assert_eq!(ver.raw(), raw, "Should return raw value"); 71 | assert_eq!(ver.major(), major, "Should return major version"); 72 | assert_eq!(ver.minor(), minor, "Should return minor version"); 73 | assert_eq!( 74 | ver.major_minor(), 75 | (major, minor), 76 | "Should return major and minor version" 77 | ); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/pull_parser.rs: -------------------------------------------------------------------------------- 1 | //! Pull parser for FBX binary. 2 | //! 3 | //! # FBX versions and types 4 | //! 5 | //! Some types are version-agnostic, and some aren't. 6 | //! 7 | //! These modules are common among all supported FBX versions: 8 | //! 9 | //! * Error types (defined in [`error`] module). 10 | //! * [`AnyParser`][`any::AnyParser`] feature (defined in [`any`] module). 11 | //! 12 | //! # Using pull parser 13 | //! 14 | //! There are two ways to set up a parser: easy setup and manual setup. 15 | //! 16 | //! ## Easy setup (recommended) 17 | //! 18 | //! If you don't care about precise FBX version (e.g. difference between FBX 7.4 19 | //! and 7.5), you can use easy setup using [`any`] module. 20 | //! 21 | //! ```no_run 22 | //! use fbxcel::pull_parser::any::AnyParser; 23 | //! 24 | //! let file = std::fs::File::open("sample.fbx").expect("Failed to open file"); 25 | //! // You can also use raw `file`, but do buffering for better efficiency. 26 | //! let reader = std::io::BufReader::new(file); 27 | //! 28 | //! // Use `AnyParser::from_seekable_reader` for readers implementing 29 | //! // `std::io::Seek`. To use readers without `std::io::Seek` implementation, 30 | //! // use `AnyPraser::from_reader` instead. 31 | //! match AnyParser::from_seekable_reader(reader).expect("Failed to setup FBX parser") { 32 | //! // Use v7400 parser (implemented in `v7400` module). 33 | //! AnyParser::V7400(mut parser) => { 34 | //! // You got a parser! Do what you want! 35 | //! }, 36 | //! // `AnyParser` is nonexhaustive. 37 | //! // You should handle new unknown parser version case. 38 | //! _ => panic!("Unsupported FBX parser is required"), 39 | //! } 40 | //! ``` 41 | //! 42 | //! ## Manual setup 43 | //! 44 | //! In this way you have full control, but usual users don't need this. 45 | //! 46 | //! 1. Get FBX header. 47 | //! 2. Decide which version of parser to use. 48 | //! 3. Create parser with source reader. 49 | //! 50 | //! ```no_run 51 | //! use fbxcel::{low::FbxHeader, pull_parser::ParserVersion}; 52 | //! 53 | //! let file = std::fs::File::open("sample.fbx").expect("Failed to open file"); 54 | //! // You can also use raw `file`, but do buffering for better efficiency. 55 | //! let mut reader = std::io::BufReader::new(file); 56 | //! 57 | //! // 1. Get FBX header. 58 | //! let header = FbxHeader::load(&mut reader) 59 | //! .expect("Failed to load FBX header"); 60 | //! // 2. Decide which version of parser to use. 61 | //! match header.parser_version() { 62 | //! // Use v7400 parser (implemented in `v7400` module). 63 | //! Some(ParserVersion::V7400) => { 64 | //! // 3. Create parser with source reader. 65 | //! // Pass both header and reader. 66 | //! // Use `Parser::from_seekable_reader` for readers implementing 67 | //! // `std::io::Seek`. To use readers without `std::io::Seek` 68 | //! // implementation, use `Parser::from_reader` instead. 69 | //! let mut parser = fbxcel::pull_parser::v7400::Parser::from_seekable_reader(header, reader) 70 | //! .expect("Failed to setup parser"); 71 | //! // You got a parser! Do what you want! 72 | //! }, 73 | //! // `ParserVersion` is nonexhaustive. 74 | //! // You should handle new unknown parser version case. 75 | //! Some(v) => panic!("Parser version {:?} is not yet supported", v), 76 | //! // No appropriate parser found 77 | //! None => panic!( 78 | //! "FBX version {:?} is not supported by backend library", 79 | //! header.version() 80 | //! ), 81 | //! } 82 | //! ``` 83 | 84 | pub use self::{ 85 | error::{Error, Result, Warning}, 86 | position::SyntacticPosition, 87 | version::ParserVersion, 88 | }; 89 | 90 | pub mod any; 91 | pub mod error; 92 | mod position; 93 | mod reader; 94 | pub mod v7400; 95 | mod version; 96 | -------------------------------------------------------------------------------- /src/pull_parser/any.rs: -------------------------------------------------------------------------------- 1 | //! Types and functions for all supported versions. 2 | 3 | use std::io::{self, Seek}; 4 | 5 | use crate::{ 6 | low::{FbxHeader, FbxVersion}, 7 | pull_parser::{self, ParserVersion}, 8 | }; 9 | 10 | pub use self::error::{Error, Result}; 11 | 12 | mod error; 13 | 14 | /// FBX tree type with any supported version. 15 | #[non_exhaustive] 16 | pub enum AnyParser { 17 | /// FBX 7.4 or later. 18 | V7400(pull_parser::v7400::Parser), 19 | } 20 | 21 | impl AnyParser { 22 | /// Loads a tree from the given reader. 23 | /// 24 | /// This works for seekable readers (which implement [`std::io::Seek`]). 25 | /// However, [`from_seekable_reader`][`Self::from_seekable_reader`] method 26 | /// should be used for them because it is more efficent. 27 | pub fn from_reader(mut reader: R) -> Result { 28 | let header = FbxHeader::load(&mut reader)?; 29 | match parser_version(header)? { 30 | ParserVersion::V7400 => { 31 | let parser = pull_parser::v7400::Parser::from_reader(header, reader) 32 | .unwrap_or_else(|e| { 33 | panic!( 34 | "Should never fail: FBX version {:?} should be supported by v7400 parser: {}", 35 | header.version(), 36 | e 37 | ) 38 | }); 39 | Ok(AnyParser::V7400(parser)) 40 | } 41 | } 42 | } 43 | 44 | /// Loads a tree from the given seekable reader. 45 | pub fn from_seekable_reader(mut reader: R) -> Result 46 | where 47 | R: Seek, 48 | { 49 | let header = FbxHeader::load(&mut reader)?; 50 | match parser_version(header)? { 51 | ParserVersion::V7400 => { 52 | let parser = pull_parser::v7400::Parser::from_seekable_reader(header, reader) 53 | .unwrap_or_else(|e| { 54 | panic!( 55 | "Should never fail: FBX version {:?} should be supported by v7400 parser: {}", 56 | header.version(), 57 | e 58 | ) 59 | }); 60 | Ok(AnyParser::V7400(parser)) 61 | } 62 | } 63 | } 64 | 65 | /// Returns the parser version. 66 | #[inline] 67 | #[must_use] 68 | pub fn parser_version(&self) -> ParserVersion { 69 | match self { 70 | AnyParser::V7400(_) => pull_parser::v7400::Parser::::PARSER_VERSION, 71 | } 72 | } 73 | 74 | /// Returns the FBX version. 75 | #[inline] 76 | #[must_use] 77 | pub fn fbx_version(&self) -> FbxVersion { 78 | match self { 79 | AnyParser::V7400(parser) => parser.fbx_version(), 80 | } 81 | } 82 | } 83 | 84 | /// Returns the parser version for the FBX data. 85 | fn parser_version(header: FbxHeader) -> Result { 86 | header 87 | .parser_version() 88 | .ok_or_else(|| Error::UnsupportedVersion(header.version())) 89 | } 90 | -------------------------------------------------------------------------------- /src/pull_parser/any/error.rs: -------------------------------------------------------------------------------- 1 | //! Error and result types for `pull_parser::any` module. 2 | 3 | use std::{error, fmt}; 4 | 5 | use crate::low::{FbxVersion, HeaderError}; 6 | 7 | /// AnyTree load result. 8 | pub type Result = std::result::Result; 9 | 10 | /// Error. 11 | #[derive(Debug)] 12 | #[non_exhaustive] 13 | pub enum Error { 14 | /// Header error. 15 | Header(HeaderError), 16 | /// Unsupported version. 17 | UnsupportedVersion(FbxVersion), 18 | } 19 | 20 | impl error::Error for Error { 21 | #[inline] 22 | fn source(&self) -> Option<&(dyn error::Error + 'static)> { 23 | match self { 24 | Error::Header(e) => Some(e), 25 | _ => None, 26 | } 27 | } 28 | } 29 | 30 | impl fmt::Display for Error { 31 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 32 | match self { 33 | Error::Header(e) => write!(f, "FBX header error: {}", e), 34 | Error::UnsupportedVersion(ver) => write!(f, "Unsupported FBX version: {:?}", ver), 35 | } 36 | } 37 | } 38 | 39 | impl From for Error { 40 | #[inline] 41 | fn from(e: HeaderError) -> Self { 42 | Error::Header(e) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/pull_parser/error.rs: -------------------------------------------------------------------------------- 1 | //! Errors and result types. 2 | //! 3 | //! Types in this module will be used among multiple versions of parsers. 4 | 5 | use std::{error, fmt, io}; 6 | 7 | use crate::pull_parser::SyntacticPosition; 8 | 9 | pub use self::{ 10 | data::{Compression, DataError}, 11 | operation::OperationError, 12 | warning::Warning, 13 | }; 14 | 15 | mod data; 16 | mod operation; 17 | mod warning; 18 | 19 | /// Parsing result. 20 | pub type Result = std::result::Result; 21 | 22 | /// Parsing error. 23 | #[derive(Debug)] 24 | pub struct Error { 25 | /// The real error. 26 | repr: Box, 27 | } 28 | 29 | impl Error { 30 | /// Returns the error kind. 31 | #[inline] 32 | #[must_use] 33 | pub fn kind(&self) -> ErrorKind { 34 | self.repr.error.kind() 35 | } 36 | 37 | /// Returns a reference to the inner error container. 38 | #[inline] 39 | #[must_use] 40 | pub fn get_ref(&self) -> &ErrorContainer { 41 | &self.repr.error 42 | } 43 | 44 | /// Returns a reference to the inner error if the type matches. 45 | #[inline] 46 | #[must_use] 47 | pub fn downcast_ref(&self) -> Option<&T> { 48 | self.repr.error.as_error().downcast_ref::() 49 | } 50 | 51 | /// Returns the syntactic position if available. 52 | #[inline] 53 | #[must_use] 54 | pub fn position(&self) -> Option<&SyntacticPosition> { 55 | self.repr.position.as_ref() 56 | } 57 | 58 | /// Creates a new `Error` with the given syntactic position info. 59 | #[inline] 60 | #[must_use] 61 | pub(crate) fn with_position(error: ErrorContainer, position: SyntacticPosition) -> Self { 62 | Self { 63 | repr: Box::new(Repr::with_position(error, position)), 64 | } 65 | } 66 | 67 | /// Sets the syntactic position and returns the new error. 68 | #[inline] 69 | #[must_use] 70 | pub(crate) fn and_position(mut self, position: SyntacticPosition) -> Self { 71 | self.repr.position = Some(position); 72 | self 73 | } 74 | } 75 | 76 | impl fmt::Display for Error { 77 | #[inline] 78 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 79 | self.repr.error.fmt(f) 80 | } 81 | } 82 | 83 | impl error::Error for Error { 84 | #[inline] 85 | fn source(&self) -> Option<&(dyn error::Error + 'static)> { 86 | self.repr.error.source() 87 | } 88 | } 89 | 90 | impl From for Error 91 | where 92 | T: Into, 93 | { 94 | #[inline] 95 | fn from(e: T) -> Self { 96 | Error { 97 | repr: Box::new(Repr::new(e.into())), 98 | } 99 | } 100 | } 101 | 102 | /// Internal representation of parsing error. 103 | #[derive(Debug)] 104 | struct Repr { 105 | /// Error. 106 | error: ErrorContainer, 107 | /// Syntactic position. 108 | position: Option, 109 | } 110 | 111 | impl Repr { 112 | /// Creates a new `Repr`. 113 | #[inline] 114 | #[must_use] 115 | pub(crate) fn new(error: ErrorContainer) -> Self { 116 | Self { 117 | error, 118 | position: None, 119 | } 120 | } 121 | 122 | /// Creates a new `Repr` with the given syntactic position info. 123 | #[inline] 124 | #[must_use] 125 | pub(crate) fn with_position(error: ErrorContainer, position: SyntacticPosition) -> Self { 126 | Self { 127 | error, 128 | position: Some(position), 129 | } 130 | } 131 | } 132 | 133 | /// Error kind for parsing errors. 134 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 135 | pub enum ErrorKind { 136 | /// Invalid data. 137 | /// 138 | /// With this error kind, the inner error must be [`DataError`]. 139 | /// 140 | /// [`DataError`]: enum.DataError.html 141 | Data, 142 | /// I/O error. 143 | /// 144 | /// With this error kind, the inner error must be [`std::io::Error`]. 145 | /// 146 | /// [`std::io::Error`]: 147 | /// https://doc.rust-lang.org/stable/std/io/struct.Error.html 148 | Io, 149 | /// Invalid operation. 150 | /// 151 | /// With this error kind, the inner error must be [`OperationError`]. 152 | /// 153 | /// [`OperationError`]: enum.OperationError.html 154 | Operation, 155 | /// Critical warning. 156 | /// 157 | /// With this error kind, the inner error must be [`Warning`]. 158 | /// 159 | /// [`Warning`]: enum.Warning.html 160 | Warning, 161 | } 162 | 163 | /// Parsing error container. 164 | #[derive(Debug)] 165 | pub enum ErrorContainer { 166 | /// Invalid data. 167 | Data(DataError), 168 | /// I/O error. 169 | Io(io::Error), 170 | /// Invalid operation. 171 | Operation(OperationError), 172 | /// Critical warning. 173 | Warning(Warning), 174 | } 175 | 176 | impl ErrorContainer { 177 | /// Returns the error kind of the error. 178 | #[must_use] 179 | pub fn kind(&self) -> ErrorKind { 180 | match self { 181 | ErrorContainer::Data(_) => ErrorKind::Data, 182 | ErrorContainer::Io(_) => ErrorKind::Io, 183 | ErrorContainer::Operation(_) => ErrorKind::Operation, 184 | ErrorContainer::Warning(_) => ErrorKind::Warning, 185 | } 186 | } 187 | 188 | /// Returns `&dyn std::error::Error`. 189 | #[must_use] 190 | pub fn as_error(&self) -> &(dyn 'static + error::Error) { 191 | match self { 192 | ErrorContainer::Data(e) => e, 193 | ErrorContainer::Io(e) => e, 194 | ErrorContainer::Operation(e) => e, 195 | ErrorContainer::Warning(e) => e, 196 | } 197 | } 198 | } 199 | 200 | impl error::Error for ErrorContainer { 201 | #[inline] 202 | #[must_use] 203 | fn source(&self) -> Option<&(dyn error::Error + 'static)> { 204 | Some(self.as_error()) 205 | } 206 | } 207 | 208 | impl fmt::Display for ErrorContainer { 209 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 210 | match self { 211 | ErrorContainer::Data(e) => write!(f, "Data error: {}", e), 212 | ErrorContainer::Io(e) => write!(f, "I/O error: {}", e), 213 | ErrorContainer::Operation(e) => write!(f, "Invalid operation: {}", e), 214 | ErrorContainer::Warning(e) => write!(f, "Warning considered critical: {}", e), 215 | } 216 | } 217 | } 218 | 219 | impl From for ErrorContainer { 220 | #[inline] 221 | fn from(e: io::Error) -> Self { 222 | ErrorContainer::Io(e) 223 | } 224 | } 225 | 226 | impl From for ErrorContainer { 227 | #[inline] 228 | fn from(e: DataError) -> Self { 229 | ErrorContainer::Data(e) 230 | } 231 | } 232 | 233 | impl From for ErrorContainer { 234 | #[inline] 235 | fn from(e: OperationError) -> Self { 236 | ErrorContainer::Operation(e) 237 | } 238 | } 239 | 240 | impl From for ErrorContainer { 241 | #[inline] 242 | fn from(e: Warning) -> Self { 243 | ErrorContainer::Warning(e) 244 | } 245 | } 246 | -------------------------------------------------------------------------------- /src/pull_parser/error/data.rs: -------------------------------------------------------------------------------- 1 | //! Data error. 2 | //! 3 | //! This is mainly syntax and low-level structure error. 4 | 5 | use std::{error, fmt, string::FromUtf8Error}; 6 | 7 | /// Data error. 8 | #[derive(Debug)] 9 | #[non_exhaustive] 10 | pub enum DataError { 11 | /// Data with broken compression. 12 | BrokenCompression(Compression, Box), 13 | /// FBX footer is broken. 14 | /// 15 | /// Detail is not available because the footer may contain variable length 16 | /// field, and it is hard to identify what is actually broken. 17 | BrokenFbxFooter, 18 | /// Got an unknown array attribute encoding. 19 | InvalidArrayAttributeEncoding(u32), 20 | /// Invalid node attribute type code. 21 | /// 22 | /// The `u8` is the code the parser got. 23 | InvalidAttributeTypeCode(u8), 24 | /// Invalid node name encoding. 25 | /// 26 | /// This error indicates that the node name is non-valid UTF-8. 27 | InvalidNodeNameEncoding(FromUtf8Error), 28 | /// Node attribute error. 29 | /// 30 | /// This error indicates that some error happened while reading node 31 | /// attributes. 32 | NodeAttributeError, 33 | /// Node length mismatch. 34 | /// 35 | /// This error indicates that a node ends at the position which differs from 36 | /// the offset declared at the header. 37 | /// 38 | /// The former `u64` is expected position, the latter `Option` is the 39 | /// actual position the node ends. 40 | /// If the error is detected before the node actually ends, the actual 41 | /// position will be `None`. 42 | NodeLengthMismatch(u64, Option), 43 | /// Unexpected attribute value or type. 44 | /// 45 | /// The former is the expected, the latter is a description of the actual value. 46 | UnexpectedAttribute(String, String), 47 | } 48 | 49 | impl error::Error for DataError { 50 | fn source(&self) -> Option<&(dyn error::Error + 'static)> { 51 | match self { 52 | DataError::BrokenCompression(_, e) => Some(e.as_ref()), 53 | DataError::InvalidNodeNameEncoding(e) => Some(e), 54 | _ => None, 55 | } 56 | } 57 | } 58 | 59 | impl fmt::Display for DataError { 60 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 61 | match self { 62 | DataError::BrokenFbxFooter => write!(f, "FBX footer is broken"), 63 | DataError::BrokenCompression(codec, e) => write!( 64 | f, 65 | "Data with broken compression (codec={:?}): {:?}", 66 | codec, e 67 | ), 68 | DataError::InvalidArrayAttributeEncoding(encoding) => { 69 | write!(f, "Unknown array attribute encoding: got {:?}", encoding) 70 | } 71 | DataError::InvalidAttributeTypeCode(code) => { 72 | write!(f, "Invalid node attribute type code: {:?}", code) 73 | } 74 | DataError::InvalidNodeNameEncoding(e) => { 75 | write!(f, "Invalid node name encoding: {:?}", e) 76 | } 77 | DataError::NodeAttributeError => { 78 | write!(f, "Some error occured while reading node attributes") 79 | } 80 | DataError::NodeLengthMismatch(expected, got) => write!( 81 | f, 82 | "Node ends with unexpected position: expected {}, got {:?}", 83 | expected, got 84 | ), 85 | DataError::UnexpectedAttribute(expected, got) => write!( 86 | f, 87 | "Unexpected attribute value or type: expected {}, got {}", 88 | expected, got 89 | ), 90 | } 91 | } 92 | } 93 | 94 | /// Compression format or algorithm. 95 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 96 | pub enum Compression { 97 | /// ZLIB compression. 98 | Zlib, 99 | } 100 | -------------------------------------------------------------------------------- /src/pull_parser/error/operation.rs: -------------------------------------------------------------------------------- 1 | //! Invalid operation. 2 | 3 | use std::{error, fmt}; 4 | 5 | use crate::{low::FbxVersion, pull_parser::ParserVersion}; 6 | 7 | /// Invalid operation. 8 | #[derive(Debug)] 9 | #[non_exhaustive] 10 | pub enum OperationError { 11 | /// Attempt to parse more data while the parsing is aborted. 12 | AlreadyAborted, 13 | /// Attempt to parse more data while the parsing is (successfully) finished. 14 | AlreadyFinished, 15 | /// Attempt to create a parser with unsupported FBX version. 16 | UnsupportedFbxVersion(ParserVersion, FbxVersion), 17 | } 18 | 19 | impl error::Error for OperationError {} 20 | 21 | impl fmt::Display for OperationError { 22 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 23 | match self { 24 | OperationError::AlreadyAborted => { 25 | write!(f, "Attempt to parse more data while the parsing is aborted") 26 | } 27 | OperationError::AlreadyFinished => write!( 28 | f, 29 | "Attempt to parse more data while the parsing is successfully finished" 30 | ), 31 | OperationError::UnsupportedFbxVersion(parser, fbx) => write!( 32 | f, 33 | "Unsupported FBX version: parser={:?}, fbx={:?}", 34 | parser, fbx 35 | ), 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/pull_parser/error/warning.rs: -------------------------------------------------------------------------------- 1 | //! Invalid operation. 2 | 3 | use std::{error, fmt}; 4 | 5 | /// Warning. 6 | #[derive(Debug)] 7 | #[non_exhaustive] 8 | pub enum Warning { 9 | /// Node name is empty. 10 | EmptyNodeName, 11 | /// Extra (unexpected) node end marker found. 12 | ExtraNodeEndMarker, 13 | /// Incorrect boolean representation. 14 | /// 15 | /// Boolean value in node attributes should be some prescribed value 16 | /// (for example `b'T'` and `b'Y'` for FBX 7.4). 17 | /// Official SDK and tools may emit those values correctly, but some 18 | /// third-party exporters emits them wrongly with `0x00` and `0x01`, and 19 | /// those will be ignored by official SDK and tools. 20 | IncorrectBooleanRepresentation, 21 | /// Footer padding length is invalid. 22 | InvalidFooterPaddingLength(usize, usize), 23 | /// Missing a node end marker where the marker is expected. 24 | MissingNodeEndMarker, 25 | /// Unexpected value for footer fields (mainly for unknown fields). 26 | UnexpectedFooterFieldValue, 27 | } 28 | 29 | impl error::Error for Warning {} 30 | 31 | impl fmt::Display for Warning { 32 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 33 | match self { 34 | Warning::EmptyNodeName => write!(f, "Node name is empty"), 35 | Warning::ExtraNodeEndMarker => write!(f, "Extra (unexpected) node end marker found"), 36 | Warning::IncorrectBooleanRepresentation => { 37 | write!(f, "Incorrect boolean representation") 38 | } 39 | Warning::InvalidFooterPaddingLength(expected, got) => write!( 40 | f, 41 | "Invalid footer padding length: expected {} bytes, got {} bytes", 42 | expected, got 43 | ), 44 | Warning::MissingNodeEndMarker => write!(f, "Missing node end marker"), 45 | Warning::UnexpectedFooterFieldValue => write!(f, "Unexpected footer field value"), 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/pull_parser/position.rs: -------------------------------------------------------------------------------- 1 | //! Syntactic position. 2 | 3 | /// Syntactic position. 4 | /// 5 | /// This contains not only byte-position, but also additional information such 6 | /// as node path and attribute index. 7 | /// 8 | /// This type is implemented based on FBX 7.4 data structure, and may change in 9 | /// future if FBX syntax has breaking changes. 10 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] 11 | pub struct SyntacticPosition { 12 | /// Byte position. 13 | pub(crate) byte_pos: u64, 14 | /// Beginning byte position of the node or attribute. 15 | pub(crate) component_byte_pos: u64, 16 | /// Node path. 17 | /// 18 | /// This is a vector of pairs of node indices in siblings (i.e. the number 19 | /// of preceding siblings) and node names. 20 | pub(crate) node_path: Vec<(usize, String)>, 21 | /// Node attribute index (if the position points an attribute). 22 | pub(crate) attribute_index: Option, 23 | } 24 | 25 | impl SyntacticPosition { 26 | /// Returns the byte position. 27 | #[inline] 28 | #[must_use] 29 | pub fn byte_pos(&self) -> u64 { 30 | self.byte_pos 31 | } 32 | 33 | /// Returns the beginning byte position of the node or attribute. 34 | #[inline] 35 | #[must_use] 36 | pub fn component_byte_pos(&self) -> u64 { 37 | self.component_byte_pos 38 | } 39 | 40 | /// Returns the node path. 41 | /// 42 | /// This is a vector of pairs of node indices in siblings (i.e. the number 43 | /// of preceding siblings) and node names. 44 | #[inline] 45 | #[must_use] 46 | pub fn node_path(&self) -> &[(usize, String)] { 47 | &self.node_path 48 | } 49 | 50 | /// Returns the node attribute index (if the position points an attribute). 51 | #[inline] 52 | #[must_use] 53 | pub fn attribute_index(&self) -> Option { 54 | self.attribute_index 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/pull_parser/reader.rs: -------------------------------------------------------------------------------- 1 | //! Parser data source. 2 | //! 3 | //! FBX parsers requires cursor position tracking and skipping. 4 | //! These features are provided by the `Reader` wrapper type. 5 | 6 | use std::fmt; 7 | use std::io::{self, SeekFrom}; 8 | 9 | /// Possibly specialized functions for the stream. 10 | #[derive(Clone, Copy)] 11 | struct ReaderFnTable { 12 | /// Skips (seeks formward) the given size. 13 | skip_distance: fn(&mut Reader, u64) -> io::Result<()>, 14 | } 15 | 16 | impl ReaderFnTable { 17 | /// Creates a new function table for a plain reader. 18 | #[inline] 19 | #[must_use] 20 | fn new_for_plain() -> Self { 21 | Self { 22 | skip_distance: Self::skip_distance_plain, 23 | } 24 | } 25 | 26 | /// Creates a new function table for a seekable reader. 27 | #[inline] 28 | #[must_use] 29 | fn new_for_seekable() -> Self 30 | where 31 | R: io::Seek, 32 | { 33 | Self { 34 | skip_distance: Self::skip_distance_seekable, 35 | } 36 | } 37 | 38 | /// Skips (seeks formward) the given size. 39 | /// 40 | /// More efficient implementation [`skip_distance_seekable`][`Self::skip_distance_seekable`] 41 | /// is provided for seekable stream. 42 | #[inline] 43 | fn skip_distance_plain(reader: &mut Reader, distance: u64) -> io::Result<()> { 44 | // NOTE: `let mut limited = self.by_ref().take(distance);` is E0507. 45 | let mut limited = io::Read::take(reader.inner.by_ref(), distance); 46 | io::copy(&mut limited, &mut io::sink())?; 47 | Ok(()) 48 | } 49 | 50 | /// Skips (seeks formward) the given size. 51 | fn skip_distance_seekable(reader: &mut Reader, mut distance: u64) -> io::Result<()> 52 | where 53 | R: io::Seek, 54 | { 55 | while distance > 0 { 56 | let part = std::cmp::min(distance, i64::MAX as u64); 57 | reader.inner.seek(SeekFrom::Current(part as i64))?; 58 | reader.advance(part as usize); 59 | distance -= part; 60 | } 61 | Ok(()) 62 | } 63 | } 64 | 65 | /// A wrapper type of the source reader. 66 | #[derive(Clone)] 67 | pub(crate) struct Reader { 68 | /// Inner stream. 69 | inner: R, 70 | /// Cached current stream position. 71 | position: usize, 72 | /// Function table. 73 | fn_table: ReaderFnTable, 74 | } 75 | 76 | impl fmt::Debug for Reader { 77 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 78 | f.debug_struct("Reader") 79 | .field("inner", &self.inner) 80 | .field("position", &self.position) 81 | .finish() 82 | } 83 | } 84 | 85 | impl Reader { 86 | /// Creates a new reader. 87 | #[inline] 88 | #[must_use] 89 | pub(crate) fn new(inner: R, current_position: usize) -> Self { 90 | Self { 91 | inner, 92 | position: current_position, 93 | fn_table: ReaderFnTable::new_for_plain(), 94 | } 95 | } 96 | 97 | /// Creates a new reader. 98 | #[inline] 99 | #[must_use] 100 | pub(crate) fn with_seekable(inner: R, current_position: usize) -> Self 101 | where 102 | R: io::Seek, 103 | { 104 | Self { 105 | inner, 106 | position: current_position, 107 | fn_table: ReaderFnTable::new_for_seekable(), 108 | } 109 | } 110 | 111 | /// Returns the current position. 112 | #[inline] 113 | #[must_use] 114 | pub(crate) fn position(&self) -> u64 { 115 | self.position as u64 116 | } 117 | 118 | /// Skips the given distance. 119 | /// 120 | /// A seek beyond the end of a stream is allowed, but behavior is defined by 121 | /// the inner stream implementation. 122 | /// See the document for [`std::io::Seek::seek()`]. 123 | #[inline] 124 | pub(crate) fn skip_distance(&mut self, distance: u64) -> io::Result<()> { 125 | (self.fn_table.skip_distance)(self, distance) 126 | } 127 | 128 | /// Skips (seeks forward) to the given position. 129 | /// 130 | /// Reader types can make this more efficient using [`std::io::Seek::seek`] 131 | /// if possible. 132 | /// 133 | /// # Panics 134 | /// 135 | /// Panics if the given position is behind the current position. 136 | #[inline] 137 | pub(crate) fn skip_to(&mut self, pos: u64) -> io::Result<()> { 138 | let distance = pos 139 | .checked_sub(self.position()) 140 | .expect("Attempt to skip backward"); 141 | self.skip_distance(distance) 142 | } 143 | 144 | /// Advances the position counter. 145 | #[inline] 146 | fn advance(&mut self, n: usize) { 147 | self.position = self.position.checked_add(n).expect("Position overflowed"); 148 | } 149 | } 150 | 151 | impl io::Read for Reader { 152 | #[inline] 153 | fn read(&mut self, buf: &mut [u8]) -> io::Result { 154 | let size = self.inner.read(buf)?; 155 | self.advance(size); 156 | Ok(size) 157 | } 158 | } 159 | 160 | impl io::BufRead for Reader { 161 | #[inline] 162 | fn fill_buf(&mut self) -> io::Result<&[u8]> { 163 | self.inner.fill_buf() 164 | } 165 | 166 | #[inline] 167 | fn consume(&mut self, amt: usize) { 168 | self.inner.consume(amt); 169 | self.advance(amt); 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /src/pull_parser/v7400.rs: -------------------------------------------------------------------------------- 1 | //! Parser-related stuff for FBX 7.4 or later. 2 | //! 3 | //! To see how to setup a parser, see module documentation of [`pull_parser`][`super`]. 4 | 5 | pub(crate) use self::read::{FromParser, FromReader}; 6 | pub use self::{ 7 | attribute::{Attributes, LoadAttribute}, 8 | event::{Event, StartNode}, 9 | parser::Parser, 10 | }; 11 | 12 | pub mod attribute; 13 | mod event; 14 | mod parser; 15 | mod read; 16 | -------------------------------------------------------------------------------- /src/pull_parser/v7400/attribute/array.rs: -------------------------------------------------------------------------------- 1 | //! Array-type node attributes. 2 | 3 | use std::{io, marker::PhantomData}; 4 | 5 | use libflate::zlib::Decoder as ZlibDecoder; 6 | 7 | use crate::{ 8 | low::v7400::ArrayAttributeEncoding, 9 | pull_parser::{error::DataError, Result}, 10 | }; 11 | 12 | /// Attribute stream decoder. 13 | // `io::BufRead` is not implemented for `ZlibDecoder`. 14 | #[derive(Debug)] 15 | pub(crate) enum AttributeStreamDecoder { 16 | /// Direct stream. 17 | Direct(R), 18 | /// Zlib-decoded stream. 19 | Zlib(ZlibDecoder), 20 | } 21 | 22 | impl AttributeStreamDecoder { 23 | /// Creates a new decoded reader. 24 | pub(crate) fn create(encoding: ArrayAttributeEncoding, reader: R) -> Result { 25 | match encoding { 26 | ArrayAttributeEncoding::Direct => Ok(AttributeStreamDecoder::Direct(reader)), 27 | ArrayAttributeEncoding::Zlib => Ok(AttributeStreamDecoder::Zlib( 28 | ZlibDecoder::new(reader) 29 | .map_err(|e| DataError::BrokenCompression(encoding.into(), e.into()))?, 30 | )), 31 | } 32 | } 33 | } 34 | 35 | impl io::Read for AttributeStreamDecoder { 36 | fn read(&mut self, buf: &mut [u8]) -> io::Result { 37 | match self { 38 | AttributeStreamDecoder::Direct(reader) => reader.read(buf), 39 | AttributeStreamDecoder::Zlib(reader) => reader.read(buf), 40 | } 41 | } 42 | } 43 | 44 | /// Array attribute values iterator for `{i,f}{32,64}` array. 45 | #[derive(Debug, Clone, Copy)] 46 | pub(crate) struct ArrayAttributeValues { 47 | /// Decoded reader. 48 | reader: R, 49 | // `total_elements`: unused. 50 | ///// Number of total elements. 51 | //total_elements: u32, 52 | /// Number of rest elements. 53 | rest_elements: u32, 54 | /// Whether an error is happened. 55 | has_error: bool, 56 | /// Element type. 57 | _element_type: PhantomData, 58 | } 59 | 60 | impl ArrayAttributeValues 61 | where 62 | R: io::Read, 63 | { 64 | /// Creates a new `ArrayAttributeValues`. 65 | #[inline] 66 | #[must_use] 67 | pub(crate) fn new(reader: R, total_elements: u32) -> Self { 68 | Self { 69 | reader, 70 | //total_elements, 71 | rest_elements: total_elements, 72 | has_error: false, 73 | _element_type: PhantomData, 74 | } 75 | } 76 | 77 | /// Returns whether an error happened or not. 78 | #[inline] 79 | #[must_use] 80 | pub(crate) fn has_error(&self) -> bool { 81 | self.has_error 82 | } 83 | } 84 | 85 | /// Implement common traits for `ArrayAttributeValues`. 86 | macro_rules! impl_array_attr_values { 87 | ($ty_elem:ty) => { 88 | impl Iterator for ArrayAttributeValues { 89 | type Item = Result<$ty_elem>; 90 | 91 | fn next(&mut self) -> Option { 92 | if self.rest_elements == 0 { 93 | return None; 94 | } 95 | let mut buf = [0_u8; std::mem::size_of::<$ty_elem>()]; 96 | match self.reader.read_exact(&mut buf) { 97 | Ok(()) => { 98 | let v = <$ty_elem>::from_le_bytes(buf); 99 | self.rest_elements = self 100 | .rest_elements 101 | .checked_sub(1) 102 | .expect("This should be executed only when there are rest elements"); 103 | Some(Ok(v)) 104 | } 105 | Err(e) => { 106 | self.has_error = true; 107 | Some(Err(e.into())) 108 | } 109 | } 110 | } 111 | 112 | #[inline] 113 | fn size_hint(&self) -> (usize, Option) { 114 | (0, Some(self.rest_elements as usize)) 115 | } 116 | } 117 | 118 | impl std::iter::FusedIterator for ArrayAttributeValues {} 119 | }; 120 | } 121 | 122 | impl_array_attr_values! { i32 } 123 | impl_array_attr_values! { i64 } 124 | impl_array_attr_values! { f32 } 125 | impl_array_attr_values! { f64 } 126 | 127 | /// Array attribute values iterator for `bool` array. 128 | #[derive(Debug, Clone, Copy)] 129 | pub(crate) struct BooleanArrayAttributeValues { 130 | /// Decoded reader. 131 | reader: R, 132 | // `total_elements`: unused. 133 | ///// Number of total elements. 134 | //total_elements: u32, 135 | /// Number of rest elements. 136 | rest_elements: u32, 137 | /// Whether an error is happened. 138 | has_error: bool, 139 | /// Whether the attribute has incorrect boolean value representation. 140 | has_incorrect_boolean_value: bool, 141 | } 142 | 143 | impl BooleanArrayAttributeValues { 144 | /// Creates a new `BooleanArrayAttributeValues`. 145 | #[inline] 146 | #[must_use] 147 | pub(crate) fn new(reader: R, total_elements: u32) -> Self { 148 | Self { 149 | reader, 150 | //total_elements, 151 | rest_elements: total_elements, 152 | has_error: false, 153 | has_incorrect_boolean_value: false, 154 | } 155 | } 156 | 157 | /// Returns whether the attribute has incorrect boolean value 158 | /// representation. 159 | #[inline] 160 | #[must_use] 161 | pub(crate) fn has_incorrect_boolean_value(&self) -> bool { 162 | self.has_incorrect_boolean_value 163 | } 164 | 165 | /// Returns whether an error happened or not. 166 | #[inline] 167 | #[must_use] 168 | pub(crate) fn has_error(&self) -> bool { 169 | self.has_error 170 | } 171 | } 172 | 173 | impl Iterator for BooleanArrayAttributeValues { 174 | type Item = Result; 175 | 176 | fn next(&mut self) -> Option { 177 | if self.rest_elements == 0 { 178 | return None; 179 | } 180 | let mut raw = 0_u8; 181 | match self.reader.read_exact(std::slice::from_mut(&mut raw)) { 182 | Ok(()) => { 183 | self.rest_elements = self 184 | .rest_elements 185 | .checked_sub(1) 186 | .expect("This should be executed only when there are rest elements"); 187 | if raw != b'T' && raw != b'Y' { 188 | self.has_incorrect_boolean_value = true; 189 | } 190 | let v = (raw & 1) != 0; 191 | Some(Ok(v)) 192 | } 193 | Err(e) => { 194 | self.has_error = true; 195 | Some(Err(e.into())) 196 | } 197 | } 198 | } 199 | 200 | #[inline] 201 | fn size_hint(&self) -> (usize, Option) { 202 | (0, Some(self.rest_elements as usize)) 203 | } 204 | } 205 | 206 | impl std::iter::FusedIterator for BooleanArrayAttributeValues {} 207 | -------------------------------------------------------------------------------- /src/pull_parser/v7400/attribute/iter.rs: -------------------------------------------------------------------------------- 1 | //! Node attribute iterators. 2 | 3 | use std::io; 4 | use std::iter; 5 | 6 | use crate::pull_parser::{ 7 | v7400::attribute::{loader::LoadAttribute, Attributes}, 8 | Result, 9 | }; 10 | 11 | /// Creates size hint from the given attributes and loaders. 12 | #[must_use] 13 | fn make_size_hint_for_attrs( 14 | attributes: &Attributes<'_, R>, 15 | loaders: &impl Iterator, 16 | ) -> (usize, Option) 17 | where 18 | R: io::Read, 19 | V: LoadAttribute, 20 | { 21 | let (loaders_min, loaders_max) = loaders.size_hint(); 22 | let attrs_rest = attributes.rest_count() as usize; 23 | let min = std::cmp::min(attrs_rest, loaders_min); 24 | let max = loaders_max.map_or_else(usize::max_value, |v| std::cmp::min(attrs_rest, v)); 25 | 26 | (min, Some(max)) 27 | } 28 | 29 | /// Loads the next attrbute. 30 | #[must_use] 31 | fn load_next( 32 | attributes: &mut Attributes<'_, R>, 33 | loaders: &mut impl Iterator, 34 | ) -> Option> 35 | where 36 | R: io::Read, 37 | V: LoadAttribute, 38 | { 39 | let loader = loaders.next()?; 40 | attributes.load_next(loader).transpose() 41 | } 42 | 43 | /// Loads the next attrbute with buffered I/O. 44 | #[must_use] 45 | fn load_next_buffered( 46 | attributes: &mut Attributes<'_, R>, 47 | loaders: &mut impl Iterator, 48 | ) -> Option> 49 | where 50 | R: io::BufRead, 51 | V: LoadAttribute, 52 | { 53 | let loader = loaders.next()?; 54 | attributes.load_next(loader).transpose() 55 | } 56 | 57 | /// Node attributes iterator. 58 | #[derive(Debug)] 59 | pub struct BorrowedIter<'a, 'r, R, I> { 60 | /// Attributes. 61 | attributes: &'a mut Attributes<'r, R>, 62 | /// Loaders. 63 | loaders: I, 64 | } 65 | 66 | impl<'a, 'r, R, I, V> BorrowedIter<'a, 'r, R, I> 67 | where 68 | R: io::Read, 69 | I: Iterator, 70 | V: LoadAttribute, 71 | { 72 | /// Creates a new iterator. 73 | #[inline] 74 | #[must_use] 75 | pub(crate) fn new(attributes: &'a mut Attributes<'r, R>, loaders: I) -> Self { 76 | Self { 77 | attributes, 78 | loaders, 79 | } 80 | } 81 | } 82 | 83 | impl<'a, 'r, R, I, V> Iterator for BorrowedIter<'a, 'r, R, I> 84 | where 85 | R: io::Read, 86 | I: Iterator, 87 | V: LoadAttribute, 88 | { 89 | type Item = Result; 90 | 91 | #[inline] 92 | fn next(&mut self) -> Option { 93 | load_next(self.attributes, &mut self.loaders) 94 | } 95 | 96 | #[inline] 97 | fn size_hint(&self) -> (usize, Option) { 98 | make_size_hint_for_attrs(self.attributes, &self.loaders) 99 | } 100 | } 101 | 102 | impl<'a, 'r, R, I, V> iter::FusedIterator for BorrowedIter<'a, 'r, R, I> 103 | where 104 | R: io::Read, 105 | I: Iterator, 106 | V: LoadAttribute, 107 | { 108 | } 109 | 110 | /// Node attributes iterator with buffered I/O. 111 | #[derive(Debug)] 112 | pub struct BorrowedIterBuffered<'a, 'r, R, I> { 113 | /// Attributes. 114 | attributes: &'a mut Attributes<'r, R>, 115 | /// Loaders. 116 | loaders: I, 117 | } 118 | 119 | impl<'a, 'r, R, I, V> BorrowedIterBuffered<'a, 'r, R, I> 120 | where 121 | R: io::Read, 122 | I: Iterator, 123 | V: LoadAttribute, 124 | { 125 | /// Creates a new iterator. 126 | #[inline] 127 | #[must_use] 128 | pub(crate) fn new(attributes: &'a mut Attributes<'r, R>, loaders: I) -> Self { 129 | Self { 130 | attributes, 131 | loaders, 132 | } 133 | } 134 | } 135 | 136 | impl<'a, 'r, R, I, V> Iterator for BorrowedIterBuffered<'a, 'r, R, I> 137 | where 138 | R: io::BufRead, 139 | I: Iterator, 140 | V: LoadAttribute, 141 | { 142 | type Item = Result; 143 | 144 | #[inline] 145 | fn next(&mut self) -> Option { 146 | load_next_buffered(self.attributes, &mut self.loaders) 147 | } 148 | 149 | #[inline] 150 | fn size_hint(&self) -> (usize, Option) { 151 | make_size_hint_for_attrs(self.attributes, &self.loaders) 152 | } 153 | } 154 | 155 | impl<'a, 'r, R, I, V> iter::FusedIterator for BorrowedIterBuffered<'a, 'r, R, I> 156 | where 157 | R: io::BufRead, 158 | I: Iterator, 159 | V: LoadAttribute, 160 | { 161 | } 162 | 163 | /// Node attributes iterator. 164 | #[derive(Debug)] 165 | pub struct OwnedIter<'r, R, I> { 166 | /// Attributes. 167 | attributes: Attributes<'r, R>, 168 | /// Loaders. 169 | loaders: I, 170 | } 171 | 172 | impl<'r, R, I, V> OwnedIter<'r, R, I> 173 | where 174 | R: io::Read, 175 | I: Iterator, 176 | V: LoadAttribute, 177 | { 178 | /// Creates a new `Iter`. 179 | #[inline] 180 | #[must_use] 181 | pub(crate) fn new(attributes: Attributes<'r, R>, loaders: I) -> Self { 182 | Self { 183 | attributes, 184 | loaders, 185 | } 186 | } 187 | } 188 | 189 | impl<'r, R, I, V> Iterator for OwnedIter<'r, R, I> 190 | where 191 | R: io::Read, 192 | I: Iterator, 193 | V: LoadAttribute, 194 | { 195 | type Item = Result; 196 | 197 | #[inline] 198 | fn next(&mut self) -> Option { 199 | load_next(&mut self.attributes, &mut self.loaders) 200 | } 201 | 202 | #[inline] 203 | fn size_hint(&self) -> (usize, Option) { 204 | make_size_hint_for_attrs(&self.attributes, &self.loaders) 205 | } 206 | } 207 | 208 | impl<'r, R, I, V> iter::FusedIterator for OwnedIter<'r, R, I> 209 | where 210 | R: io::Read, 211 | I: Iterator, 212 | V: LoadAttribute, 213 | { 214 | } 215 | 216 | /// Node attributes iterator with buffered I/O. 217 | #[derive(Debug)] 218 | pub struct OwnedIterBuffered<'r, R, I> { 219 | /// Attributes. 220 | attributes: Attributes<'r, R>, 221 | /// Loaders. 222 | loaders: I, 223 | } 224 | 225 | impl<'r, R, I, V> OwnedIterBuffered<'r, R, I> 226 | where 227 | R: io::Read, 228 | I: Iterator, 229 | V: LoadAttribute, 230 | { 231 | /// Creates a new iterator. 232 | #[inline] 233 | #[must_use] 234 | pub(crate) fn new(attributes: Attributes<'r, R>, loaders: I) -> Self { 235 | Self { 236 | attributes, 237 | loaders, 238 | } 239 | } 240 | } 241 | 242 | impl<'r, R, I, V> Iterator for OwnedIterBuffered<'r, R, I> 243 | where 244 | R: io::BufRead, 245 | I: Iterator, 246 | V: LoadAttribute, 247 | { 248 | type Item = Result; 249 | 250 | #[inline] 251 | fn next(&mut self) -> Option { 252 | load_next_buffered(&mut self.attributes, &mut self.loaders) 253 | } 254 | 255 | #[inline] 256 | fn size_hint(&self) -> (usize, Option) { 257 | make_size_hint_for_attrs(&self.attributes, &self.loaders) 258 | } 259 | } 260 | 261 | impl<'r, R, I, V> iter::FusedIterator for OwnedIterBuffered<'r, R, I> 262 | where 263 | R: io::BufRead, 264 | I: Iterator, 265 | V: LoadAttribute, 266 | { 267 | } 268 | -------------------------------------------------------------------------------- /src/pull_parser/v7400/attribute/loader.rs: -------------------------------------------------------------------------------- 1 | //! Node attribute loader. 2 | 3 | use std::{fmt, io}; 4 | 5 | use crate::pull_parser::{error::DataError, Result}; 6 | 7 | /// A trait for attribute loader types. 8 | /// 9 | /// This is a lot like a "visitor", but node attributes do not have recursive 10 | /// structures, so this loader is not "visitor". 11 | /// 12 | /// The `load_*` method corresponding to the node attribute type are called with 13 | /// its value. 14 | /// 15 | /// All of `load_*` has default implementation to return error as "unexpected 16 | /// attribute". 17 | /// Users should implement them manually for types they want to interpret. 18 | /// 19 | /// For simple types, [`pull_parser::v7400::attribute::loaders`][`super::loaders`] module contains 20 | /// useful loaders. 21 | pub trait LoadAttribute: Sized + fmt::Debug { 22 | /// Result type on successful read. 23 | type Output; 24 | 25 | /// Describes the expecting value. 26 | fn expecting(&self) -> String; 27 | 28 | /// Loads boolean value. 29 | fn load_bool(self, _: bool) -> Result { 30 | Err(DataError::UnexpectedAttribute(self.expecting(), "boolean".into()).into()) 31 | } 32 | 33 | /// Loads `i16` value. 34 | fn load_i16(self, _: i16) -> Result { 35 | Err(DataError::UnexpectedAttribute(self.expecting(), "i16".into()).into()) 36 | } 37 | 38 | /// Loads `i32` value. 39 | fn load_i32(self, _: i32) -> Result { 40 | Err(DataError::UnexpectedAttribute(self.expecting(), "i32".into()).into()) 41 | } 42 | 43 | /// Loads `i64` value. 44 | fn load_i64(self, _: i64) -> Result { 45 | Err(DataError::UnexpectedAttribute(self.expecting(), "i64".into()).into()) 46 | } 47 | 48 | /// Loads `f32` value. 49 | fn load_f32(self, _: f32) -> Result { 50 | Err(DataError::UnexpectedAttribute(self.expecting(), "f32".into()).into()) 51 | } 52 | 53 | /// Loads `f64` value. 54 | fn load_f64(self, _: f64) -> Result { 55 | Err(DataError::UnexpectedAttribute(self.expecting(), "f64".into()).into()) 56 | } 57 | 58 | /// Loads boolean array. 59 | fn load_seq_bool( 60 | self, 61 | _: impl Iterator>, 62 | _len: usize, 63 | ) -> Result { 64 | Err(DataError::UnexpectedAttribute(self.expecting(), "boolean array".into()).into()) 65 | } 66 | 67 | /// Loads `i32` array. 68 | fn load_seq_i32( 69 | self, 70 | _: impl Iterator>, 71 | _len: usize, 72 | ) -> Result { 73 | Err(DataError::UnexpectedAttribute(self.expecting(), "i32 array".into()).into()) 74 | } 75 | 76 | /// Loads `i64` array. 77 | fn load_seq_i64( 78 | self, 79 | _: impl Iterator>, 80 | _len: usize, 81 | ) -> Result { 82 | Err(DataError::UnexpectedAttribute(self.expecting(), "i64 array".into()).into()) 83 | } 84 | 85 | /// Loads `f32` array. 86 | fn load_seq_f32( 87 | self, 88 | _: impl Iterator>, 89 | _len: usize, 90 | ) -> Result { 91 | Err(DataError::UnexpectedAttribute(self.expecting(), "f32 array".into()).into()) 92 | } 93 | 94 | /// Loads `f64` array. 95 | fn load_seq_f64( 96 | self, 97 | _: impl Iterator>, 98 | _len: usize, 99 | ) -> Result { 100 | Err(DataError::UnexpectedAttribute(self.expecting(), "f64 array".into()).into()) 101 | } 102 | 103 | /// Loads binary value. 104 | /// 105 | /// This method should return error when the given reader returned error. 106 | fn load_binary(self, _: impl io::Read, _len: u64) -> Result { 107 | Err(DataError::UnexpectedAttribute(self.expecting(), "binary data".into()).into()) 108 | } 109 | 110 | /// Loads binary value on buffered reader. 111 | /// 112 | /// This method should return error when the given reader returned error. 113 | #[inline] 114 | fn load_binary_buffered(self, reader: impl io::BufRead, len: u64) -> Result { 115 | self.load_binary(reader, len) 116 | } 117 | 118 | /// Loads string value. 119 | /// 120 | /// This method should return error when the given reader returned error. 121 | fn load_string(self, _: impl io::Read, _len: u64) -> Result { 122 | Err(DataError::UnexpectedAttribute(self.expecting(), "string data".into()).into()) 123 | } 124 | 125 | /// Loads string value on buffered reader. 126 | /// 127 | /// This method should return error when the given reader returned error. 128 | #[inline] 129 | fn load_string_buffered(self, reader: impl io::BufRead, len: u64) -> Result { 130 | self.load_string(reader, len) 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/pull_parser/v7400/attribute/loaders.rs: -------------------------------------------------------------------------------- 1 | //! Node attribute loaders. 2 | 3 | pub use self::{ 4 | direct::DirectLoader, 5 | single::{ArrayLoader, BinaryLoader, PrimitiveLoader, StringLoader}, 6 | type_::TypeLoader, 7 | }; 8 | 9 | mod direct; 10 | mod single; 11 | mod type_; 12 | -------------------------------------------------------------------------------- /src/pull_parser/v7400/attribute/loaders/direct.rs: -------------------------------------------------------------------------------- 1 | //! Direct attribute value loader. 2 | 3 | use std::io; 4 | 5 | use crate::{ 6 | low::v7400::AttributeValue, 7 | pull_parser::{v7400::LoadAttribute, Result}, 8 | }; 9 | 10 | /// Loader for [`AttributeValue`]. 11 | #[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 12 | pub struct DirectLoader; 13 | 14 | impl LoadAttribute for DirectLoader { 15 | type Output = AttributeValue; 16 | 17 | #[inline] 18 | fn expecting(&self) -> String { 19 | "any type".into() 20 | } 21 | 22 | #[inline] 23 | fn load_bool(self, v: bool) -> Result { 24 | Ok(AttributeValue::Bool(v)) 25 | } 26 | 27 | #[inline] 28 | fn load_i16(self, v: i16) -> Result { 29 | Ok(AttributeValue::I16(v)) 30 | } 31 | 32 | #[inline] 33 | fn load_i32(self, v: i32) -> Result { 34 | Ok(AttributeValue::I32(v)) 35 | } 36 | 37 | #[inline] 38 | fn load_i64(self, v: i64) -> Result { 39 | Ok(AttributeValue::I64(v)) 40 | } 41 | 42 | #[inline] 43 | fn load_f32(self, v: f32) -> Result { 44 | Ok(AttributeValue::F32(v)) 45 | } 46 | 47 | #[inline] 48 | fn load_f64(self, v: f64) -> Result { 49 | Ok(AttributeValue::F64(v)) 50 | } 51 | 52 | #[inline] 53 | fn load_seq_bool( 54 | self, 55 | iter: impl Iterator>, 56 | _len: usize, 57 | ) -> Result { 58 | Ok(AttributeValue::ArrBool(iter.collect::>()?)) 59 | } 60 | 61 | #[inline] 62 | fn load_seq_i32( 63 | self, 64 | iter: impl Iterator>, 65 | _len: usize, 66 | ) -> Result { 67 | Ok(AttributeValue::ArrI32(iter.collect::>()?)) 68 | } 69 | 70 | #[inline] 71 | fn load_seq_i64( 72 | self, 73 | iter: impl Iterator>, 74 | _len: usize, 75 | ) -> Result { 76 | Ok(AttributeValue::ArrI64(iter.collect::>()?)) 77 | } 78 | 79 | #[inline] 80 | fn load_seq_f32( 81 | self, 82 | iter: impl Iterator>, 83 | _len: usize, 84 | ) -> Result { 85 | Ok(AttributeValue::ArrF32(iter.collect::>()?)) 86 | } 87 | 88 | #[inline] 89 | fn load_seq_f64( 90 | self, 91 | iter: impl Iterator>, 92 | _len: usize, 93 | ) -> Result { 94 | Ok(AttributeValue::ArrF64(iter.collect::>()?)) 95 | } 96 | 97 | #[inline] 98 | fn load_binary(self, mut reader: impl io::Read, len: u64) -> Result { 99 | let mut buf = Vec::with_capacity(len as usize); 100 | reader.read_to_end(&mut buf)?; 101 | Ok(AttributeValue::Binary(buf)) 102 | } 103 | 104 | #[inline] 105 | fn load_string(self, mut reader: impl io::Read, len: u64) -> Result { 106 | let mut buf = String::with_capacity(len as usize); 107 | reader.read_to_string(&mut buf)?; 108 | Ok(AttributeValue::String(buf)) 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/pull_parser/v7400/attribute/loaders/single.rs: -------------------------------------------------------------------------------- 1 | //! Single type loader. 2 | 3 | use std::io; 4 | 5 | use crate::pull_parser::{v7400::LoadAttribute, Result}; 6 | 7 | /// Loader for primitive types. 8 | /// 9 | /// Supported types are: `bool`, `i16` , `i32`, `i64`, `f32`, and `f64`. 10 | #[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 11 | pub struct PrimitiveLoader(std::marker::PhantomData); 12 | 13 | /// Generates `LoadAttribute` implementations for `PrimitiveLoader`. 14 | macro_rules! impl_load_attribute_for_primitives { 15 | ($ty:ty, $method_name:ident, $expecting_type:expr) => { 16 | impl LoadAttribute for PrimitiveLoader<$ty> { 17 | type Output = $ty; 18 | 19 | fn expecting(&self) -> String { 20 | $expecting_type.into() 21 | } 22 | 23 | #[inline] 24 | fn $method_name(self, v: $ty) -> Result { 25 | Ok(v) 26 | } 27 | } 28 | }; 29 | } 30 | 31 | impl_load_attribute_for_primitives!(bool, load_bool, "single boolean"); 32 | impl_load_attribute_for_primitives!(i16, load_i16, "single i16"); 33 | impl_load_attribute_for_primitives!(i32, load_i32, "single i32"); 34 | impl_load_attribute_for_primitives!(i64, load_i64, "single i64"); 35 | impl_load_attribute_for_primitives!(f32, load_f32, "single f32"); 36 | impl_load_attribute_for_primitives!(f64, load_f64, "single f64"); 37 | 38 | /// Loader for array types. 39 | /// 40 | /// Supported types are: `Vec<{bool, i32, i64, f32, f64}>`. 41 | #[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 42 | pub struct ArrayLoader(std::marker::PhantomData); 43 | 44 | /// Generates `LoadAttribute` implementations for `PrimitiveLoader`. 45 | macro_rules! impl_load_attribute_for_arrays { 46 | ($ty:ty, $method_name:ident, $expecting_type:expr) => { 47 | impl LoadAttribute for ArrayLoader> { 48 | type Output = Vec<$ty>; 49 | 50 | fn expecting(&self) -> String { 51 | $expecting_type.into() 52 | } 53 | 54 | #[inline] 55 | fn $method_name( 56 | self, 57 | iter: impl Iterator>, 58 | _: usize, 59 | ) -> Result { 60 | iter.collect::>() 61 | } 62 | } 63 | }; 64 | } 65 | 66 | impl_load_attribute_for_arrays!(bool, load_seq_bool, "boolean array"); 67 | impl_load_attribute_for_arrays!(i32, load_seq_i32, "i32 array"); 68 | impl_load_attribute_for_arrays!(i64, load_seq_i64, "i64 array"); 69 | impl_load_attribute_for_arrays!(f32, load_seq_f32, "f32 array"); 70 | impl_load_attribute_for_arrays!(f64, load_seq_f64, "f64 array"); 71 | 72 | /// Loader for a binary. 73 | #[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 74 | pub struct BinaryLoader; 75 | 76 | impl LoadAttribute for BinaryLoader { 77 | type Output = Vec; 78 | 79 | fn expecting(&self) -> String { 80 | "binary".into() 81 | } 82 | 83 | #[inline] 84 | fn load_binary(self, mut reader: impl io::Read, len: u64) -> Result { 85 | let mut buf = Vec::with_capacity(len as usize); 86 | reader.read_to_end(&mut buf)?; 87 | Ok(buf) 88 | } 89 | } 90 | 91 | /// Loader for a string. 92 | #[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 93 | pub struct StringLoader; 94 | 95 | impl LoadAttribute for StringLoader { 96 | type Output = String; 97 | 98 | fn expecting(&self) -> String { 99 | "string".into() 100 | } 101 | 102 | #[inline] 103 | fn load_string(self, mut reader: impl io::Read, len: u64) -> Result { 104 | let mut buf = String::with_capacity(len as usize); 105 | reader.read_to_string(&mut buf)?; 106 | Ok(buf) 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/pull_parser/v7400/attribute/loaders/type_.rs: -------------------------------------------------------------------------------- 1 | //! Attribute type loader. 2 | 3 | use std::io; 4 | 5 | use crate::{ 6 | low::v7400::AttributeType, 7 | pull_parser::{v7400::LoadAttribute, Result}, 8 | }; 9 | 10 | /// Loader for node attribute type ([`AttributeType`]). 11 | /// 12 | /// This returns only node attribute type ([`AttributeType`]) and discands 13 | /// its real value. 14 | #[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 15 | pub struct TypeLoader; 16 | 17 | impl LoadAttribute for TypeLoader { 18 | type Output = AttributeType; 19 | 20 | #[inline] 21 | fn expecting(&self) -> String { 22 | "any type".into() 23 | } 24 | 25 | #[inline] 26 | fn load_bool(self, _: bool) -> Result { 27 | Ok(AttributeType::Bool) 28 | } 29 | 30 | #[inline] 31 | fn load_i16(self, _: i16) -> Result { 32 | Ok(AttributeType::I16) 33 | } 34 | 35 | #[inline] 36 | fn load_i32(self, _: i32) -> Result { 37 | Ok(AttributeType::I32) 38 | } 39 | 40 | #[inline] 41 | fn load_i64(self, _: i64) -> Result { 42 | Ok(AttributeType::I64) 43 | } 44 | 45 | #[inline] 46 | fn load_f32(self, _: f32) -> Result { 47 | Ok(AttributeType::F32) 48 | } 49 | 50 | #[inline] 51 | fn load_f64(self, _: f64) -> Result { 52 | Ok(AttributeType::F64) 53 | } 54 | 55 | #[inline] 56 | fn load_seq_bool( 57 | self, 58 | _: impl Iterator>, 59 | _len: usize, 60 | ) -> Result { 61 | Ok(AttributeType::ArrBool) 62 | } 63 | 64 | #[inline] 65 | fn load_seq_i32( 66 | self, 67 | _: impl Iterator>, 68 | _len: usize, 69 | ) -> Result { 70 | Ok(AttributeType::ArrI32) 71 | } 72 | 73 | #[inline] 74 | fn load_seq_i64( 75 | self, 76 | _: impl Iterator>, 77 | _len: usize, 78 | ) -> Result { 79 | Ok(AttributeType::ArrI64) 80 | } 81 | 82 | #[inline] 83 | fn load_seq_f32( 84 | self, 85 | _: impl Iterator>, 86 | _len: usize, 87 | ) -> Result { 88 | Ok(AttributeType::ArrF32) 89 | } 90 | 91 | #[inline] 92 | fn load_seq_f64( 93 | self, 94 | _: impl Iterator>, 95 | _len: usize, 96 | ) -> Result { 97 | Ok(AttributeType::ArrF64) 98 | } 99 | 100 | #[inline] 101 | fn load_binary(self, _: impl io::Read, _len: u64) -> Result { 102 | Ok(AttributeType::Binary) 103 | } 104 | 105 | #[inline] 106 | fn load_string(self, _: impl io::Read, _len: u64) -> Result { 107 | Ok(AttributeType::String) 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/pull_parser/v7400/event.rs: -------------------------------------------------------------------------------- 1 | //! Parser event. 2 | 3 | use std::io; 4 | 5 | use crate::{ 6 | low::v7400::FbxFooter, 7 | pull_parser::{ 8 | v7400::{Attributes, Parser}, 9 | Result, 10 | }, 11 | }; 12 | 13 | /// Parser event. 14 | #[derive(Debug)] 15 | pub enum Event<'a, R> { 16 | /// Start of a node. 17 | StartNode(StartNode<'a, R>), 18 | /// End of a node. 19 | EndNode, 20 | /// End of an FBX document. 21 | /// 22 | /// This will contain `Ok(_)` if the the FBX footer is succssfully read, 23 | /// contain `Err(_)` if the parser failed to load the FBX footer. 24 | EndFbx(Result>), 25 | } 26 | 27 | /// Node start event. 28 | #[derive(Debug)] 29 | pub struct StartNode<'a, R> { 30 | /// Parser (used as a token). 31 | parser: &'a mut Parser, 32 | } 33 | 34 | impl<'a, R: 'a + io::Read> StartNode<'a, R> { 35 | /// Creates a new `StartNode`. 36 | #[inline] 37 | #[must_use] 38 | pub(crate) fn new(parser: &'a mut Parser) -> Self { 39 | Self { parser } 40 | } 41 | 42 | /// Returns the node name. 43 | #[inline] 44 | #[must_use] 45 | pub fn name(&self) -> &str { 46 | self.parser.current_node_name() 47 | } 48 | 49 | /// Returns node attributes reader. 50 | #[inline] 51 | #[must_use] 52 | pub fn attributes(self) -> Attributes<'a, R> { 53 | Attributes::from_parser(self.parser) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/pull_parser/v7400/read.rs: -------------------------------------------------------------------------------- 1 | //! Reader functions and traits. 2 | 3 | use std::io; 4 | 5 | use crate::pull_parser::{v7400::Parser, Result}; 6 | 7 | /// A trait for types readable from a reader. 8 | pub(crate) trait FromReader: Sized { 9 | /// Reads the data from the given reader. 10 | fn from_reader(reader: &mut impl io::Read) -> Result; 11 | } 12 | 13 | impl FromReader for u8 { 14 | #[inline] 15 | fn from_reader(reader: &mut impl io::Read) -> Result { 16 | let mut buf = 0_u8; 17 | reader.read_exact(std::slice::from_mut(&mut buf))?; 18 | Ok(buf) 19 | } 20 | } 21 | 22 | /// Implement `FromReader` trait for primitive types. 23 | macro_rules! impl_from_reader_for_primitives { 24 | ($ty:ty) => { 25 | impl FromReader for $ty { 26 | #[inline] 27 | fn from_reader(reader: &mut impl io::Read) -> Result { 28 | let mut buf = [0_u8; std::mem::size_of::<$ty>()]; 29 | reader.read_exact(&mut buf)?; 30 | Ok(<$ty>::from_le_bytes(buf)) 31 | } 32 | } 33 | }; 34 | } 35 | 36 | impl_from_reader_for_primitives! { u16 } 37 | impl_from_reader_for_primitives! { u32 } 38 | impl_from_reader_for_primitives! { u64 } 39 | impl_from_reader_for_primitives! { i16 } 40 | impl_from_reader_for_primitives! { i32 } 41 | impl_from_reader_for_primitives! { i64 } 42 | impl_from_reader_for_primitives! { f32 } 43 | impl_from_reader_for_primitives! { f64 } 44 | 45 | /// A trait for types readable from a parser. 46 | pub(crate) trait FromParser: Sized { 47 | /// Reads the data from the given parser. 48 | fn read_from_parser(parser: &mut Parser) -> Result; 49 | } 50 | 51 | impl FromParser for T { 52 | #[inline] 53 | fn read_from_parser(parser: &mut Parser) -> Result { 54 | FromReader::from_reader(parser.reader()) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/pull_parser/version.rs: -------------------------------------------------------------------------------- 1 | //! FBX parser version types. 2 | 3 | use log::info; 4 | 5 | use crate::low::FbxVersion; 6 | 7 | /// Parser version for each version of FBX. 8 | /// 9 | /// Some parser supports multiple versions of FBX binary. 10 | /// Each variants of this type corresponds to a parser implementation. 11 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 12 | #[non_exhaustive] 13 | pub enum ParserVersion { 14 | /// FBX 7.4 and 7.5. 15 | V7400, 16 | } 17 | 18 | impl ParserVersion { 19 | /// Returns the parser version corresponding to the given FBX version. 20 | #[must_use] 21 | pub fn from_fbx_version(fbx_version: FbxVersion) -> Option { 22 | let raw = fbx_version.raw(); 23 | match raw { 24 | 7000..=7999 => { 25 | if raw < 7400 { 26 | info!(" 7500 { 28 | info!(">FBX-7.5 might be successfully read, but unsupported"); 29 | } 30 | Some(ParserVersion::V7400) 31 | } 32 | _ => None, 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/tree.rs: -------------------------------------------------------------------------------- 1 | //! FBX data tree. 2 | //! 3 | //! This module is enabled by `tree` feature. 4 | //! 5 | //! # Creating tree 6 | //! 7 | //! There are two ways to load a tree: easy setup and manual setup. 8 | //! 9 | //! ## Easy setup (recommended) 10 | //! 11 | //! If you don't care about precise FBX version (e.g. difference between FBX 7.4 12 | //! and 7.5), or warnings handling, you can use easy setup using [`any`] module. 13 | //! 14 | //! `AnyTree` loader prints all parser warnings using `log` crate, but treats 15 | //! them non-critical, and the processing will continue. 16 | //! 17 | //! ```no_run 18 | //! use fbxcel::tree::any::AnyTree; 19 | //! 20 | //! let file = std::fs::File::open("sample.fbx").expect("Failed to open file"); 21 | //! // You can also use raw `file`, but do buffering for better efficiency. 22 | //! let reader = std::io::BufReader::new(file); 23 | //! 24 | //! // Use `from_seekable_reader` for readers implementing `std::io::Seek`. 25 | //! // To use readers without `std::io::Seek` implementation, use `from_reader` 26 | //! // instead. 27 | //! match AnyTree::from_seekable_reader(reader).expect("Failed to load tree") { 28 | //! AnyTree::V7400(fbx_version, tree, footer) => { 29 | //! // You got a tree (and footer)! Do what you want! 30 | //! } 31 | //! // `AnyTree` is nonexhaustive. 32 | //! // You should handle new unknown tree version case. 33 | //! _ => panic!("Got FBX tree of unsupported version"), 34 | //! } 35 | //! ``` 36 | //! 37 | //! ## Manual setup 38 | //! 39 | //! In this way you have full control, but usual users don't need this. 40 | //! 41 | //! 1. Get FBX parser. 42 | //! 2. Pass the parser to appropriate tree loader. 43 | //! 44 | //! About tree loaders, see [`v7400::Loader`]. 45 | //! (Currently, only `v7400` tree is supported.) 46 | 47 | pub mod any; 48 | pub mod v7400; 49 | -------------------------------------------------------------------------------- /src/tree/any.rs: -------------------------------------------------------------------------------- 1 | //! Types and functions for all supported versions. 2 | 3 | use std::io::{Read, Seek}; 4 | 5 | use log::warn; 6 | 7 | pub use self::error::{Error, Result}; 8 | use crate::{ 9 | low::{self, FbxVersion}, 10 | pull_parser::{self, any::AnyParser}, 11 | tree, 12 | }; 13 | 14 | mod error; 15 | 16 | /// FBX tree type with any supported version. 17 | #[non_exhaustive] 18 | pub enum AnyTree { 19 | /// FBX 7.4 or later. 20 | V7400( 21 | FbxVersion, 22 | tree::v7400::Tree, 23 | std::result::Result, pull_parser::Error>, 24 | ), 25 | } 26 | 27 | impl AnyTree { 28 | /// Loads a tree from the given reader. 29 | /// 30 | /// This works for seekable readers (which implement [`std::io::Seek`]), but 31 | /// [`from_seekable_reader`][`Self::from_seekable_reader`] should be used for them, because it is more 32 | /// efficent. 33 | pub fn from_reader(reader: impl Read) -> Result { 34 | match AnyParser::from_reader(reader)? { 35 | AnyParser::V7400(mut parser) => { 36 | let fbx_version = parser.fbx_version(); 37 | parser.set_warning_handler(|w, pos| { 38 | warn!("WARNING: {} (pos={:?})", w, pos); 39 | Ok(()) 40 | }); 41 | let tree_loader = tree::v7400::Loader::new(); 42 | let (tree, footer) = tree_loader.load(&mut parser)?; 43 | Ok(AnyTree::V7400(fbx_version, tree, footer)) 44 | } 45 | } 46 | } 47 | 48 | /// Loads a tree from the given seekable reader. 49 | pub fn from_seekable_reader(reader: impl Read + Seek) -> Result { 50 | match AnyParser::from_seekable_reader(reader)? { 51 | AnyParser::V7400(mut parser) => { 52 | let fbx_version = parser.fbx_version(); 53 | parser.set_warning_handler(|w, pos| { 54 | warn!("WARNING: {} (pos={:?})", w, pos); 55 | Ok(()) 56 | }); 57 | let tree_loader = tree::v7400::Loader::new(); 58 | let (tree, footer) = tree_loader.load(&mut parser)?; 59 | Ok(AnyTree::V7400(fbx_version, tree, footer)) 60 | } 61 | } 62 | } 63 | 64 | /// Returns the FBX version of the document the tree came from. 65 | #[inline] 66 | #[must_use] 67 | pub fn fbx_version(&self) -> FbxVersion { 68 | match self { 69 | Self::V7400(ver, _, _) => *ver, 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/tree/any/error.rs: -------------------------------------------------------------------------------- 1 | //! Error and result types for `tree::any` module. 2 | 3 | use std::{error, fmt}; 4 | 5 | use crate::{pull_parser, tree}; 6 | 7 | /// AnyTree load result. 8 | pub type Result = std::result::Result; 9 | 10 | /// Error. 11 | #[derive(Debug)] 12 | #[non_exhaustive] 13 | pub enum Error { 14 | /// Parser creation error. 15 | ParserCreation(pull_parser::any::Error), 16 | /// Parser error. 17 | Parser(pull_parser::Error), 18 | /// Tree load error. 19 | Tree(Box), 20 | } 21 | 22 | impl error::Error for Error { 23 | fn source(&self) -> Option<&(dyn error::Error + 'static)> { 24 | match self { 25 | Error::ParserCreation(e) => Some(e), 26 | Error::Parser(e) => Some(e), 27 | Error::Tree(e) => Some(&**e), 28 | } 29 | } 30 | } 31 | 32 | impl fmt::Display for Error { 33 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 34 | match self { 35 | Error::ParserCreation(e) => write!(f, "Failed to create a parser: {}", e), 36 | Error::Parser(e) => write!(f, "Parser error: {}", e), 37 | Error::Tree(e) => write!(f, "Tree load error: {}", e), 38 | } 39 | } 40 | } 41 | 42 | impl From for Error { 43 | #[inline] 44 | fn from(e: pull_parser::any::Error) -> Self { 45 | Error::ParserCreation(e) 46 | } 47 | } 48 | 49 | impl From for Error { 50 | #[inline] 51 | fn from(e: pull_parser::Error) -> Self { 52 | Error::Parser(e) 53 | } 54 | } 55 | 56 | impl From for Error { 57 | #[inline] 58 | fn from(e: tree::v7400::LoadError) -> Self { 59 | Error::Tree(e.into()) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/tree/v7400/error.rs: -------------------------------------------------------------------------------- 1 | //! Error types. 2 | 3 | use std::{error, fmt}; 4 | 5 | use crate::pull_parser::Error as ParserError; 6 | 7 | /// FBX data tree load error. 8 | #[derive(Debug)] 9 | #[non_exhaustive] 10 | pub enum LoadError { 11 | /// Bad parser. 12 | /// 13 | /// This error will be mainly caused by user logic error. 14 | BadParser, 15 | /// Parser error. 16 | Parser(ParserError), 17 | } 18 | 19 | impl fmt::Display for LoadError { 20 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 21 | match self { 22 | LoadError::BadParser => f.write_str("Attempt to use a bad parser"), 23 | LoadError::Parser(e) => write!(f, "Parser error: {}", e), 24 | } 25 | } 26 | } 27 | 28 | impl error::Error for LoadError { 29 | #[inline] 30 | fn source(&self) -> Option<&(dyn error::Error + 'static)> { 31 | match self { 32 | LoadError::Parser(e) => Some(e), 33 | _ => None, 34 | } 35 | } 36 | } 37 | 38 | impl From for LoadError { 39 | #[inline] 40 | fn from(e: ParserError) -> Self { 41 | LoadError::Parser(e) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/tree/v7400/loader.rs: -------------------------------------------------------------------------------- 1 | //! FBX data tree loader. 2 | 3 | use std::io; 4 | 5 | use indextree::Arena; 6 | use log::{debug, error, trace}; 7 | use string_interner::{DefaultBackend, StringInterner}; 8 | 9 | use crate::{ 10 | low::v7400::FbxFooter, 11 | pull_parser::{ 12 | v7400::{attribute::loaders::DirectLoader, Event, Parser, StartNode}, 13 | Error as ParserError, 14 | }, 15 | tree::v7400::{LoadError, NodeData, NodeId, NodeNameSym, Tree}, 16 | }; 17 | 18 | /// FBX data tree loader. 19 | #[derive(Debug, Clone)] 20 | pub struct Loader { 21 | /// Tree data. 22 | arena: Arena, 23 | /// Node name interner. 24 | node_names: StringInterner>, 25 | /// (Implicit) root node ID. 26 | root_id: NodeId, 27 | } 28 | 29 | impl Loader { 30 | /// Creates a new `Loader`. 31 | #[inline] 32 | #[must_use] 33 | pub fn new() -> Self { 34 | Self::default() 35 | } 36 | 37 | /// Loads a tree from the given parser, and returns the tree and FBX footer. 38 | /// 39 | /// The given parser should be brand-new, i.e. it should not have emited any 40 | /// events. 41 | /// This can be checked by [`Parser::is_used()`]. 42 | /// If the given parser is already used, [`LoadError::BadParser`] error will 43 | /// be returned. 44 | /// 45 | /// If the tree is successfully read but FBX footer is not, 46 | /// `Ok(tree, Err(parser_error))` is returned. 47 | pub fn load( 48 | mut self, 49 | parser: &mut Parser, 50 | ) -> Result<(Tree, Result, ParserError>), LoadError> { 51 | debug!("Loading FBX data tree from a parser"); 52 | 53 | if parser.is_used() { 54 | error!("The given parser should be brand-new, but it has already emitted some events"); 55 | return Err(LoadError::BadParser); 56 | } 57 | 58 | let mut open_nodes = vec![self.root_id]; 59 | let footer = loop { 60 | trace!("Loading next parser event: open_nodes={:?}", open_nodes); 61 | assert!( 62 | !open_nodes.is_empty(), 63 | "Open nodes stack should not be empty on loop start" 64 | ); 65 | match parser.next_event()? { 66 | Event::StartNode(start) => { 67 | trace!("Got `Event::StartNode(name={:?})`", start.name()); 68 | let parent = open_nodes 69 | .last_mut() 70 | .expect("Should never fail: Open nodes stack should not be empty here"); 71 | let current = self.add_node(*parent, start)?; 72 | 73 | // Update the open nodes stack. 74 | open_nodes.push(current); 75 | } 76 | Event::EndNode => { 77 | trace!("Got `Event::EndNode`"); 78 | open_nodes 79 | .pop() 80 | .expect("Should never fail: Open nodes stack should not be empty here"); 81 | } 82 | Event::EndFbx(footer) => { 83 | trace!("Got `Event::EndFbx(_)`"); 84 | open_nodes 85 | .pop() 86 | .expect("Should never fail: Open nodes stack should not be empty here"); 87 | break footer; 88 | } 89 | } 90 | }; 91 | assert!( 92 | open_nodes.is_empty(), 93 | "Should never fail: There should be no open nodes after `EndFbx` event is emitted" 94 | ); 95 | 96 | debug!("Successfully loaded FBX data tree"); 97 | let tree = Tree::new(self.arena, self.node_names, self.root_id); 98 | Ok((tree, footer)) 99 | } 100 | 101 | /// Creates and adds a new node to the tree. 102 | fn add_node( 103 | &mut self, 104 | parent: NodeId, 105 | start: StartNode<'_, R>, 106 | ) -> Result { 107 | trace!( 108 | "Adding a new child name={:?} to the parent {:?}", 109 | start.name(), 110 | parent 111 | ); 112 | 113 | // Create a new node. 114 | let current = { 115 | let name_sym = self.node_names.get_or_intern(start.name()); 116 | let attributes = start 117 | .attributes() 118 | .into_iter(std::iter::repeat(DirectLoader)) 119 | .collect::, _>>()?; 120 | 121 | NodeId::new(self.arena.new_node(NodeData::new(name_sym, attributes))) 122 | }; 123 | 124 | // Set the parent. 125 | parent.raw().append(current.raw(), &mut self.arena); 126 | 127 | trace!( 128 | "Successfully added a new child {:?} to the parent {:?}", 129 | current, 130 | parent 131 | ); 132 | 133 | Ok(current) 134 | } 135 | } 136 | 137 | impl Default for Loader { 138 | fn default() -> Self { 139 | let mut arena = Arena::new(); 140 | let mut node_names = StringInterner::new(); 141 | let root_id = { 142 | // Use empty string as dummy node name. 143 | let empty_sym = node_names.get_or_intern(""); 144 | NodeId::new(arena.new_node(NodeData::new(empty_sym, Vec::new()))) 145 | }; 146 | Self { 147 | arena, 148 | node_names, 149 | root_id, 150 | } 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /src/tree/v7400/macros.rs: -------------------------------------------------------------------------------- 1 | //! Macros. 2 | 3 | /// Constructs a tree. 4 | /// 5 | /// Enabled by `tree` feature. 6 | /// 7 | /// # Examples 8 | /// 9 | /// ``` 10 | /// # use fbxcel::tree_v7400; 11 | /// let empty_tree = tree_v7400! {}; 12 | /// ``` 13 | /// 14 | /// ``` 15 | /// # use fbxcel::tree_v7400; 16 | /// let tree = tree_v7400! { 17 | /// Node0: { 18 | /// Node0_0: {} 19 | /// Node0_1: {} 20 | /// } 21 | /// Node1: { 22 | /// // You can use trailing comma. 23 | /// Node1_0: {}, 24 | /// Node1_1: {}, 25 | /// } 26 | /// // Use parens to specify attributes by single array. 27 | /// // Note that the expression inside parens should implement 28 | /// // `IntoIterator`. 29 | /// Node2: (vec!["hello".into(), "world".into(), 42i32.into()]) {} 30 | /// // Use brackets to specify attributes one by one. 31 | /// Node3: ["hello", "world", 1.234f32, &b"BINARY"[..]] {} 32 | /// }; 33 | /// ``` 34 | #[cfg_attr(docsrs, doc(cfg(feature = "tree")))] 35 | #[macro_export] 36 | macro_rules! tree_v7400 { 37 | (@__node, $tree:ident, $parent:ident,) => {}; 38 | (@__node, $tree:ident, $parent:ident, , $($rest:tt)*) => { 39 | tree_v7400! { @__node, $tree, $parent, $($rest)* } 40 | }; 41 | 42 | (@__node, $tree:ident, $parent:ident, 43 | $name:ident: { 44 | $($subtree:tt)* 45 | } 46 | $($rest:tt)* 47 | ) => {{ 48 | { 49 | let _node = $tree.append_new($parent, stringify!($name)); 50 | tree_v7400! { @__node, $tree, _node, $($subtree)* } 51 | } 52 | tree_v7400! { @__node, $tree, $parent, $($rest)* } 53 | }}; 54 | (@__node, $tree:ident, $parent:ident, 55 | $name:ident: [$($attr:expr),* $(,)?] { 56 | $($subtree:tt)* 57 | } 58 | $($rest:tt)* 59 | ) => {{ 60 | { 61 | let _node = $tree.append_new($parent, stringify!($name)); 62 | $( 63 | $tree.append_attribute(_node, $attr); 64 | )* 65 | tree_v7400! { @__node, $tree, _node, $($subtree)* } 66 | } 67 | tree_v7400! { @__node, $tree, $parent, $($rest)* } 68 | }}; 69 | (@__node, $tree:ident, $parent:ident, 70 | $name:ident: ($attrs:expr) { 71 | $($subtree:tt)* 72 | } 73 | $($rest:tt)* 74 | ) => {{ 75 | { 76 | let _node = $tree.append_new($parent, stringify!($name)); 77 | $attrs.into_iter().for_each(|attr: $crate::low::v7400::AttributeValue| $tree.append_attribute(_node, attr)); 78 | tree_v7400! { @__node, $tree, _node, $($subtree)* } 79 | } 80 | tree_v7400! { @__node, $tree, $parent, $($rest)* } 81 | }}; 82 | 83 | ($($rest:tt)*) => { 84 | { 85 | #[allow(unused_mut)] 86 | let mut tree = $crate::tree::v7400::Tree::default(); 87 | let _root = tree.root().node_id(); 88 | tree_v7400! { @__node, tree, _root, $($rest)* } 89 | tree 90 | } 91 | }; 92 | } 93 | 94 | #[cfg(test)] 95 | mod tests { 96 | use crate::tree::v7400::Tree; 97 | 98 | #[test] 99 | fn empty_trees_eq() { 100 | let tree1 = tree_v7400! {}; 101 | let tree2 = tree_v7400! {}; 102 | assert!(tree1.strict_eq(&tree2)); 103 | } 104 | 105 | #[test] 106 | fn empty_nodes() { 107 | let _ = tree_v7400! { 108 | Hello: {} 109 | World: {}, 110 | }; 111 | } 112 | 113 | #[test] 114 | fn nested_node() { 115 | let _ = tree_v7400! { 116 | Hello: { 117 | Hello1: {}, 118 | Hello2: {} 119 | } 120 | World: { 121 | World1: { 122 | World1_1: {} 123 | World1_2: {} 124 | } 125 | World2: {}, 126 | }, 127 | }; 128 | } 129 | 130 | #[test] 131 | fn nested_node_with_attrs() { 132 | let _ = tree_v7400! { 133 | Hello: { 134 | Hello1: (vec!["string".into()]) {}, 135 | Hello2: [1.234f32, 42i64] {} 136 | } 137 | World: { 138 | World1: { 139 | World1_1: (vec!["Hello".into(), 42i32.into()]) {} 140 | World1_2: [] {} 141 | } 142 | World2: {}, 143 | }, 144 | }; 145 | } 146 | 147 | #[test] 148 | fn compare_complex_trees() { 149 | fn gentree() -> Tree { 150 | tree_v7400! { 151 | Node0: {}, 152 | Node1: { 153 | Node1_0: {}, 154 | Node1_1: {}, 155 | Node1_2: { 156 | Node1_2_child: {}, 157 | Node1_2_child: {}, 158 | }, 159 | }, 160 | Node2: [true, 42i16, 42i32, 42i64, 1.414f32, 1.234f64] { 161 | Node2_0: (vec![vec![true, false].into(), vec![0i32, 42i32].into()]) {}, 162 | Node2_1: [ 163 | vec![f32::NAN, f32::INFINITY], 164 | vec![f64::NAN, f64::INFINITY] 165 | ] {}, 166 | }, 167 | } 168 | } 169 | let tree1 = gentree(); 170 | let tree2 = gentree(); 171 | assert!(tree1.strict_eq(&tree2)); 172 | let empty = tree_v7400!(); 173 | assert!(!empty.strict_eq(&tree2)); 174 | } 175 | 176 | #[test] 177 | fn correct_tree() { 178 | let tree_manual = { 179 | let mut tree = Tree::default(); 180 | // Node 0: Without attributes, with children. 181 | // Node 0-x: Without attributes, without children. 182 | let node0_id = tree.append_new(tree.root().node_id(), "Node0"); 183 | tree.append_new(node0_id, "Node0_0"); 184 | tree.append_new(node0_id, "Node0_1"); 185 | 186 | // Node 1: With attributes, with children. 187 | // Node 1-x: With attributes, without children. 188 | let node1_id = tree.append_new(tree.root().node_id(), "Node1"); 189 | tree.append_attribute(node1_id, true); 190 | let node1_0_id = tree.append_new(node1_id, "Node1_0"); 191 | tree.append_attribute(node1_0_id, 42i32); 192 | tree.append_attribute(node1_0_id, 1.234f64); 193 | let node1_1_id = tree.append_new(node1_id, "Node1_1"); 194 | tree.append_attribute(node1_1_id, &[1u8, 2, 4, 8, 16] as &[_]); 195 | tree.append_attribute(node1_1_id, "Hello, world"); 196 | 197 | tree 198 | }; 199 | 200 | let tree_macro = tree_v7400! { 201 | Node0: { 202 | Node0_0: {}, 203 | Node0_1: {}, 204 | }, 205 | Node1: [true] { 206 | Node1_0: (vec![42i32.into(), 1.234f64.into()]) {} 207 | Node1_1: [&[1u8, 2, 4, 8, 16][..], "Hello, world"] {} 208 | }, 209 | }; 210 | 211 | assert!(tree_manual.strict_eq(&tree_macro)); 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /src/tree/v7400/node.rs: -------------------------------------------------------------------------------- 1 | //! Node type. 2 | 3 | use crate::tree::v7400::{DepthFirstTraverseSubtree, NodeHandle, Tree}; 4 | 5 | pub(crate) use self::{data::NodeData, name::NodeNameSym}; 6 | 7 | mod data; 8 | pub(crate) mod handle; 9 | mod name; 10 | 11 | /// Node ID in FBX data tree. 12 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 13 | pub struct NodeId(indextree::NodeId); 14 | 15 | impl NodeId { 16 | /// Creates a new `NodeId`. 17 | #[inline] 18 | #[must_use] 19 | pub(crate) fn new(id: indextree::NodeId) -> Self { 20 | NodeId(id) 21 | } 22 | 23 | /// Returns the raw node ID used by internal tree implementation. 24 | #[inline] 25 | #[must_use] 26 | pub(crate) fn raw(self) -> indextree::NodeId { 27 | self.0 28 | } 29 | 30 | /// Creates a new `NodeHandle` to make accesible to the node in the tree. 31 | /// 32 | /// # Panics and safety 33 | /// 34 | /// This may panic if the given node ID is not used in the given tree. 35 | /// 36 | /// Even if creation of an invalid node ID does not panic, subsequent 37 | /// operations through `NodeHandle` object may panic if the given node ID is 38 | /// not used in the given tree. 39 | #[inline] 40 | #[must_use] 41 | pub fn to_handle(self, tree: &Tree) -> NodeHandle<'_> { 42 | NodeHandle::new(tree, self) 43 | } 44 | 45 | /// Returns the helper object to traverse the subtree (the node itself and its descendant). 46 | #[inline] 47 | #[must_use] 48 | pub fn traverse_depth_first(&self) -> DepthFirstTraverseSubtree { 49 | DepthFirstTraverseSubtree::with_root_id(*self) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/tree/v7400/node/data.rs: -------------------------------------------------------------------------------- 1 | //! Node-local data. 2 | 3 | use crate::{low::v7400::AttributeValue, tree::v7400::node::NodeNameSym}; 4 | 5 | /// Node-local data in FBX data tree. 6 | /// 7 | /// This does not manages relations among nodes (including parent-child 8 | /// relatinos). 9 | #[derive(Debug, Clone, PartialEq)] 10 | pub(crate) struct NodeData { 11 | /// Node name. 12 | name_sym: NodeNameSym, 13 | /// Node attributes. 14 | attributes: Vec, 15 | } 16 | 17 | impl NodeData { 18 | /// Returns the node name symbol. 19 | #[inline] 20 | #[must_use] 21 | pub(crate) fn name_sym(&self) -> NodeNameSym { 22 | self.name_sym 23 | } 24 | 25 | /// Returns the reference to the attributes. 26 | #[inline] 27 | #[must_use] 28 | pub(crate) fn attributes(&self) -> &[AttributeValue] { 29 | &self.attributes 30 | } 31 | 32 | /// Appends the given value to the attributes. 33 | #[inline] 34 | pub(crate) fn append_attribute(&mut self, v: AttributeValue) { 35 | self.attributes.push(v) 36 | } 37 | 38 | /// Appends the given value to the attributes. 39 | #[inline] 40 | pub(crate) fn get_attribute_mut(&mut self, i: usize) -> Option<&mut AttributeValue> { 41 | self.attributes.get_mut(i) 42 | } 43 | 44 | /// Replaces all attributes by the given one, and returns the old. 45 | #[inline] 46 | pub(crate) fn replace_attributes(&mut self, new: Vec) -> Vec { 47 | std::mem::replace(&mut self.attributes, new) 48 | } 49 | 50 | /// Creates a new `NodeData`. 51 | #[inline] 52 | #[must_use] 53 | pub(crate) fn new(name_sym: NodeNameSym, attributes: Vec) -> Self { 54 | Self { 55 | name_sym, 56 | attributes, 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/tree/v7400/node/handle.rs: -------------------------------------------------------------------------------- 1 | //! Node handle. 2 | 3 | use std::fmt; 4 | 5 | use crate::{ 6 | low::v7400::AttributeValue, 7 | tree::v7400::{NodeData, NodeId, NodeNameSym, Tree}, 8 | }; 9 | 10 | /// Node handle. 11 | #[derive(Debug, Clone, Copy)] 12 | pub struct NodeHandle<'a> { 13 | /// The tree the node belongs to. 14 | tree: &'a Tree, 15 | /// Node ID. 16 | node_id: NodeId, 17 | } 18 | 19 | impl<'a> NodeHandle<'a> { 20 | /// Creates a new `NodeHandle`. 21 | /// 22 | /// # Panics and safety 23 | /// 24 | /// This may panic if the given node ID is not used in the given tree. 25 | /// 26 | /// Even if `new()` does not panic, subsequent operations through 27 | /// `NodeHandle` object may panic if the given node ID is not used in the 28 | /// given tree. 29 | #[inline] 30 | #[must_use] 31 | pub(crate) fn new(tree: &'a Tree, node_id: NodeId) -> Self { 32 | assert!( 33 | tree.contains_node(node_id), 34 | "The node ID is not used in the given tree: node_id={:?}", 35 | node_id 36 | ); 37 | 38 | Self { tree, node_id } 39 | } 40 | 41 | /// Returns a reference to the tree. 42 | #[inline] 43 | #[must_use] 44 | pub fn tree(&self) -> &'a Tree { 45 | self.tree 46 | } 47 | 48 | /// Returns the node ID. 49 | #[inline] 50 | #[must_use] 51 | pub fn node_id(&self) -> NodeId { 52 | self.node_id 53 | } 54 | 55 | /// Returns the internally managed node data. 56 | #[inline] 57 | #[must_use] 58 | pub(crate) fn node(&self) -> &'a indextree::Node { 59 | self.tree.node(self.node_id) 60 | } 61 | 62 | /// Returns the node name symbol. 63 | #[inline] 64 | #[must_use] 65 | pub(crate) fn name_sym(&self) -> NodeNameSym { 66 | self.node().get().name_sym() 67 | } 68 | 69 | /// Returns the node name. 70 | #[inline] 71 | #[must_use] 72 | pub fn name(&self) -> &'a str { 73 | self.tree.resolve_node_name(self.name_sym()) 74 | } 75 | 76 | /// Returns the node attributes. 77 | #[inline] 78 | #[must_use] 79 | pub fn attributes(&self) -> &'a [AttributeValue] { 80 | self.node().get().attributes() 81 | } 82 | 83 | /// Returns an iterator of children with the given name. 84 | #[inline] 85 | #[must_use] 86 | pub fn children(&self) -> Children<'a> { 87 | Children { 88 | tree: self.tree, 89 | iter: self.node_id.raw().children(&self.tree.arena), 90 | } 91 | } 92 | 93 | /// Returns an iterator of children with the given name. 94 | #[inline] 95 | #[must_use] 96 | pub fn children_by_name(&self, name: &str) -> ChildrenByName<'a> { 97 | ChildrenByName { 98 | name_sym: self.tree.node_name_sym(name), 99 | children_iter: self.children(), 100 | } 101 | } 102 | 103 | /// Returns the first child with the given name. 104 | #[inline] 105 | #[must_use] 106 | pub fn first_child_by_name(&self, name: &str) -> Option { 107 | self.children_by_name(name).next() 108 | } 109 | 110 | /// Compares nodes strictly. 111 | /// 112 | /// Returns `true` if the two trees are same. 113 | /// 114 | /// Note that `f32` and `f64` values are compared bitwise. 115 | /// 116 | /// Note that this method compares tree data, not internal states of the 117 | /// trees. 118 | #[inline] 119 | #[must_use] 120 | pub fn strict_eq(&self, other: &Self) -> bool { 121 | nodes_strict_eq(*self, *other) 122 | } 123 | } 124 | 125 | /// Implement accessors to neighbor nodes. 126 | macro_rules! impl_related_node_accessor { 127 | ( 128 | $( 129 | $(#[$meta:meta])* 130 | $accessor:ident; 131 | )* 132 | ) => { 133 | impl<'a> NodeHandle<'a> { 134 | $( 135 | impl_related_node_accessor! { @single, $(#[$meta])* $accessor; } 136 | )* 137 | } 138 | }; 139 | (@single, $(#[$meta:meta])* $accessor:ident;) => { 140 | $(#[$meta])* 141 | #[must_use] 142 | pub fn $accessor(&self) -> Option> { 143 | self.node() 144 | .$accessor() 145 | .map(|id| NodeId::new(id).to_handle(&self.tree)) 146 | } 147 | }; 148 | } 149 | 150 | impl_related_node_accessor! { 151 | /// Returns parent node handle if available. 152 | parent; 153 | /// Returns first child node handle if available. 154 | first_child; 155 | /// Returns last child node handle if available. 156 | last_child; 157 | /// Returns previous sibling node handle if available. 158 | previous_sibling; 159 | /// Returns next sibling node handle if available. 160 | next_sibling; 161 | } 162 | 163 | /// Compares nodes strictly. 164 | #[must_use] 165 | fn nodes_strict_eq(left: NodeHandle<'_>, right: NodeHandle<'_>) -> bool { 166 | // Compare name. 167 | if left.name() != right.name() { 168 | return false; 169 | } 170 | // Compare attributes. 171 | { 172 | let left = left.attributes(); 173 | let right = right.attributes(); 174 | if left.len() != right.len() { 175 | return false; 176 | } 177 | if !left.iter().zip(right).all(|(l, r)| l.strict_eq(r)) { 178 | return false; 179 | } 180 | } 181 | // Compare children. 182 | { 183 | let mut left = left.children(); 184 | let mut right = right.children(); 185 | loop { 186 | match (left.next(), right.next()) { 187 | (Some(l), Some(r)) => { 188 | if !nodes_strict_eq(l, r) { 189 | return false; 190 | } 191 | } 192 | (None, None) => break, 193 | _ => return false, 194 | } 195 | } 196 | } 197 | true 198 | } 199 | 200 | /// An iterator of children of a node. 201 | #[derive(Clone)] 202 | pub struct Children<'a> { 203 | /// Tree. 204 | tree: &'a Tree, 205 | /// Raw node children iterator. 206 | iter: indextree::Children<'a, NodeData>, 207 | } 208 | 209 | impl<'a> Iterator for Children<'a> { 210 | type Item = NodeHandle<'a>; 211 | 212 | fn next(&mut self) -> Option { 213 | let child_id = self.iter.next()?; 214 | Some(NodeId::new(child_id).to_handle(self.tree)) 215 | } 216 | } 217 | 218 | impl std::iter::FusedIterator for Children<'_> {} 219 | 220 | impl<'a> fmt::Debug for Children<'a> { 221 | #[inline] 222 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 223 | f.debug_struct("Children").finish() 224 | } 225 | } 226 | 227 | /// An iterator of children of a node, with a specific name. 228 | #[derive(Clone)] 229 | pub struct ChildrenByName<'a> { 230 | /// Name symbol. 231 | name_sym: Option, 232 | /// Children node iterator. 233 | children_iter: Children<'a>, 234 | } 235 | 236 | impl<'a> Iterator for ChildrenByName<'a> { 237 | type Item = NodeHandle<'a>; 238 | 239 | fn next(&mut self) -> Option { 240 | let name_sym = self.name_sym?; 241 | self.children_iter 242 | .find(|child| child.name_sym() == name_sym) 243 | } 244 | } 245 | 246 | impl std::iter::FusedIterator for ChildrenByName<'_> {} 247 | 248 | impl<'a> fmt::Debug for ChildrenByName<'a> { 249 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 250 | f.debug_struct("ChildrenByName") 251 | .field("name_sym", &self.name_sym) 252 | .finish() 253 | } 254 | } 255 | -------------------------------------------------------------------------------- /src/tree/v7400/node/name.rs: -------------------------------------------------------------------------------- 1 | //! Node name. 2 | 3 | use string_interner::symbol::{Symbol, SymbolU32}; 4 | 5 | /// Symbol for interned node name. 6 | // This is an opaque-typedef pattern. 7 | // `string_interner::Sym` has efficient implementation, so use it internally. 8 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 9 | pub(crate) struct NodeNameSym(SymbolU32); 10 | 11 | impl Symbol for NodeNameSym { 12 | /// This may panic if the given value is too large. 13 | /// 14 | /// As of writing this, string-interner 0.7.0 panics if the given value is 15 | /// greater than `u32::max_value() - 1`. 16 | /// See [`string_interner::Sym`] for detail. 17 | #[inline] 18 | fn try_from_usize(v: usize) -> Option { 19 | SymbolU32::try_from_usize(v).map(Self) 20 | } 21 | 22 | #[inline] 23 | fn to_usize(self) -> usize { 24 | self.0.to_usize() 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/writer.rs: -------------------------------------------------------------------------------- 1 | //! FBX writer. 2 | //! 3 | //! Enabled by `writer` feature. 4 | 5 | pub mod v7400; 6 | -------------------------------------------------------------------------------- /src/writer/v7400.rs: -------------------------------------------------------------------------------- 1 | //! Writer for FBX 7.4 or later. 2 | 3 | pub mod binary; 4 | -------------------------------------------------------------------------------- /src/writer/v7400/binary/attributes.rs: -------------------------------------------------------------------------------- 1 | //! Node attributes writer. 2 | 3 | use std::{ 4 | convert::TryFrom, 5 | io::{self, Seek, SeekFrom, Write}, 6 | }; 7 | 8 | use crate::{ 9 | low::v7400::{ArrayAttributeEncoding, ArrayAttributeHeader, AttributeType}, 10 | writer::v7400::binary::{Error, Result, Writer}, 11 | }; 12 | 13 | mod array; 14 | 15 | /// A dummy type for impossible error. 16 | pub(crate) enum Never {} 17 | 18 | impl From for Error { 19 | fn from(_: Never) -> Self { 20 | unreachable!("Should never happen") 21 | } 22 | } 23 | 24 | /// A trait for types which can be represented as single bytes array. 25 | pub(crate) trait IntoBytes: Sized { 26 | /// Calls the given function with the bytes array. 27 | fn call_with_le_bytes(self, f: impl FnOnce(&[u8]) -> R) -> R; 28 | } 29 | 30 | impl IntoBytes for bool { 31 | #[inline] 32 | fn call_with_le_bytes(self, f: impl FnOnce(&[u8]) -> R) -> R { 33 | let v = if self { b'Y' } else { b'T' }; 34 | f(&v.to_le_bytes()) 35 | } 36 | } 37 | 38 | impl IntoBytes for i16 { 39 | #[inline] 40 | fn call_with_le_bytes(self, f: impl FnOnce(&[u8]) -> R) -> R { 41 | f(&self.to_le_bytes()) 42 | } 43 | } 44 | 45 | impl IntoBytes for i32 { 46 | #[inline] 47 | fn call_with_le_bytes(self, f: impl FnOnce(&[u8]) -> R) -> R { 48 | f(&self.to_le_bytes()) 49 | } 50 | } 51 | 52 | impl IntoBytes for i64 { 53 | #[inline] 54 | fn call_with_le_bytes(self, f: impl FnOnce(&[u8]) -> R) -> R { 55 | f(&self.to_le_bytes()) 56 | } 57 | } 58 | 59 | impl IntoBytes for f32 { 60 | #[inline] 61 | fn call_with_le_bytes(self, f: impl FnOnce(&[u8]) -> R) -> R { 62 | f(&self.to_bits().to_le_bytes()) 63 | } 64 | } 65 | 66 | impl IntoBytes for f64 { 67 | #[inline] 68 | fn call_with_le_bytes(self, f: impl FnOnce(&[u8]) -> R) -> R { 69 | f(&self.to_bits().to_le_bytes()) 70 | } 71 | } 72 | 73 | /// Node attributes writer. 74 | /// 75 | /// See [module documentation](index.html) for usage. 76 | pub struct AttributesWriter<'a, W: Write> { 77 | /// Inner writer. 78 | writer: &'a mut Writer, 79 | } 80 | 81 | /// Implement `append_*` methods for single value. 82 | macro_rules! impl_single_attr_append { 83 | ($( 84 | $(#[$meta:meta])* 85 | $method:ident($ty:ty): $variant:ident; 86 | )*) => { 87 | $( 88 | $(#[$meta])* 89 | pub fn $method(&mut self, v: $ty) -> Result<()> { 90 | self.update_node_header()?; 91 | self.write_type_code(AttributeType::$variant)?; 92 | v.call_with_le_bytes(|bytes| self.writer.sink().write_all(bytes)) 93 | .map_err(Into::into) 94 | } 95 | )* 96 | } 97 | } 98 | 99 | /// Implement `append_*` methods for array values. 100 | macro_rules! impl_arr_from_iter { 101 | ($( 102 | $(#[$meta:meta])* 103 | $name:ident: $ty_elem:ty { 104 | from_result_iter: $name_from_result_iter:ident, 105 | tyval: $tyval:ident, 106 | }, 107 | )*) => {$( 108 | $(#[$meta])* 109 | #[inline] 110 | pub fn $name( 111 | &mut self, 112 | encoding: impl Into>, 113 | iter: impl IntoIterator, 114 | ) -> Result<()> { 115 | array::write_array_attr_result_iter( 116 | self, 117 | AttributeType::$tyval, 118 | encoding.into(), 119 | iter.into_iter().map(Ok::<_, Never>), 120 | ) 121 | } 122 | 123 | $(#[$meta])* 124 | #[inline] 125 | pub fn $name_from_result_iter( 126 | &mut self, 127 | encoding: impl Into>, 128 | iter: impl IntoIterator>, 129 | ) -> Result<()> 130 | where 131 | E: Into>, 132 | { 133 | array::write_array_attr_result_iter( 134 | self, 135 | AttributeType::$tyval, 136 | encoding.into(), 137 | iter.into_iter().map(|res| res.map_err(|e| Error::UserDefined(e.into()))), 138 | ) 139 | } 140 | )*} 141 | } 142 | 143 | impl<'a, W: Write + Seek> AttributesWriter<'a, W> { 144 | /// Creates a new `AttributesWriter`. 145 | #[inline] 146 | #[must_use] 147 | pub(crate) fn new(writer: &'a mut Writer) -> Self { 148 | Self { writer } 149 | } 150 | 151 | /// Returns the inner writer. 152 | #[inline] 153 | #[must_use] 154 | pub(crate) fn sink(&mut self) -> &mut W { 155 | self.writer.sink() 156 | } 157 | 158 | /// Writes the given attribute type as type code. 159 | fn write_type_code(&mut self, ty: AttributeType) -> Result<()> { 160 | self.writer 161 | .sink() 162 | .write_all(&ty.type_code().to_le_bytes()) 163 | .map_err(Into::into) 164 | } 165 | 166 | /// Updates the node header. 167 | fn update_node_header(&mut self) -> Result<()> { 168 | let node_header = self 169 | .writer 170 | .current_node_header() 171 | .expect("Should never fail: some nodes must be open if `AttributesWriter` exists"); 172 | node_header.num_attributes = 173 | node_header 174 | .num_attributes 175 | .checked_add(1) 176 | .ok_or(Error::TooManyAttributes( 177 | node_header.num_attributes as usize, 178 | ))?; 179 | 180 | Ok(()) 181 | } 182 | 183 | impl_single_attr_append! { 184 | /// Writes a single boolean attribute. 185 | append_bool(bool): Bool; 186 | /// Writes a single `i16` attribute. 187 | append_i16(i16): I16; 188 | /// Writes a single `i32` attribute. 189 | append_i32(i32): I32; 190 | /// Writes a single `i64` attribute. 191 | append_i64(i64): I64; 192 | /// Writes a single `f32` attribute. 193 | append_f32(f32): F32; 194 | /// Writes a single `f64` attribute. 195 | append_f64(f64): F64; 196 | } 197 | 198 | /// Writes the given array attribute header. 199 | #[inline] 200 | fn write_array_header(&mut self, header: &ArrayAttributeHeader) -> Result<()> { 201 | array::write_array_header(self.writer.sink(), header).map_err(Into::into) 202 | } 203 | 204 | /// Writes some headers for an array attibute, and returns header position. 205 | pub(crate) fn initialize_array( 206 | &mut self, 207 | ty: AttributeType, 208 | encoding: ArrayAttributeEncoding, 209 | ) -> Result { 210 | self.update_node_header()?; 211 | 212 | // Write attribute header. 213 | self.write_type_code(ty)?; 214 | let header_pos = self.writer.sink().stream_position()?; 215 | 216 | // Write array header placeholder. 217 | self.write_array_header(&ArrayAttributeHeader { 218 | elements_count: 0, 219 | encoding, 220 | bytelen: 0, 221 | })?; 222 | 223 | Ok(header_pos) 224 | } 225 | 226 | /// Updates an array attribute header. 227 | /// 228 | /// Note that this should be called at the end of the array attribute. 229 | fn finalize_array(&mut self, header_pos: u64, header: &ArrayAttributeHeader) -> Result<()> { 230 | // Write real array header. 231 | let end_pos = self.writer.sink().stream_position()?; 232 | self.writer.sink().seek(SeekFrom::Start(header_pos))?; 233 | self.write_array_header(header)?; 234 | self.writer.sink().seek(SeekFrom::Start(end_pos))?; 235 | 236 | Ok(()) 237 | } 238 | 239 | impl_arr_from_iter! { 240 | /// Writes a boolean array attribute. 241 | append_arr_bool_from_iter: bool { 242 | from_result_iter: append_arr_bool_from_result_iter, 243 | tyval: ArrBool, 244 | }, 245 | 246 | /// Writes an `i32` array attribute. 247 | append_arr_i32_from_iter: i32 { 248 | from_result_iter: append_arr_i32_from_result_iter, 249 | tyval: ArrI32, 250 | }, 251 | 252 | /// Writes an `i64` array attribute. 253 | append_arr_i64_from_iter: i64 { 254 | from_result_iter: append_arr_i64_from_result_iter, 255 | tyval: ArrI64, 256 | }, 257 | 258 | /// Writes an `f32` array attribute. 259 | append_arr_f32_from_iter: f32 { 260 | from_result_iter: append_arr_f32_from_result_iter, 261 | tyval: ArrF32, 262 | }, 263 | 264 | /// Writes an `f64` array attribute. 265 | append_arr_f64_from_iter: f64 { 266 | from_result_iter: append_arr_f64_from_result_iter, 267 | tyval: ArrF64, 268 | }, 269 | } 270 | 271 | /// Writes some headers for a special attribute, and returns the special 272 | /// header position. 273 | fn initialize_special(&mut self, ty: AttributeType) -> Result { 274 | self.update_node_header()?; 275 | 276 | // Write attribute header. 277 | self.write_type_code(ty)?; 278 | 279 | // Write special attribute header (dummy). 280 | let header_pos = self.writer.sink().stream_position()?; 281 | self.writer.sink().write_all(&0u32.to_le_bytes())?; 282 | 283 | Ok(header_pos) 284 | } 285 | 286 | /// Updates an array attribute header. 287 | /// 288 | /// Note that this should be called at the end of the array attribute. 289 | fn finalize_special(&mut self, header_pos: u64, bytelen: usize) -> Result<()> { 290 | // Calculate header fields. 291 | let bytelen = u32::try_from(bytelen).map_err(|_| Error::AttributeTooLong(bytelen))?; 292 | 293 | // Write real special attribute header. 294 | let end_pos = self.writer.sink().stream_position()?; 295 | self.writer.sink().seek(SeekFrom::Start(header_pos))?; 296 | self.writer.sink().write_all(&bytelen.to_le_bytes())?; 297 | self.writer.sink().seek(SeekFrom::Start(end_pos))?; 298 | 299 | Ok(()) 300 | } 301 | 302 | /// Writes a binary attribute. 303 | pub fn append_binary_direct(&mut self, binary: &[u8]) -> Result<()> { 304 | let header_pos = self.initialize_special(AttributeType::Binary)?; 305 | 306 | self.writer.sink().write_all(binary)?; 307 | 308 | self.finalize_special(header_pos, binary.len())?; 309 | 310 | Ok(()) 311 | } 312 | 313 | /// Writes a string attribute. 314 | pub fn append_string_direct(&mut self, string: &str) -> Result<()> { 315 | let header_pos = self.initialize_special(AttributeType::String)?; 316 | 317 | self.writer.sink().write_all(string.as_ref())?; 318 | 319 | self.finalize_special(header_pos, string.len())?; 320 | 321 | Ok(()) 322 | } 323 | 324 | /// Writes a binary attribute read from the given reader. 325 | pub fn append_binary_from_reader(&mut self, mut reader: impl io::Read) -> Result<()> { 326 | let header_pos = self.initialize_special(AttributeType::Binary)?; 327 | 328 | // Write bytes. 329 | let written_len = io::copy(&mut reader, self.writer.sink())?; 330 | 331 | self.finalize_special(header_pos, written_len as usize)?; 332 | 333 | Ok(()) 334 | } 335 | 336 | /// Writes a binary attribute from the given iterator. 337 | pub fn append_binary_from_iter(&mut self, iter: impl IntoIterator) -> Result<()> { 338 | let header_pos = self.initialize_special(AttributeType::Binary)?; 339 | 340 | let mut len = 0usize; 341 | iter.into_iter().try_for_each(|v| -> Result<_> { 342 | self.writer.sink().write_all(&[v])?; 343 | len = len 344 | .checked_add(1) 345 | .ok_or(Error::AttributeTooLong(usize::MAX))?; 346 | 347 | Ok(()) 348 | })?; 349 | 350 | self.finalize_special(header_pos, len)?; 351 | 352 | Ok(()) 353 | } 354 | 355 | /// Writes a binary attribute from the given iterator. 356 | pub fn append_binary_from_result_iter( 357 | &mut self, 358 | iter: impl IntoIterator>, 359 | ) -> Result<()> 360 | where 361 | E: Into>, 362 | { 363 | let header_pos = self.initialize_special(AttributeType::Binary)?; 364 | 365 | let mut len = 0usize; 366 | iter.into_iter().try_for_each(|v| -> Result<_> { 367 | let v = v.map_err(|e| Error::UserDefined(e.into()))?; 368 | self.writer.sink().write_all(&[v])?; 369 | len = len 370 | .checked_add(1) 371 | .ok_or(Error::AttributeTooLong(usize::MAX))?; 372 | 373 | Ok(()) 374 | })?; 375 | 376 | self.finalize_special(header_pos, len)?; 377 | 378 | Ok(()) 379 | } 380 | 381 | /// Writes a string attribute from the given iterator. 382 | pub fn append_string_from_iter(&mut self, iter: impl IntoIterator) -> Result<()> { 383 | let header_pos = self.initialize_special(AttributeType::String)?; 384 | 385 | let buf = &mut [0u8; 4]; 386 | let mut len = 0usize; 387 | iter.into_iter().try_for_each(|c| -> Result<_> { 388 | let char_len = c.encode_utf8(buf).len(); 389 | self.writer.sink().write_all(buf)?; 390 | len = len 391 | .checked_add(char_len) 392 | .ok_or(Error::AttributeTooLong(usize::MAX))?; 393 | 394 | Ok(()) 395 | })?; 396 | 397 | self.finalize_special(header_pos, len)?; 398 | 399 | Ok(()) 400 | } 401 | 402 | /// Writes a string attribute from the given iterator. 403 | pub fn append_string_from_result_iter( 404 | &mut self, 405 | iter: impl IntoIterator>, 406 | ) -> Result<()> 407 | where 408 | E: Into>, 409 | { 410 | let header_pos = self.initialize_special(AttributeType::String)?; 411 | 412 | let buf = &mut [0u8; 4]; 413 | let mut len = 0usize; 414 | iter.into_iter().try_for_each(|c| -> Result<_> { 415 | let c = c.map_err(|e| Error::UserDefined(e.into()))?; 416 | let char_len = c.encode_utf8(buf).len(); 417 | self.writer.sink().write_all(buf)?; 418 | len = len 419 | .checked_add(char_len) 420 | .ok_or(Error::AttributeTooLong(usize::MAX))?; 421 | 422 | Ok(()) 423 | })?; 424 | 425 | self.finalize_special(header_pos, len)?; 426 | 427 | Ok(()) 428 | } 429 | } 430 | -------------------------------------------------------------------------------- /src/writer/v7400/binary/attributes/array.rs: -------------------------------------------------------------------------------- 1 | //! Array attributes things. 2 | 3 | use std::{ 4 | convert::TryFrom, 5 | io::{self, Seek, Write}, 6 | }; 7 | 8 | use crate::{ 9 | low::v7400::{ArrayAttributeEncoding, ArrayAttributeHeader, AttributeType}, 10 | writer::v7400::binary::{ 11 | attributes::IntoBytes, AttributesWriter, CompressionError, Error, Result, 12 | }, 13 | }; 14 | 15 | /// A trait for types which can be represented as multiple bytes array. 16 | pub(crate) trait IntoBytesMulti: Sized { 17 | /// Calls the given function with the bytes array multiple times. 18 | fn call_with_le_bytes_multi( 19 | self, 20 | f: impl FnMut(&[u8]) -> std::result::Result<(), E>, 21 | ) -> std::result::Result; 22 | } 23 | 24 | impl>> IntoBytesMulti for I { 25 | fn call_with_le_bytes_multi( 26 | self, 27 | mut f: impl FnMut(&[u8]) -> std::result::Result<(), E>, 28 | ) -> std::result::Result { 29 | let mut count = 0usize; 30 | self.into_iter() 31 | .inspect(|_| count = count.checked_add(1).expect("Too many elements")) 32 | .try_for_each(|elem| elem?.call_with_le_bytes(&mut f))?; 33 | 34 | Ok(count) 35 | } 36 | } 37 | 38 | /// Writes array elements into the given writer. 39 | pub(crate) fn write_elements_result_iter( 40 | mut writer: impl Write, 41 | iter: impl IntoIterator>, 42 | ) -> Result 43 | where 44 | T: IntoBytes, 45 | E: Into, 46 | { 47 | let elements_count = iter 48 | .into_iter() 49 | .map(|res| res.map_err(Into::into)) 50 | .call_with_le_bytes_multi(|bytes| writer.write_all(bytes).map_err(Into::into))?; 51 | let elements_count = u32::try_from(elements_count) 52 | .map_err(|_| Error::TooManyArrayAttributeElements(elements_count + 1))?; 53 | 54 | Ok(elements_count) 55 | } 56 | 57 | /// Writes the given array attribute header. 58 | pub(crate) fn write_array_header( 59 | mut writer: impl Write, 60 | header: &ArrayAttributeHeader, 61 | ) -> io::Result<()> { 62 | writer.write_all(&header.elements_count.to_le_bytes())?; 63 | writer.write_all(&header.encoding.to_u32().to_le_bytes())?; 64 | writer.write_all(&header.bytelen.to_le_bytes())?; 65 | 66 | Ok(()) 67 | } 68 | 69 | /// Writes the given array attribute. 70 | pub(crate) fn write_array_attr_result_iter>( 71 | writer: &mut AttributesWriter<'_, W>, 72 | ty: AttributeType, 73 | encoding: Option, 74 | iter: impl IntoIterator>, 75 | ) -> Result<()> { 76 | let encoding = encoding.unwrap_or(ArrayAttributeEncoding::Direct); 77 | 78 | let header_pos = writer.initialize_array(ty, encoding)?; 79 | 80 | // Write elements. 81 | let start_pos = writer.sink().stream_position()?; 82 | let elements_count = match encoding { 83 | ArrayAttributeEncoding::Direct => write_elements_result_iter(writer.sink(), iter)?, 84 | ArrayAttributeEncoding::Zlib => { 85 | let mut sink = libflate::zlib::Encoder::new(writer.sink())?; 86 | let count = write_elements_result_iter(&mut sink, iter)?; 87 | sink.finish() 88 | .into_result() 89 | .map_err(CompressionError::Zlib)?; 90 | count 91 | } 92 | }; 93 | let end_pos = writer.sink().stream_position()?; 94 | let bytelen = end_pos - start_pos; 95 | 96 | // Calculate header fields. 97 | let bytelen = u32::try_from(bytelen).map_err(|_| Error::AttributeTooLong(bytelen as usize))?; 98 | 99 | // Write real array header. 100 | writer.finalize_array( 101 | header_pos, 102 | &ArrayAttributeHeader { 103 | elements_count, 104 | encoding, 105 | bytelen, 106 | }, 107 | )?; 108 | 109 | Ok(()) 110 | } 111 | -------------------------------------------------------------------------------- /src/writer/v7400/binary/error.rs: -------------------------------------------------------------------------------- 1 | //! Binary writer error. 2 | 3 | use std::{error, fmt, io}; 4 | 5 | use crate::low::FbxVersion; 6 | 7 | /// Write result. 8 | pub type Result = std::result::Result; 9 | 10 | /// Write error. 11 | #[derive(Debug)] 12 | pub enum Error { 13 | /// Node attribute is too long. 14 | AttributeTooLong(usize), 15 | /// Compression error. 16 | Compression(CompressionError), 17 | /// File is too large. 18 | FileTooLarge(u64), 19 | /// I/O error. 20 | Io(io::Error), 21 | /// There are no nodes to close. 22 | NoNodesToClose, 23 | /// Node name is too long. 24 | NodeNameTooLong(usize), 25 | /// Too many array attribute elements. 26 | TooManyArrayAttributeElements(usize), 27 | /// Too many attributes. 28 | TooManyAttributes(usize), 29 | /// There remains unclosed nodes. 30 | UnclosedNode(usize), 31 | /// Unsupported FBX version. 32 | UnsupportedFbxVersion(FbxVersion), 33 | /// User-defined error. 34 | UserDefined(Box), 35 | } 36 | 37 | impl error::Error for Error { 38 | fn source(&self) -> Option<&(dyn error::Error + 'static)> { 39 | match self { 40 | Error::Compression(e) => Some(e), 41 | Error::Io(e) => Some(e), 42 | Error::UserDefined(e) => Some(&**e), 43 | _ => None, 44 | } 45 | } 46 | } 47 | 48 | impl fmt::Display for Error { 49 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 50 | match self { 51 | Error::AttributeTooLong(v) => write!(f, "Node attribute is too long: {} bytes", v), 52 | Error::Compression(e) => write!(f, "Compression error: {}", e), 53 | Error::FileTooLarge(v) => write!(f, "File is too large: {} bytes", v), 54 | Error::Io(e) => write!(f, "I/O error: {}", e), 55 | Error::NoNodesToClose => write!(f, "There are no nodes to close"), 56 | Error::NodeNameTooLong(v) => write!(f, "Node name is too long: {} bytes", v), 57 | Error::TooManyArrayAttributeElements(v) => write!( 58 | f, 59 | "Too many array elements for a single node attribute: count={}", 60 | v 61 | ), 62 | Error::TooManyAttributes(v) => write!(f, "Too many attributes: count={}", v), 63 | Error::UnclosedNode(v) => write!(f, "There remains unclosed nodes: depth={}", v), 64 | Error::UnsupportedFbxVersion(v) => write!(f, "Unsupported FBX version: {:?}", v), 65 | Error::UserDefined(e) => write!(f, "User-defined error: {}", e), 66 | } 67 | } 68 | } 69 | 70 | impl From for Error { 71 | #[inline] 72 | fn from(e: io::Error) -> Self { 73 | Error::Io(e) 74 | } 75 | } 76 | 77 | impl From for Error { 78 | #[inline] 79 | fn from(e: CompressionError) -> Self { 80 | Error::Compression(e) 81 | } 82 | } 83 | 84 | /// Compression error. 85 | #[derive(Debug)] 86 | pub enum CompressionError { 87 | /// Zlib error. 88 | Zlib(io::Error), 89 | } 90 | 91 | impl error::Error for CompressionError { 92 | #[inline] 93 | fn source(&self) -> Option<&(dyn error::Error + 'static)> { 94 | match self { 95 | CompressionError::Zlib(e) => Some(e), 96 | } 97 | } 98 | } 99 | 100 | impl fmt::Display for CompressionError { 101 | #[inline] 102 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 103 | match self { 104 | CompressionError::Zlib(e) => write!(f, "Zlib compression error: {}", e), 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/writer/v7400/binary/footer.rs: -------------------------------------------------------------------------------- 1 | //! FBX footer. 2 | 3 | /// FBX footer padding length. 4 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 5 | pub enum FbxFooterPaddingLength { 6 | /// Default (correct) value. 7 | Default, 8 | /// Forced specified value, which can be wrong. 9 | Forced(u8), 10 | } 11 | 12 | impl Default for FbxFooterPaddingLength { 13 | #[inline] 14 | fn default() -> Self { 15 | FbxFooterPaddingLength::Default 16 | } 17 | } 18 | 19 | /// FBX 7.4 footer. 20 | #[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 21 | pub struct FbxFooter<'a> { 22 | /// Unknown (semirandom) 16-bytes data. 23 | /// 24 | /// This field is expected to have prescribed upper 4 bits, i.e. the field 25 | /// is `fx bx ax 0x dx cx dx 6x bx 7x fx 8x 1x fx 2x 7x` if the FBX data is 26 | /// exported from official SDK. 27 | /// 28 | /// Note that third party exporter will use completely random data. 29 | pub unknown1: Option<&'a [u8; 16]>, 30 | /// Padding length. 31 | /// 32 | /// Padding is `padding_len` `0`s. 33 | /// `padding_len >= 0 && padding <= 15` should hold. 34 | /// 35 | /// Note that third party exporter will not use correct padding length. 36 | pub padding_len: FbxFooterPaddingLength, 37 | /// Unknown 4-bytes data. 38 | /// 39 | /// This is expected to be `[0u8; 4]`. 40 | pub unknown2: Option<[u8; 4]>, 41 | /// Unknown 16-bytes data. 42 | /// 43 | /// This is expected to be `[0xf8, 0x5a, 0x8c, 0x6a, 0xde, 0xf5, 0xd9, 0x7e, 44 | /// 0xec, 0xe9, 0x0c, 0xe3, 0x75, 0x8f, 0x29, 0x0b]`. 45 | pub unknown3: Option<&'a [u8; 16]>, 46 | } 47 | 48 | impl<'a> FbxFooter<'a> { 49 | /// Returns the first unknown field or default. 50 | #[inline] 51 | #[must_use] 52 | pub(crate) fn unknown1(&self) -> &'a [u8; 16] { 53 | /// Default value. 54 | const DEFAULT: [u8; 16] = [ 55 | 0xf0, 0xb1, 0xa2, 0x03, 0xd4, 0xc5, 0xd6, 0x67, 0xb8, 0x79, 0xfa, 0x8b, 0x1c, 0xfd, 56 | 0x2e, 0x7f, 57 | ]; 58 | self.unknown1.unwrap_or(&DEFAULT) 59 | } 60 | 61 | /// Returns the second unknown field or default. 62 | #[inline] 63 | #[must_use] 64 | pub(crate) fn unknown2(&self) -> [u8; 4] { 65 | /// Default value. 66 | const DEFAULT: [u8; 4] = [0; 4]; 67 | self.unknown2.unwrap_or(DEFAULT) 68 | } 69 | 70 | /// Returns the third unknown field or default. 71 | #[inline] 72 | #[must_use] 73 | pub(crate) fn unknown3(&self) -> &'a [u8; 16] { 74 | /// Default value. 75 | const DEFAULT: [u8; 16] = [ 76 | 0xf8, 0x5a, 0x8c, 0x6a, 0xde, 0xf5, 0xd9, 0x7e, 0xec, 0xe9, 0x0c, 0xe3, 0x75, 0x8f, 77 | 0x29, 0x0b, 78 | ]; 79 | self.unknown3.unwrap_or(&DEFAULT) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/writer/v7400/binary/macros.rs: -------------------------------------------------------------------------------- 1 | //! Macros. 2 | 3 | /// Drives the given writer to write the given tree as FBX binary, and returns 4 | /// the `fbxcel::writer::v7400::binary::Result<()>`. 5 | /// 6 | /// Enabled by `writer` feature. 7 | /// 8 | /// # Examples 9 | /// 10 | /// ``` 11 | /// # use fbxcel::write_v7400_binary; 12 | /// use fbxcel::{low::FbxVersion, writer::v7400::binary::Writer}; 13 | /// let mut writer = Writer::new(std::io::Cursor::new(Vec::new()), FbxVersion::V7_4)?; 14 | /// 15 | /// write_v7400_binary!( 16 | /// writer=writer, 17 | /// tree={ 18 | /// Node0: { 19 | /// Node0_0: {} 20 | /// Node0_1: {} 21 | /// } 22 | /// Node1: { 23 | /// // You can use trailing comma. 24 | /// Node1_0: {}, 25 | /// Node1_1: {}, 26 | /// } 27 | /// // Use parens to specify attributes by single array. 28 | /// // Note that the expression inside parens should implement 29 | /// // `IntoIterator`. 30 | /// Node2: (vec!["hello".into(), "world".into(), 42i32.into()]) {} 31 | /// // Use brackets to specify attributes one by one. 32 | /// Node3: ["hello", "world", 1.234f32, &b"BINARY"[..]] {} 33 | /// }, 34 | /// )?; 35 | /// let _buf = writer.finalize_and_flush(&Default::default())?; 36 | /// # Ok::<_, Box>(()) 37 | /// ``` 38 | #[cfg_attr(docsrs, doc(cfg(feature = "writer")))] 39 | #[macro_export] 40 | macro_rules! write_v7400_binary { 41 | ( 42 | writer=$writer:expr, 43 | tree={$($tree:tt)*}, 44 | ) => {{ 45 | let mut f = || -> $crate::writer::v7400::binary::Result<()> { 46 | let _writer = &mut $writer; 47 | write_v7400_binary! { @__node, _writer, $($tree)* }; 48 | Ok(()) 49 | }; 50 | f() 51 | }}; 52 | 53 | 54 | (@__node, $writer:ident,) => {}; 55 | (@__node, $writer:ident, , $($tree:tt)*) => { 56 | write_v7400_binary! { @__node, $writer, $($tree)* } 57 | }; 58 | 59 | (@__node, $writer:ident, 60 | $name:ident: { 61 | $($subtree:tt)* 62 | } 63 | $($rest:tt)* 64 | ) => {{ 65 | $writer.new_node(stringify!($name))?; 66 | write_v7400_binary! { @__node, $writer, $($subtree)* } 67 | $writer.close_node()?; 68 | write_v7400_binary! { @__node, $writer, $($rest)* } 69 | }}; 70 | (@__node, $writer:ident, 71 | $name:ident: [$($attr:expr),* $(,)?] { 72 | $($subtree:tt)* 73 | } 74 | $($rest:tt)* 75 | ) => {{ 76 | let mut _attrs = $writer.new_node(stringify!($name))?; 77 | $({ 78 | let attr = $attr; 79 | write_v7400_binary!(@__attr, _attrs, attr.into())?; 80 | })* 81 | write_v7400_binary! { @__node, $writer, $($subtree)* } 82 | $writer.close_node()?; 83 | write_v7400_binary! { @__node, $writer, $($rest)* } 84 | }}; 85 | (@__node, $writer:ident, 86 | $name:ident: ($attrs:expr) { 87 | $($subtree:tt)* 88 | } 89 | $($rest:tt)* 90 | ) => {{ 91 | let mut _attrs = $writer.new_node(stringify!($name))?; 92 | $attrs.into_iter().try_for_each(|attr: $crate::low::v7400::AttributeValue| { 93 | write_v7400_binary!(@__attr, _attrs, attr.into()) 94 | })?; 95 | write_v7400_binary! { @__node, $writer, $($subtree)* } 96 | $writer.close_node()?; 97 | write_v7400_binary! { @__node, $writer, $($rest)* } 98 | }}; 99 | 100 | (@__attr, $attrs:ident, $attr:expr) => {{ 101 | use $crate::low::v7400::AttributeValue::*; 102 | match $attr { 103 | Bool(v) => $attrs.append_bool(v), 104 | I16(v) => $attrs.append_i16(v), 105 | I32(v) => $attrs.append_i32(v), 106 | I64(v) => $attrs.append_i64(v), 107 | F32(v) => $attrs.append_f32(v), 108 | F64(v) => $attrs.append_f64(v), 109 | ArrBool(v) => $attrs.append_arr_bool_from_iter(None, v), 110 | ArrI32(v) => $attrs.append_arr_i32_from_iter(None, v), 111 | ArrI64(v) => $attrs.append_arr_i64_from_iter(None, v), 112 | ArrF32(v) => $attrs.append_arr_f32_from_iter(None, v), 113 | ArrF64(v) => $attrs.append_arr_f64_from_iter(None, v), 114 | Binary(v) => $attrs.append_binary_direct(&v), 115 | String(v) => $attrs.append_string_direct(&v), 116 | } 117 | }}; 118 | } 119 | 120 | #[cfg(test)] 121 | mod tests { 122 | use std::io::Cursor; 123 | 124 | use crate::{ 125 | low::FbxVersion, 126 | writer::v7400::binary::{Result, Writer}, 127 | }; 128 | 129 | #[test] 130 | fn empty_writer() -> Result<()> { 131 | let mut writer = Writer::new(Cursor::new(Vec::new()), FbxVersion::V7_4)?; 132 | write_v7400_binary!(writer = writer, tree = {},)?; 133 | let _buf = writer.finalize_and_flush(&Default::default())?; 134 | 135 | Ok(()) 136 | } 137 | 138 | #[test] 139 | fn empty_node() -> Result<()> { 140 | let mut writer = Writer::new(Cursor::new(Vec::new()), FbxVersion::V7_4)?; 141 | write_v7400_binary!( 142 | writer=writer, 143 | tree={ 144 | Hello: {} 145 | World: {}, 146 | }, 147 | )?; 148 | let _buf = writer.finalize_and_flush(&Default::default())?; 149 | 150 | Ok(()) 151 | } 152 | 153 | #[test] 154 | fn nested_node() -> Result<()> { 155 | let mut writer = Writer::new(Cursor::new(Vec::new()), FbxVersion::V7_4)?; 156 | write_v7400_binary!( 157 | writer=writer, 158 | tree={ 159 | Hello: { 160 | Hello1: {}, 161 | Hello2: {} 162 | } 163 | World: { 164 | World1: { 165 | World1_1: {} 166 | World1_2: {} 167 | } 168 | World2: {}, 169 | }, 170 | }, 171 | )?; 172 | let _buf = writer.finalize_and_flush(&Default::default())?; 173 | 174 | Ok(()) 175 | } 176 | 177 | #[test] 178 | fn nested_node_with_attrs() -> Result<()> { 179 | let mut writer = Writer::new(Cursor::new(Vec::new()), FbxVersion::V7_4)?; 180 | write_v7400_binary!( 181 | writer=writer, 182 | tree={ 183 | Hello: { 184 | Hello1: (vec!["string".into()]) {}, 185 | Hello2: [1.234f32, 42i64] {} 186 | } 187 | World: { 188 | World1: { 189 | World1_1: (vec!["Hello".into(), 42i32.into()]) {} 190 | World1_2: [] {} 191 | } 192 | World2: {}, 193 | }, 194 | }, 195 | )?; 196 | let _buf = writer.finalize_and_flush(&Default::default())?; 197 | 198 | Ok(()) 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /tests/missing-and-extra-node-end-marker.rs: -------------------------------------------------------------------------------- 1 | //! Tests for writer, tree, and parser. 2 | #![cfg(all(feature = "tree", feature = "writer"))] 3 | 4 | use std::{cell::RefCell, io::Cursor, iter, rc::Rc}; 5 | 6 | use fbxcel::{ 7 | low::FbxVersion, 8 | pull_parser::{any::AnyParser, error::Warning}, 9 | }; 10 | 11 | use self::v7400::writer::{ 12 | expect_fbx_end, expect_node_end, expect_node_start, CUSTOM_UNKNOWN1, MAGIC, UNKNOWN3, 13 | }; 14 | 15 | mod v7400; 16 | 17 | /// Parses a node which lacks necessary node end marker. 18 | #[test] 19 | fn missing_node_end_marker() -> Result<(), Box> { 20 | let data = { 21 | let raw_ver = 7400_u32; 22 | let mut vec = Vec::new(); 23 | // Header. 24 | { 25 | // Magic. 26 | vec.extend(MAGIC); 27 | // Version. 28 | vec.extend(raw_ver.to_le_bytes()); 29 | } 30 | // Nodes. 31 | { 32 | // Container node. 33 | { 34 | const CONTAINER: &[u8] = b"Container"; 35 | let container_start = vec.len(); 36 | // End offset. 37 | vec.extend([0; 4]); 38 | // Number of node properties. 39 | vec.extend([0; 4]); 40 | // Length of node properties in bytes. 41 | vec.extend([0; 4]); 42 | // Node name length. 43 | vec.push(CONTAINER.len() as u8); 44 | // Node name. 45 | vec.extend(CONTAINER); 46 | 47 | // Invalid node. 48 | { 49 | const INVALID_NODE: &[u8] = b"InvalidNode"; 50 | let invalid_node_start = vec.len(); 51 | // End offset. 52 | vec.extend([0; 4]); 53 | // Number of node properties. 54 | vec.extend([0; 4]); 55 | // Length of node properties in bytes. 56 | vec.extend([0; 4]); 57 | // Node name length. 58 | vec.push(INVALID_NODE.len() as u8); 59 | // Node name. 60 | vec.extend(INVALID_NODE); 61 | let end_pos = (vec.len() as u32).to_le_bytes(); 62 | vec[invalid_node_start..(invalid_node_start + 4)].copy_from_slice(&end_pos); 63 | } 64 | 65 | // Node end marker. 66 | vec.extend([0; 13]); 67 | let end_pos = (vec.len() as u32).to_le_bytes(); 68 | vec[container_start..(container_start + 4)].copy_from_slice(&end_pos); 69 | } 70 | // End of implicit root. 71 | { 72 | vec.extend(iter::repeat(0).take(4 * 3 + 1)); 73 | } 74 | } 75 | // Footer. 76 | { 77 | // Footer: unknown1. 78 | vec.extend(CUSTOM_UNKNOWN1); 79 | // Footer: padding. 80 | { 81 | let len = vec.len().wrapping_neg() % 16; 82 | assert_eq!((vec.len() + len) % 16, 0); 83 | vec.extend(iter::repeat(0).take(len)); 84 | } 85 | // Footer: unknown2. 86 | vec.extend([0; 4]); 87 | // Footer: FBX version. 88 | vec.extend(raw_ver.to_le_bytes()); 89 | // Footer: 120 zeroes. 90 | vec.extend(iter::repeat(0).take(120)); 91 | // Footer: unknown3. 92 | vec.extend(UNKNOWN3); 93 | } 94 | vec 95 | }; 96 | 97 | assert_eq!(data.len() % 16, 0); 98 | 99 | let mut parser = match AnyParser::from_seekable_reader(Cursor::new(data))? { 100 | AnyParser::V7400(parser) => parser, 101 | _ => panic!("Generated data should be parsable with v7400 parser"), 102 | }; 103 | let warnings = Rc::new(RefCell::new(Vec::new())); 104 | parser.set_warning_handler({ 105 | let warnings = warnings.clone(); 106 | move |warning, _pos| { 107 | warnings.borrow_mut().push(warning); 108 | Ok(()) 109 | } 110 | }); 111 | assert_eq!(parser.fbx_version(), FbxVersion::V7_4); 112 | 113 | { 114 | let attrs = expect_node_start(&mut parser, "Container")?; 115 | assert_eq!(attrs.total_count(), 0); 116 | } 117 | { 118 | let attrs = expect_node_start(&mut parser, "InvalidNode")?; 119 | assert_eq!(attrs.total_count(), 0); 120 | } 121 | expect_node_end(&mut parser)?; 122 | expect_node_end(&mut parser)?; 123 | 124 | let _: Box = expect_fbx_end(&mut parser)??; 125 | 126 | match &warnings.borrow()[..] { 127 | [Warning::MissingNodeEndMarker] => {} 128 | v => panic!("Unexpected warnings: {:?}", v), 129 | } 130 | 131 | Ok(()) 132 | } 133 | 134 | /// Parses a node which has extra node end marker. 135 | #[test] 136 | fn extra_node_end_marker() -> Result<(), Box> { 137 | let data = { 138 | let raw_ver = 7400_u32; 139 | let mut vec = Vec::new(); 140 | // Header. 141 | { 142 | // Magic. 143 | vec.extend(MAGIC); 144 | // Version. 145 | vec.extend(raw_ver.to_le_bytes()); 146 | } 147 | // Nodes. 148 | { 149 | // Container node. 150 | { 151 | const CONTAINER: &[u8] = b"Container"; 152 | let container_start = vec.len(); 153 | // End offset. 154 | vec.extend([0; 4]); 155 | // Number of node properties. 156 | vec.extend([0; 4]); 157 | // Length of node properties in bytes. 158 | vec.extend([0; 4]); 159 | // Node name length. 160 | vec.push(CONTAINER.len() as u8); 161 | // Node name. 162 | vec.extend(CONTAINER); 163 | 164 | // Invalid node. 165 | { 166 | const INVALID_NODE: &[u8] = b"InvalidNode"; 167 | let invalid_node_start = vec.len(); 168 | // End offset. 169 | vec.extend([0; 4]); 170 | // Number of node properties. 171 | vec.extend(1_u32.to_le_bytes()); 172 | // Length of node properties in bytes. 173 | vec.extend(2_u32.to_le_bytes()); 174 | // Node name length. 175 | vec.push(INVALID_NODE.len() as u8); 176 | // Node name. 177 | vec.extend(INVALID_NODE); 178 | // An attribute. 179 | vec.extend([b'C', b'T']); 180 | // Extra node end marker. 181 | vec.extend([0; 13]); 182 | let end_pos = (vec.len() as u32).to_le_bytes(); 183 | vec[invalid_node_start..(invalid_node_start + 4)].copy_from_slice(&end_pos); 184 | } 185 | 186 | // Node end marker. 187 | vec.extend([0; 13]); 188 | let end_pos = (vec.len() as u32).to_le_bytes(); 189 | vec[container_start..(container_start + 4)].copy_from_slice(&end_pos); 190 | } 191 | // End of implicit root. 192 | { 193 | vec.extend(iter::repeat(0).take(4 * 3 + 1)); 194 | } 195 | } 196 | // Footer. 197 | { 198 | // Footer: unknown1. 199 | vec.extend(CUSTOM_UNKNOWN1); 200 | // Footer: padding. 201 | { 202 | let len = vec.len().wrapping_neg() % 16; 203 | assert_eq!((vec.len() + len) % 16, 0); 204 | vec.extend(iter::repeat(0).take(len)); 205 | } 206 | // Footer: unknown2. 207 | vec.extend([0; 4]); 208 | // Footer: FBX version. 209 | vec.extend(raw_ver.to_le_bytes()); 210 | // Footer: 120 zeroes. 211 | vec.extend(iter::repeat(0).take(120)); 212 | // Footer: unknown3. 213 | vec.extend(UNKNOWN3); 214 | } 215 | vec 216 | }; 217 | 218 | assert_eq!(data.len() % 16, 0); 219 | 220 | let mut parser = match AnyParser::from_seekable_reader(Cursor::new(data))? { 221 | AnyParser::V7400(parser) => parser, 222 | _ => panic!("Generated data should be parsable with v7400 parser"), 223 | }; 224 | let warnings = Rc::new(RefCell::new(Vec::new())); 225 | parser.set_warning_handler({ 226 | let warnings = warnings.clone(); 227 | move |warning, _pos| { 228 | warnings.borrow_mut().push(warning); 229 | Ok(()) 230 | } 231 | }); 232 | assert_eq!(parser.fbx_version(), FbxVersion::V7_4); 233 | 234 | { 235 | let attrs = expect_node_start(&mut parser, "Container")?; 236 | assert_eq!(attrs.total_count(), 0); 237 | } 238 | { 239 | let attrs = expect_node_start(&mut parser, "InvalidNode")?; 240 | assert_eq!(attrs.total_count(), 1); 241 | } 242 | expect_node_end(&mut parser)?; 243 | expect_node_end(&mut parser)?; 244 | 245 | let _: Box = expect_fbx_end(&mut parser)??; 246 | 247 | match &warnings.borrow()[..] { 248 | [Warning::ExtraNodeEndMarker] => {} 249 | v => panic!("Unexpected warnings: {:?}", v), 250 | } 251 | 252 | Ok(()) 253 | } 254 | -------------------------------------------------------------------------------- /tests/v7400/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod writer; 2 | -------------------------------------------------------------------------------- /tests/v7400/writer.rs: -------------------------------------------------------------------------------- 1 | #![cfg(feature = "writer")] 2 | 3 | use std::io; 4 | 5 | use fbxcel::pull_parser::{ 6 | v7400::{Attributes, Event, Parser}, 7 | Error as ParseError, 8 | }; 9 | 10 | pub const MAGIC: &[u8] = b"Kaydara FBX Binary \x00\x1a\x00"; 11 | 12 | pub const CUSTOM_UNKNOWN1: [u8; 16] = [ 13 | 0xff, 0xbe, 0xad, 0x0c, 0xdb, 0xca, 0xd9, 0x68, 0xb7, 0x76, 0xf5, 0x84, 0x13, 0xf2, 0x21, 0x70, 14 | ]; 15 | 16 | pub const UNKNOWN3: [u8; 16] = [ 17 | 0xf8, 0x5a, 0x8c, 0x6a, 0xde, 0xf5, 0xd9, 0x7e, 0xec, 0xe9, 0x0c, 0xe3, 0x75, 0x8f, 0x29, 0x0b, 18 | ]; 19 | 20 | pub fn expect_node_start<'a, R: io::Read + std::fmt::Debug>( 21 | parser: &'a mut Parser, 22 | name: &str, 23 | ) -> Result, Box> { 24 | match parser.next_event()? { 25 | Event::StartNode(node) => { 26 | assert_eq!(node.name(), name); 27 | Ok(node.attributes()) 28 | } 29 | ev => panic!("Unexpected event: {:?}", ev), 30 | } 31 | } 32 | 33 | pub fn expect_node_end( 34 | parser: &mut Parser, 35 | ) -> Result<(), Box> { 36 | match parser.next_event()? { 37 | Event::EndNode => Ok(()), 38 | ev => panic!("Unexpected event: {:?}", ev), 39 | } 40 | } 41 | 42 | pub fn expect_fbx_end( 43 | parser: &mut Parser, 44 | ) -> Result, ParseError>, Box> { 45 | match parser.next_event()? { 46 | Event::EndFbx(footer_res) => Ok(footer_res), 47 | ev => panic!("Unexpected event: {:?}", ev), 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /tests/write-and-parse-v7400-binary.rs: -------------------------------------------------------------------------------- 1 | //! Writer and parser test. 2 | #![cfg(feature = "writer")] 3 | 4 | use std::{cell::RefCell, io::Cursor, iter, rc::Rc}; 5 | 6 | use fbxcel::{ 7 | low::{v7400::AttributeValue, FbxVersion}, 8 | pull_parser::{any::AnyParser, v7400::attribute::loaders::DirectLoader}, 9 | write_v7400_binary, 10 | writer::v7400::binary::{FbxFooter, Writer}, 11 | }; 12 | 13 | use self::v7400::writer::{ 14 | expect_fbx_end, expect_node_end, expect_node_start, CUSTOM_UNKNOWN1, MAGIC, UNKNOWN3, 15 | }; 16 | 17 | mod v7400; 18 | 19 | /// Compares expected binary and binary generated with events. 20 | #[test] 21 | fn empty_write_v7400() -> Result<(), Box> { 22 | let mut dest = Vec::new(); 23 | let cursor = Cursor::new(&mut dest); 24 | let writer = Writer::new(cursor, FbxVersion::V7_4)?; 25 | let footer = FbxFooter { 26 | unknown1: Some(&CUSTOM_UNKNOWN1), 27 | padding_len: Default::default(), 28 | unknown2: None, 29 | unknown3: None, 30 | }; 31 | writer.finalize_and_flush(&footer)?; 32 | 33 | let expected = { 34 | let raw_ver = 7400u32; 35 | let mut vec = Vec::new(); 36 | // Header. 37 | { 38 | // Magic. 39 | vec.extend(MAGIC); 40 | // Version. 41 | vec.extend(raw_ver.to_le_bytes()); 42 | } 43 | // No nodes. 44 | { 45 | // End of implicit root. 46 | { 47 | vec.extend(iter::repeat(0).take(4 * 3 + 1)); 48 | } 49 | } 50 | // Footer. 51 | { 52 | // Footer: unknown1. 53 | vec.extend(CUSTOM_UNKNOWN1); 54 | // Footer: padding. 55 | { 56 | let len = vec.len().wrapping_neg() % 16; 57 | assert_eq!((vec.len() + len) % 16, 0); 58 | vec.extend(iter::repeat(0).take(len)); 59 | } 60 | // Footer: unknown2. 61 | vec.extend([0; 4]); 62 | // Footer: FBX version. 63 | vec.extend(raw_ver.to_le_bytes()); 64 | // Footer: 120 zeroes. 65 | vec.extend(iter::repeat(0).take(120)); 66 | // Footer: unknown3. 67 | vec.extend(UNKNOWN3); 68 | } 69 | vec 70 | }; 71 | 72 | assert_eq!(dest.len() % 16, 0); 73 | assert_eq!(dest, expected); 74 | 75 | let mut parser = match AnyParser::from_seekable_reader(Cursor::new(dest))? { 76 | AnyParser::V7400(parser) => parser, 77 | _ => panic!("Generated data should be parsable with v7400 parser"), 78 | }; 79 | let warnings = Rc::new(RefCell::new(Vec::new())); 80 | parser.set_warning_handler({ 81 | let warnings = warnings.clone(); 82 | move |warning, _pos| { 83 | warnings.borrow_mut().push(warning); 84 | Ok(()) 85 | } 86 | }); 87 | assert_eq!(parser.fbx_version(), FbxVersion::V7_4); 88 | 89 | { 90 | let footer_res = expect_fbx_end(&mut parser)?; 91 | let footer = footer_res?; 92 | assert_eq!(footer.unknown1, CUSTOM_UNKNOWN1); 93 | assert_eq!(footer.unknown2, [0u8; 4]); 94 | assert_eq!(footer.unknown3, UNKNOWN3); 95 | } 96 | 97 | assert_eq!(warnings.borrow().len(), 0); 98 | 99 | Ok(()) 100 | } 101 | 102 | /// Compares expected binary and binary generated with events. 103 | #[test] 104 | fn tree_write_v7500() -> Result<(), Box> { 105 | let mut dest = Vec::new(); 106 | let cursor = Cursor::new(&mut dest); 107 | let mut writer = Writer::new(cursor, FbxVersion::V7_5)?; 108 | write_v7400_binary!( 109 | writer=writer, 110 | tree={ 111 | Node0: { 112 | Node0_0: {}, 113 | Node0_1: {}, 114 | }, 115 | Node1: [true] { 116 | Node1_0: (vec![42i32.into(), 1.234f64.into()]) {} 117 | Node1_1: [&[1u8, 2, 4, 8, 16][..], "Hello, world"] {} 118 | }, 119 | }, 120 | )?; 121 | writer.finalize_and_flush(&Default::default())?; 122 | 123 | let mut parser = match AnyParser::from_seekable_reader(Cursor::new(dest))? { 124 | AnyParser::V7400(parser) => parser, 125 | _ => panic!("Generated data should be parsable with v7400 parser"), 126 | }; 127 | let warnings = Rc::new(RefCell::new(Vec::new())); 128 | parser.set_warning_handler({ 129 | let warnings = warnings.clone(); 130 | move |warning, _pos| { 131 | warnings.borrow_mut().push(warning); 132 | Ok(()) 133 | } 134 | }); 135 | assert_eq!(parser.fbx_version(), FbxVersion::V7_5); 136 | 137 | { 138 | let attrs = expect_node_start(&mut parser, "Node0")?; 139 | assert_eq!(attrs.total_count(), 0); 140 | } 141 | { 142 | let attrs = expect_node_start(&mut parser, "Node0_0")?; 143 | assert_eq!(attrs.total_count(), 0); 144 | } 145 | expect_node_end(&mut parser)?; 146 | { 147 | let attrs = expect_node_start(&mut parser, "Node0_1")?; 148 | assert_eq!(attrs.total_count(), 0); 149 | } 150 | expect_node_end(&mut parser)?; 151 | expect_node_end(&mut parser)?; 152 | { 153 | let attrs = expect_node_start(&mut parser, "Node1")?; 154 | assert_eq!(attrs.total_count(), 1); 155 | } 156 | { 157 | let attrs = expect_node_start(&mut parser, "Node1_0")?; 158 | assert_eq!(attrs.total_count(), 2); 159 | } 160 | expect_node_end(&mut parser)?; 161 | { 162 | let attrs = expect_node_start(&mut parser, "Node1_1")?; 163 | assert_eq!(attrs.total_count(), 2); 164 | } 165 | expect_node_end(&mut parser)?; 166 | expect_node_end(&mut parser)?; 167 | 168 | { 169 | let footer_res = expect_fbx_end(&mut parser)?; 170 | assert!(footer_res.is_ok()); 171 | } 172 | 173 | assert_eq!(warnings.borrow().len(), 0); 174 | 175 | Ok(()) 176 | } 177 | 178 | #[test] 179 | fn macro_v7400_idempotence() -> Result<(), Box> { 180 | let version = FbxVersion::V7_4; 181 | let mut writer = Writer::new(std::io::Cursor::new(Vec::new()), version)?; 182 | 183 | write_v7400_binary!( 184 | writer=writer, 185 | tree={ 186 | Node0: { 187 | Node0_0: {}, 188 | Node0_1: {}, 189 | }, 190 | Node1: [true] { 191 | Node1_0: (vec![42i32.into(), 1.234f64.into()]) {} 192 | Node1_1: [&[1u8, 2, 4, 8, 16][..], "Hello, world"] {} 193 | }, 194 | }, 195 | )?; 196 | let bin = writer.finalize_and_flush(&Default::default())?.into_inner(); 197 | 198 | let mut parser = match AnyParser::from_seekable_reader(Cursor::new(bin))? { 199 | AnyParser::V7400(parser) => parser, 200 | _ => panic!("Generated data should be parsable with v7400 parser"), 201 | }; 202 | let warnings = Rc::new(RefCell::new(Vec::new())); 203 | parser.set_warning_handler({ 204 | let warnings = warnings.clone(); 205 | move |warning, _pos| { 206 | warnings.borrow_mut().push(warning); 207 | Ok(()) 208 | } 209 | }); 210 | assert_eq!(parser.fbx_version(), version); 211 | 212 | { 213 | let attrs = expect_node_start(&mut parser, "Node0")?; 214 | assert_eq!(attrs.total_count(), 0); 215 | } 216 | { 217 | let attrs = expect_node_start(&mut parser, "Node0_0")?; 218 | assert_eq!(attrs.total_count(), 0); 219 | } 220 | expect_node_end(&mut parser)?; 221 | { 222 | let attrs = expect_node_start(&mut parser, "Node0_1")?; 223 | assert_eq!(attrs.total_count(), 0); 224 | } 225 | expect_node_end(&mut parser)?; 226 | expect_node_end(&mut parser)?; 227 | { 228 | let mut attrs = expect_node_start(&mut parser, "Node1")?; 229 | assert_eq!( 230 | attrs.load_next(DirectLoader)?, 231 | Some(AttributeValue::from(true)) 232 | ); 233 | assert_eq!(attrs.total_count(), 1); 234 | } 235 | { 236 | let mut attrs = expect_node_start(&mut parser, "Node1_0")?; 237 | assert_eq!( 238 | attrs.load_next(DirectLoader)?, 239 | Some(AttributeValue::from(42i32)) 240 | ); 241 | assert!(attrs 242 | .load_next(DirectLoader)? 243 | .map_or(false, |attr| attr.strict_eq(&1.234f64.into()))); 244 | assert_eq!(attrs.total_count(), 2); 245 | } 246 | expect_node_end(&mut parser)?; 247 | { 248 | let mut attrs = expect_node_start(&mut parser, "Node1_1")?; 249 | assert_eq!( 250 | attrs.load_next(DirectLoader)?, 251 | Some(AttributeValue::from(vec![1u8, 2, 4, 8, 16])) 252 | ); 253 | assert_eq!( 254 | attrs.load_next(DirectLoader)?, 255 | Some(AttributeValue::from("Hello, world")) 256 | ); 257 | assert_eq!(attrs.total_count(), 2); 258 | } 259 | expect_node_end(&mut parser)?; 260 | expect_node_end(&mut parser)?; 261 | 262 | { 263 | let footer_res = expect_fbx_end(&mut parser)?; 264 | assert!(footer_res.is_ok()); 265 | } 266 | 267 | assert_eq!(warnings.borrow().len(), 0); 268 | 269 | Ok(()) 270 | } 271 | -------------------------------------------------------------------------------- /tests/write-tree-and-parse-v7400-binary.rs: -------------------------------------------------------------------------------- 1 | //! Tests for writer, tree, and parser. 2 | #![cfg(all(feature = "tree", feature = "writer"))] 3 | 4 | use std::{cell::RefCell, io::Cursor, rc::Rc}; 5 | 6 | use fbxcel::{ 7 | low::FbxVersion, pull_parser::any::AnyParser, tree::v7400::Loader as TreeLoader, tree_v7400, 8 | writer::v7400::binary::Writer, 9 | }; 10 | 11 | /// Construct tree, export it to binary, parse it and construct tree, and 12 | /// compare them. 13 | #[test] 14 | fn tree_write_parse_idempotence_v7500() -> Result<(), Box> { 15 | // Construct tree. 16 | let tree1 = tree_v7400! { 17 | Node0: { 18 | Node0_0: {}, 19 | Node0_1: {}, 20 | }, 21 | Node1: [true] { 22 | Node1_0: (vec![42i32.into(), 1.234f64.into()]) {} 23 | Node1_1: [&[1u8, 2, 4, 8, 16][..], "Hello, world"] {} 24 | }, 25 | }; 26 | 27 | let mut writer = Writer::new(Cursor::new(Vec::new()), FbxVersion::V7_5)?; 28 | writer.write_tree(&tree1)?; 29 | let bin = writer.finalize_and_flush(&Default::default())?.into_inner(); 30 | 31 | let mut parser = match AnyParser::from_seekable_reader(Cursor::new(bin))? { 32 | AnyParser::V7400(parser) => parser, 33 | _ => panic!("Generated data should be parsable with v7400 parser"), 34 | }; 35 | let warnings = Rc::new(RefCell::new(Vec::new())); 36 | parser.set_warning_handler({ 37 | let warnings = warnings.clone(); 38 | move |warning, _pos| { 39 | warnings.borrow_mut().push(warning); 40 | Ok(()) 41 | } 42 | }); 43 | assert_eq!(parser.fbx_version(), FbxVersion::V7_5); 44 | 45 | let (tree2, footer_res) = TreeLoader::new().load(&mut parser)?; 46 | 47 | assert_eq!(warnings.borrow().len(), 0); 48 | assert!(footer_res.is_ok()); 49 | 50 | assert!(tree1.strict_eq(&tree2)); 51 | 52 | Ok(()) 53 | } 54 | --------------------------------------------------------------------------------