lib3mf_core/writer/
displacement_writer.rs

1use crate::error::Result;
2use crate::model::{Channel, Displacement2D, DisplacementMesh, FilterMode, TileStyle};
3use crate::writer::xml_writer::XmlWriter;
4use std::io::Write;
5
6/// Writes a DisplacementMesh element.
7pub fn write_displacement_mesh<W: Write>(
8    writer: &mut XmlWriter<W>,
9    mesh: &DisplacementMesh,
10) -> Result<()> {
11    writer.start_element("d:displacementmesh").write_start()?;
12
13    // Write vertices section
14    writer.start_element("d:vertices").write_start()?;
15    for v in &mesh.vertices {
16        writer
17            .start_element("d:vertex")
18            .attr("x", &v.x.to_string())
19            .attr("y", &v.y.to_string())
20            .attr("z", &v.z.to_string())
21            .write_empty()?;
22    }
23    writer.end_element("d:vertices")?;
24
25    // Write triangles section
26    writer.start_element("d:triangles").write_start()?;
27    for t in &mesh.triangles {
28        let mut builder = writer
29            .start_element("d:triangle")
30            .attr("v1", &t.v1.to_string())
31            .attr("v2", &t.v2.to_string())
32            .attr("v3", &t.v3.to_string());
33
34        // Add displacement indices if present
35        if let Some(d1) = t.d1 {
36            builder = builder.attr("d1", &d1.to_string());
37        }
38        if let Some(d2) = t.d2 {
39            builder = builder.attr("d2", &d2.to_string());
40        }
41        if let Some(d3) = t.d3 {
42            builder = builder.attr("d3", &d3.to_string());
43        }
44
45        // Add material properties if present
46        if let Some(p1) = t.p1 {
47            builder = builder.attr("p1", &p1.to_string());
48        }
49        if let Some(p2) = t.p2 {
50            builder = builder.attr("p2", &p2.to_string());
51        }
52        if let Some(p3) = t.p3 {
53            builder = builder.attr("p3", &p3.to_string());
54        }
55        if let Some(pid) = t.pid {
56            builder = builder.attr("pid", &pid.to_string());
57        }
58
59        builder.write_empty()?;
60    }
61    writer.end_element("d:triangles")?;
62
63    // Write normal vectors section
64    writer.start_element("d:normvectors").write_start()?;
65    for n in &mesh.normals {
66        writer
67            .start_element("d:normvector")
68            .attr("nx", &n.nx.to_string())
69            .attr("ny", &n.ny.to_string())
70            .attr("nz", &n.nz.to_string())
71            .write_empty()?;
72    }
73    writer.end_element("d:normvectors")?;
74
75    // Write gradient vectors if present
76    if let Some(gradients) = &mesh.gradients {
77        writer.start_element("d:disp2dgroups").write_start()?;
78        writer.start_element("d:disp2dgroup").write_start()?;
79        for g in gradients {
80            writer
81                .start_element("d:tex2dcoord")
82                .attr("gu", &g.gu.to_string())
83                .attr("gv", &g.gv.to_string())
84                .write_empty()?;
85        }
86        writer.end_element("d:disp2dgroup")?;
87        writer.end_element("d:disp2dgroups")?;
88    }
89
90    writer.end_element("d:displacementmesh")?;
91    Ok(())
92}
93
94/// Writes a Displacement2D resource element.
95pub fn write_displacement_2d<W: Write>(
96    writer: &mut XmlWriter<W>,
97    res: &Displacement2D,
98) -> Result<()> {
99    let mut builder = writer
100        .start_element("d:displacement2d")
101        .attr("id", &res.id.0.to_string())
102        .attr("path", &res.path);
103
104    // Channel: write only if not default (G)
105    if res.channel != Channel::G {
106        builder = builder.attr("channel", channel_to_str(res.channel));
107    }
108
109    // TileStyle: write only if not default (Wrap)
110    if res.tile_style != TileStyle::Wrap {
111        builder = builder.attr("tilestyle", tile_style_to_str(res.tile_style));
112    }
113
114    // FilterMode: write only if not default (Linear)
115    if res.filter != FilterMode::Linear {
116        builder = builder.attr("filter", filter_mode_to_str(res.filter));
117    }
118
119    // Height: always required
120    builder = builder.attr("height", &res.height.to_string());
121
122    // Offset: write only if non-zero
123    if res.offset != 0.0 {
124        builder = builder.attr("offset", &res.offset.to_string());
125    }
126
127    builder.write_empty()?;
128    Ok(())
129}
130
131/// Converts a Channel enum to its string representation (uppercase).
132fn channel_to_str(c: Channel) -> &'static str {
133    match c {
134        Channel::R => "R",
135        Channel::G => "G",
136        Channel::B => "B",
137        Channel::A => "A",
138    }
139}
140
141/// Converts a TileStyle enum to its string representation (lowercase).
142fn tile_style_to_str(ts: TileStyle) -> &'static str {
143    match ts {
144        TileStyle::Wrap => "wrap",
145        TileStyle::Mirror => "mirror",
146        TileStyle::Clamp => "clamp",
147        TileStyle::None => "none",
148    }
149}
150
151/// Converts a FilterMode enum to its string representation (lowercase).
152fn filter_mode_to_str(fm: FilterMode) -> &'static str {
153    match fm {
154        FilterMode::Linear => "linear",
155        FilterMode::Nearest => "nearest",
156    }
157}