Skip to main content

lib3mf/extensions/
slice.rs

1//! Slice extension handler implementation
2
3use crate::error::Result;
4use crate::extension::ExtensionHandler;
5use crate::model::{Extension, Model};
6use crate::validator::{validate_slice_extension, validate_slices};
7
8/// Handler for the Slice extension
9///
10/// The Slice extension provides support for sliced 3D models, allowing models to be
11/// represented as a stack of 2D cross-sections at different Z heights. This is commonly
12/// used for additive manufacturing workflows.
13///
14/// # Validation
15///
16/// This handler validates:
17/// - Slice stack structure and Z-coordinate ordering
18/// - Polygon validity (closed, proper vertex references)
19/// - Planar transform constraints for objects with slicestacks
20/// - SliceRef path requirements and external file references
21///
22/// # Example
23///
24/// ```ignore
25/// use lib3mf::extension::ExtensionRegistry;
26/// use lib3mf::extensions::slice::SliceExtensionHandler;
27/// use std::sync::Arc;
28///
29/// let mut registry = ExtensionRegistry::new();
30/// registry.register(Arc::new(SliceExtensionHandler));
31/// ```
32pub struct SliceExtensionHandler;
33
34impl ExtensionHandler for SliceExtensionHandler {
35    fn extension_type(&self) -> Extension {
36        Extension::Slice
37    }
38
39    fn validate(&self, model: &Model) -> Result<()> {
40        // Run all slice-specific validations
41        validate_slices(model)?;
42        validate_slice_extension(model)?;
43        Ok(())
44    }
45
46    fn is_used_in_model(&self, model: &Model) -> bool {
47        // Check if model has any slice stacks or objects referencing slicestacks
48        if !model.resources.slice_stacks.is_empty() {
49            return true;
50        }
51
52        // Check if any objects reference slicestacks
53        model
54            .resources
55            .objects
56            .iter()
57            .any(|obj| obj.slicestackid.is_some())
58    }
59}
60
61#[cfg(test)]
62mod tests {
63    use super::*;
64    use crate::model::{Object, Slice, SlicePolygon, SliceSegment, SliceStack, Vertex2D};
65
66    #[test]
67    fn test_extension_type() {
68        let handler = SliceExtensionHandler;
69        assert_eq!(handler.extension_type(), Extension::Slice);
70    }
71
72    #[test]
73    fn test_namespace() {
74        let handler = SliceExtensionHandler;
75        assert_eq!(
76            handler.namespace(),
77            "http://schemas.microsoft.com/3dmanufacturing/slice/2015/07"
78        );
79    }
80
81    #[test]
82    fn test_name() {
83        let handler = SliceExtensionHandler;
84        assert_eq!(handler.name(), "Slice");
85    }
86
87    #[test]
88    fn test_is_used_in_model_with_slice_stacks() {
89        let handler = SliceExtensionHandler;
90        let mut model = Model::new();
91
92        // Initially no slices
93        assert!(!handler.is_used_in_model(&model));
94
95        // Add a slice stack
96        let slice_stack = SliceStack::new(1, 0.0);
97        model.resources.slice_stacks.push(slice_stack);
98
99        assert!(handler.is_used_in_model(&model));
100    }
101
102    #[test]
103    fn test_is_used_in_model_with_object_reference() {
104        let handler = SliceExtensionHandler;
105        let mut model = Model::new();
106
107        // Initially no slices
108        assert!(!handler.is_used_in_model(&model));
109
110        // Add an object that references a slicestack
111        let mut object = Object::new(1);
112        object.slicestackid = Some(1);
113        model.resources.objects.push(object);
114
115        assert!(handler.is_used_in_model(&model));
116    }
117
118    #[test]
119    fn test_validate_empty_model() {
120        let handler = SliceExtensionHandler;
121        let model = Model::new();
122
123        // Empty model should validate successfully
124        assert!(handler.validate(&model).is_ok());
125    }
126
127    #[test]
128    fn test_validate_valid_slice_stack() {
129        let handler = SliceExtensionHandler;
130        let mut model = Model::new();
131
132        // Create a valid slice stack with one slice
133        let mut slice_stack = SliceStack::new(1, 0.0);
134
135        // Create a slice with a valid triangle polygon
136        let mut slice = Slice::new(1.0);
137        slice.vertices.push(Vertex2D::new(0.0, 0.0));
138        slice.vertices.push(Vertex2D::new(1.0, 0.0));
139        slice.vertices.push(Vertex2D::new(0.5, 1.0));
140
141        let mut polygon = SlicePolygon::new(0);
142        polygon.segments.push(SliceSegment::new(1));
143        polygon.segments.push(SliceSegment::new(2));
144        polygon.segments.push(SliceSegment::new(0)); // Close the polygon
145
146        slice.polygons.push(polygon);
147        slice_stack.slices.push(slice);
148
149        model.resources.slice_stacks.push(slice_stack);
150
151        // Should validate successfully
152        assert!(handler.validate(&model).is_ok());
153    }
154
155    #[test]
156    fn test_validate_invalid_ztop_below_zbottom() {
157        let handler = SliceExtensionHandler;
158        let mut model = Model::new();
159
160        // Create a slice stack with zbottom > slice ztop (invalid)
161        let mut slice_stack = SliceStack::new(1, 5.0); // zbottom = 5.0
162
163        let slice = Slice::new(1.0); // ztop = 1.0 < zbottom (invalid!)
164        slice_stack.slices.push(slice);
165
166        model.resources.slice_stacks.push(slice_stack);
167
168        // Should fail validation
169        assert!(handler.validate(&model).is_err());
170    }
171
172    #[test]
173    fn test_validate_non_increasing_ztop() {
174        let handler = SliceExtensionHandler;
175        let mut model = Model::new();
176
177        let mut slice_stack = SliceStack::new(1, 0.0);
178
179        // Add slices with non-strictly increasing ztop values
180        slice_stack.slices.push(Slice::new(1.0));
181        slice_stack.slices.push(Slice::new(1.0)); // Same as previous (invalid!)
182
183        model.resources.slice_stacks.push(slice_stack);
184
185        // Should fail validation
186        assert!(handler.validate(&model).is_err());
187    }
188
189    #[test]
190    fn test_validate_invalid_polygon_not_closed() {
191        let handler = SliceExtensionHandler;
192        let mut model = Model::new();
193
194        let mut slice_stack = SliceStack::new(1, 0.0);
195
196        let mut slice = Slice::new(1.0);
197        slice.vertices.push(Vertex2D::new(0.0, 0.0));
198        slice.vertices.push(Vertex2D::new(1.0, 0.0));
199        slice.vertices.push(Vertex2D::new(0.5, 1.0));
200
201        let mut polygon = SlicePolygon::new(0);
202        polygon.segments.push(SliceSegment::new(1));
203        polygon.segments.push(SliceSegment::new(2));
204        // Missing segment back to startv=0 (not closed!)
205
206        slice.polygons.push(polygon);
207        slice_stack.slices.push(slice);
208
209        model.resources.slice_stacks.push(slice_stack);
210
211        // Should fail validation
212        assert!(handler.validate(&model).is_err());
213    }
214
215    #[test]
216    fn test_post_parse_default() {
217        let handler = SliceExtensionHandler;
218        let mut model = Model::new();
219
220        // Default implementation should do nothing and succeed
221        assert!(handler.post_parse(&mut model).is_ok());
222    }
223
224    #[test]
225    fn test_pre_write_default() {
226        let handler = SliceExtensionHandler;
227        let mut model = Model::new();
228
229        // Default implementation should do nothing and succeed
230        assert!(handler.pre_write(&mut model).is_ok());
231    }
232}