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    pub key_store: Option<KeyStore>, // Usually one KeyStore per model/part
72}
73
74impl ResourceCollection {
75    /// Creates a new empty resource collection.
76    pub fn new() -> Self {
77        Self::default()
78    }
79
80    /// Checks if a resource with the given ID exists in any resource type.
81    ///
82    /// Returns `true` if the ID is used by any resource (object, material, texture, etc.).
83    pub fn exists(&self, id: ResourceId) -> bool {
84        self.objects.contains_key(&id)
85            || self.base_materials.contains_key(&id)
86            || self.color_groups.contains_key(&id)
87            || self.slice_stacks.contains_key(&id)
88            || self.volumetric_stacks.contains_key(&id)
89            || self.texture_2d.contains_key(&id)
90            || self.texture_2d_groups.contains_key(&id)
91            || self.composite_materials.contains_key(&id)
92            || self.multi_properties.contains_key(&id)
93            || self.displacement_2d.contains_key(&id)
94    }
95
96    /// Adds an object to the collection.
97    ///
98    /// # Errors
99    ///
100    /// Returns `Lib3mfError::Validation` if a resource with the same ID already exists.
101    pub fn add_object(&mut self, object: Object) -> Result<()> {
102        if self.exists(object.id) {
103            return Err(Lib3mfError::Validation(format!(
104                "Duplicate resource ID: {}",
105                object.id.0
106            )));
107        }
108        self.objects.insert(object.id, object);
109        Ok(())
110    }
111
112    /// Adds a base materials group to the collection.
113    ///
114    /// # Errors
115    ///
116    /// Returns `Lib3mfError::Validation` if a resource with the same ID already exists.
117    pub fn add_base_materials(&mut self, group: BaseMaterialsGroup) -> Result<()> {
118        if self.exists(group.id) {
119            return Err(Lib3mfError::Validation(format!(
120                "Duplicate resource ID: {}",
121                group.id.0
122            )));
123        }
124        self.base_materials.insert(group.id, group);
125        Ok(())
126    }
127
128    /// Adds a color group to the collection.
129    ///
130    /// # Errors
131    ///
132    /// Returns `Lib3mfError::Validation` if a resource with the same ID already exists.
133    pub fn add_color_group(&mut self, group: ColorGroup) -> Result<()> {
134        if self.exists(group.id) {
135            return Err(Lib3mfError::Validation(format!(
136                "Duplicate resource ID: {}",
137                group.id.0
138            )));
139        }
140        self.color_groups.insert(group.id, group);
141        Ok(())
142    }
143
144    pub fn add_slice_stack(&mut self, stack: SliceStack) -> Result<()> {
145        if self.exists(stack.id) {
146            return Err(Lib3mfError::Validation(format!(
147                "Duplicate resource ID: {}",
148                stack.id.0
149            )));
150        }
151        self.slice_stacks.insert(stack.id, stack);
152        Ok(())
153    }
154
155    pub fn add_volumetric_stack(&mut self, stack: VolumetricStack) -> Result<()> {
156        if self.exists(stack.id) {
157            return Err(Lib3mfError::Validation(format!(
158                "Duplicate resource ID: {}",
159                stack.id.0
160            )));
161        }
162        self.volumetric_stacks.insert(stack.id, stack);
163        Ok(())
164    }
165
166    pub fn set_key_store(&mut self, store: KeyStore) {
167        self.key_store = Some(store);
168    }
169
170    /// Retrieves an object by its ID.
171    ///
172    /// Returns `None` if no object with the given ID exists.
173    pub fn get_object(&self, id: ResourceId) -> Option<&Object> {
174        self.objects.get(&id)
175    }
176
177    /// Retrieves a base materials group by its ID.
178    ///
179    /// Returns `None` if no base materials group with the given ID exists.
180    pub fn get_base_materials(&self, id: ResourceId) -> Option<&BaseMaterialsGroup> {
181        self.base_materials.get(&id)
182    }
183
184    /// Retrieves a color group by its ID.
185    ///
186    /// Returns `None` if no color group with the given ID exists.
187    pub fn get_color_group(&self, id: ResourceId) -> Option<&ColorGroup> {
188        self.color_groups.get(&id)
189    }
190
191    pub fn get_slice_stack(&self, id: ResourceId) -> Option<&SliceStack> {
192        self.slice_stacks.get(&id)
193    }
194
195    pub fn get_volumetric_stack(&self, id: ResourceId) -> Option<&VolumetricStack> {
196        self.volumetric_stacks.get(&id)
197    }
198
199    pub fn add_texture_2d(&mut self, texture: Texture2D) -> Result<()> {
200        if self.exists(texture.id) {
201            return Err(Lib3mfError::Validation(format!(
202                "Duplicate resource ID: {}",
203                texture.id.0
204            )));
205        }
206        self.texture_2d.insert(texture.id, texture);
207        Ok(())
208    }
209
210    pub fn add_texture_2d_group(&mut self, group: Texture2DGroup) -> Result<()> {
211        if self.exists(group.id) {
212            return Err(Lib3mfError::Validation(format!(
213                "Duplicate resource ID: {}",
214                group.id.0
215            )));
216        }
217        self.texture_2d_groups.insert(group.id, group);
218        Ok(())
219    }
220
221    pub fn get_texture_2d_group(&self, id: ResourceId) -> Option<&Texture2DGroup> {
222        self.texture_2d_groups.get(&id)
223    }
224
225    pub fn add_composite_materials(&mut self, group: CompositeMaterials) -> Result<()> {
226        if self.exists(group.id) {
227            return Err(Lib3mfError::Validation(format!(
228                "Duplicate resource ID: {}",
229                group.id.0
230            )));
231        }
232        self.composite_materials.insert(group.id, group);
233        Ok(())
234    }
235
236    pub fn get_composite_materials(&self, id: ResourceId) -> Option<&CompositeMaterials> {
237        self.composite_materials.get(&id)
238    }
239
240    pub fn add_multi_properties(&mut self, group: MultiProperties) -> Result<()> {
241        if self.exists(group.id) {
242            return Err(Lib3mfError::Validation(format!(
243                "Duplicate resource ID: {}",
244                group.id.0
245            )));
246        }
247        self.multi_properties.insert(group.id, group);
248        Ok(())
249    }
250
251    pub fn get_multi_properties(&self, id: ResourceId) -> Option<&MultiProperties> {
252        self.multi_properties.get(&id)
253    }
254
255    pub fn base_material_groups_count(&self) -> usize {
256        self.base_materials.len()
257    }
258
259    pub fn color_groups_count(&self) -> usize {
260        self.color_groups.len()
261    }
262
263    pub fn volumetric_stacks_count(&self) -> usize {
264        self.volumetric_stacks.len()
265    }
266
267    pub fn texture_2d_groups_count(&self) -> usize {
268        self.texture_2d_groups.len()
269    }
270
271    pub fn composite_materials_count(&self) -> usize {
272        self.composite_materials.len()
273    }
274
275    pub fn multi_properties_count(&self) -> usize {
276        self.multi_properties.len()
277    }
278
279    /// Returns an iterator over all objects in the collection.
280    pub fn iter_objects(&self) -> impl Iterator<Item = &Object> {
281        self.objects.values()
282    }
283
284    /// Returns a mutable iterator over all objects in the collection.
285    pub fn iter_objects_mut(&mut self) -> impl Iterator<Item = &mut Object> {
286        self.objects.values_mut()
287    }
288
289    /// Returns an iterator over all base material groups in the collection.
290    pub fn iter_base_materials(&self) -> impl Iterator<Item = &BaseMaterialsGroup> {
291        self.base_materials.values()
292    }
293
294    /// Returns an iterator over all color groups in the collection.
295    pub fn iter_color_groups(&self) -> impl Iterator<Item = &ColorGroup> {
296        self.color_groups.values()
297    }
298
299    /// Returns an iterator over all texture 2D groups in the collection.
300    pub fn iter_textures(&self) -> impl Iterator<Item = &Texture2DGroup> {
301        self.texture_2d_groups.values()
302    }
303
304    /// Returns an iterator over all composite material groups in the collection.
305    pub fn iter_composite_materials(&self) -> impl Iterator<Item = &CompositeMaterials> {
306        self.composite_materials.values()
307    }
308
309    /// Returns an iterator over all multi-property groups in the collection.
310    pub fn iter_multi_properties(&self) -> impl Iterator<Item = &MultiProperties> {
311        self.multi_properties.values()
312    }
313
314    pub fn add_displacement_2d(&mut self, res: Displacement2D) -> Result<()> {
315        if self.exists(res.id) {
316            return Err(Lib3mfError::Validation(format!(
317                "Duplicate resource ID: {}",
318                res.id.0
319            )));
320        }
321        self.displacement_2d.insert(res.id, res);
322        Ok(())
323    }
324
325    pub fn get_displacement_2d(&self, id: ResourceId) -> Option<&Displacement2D> {
326        self.displacement_2d.get(&id)
327    }
328
329    pub fn displacement_2d_count(&self) -> usize {
330        self.displacement_2d.len()
331    }
332
333    pub fn iter_displacement_2d(&self) -> impl Iterator<Item = &Displacement2D> {
334        self.displacement_2d.values()
335    }
336}