use ifc_model::{Entity, EntityId, Model, Value};
use crate::constraint::product_world_transform;
use crate::error::{GeometryError, GeometryResult};
use crate::resource::placement::axis_placement_transform;
use crate::slots::Slots;
use crate::transform::Transform;
use crate::units::UnitScale;
use super::representation::{
representation_slot, select_product_representation, RepresentationPurpose,
};
pub mod context_slot {
pub const CONTEXT_IDENTIFIER: usize = 0;
pub const CONTEXT_TYPE: usize = 1;
pub const COORDINATE_SPACE_DIMENSION: usize = 2;
pub const PRECISION: usize = 3;
pub const WORLD_COORDINATE_SYSTEM: usize = 4;
pub const TRUE_NORTH: usize = 5;
}
pub mod sub_context_slot {
pub const PARENT_CONTEXT: usize = 6;
pub const TARGET_SCALE: usize = 7;
pub const TARGET_VIEW: usize = 8;
pub const USER_DEFINED_TARGET_VIEW: usize = 9;
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TargetView {
PlanView,
ModelView,
ElevationView,
SectionView,
GraphView,
SketchView,
ReflectedPlanView,
UserDefined(Option<String>),
NotDefined,
Other(String),
}
impl TargetView {
fn parse(literal: &str, user_defined: Option<String>) -> Self {
match literal.trim_matches('.').to_ascii_uppercase().as_str() {
"PLAN_VIEW" => Self::PlanView,
"MODEL_VIEW" => Self::ModelView,
"ELEVATION_VIEW" => Self::ElevationView,
"SECTION_VIEW" => Self::SectionView,
"GRAPH_VIEW" => Self::GraphView,
"SKETCH_VIEW" => Self::SketchView,
"REFLECTED_PLAN_VIEW" => Self::ReflectedPlanView,
"USERDEFINED" => Self::UserDefined(user_defined),
"NOTDEFINED" => Self::NotDefined,
other => Self::Other(other.to_string()),
}
}
#[must_use]
pub fn is_plan(&self) -> bool {
matches!(self, Self::PlanView | Self::ReflectedPlanView)
}
}
#[derive(Debug, Clone, Copy)]
pub struct RepresentationContext<'m> {
slots: Slots<'m>,
}
impl<'m> RepresentationContext<'m> {
pub fn new(id: EntityId, entity: &'m Entity) -> Self {
Self {
slots: Slots::new(id, entity),
}
}
pub fn id(&self) -> EntityId {
self.slots.id()
}
pub fn is_sub_context(&self) -> bool {
self.slots
.type_name()
.eq_ignore_ascii_case("IFCGEOMETRICREPRESENTATIONSUBCONTEXT")
}
pub fn identifier(&self) -> Option<String> {
self.slots.opt_text(context_slot::CONTEXT_IDENTIFIER)
}
pub fn context_type(&self) -> Option<String> {
self.slots.opt_text(context_slot::CONTEXT_TYPE)
}
pub fn parent(&self) -> Option<EntityId> {
match self.slots.opt(sub_context_slot::PARENT_CONTEXT)? {
Value::Ref(id) => Some(*id),
_ => None,
}
}
pub fn target_scale(&self) -> Option<f64> {
match self
.slots
.opt(sub_context_slot::TARGET_SCALE)?
.unwrap_typed()
{
Value::Real(value) => Some(*value),
Value::Integer(value) => Some(*value as f64),
_ => None,
}
}
pub fn target_view(&self) -> Option<TargetView> {
let literal = match self
.slots
.opt(sub_context_slot::TARGET_VIEW)?
.unwrap_typed()
{
Value::Enum(text) => text.to_string(),
_ => return None,
};
let user_defined = self
.slots
.opt_text(sub_context_slot::USER_DEFINED_TARGET_VIEW);
Some(TargetView::parse(&literal, user_defined))
}
pub fn is_plan_view(&self) -> bool {
self.target_view().is_some_and(|view| view.is_plan())
}
}
const MAX_PARENT_DEPTH: usize = 8;
fn resolve_inherited(model: &Model, start: EntityId, slot: usize) -> Option<&Value> {
let mut current = start;
for _ in 0..MAX_PARENT_DEPTH {
let entity = model.get(current)?;
let view = RepresentationContext::new(current, entity);
match entity.attribute(slot) {
Some(value) if !matches!(value, Value::Derived | Value::Null) => {
return Some(value);
}
Some(Value::Derived) => {
current = view.parent()?;
}
_ => return None,
}
}
None
}
impl RepresentationContext<'_> {
pub fn precision(&self, model: &Model) -> Option<f64> {
match resolve_inherited(model, self.id(), context_slot::PRECISION)?.unwrap_typed() {
Value::Real(value) => Some(*value),
Value::Integer(value) => Some(*value as f64),
_ => None,
}
}
pub fn coordinate_space_dimension(&self, model: &Model) -> Option<i64> {
match resolve_inherited(model, self.id(), context_slot::COORDINATE_SPACE_DIMENSION)?
.unwrap_typed()
{
Value::Integer(value) => Some(*value),
_ => None,
}
}
pub fn world_coordinate_system(&self, model: &Model) -> Option<EntityId> {
match resolve_inherited(model, self.id(), context_slot::WORLD_COORDINATE_SYSTEM)? {
Value::Ref(id) => Some(*id),
_ => None,
}
}
pub fn true_north(&self, model: &Model) -> Option<EntityId> {
match resolve_inherited(model, self.id(), context_slot::TRUE_NORTH)? {
Value::Ref(id) => Some(*id),
_ => None,
}
}
}
pub fn all_contexts(model: &Model) -> Vec<RepresentationContext<'_>> {
let mut out = Vec::new();
for type_name in [
"IFCGEOMETRICREPRESENTATIONCONTEXT",
"IFCGEOMETRICREPRESENTATIONSUBCONTEXT",
] {
for &id in model.ids_of_type(type_name) {
if let Some(entity) = model.get(id) {
out.push(RepresentationContext::new(id, entity));
}
}
}
out.sort_by_key(RepresentationContext::id);
out
}
pub fn plan_contexts(model: &Model) -> Vec<RepresentationContext<'_>> {
all_contexts(model)
.into_iter()
.filter(RepresentationContext::is_plan_view)
.collect()
}
pub fn context_of(model: &Model, representation: EntityId) -> Option<RepresentationContext<'_>> {
let entity = model.get(representation)?;
let context_id =
match Slots::new(representation, entity).opt(representation_slot::CONTEXT_OF_ITEMS)? {
Value::Ref(id) => *id,
_ => return None,
};
let context_entity = model.get(context_id)?;
Some(RepresentationContext::new(context_id, context_entity))
}
pub(crate) fn representation_frame(
model: &Model,
units: &UnitScale,
representation: EntityId,
) -> GeometryResult<Transform> {
let Some(context) = context_of(model, representation) else {
return Ok(Transform::identity());
};
let Some(placement_id) = context.world_coordinate_system(model) else {
return Ok(Transform::identity());
};
let placement = model
.get(placement_id)
.ok_or(GeometryError::MissingEntity {
referrer: context.id(),
missing: placement_id,
})?;
Ok(axis_placement_transform(model, placement_id, placement)?.to_metres(units))
}
pub fn product_representation_frame(
model: &Model,
units: &UnitScale,
product: EntityId,
purpose: RepresentationPurpose,
) -> GeometryResult<Option<Transform>> {
let Some(representation) = select_product_representation(model, product, purpose)? else {
return Ok(None);
};
let placement = product_world_transform(model, units, product)?;
Ok(Some(
representation_frame(model, units, representation)?.compose(&placement),
))
}