use ifc_model::EntityId;
use crate::constituent::MaterialConstituent;
use crate::view::{borrowed_entity, optional_refs, optional_text, MaterialView};
use crate::{MaterialError, MaterialResult};
borrowed_entity!(MaterialConstituentSet, "IFCMATERIALCONSTITUENTSET");
impl<'m> MaterialConstituentSet<'m> {
pub fn name(self) -> MaterialResult<Option<&'m str>> {
optional_text(
"IFCMATERIALCONSTITUENTSET",
self.id(),
self.entity(),
self.slot("Name")?,
"Name",
)
}
pub fn description(self) -> MaterialResult<Option<&'m str>> {
optional_text(
"IFCMATERIALCONSTITUENTSET",
self.id(),
self.entity(),
self.slot("Description")?,
"Description",
)
}
pub fn constituent_ids(self) -> MaterialResult<Option<Vec<EntityId>>> {
optional_refs(
"IFCMATERIALCONSTITUENTSET",
self.id(),
self.entity(),
self.slot("MaterialConstituents")?,
"MaterialConstituents",
1,
)
}
}
impl<'m> MaterialView<'m> {
pub fn constituent_sets(self) -> impl Iterator<Item = MaterialConstituentSet<'m>> + 'm {
let release = self.release();
self.model()
.of_type("IFCMATERIALCONSTITUENTSET")
.map(move |(id, entity)| MaterialConstituentSet::from_known(id, entity, release))
}
}
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub enum ConstituentFractionDiagnostic {
SumNotOne {
set: EntityId,
sum: f64,
tolerance: f64,
},
PartiallyStated {
set: EntityId,
stated: Vec<EntityId>,
missing: Vec<EntityId>,
stated_sum: f64,
},
}
impl<'m> MaterialView<'m> {
pub fn constituent_fraction_diagnostic(
self,
set: MaterialConstituentSet<'m>,
tolerance: f64,
) -> MaterialResult<Option<ConstituentFractionDiagnostic>> {
let Some(ids) = set.constituent_ids()? else {
return Ok(None);
};
let mut stated = Vec::new();
let mut missing = Vec::new();
let mut sum = 0.0;
for id in ids {
let entity = self.entity(set.id(), id)?;
if !entity.is_type("IFCMATERIALCONSTITUENT") {
return Err(MaterialError::ReferenceType {
source_id: set.id(),
target: id,
expected: "IFCMATERIALCONSTITUENT",
actual: entity.type_name.to_string(),
});
}
match MaterialConstituent::from_known(id, entity, set.release()).fraction()? {
Some(fraction) => {
stated.push(id);
sum += fraction;
}
None => missing.push(id),
}
}
if stated.is_empty() {
return Ok(None);
}
if !missing.is_empty() {
return Ok(Some(ConstituentFractionDiagnostic::PartiallyStated {
set: set.id(),
stated,
missing,
stated_sum: sum,
}));
}
if (sum - 1.0_f64).abs() <= tolerance {
return Ok(None);
}
Ok(Some(ConstituentFractionDiagnostic::SumNotOne {
set: set.id(),
sum,
tolerance,
}))
}
}