use super::{source::{refs, Source}, *};
use ifc_lite_core::DecodedEntity;
pub(super) fn body(source: &mut Source<'_>, product_id: u32, post_opening: bool) -> Result<(DecodedEntity, DecodedEntity), String> {
let product = source.entity(product_id)?;
if !product.ifc_type.is_subtype_of(IfcType::IfcElement)
|| product.ifc_type.name().ends_with("StandardCase")
|| product.ifc_type.is_subtype_of(IfcType::IfcFeatureElement) {
return Err("Evaluated appearance requires an element whose IFC class permits tessellation".into());
}
source.validate_world_placement(&product)?;
let pds_id = product.get_ref(6).ok_or("Product has no representation")?;
let pds = source.entity(pds_id)?;
if pds.ifc_type != IfcType::IfcProductDefinitionShape || !source.single_parent(pds_id, product_id) {
return Err("Evaluated appearance requires an occurrence-owned ProductDefinitionShape".into());
}
let mut found = None;
for id in refs(pds.get(2))? {
let rep = source.entity(id)?;
if rep.ifc_type != IfcType::IfcShapeRepresentation { return Err("Unsupported product representation".into()); }
if !rep.get_string(1).is_some_and(|s| s.eq_ignore_ascii_case("Body")) {
if !matches!(rep.get_string(2), Some("BoundingBox" | "Curve2D" | "Curve3D" | "GeometricCurveSet")) {
return Err("Additional renderable representations are unsupported for evaluated appearance".into());
}
continue;
}
if found.is_some() { return Err("Evaluated appearance requires exactly one Body representation".into()); }
if !source.incoming.get(&id).is_some_and(|incoming| incoming.contains(&pds_id)
&& incoming.iter().all(|parent| *parent == pds_id || (source.types.get(parent) == Some(&IfcType::IfcPresentationLayerAssignment)
|| post_opening && source.types.get(parent)==Some(&IfcType::IfcRepresentationMap)))) {
return Err("Body is shared or has unsupported layer/aspect associations".into());
}
if rep.get_string(2) != Some("MappedRepresentation") && !(post_opening && rep.get_string(2)==Some("SweptSolid")) {
return Err("Evaluated appearance currently supports mapped occurrence bodies only".into());
}
let items=refs(rep.get(3))?;
if items.len()!=1 || (rep.get_string(2)==Some("MappedRepresentation") && source.types.get(&items[0])!=Some(&IfcType::IfcMappedItem)) {
return Err("Evaluated appearance requires one mapped occurrence item".into());
}
found = Some(rep);
}
Ok((product, found.ok_or("No occurrence Body representation")?))
}
pub(super) fn surface_styles(source: &mut Source<'_>, item: u32) -> Result<Vec<u32>, String> {
let styled = source.styled_items.get(&item).cloned().unwrap_or_default();
if styled.len() != 1 { return Err("Evaluated appearance requires one explicit source surface style".into()); }
let entity = source.entity(styled[0])?;
let direct = refs(entity.get(1))?;
if direct.len() > 8 { return Err("Source style assignment exceeds its budget".into()); }
let mut result = Vec::new();
for id in direct {
let style = source.entity(id)?;
let ids = if source.style_assignments.contains(&id) { refs(style.get(0))? } else { vec![id] };
if ids.len() > 8 { return Err("Source style assignment exceeds its budget".into()); }
for id in ids {
if source.types.get(&id) != Some(&IfcType::IfcSurfaceStyle) { return Err("Source has unsupported non-surface styles".into()); }
result.push(id);
}
}
if result.len() != 1 { return Err("Multiple or missing source surface styles are unsupported".into()); }
let surface = source.entity(result[0])?;
let members=refs(surface.get(2))?;
if members.len()>5 { return Err("Surface style exceeds its schema budget".into()); }
for id in members {
if source.types.get(&id) == Some(&IfcType::IfcSurfaceStyleWithTextures) {
return Err("Evaluated conversion of existing texture coordinates is not supported".into());
}
}
Ok(result)
}
pub(super) fn local_points(source: &mut Source<'_>, product: &DecodedEntity,
mesh: &crate::types::mesh::MeshData) -> Result<Vec<[f64; 3]>, String> {
let scale = source.decoder.length_unit_scale();
let context = source.context.as_ref().ok_or("Missing canonical context")?;
let transform = ifc_lite_geometry::GeometryRouter::with_scale(scale)
.resolve_scaled_placement(product, &mut source.decoder).map_err(|e| e.to_string())?;
let columns: [[f64;3];3] = std::array::from_fn(|i| std::array::from_fn(|j| transform[i*4+j]));
let dot = |a:[f64;3], b:[f64;3]| a.iter().zip(b).map(|(a,b)| a*b).sum::<f64>();
if !scale.is_finite() || scale <= 0. || transform.iter().any(|v| !v.is_finite())
|| (0..3).any(|i| (0..3).any(|j| (dot(columns[i], columns[j]) - if i==j {1.} else {0.}).abs()>1e-10)) {
return Err("Evaluated appearance requires a finite rigid product placement".into());
}
let rtc: [f64;3] = if context.meta.needs_shift { context.meta.rtc_offset.into() } else { [0.;3] };
mesh.positions.chunks_exact(3).map(|p| {
let delta = std::array::from_fn(|i| f64::from(p[i])+mesh.origin[i]+rtc[i]-transform[12+i]);
let point = columns.map(|axis| dot(axis,delta)/scale);
if point.iter().any(|v| !v.is_finite()) { Err("Evaluated coordinate exceeds numeric range".into()) } else { Ok(point) }
}).collect()
}
pub(super) fn validate_style_tree(source:&mut Source<'_>, body:&DecodedEntity, leaf:u32)->Result<(),String> {
let mut pending=vec![body.id];
let mut visited=std::collections::BTreeSet::new();
let mut work=0usize;
while let Some(id)=pending.pop() {
if !visited.insert(id) {continue;}
if visited.len()>4096 {return Err("Evaluated style graph exceeds its node budget".into());}
if id!=leaf && source.styled_items.get(&id).is_some_and(|items|!items.is_empty()) {
return Err("Mapped-chain or nested geometry style overrides are unsupported for evaluated appearance".into());
}
if source.incoming.get(&id).is_some_and(|parents|parents.iter()
.any(|parent|source.types.get(parent)==Some(&IfcType::IfcPresentationLayerWithStyle))) {
return Err("Styled presentation layers on evaluated geometry are unsupported".into());
}
let entity=source.entity(id)?;
let mut values:Vec<&ifc_lite_core::AttributeValue>=if entity.ifc_type==IfcType::IfcShapeRepresentation {
entity.get(3).into_iter().collect()
} else {entity.attributes.iter().collect()};
while let Some(value)=values.pop() {
work+=1;
if work>65_536 {return Err("Evaluated style graph exceeds its reference budget".into());}
match value {
ifc_lite_core::AttributeValue::EntityRef(id)=>pending.push(*id),
ifc_lite_core::AttributeValue::List(items)=>values.extend(items),
_=>{},
}
}
}
Ok(())
}