blend_rs/blend/reader/mod.rs
1mod check;
2
3use std::{fmt, mem};
4use std::error::Error;
5use std::marker::PhantomData;
6use std::ops::Range;
7
8use thiserror::Error;
9
10use blend_inspect_rs::{BlendFile, BlendSource, Data, Endianness, FileBlock, parse, Version};
11
12use crate::blend::{GeneratedBlendStruct, PointerLike, PointerTarget};
13use crate::blend::reader::check::{check_blend, check_same_type};
14use crate::blend::traverse::{DoubleLinked, DoubleLinkedIter};
15
16
17/// Main struct for reading `.blend` files.
18///
19/// This struct is created by the [`read`] function.
20///
21/// # Examples
22///
23/// ```rust
24/// use blend_rs::blend::{read, Reader, ReadError};
25///
26/// let blend_data = std::fs::read("examples/example-3.2.blend")
27/// .expect("file 'examples/example-3.2.blend' should exist and be readable");
28///
29/// let reader: Result<Reader, ReadError> = read(&blend_data);
30/// ```
31pub struct Reader<'a> {
32 data: Data<'a>,
33 blend: BlendFile,
34}
35
36impl <'a> Reader<'a> {
37
38 /// Returns an iterator over all structs of the specified type.
39 ///
40 /// # Errors
41 /// * Fails with a [`VersionMismatchError`], if the [`GeneratedBlendStruct`] used to read
42 /// is generated for a different version than the Blender data actually has.
43 /// * Fails with a [`PointerSizeMismatchError`], if the size of addresses (32bit, 64bit)
44 /// differs from the address size found in the Blender data.
45 /// * Fails with a [`EndiannessMismatchError`], if the [`Endianness`] (little, big) differs from
46 /// the [`Endianness`] found in the Blender data.
47 ///
48 /// # Examples
49 ///
50 /// ```rust
51 /// use blend_rs::blend::{read, StructIter};
52 /// use blend_rs::blender3_2::Mesh;
53 ///
54 /// let blend_data = std::fs::read("examples/example-3.2.blend")
55 /// .expect("file 'examples/example-3.2.blend' to be readable");
56 ///
57 /// let reader = read(&blend_data)
58 /// .expect("Blender file should be parsable");
59 ///
60 /// let meshes: StructIter<Mesh> = reader.iter::<Mesh>()
61 /// .expect("Blender file should contains Meshes");
62 /// ```
63 /// [`VersionMismatchError`]: ReadError::VersionMismatchError
64 /// [`PointerSizeMismatchError`]: ReadError::PointerSizeMismatchError
65 /// [`EndiannessMismatchError`]: ReadError::EndiannessMismatchError
66 ///
67 pub fn iter<S>(&self) -> Result<StructIter<S>, ReadError>
68 where S: 'a + GeneratedBlendStruct {
69 check_blend::<S>(&self.blend)?;
70 let views: Vec<FileBlockView<'a, S>> = self.blend.blocks.iter()
71 .filter_map(|block| {
72 if block.sdna == S::STRUCT_INDEX {
73 Some(FileBlockView::new(self.data, &block))
74 } else {
75 None
76 }
77 }).collect();
78 Ok(StructIter::new(views))
79 }
80
81 /// Dereferences the specified [`PointerLike`] and returns an iterator over the structs.
82 ///
83 /// # Errors
84 /// * Fails with a [`VersionMismatchError`], if the [`GeneratedBlendStruct`] used to read
85 /// is generated for a different version than the Blender data actually has.
86 /// * Fails with a [`PointerSizeMismatchError`], if the size of addresses (32bit, 64bit)
87 /// differs from the address size found in the Blender data.
88 /// * Fails with a [`EndiannessMismatchError`], if the [`Endianness`] (little, big) differs from
89 /// the [`Endianness`] found in the Blender data.
90 /// * Fails with a [`InvalidPointerAddressError`], if the address of the specified
91 /// [`PointerLike`] is invalid.
92 /// * Fails with a [`NullPointerError`], if the address of the specified [`PointerLike`] is zero.
93 /// * Fails with a [`InvalidPointerTypeError`], if the generic target-type of the [`PointerLike`]
94 /// differs from the type actually found at the address in the Blender data.
95 ///
96 /// # Examples
97 ///
98 /// ```rust
99 /// use blend_rs::blend::{read, PointerLike, NameLike, StructIter};
100 /// use blend_rs::blender3_2::{Mesh, MLoop, MPoly};
101 ///
102 /// let blend_data = std::fs::read("examples/example-3.2.blend")
103 /// .expect("file 'examples/example-3.2.blend' should exist and be readable");
104 ///
105 /// let reader = read(&blend_data)
106 /// .expect("Blender file should be parsable");
107 ///
108 /// let mesh: &Mesh = reader.iter::<Mesh>()
109 /// .expect("Blender file should contains Meshes")
110 /// .find(|mesh| mesh.id.name.to_name_str_unchecked() == "Cube")
111 /// .expect("Blender file should contain a Mesh with name 'Cube'");
112 ///
113 /// let polygons: StructIter<MPoly> = reader.deref(&mesh.mpoly)
114 /// .expect("an iterator over all polygons of the Mesh");
115 /// ```
116 /// [`InvalidPointerAddressError`]: ReadError::InvalidPointerAddressError
117 /// [`NullPointerError`]: ReadError::NullPointerError
118 /// [`VersionMismatchError`]: ReadError::VersionMismatchError
119 /// [`PointerSizeMismatchError`]: ReadError::PointerSizeMismatchError
120 /// [`EndiannessMismatchError`]: ReadError::EndiannessMismatchError
121 /// [`InvalidPointerTypeError`]: ReadError::InvalidPointerTypeError
122 ///
123 pub fn deref<P, S>(&self, pointer: &P) -> Result<StructIter<S>, ReadError>
124 where P: PointerLike<S>,
125 S: 'a + GeneratedBlendStruct + PointerTarget<S> {
126 let block = self.look_up(pointer)?;
127 check_blend::<S>(&self.blend)?;
128 check_same_type(&self.blend, S::STRUCT_TYPE_INDEX, block)?;
129 Ok(StructIter::new(vec![FileBlockView::new(self.data, block)]))
130 }
131
132 /// Dereferences the specified [`PointerLike`] and returns the struct.
133 ///
134 /// # Errors
135 /// * Fails with a [`VersionMismatchError`], if the [`GeneratedBlendStruct`] used to read
136 /// is generated for a different version than the Blender data actually has.
137 /// * Fails with a [`PointerSizeMismatchError`], if the size of addresses (32bit, 64bit)
138 /// differs from the address size found in the Blender data.
139 /// * Fails with a [`EndiannessMismatchError`], if the [`Endianness`] (little, big) differs from
140 /// the [`Endianness`] found in the Blender data.
141 /// * Fails with a [`InvalidPointerAddressError`], if the address of the specified
142 /// [`PointerLike`] is invalid.
143 /// * Fails with a [`NullPointerError`], if the address of the specified [`PointerLike`] is zero.
144 /// * Fails with a [`InvalidPointerTypeError`], if the generic target-type of the [`PointerLike`]
145 /// differs from the type actually found at the address in the Blender data.
146 ///
147 /// # Examples
148 ///
149 /// ```rust
150 /// use blend_rs::blend::{read, PointerLike, NameLike};
151 /// use blend_rs::blender3_2::{Object, Mesh};
152 ///
153 /// let blend_data = std::fs::read("examples/example-3.2.blend")
154 /// .expect("file 'examples/example-3.2.blend' should exist and be readable");
155 ///
156 /// let reader = read(&blend_data)
157 /// .expect("Blender file should be parsable");
158 ///
159 /// let cube: &Object = reader.iter::<Object>()
160 /// .expect("Blender file should contains Objects")
161 /// .find(|object| object.id.name.to_name_str_unchecked() == "Cube")
162 /// .expect("Blender file should contain an Object with name 'Cube'");
163 ///
164 /// let mesh: &Mesh = reader.deref_single(&cube.data.as_instance_of::<Mesh>())
165 /// .expect("object 'Cube' should have a Mesh");
166 /// ```
167 /// [`InvalidPointerAddressError`]: ReadError::InvalidPointerAddressError
168 /// [`NullPointerError`]: ReadError::NullPointerError
169 /// [`VersionMismatchError`]: ReadError::VersionMismatchError
170 /// [`PointerSizeMismatchError`]: ReadError::PointerSizeMismatchError
171 /// [`EndiannessMismatchError`]: ReadError::EndiannessMismatchError
172 /// [`InvalidPointerTypeError`]: ReadError::InvalidPointerTypeError
173 ///
174 pub fn deref_single<P, S>(&self, pointer: &P) -> Result<&'a S, ReadError>
175 where P: PointerLike<S>,
176 S: 'a + GeneratedBlendStruct + PointerTarget<S> {
177 let block = self.look_up(pointer)?;
178 check_blend::<S>(&self.blend)?;
179 check_same_type(&self.blend, S::STRUCT_TYPE_INDEX, block)?;
180 let file_block_view: FileBlockView<S> = FileBlockView::new(self.data, block);
181 match file_block_view.len() {
182 1 => Ok(file_block_view.view(0)),
183 0 => Err(ReadError::NoSuchElementError),
184 _ => Err(ReadError::MoreThanOneElementError { count: file_block_view.len() })
185 }
186 }
187
188 /// Dereferences the specified [`PointerLike`] and returns a slice of the raw data.
189 ///
190 /// # Errors
191 /// * Fails with a [`VersionMismatchError`], if the [`GeneratedBlendStruct`] used to read
192 /// is generated for a different version than the Blender data actually has.
193 /// * Fails with a [`PointerSizeMismatchError`], if the size of addresses (32bit, 64bit)
194 /// differs from the address size found in the Blender data.
195 /// * Fails with a [`EndiannessMismatchError`], if the [`Endianness`] (little, big) differs from
196 /// the [`Endianness`] found in the Blender data.
197 /// * Fails with a [`InvalidPointerAddressError`], if the address of the specified
198 /// [`PointerLike`] is invalid.
199 /// * Fails with a [`NullPointerError`], if the address of the specified [`PointerLike`] is zero.
200 ///
201 /// # Examples
202 ///
203 /// ```rust
204 /// use blend_rs::blend::{read, PointerLike, NameLike};
205 /// use blend_rs::blender3_2::{PackedFile};
206 ///
207 /// let blend_data = std::fs::read("examples/example-3.2.blend")
208 /// .expect("file 'examples/example-3.2.blend' should exist and be readable");
209 ///
210 /// let reader = read(&blend_data)
211 /// .expect("Blender data should be parsable");
212 ///
213 /// let packed_file: &PackedFile = reader.iter::<PackedFile>()
214 /// .expect("an iterator over all PackedFiles")
215 /// .first()
216 /// .expect("at least one PackedFile");
217 ///
218 /// let data = reader.deref_raw(&packed_file.data)
219 /// .expect("raw data of the PackedFile");
220 /// ```
221 /// [`InvalidPointerAddressError`]: ReadError::InvalidPointerAddressError
222 /// [`NullPointerError`]: ReadError::NullPointerError
223 /// [`VersionMismatchError`]: ReadError::VersionMismatchError
224 /// [`PointerSizeMismatchError`]: ReadError::PointerSizeMismatchError
225 /// [`EndiannessMismatchError`]: ReadError::EndiannessMismatchError
226 ///
227 pub fn deref_raw<P, S>(&self, pointer: &P) -> Result<Data<'a>, ReadError>
228 where P: PointerLike<S>,
229 S: 'a + GeneratedBlendStruct + PointerTarget<S> {
230 // check_blend::<T>(&self.blend)?; // TODO: Enable check when Pointer/Void etc. implement GeneratedBlendStruct
231 let block = self.look_up(pointer)?;
232 Ok(&self.data[block.data_location()..block.data_location() + block.length])
233 }
234
235 /// Dereferences the specified [`PointerLike`] and returns a sub-slice of the raw data.
236 ///
237 /// # Errors
238 /// * Fails with a [`VersionMismatchError`], if the [`GeneratedBlendStruct`] used to read
239 /// is generated for a different version than the Blender data actually has.
240 /// * Fails with a [`PointerSizeMismatchError`], if the size of addresses (32bit, 64bit)
241 /// differs from the address size found in the Blender data.
242 /// * Fails with a [`EndiannessMismatchError`], if the [`Endianness`] (little, big) differs from
243 /// the [`Endianness`] found in the Blender data.
244 /// * Fails with a [`InvalidPointerAddressError`], if the address of the specified
245 /// [`PointerLike`] is invalid.
246 /// * Fails with a [`NullPointerError`], if the address of the specified [`PointerLike`] is zero.
247 ///
248 /// # Examples
249 ///
250 /// ```rust
251 /// use blend_rs::blend::{read, PointerLike, NameLike};
252 /// use blend_rs::blender3_2::{PackedFile};
253 ///
254 /// let blend_data = std::fs::read("examples/example-3.2.blend")
255 /// .expect("file 'examples/example-3.2.blend' should exist and be readable");
256 ///
257 /// let reader = read(&blend_data)
258 /// .expect("Blender file should be parsable");
259 ///
260 /// let packed_file: &PackedFile = reader.iter::<PackedFile>()
261 /// .expect("Blender file should contains PackedFiles")
262 /// .first()
263 /// .expect("Blender file should contain at least one PackedFiles");
264 ///
265 /// let magic_number = reader.deref_raw_range(&packed_file.data, 0..4 as usize)
266 /// .expect("a range of raw data of the PackedFile");
267 ///
268 /// ```
269 /// [`InvalidPointerAddressError`]: ReadError::InvalidPointerAddressError
270 /// [`NullPointerError`]: ReadError::NullPointerError
271 /// [`VersionMismatchError`]: ReadError::VersionMismatchError
272 /// [`PointerSizeMismatchError`]: ReadError::PointerSizeMismatchError
273 /// [`EndiannessMismatchError`]: ReadError::EndiannessMismatchError
274 ///
275 pub fn deref_raw_range<P, S>(&self, pointer: &P, range: Range<usize>) -> Result<Data<'a>, ReadError>
276 where P: PointerLike<S>,
277 S: 'a + GeneratedBlendStruct + PointerTarget<S> {
278 self.deref_raw(pointer).map(|data| {
279 &data[range.start..range.end]
280 })
281 }
282
283 pub fn traverse_double_linked<PD, D, PT>(&self, pointer: &PD) -> Result<DoubleLinkedIter<D, PT>, ReadError>
284 where PD: PointerLike<D>,
285 D: 'a + DoubleLinked<PT> + GeneratedBlendStruct + PointerTarget<D>,
286 PT: PointerLike<D> {
287
288 self.deref_single(pointer).map(|first| {
289 DoubleLinkedIter::new(self, first)
290 })
291 }
292
293 fn look_up<A, B>(&self, pointer: &A) -> Result<&FileBlock, ReadError>
294 where A: PointerLike<B>,
295 B: 'a + GeneratedBlendStruct + PointerTarget<B> {
296 let address = pointer.address();
297 let lookup = address
298 .map(|address| self.blend.look_up(address))
299 .flatten();
300 match lookup {
301 None => {
302 let address = address.map(|value| value.get()).unwrap_or(0usize);
303 if address != 0 {
304 Err(ReadError::InvalidPointerAddressError { address })
305 }
306 else {
307 Err(ReadError::NullPointerError)
308 }
309 },
310 Some(block) => {
311 Ok(block)
312 }
313 }
314 }
315}
316
317/// Iterator over [`GeneratedBlendStructs`].
318///
319/// This struct is created by the [`iter`] and [`deref`] function of the [`Reader`].
320///
321/// [`GeneratedBlendStructs`]: GeneratedBlendStruct
322/// [`iter`]: Reader::iter
323/// [`deref`]: Reader::deref
324///
325pub struct StructIter<'a, A>
326where A: GeneratedBlendStruct {
327 views: Vec<FileBlockView<'a, A>>,
328 view_index: usize,
329 struct_index: usize,
330 length: usize,
331}
332
333impl <'a, A> StructIter<'a, A>
334where A: GeneratedBlendStruct {
335
336 fn new(views: Vec<FileBlockView<'a, A>>) -> StructIter<'a, A>{
337 let length = views.iter().map(|view| view.len()).sum();
338 StructIter {
339 views,
340 view_index: 0,
341 struct_index: 0,
342 length,
343 }
344 }
345
346 pub fn find<P>(&self, predicate: P) -> Option<&'a A>
347 where P: Fn(&'a A) -> bool {
348 let mut view_index = 0;
349 let mut struct_index = 0;
350 while view_index < self.views.len() {
351 if struct_index < self.views[view_index].len() {
352 let viewed_struct = self.views[view_index].view(struct_index);
353 if predicate(viewed_struct) {
354 return Some(viewed_struct)
355 }
356 else {
357 struct_index += 1;
358 }
359 }
360 else {
361 struct_index = 0;
362 view_index += 1;
363 }
364 }
365 None
366 }
367
368 pub fn first(&self) -> Option<&'a A> {
369 if self.views.len() > 0 && self.views[0].len() > 0 {
370 Some(self.views[0].view(0))
371 }
372 else {
373 None
374 }
375 }
376}
377
378impl <'a, A> Iterator for StructIter<'a, A>
379where A: 'a + GeneratedBlendStruct {
380
381 type Item = &'a A;
382
383 fn next(&mut self) -> Option<Self::Item> {
384 if self.view_index < self.views.len() {
385 if self.struct_index < self.views[self.view_index].len() {
386 let result = self.views[self.view_index].view(self.struct_index);
387 self.struct_index += 1;
388 Some(result)
389 }
390 else {
391 self.view_index += 1;
392 self.struct_index = 0;
393
394 if self.view_index < self.views.len() && self.views[self.view_index].len() > 0 {
395 let result = self.views[self.view_index].view(self.struct_index);
396 self.struct_index += 1;
397 Some(result)
398 }
399 else {
400 None
401 }
402 }
403 }
404 else {
405 None
406 }
407 }
408
409 fn size_hint(&self) -> (usize, Option<usize>) {
410 (self.length, Some(self.length))
411 }
412}
413
414impl <'a, A> ExactSizeIterator for StructIter<'a, A>
415where A: 'a + GeneratedBlendStruct {}
416
417impl <'a, A> fmt::Debug for StructIter<'a, A>
418where A: 'a + GeneratedBlendStruct {
419 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
420 formatter.debug_struct("StructIter")
421 .field("views", &self.views)
422 .field("view_index", &self.view_index)
423 .field("struct_index", &self.struct_index)
424 .finish()
425 }
426}
427
428struct FileBlockView<'a, A> {
429 data: Data<'a>,
430 count: usize,
431 size: usize,
432 phantom: PhantomData<&'a A>,
433}
434
435impl <'a, A> fmt::Debug for FileBlockView<'a, A>
436where A: 'a + GeneratedBlendStruct {
437 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
438 formatter.debug_struct("FileBlockView")
439 .field("data", &self.data)
440 .field("count", &self.count)
441 .field("size", &self.size)
442 .finish()
443 }
444}
445
446impl <'a, A> FileBlockView<'a, A>
447where A: 'a + GeneratedBlendStruct {
448
449 fn new(data: Data<'a>, block: &FileBlock) -> FileBlockView<'a, A> {
450 let start_offset = block.data_location();
451 let end_offset = block.data_location() + (mem::size_of::<A>() * block.count);
452 Self {
453 data: &data[start_offset..end_offset],
454 count: block.count,
455 size: mem::size_of::<A>(),
456 phantom: PhantomData::default(),
457 }
458 }
459
460 fn view(&self, index: usize) -> &'a A {
461 let offset = self.size * index;
462 let (before, body, after) = unsafe {
463 self.data[offset..(offset + self.size)].align_to::<A>()
464 };
465 if before.len() == 0 && body.len() == 1 && after.len() == 0 {
466 &body[0]
467 }
468 else {
469 panic!("Failed to align struct '{}' (prefix={}, suffix={}, alignments={})! ", A::STRUCT_NAME, before.len(), after.len(), body.len())
470 }
471 }
472
473 fn len(&self) -> usize {
474 self.count
475 }
476}
477
478#[derive(Error, Debug)]
479pub enum ReadError {
480
481 #[error("Failed to parse blender data.")]
482 ParseError {
483 #[source]
484 cause: Box<dyn Error>
485 },
486
487 #[error("Expected blend version {expected} but actual version is {actual}!")]
488 VersionMismatchError {
489 expected: Version,
490 actual: Version
491 },
492
493 #[error("Expected pointer size {expected} but actual pointer size is {actual}!")]
494 PointerSizeMismatchError {
495 expected: usize,
496 actual: usize
497 },
498
499 #[error("Expected endianness {expected} but actual endianness is {actual}!")]
500 EndiannessMismatchError {
501 expected: Endianness,
502 actual: Endianness
503 },
504
505 #[error("Pointer address is null!")]
506 NullPointerError,
507
508 #[error("Invalid pointer address '{address}'!")]
509 InvalidPointerAddressError { address: usize },
510
511 #[error("Invalid pointer type!")]
512 InvalidPointerTypeError { expected: String, actual: String },
513
514 #[error("Contains no elements!")]
515 NoSuchElementError,
516
517 #[error("Contains more than one matching element!")]
518 MoreThanOneElementError { count: usize },
519}
520
521/// Creates a [`Reader`] from a [`BlendSource`].
522///
523/// # Errors
524/// * This function may fail with a [`ParseError`].
525///
526/// # Examples
527///
528/// ```rust
529/// use blend_rs::blend::{read, Reader};
530///
531/// let blend_data = std::fs::read("examples/example-3.2.blend")
532/// .expect("file 'examples/example-3.2.blend' should exist and be readable");
533///
534/// let reader = read(&blend_data)
535/// .expect("Blender file should be parsable");
536/// ```
537/// [`ParseError`]: [`ReadError::ParseError`]
538///
539pub fn read<'a, A>(source: A) -> Result<Reader<'a>, ReadError>
540where A: BlendSource<'a> {
541
542 let data = source.data();
543 parse(source.data())
544 .map(|blend| Reader { data, blend } )
545 .map_err(|cause| { ReadError::ParseError { cause: Box::new(cause) } })
546}
547
548#[cfg(test)]
549mod test {
550 use hamcrest2::{assert_that, equal_to, err, HamcrestMatcher, is};
551
552 use crate::blend::{PointerLike, NameLike, read};
553 use crate::blender3_2::Object;
554
555 #[test]
556 fn test_that_iter_should_fail_on_version_mismatch() {
557
558 let blend_data = std::fs::read("examples/example-3.2.blend").unwrap();
559 let reader = read(&blend_data).unwrap();
560
561 assert_that!(reader.iter::<crate::blender2_79::Object>(), is(err()))
562 }
563
564 #[test]
565 fn test_that_iter_should_fail_on_pointer_size_mismatch() {
566
567 let blend_data = std::fs::read("gen/blender2_80.blend").unwrap();
568 let reader = read(&blend_data).unwrap();
569
570 assert_that!(reader.iter::<crate::blender2_80x86::Object>(), is(err()))
571 }
572
573 #[test]
574 fn test_that_deref_should_fail_on_version_mismatch() {
575
576 let blend_data = std::fs::read("examples/example-3.2.blend").unwrap();
577 let reader = read(&blend_data).unwrap();
578
579 let cube: &Object = reader.iter::<Object>().unwrap()
580 .find(|object| object.id.name.to_name_str_unchecked() == "Cube")
581 .unwrap();
582
583 assert_that!(reader.deref(&cube.data.as_instance_of::<crate::blender2_79::Mesh>()), is(err()))
584 }
585
586 #[test]
587 fn test_that_deref_single_should_fail_on_version_mismatch() {
588
589 let blend_data = std::fs::read("examples/example-3.2.blend").unwrap();
590 let reader = read(&blend_data).unwrap();
591
592 let cube: &Object = reader.iter::<Object>().unwrap()
593 .find(|object| object.id.name.to_name_str_unchecked() == "Cube")
594 .unwrap();
595
596 let mesh = reader.deref_single(&cube.data.as_instance_of::<crate::blender2_79::Mesh>());
597 assert_that!(mesh.is_err(), is(true))
598 }
599
600 #[test]
601 fn test_that_find_returns_the_first_struct_matching_the_specified_predicate() {
602 let blend_data = std::fs::read("examples/example-3.2.blend").unwrap();
603 let reader = read(&blend_data).unwrap();
604
605 let iter = reader.iter::<Object>().unwrap();
606 let result = iter.find(|object| object.id.name.to_name_str_unchecked() == "Cube");
607
608 assert_that!(result.is_some(), is(true))
609 }
610
611 #[test]
612 fn test_that_find_returns_none_if_no_struct_matches_the_specified_predicate() {
613 let blend_data = std::fs::read("examples/example-3.2.blend").unwrap();
614 let reader = read(&blend_data).unwrap();
615
616 let iter = reader.iter::<Object>().unwrap();
617 let result = iter.find(|object| object.id.name.to_name_str_unchecked() == "Fubar");
618
619 assert_that!(result.is_none(), is(true))
620 }
621
622 #[test]
623 fn test_that_first_returns_the_first_struct() {
624 let blend_data = std::fs::read("examples/example-3.2.blend").unwrap();
625 let reader = read(&blend_data).unwrap();
626
627 let result = reader.iter::<Object>().unwrap().first();
628
629 assert_that!(result.is_some(), is(true))
630 }
631
632 #[test]
633 fn test_that_len_returns_the_number_of_struct() {
634 let blend_data = std::fs::read("examples/example-3.2.blend").unwrap();
635 let reader = read(&blend_data).unwrap();
636
637 let result = reader.iter::<Object>().unwrap();
638
639 assert_that!(result.len(), is(equal_to(3)))
640 }
641}