use crate::error::{Error, Result};
use crate::model::*;
use quick_xml::Reader;
use std::collections::HashSet;
use super::{parse_attributes, validate_attributes};
pub(super) fn validate_displacement_namespace_prefix(
name_str: &str,
element_type: &str,
) -> Result<()> {
let has_displacement_prefix =
name_str.starts_with("d:") || name_str.starts_with("displacement:");
if !has_displacement_prefix {
return Err(Error::InvalidXml(format!(
"Element <{}> under displacementmesh must use the displacement namespace prefix (e.g., <d:{}>). \
Per 3MF Displacement Extension spec 4.1, all elements under <displacementmesh> MUST specify \
the displacement namespace prefix.",
name_str, element_type
)));
}
Ok(())
}
pub fn parse_displacement_triangle<R: std::io::BufRead>(
reader: &Reader<R>,
e: &quick_xml::events::BytesStart,
) -> Result<DisplacementTriangle> {
let attrs = parse_attributes(reader, e)?;
validate_attributes(
&attrs,
&[
"v1", "v2", "v3", "pid", "pindex", "p1", "p2", "p3", "did", "d1", "d2", "d3",
],
"triangle",
)?;
let v1 = attrs
.get("v1")
.ok_or_else(|| Error::InvalidXml("Displacement triangle missing v1 attribute".to_string()))?
.parse::<usize>()?;
let v2 = attrs
.get("v2")
.ok_or_else(|| Error::InvalidXml("Displacement triangle missing v2 attribute".to_string()))?
.parse::<usize>()?;
let v3 = attrs
.get("v3")
.ok_or_else(|| Error::InvalidXml("Displacement triangle missing v3 attribute".to_string()))?
.parse::<usize>()?;
let mut triangle = DisplacementTriangle::new(v1, v2, v3);
if let Some(pid) = attrs.get("pid") {
triangle.pid = Some(pid.parse::<usize>()?);
}
if let Some(pindex) = attrs.get("pindex") {
triangle.pindex = Some(pindex.parse::<usize>()?);
}
if let Some(p1) = attrs.get("p1") {
triangle.p1 = Some(p1.parse::<usize>()?);
}
if let Some(p2) = attrs.get("p2") {
triangle.p2 = Some(p2.parse::<usize>()?);
}
if let Some(p3) = attrs.get("p3") {
triangle.p3 = Some(p3.parse::<usize>()?);
}
if let Some(did) = attrs.get("did") {
triangle.did = Some(did.parse::<usize>()?);
}
if let Some(d1) = attrs.get("d1") {
triangle.d1 = Some(d1.parse::<usize>()?);
}
if let Some(d2) = attrs.get("d2") {
triangle.d2 = Some(d2.parse::<usize>()?);
}
if let Some(d3) = attrs.get("d3") {
triangle.d3 = Some(d3.parse::<usize>()?);
}
Ok(triangle)
}
pub(super) fn parse_displacement2d<R: std::io::BufRead>(
reader: &Reader<R>,
e: &quick_xml::events::BytesStart,
) -> Result<Displacement2D> {
let attrs = parse_attributes(reader, e)?;
validate_attributes(
&attrs,
&[
"id",
"path",
"channel",
"tilestyleu",
"tilestylev",
"filter",
],
"displacement2d",
)?;
let id = attrs
.get("id")
.ok_or_else(|| Error::InvalidXml("displacement2d missing id attribute".to_string()))?
.parse::<usize>()?;
let path = attrs
.get("path")
.ok_or_else(|| Error::InvalidXml("displacement2d missing path attribute".to_string()))?
.to_string();
let mut disp = Displacement2D::new(id, path);
if let Some(channel_str) = attrs.get("channel") {
disp.channel = match channel_str.to_uppercase().as_str() {
"R" => Channel::R,
"G" => Channel::G,
"B" => Channel::B,
"A" => Channel::A,
_ => {
return Err(Error::InvalidXml(format!(
"Invalid channel value '{}'. Valid values are: R, G, B, A",
channel_str
)));
}
};
}
if let Some(tileu_str) = attrs.get("tilestyleu") {
disp.tilestyleu = match tileu_str.to_lowercase().as_str() {
"wrap" => TileStyle::Wrap,
"mirror" => TileStyle::Mirror,
"clamp" => TileStyle::Clamp,
"none" => TileStyle::None,
_ => {
return Err(Error::InvalidXml(format!(
"Invalid tilestyleu value '{}'. Valid values are: wrap, mirror, clamp, none",
tileu_str
)));
}
};
}
if let Some(tilev_str) = attrs.get("tilestylev") {
disp.tilestylev = match tilev_str.to_lowercase().as_str() {
"wrap" => TileStyle::Wrap,
"mirror" => TileStyle::Mirror,
"clamp" => TileStyle::Clamp,
"none" => TileStyle::None,
_ => {
return Err(Error::InvalidXml(format!(
"Invalid tilestylev value '{}'. Valid values are: wrap, mirror, clamp, none",
tilev_str
)));
}
};
}
if let Some(filter_str) = attrs.get("filter") {
disp.filter = match filter_str.to_lowercase().as_str() {
"auto" => FilterMode::Auto,
"linear" => FilterMode::Linear,
"nearest" => FilterMode::Nearest,
_ => {
return Err(Error::InvalidXml(format!(
"Invalid filter value '{}'. Valid values are: auto, linear, nearest",
filter_str
)));
}
};
}
Ok(disp)
}
pub(super) fn parse_normvectorgroup_start<R: std::io::BufRead>(
reader: &Reader<R>,
e: &quick_xml::events::BytesStart,
) -> Result<NormVectorGroup> {
let attrs = parse_attributes(reader, e)?;
validate_attributes(&attrs, &["id"], "normvectorgroup")?;
let id = attrs
.get("id")
.ok_or_else(|| Error::InvalidXml("normvectorgroup missing id attribute".to_string()))?
.parse::<usize>()?;
Ok(NormVectorGroup::new(id))
}
pub(super) fn parse_normvector<R: std::io::BufRead>(
reader: &Reader<R>,
e: &quick_xml::events::BytesStart,
) -> Result<NormVector> {
let attrs = parse_attributes(reader, e)?;
validate_attributes(&attrs, &["x", "y", "z"], "normvector")?;
let x = attrs
.get("x")
.ok_or_else(|| Error::InvalidXml("normvector missing x attribute".to_string()))?
.parse::<f64>()?;
let y = attrs
.get("y")
.ok_or_else(|| Error::InvalidXml("normvector missing y attribute".to_string()))?
.parse::<f64>()?;
let z = attrs
.get("z")
.ok_or_else(|| Error::InvalidXml("normvector missing z attribute".to_string()))?
.parse::<f64>()?;
if !x.is_finite() || !y.is_finite() || !z.is_finite() {
return Err(Error::InvalidXml(format!(
"NormVector has non-finite values: x={}, y={}, z={}",
x, y, z
)));
}
Ok(NormVector::new(x, y, z))
}
pub(super) fn parse_disp2dgroup_start<R: std::io::BufRead>(
reader: &Reader<R>,
e: &quick_xml::events::BytesStart,
declared_displacement2d_ids: &HashSet<usize>,
declared_normvectorgroup_ids: &HashSet<usize>,
) -> Result<Disp2DGroup> {
let attrs = parse_attributes(reader, e)?;
validate_attributes(
&attrs,
&["id", "dispid", "nid", "height", "offset"],
"disp2dgroup",
)?;
let id = attrs
.get("id")
.ok_or_else(|| Error::InvalidXml("disp2dgroup missing id attribute".to_string()))?
.parse::<usize>()?;
let dispid = attrs
.get("dispid")
.ok_or_else(|| Error::InvalidXml("disp2dgroup missing dispid attribute".to_string()))?
.parse::<usize>()?;
if !declared_displacement2d_ids.contains(&dispid) {
return Err(Error::InvalidXml(format!(
"Disp2DGroup references Displacement2D with ID {} which has not been declared yet. \
Resources must be declared before they are referenced.",
dispid
)));
}
let nid = attrs
.get("nid")
.ok_or_else(|| Error::InvalidXml("disp2dgroup missing nid attribute".to_string()))?
.parse::<usize>()?;
if !declared_normvectorgroup_ids.contains(&nid) {
return Err(Error::InvalidXml(format!(
"Disp2DGroup references NormVectorGroup with ID {} which has not been declared yet. \
Resources must be declared before they are referenced.",
nid
)));
}
let height = attrs
.get("height")
.ok_or_else(|| Error::InvalidXml("disp2dgroup missing height attribute".to_string()))?
.parse::<f64>()?;
if !height.is_finite() {
return Err(Error::InvalidXml(format!(
"Disp2DGroup height must be finite, got: {}",
height
)));
}
let mut disp2dgroup = Disp2DGroup::new(id, dispid, nid, height);
if let Some(offset_str) = attrs.get("offset") {
let offset = offset_str.parse::<f64>()?;
if !offset.is_finite() {
return Err(Error::InvalidXml(format!(
"Disp2DGroup offset must be finite, got: {}",
offset
)));
}
disp2dgroup.offset = offset;
}
Ok(disp2dgroup)
}
pub(super) fn parse_disp2dcoord<R: std::io::BufRead>(
reader: &Reader<R>,
e: &quick_xml::events::BytesStart,
) -> Result<Disp2DCoords> {
let attrs = parse_attributes(reader, e)?;
validate_attributes(&attrs, &["u", "v", "n", "f"], "disp2dcoord")?;
let u = attrs
.get("u")
.ok_or_else(|| Error::InvalidXml("disp2dcoord missing u attribute".to_string()))?
.parse::<f64>()?;
let v = attrs
.get("v")
.ok_or_else(|| Error::InvalidXml("disp2dcoord missing v attribute".to_string()))?
.parse::<f64>()?;
if !u.is_finite() || !v.is_finite() {
return Err(Error::InvalidXml(format!(
"Disp2DCoord u and v must be finite, got: u={}, v={}",
u, v
)));
}
let n = attrs
.get("n")
.ok_or_else(|| Error::InvalidXml("disp2dcoord missing n attribute".to_string()))?
.parse::<usize>()?;
let mut coords = Disp2DCoords::new(u, v, n);
if let Some(f_str) = attrs.get("f") {
let f = f_str.parse::<f64>()?;
if !f.is_finite() {
return Err(Error::InvalidXml(format!(
"Disp2DCoord displacement factor must be finite, got: {}",
f
)));
}
coords.f = f;
}
Ok(coords)
}