lib3mf 0.1.2

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
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
# lib3mf_rust

A pure Rust implementation for parsing 3MF (3D Manufacturing Format) files with **no unsafe code**.

Note: Most part of this code has been vibe-coded using GitHub Copilot Agents

[![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)

## Overview

This library provides a pure Rust implementation for reading and parsing 3MF files, which are ZIP-based containers following the Open Packaging Conventions (OPC) standard and containing XML-based 3D model data.

The 3MF format is the modern standard for 3D printing, supporting rich model information including:
- 3D mesh geometry (vertices and triangles)
- Materials and colors
- Metadata
- Build specifications
- And more

## Features

- **Pure Rust implementation** - No C/C++ dependencies
-**No unsafe code** - Enforced with `#![forbid(unsafe_code)]`
-**Extension support** - Configurable support for 3MF extensions with validation
- ✅ Parse 3MF file structure (ZIP/OPC container)
- ✅ Read 3D model data including meshes, vertices, and triangles
-**Write/Serialize 3MF files** - Create and write 3MF files
- ✅ Support for materials and colors
- ✅ Metadata extraction and writing
- ✅ Build item specifications
- ✅ Comprehensive error handling
- ✅ Round-trip support (read-write-read)

## Installation

Add this to your `Cargo.toml`:

```toml
[dependencies]
lib3mf = "0.1"
```

## Usage

### Reading 3MF Files

#### Basic Example

```rust
use lib3mf::Model;
use std::fs::File;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Open and parse a 3MF file
    let file = File::open("model.3mf")?;
    let model = Model::from_reader(file)?;

    // Access model information
    println!("Unit: {}", model.unit);
    println!("Objects: {}", model.resources.objects.len());

    // Iterate through objects
    for obj in &model.resources.objects {
        if let Some(ref mesh) = obj.mesh {
            println!("Object {} has {} vertices and {} triangles",
                obj.id, mesh.vertices.len(), mesh.triangles.len());
        }
    }

    Ok(())
}
```

### Writing 3MF Files

#### Creating and Writing a Simple Model

```rust
use lib3mf::{Model, Object, Mesh, Vertex, Triangle, BuildItem};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Create a new model
    let mut model = Model::new();
    model.unit = "millimeter".to_string();

    // Create a mesh with a simple triangle
    let mut mesh = Mesh::new();
    mesh.vertices.push(Vertex::new(0.0, 0.0, 0.0));
    mesh.vertices.push(Vertex::new(10.0, 0.0, 0.0));
    mesh.vertices.push(Vertex::new(5.0, 10.0, 0.0));
    mesh.triangles.push(Triangle::new(0, 1, 2));

    // Create an object with the mesh
    let mut object = Object::new(1);
    object.name = Some("Triangle".to_string());
    object.mesh = Some(mesh);

    // Add object to resources
    model.resources.objects.push(object);

    // Add to build
    model.build.items.push(BuildItem::new(1));

    // Write to file
    model.write_to_file("output.3mf")?;

    Ok(())
}
```

#### Round-Trip Example

```rust
use lib3mf::Model;
use std::fs::File;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Read an existing 3MF file
    let file = File::open("input.3mf")?;
    let model = Model::from_reader(file)?;

    // Modify the model
    // ... make changes ...

    // Write back to a new file
    model.write_to_file("output.3mf")?;

    Ok(())
}
```

### Streaming Parser for Large Files

For very large files, use the streaming parser to process objects one at a time without loading everything into memory:

```rust
use lib3mf::streaming::StreamingParser;
use std::fs::File;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let file = File::open("large_model.3mf")?;
    let mut parser = StreamingParser::new(file)?;

    // Process objects one at a time
    for object in parser.objects() {
        let object = object?;
        if let Some(ref mesh) = object.mesh {
            println!("Object {}: {} vertices",
                object.id, mesh.vertices.len());
        }
        // Object is dropped here, freeing memory
    }

    Ok(())
}
```

### Accessing Production Extension Data

The Production Extension provides UUIDs and routing paths for manufacturing workflows:

```rust
use lib3mf::Model;
use std::fs::File;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let file = File::open("production_model.3mf")?;
    let model = Model::from_reader(file)?;

    // Access production UUIDs from objects
    for obj in &model.resources.objects {
        if let Some(ref production) = obj.production {
            println!("Object {} has production UUID: {}", 
                obj.id, production.uuid.as_deref().unwrap_or("<none>"));
            
            if let Some(ref path) = production.path {
                println!("  Production path: {}", path);
            }
        }
    }

    // Access production UUID from build element
    if let Some(ref build_uuid) = model.build.production_uuid {
        println!("Build has production UUID: {}", build_uuid);
    }

    // Access production UUIDs from build items
    for item in &model.build.items {
        if let Some(ref item_uuid) = item.production_uuid {
            println!("Build item {} has production UUID: {}", 
                item.objectid, item_uuid);
        }
    }

    Ok(())
}
```

### Accessing Displacement Data

The library parses displacement extension data into accessible structures:

```rust
use lib3mf::Model;
use std::fs::File;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let file = File::open("displaced_model.3mf")?;
    let model = Model::from_reader(file)?;

    // Access displacement maps
    for disp_map in &model.resources.displacement_maps {
        println!("Displacement map {} at path: {}", disp_map.id, disp_map.path);
        println!("  Channel: {:?}, Filter: {:?}", disp_map.channel, disp_map.filter);
        println!("  Tile U: {:?}, Tile V: {:?}", disp_map.tilestyleu, disp_map.tilestylev);
    }

    // Access normalized vector groups
    for nvgroup in &model.resources.norm_vector_groups {
        println!("NormVectorGroup {} with {} vectors", nvgroup.id, nvgroup.vectors.len());
        for (i, vec) in nvgroup.vectors.iter().enumerate() {
            println!("  Vector {}: ({}, {}, {})", i, vec.x, vec.y, vec.z);
        }
    }

    // Access displacement coordinate groups
    for d2dgroup in &model.resources.disp2d_groups {
        println!("Disp2DGroup {} using displacement map {} and vectors {}",
            d2dgroup.id, d2dgroup.dispid, d2dgroup.nid);
        println!("  Height: {}, Offset: {}", d2dgroup.height, d2dgroup.offset);
        println!("  Coordinates: {} entries", d2dgroup.coords.len());
    }

    // Access advanced materials (Materials Extension)
    // Texture2D resources
    for texture in &model.resources.texture2d_resources {
        println!("Texture2D {}: path={}, type={}", 
            texture.id, texture.path, texture.contenttype);
    }

    // Texture2D groups with UV coordinates
    for tex_group in &model.resources.texture2d_groups {
        println!("Texture2DGroup {} references texture {}, {} coordinates",
            tex_group.id, tex_group.texid, tex_group.tex2coords.len());
    }

    // Composite materials
    for comp in &model.resources.composite_materials {
        println!("CompositeMaterials {} mixes base materials: {:?}",
            comp.id, comp.matindices);
    }

    // Multi-properties for layered material effects
    for multi in &model.resources.multi_properties {
        println!("MultiProperties {} layers property groups: {:?}",
            multi.id, multi.pids);
    }

    Ok(())
}
```

### Accessing Beam Lattice Data

The Beam Lattice Extension is fully supported with complete data extraction:

```rust
use lib3mf::Model;
use std::fs::File;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let file = File::open("lattice_model.3mf")?;
    let model = Model::from_reader(file)?;

    // Access beam lattice structures
    for obj in &model.resources.objects {
        if let Some(ref mesh) = obj.mesh {
            if let Some(ref beamset) = mesh.beamset {
                println!("BeamSet found!");
                println!("  Default radius: {} mm", beamset.radius);
                println!("  Minimum length: {} mm", beamset.min_length);
                println!("  Cap mode: {:?}", beamset.cap_mode); // Sphere or Butt
                println!("  Total beams: {}", beamset.beams.len());

                // Access individual beams
                for beam in &beamset.beams {
                    print!("  Beam: vertex {} -> vertex {}", beam.v1, beam.v2);
                    
                    // Check for per-beam radii
                    if let Some(r1) = beam.r1 {
                        print!(", r1={} mm", r1);
                    }
                    if let Some(r2) = beam.r2 {
                        print!(", r2={} mm", r2);
                    }
                    println!();
                }
            }
        }
    }

    Ok(())
}
```

### Advanced Materials

The Materials Extension now supports advanced features for full-color 3D printing:

```rust
use lib3mf::Model;
use std::fs::File;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let model = Model::from_reader(File::open("model.3mf")?)?;

    // Access Texture2D resources for applying images to models
    for texture in &model.resources.texture2d_resources {
        println!("Texture: {} ({})", texture.path, texture.contenttype);
        println!("  Tile: u={:?}, v={:?}", texture.tilestyleu, texture.tilestylev);
        println!("  Filter: {:?}", texture.filter);
    }

    // Access texture coordinate mappings
    for tex_group in &model.resources.texture2d_groups {
        for (i, coord) in tex_group.tex2coords.iter().enumerate() {
            println!("  Coord[{}]: u={}, v={}", i, coord.u, coord.v);
        }
    }

    // Access composite materials (mixing base materials)
    for comp in &model.resources.composite_materials {
        for composite in &comp.composites {
            println!("  Mix ratios: {:?}", composite.values);
        }
    }

    // Access multi-properties (layered materials)
    for multi in &model.resources.multi_properties {
        println!("  Blend methods: {:?}", multi.blendmethods);
        for m in &multi.multis {
            println!("    Indices: {:?}", m.pindices);
        }
    }

    Ok(())
}
```

### Extension Support

3MF files can require specific extensions beyond the core specification. You can control which extensions your application supports:

```rust
use lib3mf::{Model, ParserConfig, Extension};
use std::fs::File;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let file = File::open("model.3mf")?;
    
    // Configure which extensions you support
    let config = ParserConfig::new()
        .with_extension(Extension::Material)
        .with_extension(Extension::Production);
    
    // Parse with validation - will reject files requiring unsupported extensions
    let model = Model::from_reader_with_config(file, config)?;
    
    // Check what extensions are required by the file
    for ext in &model.required_extensions {
        println!("File requires extension: {}", ext.name());
    }
    
    Ok(())
}
```

The parser supports the following extensions:
- `Extension::Core` - Core 3MF specification (always supported)
- `Extension::Material` - Materials & Properties
- `Extension::Production` - Production information (UUIDs, paths)
- `Extension::Slice` - Slice data for layer-by-layer manufacturing
- `Extension::BeamLattice` - Beam and lattice structures
- `Extension::SecureContent` - Digital signatures and encryption
- `Extension::BooleanOperations` - Volumetric design
- `Extension::Displacement` - Surface displacement maps
- `Extension::Volumetric` - Volumetric data (voxel grids and implicit volumes)

### Polygon Clipping for Slice Self-Intersection Resolution

The library includes polygon clipping operations for resolving self-intersections in slice data:

```rust
use lib3mf::polygon_clipping::resolve_self_intersections;
use lib3mf::model::{SlicePolygon, SliceSegment, Vertex2D};

// Create a slice polygon
let vertices = vec![
    Vertex2D::new(0.0, 0.0),
    Vertex2D::new(100.0, 0.0),
    Vertex2D::new(100.0, 100.0),
    Vertex2D::new(0.0, 100.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));

// Resolve any self-intersections
let mut result_vertices = Vec::new();
let clean_polygons = resolve_self_intersections(
    &polygon,
    &vertices,
    &mut result_vertices
).expect("Failed to resolve self-intersections");
```

The polygon clipping module provides:
- **Self-intersection resolution** - Clean up invalid slice polygons
- **Union operations** - Combine overlapping slice regions
- **Intersection operations** - Find overlapping areas
- **Difference operations** - Subtract one region from another

Based on the Clipper2 library (successor to polyclipping used in C++ lib3mf).


### Custom Extension Support

You can register and handle custom/proprietary 3MF extensions with callback handlers:

```rust
use lib3mf::{Model, ParserConfig, CustomExtensionContext, CustomElementResult};
use std::sync::Arc;
use std::fs::File;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let file = File::open("model_with_custom_ext.3mf")?;
    
    // Register a custom extension with element and validation handlers
    let config = ParserConfig::new()
        .with_custom_extension_handlers(
            "http://example.com/myextension/2024/01",  // Namespace URI
            "MyExtension",                              // Human-readable name
            // Element handler - called when custom elements are encountered
            Arc::new(|ctx: &CustomExtensionContext| -> Result<CustomElementResult, String> {
                println!("Custom element: {}", ctx.element_name);
                println!("Attributes: {:?}", ctx.attributes);
                // Process the custom element here
                Ok(CustomElementResult::Handled)
            }),
            // Validation handler - called during model validation
            Arc::new(|model| -> Result<(), String> {
                // Add custom validation rules here
                if model.resources.objects.is_empty() {
                    return Err("Custom validation failed".to_string());
                }
                Ok(())
            })
        );
    
    let model = Model::from_reader_with_config(file, config)?;
    
    // Check custom extensions required by the file
    for namespace in &model.required_custom_extensions {
        println!("Custom extension: {}", namespace);
    }
    
    Ok(())
}
```

Custom extension features:
- **Element handlers** - Process custom XML elements from your extension
- **Validation callbacks** - Add custom validation rules for your extension data
- **Multiple extensions** - Register multiple custom extensions simultaneously
- **Error handling** - Custom handlers can return errors for invalid data

See `examples/custom_extension.rs` for a complete working example.

### Mesh Operations and Geometry Analysis

The library includes comprehensive mesh operation capabilities using the `parry3d` crate for accurate geometric computations:

```rust
use lib3mf::{mesh_ops, Model};
use std::fs::File;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let file = File::open("model.3mf")?;
    let model = Model::from_reader(file)?;

    for object in &model.resources.objects {
        if let Some(ref mesh) = object.mesh {
            // Compute signed volume (detects inverted meshes)
            let signed_volume = mesh_ops::compute_mesh_signed_volume(mesh)?;
            if signed_volume < 0.0 {
                println!("Warning: Object {} has inverted triangles", object.id);
            }

            // Compute absolute volume
            let volume = mesh_ops::compute_mesh_volume(mesh)?;
            println!("Volume: {} cubic units", volume);

            // Compute bounding box
            let (min, max) = mesh_ops::compute_mesh_aabb(mesh)?;
            println!("AABB: min={:?}, max={:?}", min, max);
        }
    }

    // Analyze build items with transformations
    for item in &model.build.items {
        let object = model.resources.objects
            .iter()
            .find(|obj| obj.id == item.objectid);

        if let Some(object) = object {
            if let Some(ref mesh) = object.mesh {
                // Compute transformed bounding box
                let (min, max) = mesh_ops::compute_transformed_aabb(
                    mesh,
                    item.transform.as_ref()
                )?;
                println!("Transformed AABB: min={:?}, max={:?}", min, max);
            }
        }
    }

    // Compute overall build volume
    if let Some((min, max)) = mesh_ops::compute_build_volume(&model) {
        println!("Build Volume: {:?} to {:?}", min, max);
    }

    Ok(())
}
```

**Mesh Operations Features:**
- **Volume Computation**: Signed volume for orientation detection, absolute volume for manufacturing
- **Bounding Boxes**: Axis-aligned bounding boxes (AABB) for spatial queries
- **Transformations**: Apply affine transforms and compute transformed bounding boxes
- **Build Volume**: Calculate overall build volume encompassing all build items
- **Validation Support**: Used internally for mesh orientation validation (N_XXX_0416, N_XXX_0421)

See `examples/mesh_analysis.rs` for a complete demonstration.

### Mesh Subdivision

The library provides mesh subdivision utilities for increasing vertex density, which is essential for displacement mapping and other mesh processing operations:

```rust
use lib3mf::{subdivide_simple, subdivide, Mesh, Vertex, Triangle, SubdivisionMethod, SubdivisionOptions};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Create a simple mesh
    let mut mesh = Mesh::new();
    mesh.vertices.push(Vertex::new(0.0, 0.0, 0.0));
    mesh.vertices.push(Vertex::new(10.0, 0.0, 0.0));
    mesh.vertices.push(Vertex::new(5.0, 10.0, 0.0));
    mesh.triangles.push(Triangle::new(0, 1, 2));

    // Simple midpoint subdivision
    let subdivided = subdivide_simple(&mesh, 2);
    println!("After 2 levels: {} triangles", subdivided.triangles.len()); // 16 triangles

    // Using subdivision options
    let options = SubdivisionOptions {
        method: SubdivisionMethod::Midpoint,
        levels: 1,
        preserve_boundaries: true,
        interpolate_uvs: true,
    };
    let subdivided = subdivide(&mesh, &options);

    // Note: Loop subdivision is planned but not yet implemented
    // Currently it produces the same result as Midpoint subdivision
    
    Ok(())
}
```

**Subdivision Features:**
- **Midpoint Subdivision**: Fast, simple subdivision that splits each triangle into 4
- **Loop Subdivision**: Planned feature for smoother surfaces (currently same as Midpoint)
- **Property Preservation**: Triangle properties (pid, p1, p2, p3) are preserved during subdivision
- **Shared Edge Optimization**: Vertices on shared edges are reused to avoid duplicates
- **Iterative Subdivision**: Apply subdivision multiple times for higher density

**Growth Rates:**
- Level 1: 4× triangles (1 → 4)
- Level 2: 16× triangles (1 → 16)
- Level 3: 64× triangles (1 → 64)
- Level 4: 256× triangles (1 → 256)

**Use Cases:**
- Displacement map rendering - increase vertex density for surface detail
- Mesh refinement - improve triangle quality before operations
- LOD generation - create multiple detail levels

See `examples/mesh_subdivision.rs` for a complete demonstration.

### Running Examples

The repository includes several comprehensive examples demonstrating different features:

#### Basic Parsing
```bash
cargo run --example parse_3mf
```

#### Extension Support
```bash
cargo run --example extension_support test_files/material/kinect_scan.3mf all
cargo run --example extension_support test_files/material/kinect_scan.3mf core-only
```

#### Export to Other Formats
Convert 3MF files to STL (for 3D printing):
```bash
cargo run --example export_to_stl test_files/core/box.3mf output.stl
```

Convert 3MF files to OBJ (for 3D modeling):
```bash
cargo run --example export_to_obj test_files/core/box.3mf output.obj
```

#### Validation and Error Handling
```bash
cargo run --example validation_errors test_files/core/box.3mf permissive
cargo run --example validation_errors test_files/core/box.3mf strict
```

#### Working with Build Items and Transformations
```bash
cargo run --example build_transformations test_files/core/box.3mf
```

#### Mesh Operations and Geometry Analysis
```bash
cargo run --example mesh_analysis test_files/core/box.3mf
```

#### Extracting Color Information
```bash
cargo run --example extract_colors test_files/material/kinect_scan.3mf
```

### 3MF Viewer Tool

The repository includes a comprehensive command-line viewer tool for analyzing and visualizing 3MF files:

```bash
# Navigate to the viewer directory
cd tools/viewer

# View a 3MF file with basic information
cargo run --release -- ../../test_files/core/box.3mf

# View with detailed mesh information
cargo run --release -- ../../test_files/material/kinect_scan.3mf --detailed

# Export a wireframe preview image
cargo run --release -- ../../test_files/core/cylinder.3mf --export-preview preview.png

# Show all vertices and triangles (very verbose)
cargo run --release -- ../../test_files/core/box.3mf --show-all
```

The viewer displays:
- Model information (unit, namespace, extensions)
- Metadata entries
- Object and mesh details (vertex/triangle counts, bounding boxes)
- Materials and color groups
- Build items and transformations
- Wireframe preview export capability

See `tools/viewer/README.md` for complete documentation.

## Architecture

The library is organized into several modules:

- **`model`** - Data structures representing 3MF models (vertices, triangles, meshes, objects, etc.)
- **`opc`** - Open Packaging Conventions (OPC) handling for ZIP-based containers
- **`parser`** - XML parsing for 3D model files
- **`error`** - Comprehensive error types with detailed messages

## 3MF Format Support

This implementation currently supports:

- **Core 3MF Specification**
  - Model structure (resources, build, metadata)
  - Mesh geometry (vertices, triangles)
  - Object definitions
  - Build items with transformations
  - Basic materials and colors

- **Materials Extension**
  - ✅ Color groups (m:colorgroup)
  - ✅ Per-triangle material references (pid attributes)
  - ✅ Base materials with display colors
  - ✅ Texture2D resources with image paths and content types
  - ✅ Texture2DGroup with UV texture coordinates
  - ✅ Composite materials mixing base materials in defined ratios
  - ✅ Multi-properties for layering and blending property groups

- **Displacement Extension**
  - Displacement2D resources (displacement maps with PNG textures)
  - NormVectorGroup (normalized displacement vectors)
  - Disp2DGroup (displacement coordinate groups)
  - Displacement coordinates (u, v, n, f values)
  - Texture filtering and tiling modes
  - Surface displacement data structures

### Extension Support and Validation

The parser supports **conditional extension validation**, allowing consumers to specify which 3MF extensions they support. When a 3MF file declares required extensions via the `requiredextensions` attribute, the parser validates that all required extensions are supported before processing the file.

**Supported Extensions:**

- **Core Specification** - Fully supported (always enabled)
-**Materials Extension** - Fully supported with color groups, base materials, Texture2D, composite materials, and multi-properties
-**Production Extension** - UUID and path extraction from objects, build, and build items
-**Slice Extension** - Fully supported with slice stacks and polygons  
-**Beam Lattice Extension** - Fully supported with BeamSet, Beam structures, radii, and cap modes
-**Secure Content Extension** - Recognized and validated
-**Boolean Operations Extension** - Recognized and validated
-**Displacement Extension** - Fully supported with displacement maps, normal vectors, and coordinate groups

**Validation Behavior:**

By default, `Model::from_reader()` accepts files with any known extension for backward compatibility. Use `Model::from_reader_with_config()` to enforce specific extension requirements:

```rust
// Only accept core files (no extensions)
let config = ParserConfig::new();

// Accept core + materials
let config = ParserConfig::new().with_extension(Extension::Material);

// Accept all known extensions
let config = ParserConfig::with_all_extensions();
```

All extensions support full data extraction and are production-ready.

### Future Enhancements

Potential future additions could include:
- Extended conformance test coverage
- Additional export formats
- Performance optimizations for very large files
- Streaming support for additional extensions

## Testing

The library includes comprehensive unit, integration, and conformance tests:

```bash
# Run all tests
cargo test

# Run with output
cargo test -- --nocapture

# Run linter
cargo clippy -- -D warnings

# Run official 3MF conformance tests
cargo test --test conformance_tests summary -- --ignored --nocapture

# Run a specific conformance suite
cargo test --test conformance_tests suite3_core -- --nocapture
```

### Continuous Integration

The repository uses GitHub Actions for continuous integration with optimized parallel execution:

- **Basic Tests Job**: Runs standard library and integration tests as a fast preliminary check
- **Security Audit**: Automated daily dependency vulnerability scanning using `cargo-audit`
- **Conformance Test Matrix**: Runs all 11 conformance test suites in parallel for faster feedback
  - suite1_core_slice_prod
  - suite2_core_prod_matl
  - suite3_core
  - suite4_core_slice
  - suite5_core_prod
  - suite6_core_matl
  - suite7_beam
  - suite8_secure
  - suite9_core_ext
  - suite10_boolean
  - suite11_displacement
- **Conformance Summary Job**: Generates an overall conformance report after all suites complete

This parallel approach significantly reduces CI execution time compared to running suites sequentially.

### Conformance Testing

This library has been validated against the official [3MF Consortium test suites](https://github.com/3MFConsortium/test_suites), which include over 2,200 test cases covering all 3MF specifications and extensions.

**Current Conformance Results:**
- **100% Positive Test Compliance**: All 1,719 valid 3MF files parse successfully
-**Negative Test Compliance**: Estimated ~90% (requires test_suites to measure precisely)
- 📊 **Overall Conformance**: Estimated ~97.6% (improved from 77.4% baseline)

**Note:** Precise negative test metrics require the test_suites repository. Clone with:
```bash
git clone --depth 1 https://github.com/3MFConsortium/test_suites.git
cargo run --example analyze_negative_tests
```

**Key Validation Improvements:**
- **Strict color format validation** - Rejects invalid hexadecimal color values
-**Proper resource ID namespaces** - Objects and property resources have separate ID spaces
- ✅ Duplicate metadata names - Ensures metadata uniqueness
- ✅ Duplicate resource IDs - Validates property group ID uniqueness
- ✅ Invalid XML structure - Rejects malformed models
- ✅ Comprehensive material property validation
- ✅ Triangle property reference validation

The parser successfully handles files using all 3MF extensions including:
- Core Specification (1.4.0)
- Materials & Properties Extension (1.2.1)
- Production Extension (1.2.0)
- Slice Extension (1.0.2)
- Beam Lattice Extension (1.2.0)
- Secure Content Extension (1.0.2) - ⚠️ **Test-only decryption + metadata extraction**
- Boolean Operations Extension (1.1.1)
- Displacement Extension (1.0.0)

**Important Security Note**: The Secure Content extension provides:
1. **Test-only decryption** using Suite 8 test keys for conformance validation
2. **Complete keystore metadata extraction** for production applications

Files encrypted with Suite 8 test keys (consumerid="test3mf01") are automatically decrypted during parsing. For production applications, access all encryption metadata (consumers, encryption parameters, access rights) and implement decryption using external cryptographic libraries. **Never use embedded test keys in production.**

See [CONFORMANCE_REPORT.md](CONFORMANCE_REPORT.md) for detailed test results and analysis.

## Fuzzing

The library includes comprehensive fuzzing infrastructure using cargo-fuzz and libFuzzer to discover bugs, crashes, and security vulnerabilities.

### Fuzzing Targets

- **fuzz_parse_3mf**: Tests complete 3MF parsing pipeline (ZIP + XML + model construction)
- **fuzz_parse_with_extensions**: Tests parsing with all 7 extensions enabled
- **fuzz_xml_parser**: Tests XML parser robustness with malformed inputs
- **fuzz_mesh_validation**: Tests mesh operations (volume, AABB, slicing)

### Running Fuzzers Locally

Fuzzing requires Rust nightly:

```bash
# Install nightly and cargo-fuzz
rustup install nightly
rustup default nightly
cargo install cargo-fuzz

# Run a fuzzer for 5 minutes
cargo fuzz run fuzz_parse_3mf -- -max_total_time=300

# Run all fuzzers
for target in fuzz_parse_3mf fuzz_parse_with_extensions fuzz_xml_parser fuzz_mesh_validation; do
    cargo fuzz run $target -- -max_total_time=60
done
```

### Continuous Fuzzing

Fuzzing runs automatically via GitHub Actions:
- **Nightly**: Every day at 2 AM UTC (5 minutes per target)
- **Extended**: 1-hour sessions for main parsers
- **PR checks**: On fuzzing infrastructure changes

**Automatic Bug Reporting**: When crashes are discovered during nightly fuzzing, the CI automatically:
- Analyzes the crash (type, severity, stack trace)
- Creates a detailed GitHub issue with reproduction steps
- Provides initial investigation guidance
- Prevents duplicate issues for the same crash

See [fuzz/README.md](fuzz/README.md) for detailed fuzzing documentation and [docs/FUZZING_AUTOMATION.md](docs/FUZZING_AUTOMATION.md) for details on the automated bug reporting system.

## Performance

The library is optimized for parsing large 3MF files efficiently:

- **Linear scaling**: Performance scales linearly with file size
- **Memory efficient**: Streaming XML parsing with pre-allocated buffers
- **Benchmarked**: Comprehensive benchmark suite using criterion.rs

```bash
# Run performance benchmarks
cargo bench

# Run specific benchmark group
cargo bench -- parse_large
```

**Typical Performance:**
- Small files (1,000 vertices): ~1 ms
- Medium files (10,000 vertices): ~7 ms
- Large files (100,000 vertices): ~70 ms

## Safety

This library is designed with safety in mind:

- **No unsafe code** - The entire codebase forbids unsafe code
- **Type safety** - Leverages Rust's type system for correctness
- **Memory safety** - All memory management is handled by Rust's ownership system
- **Error handling** - Comprehensive error types using `thiserror`
- **Security audits** - Automated dependency vulnerability scanning via CI

## Dependencies

The library uses minimal, well-maintained dependencies:

- `zip` - For reading ZIP archives (3MF container format)
- `quick-xml` - For parsing XML model files
- `thiserror` - For error handling
- `parry3d` - For triangle mesh geometric operations (volume, bounding boxes)
- `nalgebra` - Linear algebra library (used by parry3d)

All dependencies are regularly monitored for security vulnerabilities using:
- **Automated security audits**: Daily CI scans using `cargo-audit` against the RustSec Advisory Database
- **Manual review**: Dependencies are updated and reviewed regularly

To run a security audit locally:
```bash
cargo install cargo-audit
cargo audit
```

## Contributing

Contributions are welcome! Please feel free to submit issues or pull requests.

## License

This project is licensed under the MIT License. See [LICENSE](LICENSE) for details.

## References

- [3MF Specification]https://3mf.io/specification/
- [3MF Consortium]https://3mf.io/
- [lib3mf (Official C++ implementation)]https://github.com/3MFConsortium/lib3mf

## Acknowledgments

This implementation is inspired by the official lib3mf library but is a complete rewrite in Rust with a focus on safety and simplicity.