lib3mf_core/parser/
boolean_parser.rs

1use crate::error::{Lib3mfError, Result};
2use crate::model::{BooleanOperation, BooleanOperationType, BooleanShape, ResourceId};
3use crate::parser::component_parser::parse_transform;
4use crate::parser::xml_parser::{XmlParser, get_attribute, get_attribute_u32};
5use quick_xml::events::Event;
6use std::io::BufRead;
7
8/// Parse a `<booleanshape>` element into a BooleanShape structure.
9///
10/// A booleanshape defines geometry through constructive solid geometry (CSG)
11/// operations on referenced objects.
12///
13/// Per Boolean Operations Extension v1.1.1:
14/// - Requires base objectid attribute (parsed by caller)
15/// - Optional transform on base (parsed by caller)
16/// - Optional p:path for external references (parsed by caller)
17/// - Contains nested `<boolean>` elements defining operations
18pub fn parse_boolean_shape<R: BufRead>(
19    parser: &mut XmlParser<R>,
20    base_object_id: ResourceId,
21    base_transform: glam::Mat4,
22    base_path: Option<String>,
23) -> Result<BooleanShape> {
24    let mut operations = Vec::new();
25
26    // Parse nested <boolean> elements
27    loop {
28        match parser.read_next_event()? {
29            Event::Start(e) | Event::Empty(e) => {
30                let local_name = e.local_name();
31                if local_name.as_ref() == b"boolean" {
32                    let operation = parse_boolean_operation(&e)?;
33                    operations.push(operation);
34                }
35            }
36            Event::End(e) => {
37                let local_name = e.local_name();
38                if local_name.as_ref() == b"booleanshape" {
39                    break;
40                }
41            }
42            Event::Eof => {
43                return Err(Lib3mfError::Validation(
44                    "Unexpected EOF in booleanshape".to_string(),
45                ));
46            }
47            _ => {}
48        }
49    }
50
51    Ok(BooleanShape {
52        base_object_id,
53        base_transform,
54        base_path,
55        operations,
56    })
57}
58
59/// Parse a single <boolean> operation element.
60fn parse_boolean_operation(elem: &quick_xml::events::BytesStart) -> Result<BooleanOperation> {
61    let object_id = ResourceId(get_attribute_u32(elem, b"objectid")?);
62
63    // Parse operation type (defaults to "union" per spec)
64    let operation_type = if let Some(op_str) = get_attribute(elem, b"operation") {
65        match op_str.as_ref() {
66            "union" => BooleanOperationType::Union,
67            "difference" => BooleanOperationType::Difference,
68            "intersection" => BooleanOperationType::Intersection,
69            _ => BooleanOperationType::Union, // Default on unknown
70        }
71    } else {
72        BooleanOperationType::Union
73    };
74
75    let transform = if let Some(s) = get_attribute(elem, b"transform") {
76        parse_transform(&s)?
77    } else {
78        glam::Mat4::IDENTITY
79    };
80
81    let path = get_attribute(elem, b"path")
82        .or_else(|| get_attribute(elem, b"p:path"))
83        .map(|s| s.into_owned());
84
85    Ok(BooleanOperation {
86        operation_type,
87        object_id,
88        transform,
89        path,
90    })
91}