1use crate::error::{Lib3mfError, Result};
2use crate::model::{Geometry, Model, Object, Unit};
3use crate::parser::boolean_parser::parse_boolean_shape;
4use crate::parser::build_parser::parse_build;
5use crate::parser::component_parser::parse_components;
6use crate::parser::displacement_parser::{parse_displacement_2d, parse_displacement_mesh};
7use crate::parser::material_parser::{
8 parse_base_materials, parse_color_group, parse_composite_materials, parse_multi_properties,
9 parse_texture_2d_group,
10};
11use crate::parser::mesh_parser::parse_mesh;
12use crate::parser::slice_parser::parse_slice_stack_content;
13use crate::parser::volumetric_parser::parse_volumetric_stack_content;
14use crate::parser::xml_parser::{XmlParser, get_attribute, get_attribute_f32, get_attribute_u32};
15use quick_xml::events::Event;
16use std::io::BufRead;
17
18pub fn parse_model<R: BufRead>(reader: R) -> Result<Model> {
19 let mut parser = XmlParser::new(reader);
20 let mut model = Model::default();
21
22 loop {
23 match parser.read_next_event()? {
24 Event::Start(e) => match e.name().as_ref() {
25 b"model" => {
26 if let Some(unit_str) = get_attribute(&e, b"unit") {
27 model.unit = match unit_str.as_ref() {
28 "micron" => Unit::Micron,
29 "millimeter" => Unit::Millimeter,
30 "centimeter" => Unit::Centimeter,
31 "inch" => Unit::Inch,
32 "foot" => Unit::Foot,
33 "meter" => Unit::Meter,
34 _ => Unit::Millimeter, };
36 }
37 model.language = get_attribute(&e, b"xml:lang").map(|s| s.into_owned());
38 }
39 b"metadata" => {
40 let name = get_attribute(&e, b"name")
41 .ok_or(Lib3mfError::Validation("Metadata missing name".to_string()))?
42 .into_owned();
43 let content = parser.read_text_content()?;
44 model.metadata.insert(name, content);
45 }
46 b"resources" => parse_resources(&mut parser, &mut model)?,
47 b"build" => {
48 model.build = parse_build(&mut parser)?;
49 }
50 _ => {}
51 },
52 Event::Empty(e) => {
53 if e.name().as_ref() == b"metadata" {
54 let name = get_attribute(&e, b"name")
55 .ok_or(Lib3mfError::Validation("Metadata missing name".to_string()))?;
56 model.metadata.insert(name.into_owned(), String::new());
57 }
58 }
59 Event::End(e) if e.name().as_ref() == b"model" => break,
60 Event::Eof => break,
61 _ => {}
62 }
63 }
64
65 Ok(model)
66}
67
68fn parse_resources<R: BufRead>(parser: &mut XmlParser<R>, model: &mut Model) -> Result<()> {
69 loop {
70 match parser.read_next_event()? {
71 Event::Start(e) => {
72 let local_name = e.local_name();
73 match local_name.as_ref() {
74 b"object" => {
75 let id = crate::model::ResourceId(get_attribute_u32(&e, b"id")?);
76 let name = get_attribute(&e, b"name").map(|s| s.into_owned());
77 let part_number = get_attribute(&e, b"partnumber").map(|s| s.into_owned());
78 let pid = get_attribute_u32(&e, b"pid")
79 .map(crate::model::ResourceId)
80 .ok();
81 let pindex = get_attribute_u32(&e, b"pindex").ok();
82 let uuid = crate::parser::xml_parser::get_attribute_uuid(&e)?;
83
84 let slice_stack_id = get_attribute_u32(&e, b"slicestackid")
86 .or_else(|_| get_attribute_u32(&e, b"s:slicestackid"))
87 .map(crate::model::ResourceId)
88 .ok();
89
90 let vol_stack_id = get_attribute_u32(&e, b"volumetricstackid")
92 .or_else(|_| get_attribute_u32(&e, b"v:volumetricstackid"))
93 .map(crate::model::ResourceId)
94 .ok();
95
96 let object_type = match get_attribute(&e, b"type") {
97 Some(type_str) => match type_str.as_ref() {
98 "model" => crate::model::ObjectType::Model,
99 "support" => crate::model::ObjectType::Support,
100 "solidsupport" => crate::model::ObjectType::SolidSupport,
101 "surface" => crate::model::ObjectType::Surface,
102 "other" => crate::model::ObjectType::Other,
103 unknown => {
104 eprintln!(
105 "Warning: Unknown object type '{}', defaulting to 'model'",
106 unknown
107 );
108 crate::model::ObjectType::Model
109 }
110 },
111 None => crate::model::ObjectType::Model,
112 };
113
114 let thumbnail = get_attribute(&e, b"thumbnail").map(|s| s.into_owned());
115
116 let geometry_content = parse_object_geometry(parser)?;
117
118 let geometry = if let Some(ssid) = slice_stack_id {
119 if geometry_content.has_content() {
120 eprintln!(
121 "Warning: Object {} has slicestackid but also contains geometry content; geometry will be ignored",
122 id.0
123 );
124 }
125 crate::model::Geometry::SliceStack(ssid)
126 } else if let Some(vsid) = vol_stack_id {
127 if geometry_content.has_content() {
128 eprintln!(
129 "Warning: Object {} has volumetricstackid but also contains geometry content; geometry will be ignored",
130 id.0
131 );
132 }
133 crate::model::Geometry::VolumetricStack(vsid)
134 } else {
135 geometry_content
136 };
137
138 let object = Object {
139 id,
140 object_type,
141 name,
142 part_number,
143 uuid,
144 pid,
145 pindex,
146 thumbnail,
147 geometry,
148 };
149 model.resources.add_object(object)?;
150 }
151 b"basematerials" => {
152 let id = crate::model::ResourceId(get_attribute_u32(&e, b"id")?);
153 let group = parse_base_materials(parser, id)?;
154 model.resources.add_base_materials(group)?;
155 }
156 b"colorgroup" => {
157 let id = crate::model::ResourceId(get_attribute_u32(&e, b"id")?);
158 let group = parse_color_group(parser, id)?;
159 model.resources.add_color_group(group)?;
160 }
161 b"texture2dgroup" => {
162 let id = crate::model::ResourceId(get_attribute_u32(&e, b"id")?);
163 let texid = crate::model::ResourceId(get_attribute_u32(&e, b"texid")?);
164 let group = parse_texture_2d_group(parser, id, texid)?;
165 model.resources.add_texture_2d_group(group)?;
166 }
167 b"compositematerials" => {
168 let id = crate::model::ResourceId(get_attribute_u32(&e, b"id")?);
169 let matid = crate::model::ResourceId(get_attribute_u32(&e, b"matid")?);
170 let matindices_str = get_attribute(&e, b"matindices").ok_or_else(|| {
171 Lib3mfError::Validation(
172 "compositematerials missing matindices".to_string(),
173 )
174 })?;
175 let indices = matindices_str
176 .split_whitespace()
177 .map(|s| {
178 s.parse::<u32>().map_err(|_| {
179 Lib3mfError::Validation("Invalid matindices value".to_string())
180 })
181 })
182 .collect::<Result<Vec<u32>>>()?;
183 let group = parse_composite_materials(parser, id, matid, indices)?;
184 model.resources.add_composite_materials(group)?;
185 }
186 b"multiproperties" => {
187 let id = crate::model::ResourceId(get_attribute_u32(&e, b"id")?);
188 let pids_str = get_attribute(&e, b"pids").ok_or_else(|| {
189 Lib3mfError::Validation("multiproperties missing pids".to_string())
190 })?;
191 let pids = pids_str
192 .split_whitespace()
193 .map(|s| {
194 s.parse::<u32>()
195 .map_err(|_| {
196 Lib3mfError::Validation("Invalid pid value".to_string())
197 })
198 .map(crate::model::ResourceId)
199 })
200 .collect::<Result<Vec<crate::model::ResourceId>>>()?;
201
202 let blendmethods_str =
203 get_attribute(&e, b"blendmethods").ok_or_else(|| {
204 Lib3mfError::Validation(
205 "multiproperties missing blendmethods".to_string(),
206 )
207 })?;
208 let blend_methods = blendmethods_str
209 .split_whitespace()
210 .map(|s| match s {
211 "mix" => Ok(crate::model::BlendMethod::Mix),
212 "multiply" => Ok(crate::model::BlendMethod::Multiply),
213 _ => Err(Lib3mfError::Validation(format!(
214 "Invalid blend method: {}",
215 s
216 ))),
217 })
218 .collect::<Result<Vec<crate::model::BlendMethod>>>()?;
219
220 let group = parse_multi_properties(parser, id, pids, blend_methods)?;
221 model.resources.add_multi_properties(group)?;
222 }
223 b"slicestack" => {
224 let id = crate::model::ResourceId(get_attribute_u32(&e, b"id")?);
225 let z_bottom = get_attribute_f32(&e, b"zbottom").unwrap_or(0.0);
226 let stack = parse_slice_stack_content(parser, id, z_bottom)?;
227 model.resources.add_slice_stack(stack)?;
228 }
229 b"volumetricstack" => {
230 let id = crate::model::ResourceId(get_attribute_u32(&e, b"id")?);
231 let stack = parse_volumetric_stack_content(parser, id, 0.0)?;
232 model.resources.add_volumetric_stack(stack)?;
233 }
234 b"booleanshape" => {
235 let id = crate::model::ResourceId(get_attribute_u32(&e, b"id")?);
236 let base_object_id =
237 crate::model::ResourceId(get_attribute_u32(&e, b"objectid")?);
238 let base_transform = if let Some(s) = get_attribute(&e, b"transform") {
239 crate::parser::component_parser::parse_transform(&s)?
240 } else {
241 glam::Mat4::IDENTITY
242 };
243 let base_path = get_attribute(&e, b"path")
244 .or_else(|| get_attribute(&e, b"p:path"))
245 .map(|s| s.into_owned());
246
247 let bool_shape =
248 parse_boolean_shape(parser, base_object_id, base_transform, base_path)?;
249
250 let object = Object {
252 id,
253 object_type: crate::model::ObjectType::Model,
254 name: None,
255 part_number: None,
256 uuid: None,
257 pid: None,
258 pindex: None,
259 thumbnail: None,
260 geometry: Geometry::BooleanShape(bool_shape),
261 };
262 model.resources.add_object(object)?;
263 }
264 b"displacement2d" => {
265 let id = crate::model::ResourceId(get_attribute_u32(&e, b"id")?);
266 let path = get_attribute(&e, b"path")
267 .ok_or_else(|| {
268 Lib3mfError::Validation(
269 "displacement2d missing path attribute".to_string(),
270 )
271 })?
272 .into_owned();
273
274 let channel = if let Some(ch_str) = get_attribute(&e, b"channel") {
275 match ch_str.as_ref() {
276 "R" => crate::model::Channel::R,
277 "G" => crate::model::Channel::G,
278 "B" => crate::model::Channel::B,
279 "A" => crate::model::Channel::A,
280 _ => crate::model::Channel::G,
281 }
282 } else {
283 crate::model::Channel::G
284 };
285
286 let tile_style = if let Some(ts_str) = get_attribute(&e, b"tilestyle") {
287 match ts_str.to_lowercase().as_str() {
288 "wrap" => crate::model::TileStyle::Wrap,
289 "mirror" => crate::model::TileStyle::Mirror,
290 "clamp" => crate::model::TileStyle::Clamp,
291 "none" => crate::model::TileStyle::None,
292 _ => crate::model::TileStyle::Wrap,
293 }
294 } else {
295 crate::model::TileStyle::Wrap
296 };
297
298 let filter = if let Some(f_str) = get_attribute(&e, b"filter") {
299 match f_str.to_lowercase().as_str() {
300 "linear" => crate::model::FilterMode::Linear,
301 "nearest" => crate::model::FilterMode::Nearest,
302 _ => crate::model::FilterMode::Linear,
303 }
304 } else {
305 crate::model::FilterMode::Linear
306 };
307
308 let height = get_attribute_f32(&e, b"height")?;
309 let offset = get_attribute_f32(&e, b"offset").unwrap_or(0.0);
310
311 let displacement = parse_displacement_2d(
312 parser, id, path, channel, tile_style, filter, height, offset,
313 )?;
314 model.resources.add_displacement_2d(displacement)?;
315 }
316 _ => {}
317 }
318 }
319 Event::End(e) if e.name().as_ref() == b"resources" => break,
320 Event::Eof => {
321 return Err(Lib3mfError::Validation(
322 "Unexpected EOF in resources".to_string(),
323 ));
324 }
325 _ => {}
326 }
327 }
328 Ok(())
329}
330
331fn parse_object_geometry<R: BufRead>(parser: &mut XmlParser<R>) -> Result<Geometry> {
332 let mut geometry = Geometry::Mesh(crate::model::Mesh::default()); loop {
342 match parser.read_next_event()? {
343 Event::Start(e) => {
344 let local_name = e.local_name();
345 match local_name.as_ref() {
346 b"mesh" => {
347 geometry = Geometry::Mesh(parse_mesh(parser)?);
348 }
349 b"components" => {
350 geometry = Geometry::Components(parse_components(parser)?);
351 }
352 b"displacementmesh" => {
353 geometry = Geometry::DisplacementMesh(parse_displacement_mesh(parser)?);
354 }
355 _ => {}
356 }
357 }
358 Event::End(e) if e.name().as_ref() == b"object" => break,
359 Event::Eof => {
360 return Err(Lib3mfError::Validation(
361 "Unexpected EOF in object".to_string(),
362 ));
363 }
364 _ => {}
365 }
366 }
367 Ok(geometry)
368}