1#![deny(unsafe_op_in_unsafe_fn)]
50#![warn(missing_docs)]
51
52#[cfg(any(
53 all(feature = "prebuilt", feature = "build-assimp"),
54 all(feature = "prebuilt", feature = "system"),
55 all(feature = "build-assimp", feature = "system"),
56))]
57compile_error!(
58 "Build mode features are mutually exclusive. Use exactly one of: `prebuilt` (default), `build-assimp`, or `system`.\n\
59 Hint: for system Assimp use `--no-default-features --features system`."
60);
61
62#[cfg(feature = "raw-sys")]
63pub use asset_importer_sys as sys;
64
65#[cfg(not(feature = "raw-sys"))]
66pub(crate) use asset_importer_sys as sys;
67
68pub use crate::{
70 error::{Error, Result},
71 importer::{ImportBuilder, Importer, PropertyStore, PropertyValue, import_properties},
72 scene::{MemoryInfo, Scene},
73 types::*,
74};
75
76pub mod raw;
78
79#[cfg(feature = "export")]
80pub use crate::exporter::{ExportBlob, ExportBuilder, ExportFormatDesc, export_properties};
81
82#[allow(deprecated)]
84pub use crate::logging::{LogLevel, LogStream, Logger};
85
86pub use crate::metadata::{Metadata, MetadataEntry, MetadataType};
88
89pub use crate::material::{
91 Material, MaterialPropertyInfo, MaterialPropertyIterator, MaterialPropertyRef,
92 MaterialStringRef, PropertyTypeInfo, TextureInfo, TextureInfoRef, TextureType, material_keys,
93};
94
95pub use crate::texture::{Texel, Texture, TextureData, TextureIterator};
97
98pub use crate::aabb::AABB;
100
101pub use crate::bone::{Bone, BoneIterator, VertexWeight};
103
104pub use crate::animation::Animation;
106
107pub use crate::importer_desc::{
109 ImporterDesc, ImporterDescIterator, ImporterFlags, get_all_importer_descs,
110 get_all_importer_descs_iter, get_importer_desc, get_importer_desc_cstr,
111};
112
113mod bridge_properties;
115pub mod error;
116pub(crate) mod ffi;
117pub mod importer;
118pub mod importer_desc;
119pub mod scene;
120pub mod types;
121
122pub mod animation;
124pub mod camera;
125pub mod light;
126pub mod material;
127pub mod mesh;
128pub mod node;
129
130pub mod aabb;
132pub mod bone;
133pub mod texture;
134
135#[cfg(feature = "export")]
137pub mod exporter;
138pub mod io;
139pub mod logging;
140pub mod metadata;
141pub mod progress;
142
143pub mod math;
145pub mod postprocess;
146pub mod utils;
147
148mod ptr;
149
150pub mod version {
152 pub const CRATE_VERSION: &str = env!("CARGO_PKG_VERSION");
154
155 pub fn assimp_version() -> String {
157 format!(
158 "{}.{}.{}",
159 assimp_version_major(),
160 assimp_version_minor(),
161 assimp_version_patch()
162 )
163 }
164
165 pub fn assimp_version_major() -> u32 {
167 unsafe { crate::sys::aiGetVersionMajor() }
168 }
169
170 pub fn assimp_version_minor() -> u32 {
172 unsafe { crate::sys::aiGetVersionMinor() }
173 }
174
175 pub fn assimp_version_patch() -> u32 {
177 unsafe { crate::sys::aiGetVersionPatch() }
178 }
179
180 pub fn assimp_version_revision() -> u32 {
182 unsafe { crate::sys::aiGetVersionRevision() }
183 }
184
185 pub fn assimp_version_string() -> String {
187 assimp_version()
188 }
189
190 pub fn assimp_compile_flags() -> u32 {
192 unsafe { crate::sys::aiGetCompileFlags() }
193 }
194
195 pub fn assimp_branch_name() -> String {
197 unsafe { crate::error::c_str_to_string_or_empty(crate::sys::aiGetBranchName()) }
198 }
199
200 pub fn assimp_legal_string() -> String {
202 unsafe { crate::error::c_str_to_string_or_empty(crate::sys::aiGetLegalString()) }
203 }
204}
205
206pub fn is_extension_supported(extension: &str) -> crate::Result<bool> {
208 let c_extension = std::ffi::CString::new(extension).map_err(|_| {
209 crate::Error::invalid_parameter("file extension contains NUL byte".to_string())
210 })?;
211 Ok(unsafe { crate::sys::aiIsExtensionSupported(c_extension.as_ptr()) != 0 })
212}
213
214const FALLBACK_IMPORT_EXTENSIONS: [&str; 15] = [
215 ".obj", ".fbx", ".dae", ".gltf", ".glb", ".3ds", ".blend", ".x", ".ply", ".stl", ".md2",
216 ".md3", ".md5", ".ase", ".ifc",
217];
218
219#[derive(Debug, Clone)]
224pub struct ImportExtensions {
225 raw: Option<String>,
226}
227
228#[derive(Debug)]
229enum ImportExtensionsIterInner<'a> {
230 Assimp(std::str::Split<'a, char>),
231 Fallback(std::slice::Iter<'a, &'static str>),
232}
233
234#[derive(Debug)]
236pub struct ImportExtensionsIter<'a> {
237 inner: ImportExtensionsIterInner<'a>,
238}
239
240impl<'a> Iterator for ImportExtensionsIter<'a> {
241 type Item = &'a str;
242
243 fn next(&mut self) -> Option<Self::Item> {
244 loop {
245 match &mut self.inner {
246 ImportExtensionsIterInner::Assimp(split) => {
247 let ext = split.next()?;
248 let trimmed = ext.trim();
249 if trimmed.starts_with("*.") && trimmed.len() > 1 {
250 return Some(&trimmed[1..]);
251 }
252 }
253 ImportExtensionsIterInner::Fallback(iter) => {
254 let s: &'static str = iter.next()?;
255 return Some(s);
256 }
257 }
258 }
259 }
260}
261
262impl ImportExtensions {
263 pub fn raw_assimp_list(&self) -> Option<&str> {
265 self.raw.as_deref()
266 }
267
268 pub fn iter(&self) -> ImportExtensionsIter<'_> {
270 if let Some(s) = self.raw.as_deref() {
271 ImportExtensionsIter {
272 inner: ImportExtensionsIterInner::Assimp(s.split(';')),
273 }
274 } else {
275 ImportExtensionsIter {
276 inner: ImportExtensionsIterInner::Fallback(FALLBACK_IMPORT_EXTENSIONS.iter()),
277 }
278 }
279 }
280
281 pub fn to_vec(&self) -> Vec<String> {
283 self.iter().map(str::to_string).collect()
284 }
285}
286
287pub fn get_import_extensions_list() -> ImportExtensions {
289 let mut ai_string = crate::sys::aiString {
290 length: 0,
291 data: [0; 1024],
292 };
293
294 unsafe {
295 crate::sys::aiGetExtensionList(&mut ai_string);
296 }
297
298 if ai_string.length > 0 {
299 ImportExtensions {
300 raw: Some(crate::types::ai_string_to_string(&ai_string)),
301 }
302 } else {
303 ImportExtensions { raw: None }
304 }
305}
306
307pub fn get_import_extensions() -> Vec<String> {
309 get_import_extensions_list().to_vec()
310}
311
312#[cfg(feature = "export")]
314pub fn get_export_formats() -> Vec<crate::exporter::ExportFormatDesc> {
315 get_export_formats_iter().collect()
316}
317
318#[cfg(feature = "export")]
322pub fn get_export_formats_iter() -> ExportFormatDescIterator {
323 ExportFormatDescIterator {
324 index: 0,
325 count: unsafe { sys::aiGetExportFormatCount() },
326 }
327}
328
329#[cfg(feature = "export")]
331pub struct ExportFormatDescIterator {
332 index: usize,
333 count: usize,
334}
335
336#[cfg(feature = "export")]
337impl Iterator for ExportFormatDescIterator {
338 type Item = crate::exporter::ExportFormatDesc;
339
340 fn next(&mut self) -> Option<Self::Item> {
341 while self.index < self.count {
342 let i = self.index;
343 self.index += 1;
344 unsafe {
345 let desc_ptr = sys::aiGetExportFormatDescription(i);
346 if desc_ptr.is_null() {
347 continue;
348 }
349 let Some(desc_ref) = crate::ffi::ref_from_ptr(self, desc_ptr) else {
350 continue;
351 };
352 let desc = crate::exporter::ExportFormatDesc::from_raw(desc_ref);
353 sys::aiReleaseExportFormatDescription(desc_ptr);
354 return Some(desc);
355 }
356 }
357 None
358 }
359
360 fn size_hint(&self) -> (usize, Option<usize>) {
361 let remaining = self.count.saturating_sub(self.index);
362 (0, Some(remaining))
363 }
364}
365
366pub fn enable_verbose_logging(enable: bool) {
368 unsafe {
369 crate::sys::aiEnableVerboseLogging(if enable { 1 } else { 0 });
370 }
371}
372
373#[cfg(test)]
374mod tests {
375 use super::*;
376
377 #[test]
378 fn test_version_info() {
379 let version = version::assimp_version();
380 assert!(!version.is_empty());
381
382 let major = version::assimp_version_major();
383 let minor = version::assimp_version_minor();
384 let patch = version::assimp_version_patch();
385 let revision = version::assimp_version_revision();
386
387 assert!(major >= 5);
389 println!(
390 "Assimp version: {}.{}.{} (revision {})",
391 major, minor, patch, revision
392 );
393 }
394
395 #[test]
396 fn test_extension_support() {
397 assert!(is_extension_supported("obj").unwrap());
399 assert!(is_extension_supported("fbx").unwrap());
400 assert!(is_extension_supported("dae").unwrap());
401 assert!(is_extension_supported("gltf").unwrap());
402
403 assert!(!is_extension_supported("xyz").unwrap());
405 }
406
407 #[test]
408 fn test_get_extensions() {
409 let extensions = get_import_extensions();
410 assert!(!extensions.is_empty());
411 assert!(extensions.contains(&".obj".to_string()));
412 println!("Supported extensions: {:?}", extensions);
413 }
414
415 #[test]
416 fn test_send_sync_traits() {
417 fn assert_send_sync<T: Send + Sync>() {}
421
422 assert_send_sync::<Scene>();
424
425 println!("✅ Core types implement Send + Sync!");
427 }
428}