use ifc_model::{EntityId, Model, Transaction, Value};
use super::release::{bind, Release};
use super::validate::invalid;
use super::{CostAuthoringError, CostAuthoringResult};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum QuantityKind {
Length,
Area,
Volume,
Count,
Weight,
Time,
Number,
}
impl QuantityKind {
pub fn entity_name(self) -> &'static str {
match self {
Self::Length => "IFCQUANTITYLENGTH",
Self::Area => "IFCQUANTITYAREA",
Self::Volume => "IFCQUANTITYVOLUME",
Self::Count => "IFCQUANTITYCOUNT",
Self::Weight => "IFCQUANTITYWEIGHT",
Self::Time => "IFCQUANTITYTIME",
Self::Number => "IFCQUANTITYNUMBER",
}
}
pub fn measure_name(self) -> &'static str {
match self {
Self::Length => "IFCLENGTHMEASURE",
Self::Area => "IFCAREAMEASURE",
Self::Volume => "IFCVOLUMEMEASURE",
Self::Count => "IFCCOUNTMEASURE",
Self::Weight => "IFCMASSMEASURE",
Self::Time => "IFCTIMEMEASURE",
Self::Number => "IFCNUMERICMEASURE",
}
}
fn value_attribute(self) -> &'static str {
match self {
Self::Length => "LengthValue",
Self::Area => "AreaValue",
Self::Volume => "VolumeValue",
Self::Count => "CountValue",
Self::Weight => "WeightValue",
Self::Time => "TimeValue",
Self::Number => "NumberValue",
}
}
}
#[derive(Debug, Clone, Copy)]
#[non_exhaustive]
pub struct QuantityDraft<'a> {
pub kind: QuantityKind,
pub name: &'a str,
pub description: Option<&'a str>,
pub unit: Option<EntityId>,
pub value: f64,
pub formula: Option<&'a str>,
}
pub fn create_quantity(
tx: &mut Transaction,
model: &Model,
draft: QuantityDraft<'_>,
) -> CostAuthoringResult<EntityId> {
let release = bind(model)?;
let entity = draft.kind.entity_name();
if draft.name.trim().is_empty() {
return Err(invalid(entity, "Name", "expected a non-empty name"));
}
if !draft.value.is_finite() {
return Err(invalid(entity, "Value", "expected a finite measure"));
}
if draft.kind != QuantityKind::Number && draft.value < 0.0 {
return Err(invalid(
entity,
"Value",
"expected a finite non-negative measure",
));
}
if draft.kind == QuantityKind::Count && draft.value.fract() != 0.0 {
return Err(invalid(entity, "CountValue", "expected a whole count"));
}
if let Some(unit) = draft.unit {
super::validate::reference_type(tx, model, entity, "Unit", unit, "IFCNAMEDUNIT")?;
}
let value = if draft.kind == QuantityKind::Count {
#[allow(clippy::cast_possible_truncation)]
Value::Integer(draft.value as i64)
} else {
Value::Real(draft.value)
};
let text = |text: Option<&str>| text.map_or(Value::Null, |text| Value::Text(text.into()));
let record = release.record(
entity,
vec![
("Name", Value::Text(draft.name.into())),
("Description", text(draft.description)),
("Unit", draft.unit.map_or(Value::Null, Value::Ref)),
(draft.kind.value_attribute(), value),
("Formula", text(draft.formula)),
],
)?;
Ok(tx.create(record))
}
pub fn assign_cost_quantities(
tx: &mut Transaction,
model: &Model,
cost_item: EntityId,
quantities: &[EntityId],
) -> CostAuthoringResult<()> {
const ENTITY: &str = "IFCCOSTITEM";
let release = bind(model)?;
super::validate::reference_type(tx, model, ENTITY, "CostQuantities", cost_item, ENTITY)?;
let (slot, declared) = release.declared(ENTITY, "CostQuantities")?;
super::validate::non_empty_unique(ENTITY, "CostQuantities", quantities)?;
for &quantity in quantities {
require_quantity(tx, model, release, &declared.type_name, quantity)?;
}
tx.set_attribute(
cost_item,
slot,
Value::List(quantities.iter().copied().map(Value::Ref).collect()),
);
Ok(())
}
fn require_quantity(
tx: &Transaction,
model: &Model,
release: Release,
declared: &str,
target: EntityId,
) -> CostAuthoringResult<()> {
let Some(actual) = super::validate::projected_type(tx, model, target) else {
return Err(CostAuthoringError::MissingReference {
entity: "IFCCOSTITEM",
attribute: "CostQuantities",
target,
});
};
let schema = release.schema();
let instantiable = schema.entity(&actual).is_some_and(|e| !e.abstract_);
if instantiable && schema.accepts_type(declared, &actual) {
return Ok(());
}
Err(CostAuthoringError::WrongReferenceType {
entity: "IFCCOSTITEM",
attribute: "CostQuantities",
target,
actual,
expected: "IFCPHYSICALQUANTITY",
})
}
impl<'a> QuantityDraft<'a> {
#[must_use]
pub const fn new(kind: QuantityKind, name: &'a str, value: f64) -> Self {
Self {
kind,
name,
description: None,
unit: None,
value,
formula: None,
}
}
#[must_use]
pub fn description(mut self, value: &'a str) -> Self {
self.description = Some(value);
self
}
#[must_use]
pub fn unit(mut self, value: EntityId) -> Self {
self.unit = Some(value);
self
}
#[must_use]
pub fn formula(mut self, value: &'a str) -> Self {
self.formula = Some(value);
self
}
}