Skip to main content

lib3mf_core/model/
resources.rs

1use crate::error::{Lib3mfError, Result};
2use crate::model::{
3    BaseMaterialsGroup, ColorGroup, CompositeMaterials, Displacement2D, KeyStore, MultiProperties,
4    Object, SliceStack, Texture2D, Texture2DGroup, VolumetricStack,
5};
6use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8
9/// Unique identifier for a resource within the model.
10///
11/// A type-safe wrapper around `u32` that prevents accidentally mixing resource IDs
12/// with raw integers. All resources in a 3MF model share a global ID namespace,
13/// meaning an ID can only be used once across all resource types (objects, materials,
14/// textures, etc.).
15///
16/// # Examples
17///
18/// ```
19/// use lib3mf_core::model::ResourceId;
20///
21/// let id = ResourceId(42);
22/// assert_eq!(id.0, 42);
23/// ```
24#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
25pub struct ResourceId(pub u32);
26
27/// Central registry for all resources in a 3MF model.
28///
29/// The `ResourceCollection` manages all reusable resources including objects,
30/// materials, textures, and extension-specific resources. It enforces the global
31/// ID namespace requirement: each [`ResourceId`] can only be used once across
32/// all resource types.
33///
34/// Resources are stored in separate `HashMap<ResourceId, T>` collections internally,
35/// allowing efficient lookup by ID.
36///
37/// # Examples
38///
39/// ```
40/// use lib3mf_core::model::{ResourceCollection, Object, ResourceId, Geometry, Mesh, ObjectType};
41///
42/// let mut resources = ResourceCollection::new();
43///
44/// let obj = Object {
45///     id: ResourceId(1),
46///     object_type: ObjectType::Model,
47///     name: None,
48///     part_number: None,
49///     uuid: None,
50///     pid: None,
51///     pindex: None,
52///     thumbnail: None,
53///     geometry: Geometry::Mesh(Mesh::default()),
54/// };
55///
56/// resources.add_object(obj).expect("Failed to add object");
57/// assert!(resources.exists(ResourceId(1)));
58/// ```
59#[derive(Debug, Clone, Default, Serialize, Deserialize)]
60pub struct ResourceCollection {
61    objects: HashMap<ResourceId, Object>,
62    base_materials: HashMap<ResourceId, BaseMaterialsGroup>,
63    color_groups: HashMap<ResourceId, ColorGroup>,
64    slice_stacks: HashMap<ResourceId, SliceStack>,
65    volumetric_stacks: HashMap<ResourceId, VolumetricStack>,
66    texture_2d: HashMap<ResourceId, Texture2D>,
67    texture_2d_groups: HashMap<ResourceId, Texture2DGroup>,
68    composite_materials: HashMap<ResourceId, CompositeMaterials>,
69    multi_properties: HashMap<ResourceId, MultiProperties>,
70    displacement_2d: HashMap<ResourceId, Displacement2D>,
71    /// Optional Secure Content key store for this model (at most one per model).
72    pub key_store: Option<KeyStore>, // Usually one KeyStore per model/part
73}
74
75impl ResourceCollection {
76    /// Creates a new empty resource collection.
77    pub fn new() -> Self {
78        Self::default()
79    }
80
81    /// Checks if a resource with the given ID exists in any resource type.
82    ///
83    /// Returns `true` if the ID is used by any resource (object, material, texture, etc.).
84    pub fn exists(&self, id: ResourceId) -> bool {
85        self.objects.contains_key(&id)
86            || self.base_materials.contains_key(&id)
87            || self.color_groups.contains_key(&id)
88            || self.slice_stacks.contains_key(&id)
89            || self.volumetric_stacks.contains_key(&id)
90            || self.texture_2d.contains_key(&id)
91            || self.texture_2d_groups.contains_key(&id)
92            || self.composite_materials.contains_key(&id)
93            || self.multi_properties.contains_key(&id)
94            || self.displacement_2d.contains_key(&id)
95    }
96
97    /// Adds an object to the collection.
98    ///
99    /// # Errors
100    ///
101    /// Returns `Lib3mfError::Validation` if a resource with the same ID already exists.
102    pub fn add_object(&mut self, object: Object) -> Result<()> {
103        if self.exists(object.id) {
104            return Err(Lib3mfError::Validation(format!(
105                "Duplicate resource ID: {}",
106                object.id.0
107            )));
108        }
109        self.objects.insert(object.id, object);
110        Ok(())
111    }
112
113    /// Adds a base materials group to the collection.
114    ///
115    /// # Errors
116    ///
117    /// Returns `Lib3mfError::Validation` if a resource with the same ID already exists.
118    pub fn add_base_materials(&mut self, group: BaseMaterialsGroup) -> Result<()> {
119        if self.exists(group.id) {
120            return Err(Lib3mfError::Validation(format!(
121                "Duplicate resource ID: {}",
122                group.id.0
123            )));
124        }
125        self.base_materials.insert(group.id, group);
126        Ok(())
127    }
128
129    /// Adds a color group to the collection.
130    ///
131    /// # Errors
132    ///
133    /// Returns `Lib3mfError::Validation` if a resource with the same ID already exists.
134    pub fn add_color_group(&mut self, group: ColorGroup) -> Result<()> {
135        if self.exists(group.id) {
136            return Err(Lib3mfError::Validation(format!(
137                "Duplicate resource ID: {}",
138                group.id.0
139            )));
140        }
141        self.color_groups.insert(group.id, group);
142        Ok(())
143    }
144
145    /// Adds a slice stack resource to the collection.
146    ///
147    /// # Errors
148    ///
149    /// Returns `Lib3mfError::Validation` if a resource with the same ID already exists.
150    pub fn add_slice_stack(&mut self, stack: SliceStack) -> Result<()> {
151        if self.exists(stack.id) {
152            return Err(Lib3mfError::Validation(format!(
153                "Duplicate resource ID: {}",
154                stack.id.0
155            )));
156        }
157        self.slice_stacks.insert(stack.id, stack);
158        Ok(())
159    }
160
161    /// Adds a volumetric stack resource to the collection.
162    ///
163    /// # Errors
164    ///
165    /// Returns `Lib3mfError::Validation` if a resource with the same ID already exists.
166    pub fn add_volumetric_stack(&mut self, stack: VolumetricStack) -> Result<()> {
167        if self.exists(stack.id) {
168            return Err(Lib3mfError::Validation(format!(
169                "Duplicate resource ID: {}",
170                stack.id.0
171            )));
172        }
173        self.volumetric_stacks.insert(stack.id, stack);
174        Ok(())
175    }
176
177    /// Sets the Secure Content key store for this model (replaces any existing key store).
178    pub fn set_key_store(&mut self, store: KeyStore) {
179        self.key_store = Some(store);
180    }
181
182    /// Retrieves an object by its ID.
183    ///
184    /// Returns `None` if no object with the given ID exists.
185    pub fn get_object(&self, id: ResourceId) -> Option<&Object> {
186        self.objects.get(&id)
187    }
188
189    /// Retrieves a base materials group by its ID.
190    ///
191    /// Returns `None` if no base materials group with the given ID exists.
192    pub fn get_base_materials(&self, id: ResourceId) -> Option<&BaseMaterialsGroup> {
193        self.base_materials.get(&id)
194    }
195
196    /// Retrieves a color group by its ID.
197    ///
198    /// Returns `None` if no color group with the given ID exists.
199    pub fn get_color_group(&self, id: ResourceId) -> Option<&ColorGroup> {
200        self.color_groups.get(&id)
201    }
202
203    /// Retrieves a slice stack by its ID.
204    ///
205    /// Returns `None` if no slice stack with the given ID exists.
206    pub fn get_slice_stack(&self, id: ResourceId) -> Option<&SliceStack> {
207        self.slice_stacks.get(&id)
208    }
209
210    /// Retrieves a volumetric stack by its ID.
211    ///
212    /// Returns `None` if no volumetric stack with the given ID exists.
213    pub fn get_volumetric_stack(&self, id: ResourceId) -> Option<&VolumetricStack> {
214        self.volumetric_stacks.get(&id)
215    }
216
217    /// Adds a 2D texture resource to the collection.
218    ///
219    /// # Errors
220    ///
221    /// Returns `Lib3mfError::Validation` if a resource with the same ID already exists.
222    pub fn add_texture_2d(&mut self, texture: Texture2D) -> Result<()> {
223        if self.exists(texture.id) {
224            return Err(Lib3mfError::Validation(format!(
225                "Duplicate resource ID: {}",
226                texture.id.0
227            )));
228        }
229        self.texture_2d.insert(texture.id, texture);
230        Ok(())
231    }
232
233    /// Adds a 2D texture coordinate group to the collection.
234    ///
235    /// # Errors
236    ///
237    /// Returns `Lib3mfError::Validation` if a resource with the same ID already exists.
238    pub fn add_texture_2d_group(&mut self, group: Texture2DGroup) -> Result<()> {
239        if self.exists(group.id) {
240            return Err(Lib3mfError::Validation(format!(
241                "Duplicate resource ID: {}",
242                group.id.0
243            )));
244        }
245        self.texture_2d_groups.insert(group.id, group);
246        Ok(())
247    }
248
249    /// Retrieves a 2D texture coordinate group by its ID.
250    ///
251    /// Returns `None` if no texture 2D group with the given ID exists.
252    pub fn get_texture_2d_group(&self, id: ResourceId) -> Option<&Texture2DGroup> {
253        self.texture_2d_groups.get(&id)
254    }
255
256    /// Adds a composite materials group to the collection.
257    ///
258    /// # Errors
259    ///
260    /// Returns `Lib3mfError::Validation` if a resource with the same ID already exists.
261    pub fn add_composite_materials(&mut self, group: CompositeMaterials) -> Result<()> {
262        if self.exists(group.id) {
263            return Err(Lib3mfError::Validation(format!(
264                "Duplicate resource ID: {}",
265                group.id.0
266            )));
267        }
268        self.composite_materials.insert(group.id, group);
269        Ok(())
270    }
271
272    /// Retrieves a composite materials group by its ID.
273    ///
274    /// Returns `None` if no composite materials group with the given ID exists.
275    pub fn get_composite_materials(&self, id: ResourceId) -> Option<&CompositeMaterials> {
276        self.composite_materials.get(&id)
277    }
278
279    /// Adds a multi-properties group to the collection.
280    ///
281    /// # Errors
282    ///
283    /// Returns `Lib3mfError::Validation` if a resource with the same ID already exists.
284    pub fn add_multi_properties(&mut self, group: MultiProperties) -> Result<()> {
285        if self.exists(group.id) {
286            return Err(Lib3mfError::Validation(format!(
287                "Duplicate resource ID: {}",
288                group.id.0
289            )));
290        }
291        self.multi_properties.insert(group.id, group);
292        Ok(())
293    }
294
295    /// Retrieves a multi-properties group by its ID.
296    ///
297    /// Returns `None` if no multi-properties group with the given ID exists.
298    pub fn get_multi_properties(&self, id: ResourceId) -> Option<&MultiProperties> {
299        self.multi_properties.get(&id)
300    }
301
302    /// Returns the number of base material groups in the collection.
303    pub fn base_material_groups_count(&self) -> usize {
304        self.base_materials.len()
305    }
306
307    /// Returns the number of color groups in the collection.
308    pub fn color_groups_count(&self) -> usize {
309        self.color_groups.len()
310    }
311
312    /// Returns the number of volumetric stacks in the collection.
313    pub fn volumetric_stacks_count(&self) -> usize {
314        self.volumetric_stacks.len()
315    }
316
317    /// Returns the number of 2D texture coordinate groups in the collection.
318    pub fn texture_2d_groups_count(&self) -> usize {
319        self.texture_2d_groups.len()
320    }
321
322    /// Returns the number of composite materials groups in the collection.
323    pub fn composite_materials_count(&self) -> usize {
324        self.composite_materials.len()
325    }
326
327    /// Returns the number of multi-properties groups in the collection.
328    pub fn multi_properties_count(&self) -> usize {
329        self.multi_properties.len()
330    }
331
332    /// Returns an iterator over all objects in the collection.
333    pub fn iter_objects(&self) -> impl Iterator<Item = &Object> {
334        self.objects.values()
335    }
336
337    /// Returns a mutable iterator over all objects in the collection.
338    pub fn iter_objects_mut(&mut self) -> impl Iterator<Item = &mut Object> {
339        self.objects.values_mut()
340    }
341
342    /// Returns an iterator over all base material groups in the collection.
343    pub fn iter_base_materials(&self) -> impl Iterator<Item = &BaseMaterialsGroup> {
344        self.base_materials.values()
345    }
346
347    /// Returns an iterator over all color groups in the collection.
348    pub fn iter_color_groups(&self) -> impl Iterator<Item = &ColorGroup> {
349        self.color_groups.values()
350    }
351
352    /// Returns an iterator over all texture 2D groups in the collection.
353    pub fn iter_textures(&self) -> impl Iterator<Item = &Texture2DGroup> {
354        self.texture_2d_groups.values()
355    }
356
357    /// Returns an iterator over all composite material groups in the collection.
358    pub fn iter_composite_materials(&self) -> impl Iterator<Item = &CompositeMaterials> {
359        self.composite_materials.values()
360    }
361
362    /// Returns an iterator over all multi-property groups in the collection.
363    pub fn iter_multi_properties(&self) -> impl Iterator<Item = &MultiProperties> {
364        self.multi_properties.values()
365    }
366
367    /// Adds a 2D displacement texture resource to the collection.
368    ///
369    /// # Errors
370    ///
371    /// Returns `Lib3mfError::Validation` if a resource with the same ID already exists.
372    pub fn add_displacement_2d(&mut self, res: Displacement2D) -> Result<()> {
373        if self.exists(res.id) {
374            return Err(Lib3mfError::Validation(format!(
375                "Duplicate resource ID: {}",
376                res.id.0
377            )));
378        }
379        self.displacement_2d.insert(res.id, res);
380        Ok(())
381    }
382
383    /// Retrieves a 2D displacement texture resource by its ID.
384    ///
385    /// Returns `None` if no displacement 2D resource with the given ID exists.
386    pub fn get_displacement_2d(&self, id: ResourceId) -> Option<&Displacement2D> {
387        self.displacement_2d.get(&id)
388    }
389
390    /// Returns the number of 2D displacement texture resources in the collection.
391    pub fn displacement_2d_count(&self) -> usize {
392        self.displacement_2d.len()
393    }
394
395    /// Returns an iterator over all 2D displacement texture resources in the collection.
396    pub fn iter_displacement_2d(&self) -> impl Iterator<Item = &Displacement2D> {
397        self.displacement_2d.values()
398    }
399
400    /// Returns an iterator over all Texture2D resources in the collection.
401    pub fn iter_texture_2d(&self) -> impl Iterator<Item = &Texture2D> {
402        self.texture_2d.values()
403    }
404
405    /// Returns an iterator over all slice stacks in the collection.
406    pub fn iter_slice_stacks(&self) -> impl Iterator<Item = &SliceStack> {
407        self.slice_stacks.values()
408    }
409
410    /// Returns an iterator over all volumetric stacks in the collection.
411    pub fn iter_volumetric_stacks(&self) -> impl Iterator<Item = &VolumetricStack> {
412        self.volumetric_stacks.values()
413    }
414}