mod kind;
mod sweep;
pub use kind::BodyKind;
use ifc_model::{EntityId, Model};
use super::context::product_representation_frame;
use super::profile::ProfileDescription;
use super::representation::{select_shape_representation, Representation, RepresentationPurpose};
use crate::error::{GeometryError, GeometryResult};
use crate::resource::mapped::MappingWalker;
use crate::resource::operator::operator_transform;
use crate::resource::placement::axis_placement_transform;
use crate::slots::Slots;
use crate::transform::Transform;
use crate::units::UnitScale;
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub struct BodyDescription {
pub product: EntityId,
pub representation: EntityId,
pub items: Vec<BodyItem>,
}
impl BodyDescription {
pub fn sole_item(&self) -> Option<&BodyItem> {
match self.items.as_slice() {
[item] => Some(item),
_ => None,
}
}
}
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub struct BodyItem {
pub item: EntityId,
pub type_name: String,
pub mapped_by: Vec<EntityId>,
pub kind: BodyKind,
pub swept: Option<SweptSolid>,
pub item_world: Transform,
}
impl BodyItem {
pub fn is_mirrored(&self) -> Option<bool> {
let determinant = self.item_world.determinant();
(determinant.is_finite() && determinant != 0.0).then_some(determinant < 0.0)
}
}
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub struct SweptSolid {
pub profile: ProfileDescription,
pub end_profile: Option<ProfileDescription>,
pub placement_world: Transform,
pub path: SweepPath,
}
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub enum SweepPath {
#[non_exhaustive]
Extrusion {
direction_world: [f64; 3],
depth: f64,
},
#[non_exhaustive]
Revolution {
axis_origin_world: [f64; 3],
axis_direction_world: [f64; 3],
angle: f64,
},
#[non_exhaustive]
Directrix {
directrix: EntityId,
},
}
pub fn body_description(
model: &Model,
units: &UnitScale,
product: EntityId,
) -> GeometryResult<Option<BodyDescription>> {
let Some(representation) = select_shape_representation(model, product)? else {
return Ok(None);
};
let Some(world) =
product_representation_frame(model, units, product, RepresentationPurpose::Body)?
else {
return Ok(None);
};
let mut walk = Walk {
model,
units,
walker: MappingWalker::new(),
mapped_by: Vec::new(),
items: Vec::new(),
};
walk.representation(product, representation, world)?;
Ok(Some(BodyDescription {
product,
representation,
items: walk.items,
}))
}
struct Walk<'m> {
model: &'m Model,
units: &'m UnitScale,
walker: MappingWalker,
mapped_by: Vec<EntityId>,
items: Vec<BodyItem>,
}
impl Walk<'_> {
fn representation(
&mut self,
referrer: EntityId,
representation: EntityId,
frame: Transform,
) -> GeometryResult<()> {
let entity = self
.model
.get(representation)
.ok_or(GeometryError::MissingEntity {
referrer,
missing: representation,
})?;
for item in Representation::new(representation, entity).items()? {
self.item(representation, item, frame)?;
}
Ok(())
}
fn item(&mut self, referrer: EntityId, item: EntityId, frame: Transform) -> GeometryResult<()> {
let entity = self.model.get(item).ok_or(GeometryError::MissingEntity {
referrer,
missing: item,
})?;
let type_name = entity.type_name.to_ascii_uppercase();
if type_name == "IFCMAPPEDITEM" {
return self.mapped(item, frame);
}
let kind = BodyKind::classify(&type_name).ok_or_else(|| {
Slots::new(item, entity).unsupported("representation item family is not described")
})?;
let swept = if kind.is_swept_area() {
let culprit = self.mapped_by.last().copied().unwrap_or(item);
Some(sweep::describe(
self.model, self.units, item, entity, frame, culprit,
)?)
} else {
None
};
self.items.push(BodyItem {
item,
type_name,
mapped_by: self.mapped_by.clone(),
kind,
swept,
item_world: frame,
});
Ok(())
}
fn mapped(&mut self, item: EntityId, frame: Transform) -> GeometryResult<()> {
self.walker.enter(item)?;
let result = self.mapped_inner(item, frame);
self.walker.exit();
result
}
fn mapped_inner(&mut self, item: EntityId, frame: Transform) -> GeometryResult<()> {
let instance = self.walker.resolve(self.model, item)?;
let target_entity =
self.model
.get(instance.mapping_target)
.ok_or(GeometryError::MissingEntity {
referrer: item,
missing: instance.mapping_target,
})?;
let target = operator_transform(self.model, instance.mapping_target, target_entity)?
.to_metres(self.units);
let origin_entity =
self.model
.get(instance.mapping_origin)
.ok_or(GeometryError::MissingEntity {
referrer: item,
missing: instance.mapping_origin,
})?;
let origin = axis_placement_transform(self.model, instance.mapping_origin, origin_entity)?
.to_metres(self.units);
let inner = frame.compose(&target).compose(&origin);
self.mapped_by.push(item);
let result = self.representation(item, instance.mapped_representation, inner);
self.mapped_by.pop();
result
}
}