lib3mf_core/writer/
beamlattice_writer.rs

1use crate::error::Result;
2use crate::model::{BeamLattice, CapMode, ClippingMode};
3use crate::writer::xml_writer::XmlWriter;
4use std::io::Write;
5
6/// Writes a `<beamlattice>` element inside a `<mesh>` element.
7///
8/// The beam lattice is written as a child element of `<mesh>`, after the
9/// `<triangles>` section, per the Beam Lattice Extension v1.2.0 specification.
10///
11/// Element names are unqualified (no namespace prefix) because the parser
12/// matches them by local name. The namespace URI is declared on the root
13/// model element as `xmlns:bl`.
14pub fn write_beam_lattice<W: Write>(
15    writer: &mut XmlWriter<W>,
16    lattice: &BeamLattice,
17) -> Result<()> {
18    let mut bl_elem = writer.start_element("beamlattice");
19
20    // Write radius attribute when present (default radius for beams)
21    if let Some(radius) = lattice.radius {
22        bl_elem = bl_elem.attr("radius", &radius.to_string());
23    }
24
25    bl_elem = bl_elem
26        .attr("minlength", &lattice.min_length.to_string())
27        .attr("precision", &lattice.precision.to_string());
28
29    // clippingmode: omit when None (default) to match spec conventions
30    if lattice.clipping_mode != ClippingMode::None {
31        bl_elem = bl_elem.attr("clippingmode", clipping_mode_to_str(lattice.clipping_mode));
32    }
33
34    bl_elem.write_start()?;
35
36    // Write beams section
37    writer.start_element("beams").write_start()?;
38    for beam in &lattice.beams {
39        let mut b = writer
40            .start_element("beam")
41            .attr("v1", &beam.v1.to_string())
42            .attr("v2", &beam.v2.to_string())
43            .attr("r1", &beam.r1.to_string())
44            .attr("r2", &beam.r2.to_string());
45
46        // cap: omit for default (Sphere) to match spec conventions
47        if beam.cap_mode != CapMode::Sphere {
48            b = b.attr("cap", cap_mode_to_str(beam.cap_mode));
49        }
50
51        if let Some(p1) = beam.p1 {
52            b = b.attr("p1", &p1.to_string());
53        }
54        if let Some(p2) = beam.p2 {
55            b = b.attr("p2", &p2.to_string());
56        }
57
58        b.write_empty()?;
59    }
60    writer.end_element("beams")?;
61
62    // Write beam sets section (only when non-empty)
63    if !lattice.beam_sets.is_empty() {
64        writer.start_element("beamsets").write_start()?;
65        for beam_set in &lattice.beam_sets {
66            let mut bs = writer.start_element("beamset");
67            if let Some(name) = &beam_set.name {
68                bs = bs.attr("name", name);
69            }
70            if let Some(identifier) = &beam_set.identifier {
71                bs = bs.attr("identifier", identifier);
72            }
73
74            if beam_set.refs.is_empty() {
75                bs.write_empty()?;
76            } else {
77                bs.write_start()?;
78                for &ref_index in &beam_set.refs {
79                    writer
80                        .start_element("ref")
81                        .attr("index", &ref_index.to_string())
82                        .write_empty()?;
83                }
84                writer.end_element("beamset")?;
85            }
86        }
87        writer.end_element("beamsets")?;
88    }
89
90    writer.end_element("beamlattice")?;
91    Ok(())
92}
93
94/// Converts a `CapMode` enum to its XML attribute string value.
95fn cap_mode_to_str(c: CapMode) -> &'static str {
96    match c {
97        CapMode::Sphere => "sphere",
98        CapMode::Hemisphere => "hemisphere",
99        CapMode::Butt => "butt",
100    }
101}
102
103/// Converts a `ClippingMode` enum to its XML attribute string value.
104fn clipping_mode_to_str(cm: ClippingMode) -> &'static str {
105    match cm {
106        ClippingMode::None => "none",
107        ClippingMode::Inside => "inside",
108        ClippingMode::Outside => "outside",
109    }
110}