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}