lib3mf 0.1.6

Pure Rust implementation for 3MF (3D Manufacturing Format) parsing and writing
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
//! Polygon clipping and self-intersection resolution for slices
//!
//! This module provides polygon clipping operations using the Clipper2 library,
//! which is a Rust port of the Angus Johnson's Clipper2 library (the successor
//! to the polyclipping library used in the C++ lib3mf implementation).
//!
//! Clipper2 provides robust polygon boolean operations including:
//! - Union: Combine multiple polygons
//! - Intersection: Find overlapping areas
//! - Difference: Subtract one polygon from another
//! - XOR: Symmetric difference
//! - Simplification: Remove self-intersections and degenerate vertices
//!
//! These operations are essential for resolving self-intersections in slice polygons,
//! which can occur during the slicing process in 3D manufacturing workflows.

use crate::model::{SlicePolygon, Vertex2D};
use clipper2::*;

/// Error type for polygon clipping operations
#[derive(Debug, thiserror::Error)]
pub enum ClippingError {
    /// Invalid polygon data
    #[error("Invalid polygon: {0}")]
    InvalidPolygon(String),

    /// Clipper operation failed
    #[error("Clipper operation failed: {0}")]
    ClipperError(String),
}

/// Convert slice polygons to Clipper2 paths
///
/// Converts the lib3mf slice polygon format (vertex indices with segments)
/// into Clipper2's path format (Vec<(f64, f64)>).
fn slice_polygon_to_paths(
    polygon: &SlicePolygon,
    vertices: &[Vertex2D],
) -> Result<Vec<(f64, f64)>, ClippingError> {
    let mut path = Vec::new();

    // Add starting vertex
    if polygon.startv >= vertices.len() {
        return Err(ClippingError::InvalidPolygon(format!(
            "Start vertex index {} out of bounds",
            polygon.startv
        )));
    }

    let start_vertex = &vertices[polygon.startv];
    path.push((start_vertex.x, start_vertex.y));

    // Add vertices from segments
    for segment in &polygon.segments {
        if segment.v2 >= vertices.len() {
            return Err(ClippingError::InvalidPolygon(format!(
                "Segment vertex index {} out of bounds",
                segment.v2
            )));
        }

        let vertex = &vertices[segment.v2];
        path.push((vertex.x, vertex.y));
    }

    Ok(path)
}

/// Convert Clipper2 paths back to slice polygons
///
/// Converts Clipper2's path format back into lib3mf slice polygon format.
/// This creates new vertices and polygon structures.
fn paths_to_slice_polygons(
    paths: Vec<Vec<(f64, f64)>>,
    vertices: &mut Vec<Vertex2D>,
) -> Vec<SlicePolygon> {
    let mut polygons = Vec::new();

    for path in paths {
        if path.len() < 3 {
            // Skip degenerate polygons (need at least 3 vertices)
            continue;
        }

        let start_idx = vertices.len();

        // Add the first vertex and create polygon
        let first_point = path[0];
        vertices.push(Vertex2D::new(first_point.0, first_point.1));

        let mut polygon = SlicePolygon::new(start_idx);

        // Add remaining vertices as segments
        for point in path.iter().skip(1) {
            let vertex_idx = vertices.len();
            vertices.push(Vertex2D::new(point.0, point.1));
            polygon
                .segments
                .push(crate::model::SliceSegment::new(vertex_idx));
        }

        polygons.push(polygon);
    }

    polygons
}

