use crate::error::Result;
use crate::model::{BooleanOperationType, Geometry, Model, Unit};
use crate::writer::displacement_writer::{write_displacement_2d, write_displacement_mesh};
use crate::writer::mesh_writer::write_mesh;
use crate::writer::slice_writer;
use crate::writer::volumetric_writer;
use crate::writer::xml_writer::XmlWriter;
use std::io::Write;
use std::collections::HashMap;
fn format_transform_matrix(mat: &glam::Mat4) -> String {
format!(
"{} {} {} {} {} {} {} {} {} {} {} {}",
mat.x_axis.x,
mat.x_axis.y,
mat.x_axis.z,
mat.y_axis.x,
mat.y_axis.y,
mat.y_axis.z,
mat.z_axis.x,
mat.z_axis.y,
mat.z_axis.z,
mat.w_axis.x,
mat.w_axis.y,
mat.w_axis.z
)
}
impl Model {
pub fn write_xml<W: Write>(
&self,
writer: W,
thumbnail_relationships: Option<&HashMap<String, String>>,
) -> Result<()> {
let mut xml = XmlWriter::new(writer);
xml.write_declaration()?;
let mut root = xml
.start_element("model")
.attr("unit", self.unit_str())
.attr("xml:lang", self.language.as_deref().unwrap_or("en-US"))
.attr(
"xmlns",
"http://schemas.microsoft.com/3dmanufacturing/core/2015/02",
)
.attr(
"xmlns:m",
"http://schemas.microsoft.com/3dmanufacturing/material/2015/02",
)
.attr(
"xmlns:p",
"http://schemas.microsoft.com/3dmanufacturing/production/2015/06",
)
.attr(
"xmlns:b",
"http://schemas.3mf.io/3dmanufacturing/booleanoperations/2023/07",
)
.attr(
"xmlns:d",
"http://schemas.microsoft.com/3dmanufacturing/displacement/2024/01",
)
.attr(
"xmlns:bl",
"http://schemas.microsoft.com/3dmanufacturing/beamlattice/2017/02",
)
.attr(
"xmlns:s",
"http://schemas.microsoft.com/3dmanufacturing/slice/2015/07",
)
.attr(
"xmlns:v",
"http://schemas.microsoft.com/3dmanufacturing/volumetric/2018/11",
);
let mut extra_ns: Vec<(&String, &String)> = self.extra_namespaces.iter().collect();
extra_ns.sort_by_key(|(k, _)| k.as_str());
let extra_ns_owned: Vec<(String, String)> = extra_ns
.into_iter()
.map(|(k, v)| (format!("xmlns:{}", k), v.clone()))
.collect();
for (attr_name, uri) in &extra_ns_owned {
root = root.attr(attr_name, uri.as_str());
}
root.write_start()?;
for (key, value) in &self.metadata {
xml.start_element("metadata")
.attr("name", key)
.write_start()?;
xml.write_text(value)?;
xml.end_element("metadata")?;
}
xml.start_element("resources").write_start()?;
for color_group in self.resources.iter_color_groups() {
xml.start_element("colorgroup")
.attr("id", &color_group.id.0.to_string())
.write_start()?;
for color in &color_group.colors {
xml.start_element("color")
.attr("color", &color.to_hex())
.write_empty()?;
}
xml.end_element("colorgroup")?;
}
for base_materials in self.resources.iter_base_materials() {
xml.start_element("m:basematerials")
.attr("id", &base_materials.id.0.to_string())
.write_start()?;
for material in &base_materials.materials {
xml.start_element("m:base")
.attr("name", &material.name)
.attr("displaycolor", &material.display_color.to_hex())
.write_empty()?;
}
xml.end_element("m:basematerials")?;
}
for texture_group in self.resources.iter_textures() {
xml.start_element("m:texture2dgroup")
.attr("id", &texture_group.id.0.to_string())
.attr("texid", &texture_group.texture_id.0.to_string())
.write_start()?;
for coord in &texture_group.coords {
xml.start_element("m:tex2coord")
.attr("u", &coord.u.to_string())
.attr("v", &coord.v.to_string())
.write_empty()?;
}
xml.end_element("m:texture2dgroup")?;
}
for composite in self.resources.iter_composite_materials() {
xml.start_element("m:compositematerials")
.attr("id", &composite.id.0.to_string())
.attr("matid", &composite.base_material_id.0.to_string())
.write_start()?;
for comp in &composite.composites {
xml.start_element("m:composite")
.attr(
"values",
&comp
.values
.iter()
.map(|v| v.to_string())
.collect::<Vec<_>>()
.join(" "),
)
.write_empty()?;
}
xml.end_element("m:compositematerials")?;
}
for multi_props in self.resources.iter_multi_properties() {
xml.start_element("m:multiproperties")
.attr("id", &multi_props.id.0.to_string())
.attr(
"pids",
&multi_props
.pids
.iter()
.map(|id| id.0.to_string())
.collect::<Vec<_>>()
.join(" "),
)
.write_start()?;
for multi in &multi_props.multis {
xml.start_element("m:multi")
.attr(
"pindices",
&multi
.pindices
.iter()
.map(|idx: &u32| idx.to_string())
.collect::<Vec<_>>()
.join(" "),
)
.write_empty()?;
}
xml.end_element("m:multiproperties")?;
}
for displacement_2d in self.resources.iter_displacement_2d() {
write_displacement_2d(&mut xml, displacement_2d)?;
}
let slice_opts = slice_writer::SliceWriteOptions::default();
for stack in self.resources.iter_slice_stacks() {
slice_writer::write_slice_stack(&mut xml, stack, &slice_opts)?;
}
for stack in self.resources.iter_volumetric_stacks() {
volumetric_writer::write_volumetric_stack(&mut xml, stack)?;
}
for obj in self.resources.iter_objects() {
match &obj.geometry {
Geometry::BooleanShape(bs) => {
let mut bool_elem = xml
.start_element("b:booleanshape")
.attr("id", &obj.id.0.to_string())
.attr("objectid", &bs.base_object_id.0.to_string());
if bs.base_transform != glam::Mat4::IDENTITY {
bool_elem = bool_elem
.attr("transform", &format_transform_matrix(&bs.base_transform));
}
if let Some(path) = &bs.base_path {
bool_elem = bool_elem.attr("p:path", path);
}
bool_elem.write_start()?;
for op in &bs.operations {
let op_type_str = match op.operation_type {
BooleanOperationType::Union => "union",
BooleanOperationType::Difference => "difference",
BooleanOperationType::Intersection => "intersection",
};
let mut op_elem = xml
.start_element("b:boolean")
.attr("objectid", &op.object_id.0.to_string())
.attr("operation", op_type_str);
if op.transform != glam::Mat4::IDENTITY {
op_elem =
op_elem.attr("transform", &format_transform_matrix(&op.transform));
}
if let Some(path) = &op.path {
op_elem = op_elem.attr("p:path", path);
}
op_elem.write_empty()?;
}
xml.end_element("b:booleanshape")?;
}
_ => {
let mut obj_elem = xml
.start_element("object")
.attr("id", &obj.id.0.to_string())
.attr("type", &obj.object_type.to_string());
if let Geometry::SliceStack(ssid) = &obj.geometry {
obj_elem = obj_elem.attr("slicestackid", &ssid.0.to_string());
}
if let Geometry::VolumetricStack(vsid) = &obj.geometry {
obj_elem = obj_elem.attr("volumetricstackid", &vsid.0.to_string());
}
if let Some(pid) = obj.pid {
obj_elem = obj_elem.attr("pid", &pid.0.to_string());
}
if let Some(pindex) = obj.pindex {
obj_elem = obj_elem.attr("pindex", &pindex.to_string());
}
if let Some(pid) = obj.part_number.as_ref() {
obj_elem = obj_elem.attr("partnumber", pid);
}
if let Some(uuid) = obj.uuid.as_ref() {
obj_elem = obj_elem.attr("p:UUID", &uuid.to_string());
}
if let Some(name) = obj.name.as_ref() {
obj_elem = obj_elem.attr("name", name);
}
if let Some(thumb_path) = obj.thumbnail.as_ref()
&& let Some(rels) = thumbnail_relationships
{
let lookup_key = if thumb_path.starts_with('/') {
thumb_path.clone()
} else {
format!("/{}", thumb_path)
};
if let Some(rel_id) = rels.get(&lookup_key) {
obj_elem = obj_elem.attr("thumbnail", rel_id);
}
}
obj_elem.write_start()?;
match &obj.geometry {
Geometry::Mesh(mesh) => write_mesh(&mut xml, mesh)?,
Geometry::Components(comps) => {
xml.start_element("components").write_start()?;
for c in &comps.components {
let mut comp = xml
.start_element("component")
.attr("objectid", &c.object_id.0.to_string());
if let Some(path) = c.path.as_ref() {
comp = comp.attr("p:path", path);
}
if let Some(uuid) = c.uuid.as_ref() {
comp = comp.attr("p:UUID", &uuid.to_string());
}
if c.transform != glam::Mat4::IDENTITY {
comp = comp
.attr("transform", &format_transform_matrix(&c.transform));
}
comp.write_empty()?;
}
xml.end_element("components")?;
}
Geometry::SliceStack(_) => {
}
Geometry::VolumetricStack(_) => {
}
Geometry::BooleanShape(_) => {
unreachable!("BooleanShape handled in outer match")
}
Geometry::DisplacementMesh(mesh) => {
write_displacement_mesh(&mut xml, mesh)?;
}
}
xml.end_element("object")?;
}
}
}
xml.end_element("resources")?;
xml.start_element("build").write_start()?;
for item in &self.build.items {
let mut build_item = xml
.start_element("item")
.attr("objectid", &item.object_id.0.to_string());
if item.transform != glam::Mat4::IDENTITY {
build_item =
build_item.attr("transform", &format_transform_matrix(&item.transform));
}
if let Some(ref pn) = item.part_number {
build_item = build_item.attr("partnumber", pn);
}
if let Some(printable) = item.printable {
build_item = build_item.attr("printable", if printable { "1" } else { "0" });
}
build_item.write_empty()?;
}
xml.end_element("build")?;
xml.end_element("model")?;
Ok(())
}
fn unit_str(&self) -> &'static str {
match self.unit {
Unit::Micron => "micron",
Unit::Millimeter => "millimeter",
Unit::Centimeter => "centimeter",
Unit::Inch => "inch",
Unit::Foot => "foot",
Unit::Meter => "meter",
}
}
}