ifc_lite_server/services/data_model/
materials.rs1use super::material_units::MaterialUnitContext;
8use super::types::{EntityJob, MaterialAssociation};
9use ifc_lite_core::{EntityDecoder, IfcType};
10use rayon::prelude::*;
11use std::collections::HashMap;
12use std::sync::Arc;
13
14struct ResolvedLayer {
16 kind: &'static str,
17 member_count: u32,
18 set_name: Option<String>,
19 layer_index: u32,
20 material_name: String,
21 material_name_present: bool,
22 material_id: Option<u32>,
23 member_name: Option<String>,
24 material_category: Option<String>,
25 fraction: Option<f64>,
26 thickness: Option<f64>,
27 is_ventilated: Option<bool>,
28 category: Option<String>,
29}
30
31fn material_details_of(decoder: &mut EntityDecoder, material_id: u32) -> Option<(String, Option<String>, bool)> {
33 let mat = decoder.decode_by_id(material_id).ok()?;
34 if !mat.ifc_type.is_subtype_of(IfcType::IfcMaterial) {
35 return None;
36 }
37 Some((mat.get_string(0).unwrap_or("").to_string(), mat.get_string(2).map(str::to_string), mat.get_string(0).is_some()))
39}
40
41fn resolve_material(decoder: &mut EntityDecoder, id: u32, unit_scale: f64) -> Vec<ResolvedLayer> {
46 let Ok(entity) = decoder.decode_by_id(id) else {
47 return Vec::new();
48 };
49 let ty = entity.ifc_type.as_str().to_ascii_uppercase();
50
51 match ty.as_str() {
52 "IFCMATERIAL" => {
53 vec![ResolvedLayer {
54 kind: "IfcMaterial",
55 member_count: 1,
56 set_name: None,
57 layer_index: 0,
58 material_name: entity.get_string(0).unwrap_or("").to_string(),
59 material_name_present: entity.get_string(0).is_some(),
60 material_id: Some(id),
61 member_name: None,
62 material_category: entity.get_string(2).map(str::to_string),
63 fraction: None,
64 thickness: None,
65 is_ventilated: None,
66 category: entity.get_string(2).map(str::to_string),
67 }]
68 },
69 "IFCMATERIALLAYERSETUSAGE" => {
70 match entity.get_ref(0) {
72 Some(set_id) => resolve_material(decoder, set_id, unit_scale),
73 None => Vec::new(),
74 }
75 }
76 "IFCMATERIALLAYERSET" => {
77 let set_name = entity.get_string(1).map(|s| s.to_string());
78 let member_count = entity.get_list(0).map_or(0, |l| l.len() as u32);
79 let layer_ids: Vec<u32> = entity
80 .get_list(0)
81 .map(|l| l.iter().filter_map(|v| v.as_entity_ref()).collect())
82 .unwrap_or_default();
83 layer_ids
84 .iter()
85 .copied()
86 .enumerate()
87 .filter_map(|(i, layer_id)| {
88 let layer = decoder.decode_by_id(layer_id).ok()?;
89 if !layer.ifc_type.is_subtype_of(IfcType::IfcMaterialLayer) {
90 return None;
91 }
92 let (material_name, material_category, material_name_present) = match layer.get_ref(0) {
95 Some(mid) => material_details_of(decoder, mid)?,
96 None => (String::new(), None, false),
97 };
98 let thickness = layer.get_float(1).map(|t| t * unit_scale);
99 let is_ventilated = super::read_logical(&layer, 2);
100 let category = layer.get_string(5).map(|s| s.to_string());
101 Some(ResolvedLayer {
102 kind: "IfcMaterialLayerSet",
103 member_count,
104 set_name: set_name.clone(),
105 layer_index: i as u32,
106 material_name,
107 material_name_present,
108 material_id: layer.get_ref(0),
109 member_name: layer.get_string(3).map(str::to_string),
110 material_category,
111 fraction: None,
112 thickness,
113 is_ventilated,
114 category,
115 })
116 })
117 .collect()
118 }
119 "IFCMATERIALLIST" => {
120 let member_count = entity.get_list(0).map_or(0, |l| l.len() as u32);
121 let mat_ids: Vec<u32> = entity
122 .get_list(0)
123 .map(|l| l.iter().filter_map(|v| v.as_entity_ref()).collect())
124 .unwrap_or_default();
125 mat_ids
126 .iter()
127 .copied()
128 .enumerate()
129 .filter_map(|(i, mid)| {
130 let (material_name, material_category, material_name_present) = material_details_of(decoder, mid)?;
131 Some(ResolvedLayer {
132 kind: "IfcMaterialList",
133 member_count,
134 set_name: None,
135 layer_index: i as u32,
136 material_name,
137 material_name_present,
138 material_id: Some(mid),
139 member_name: None,
140 material_category,
141 fraction: None,
142 thickness: None,
143 is_ventilated: None,
144 category: None,
145 })
146 })
147 .collect()
148 }
149 "IFCMATERIALCONSTITUENTSET" => {
150 let set_name = entity.get_string(0).map(|s| s.to_string());
154 let member_count = entity.get_list(2).map_or(0, |l| l.len() as u32);
155 let constituent_ids: Vec<u32> = entity
156 .get_list(2)
157 .map(|l| l.iter().filter_map(|v| v.as_entity_ref()).collect())
158 .unwrap_or_default();
159 constituent_ids
160 .iter()
161 .copied()
162 .enumerate()
163 .filter_map(|(i, cid)| {
164 let constituent = decoder.decode_by_id(cid).ok()?;
165 if !constituent.ifc_type.is_subtype_of(IfcType::IfcMaterialConstituent) {
166 return None;
167 }
168 let (material_name, material_category, material_name_present) = match constituent.get_ref(2) {
169 Some(mid) => material_details_of(decoder, mid)?,
170 None => (String::new(), None, false),
171 };
172 Some(ResolvedLayer {
173 kind: "IfcMaterialConstituentSet",
174 member_count,
175 set_name: set_name.clone(),
176 layer_index: i as u32,
177 material_name,
178 material_name_present,
179 material_id: constituent.get_ref(2),
180 member_name: constituent.get_string(0).map(str::to_string),
181 material_category,
182 fraction: constituent.get_float(3),
183 thickness: None,
184 is_ventilated: None,
185 category: constituent.get_string(4).map(|s| s.to_string()),
186 })
187 })
188 .collect()
189 }
190 "IFCMATERIALPROFILESETUSAGE" => {
191 match entity.get_ref(0) {
193 Some(set_id) => resolve_material(decoder, set_id, unit_scale),
194 None => Vec::new(),
195 }
196 }
197 "IFCMATERIALPROFILESET" => {
198 let set_name = entity.get_string(0).map(|s| s.to_string());
203 let member_count = entity.get_list(2).map_or(0, |l| l.len() as u32);
204 let profile_ids: Vec<u32> = entity
205 .get_list(2)
206 .map(|l| l.iter().filter_map(|v| v.as_entity_ref()).collect())
207 .unwrap_or_default();
208 profile_ids
209 .iter()
210 .copied()
211 .enumerate()
212 .filter_map(|(i, pid)| {
213 let profile = decoder.decode_by_id(pid).ok()?;
214 if !profile.ifc_type.is_subtype_of(IfcType::IfcMaterialProfile) {
215 return None;
216 }
217 let (material_name, material_category, material_name_present) = match profile.get_ref(2) {
218 Some(mid) => material_details_of(decoder, mid)?,
219 None => (String::new(), None, false),
220 };
221 Some(ResolvedLayer {
222 kind: "IfcMaterialProfileSet",
223 member_count,
224 set_name: set_name.clone(),
225 layer_index: i as u32,
226 material_name,
227 material_name_present,
228 material_id: profile.get_ref(2),
229 member_name: profile.get_string(0).map(str::to_string),
230 material_category,
231 fraction: None,
232 thickness: None,
233 is_ventilated: None,
234 category: profile.get_string(5).map(|s| s.to_string()),
235 })
236 })
237 .collect()
238 }
239 _ => Vec::new(),
240 }
241}
242
243pub(super) fn extract_materials(
245 jobs: &[EntityJob],
246 content: &Arc<Vec<u8>>,
247 entity_index: &Arc<ifc_lite_core::EntityIndex>,
248 units: &MaterialUnitContext,
249) -> Vec<MaterialAssociation> {
250 let rel_jobs: Vec<_> = jobs
251 .iter()
252 .filter(|job| {
253 job.type_name
254 .eq_ignore_ascii_case("IFCRELASSOCIATESMATERIAL")
255 })
256 .collect();
257
258 tracing::debug!(count = rel_jobs.len(), "Extracting materials");
259
260 rel_jobs
261 .par_iter()
262 .flat_map(|job| {
263 let mut decoder =
264 EntityDecoder::with_arc_index(content.as_slice(), entity_index.clone());
265 let Ok(rel) = decoder.decode_at(job.start, job.end) else {
266 return Vec::new();
267 };
268 let related = super::related_object_ids(&rel);
269 let Some(material_id) = rel.get_ref(5) else {
271 return Vec::new();
272 };
273 let mut resolved_by_scale: HashMap<u64, Vec<ResolvedLayer>> = HashMap::new();
274 related
275 .into_iter()
276 .flat_map(|element_id| {
277 let scale = units.scale_for(element_id);
278 let layers = resolved_by_scale
279 .entry(scale.to_bits())
280 .or_insert_with(|| resolve_material(&mut decoder, material_id, scale));
281 if units.has_mixed_type_scales(element_id)
282 && layers.iter().any(|layer| layer.thickness.is_some())
283 {
284 return Vec::new();
285 }
286 layers
287 .iter()
288 .map(move |layer| MaterialAssociation {
289 element_id,
290 association_id: job.id,
291 definition_id: material_id,
292 member_count: layer.member_count,
293 kind: layer.kind.to_string(),
294 set_name: layer.set_name.clone(),
295 layer_index: layer.layer_index,
296 material_name: layer.material_name.clone(),
297 material_name_present: layer.material_name_present,
298 material_id: layer.material_id,
299 member_name: layer.member_name.clone(),
300 material_category: layer.material_category.clone(),
301 fraction: layer.fraction,
302 thickness: layer.thickness,
303 is_ventilated: layer.is_ventilated,
304 category: layer.category.clone(),
305 })
306 .collect::<Vec<_>>()
307 })
308 .collect()
309 })
310 .collect()
311}