/// Resolve self-intersections in a slice polygon
///
/// Uses Clipper2's simplify operation to remove self-intersections and
/// degenerate vertices from a polygon. This is useful for cleaning up
/// slice data that may have been generated with numerical errors or
/// topology issues.
///
/// # Arguments
///
/// * `polygon` - The slice polygon to simplify
/// * `vertices` - The vertex buffer containing the polygon's vertices
/// * `result_vertices` - Output vertex buffer for the simplified polygon(s)
///
/// # Returns
///
/// A vector of simplified polygons. Note that resolving self-intersections
/// may result in multiple output polygons if the self-intersection divides
/// the polygon into separate regions.
///
/// # Example
///
/// ```ignore
/// use lib3mf::polygon_clipping::resolve_self_intersections;
/// use lib3mf::model::{SlicePolygon, Vertex2D};
///
/// let polygon = SlicePolygon::new(0);
/// let vertices = vec![
///     Vertex2D::new(0.0, 0.0),
///     Vertex2D::new(10.0, 0.0),
///     Vertex2D::new(10.0, 10.0),
///     Vertex2D::new(0.0, 10.0),
/// ];
/// let mut result_vertices = Vec::new();
///
/// let simplified = resolve_self_intersections(&polygon, &vertices, &mut result_vertices)
///     .expect("Failed to resolve self-intersections");
/// ```
pub fn resolve_self_intersections(
    polygon: &SlicePolygon,
    vertices: &[Vertex2D],
    result_vertices: &mut Vec<Vertex2D>,
) -> Result<Vec<SlicePolygon>, ClippingError> {
    // Convert to Clipper2 path
    let path = slice_polygon_to_paths(polygon, vertices)?;

    // Simplify the polygon to remove self-intersections and nearly collinear points
    // epsilon=0.01 removes points within 0.01 units of a line
    // is_open=false because slice polygons are closed
    let simplified = simplify::<Centi>(vec![path], 0.01, false);

    // Convert back to slice polygons
    let result: Vec<Vec<(f64, f64)>> = simplified.into();
    Ok(paths_to_slice_polygons(result, result_vertices))
}

/// Perform union operation on multiple slice polygons
///
/// Combines multiple polygons into a single unified set of polygons,
/// merging overlapping areas.
///
/// # Arguments
///
/// * `polygons` - The slice polygons to union
/// * `vertices` - The vertex buffer containing the polygons' vertices
/// * `result_vertices` - Output vertex buffer for the result polygon(s)
///
/// # Returns
///
/// A vector of polygons representing the union of all input polygons.
pub fn union_polygons(
    polygons: &[SlicePolygon],
    vertices: &[Vertex2D],
    result_vertices: &mut Vec<Vertex2D>,
) -> Result<Vec<SlicePolygon>, ClippingError> {
    if polygons.is_empty() {
        return Ok(Vec::new());
    }

    // Convert all polygons to paths
    let mut all_paths = Vec::new();
    for polygon in polygons {
        all_paths.push(slice_polygon_to_paths(polygon, vertices)?);
    }

    // If only one polygon, simplify it and return
    if all_paths.len() == 1 {
        let simplified = simplify::<Centi>(all_paths, 0.01, false);
        let result_paths: Vec<Vec<(f64, f64)>> = simplified.into();
        return Ok(paths_to_slice_polygons(result_paths, result_vertices));
    }

    // Perform union operation on multiple polygons
    let first_path = all_paths.remove(0);
    let result = union::<Centi>(vec![first_path], all_paths, FillRule::default())
        .map_err(|e| ClippingError::ClipperError(format!("{:?}", e)))?;

    // Convert back to slice polygons
    let result_paths: Vec<Vec<(f64, f64)>> = result.into();
    Ok(paths_to_slice_polygons(result_paths, result_vertices))
}

/// Perform intersection operation on two sets of slice polygons
///
/// Finds the overlapping areas between two sets of polygons.
///
/// # Arguments
///
/// * `subject_polygons` - The first set of polygons
/// * `clip_polygons` - The second set of polygons
/// * `vertices` - The vertex buffer containing the polygons' vertices
/// * `result_vertices` - Output vertex buffer for the result polygon(s)
///
/// # Returns
///
/// A vector of polygons representing the intersection of the two sets.
pub fn intersect_polygons(
    subject_polygons: &[SlicePolygon],
    clip_polygons: &[SlicePolygon],
    vertices: &[Vertex2D],
    result_vertices: &mut Vec<Vertex2D>,
) -> Result<Vec<SlicePolygon>, ClippingError> {
    if subject_polygons.is_empty() || clip_polygons.is_empty() {
        return Ok(Vec::new());
    }

    // Convert subject polygons to paths
    let mut subject_paths = Vec::new();
    for polygon in subject_polygons {
        subject_paths.push(slice_polygon_to_paths(polygon, vertices)?);
    }

    // Convert clip polygons to paths
    let mut clip_paths = Vec::new();
    for polygon in clip_polygons {
        clip_paths.push(slice_polygon_to_paths(polygon, vertices)?);
    }

    // Perform intersection operation
    let result = intersect::<Centi>(subject_paths, clip_paths, FillRule::default())
        .map_err(|e| ClippingError::ClipperError(format!("{:?}", e)))?;

    // Convert back to slice polygons
    let result_paths: Vec<Vec<(f64, f64)>> = result.into();
    Ok(paths_to_slice_polygons(result_paths, result_vertices))
}

