1use ifc_model::{Entity, EntityId};
8
9use crate::release::{is_type_object, Release};
10use crate::view::{required_ref, required_refs};
11use crate::{
12 Material, MaterialAssignment, MaterialConstituent, MaterialConstituentSet, MaterialError,
13 MaterialLayer, MaterialLayerSet, MaterialLayerSetUsage, MaterialLayerWithOffsets, MaterialList,
14 MaterialProfile, MaterialProfileSet, MaterialProfileSetUsage, MaterialProfileSetUsageTapering,
15 MaterialProfileWithOffsets, MaterialResult, MaterialView,
16};
17
18#[derive(Debug, Clone, Copy)]
25pub enum MaterialDefinition<'m> {
26 Material(Material<'m>),
28 Constituent(MaterialConstituent<'m>),
30 ConstituentSet(MaterialConstituentSet<'m>),
32 Layer(MaterialLayer<'m>),
34 LayerWithOffsets(MaterialLayerWithOffsets<'m>),
36 LayerSet(MaterialLayerSet<'m>),
38 Profile(MaterialProfile<'m>),
40 ProfileWithOffsets(MaterialProfileWithOffsets<'m>),
42 ProfileSet(MaterialProfileSet<'m>),
44}
45
46#[derive(Debug, Clone, Copy)]
51pub enum MaterialUsageDefinition<'m> {
52 LayerSet(MaterialLayerSetUsage<'m>),
54 ProfileSet(MaterialProfileSetUsage<'m>),
56 ProfileSetTapering(MaterialProfileSetUsageTapering<'m>),
58}
59
60#[derive(Debug, Clone, Copy)]
62pub enum ResolvedMaterialSelect<'m> {
63 Definition(MaterialDefinition<'m>),
65 List(MaterialList<'m>),
67 Usage(MaterialUsageDefinition<'m>),
69}
70
71#[derive(Debug, Clone, Copy, PartialEq, Eq)]
74pub enum AssignmentSource {
75 Occurrence,
77 Type(EntityId),
80}
81
82#[derive(Debug, Clone, Copy)]
84pub struct ResolvedAssignment<'m> {
85 pub assignment: MaterialAssignment<'m>,
87 pub material: ResolvedMaterialSelect<'m>,
89 pub source: AssignmentSource,
91}
92
93pub(crate) const SELECT_MEMBERS: &[&str] = &[
97 "IFCMATERIAL",
98 "IFCMATERIALCONSTITUENT",
99 "IFCMATERIALCONSTITUENTSET",
100 "IFCMATERIALLAYER",
101 "IFCMATERIALLAYERWITHOFFSETS",
102 "IFCMATERIALLAYERSET",
103 "IFCMATERIALPROFILE",
104 "IFCMATERIALPROFILEWITHOFFSETS",
105 "IFCMATERIALPROFILESET",
106 "IFCMATERIALLIST",
107 "IFCMATERIALLAYERSETUSAGE",
108 "IFCMATERIALPROFILESETUSAGE",
109 "IFCMATERIALPROFILESETUSAGETAPERING",
110];
111
112fn classify<'m>(
115 id: EntityId,
116 entity: &'m Entity,
117 release: Release<'m>,
118) -> Option<(&'static str, ResolvedMaterialSelect<'m>)> {
119 use MaterialDefinition as D;
120 use MaterialUsageDefinition as U;
121 use ResolvedMaterialSelect as S;
122 let name = SELECT_MEMBERS
123 .iter()
124 .copied()
125 .find(|name| entity.is_type(name))?;
126 let resolved = match name {
127 "IFCMATERIAL" => S::Definition(D::Material(Material::from_known(id, entity, release))),
128 "IFCMATERIALCONSTITUENT" => S::Definition(D::Constituent(MaterialConstituent::from_known(
129 id, entity, release,
130 ))),
131 "IFCMATERIALCONSTITUENTSET" => S::Definition(D::ConstituentSet(
132 MaterialConstituentSet::from_known(id, entity, release),
133 )),
134 "IFCMATERIALLAYER" => {
135 S::Definition(D::Layer(MaterialLayer::from_known(id, entity, release)))
136 }
137 "IFCMATERIALLAYERWITHOFFSETS" => S::Definition(D::LayerWithOffsets(
138 MaterialLayerWithOffsets::from_known(id, entity, release),
139 )),
140 "IFCMATERIALLAYERSET" => S::Definition(D::LayerSet(MaterialLayerSet::from_known(
141 id, entity, release,
142 ))),
143 "IFCMATERIALPROFILE" => {
144 S::Definition(D::Profile(MaterialProfile::from_known(id, entity, release)))
145 }
146 "IFCMATERIALPROFILEWITHOFFSETS" => S::Definition(D::ProfileWithOffsets(
147 MaterialProfileWithOffsets::from_known(id, entity, release),
148 )),
149 "IFCMATERIALPROFILESET" => S::Definition(D::ProfileSet(MaterialProfileSet::from_known(
150 id, entity, release,
151 ))),
152 "IFCMATERIALLIST" => S::List(MaterialList::from_known(id, entity, release)),
153 "IFCMATERIALLAYERSETUSAGE" => S::Usage(U::LayerSet(MaterialLayerSetUsage::from_known(
154 id, entity, release,
155 ))),
156 "IFCMATERIALPROFILESETUSAGE" => S::Usage(U::ProfileSet(
157 MaterialProfileSetUsage::from_known(id, entity, release),
158 )),
159 _ => S::Usage(U::ProfileSetTapering(
160 MaterialProfileSetUsageTapering::from_known(id, entity, release),
161 )),
162 };
163 Some((name, resolved))
164}
165
166impl<'m> MaterialView<'m> {
167 pub fn material_definitions(self) -> impl Iterator<Item = MaterialDefinition<'m>> + 'm {
173 let release = self.release();
174 self.model()
175 .iter()
176 .filter_map(move |(id, entity)| match classify(id, entity, release)?.1 {
177 ResolvedMaterialSelect::Definition(definition) => Some(definition),
178 _ => None,
179 })
180 }
181
182 pub fn material_usage_definitions(
186 self,
187 ) -> impl Iterator<Item = MaterialUsageDefinition<'m>> + 'm {
188 let release = self.release();
189 self.model()
190 .iter()
191 .filter_map(move |(id, entity)| match classify(id, entity, release)?.1 {
192 ResolvedMaterialSelect::Usage(usage) => Some(usage),
193 _ => None,
194 })
195 }
196
197 pub fn resolve_material_select(
209 self,
210 id: EntityId,
211 ) -> MaterialResult<ResolvedMaterialSelect<'m>> {
212 let entity = self.entity(id, id)?;
213 let wrong = || MaterialError::WrongEntityType {
214 expected: "IfcMaterialSelect",
215 actual: entity.type_name.to_string(),
216 };
217 let (name, resolved) = classify(id, entity, self.release()).ok_or_else(wrong)?;
218 let (_, schema) = self.release().require_entity(name, Some(id))?;
219 if !schema.accepts_type("IfcMaterialSelect", name) {
220 return Err(wrong());
221 }
222 Ok(resolved)
223 }
224
225 pub fn assigned_material(
244 self,
245 object: EntityId,
246 ) -> MaterialResult<Option<ResolvedAssignment<'m>>> {
247 self.model()
248 .get(object)
249 .ok_or(MaterialError::UnknownEntity { id: object })?;
250
251 let direct = self.assignments_for(object)?;
252 if direct.len() > 1 {
253 return Err(MaterialError::AmbiguousAssignment {
254 object,
255 count: direct.len(),
256 });
257 }
258 if let Some(assignment) = direct.first().copied() {
259 return self
260 .resolve_assignment(assignment, AssignmentSource::Occurrence)
261 .map(Some);
262 }
263
264 let Some((relation_id, type_id)) = self.type_relation(object)? else {
265 return Ok(None);
266 };
267 let type_entity = self.entity(relation_id, type_id)?;
268 if !is_type_object(self.release(), &type_entity.type_name)? {
269 return Err(MaterialError::ReferenceType {
270 source_id: relation_id,
271 target: type_id,
272 expected: "IFCTYPEOBJECT subtype",
273 actual: type_entity.type_name.to_string(),
274 });
275 }
276
277 let assigned = self.assignments_for(type_id)?;
278 if assigned.len() > 1 {
279 return Err(MaterialError::AmbiguousAssignment {
280 object: type_id,
281 count: assigned.len(),
282 });
283 }
284 assigned
285 .first()
286 .copied()
287 .map(|assignment| self.resolve_assignment(assignment, AssignmentSource::Type(type_id)))
288 .transpose()
289 }
290
291 fn type_relation(self, object: EntityId) -> MaterialResult<Option<(EntityId, EntityId)>> {
294 const ENTITY: &str = "IFCRELDEFINESBYTYPE";
295 let release = self.release();
296 let mut type_relations = Vec::new();
297 for (relation_id, relation) in self.model().of_type(ENTITY) {
298 let related_slot = release.slot(ENTITY, relation_id, "RelatedObjects")?;
299 let related = required_refs(
300 ENTITY,
301 relation_id,
302 relation,
303 related_slot,
304 "RelatedObjects",
305 1,
306 )?;
307 if related.contains(&object) {
308 let type_slot = release.slot(ENTITY, relation_id, "RelatingType")?;
309 let type_id =
310 required_ref(ENTITY, relation_id, relation, type_slot, "RelatingType")?;
311 type_relations.push((relation_id, type_id));
312 }
313 }
314 if type_relations.len() > 1 {
315 return Err(MaterialError::AmbiguousType {
316 object,
317 count: type_relations.len(),
318 });
319 }
320 Ok(type_relations.first().copied())
321 }
322
323 fn resolve_assignment(
324 self,
325 assignment: MaterialAssignment<'m>,
326 source: AssignmentSource,
327 ) -> MaterialResult<ResolvedAssignment<'m>> {
328 let material_id = assignment.relating_material_id()?;
329 Ok(ResolvedAssignment {
330 assignment,
331 material: self.resolve_material_select(material_id)?,
332 source,
333 })
334 }
335}