1use ifc_model::EntityId;
4
5use crate::{
6 Material, MaterialAssignment, MaterialConstituent, MaterialConstituentSet, MaterialError,
7 MaterialLayer, MaterialLayerSet, MaterialLayerSetUsage, MaterialLayerWithOffsets, MaterialList,
8 MaterialProfile, MaterialProfileSet, MaterialProfileSetUsage, MaterialProfileSetUsageTapering,
9 MaterialProfileWithOffsets, MaterialResult, MaterialView,
10};
11
12#[derive(Debug, Clone, Copy)]
14pub enum MaterialDefinition<'m> {
15 Material(Material<'m>),
17 Constituent(MaterialConstituent<'m>),
19 ConstituentSet(MaterialConstituentSet<'m>),
21 Layer(MaterialLayer<'m>),
23 LayerWithOffsets(MaterialLayerWithOffsets<'m>),
25 LayerSet(MaterialLayerSet<'m>),
27 Profile(MaterialProfile<'m>),
29 ProfileWithOffsets(MaterialProfileWithOffsets<'m>),
31 ProfileSet(MaterialProfileSet<'m>),
33}
34
35#[derive(Debug, Clone, Copy)]
37pub enum MaterialUsageDefinition<'m> {
38 LayerSet(MaterialLayerSetUsage<'m>),
40 ProfileSet(MaterialProfileSetUsage<'m>),
42 ProfileSetTapering(MaterialProfileSetUsageTapering<'m>),
44}
45
46#[derive(Debug, Clone, Copy)]
48pub enum ResolvedMaterialSelect<'m> {
49 Definition(MaterialDefinition<'m>),
51 List(MaterialList<'m>),
53 Usage(MaterialUsageDefinition<'m>),
55}
56
57#[derive(Debug, Clone, Copy, PartialEq, Eq)]
60pub enum AssignmentSource {
61 Occurrence,
63 Type(EntityId),
66}
67
68#[derive(Debug, Clone, Copy)]
70pub struct ResolvedAssignment<'m> {
71 pub assignment: MaterialAssignment<'m>,
73 pub material: ResolvedMaterialSelect<'m>,
75 pub source: AssignmentSource,
77}
78
79impl<'m> MaterialView<'m> {
80 pub fn material_definitions(self) -> impl Iterator<Item = MaterialDefinition<'m>> + 'm {
82 self.model().iter().filter_map(move |(id, _)| {
83 match self.resolve_material_select(id).ok()? {
84 ResolvedMaterialSelect::Definition(definition) => Some(definition),
85 _ => None,
86 }
87 })
88 }
89
90 pub fn material_usage_definitions(
92 self,
93 ) -> impl Iterator<Item = MaterialUsageDefinition<'m>> + 'm {
94 self.model().iter().filter_map(move |(id, _)| {
95 match self.resolve_material_select(id).ok()? {
96 ResolvedMaterialSelect::Usage(usage) => Some(usage),
97 _ => None,
98 }
99 })
100 }
101
102 pub fn resolve_material_select(
107 self,
108 id: EntityId,
109 ) -> MaterialResult<ResolvedMaterialSelect<'m>> {
110 let entity = self.entity(id, id)?;
111 let entity_type = entity.type_name.to_ascii_uppercase();
112 let definition = match entity_type.as_str() {
113 "IFCMATERIAL" => MaterialDefinition::Material(Material::from_known(id, entity)),
114 "IFCMATERIALCONSTITUENT" => {
115 MaterialDefinition::Constituent(MaterialConstituent::from_known(id, entity))
116 }
117 "IFCMATERIALCONSTITUENTSET" => {
118 MaterialDefinition::ConstituentSet(MaterialConstituentSet::from_known(id, entity))
119 }
120 "IFCMATERIALLAYER" => MaterialDefinition::Layer(MaterialLayer::from_known(id, entity)),
121 "IFCMATERIALLAYERWITHOFFSETS" => MaterialDefinition::LayerWithOffsets(
122 MaterialLayerWithOffsets::from_known(id, entity),
123 ),
124 "IFCMATERIALLAYERSET" => {
125 MaterialDefinition::LayerSet(MaterialLayerSet::from_known(id, entity))
126 }
127 "IFCMATERIALPROFILE" => {
128 MaterialDefinition::Profile(MaterialProfile::from_known(id, entity))
129 }
130 "IFCMATERIALPROFILEWITHOFFSETS" => MaterialDefinition::ProfileWithOffsets(
131 MaterialProfileWithOffsets::from_known(id, entity),
132 ),
133 "IFCMATERIALPROFILESET" => {
134 MaterialDefinition::ProfileSet(MaterialProfileSet::from_known(id, entity))
135 }
136 "IFCMATERIALLIST" => {
137 return Ok(ResolvedMaterialSelect::List(MaterialList::from_known(
138 id, entity,
139 )));
140 }
141 "IFCMATERIALLAYERSETUSAGE" => {
142 return Ok(ResolvedMaterialSelect::Usage(
143 MaterialUsageDefinition::LayerSet(MaterialLayerSetUsage::from_known(
144 id, entity,
145 )),
146 ));
147 }
148 "IFCMATERIALPROFILESETUSAGE" => {
149 return Ok(ResolvedMaterialSelect::Usage(
150 MaterialUsageDefinition::ProfileSet(MaterialProfileSetUsage::from_known(
151 id, entity,
152 )),
153 ));
154 }
155 "IFCMATERIALPROFILESETUSAGETAPERING" => {
156 return Ok(ResolvedMaterialSelect::Usage(
157 MaterialUsageDefinition::ProfileSetTapering(
158 MaterialProfileSetUsageTapering::from_known(id, entity),
159 ),
160 ));
161 }
162 _ => {
163 return Err(MaterialError::WrongEntityType {
164 expected: "IfcMaterialSelect",
165 actual: entity.type_name.to_string(),
166 });
167 }
168 };
169 Ok(ResolvedMaterialSelect::Definition(definition))
170 }
171
172 pub fn assigned_material(
182 self,
183 object: EntityId,
184 ) -> MaterialResult<Option<ResolvedAssignment<'m>>> {
185 self.model()
186 .get(object)
187 .ok_or(MaterialError::UnknownEntity { id: object })?;
188
189 let direct = self.assignments_for(object)?;
190 if direct.len() > 1 {
191 return Err(MaterialError::AmbiguousAssignment {
192 object,
193 count: direct.len(),
194 });
195 }
196 if let Some(assignment) = direct.first().copied() {
197 return self
198 .resolve_assignment(assignment, AssignmentSource::Occurrence)
199 .map(Some);
200 }
201
202 let mut type_relations = Vec::new();
203 for (relation_id, relation) in self.model().of_type("IFCRELDEFINESBYTYPE") {
204 let related = crate::view::required_refs(
205 "IFCRELDEFINESBYTYPE",
206 relation_id,
207 relation,
208 4,
209 "RelatedObjects",
210 1,
211 )?;
212 if related.contains(&object) {
213 let type_id = crate::view::required_ref(
214 "IFCRELDEFINESBYTYPE",
215 relation_id,
216 relation,
217 5,
218 "RelatingType",
219 )?;
220 type_relations.push((relation_id, type_id));
221 }
222 }
223 if type_relations.len() > 1 {
224 return Err(MaterialError::AmbiguousType {
225 object,
226 count: type_relations.len(),
227 });
228 }
229 let Some((relation_id, type_id)) = type_relations.first().copied() else {
230 return Ok(None);
231 };
232 let type_entity = self.entity(relation_id, type_id)?;
233 if !super::ifc4_type_objects::is_concrete_type_object(&type_entity.type_name) {
234 return Err(MaterialError::ReferenceType {
235 source_id: relation_id,
236 target: type_id,
237 expected: "IFCTYPEOBJECT subtype",
238 actual: type_entity.type_name.to_string(),
239 });
240 }
241
242 let assigned = self.assignments_for(type_id)?;
243 if assigned.len() > 1 {
244 return Err(MaterialError::AmbiguousAssignment {
245 object: type_id,
246 count: assigned.len(),
247 });
248 }
249 assigned
250 .first()
251 .copied()
252 .map(|assignment| self.resolve_assignment(assignment, AssignmentSource::Type(type_id)))
253 .transpose()
254 }
255
256 fn resolve_assignment(
257 self,
258 assignment: MaterialAssignment<'m>,
259 source: AssignmentSource,
260 ) -> MaterialResult<ResolvedAssignment<'m>> {
261 let material_id = assignment.relating_material_id()?;
262 Ok(ResolvedAssignment {
263 assignment,
264 material: self.resolve_material_select(material_id)?,
265 source,
266 })
267 }
268}