/// Perform difference operation on two sets of slice polygons
///
/// Subtracts the clip polygons from the subject polygons.
///
/// # Arguments
///
/// * `subject_polygons` - The polygons to subtract from
/// * `clip_polygons` - The polygons to subtract
/// * `vertices` - The vertex buffer containing the polygons' vertices
/// * `result_vertices` - Output vertex buffer for the result polygon(s)
///
/// # Returns
///
/// A vector of polygons representing the difference.
pub fn difference_polygons(
    subject_polygons: &[SlicePolygon],
    clip_polygons: &[SlicePolygon],
    vertices: &[Vertex2D],
    result_vertices: &mut Vec<Vertex2D>,
) -> Result<Vec<SlicePolygon>, ClippingError> {
    if subject_polygons.is_empty() {
        return Ok(Vec::new());
    }

    // Convert subject polygons to paths
    let mut subject_paths = Vec::new();
    for polygon in subject_polygons {
        subject_paths.push(slice_polygon_to_paths(polygon, vertices)?);
    }

    if clip_polygons.is_empty() {
        // Nothing to subtract, simplify and return subject
        let simplified = simplify::<Centi>(subject_paths, 0.01, false);
        let result_paths: Vec<Vec<(f64, f64)>> = simplified.into();
        return Ok(paths_to_slice_polygons(result_paths, result_vertices));
    }

    // Convert clip polygons to paths
    let mut clip_paths = Vec::new();
    for polygon in clip_polygons {
        clip_paths.push(slice_polygon_to_paths(polygon, vertices)?);
    }

    // Perform difference operation
    let result = difference::<Centi>(subject_paths, clip_paths, FillRule::default())
        .map_err(|e| ClippingError::ClipperError(format!("{:?}", e)))?;

    // Convert back to slice polygons
    let result_paths: Vec<Vec<(f64, f64)>> = result.into();
    Ok(paths_to_slice_polygons(result_paths, result_vertices))
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::model::SliceSegment;

    #[test]
    fn test_resolve_simple_polygon() {
        // Create a simple square polygon without self-intersections
        let vertices = vec![
            Vertex2D::new(0.0, 0.0),
            Vertex2D::new(10.0, 0.0),
            Vertex2D::new(10.0, 10.0),
            Vertex2D::new(0.0, 10.0),
        ];

        let mut polygon = SlicePolygon::new(0);
        polygon.segments.push(SliceSegment::new(1));
        polygon.segments.push(SliceSegment::new(2));
        polygon.segments.push(SliceSegment::new(3));

        let mut result_vertices = Vec::new();
        let result = resolve_self_intersections(&polygon, &vertices, &mut result_vertices)
            .expect("Failed to resolve polygon");

        // Should return a single polygon
        assert_eq!(result.len(), 1, "Expected one polygon");
        assert!(!result_vertices.is_empty(), "Expected vertices in result");
    }

    #[test]
    fn test_union_two_squares() {
        // Create two overlapping squares
        let vertices = vec![
            // First square (0,0) to (10,10)
            Vertex2D::new(0.0, 0.0),
            Vertex2D::new(10.0, 0.0),
            Vertex2D::new(10.0, 10.0),
            Vertex2D::new(0.0, 10.0),
            // Second square (5,5) to (15,15)
            Vertex2D::new(5.0, 5.0),
            Vertex2D::new(15.0, 5.0),
            Vertex2D::new(15.0, 15.0),
            Vertex2D::new(5.0, 15.0),
        ];

        let mut polygon1 = SlicePolygon::new(0);
        polygon1.segments.push(SliceSegment::new(1));
        polygon1.segments.push(SliceSegment::new(2));
        polygon1.segments.push(SliceSegment::new(3));

        let mut polygon2 = SlicePolygon::new(4);
        polygon2.segments.push(SliceSegment::new(5));
        polygon2.segments.push(SliceSegment::new(6));
        polygon2.segments.push(SliceSegment::new(7));

        let mut result_vertices = Vec::new();
        let result = union_polygons(&[polygon1, polygon2], &vertices, &mut result_vertices)
            .expect("Failed to union polygons");

        // Should return at least one polygon (the union)
        assert!(!result.is_empty(), "Expected at least one polygon in union");
        assert!(!result_vertices.is_empty(), "Expected vertices in result");
    }

    #[test]
    fn test_intersection_two_squares() {
        // Create two overlapping squares
        let vertices = vec![
            // First square (0,0) to (10,10)
            Vertex2D::new(0.0, 0.0),
            Vertex2D::new(10.0, 0.0),
            Vertex2D::new(10.0, 10.0),
            Vertex2D::new(0.0, 10.0),
            // Second square (5,5) to (15,15)
            Vertex2D::new(5.0, 5.0),
            Vertex2D::new(15.0, 5.0),
            Vertex2D::new(15.0, 15.0),
            Vertex2D::new(5.0, 15.0),
        ];

        let mut polygon1 = SlicePolygon::new(0);
        polygon1.segments.push(SliceSegment::new(1));
        polygon1.segments.push(SliceSegment::new(2));
        polygon1.segments.push(SliceSegment::new(3));

        let mut polygon2 = SlicePolygon::new(4);
        polygon2.segments.push(SliceSegment::new(5));
        polygon2.segments.push(SliceSegment::new(6));
        polygon2.segments.push(SliceSegment::new(7));

        let mut result_vertices = Vec::new();
        let result = intersect_polygons(&[polygon1], &[polygon2], &vertices, &mut result_vertices)
            .expect("Failed to intersect polygons");

        // Should return the overlapping region (5,5) to (10,10)
        assert!(
            !result.is_empty(),
            "Expected at least one polygon in intersection"
        );
    }

    #[test]
    fn test_difference_two_squares() {
        // Create two overlapping squares
        let vertices = vec![
            // First square (0,0) to (10,10)
            Vertex2D::new(0.0, 0.0),
            Vertex2D::new(10.0, 0.0),
            Vertex2D::new(10.0, 10.0),
            Vertex2D::new(0.0, 10.0),
            // Second square (5,5) to (15,15)
            Vertex2D::new(5.0, 5.0),
            Vertex2D::new(15.0, 5.0),
            Vertex2D::new(15.0, 15.0),
            Vertex2D::new(5.0, 15.0),
        ];

        let mut polygon1 = SlicePolygon::new(0);
        polygon1.segments.push(SliceSegment::new(1));
        polygon1.segments.push(SliceSegment::new(2));
        polygon1.segments.push(SliceSegment::new(3));

        let mut polygon2 = SlicePolygon::new(4);
        polygon2.segments.push(SliceSegment::new(5));
        polygon2.segments.push(SliceSegment::new(6));
        polygon2.segments.push(SliceSegment::new(7));

        let mut result_vertices = Vec::new();
        let result = difference_polygons(&[polygon1], &[polygon2], &vertices, &mut result_vertices)
            .expect("Failed to compute difference");

        // Should return the first square minus the overlapping region
        assert!(
            !result.is_empty(),
            "Expected at least one polygon in difference"
        );
    }

    #[test]
    fn test_invalid_vertex_index() {
        // Create a polygon with an out-of-bounds vertex index
        let vertices = vec![Vertex2D::new(0.0, 0.0), Vertex2D::new(10.0, 0.0)];

        let mut polygon = SlicePolygon::new(0);
        polygon.segments.push(SliceSegment::new(1));
        polygon.segments.push(SliceSegment::new(5)); // Out of bounds!

        let mut result_vertices = Vec::new();
        let result = resolve_self_intersections(&polygon, &vertices, &mut result_vertices);

        // Should return an error
        assert!(result.is_err(), "Expected error for out-of-bounds vertex");
    }
}