Skip to main content

lib3mf_core/parser/
streaming.rs

1use crate::error::{Lib3mfError, Result};
2use crate::model::{Beam, BuildItem, CapMode, DisplacementTriangle, ResourceId};
3use crate::parser::material_parser::{parse_base_materials, parse_color_group};
4use crate::parser::visitor::ModelVisitor;
5use crate::parser::xml_parser::{XmlParser, get_attribute, get_attribute_f32, get_attribute_u32};
6use glam::Mat4;
7use quick_xml::events::Event;
8use std::io::BufRead;
9
10/// Parses a 3MF model from an XML reader in a streaming fashion,
11/// emitting events to the provided visitor.
12pub fn parse_model_streaming<R: BufRead, V: ModelVisitor>(
13    reader: R,
14    visitor: &mut V,
15) -> Result<()> {
16    let mut parser = XmlParser::new(reader);
17
18    visitor.on_start_model()?;
19
20    loop {
21        match parser.read_next_event()? {
22            Event::Start(e) => {
23                match e.name().as_ref() {
24                    b"model" => {
25                        // Attributes like unit/lang handled here if needed?
26                    }
27                    b"metadata" => {
28                        let name = get_attribute(&e, b"name")
29                            .ok_or(Lib3mfError::Validation("Metadata missing name".to_string()))?
30                            .into_owned();
31                        let content = parser.read_text_content()?;
32                        visitor.on_metadata(&name, &content)?;
33                    }
34                    b"resources" => {
35                        visitor.on_start_resources()?;
36                        parse_resources_streaming(&mut parser, visitor)?;
37                        visitor.on_end_resources()?;
38                    }
39                    b"build" => {
40                        visitor.on_start_build()?;
41                        parse_build_streaming(&mut parser, visitor)?;
42                        visitor.on_end_build()?;
43                    }
44                    _ => {}
45                }
46            }
47            Event::Empty(e) => {
48                if e.name().as_ref() == b"metadata" {
49                    let name = get_attribute(&e, b"name")
50                        .ok_or(Lib3mfError::Validation("Metadata missing name".to_string()))?;
51                    visitor.on_metadata(name.as_ref(), "")?;
52                }
53            }
54            Event::End(e) if e.name().as_ref() == b"model" => break,
55            Event::Eof => break,
56            _ => {}
57        }
58    }
59
60    visitor.on_end_model()?;
61    Ok(())
62}
63
64fn parse_resources_streaming<R: BufRead, V: ModelVisitor>(
65    parser: &mut XmlParser<R>,
66    visitor: &mut V,
67) -> Result<()> {
68    loop {
69        match parser.read_next_event()? {
70            Event::Start(e) => {
71                let local_name = e.local_name();
72                match local_name.as_ref() {
73                    b"object" => {
74                        let id = ResourceId(get_attribute_u32(&e, b"id")?);
75                        parse_object_content_streaming(parser, visitor, id)?;
76                    }
77                    b"basematerials" => {
78                        let id = ResourceId(get_attribute_u32(&e, b"id")?);
79                        let group = parse_base_materials(parser, id)?;
80                        visitor.on_base_materials(id, &group)?;
81                    }
82                    b"colorgroup" => {
83                        let id = ResourceId(get_attribute_u32(&e, b"id")?);
84                        let group = parse_color_group(parser, id)?;
85                        visitor.on_color_group(id, &group)?;
86                    }
87                    _ => {}
88                }
89            }
90            Event::End(e) if e.name().as_ref() == b"resources" => break,
91            Event::Eof => {
92                return Err(Lib3mfError::Validation(
93                    "Unexpected EOF in resources".to_string(),
94                ));
95            }
96            _ => {}
97        }
98    }
99    Ok(())
100}
101
102fn parse_object_content_streaming<R: BufRead, V: ModelVisitor>(
103    parser: &mut XmlParser<R>,
104    visitor: &mut V,
105    object_id: ResourceId,
106) -> Result<()> {
107    loop {
108        match parser.read_next_event()? {
109            Event::Start(e) => match e.local_name().as_ref() {
110                b"mesh" => {
111                    visitor.on_start_mesh(object_id)?;
112                    parse_mesh_streaming(parser, visitor, object_id)?;
113                    visitor.on_end_mesh()?;
114                }
115                b"components" => {
116                    // Skipping components for now
117                }
118                b"displacementmesh" => {
119                    visitor.on_start_displacement_mesh(object_id)?;
120                    parse_displacement_mesh_streaming(parser, visitor)?;
121                    visitor.on_end_displacement_mesh()?;
122                }
123                _ => {}
124            },
125            Event::End(e) if e.local_name().as_ref() == b"object" => break,
126            Event::Eof => {
127                return Err(Lib3mfError::Validation(
128                    "Unexpected EOF in object".to_string(),
129                ));
130            }
131            _ => {}
132        }
133    }
134    Ok(())
135}
136
137fn parse_mesh_streaming<R: BufRead, V: ModelVisitor>(
138    parser: &mut XmlParser<R>,
139    visitor: &mut V,
140    object_id: ResourceId,
141) -> Result<()> {
142    loop {
143        match parser.read_next_event()? {
144            Event::Start(e) => match e.local_name().as_ref() {
145                b"vertices" => parse_vertices_streaming(parser, visitor)?,
146                b"triangles" => parse_triangles_streaming(parser, visitor)?,
147                b"beamlattice" => {
148                    let default_radius = get_attribute_f32(&e, b"radius").unwrap_or(0.0);
149                    visitor.on_start_beam_lattice(object_id)?;
150                    parse_beam_lattice_streaming(parser, visitor, default_radius)?;
151                    visitor.on_end_beam_lattice()?;
152                }
153                _ => {}
154            },
155            Event::End(e) if e.local_name().as_ref() == b"mesh" => break,
156            Event::Eof => {
157                return Err(Lib3mfError::Validation(
158                    "Unexpected EOF in mesh".to_string(),
159                ));
160            }
161            _ => {}
162        }
163    }
164    Ok(())
165}
166
167fn parse_vertices_streaming<R: BufRead, V: ModelVisitor>(
168    parser: &mut XmlParser<R>,
169    visitor: &mut V,
170) -> Result<()> {
171    loop {
172        match parser.read_next_event()? {
173            Event::Start(e) | Event::Empty(e) => {
174                if e.name().as_ref() == b"vertex" {
175                    let x = get_attribute_f32(&e, b"x")?;
176                    let y = get_attribute_f32(&e, b"y")?;
177                    let z = get_attribute_f32(&e, b"z")?;
178                    visitor.on_vertex(x, y, z)?;
179                }
180            }
181            Event::End(e) if e.name().as_ref() == b"vertices" => break,
182            Event::Eof => {
183                return Err(Lib3mfError::Validation(
184                    "Unexpected EOF in vertices".to_string(),
185                ));
186            }
187            _ => {}
188        }
189    }
190    Ok(())
191}
192
193fn parse_triangles_streaming<R: BufRead, V: ModelVisitor>(
194    parser: &mut XmlParser<R>,
195    visitor: &mut V,
196) -> Result<()> {
197    loop {
198        match parser.read_next_event()? {
199            Event::Start(e) | Event::Empty(e) => {
200                if e.name().as_ref() == b"triangle" {
201                    let v1 = get_attribute_u32(&e, b"v1")?;
202                    let v2 = get_attribute_u32(&e, b"v2")?;
203                    let v3 = get_attribute_u32(&e, b"v3")?;
204                    visitor.on_triangle(v1, v2, v3)?;
205                }
206            }
207            Event::End(e) if e.name().as_ref() == b"triangles" => break,
208            Event::Eof => {
209                return Err(Lib3mfError::Validation(
210                    "Unexpected EOF in triangles".to_string(),
211                ));
212            }
213            _ => {}
214        }
215    }
216    Ok(())
217}
218
219fn parse_beam_lattice_streaming<R: BufRead, V: ModelVisitor>(
220    parser: &mut XmlParser<R>,
221    visitor: &mut V,
222    default_radius: f32,
223) -> Result<()> {
224    loop {
225        match parser.read_next_event()? {
226            Event::Start(e) => match e.local_name().as_ref() {
227                b"beams" => parse_beams_streaming(parser, visitor, default_radius)?,
228                b"beamsets" => {
229                    // BeamSets reference beams by index and require all beams to be
230                    // known first — incompatible with streaming semantics. Skip silently.
231                    parser.read_to_end(b"beamsets")?;
232                }
233                _ => {}
234            },
235            Event::End(e) if e.local_name().as_ref() == b"beamlattice" => break,
236            Event::Eof => {
237                return Err(Lib3mfError::Validation(
238                    "Unexpected EOF in beamlattice".to_string(),
239                ));
240            }
241            _ => {}
242        }
243    }
244    Ok(())
245}
246
247fn parse_beams_streaming<R: BufRead, V: ModelVisitor>(
248    parser: &mut XmlParser<R>,
249    visitor: &mut V,
250    default_radius: f32,
251) -> Result<()> {
252    loop {
253        match parser.read_next_event()? {
254            Event::Start(e) | Event::Empty(e) if e.local_name().as_ref() == b"beam" => {
255                let v1 = get_attribute_u32(&e, b"v1")?;
256                let v2 = get_attribute_u32(&e, b"v2")?;
257                let r1 = get_attribute_f32(&e, b"r1").unwrap_or(default_radius);
258                let r2 = get_attribute_f32(&e, b"r2").unwrap_or(r1);
259                let p1 = get_attribute_u32(&e, b"p1").ok();
260                let p2 = get_attribute_u32(&e, b"p2").ok();
261                let cap_mode = if let Some(s) = get_attribute(&e, b"cap") {
262                    match s.as_ref() {
263                        "sphere" => CapMode::Sphere,
264                        "hemisphere" => CapMode::Hemisphere,
265                        "butt" => CapMode::Butt,
266                        _ => CapMode::Sphere,
267                    }
268                } else {
269                    CapMode::Sphere
270                };
271                visitor.on_beam(&Beam {
272                    v1,
273                    v2,
274                    r1,
275                    r2,
276                    p1,
277                    p2,
278                    cap_mode,
279                })?;
280            }
281            Event::End(e) if e.local_name().as_ref() == b"beams" => break,
282            Event::Eof => {
283                return Err(Lib3mfError::Validation(
284                    "Unexpected EOF in beams".to_string(),
285                ));
286            }
287            _ => {}
288        }
289    }
290    Ok(())
291}
292
293fn parse_displacement_mesh_streaming<R: BufRead, V: ModelVisitor>(
294    parser: &mut XmlParser<R>,
295    visitor: &mut V,
296) -> Result<()> {
297    loop {
298        match parser.read_next_event()? {
299            Event::Start(e) => match e.local_name().as_ref() {
300                b"vertices" => parse_displacement_vertices_streaming(parser, visitor)?,
301                b"triangles" => parse_displacement_triangles_streaming(parser, visitor)?,
302                b"normvectors" => parse_displacement_normals_streaming(parser, visitor)?,
303                b"disp2dgroups" => {
304                    // Gradient vectors have two-level nesting (disp2dgroups > disp2dgroup > gradient).
305                    // They are small secondary data and incompatible with single-pass streaming.
306                    // Skip silently. Use DOM mode if gradient data is required.
307                    parser.read_to_end(b"disp2dgroups")?;
308                }
309                _ => {}
310            },
311            Event::End(e) if e.local_name().as_ref() == b"displacementmesh" => break,
312            Event::Eof => {
313                return Err(Lib3mfError::Validation(
314                    "Unexpected EOF in displacementmesh".to_string(),
315                ));
316            }
317            _ => {}
318        }
319    }
320    Ok(())
321}
322
323fn parse_displacement_vertices_streaming<R: BufRead, V: ModelVisitor>(
324    parser: &mut XmlParser<R>,
325    visitor: &mut V,
326) -> Result<()> {
327    loop {
328        match parser.read_next_event()? {
329            Event::Start(e) | Event::Empty(e) if e.local_name().as_ref() == b"vertex" => {
330                let x = get_attribute_f32(&e, b"x")?;
331                let y = get_attribute_f32(&e, b"y")?;
332                let z = get_attribute_f32(&e, b"z")?;
333                visitor.on_displacement_vertex(x, y, z)?;
334            }
335            Event::End(e) if e.local_name().as_ref() == b"vertices" => break,
336            Event::Eof => {
337                return Err(Lib3mfError::Validation(
338                    "Unexpected EOF in displacement vertices".to_string(),
339                ));
340            }
341            _ => {}
342        }
343    }
344    Ok(())
345}
346
347fn parse_displacement_triangles_streaming<R: BufRead, V: ModelVisitor>(
348    parser: &mut XmlParser<R>,
349    visitor: &mut V,
350) -> Result<()> {
351    loop {
352        match parser.read_next_event()? {
353            Event::Start(e) | Event::Empty(e) if e.local_name().as_ref() == b"triangle" => {
354                let triangle = DisplacementTriangle {
355                    v1: get_attribute_u32(&e, b"v1")?,
356                    v2: get_attribute_u32(&e, b"v2")?,
357                    v3: get_attribute_u32(&e, b"v3")?,
358                    d1: get_attribute_u32(&e, b"d1").ok(),
359                    d2: get_attribute_u32(&e, b"d2").ok(),
360                    d3: get_attribute_u32(&e, b"d3").ok(),
361                    p1: get_attribute_u32(&e, b"p1").ok(),
362                    p2: get_attribute_u32(&e, b"p2").ok(),
363                    p3: get_attribute_u32(&e, b"p3").ok(),
364                    pid: get_attribute_u32(&e, b"pid").ok(),
365                };
366                visitor.on_displacement_triangle(&triangle)?;
367            }
368            Event::End(e) if e.local_name().as_ref() == b"triangles" => break,
369            Event::Eof => {
370                return Err(Lib3mfError::Validation(
371                    "Unexpected EOF in displacement triangles".to_string(),
372                ));
373            }
374            _ => {}
375        }
376    }
377    Ok(())
378}
379
380fn parse_displacement_normals_streaming<R: BufRead, V: ModelVisitor>(
381    parser: &mut XmlParser<R>,
382    visitor: &mut V,
383) -> Result<()> {
384    loop {
385        match parser.read_next_event()? {
386            Event::Start(e) | Event::Empty(e) if e.local_name().as_ref() == b"normvector" => {
387                let nx = get_attribute_f32(&e, b"nx")?;
388                let ny = get_attribute_f32(&e, b"ny")?;
389                let nz = get_attribute_f32(&e, b"nz")?;
390                visitor.on_displacement_normal(nx, ny, nz)?;
391            }
392            Event::End(e) if e.local_name().as_ref() == b"normvectors" => break,
393            Event::Eof => {
394                return Err(Lib3mfError::Validation(
395                    "Unexpected EOF in displacement normals".to_string(),
396                ));
397            }
398            _ => {}
399        }
400    }
401    Ok(())
402}
403
404fn parse_build_streaming<R: BufRead, V: ModelVisitor>(
405    parser: &mut XmlParser<R>,
406    visitor: &mut V,
407) -> Result<()> {
408    loop {
409        match parser.read_next_event()? {
410            Event::Start(e) | Event::Empty(e) => {
411                if e.name().as_ref() == b"item" {
412                    let object_id = ResourceId(get_attribute_u32(&e, b"objectid")?);
413                    let item = BuildItem {
414                        object_id,
415                        transform: Mat4::IDENTITY,
416                        part_number: None,
417                        uuid: None,
418                        path: None,
419                        printable: None,
420                    };
421                    visitor.on_build_item(&item)?;
422                }
423            }
424            Event::End(e) if e.name().as_ref() == b"build" => break,
425            Event::Eof => {
426                return Err(Lib3mfError::Validation(
427                    "Unexpected EOF in build".to_string(),
428                ));
429            }
430            _ => {}
431        }
432    }
433    Ok(())
434}