lib3mf_core/model/
resources.rs

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