use std::sync::Arc;
use ifc_model::{EntityId, Model};
use crate::error::PropertyAnomaly;
use crate::nesting::Nesting;
use crate::quantity::complex::complex_quantities;
use crate::quantity::simple::{read_simple, text, value_slots, UnresolvedValue};
use crate::unit::UnitKind;
const NAME: usize = 0;
const COMPLEX_DISCRIMINATION: usize = 3;
const SET_METHOD: usize = 4;
const SET_QUANTITIES: usize = 5;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum QuantityKind {
Length,
Area,
Volume,
Count,
Weight,
Time,
Number,
}
impl QuantityKind {
#[must_use]
pub fn from_type_name(name: &str) -> Option<Self> {
Self::from_type(&name.to_ascii_uppercase())
}
#[must_use]
pub fn type_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",
}
}
#[must_use]
pub fn measure_type(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 from_type(name: &str) -> Option<Self> {
Some(match name {
"IFCQUANTITYLENGTH" => Self::Length,
"IFCQUANTITYAREA" => Self::Area,
"IFCQUANTITYVOLUME" => Self::Volume,
"IFCQUANTITYCOUNT" => Self::Count,
"IFCQUANTITYWEIGHT" => Self::Weight,
"IFCQUANTITYTIME" => Self::Time,
"IFCQUANTITYNUMBER" => Self::Number,
_ => return None,
})
}
pub fn required_unit(self) -> Option<&'static str> {
Some(match self {
Self::Length => "LENGTHUNIT",
Self::Area => "AREAUNIT",
Self::Volume => "VOLUMEUNIT",
Self::Weight => "MASSUNIT",
Self::Time => "TIMEUNIT",
Self::Count | Self::Number => return None,
})
}
pub(crate) 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",
}
}
pub(crate) fn requires_non_negative(self) -> bool {
self != Self::Number
}
}
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub enum Quantity {
Simple {
id: EntityId,
name: Option<Arc<str>>,
description: Option<Arc<str>>,
kind: QuantityKind,
value: f64,
unit: Option<EntityId>,
formula: Option<Arc<str>>,
},
Unresolved {
id: EntityId,
name: Option<Arc<str>>,
description: Option<Arc<str>>,
kind: QuantityKind,
unit: Option<EntityId>,
formula: Option<Arc<str>>,
reason: UnresolvedValue,
},
Complex {
id: EntityId,
name: Option<Arc<str>>,
discrimination: Option<Arc<str>>,
quantities: Vec<Quantity>,
},
Unsupported {
id: EntityId,
type_name: Arc<str>,
},
}
impl Quantity {
pub fn id(&self) -> EntityId {
match self {
Self::Simple { id, .. }
| Self::Unresolved { id, .. }
| Self::Complex { id, .. }
| Self::Unsupported { id, .. } => *id,
}
}
pub fn name(&self) -> Option<&str> {
match self {
Self::Simple { name, .. }
| Self::Unresolved { name, .. }
| Self::Complex { name, .. } => name.as_deref(),
Self::Unsupported { .. } => None,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct QuantitySet {
pub id: EntityId,
pub name: Option<Arc<str>>,
pub method: Option<Arc<str>>,
pub quantities: Vec<Quantity>,
}
impl QuantitySet {
pub fn quantity(&self, name: &str) -> Option<&Quantity> {
self.quantities.iter().find(|q| q.name() == Some(name))
}
}
pub fn quantity_set(model: &Model, id: EntityId) -> Option<(QuantitySet, Vec<PropertyAnomaly>)> {
let entity = model.get(id)?;
if !entity.type_name.eq_ignore_ascii_case("IFCELEMENTQUANTITY") {
return None;
}
let mut anomalies = Vec::new();
let mut nesting = Nesting::new(&mut anomalies);
let mut quantities = Vec::new();
for member in nesting.members(id, "Quantities", entity.attributes.get(SET_QUANTITIES)) {
if !nesting.admit(model, id, member) {
continue;
}
if let Some(quantity) = read_quantity(model, member, &mut nesting) {
quantities.push(quantity);
}
}
Some((
QuantitySet {
id,
name: entity.attributes.get(2).and_then(text),
method: entity.attributes.get(SET_METHOD).and_then(text),
quantities,
},
anomalies,
))
}
pub(super) fn read_quantity(
model: &Model,
id: EntityId,
nesting: &mut Nesting<'_>,
) -> Option<Quantity> {
let entity = model.get(id)?;
let ty = entity.type_name.to_ascii_uppercase();
let name = entity.attributes.get(NAME).and_then(text);
if ty == "IFCPHYSICALCOMPLEXQUANTITY" {
return Some(Quantity::Complex {
id,
name,
discrimination: entity.attributes.get(COMPLEX_DISCRIMINATION).and_then(text),
quantities: complex_quantities(model, id, entity, nesting),
});
}
let Some(kind) = QuantityKind::from_type(&ty) else {
return Some(Quantity::Unsupported {
id,
type_name: ty.as_str().into(),
});
};
let Some(slots) = value_slots(model, kind) else {
return Some(Quantity::Unsupported {
id,
type_name: ty.as_str().into(),
});
};
Some(read_simple(model, id, entity, kind, slots, name, nesting))
}
pub fn quantity_sets(model: &Model) -> (Vec<QuantitySet>, Vec<PropertyAnomaly>) {
let mut sets = Vec::new();
let mut anomalies = Vec::new();
let mut ids: Vec<_> = model.ids_of_type("IFCELEMENTQUANTITY").to_vec();
ids.sort_unstable();
for id in ids {
if let Some((set, mut found)) = quantity_set(model, id) {
sets.push(set);
anomalies.append(&mut found);
}
}
(sets, anomalies)
}
pub fn stated_unit(model: &Model, quantity: &Quantity) -> Option<UnitKind> {
match quantity {
Quantity::Simple { unit, .. } | Quantity::Unresolved { unit, .. } => {
let id = (*unit)?;
crate::unit::unit(model, id)
}
_ => None,
}
}