use lib3mf_core::model::{CapMode, ClippingMode, Geometry, ResourceId};
use lib3mf_core::parser::parse_model;
use std::io::Cursor;
#[test]
fn test_slice_and_beamlattice_coexistence() -> anyhow::Result<()> {
let xml = r##"<?xml version="1.0" encoding="UTF-8"?>
<model unit="millimeter" xml:lang="en-US"
xmlns="http://schemas.microsoft.com/3dmanufacturing/2013/01/3dmodel"
xmlns:s="http://schemas.microsoft.com/3dmanufacturing/slice/2015/07">
<resources>
<!-- SliceStack resource -->
<slicestack id="10" zbottom="0.0">
<slice ztop="1.0">
<vertices>
<vertex x="0" y="0" />
<vertex x="10" y="0" />
<vertex x="10" y="10" />
<vertex x="0" y="10" />
</vertices>
<polygon start="0">
<segment v2="1" />
<segment v2="2" />
<segment v2="3" />
<segment v2="0" />
</polygon>
</slice>
</slicestack>
<!-- Object with mesh + beam lattice -->
<object id="1" type="model">
<mesh>
<vertices>
<vertex x="0" y="0" z="0" />
<vertex x="10" y="0" z="0" />
<vertex x="0" y="10" z="0" />
<vertex x="0" y="0" z="10" />
</vertices>
<triangles>
<triangle v1="0" v2="1" v3="2" />
<triangle v1="0" v2="1" v3="3" />
</triangles>
<beamlattice minlength="0.1" clippingmode="inside">
<beams>
<beam v1="0" v2="1" r1="1.0" r2="1.0" cap="hemisphere" />
<beam v1="0" v2="2" r1="1.5" />
</beams>
</beamlattice>
</mesh>
</object>
<!-- Object referencing SliceStack -->
<object id="2" type="model" slicestackid="10" />
</resources>
<build>
<item objectid="1" />
<item objectid="2" />
</build>
</model>"##;
let model = parse_model(Cursor::new(xml))?;
let slice_stack = model
.resources
.get_slice_stack(ResourceId(10))
.expect("SliceStack 10 should exist");
assert_eq!(
slice_stack.z_bottom, 0.0,
"SliceStack z_bottom should be 0.0"
);
assert_eq!(
slice_stack.slices.len(),
1,
"SliceStack should have 1 slice"
);
assert_eq!(
slice_stack.slices[0].vertices.len(),
4,
"Slice should have 4 vertices"
);
let obj1 = model
.resources
.get_object(ResourceId(1))
.expect("Object 1 should exist");
if let Geometry::Mesh(mesh) = &obj1.geometry {
assert_eq!(mesh.vertices.len(), 4, "Mesh should have 4 vertices");
assert_eq!(mesh.triangles.len(), 2, "Mesh should have 2 triangles");
let lattice = mesh
.beam_lattice
.as_ref()
.expect("Beam lattice should exist");
assert_eq!(lattice.beams.len(), 2, "Should have 2 beams");
assert_eq!(
lattice.clipping_mode,
ClippingMode::Inside,
"Clipping mode should be Inside"
);
assert_eq!(
lattice.beams[0].cap_mode,
CapMode::Hemisphere,
"First beam should have hemisphere cap"
);
} else {
panic!("Object 1 should have Mesh geometry");
}
let obj2 = model
.resources
.get_object(ResourceId(2))
.expect("Object 2 should exist");
if let Geometry::SliceStack(stack_id) = obj2.geometry {
assert_eq!(
stack_id,
ResourceId(10),
"Object 2 should reference SliceStack 10"
);
} else {
panic!("Object 2 should have SliceStack geometry");
}
assert_eq!(model.build.items.len(), 2, "Build should have 2 items");
Ok(())
}
#[test]
fn test_volumetric_and_materials() -> anyhow::Result<()> {
let xml = r##"<?xml version="1.0" encoding="UTF-8"?>
<model unit="millimeter" xml:lang="en-US" xmlns="http://schemas.microsoft.com/3dmanufacturing/2013/01/3dmodel">
<resources>
<!-- Base materials -->
<basematerials id="5">
<base name="Red" displaycolor="#FF0000" />
<base name="Blue" displaycolor="#0000FF" />
</basematerials>
<!-- Volumetric stack -->
<volumetricstack id="10">
<layer z="0.0" path="/3D/layer0.png" />
<layer z="1.0" path="/3D/layer1.png" />
</volumetricstack>
<!-- Object with both volumetric and material references -->
<object id="1" type="model" volumetricstackid="10" pid="5" pindex="0" />
</resources>
<build>
<item objectid="1" />
</build>
</model>"##;
let model = parse_model(Cursor::new(xml))?;
let materials = model
.resources
.get_base_materials(ResourceId(5))
.expect("BaseMaterials 5 should exist");
assert_eq!(materials.materials.len(), 2, "Should have 2 base materials");
assert_eq!(materials.materials[0].name, "Red", "First material is Red");
assert_eq!(
materials.materials[1].name, "Blue",
"Second material is Blue"
);
let vol_stack = model
.resources
.get_volumetric_stack(ResourceId(10))
.expect("VolumetricStack 10 should exist");
assert_eq!(vol_stack.layers.len(), 2, "Should have 2 layers");
assert_eq!(vol_stack.layers[0].z_height, 0.0, "First layer at z=0.0");
assert_eq!(vol_stack.layers[1].z_height, 1.0, "Second layer at z=1.0");
let obj = model
.resources
.get_object(ResourceId(1))
.expect("Object 1 should exist");
if let Geometry::VolumetricStack(stack_id) = obj.geometry {
assert_eq!(
stack_id,
ResourceId(10),
"Object should reference VolumetricStack 10"
);
} else {
panic!("Object should have VolumetricStack geometry");
}
assert_eq!(
obj.pid,
Some(ResourceId(5)),
"Object should reference BaseMaterials 5"
);
assert_eq!(obj.pindex, Some(0), "Object pindex should be 0");
Ok(())
}
#[test]
fn test_all_extensions_combined() -> anyhow::Result<()> {
let xml = r##"<?xml version="1.0" encoding="UTF-8"?>
<model unit="millimeter" xml:lang="en-US"
xmlns="http://schemas.microsoft.com/3dmanufacturing/2013/01/3dmodel"
xmlns:s="http://schemas.microsoft.com/3dmanufacturing/slice/2015/07">
<resources>
<!-- Base materials -->
<basematerials id="1">
<base name="Red" displaycolor="#FF0000" />
</basematerials>
<!-- Color group -->
<colorgroup id="2">
<color color="#0000FFFF" />
<color color="#00FF00FF" />
</colorgroup>
<!-- Object with mesh and beam lattice -->
<object id="3" type="model" pid="1" pindex="0">
<mesh>
<vertices>
<vertex x="0" y="0" z="0" />
<vertex x="5" y="0" z="0" />
<vertex x="0" y="5" z="0" />
</vertices>
<triangles>
<triangle v1="0" v2="1" v3="2" />
</triangles>
<beamlattice minlength="0.5">
<beams>
<beam v1="0" v2="1" r1="0.5" />
</beams>
</beamlattice>
</mesh>
</object>
<!-- SliceStack -->
<slicestack id="10" zbottom="0.0">
<slice ztop="1.0">
<vertices>
<vertex x="0" y="0" />
<vertex x="5" y="5" />
</vertices>
<polygon start="0">
<segment v2="1" />
<segment v2="0" />
</polygon>
</slice>
</slicestack>
<!-- Object referencing SliceStack -->
<object id="11" type="model" slicestackid="10" pid="2" pindex="1" />
<!-- VolumetricStack -->
<volumetricstack id="20">
<layer z="0.5" path="/3D/vol.png" />
</volumetricstack>
<!-- Object referencing VolumetricStack -->
<object id="21" type="model" volumetricstackid="20" />
</resources>
<build>
<item objectid="3" />
<item objectid="11" />
<item objectid="21" />
</build>
</model>"##;
let model = parse_model(Cursor::new(xml))?;
assert!(
model.resources.get_base_materials(ResourceId(1)).is_some(),
"BaseMaterials should exist"
);
assert!(
model.resources.get_color_group(ResourceId(2)).is_some(),
"ColorGroup should exist"
);
assert!(
model.resources.get_object(ResourceId(3)).is_some(),
"Object 3 with beam lattice should exist"
);
assert!(
model.resources.get_slice_stack(ResourceId(10)).is_some(),
"SliceStack should exist"
);
assert!(
model.resources.get_object(ResourceId(11)).is_some(),
"Object 11 with slice reference should exist"
);
assert!(
model
.resources
.get_volumetric_stack(ResourceId(20))
.is_some(),
"VolumetricStack should exist"
);
assert!(
model.resources.get_object(ResourceId(21)).is_some(),
"Object 21 with volumetric reference should exist"
);
let obj3 = model.resources.get_object(ResourceId(3)).unwrap();
if let Geometry::Mesh(mesh) = &obj3.geometry {
assert!(
mesh.beam_lattice.is_some(),
"Object 3 should have beam lattice"
);
assert_eq!(
mesh.beam_lattice.as_ref().unwrap().beams.len(),
1,
"Should have 1 beam"
);
} else {
panic!("Object 3 should have Mesh geometry");
}
assert_eq!(model.build.items.len(), 3, "Build should have 3 items");
assert_eq!(
model.build.items[0].object_id,
ResourceId(3),
"First build item is object 3"
);
assert_eq!(
model.build.items[1].object_id,
ResourceId(11),
"Second build item is object 11"
);
assert_eq!(
model.build.items[2].object_id,
ResourceId(21),
"Third build item is object 21"
);
Ok(())
}
#[test]
fn test_extension_roundtrip() -> anyhow::Result<()> {
let mut model = lib3mf_core::model::Model::default();
model.unit = lib3mf_core::model::Unit::Millimeter;
let materials = lib3mf_core::model::BaseMaterialsGroup {
id: ResourceId(1),
materials: vec![
lib3mf_core::model::BaseMaterial {
name: "Red".to_string(),
display_color: lib3mf_core::model::Color::new(255, 0, 0, 255),
},
lib3mf_core::model::BaseMaterial {
name: "Green".to_string(),
display_color: lib3mf_core::model::Color::new(0, 255, 0, 255),
},
],
};
model.resources.add_base_materials(materials)?;
let mesh = lib3mf_core::model::Mesh {
vertices: vec![
lib3mf_core::model::Vertex {
x: 0.0,
y: 0.0,
z: 0.0,
},
lib3mf_core::model::Vertex {
x: 10.0,
y: 0.0,
z: 0.0,
},
lib3mf_core::model::Vertex {
x: 0.0,
y: 10.0,
z: 0.0,
},
],
triangles: vec![lib3mf_core::model::Triangle {
v1: 0,
v2: 1,
v3: 2,
pid: None,
p1: None,
p2: None,
p3: None,
}],
beam_lattice: None,
};
let object = lib3mf_core::model::Object {
id: ResourceId(2),
name: None,
part_number: None,
uuid: None,
object_type: lib3mf_core::model::ObjectType::Model,
thumbnail: None,
pid: Some(ResourceId(1)),
pindex: Some(0),
geometry: Geometry::Mesh(mesh),
};
model.resources.add_object(object)?;
model.build.items.push(lib3mf_core::model::BuildItem {
object_id: ResourceId(2),
uuid: None,
path: None,
part_number: None,
transform: glam::Mat4::IDENTITY,
printable: None,
});
let materials = model
.resources
.get_base_materials(ResourceId(1))
.expect("BaseMaterials should exist in model");
assert_eq!(materials.materials.len(), 2, "Should have 2 materials");
assert_eq!(materials.materials[0].name, "Red", "First material is Red");
assert_eq!(
materials.materials[1].name, "Green",
"Second material is Green"
);
let obj = model
.resources
.get_object(ResourceId(2))
.expect("Object should exist in model");
assert_eq!(
obj.pid,
Some(ResourceId(1)),
"Object should reference materials"
);
assert_eq!(obj.pindex, Some(0), "Material index should be 0");
if let Geometry::Mesh(mesh) = &obj.geometry {
assert_eq!(mesh.vertices.len(), 3, "Mesh should have 3 vertices");
assert_eq!(mesh.triangles.len(), 1, "Mesh should have 1 triangle");
} else {
panic!("Object should have Mesh geometry");
}
assert_eq!(model.build.items.len(), 1, "Build should have 1 item");
assert_eq!(
model.build.items[0].object_id,
ResourceId(2),
"Build item references correct object"
);
Ok(())
}
#[test]
fn test_resource_id_namespace() -> anyhow::Result<()> {
let xml = r##"<?xml version="1.0" encoding="UTF-8"?>
<model unit="millimeter" xmlns="http://schemas.microsoft.com/3dmanufacturing/2013/01/3dmodel"
xmlns:s="http://schemas.microsoft.com/3dmanufacturing/slice/2015/07">
<resources>
<!-- Use sequential IDs across different resource types -->
<basematerials id="1">
<base name="Material1" displaycolor="#FF0000" />
</basematerials>
<slicestack id="2" zbottom="0.0">
<slice ztop="1.0">
<vertices>
<vertex x="0" y="0" />
<vertex x="1" y="1" />
</vertices>
<polygon start="0">
<segment v2="1" />
<segment v2="0" />
</polygon>
</slice>
</slicestack>
<object id="3" type="model">
<mesh>
<vertices>
<vertex x="0" y="0" z="0" />
<vertex x="1" y="0" z="0" />
<vertex x="0" y="1" z="0" />
</vertices>
<triangles>
<triangle v1="0" v2="1" v3="2" />
</triangles>
</mesh>
</object>
<colorgroup id="4">
<color color="#00FF00FF" />
</colorgroup>
<volumetricstack id="5">
<layer z="0.5" path="/3D/vol.png" />
</volumetricstack>
</resources>
<build>
<item objectid="3" />
</build>
</model>"##;
let model = parse_model(Cursor::new(xml))?;
assert!(
model.resources.get_base_materials(ResourceId(1)).is_some(),
"BaseMaterials with ID 1 should be accessible"
);
assert!(
model.resources.get_slice_stack(ResourceId(2)).is_some(),
"SliceStack with ID 2 should be accessible"
);
assert!(
model.resources.get_object(ResourceId(3)).is_some(),
"Object with ID 3 should be accessible"
);
assert!(
model.resources.get_color_group(ResourceId(4)).is_some(),
"ColorGroup with ID 4 should be accessible"
);
assert!(
model
.resources
.get_volumetric_stack(ResourceId(5))
.is_some(),
"VolumetricStack with ID 5 should be accessible"
);
assert!(
model.resources.get_object(ResourceId(1)).is_none(),
"BaseMaterials should not be accessible as Object"
);
assert!(
model.resources.get_slice_stack(ResourceId(3)).is_none(),
"Object should not be accessible as SliceStack"
);
assert!(
model.resources.get_base_materials(ResourceId(5)).is_none(),
"VolumetricStack should not be accessible as BaseMaterials"
);
Ok(())
}
#[test]
fn test_empty_model_with_extension_namespaces() -> anyhow::Result<()> {
let xml = r##"<?xml version="1.0" encoding="UTF-8"?>
<model unit="millimeter" xml:lang="en-US"
xmlns="http://schemas.microsoft.com/3dmanufacturing/2013/01/3dmodel"
xmlns:s="http://schemas.microsoft.com/3dmanufacturing/slice/2015/07"
xmlns:v="http://schemas.microsoft.com/3dmanufacturing/volumetric/2017/05">
<resources>
<!-- Simple object with no extensions -->
<object id="1" type="model">
<mesh>
<vertices>
<vertex x="0" y="0" z="0" />
<vertex x="1" y="0" z="0" />
<vertex x="0" y="1" z="0" />
</vertices>
<triangles>
<triangle v1="0" v2="1" v3="2" />
</triangles>
</mesh>
</object>
</resources>
<build>
<item objectid="1" />
</build>
</model>"##;
let model = parse_model(Cursor::new(xml))?;
let obj = model
.resources
.get_object(ResourceId(1))
.expect("Object should exist");
if let Geometry::Mesh(mesh) = &obj.geometry {
assert_eq!(mesh.vertices.len(), 3, "Should have 3 vertices");
assert_eq!(mesh.triangles.len(), 1, "Should have 1 triangle");
assert!(mesh.beam_lattice.is_none(), "Should have no beam lattice");
} else {
panic!("Object should have Mesh geometry");
}
assert_eq!(model.build.items.len(), 1, "Should have 1 build item");
Ok(())
}