use super::*;
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum StepMappedItemError {
Degenerate {
item: usize,
map: usize,
determinant: f64,
},
}
impl std::fmt::Display for StepMappedItemError {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Degenerate {
item,
map,
determinant,
} => write!(
formatter,
"step_import: MAPPED_ITEM #{item} (REPRESENTATION_MAP #{map}) has a singular \
mapping transform (determinant {determinant}): it collapses the mapped \
representation, so nothing was instantiated for it"
),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) struct MappedItem {
pub(crate) item: usize,
pub(crate) map: usize,
pub(crate) origin: usize,
pub(crate) target: usize,
pub(crate) mapped_rep: usize,
}
pub(super) fn mapped_item_designator(resolver: &Resolver, item: usize) -> String {
(|| {
match resolver.get(item).ok()?.find("MAPPED_ITEM")?.first()? {
Value::Str(text) if !text.trim().is_empty() => Some(text.trim().to_string()),
_ => None,
}
})()
.unwrap_or_else(|| format!("mapped#{item}"))
}
pub(super) fn representation_mapped_items(resolver: &Resolver, rep: usize) -> Vec<MappedItem> {
let mut items = Vec::new();
let Ok(entity) = resolver.get(rep) else {
return items;
};
let Some(list) = representation_items(entity) else {
return items;
};
for value in list {
let Ok(item_ref) = value.as_ref_id() else {
continue;
};
if let Some(item) = mapped_item(resolver, item_ref) {
items.push(item);
}
}
items.sort_unstable_by_key(|item| item.item);
items.dedup();
items
}
fn mapped_item(resolver: &Resolver, item_ref: usize) -> Option<MappedItem> {
let args = resolver.get(item_ref).ok()?.find("MAPPED_ITEM")?;
let map = args.get(1)?.as_ref_id().ok()?;
let target = args.get(2)?.as_ref_id().ok()?;
let map_args = resolver.get(map).ok()?.find("REPRESENTATION_MAP")?;
let origin = map_args.first()?.as_ref_id().ok()?;
let mapped_rep = map_args.get(1)?.as_ref_id().ok()?;
Some(MappedItem {
item: item_ref,
map,
origin,
target,
mapped_rep,
})
}
pub(super) fn mapped_item_transform(
entities: &HashMap<usize, Entity>,
item: &MappedItem,
origin_scale: f64,
target_scale: f64,
) -> Result<Mat4, StepMappedItemError> {
let origin = Resolver::new(entities, origin_scale);
let target = Resolver::new(entities, target_scale);
let origin_matrix = placement_matrix(&origin, item.origin).unwrap_or_else(mat4_identity);
let target_matrix = placement_matrix(&target, item.target).unwrap_or_else(mat4_identity);
let composed = mat4_affine_inverse(&origin_matrix)
.map(|inverse| mat4_mul(&target_matrix, &inverse))
.unwrap_or_else(|_| target_matrix);
let determinant = mat4_determinant3(&composed);
if determinant.abs() <= 1e-14 || !composed.iter().all(|value| value.is_finite()) {
return Err(StepMappedItemError::Degenerate {
item: item.item,
map: item.map,
determinant,
});
}
Ok(composed)
}
fn placement_matrix(resolver: &Resolver, id: usize) -> Option<Mat4> {
if let Ok(frame) = resolver.placement(id) {
return Some(frame_matrix(&frame));
}
transformation_operator_matrix(resolver, id)
}
fn transformation_operator_matrix(resolver: &Resolver, id: usize) -> Option<Mat4> {
let entity = resolver.get(id).ok()?;
let args = entity
.find("CARTESIAN_TRANSFORMATION_OPERATOR_3D")
.or_else(|| entity.find("CARTESIAN_TRANSFORMATION_OPERATOR"))?;
let is_point = |value: &Value| {
value
.as_ref_id()
.ok()
.and_then(|point_ref| resolver.get(point_ref).ok())
.is_some_and(|point| point.has("CARTESIAN_POINT"))
};
let anchor = args.iter().position(is_point)?;
let local_origin = resolver.point(args[anchor].as_ref_id().ok()?).ok()?;
let direction = |offset: usize, before: bool| -> Option<Vec3> {
let index = if before {
anchor.checked_sub(offset)?
} else {
anchor + offset
};
match args.get(index) {
Some(Value::Ref(reference)) => resolver.direction(*reference).ok(),
_ => None,
}
};
let axis1 = direction(2, true);
let axis2 = direction(1, true);
let axis3 = direction(2, false);
let scale = match args.get(anchor + 1) {
Some(value) => value.as_real().unwrap_or(1.0),
None => 1.0,
};
let u3 = axis3.unwrap_or_else(|| Vec3::new(0.0, 0.0, 1.0));
let u1 = axis1
.and_then(|axis| axis.sub(u3.scale(axis.dot(u3))).normalized().ok())
.or_else(|| u3.perpendicular().ok())?;
let u2 = match axis2 {
Some(axis) => axis
.sub(u3.scale(axis.dot(u3)))
.sub(u1.scale(axis.dot(u1)))
.normalized()
.ok()?,
None => u3.cross(u1).normalized().ok()?,
};
let (u1, u2, u3) = (u1.scale(scale), u2.scale(scale), u3.scale(scale));
Some([
u1.x,
u2.x,
u3.x,
local_origin.x,
u1.y,
u2.y,
u3.y,
local_origin.y,
u1.z,
u2.z,
u3.z,
local_origin.z,
0.0,
0.0,
0.0,
1.0,
])
}