use ifc_model::{Entity, EntityId};
use crate::release::{is_type_object, Release};
use crate::view::{required_ref, required_refs};
use crate::{
Material, MaterialAssignment, MaterialConstituent, MaterialConstituentSet, MaterialError,
MaterialLayer, MaterialLayerSet, MaterialLayerSetUsage, MaterialLayerWithOffsets, MaterialList,
MaterialProfile, MaterialProfileSet, MaterialProfileSetUsage, MaterialProfileSetUsageTapering,
MaterialProfileWithOffsets, MaterialResult, MaterialView,
};
#[derive(Debug, Clone, Copy)]
pub enum MaterialDefinition<'m> {
Material(Material<'m>),
Constituent(MaterialConstituent<'m>),
ConstituentSet(MaterialConstituentSet<'m>),
Layer(MaterialLayer<'m>),
LayerWithOffsets(MaterialLayerWithOffsets<'m>),
LayerSet(MaterialLayerSet<'m>),
Profile(MaterialProfile<'m>),
ProfileWithOffsets(MaterialProfileWithOffsets<'m>),
ProfileSet(MaterialProfileSet<'m>),
}
#[derive(Debug, Clone, Copy)]
pub enum MaterialUsageDefinition<'m> {
LayerSet(MaterialLayerSetUsage<'m>),
ProfileSet(MaterialProfileSetUsage<'m>),
ProfileSetTapering(MaterialProfileSetUsageTapering<'m>),
}
#[derive(Debug, Clone, Copy)]
pub enum ResolvedMaterialSelect<'m> {
Definition(MaterialDefinition<'m>),
List(MaterialList<'m>),
Usage(MaterialUsageDefinition<'m>),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AssignmentSource {
Occurrence,
Type(EntityId),
}
#[derive(Debug, Clone, Copy)]
pub struct ResolvedAssignment<'m> {
pub assignment: MaterialAssignment<'m>,
pub material: ResolvedMaterialSelect<'m>,
pub source: AssignmentSource,
}
pub(crate) const SELECT_MEMBERS: &[&str] = &[
"IFCMATERIAL",
"IFCMATERIALCONSTITUENT",
"IFCMATERIALCONSTITUENTSET",
"IFCMATERIALLAYER",
"IFCMATERIALLAYERWITHOFFSETS",
"IFCMATERIALLAYERSET",
"IFCMATERIALPROFILE",
"IFCMATERIALPROFILEWITHOFFSETS",
"IFCMATERIALPROFILESET",
"IFCMATERIALLIST",
"IFCMATERIALLAYERSETUSAGE",
"IFCMATERIALPROFILESETUSAGE",
"IFCMATERIALPROFILESETUSAGETAPERING",
];
fn classify<'m>(
id: EntityId,
entity: &'m Entity,
release: Release<'m>,
) -> Option<(&'static str, ResolvedMaterialSelect<'m>)> {
use MaterialDefinition as D;
use MaterialUsageDefinition as U;
use ResolvedMaterialSelect as S;
let name = SELECT_MEMBERS
.iter()
.copied()
.find(|name| entity.is_type(name))?;
let resolved = match name {
"IFCMATERIAL" => S::Definition(D::Material(Material::from_known(id, entity, release))),
"IFCMATERIALCONSTITUENT" => S::Definition(D::Constituent(MaterialConstituent::from_known(
id, entity, release,
))),
"IFCMATERIALCONSTITUENTSET" => S::Definition(D::ConstituentSet(
MaterialConstituentSet::from_known(id, entity, release),
)),
"IFCMATERIALLAYER" => {
S::Definition(D::Layer(MaterialLayer::from_known(id, entity, release)))
}
"IFCMATERIALLAYERWITHOFFSETS" => S::Definition(D::LayerWithOffsets(
MaterialLayerWithOffsets::from_known(id, entity, release),
)),
"IFCMATERIALLAYERSET" => S::Definition(D::LayerSet(MaterialLayerSet::from_known(
id, entity, release,
))),
"IFCMATERIALPROFILE" => {
S::Definition(D::Profile(MaterialProfile::from_known(id, entity, release)))
}
"IFCMATERIALPROFILEWITHOFFSETS" => S::Definition(D::ProfileWithOffsets(
MaterialProfileWithOffsets::from_known(id, entity, release),
)),
"IFCMATERIALPROFILESET" => S::Definition(D::ProfileSet(MaterialProfileSet::from_known(
id, entity, release,
))),
"IFCMATERIALLIST" => S::List(MaterialList::from_known(id, entity, release)),
"IFCMATERIALLAYERSETUSAGE" => S::Usage(U::LayerSet(MaterialLayerSetUsage::from_known(
id, entity, release,
))),
"IFCMATERIALPROFILESETUSAGE" => S::Usage(U::ProfileSet(
MaterialProfileSetUsage::from_known(id, entity, release),
)),
_ => S::Usage(U::ProfileSetTapering(
MaterialProfileSetUsageTapering::from_known(id, entity, release),
)),
};
Some((name, resolved))
}
impl<'m> MaterialView<'m> {
pub fn material_definitions(self) -> impl Iterator<Item = MaterialDefinition<'m>> + 'm {
let release = self.release();
self.model()
.iter()
.filter_map(move |(id, entity)| match classify(id, entity, release)?.1 {
ResolvedMaterialSelect::Definition(definition) => Some(definition),
_ => None,
})
}
pub fn material_usage_definitions(
self,
) -> impl Iterator<Item = MaterialUsageDefinition<'m>> + 'm {
let release = self.release();
self.model()
.iter()
.filter_map(move |(id, entity)| match classify(id, entity, release)?.1 {
ResolvedMaterialSelect::Usage(usage) => Some(usage),
_ => None,
})
}
pub fn resolve_material_select(
self,
id: EntityId,
) -> MaterialResult<ResolvedMaterialSelect<'m>> {
let entity = self.entity(id, id)?;
let wrong = || MaterialError::WrongEntityType {
expected: "IfcMaterialSelect",
actual: entity.type_name.to_string(),
};
let (name, resolved) = classify(id, entity, self.release()).ok_or_else(wrong)?;
let (_, schema) = self.release().require_entity(name, Some(id))?;
if !schema.accepts_type("IfcMaterialSelect", name) {
return Err(wrong());
}
Ok(resolved)
}
pub fn assigned_material(
self,
object: EntityId,
) -> MaterialResult<Option<ResolvedAssignment<'m>>> {
self.model()
.get(object)
.ok_or(MaterialError::UnknownEntity { id: object })?;
let direct = self.assignments_for(object)?;
if direct.len() > 1 {
return Err(MaterialError::AmbiguousAssignment {
object,
count: direct.len(),
});
}
if let Some(assignment) = direct.first().copied() {
return self
.resolve_assignment(assignment, AssignmentSource::Occurrence)
.map(Some);
}
let Some((relation_id, type_id)) = self.type_relation(object)? else {
return Ok(None);
};
let type_entity = self.entity(relation_id, type_id)?;
if !is_type_object(self.release(), &type_entity.type_name)? {
return Err(MaterialError::ReferenceType {
source_id: relation_id,
target: type_id,
expected: "IFCTYPEOBJECT subtype",
actual: type_entity.type_name.to_string(),
});
}
let assigned = self.assignments_for(type_id)?;
if assigned.len() > 1 {
return Err(MaterialError::AmbiguousAssignment {
object: type_id,
count: assigned.len(),
});
}
assigned
.first()
.copied()
.map(|assignment| self.resolve_assignment(assignment, AssignmentSource::Type(type_id)))
.transpose()
}
fn type_relation(self, object: EntityId) -> MaterialResult<Option<(EntityId, EntityId)>> {
const ENTITY: &str = "IFCRELDEFINESBYTYPE";
let release = self.release();
let mut type_relations = Vec::new();
for (relation_id, relation) in self.model().of_type(ENTITY) {
let related_slot = release.slot(ENTITY, relation_id, "RelatedObjects")?;
let related = required_refs(
ENTITY,
relation_id,
relation,
related_slot,
"RelatedObjects",
1,
)?;
if related.contains(&object) {
let type_slot = release.slot(ENTITY, relation_id, "RelatingType")?;
let type_id =
required_ref(ENTITY, relation_id, relation, type_slot, "RelatingType")?;
type_relations.push((relation_id, type_id));
}
}
if type_relations.len() > 1 {
return Err(MaterialError::AmbiguousType {
object,
count: type_relations.len(),
});
}
Ok(type_relations.first().copied())
}
fn resolve_assignment(
self,
assignment: MaterialAssignment<'m>,
source: AssignmentSource,
) -> MaterialResult<ResolvedAssignment<'m>> {
let material_id = assignment.relating_material_id()?;
Ok(ResolvedAssignment {
assignment,
material: self.resolve_material_select(material_id)?,
source,
})
}
}