use ifc_model::{Entity, EntityId, Model, Value};
mod slot {
pub const NAME: usize = 0;
pub const DESCRIPTION: usize = 1;
pub const APPLIED_VALUE: usize = 2;
pub const UNIT_BASIS: usize = 3;
pub const APPLICABLE_DATE: usize = 4;
pub const FIXED_UNTIL_DATE: usize = 5;
pub const CATEGORY: usize = 6;
pub const CONDITION: usize = 7;
pub const ARITHMETIC_OPERATOR: usize = 8;
pub const COMPONENTS: usize = 9;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ArithmeticOperator {
Add,
Divide,
Multiply,
Subtract,
}
impl ArithmeticOperator {
#[must_use]
pub fn parse(token: &str) -> Option<Self> {
Some(match token {
"ADD" => Self::Add,
"DIVIDE" => Self::Divide,
"MULTIPLY" => Self::Multiply,
"SUBTRACT" => Self::Subtract,
_ => return None,
})
}
#[must_use]
pub fn is_order_sensitive(self) -> bool {
matches!(self, Self::Divide | Self::Subtract)
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct UnitBasis {
pub id: EntityId,
pub value: Option<f64>,
pub measure: Option<&'static str>,
pub unit: Option<EntityId>,
}
#[derive(Debug, Clone, Copy)]
pub struct CostValue<'m> {
id: EntityId,
entity: &'m Entity,
}
impl<'m> CostValue<'m> {
#[must_use]
pub fn new(id: EntityId, entity: &'m Entity) -> Self {
Self { id, entity }
}
#[must_use]
pub fn id(&self) -> EntityId {
self.id
}
#[must_use]
pub fn name(&self) -> Option<&'m str> {
self.entity.text(slot::NAME)
}
#[must_use]
pub fn description(&self) -> Option<&'m str> {
self.entity.text(slot::DESCRIPTION)
}
#[must_use]
pub fn amount(&self) -> Option<f64> {
self.entity
.attribute(slot::APPLIED_VALUE)?
.unwrap_typed()
.as_f64()
}
#[must_use]
pub fn measure(&self) -> Option<&'m str> {
match self.entity.attribute(slot::APPLIED_VALUE)? {
Value::Typed { type_name, .. } => Some(type_name),
_ => None,
}
}
#[must_use]
pub fn is_monetary(&self) -> bool {
self.measure()
.is_some_and(|m| m.eq_ignore_ascii_case("IFCMONETARYMEASURE"))
}
#[must_use]
pub fn category(&self) -> Option<&'m str> {
self.entity.text(slot::CATEGORY)
}
#[must_use]
pub fn condition(&self) -> Option<&'m str> {
self.entity.text(slot::CONDITION)
}
#[must_use]
pub fn applicable_date(&self) -> Option<&'m str> {
self.entity.text(slot::APPLICABLE_DATE)
}
#[must_use]
pub fn fixed_until_date(&self) -> Option<&'m str> {
self.entity.text(slot::FIXED_UNTIL_DATE)
}
#[must_use]
pub fn operator(&self) -> Option<ArithmeticOperator> {
match self.entity.attribute(slot::ARITHMETIC_OPERATOR)? {
Value::Enum(token) => ArithmeticOperator::parse(token),
_ => None,
}
}
#[must_use]
pub fn component_refs(&self) -> Vec<EntityId> {
let mut out = Vec::new();
if let Some(v) = self.entity.attribute(slot::COMPONENTS) {
v.for_each_ref(&mut |id| out.push(id));
}
out
}
#[must_use]
pub fn is_composed(&self) -> bool {
!self.component_refs().is_empty()
}
#[must_use]
pub fn unit_basis(&self, model: &Model) -> Option<UnitBasis> {
let id = match self.entity.attribute(slot::UNIT_BASIS)? {
Value::Ref(id) => *id,
_ => return None,
};
let measure_with_unit = model.get(id)?;
let component = measure_with_unit.attribute(0);
Some(UnitBasis {
id,
value: component.and_then(|v| v.unwrap_typed().as_f64()),
measure: component.and_then(|v| match v {
Value::Typed { type_name, .. } => KNOWN_MEASURES
.iter()
.find(|m| type_name.eq_ignore_ascii_case(m))
.copied(),
_ => None,
}),
unit: match measure_with_unit.attribute(1) {
Some(Value::Ref(unit)) => Some(*unit),
_ => None,
},
})
}
}
const KNOWN_MEASURES: &[&str] = &[
"IFCVOLUMEMEASURE",
"IFCAREAMEASURE",
"IFCLENGTHMEASURE",
"IFCMASSMEASURE",
"IFCCOUNTMEASURE",
"IFCTIMEMEASURE",
"IFCMONETARYMEASURE",
];