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