1use ifc_lite_core::{EntityDecoder, EntityScanner, IfcType};
15use rustc_hash::FxHashMap;
16
17pub fn propagate_voids_to_parts(
27 void_index: &mut FxHashMap<u32, Vec<u32>>,
28 content: &str,
29 decoder: &mut EntityDecoder,
30) {
31 let mut scanner = EntityScanner::new(content);
32 let mut propagations: Vec<(u32, Vec<u32>)> = Vec::new();
33
34 while let Some((id, type_name, start, end)) = scanner.next_entity() {
35 if type_name != "IFCRELAGGREGATES" {
36 continue;
37 }
38 let entity = match decoder.decode_at_with_id(id, start, end) {
39 Ok(e) => e,
40 Err(_) => continue,
41 };
42
43 let parent_id = match entity.get_ref(4) {
45 Some(id) => id,
46 None => continue,
47 };
48
49 if !void_index.contains_key(&parent_id) {
50 continue;
51 }
52
53 let children_attr = match entity.get(5) {
54 Some(attr) => attr,
55 None => continue,
56 };
57 let children: Vec<u32> = match children_attr.as_list() {
58 Some(list) => list
59 .iter()
60 .filter_map(|item| item.as_entity_ref())
61 .collect(),
62 None => continue,
63 };
64
65 let mut eligible_children = Vec::new();
66 for child_id in children {
67 if let Ok(child) = decoder.decode_by_id(child_id) {
68 if child.ifc_type == IfcType::IfcBuildingElementPart {
69 let has_repr = child.get(6).map(|a| !a.is_null()).unwrap_or(false);
70 if has_repr {
71 eligible_children.push(child_id);
72 }
73 }
74 }
75 }
76
77 if !eligible_children.is_empty() {
78 propagations.push((parent_id, eligible_children));
79 }
80 }
81
82 for (parent_id, children) in propagations {
83 let parent_voids = match void_index.get(&parent_id) {
84 Some(v) => v.clone(),
85 None => continue,
86 };
87 for child_id in children {
88 void_index
89 .entry(child_id)
90 .or_default()
91 .extend(parent_voids.iter().copied());
92 }
93 }
94}
95
96#[derive(Debug, Clone)]
101pub struct VoidIndex {
102 host_to_voids: FxHashMap<u32, Vec<u32>>,
104 void_to_host: FxHashMap<u32, u32>,
106 relationship_count: usize,
108}
109
110impl VoidIndex {
111 pub fn new() -> Self {
113 Self {
114 host_to_voids: FxHashMap::default(),
115 void_to_host: FxHashMap::default(),
116 relationship_count: 0,
117 }
118 }
119
120 pub fn from_content(content: &str, decoder: &mut EntityDecoder) -> Self {
132 let mut index = Self::new();
133 let mut scanner = EntityScanner::new(content);
134
135 while let Some((_id, type_name, start, end)) = scanner.next_entity() {
136 if type_name == "IFCRELVOIDSELEMENT" {
138 if let Ok(entity) = decoder.decode_at(start, end) {
139 if let (Some(host_id), Some(void_id)) = (entity.get_ref(4), entity.get_ref(5)) {
146 index.add_relationship(host_id, void_id);
147 }
148 }
149 }
150 }
151
152 index
153 }
154
155 pub fn add_relationship(&mut self, host_id: u32, void_id: u32) {
157 self.host_to_voids.entry(host_id).or_default().push(void_id);
158 self.void_to_host.insert(void_id, host_id);
159 self.relationship_count += 1;
160 }
161
162 pub fn get_voids(&self, host_id: u32) -> &[u32] {
170 self.host_to_voids
171 .get(&host_id)
172 .map(|v| v.as_slice())
173 .unwrap_or(&[])
174 }
175
176 pub fn get_host(&self, void_id: u32) -> Option<u32> {
184 self.void_to_host.get(&void_id).copied()
185 }
186
187 pub fn has_voids(&self, host_id: u32) -> bool {
189 self.host_to_voids
190 .get(&host_id)
191 .map(|v| !v.is_empty())
192 .unwrap_or(false)
193 }
194
195 pub fn void_count(&self, host_id: u32) -> usize {
197 self.host_to_voids
198 .get(&host_id)
199 .map(|v| v.len())
200 .unwrap_or(0)
201 }
202
203 pub fn host_count(&self) -> usize {
205 self.host_to_voids.len()
206 }
207
208 pub fn total_relationships(&self) -> usize {
210 self.relationship_count
211 }
212
213 pub fn iter(&self) -> impl Iterator<Item = (u32, &[u32])> {
215 self.host_to_voids.iter().map(|(k, v)| (*k, v.as_slice()))
216 }
217
218 pub fn hosts_with_voids(&self) -> Vec<u32> {
220 self.host_to_voids.keys().copied().collect()
221 }
222
223 pub fn is_void(&self, entity_id: u32) -> bool {
225 self.void_to_host.contains_key(&entity_id)
226 }
227
228 pub fn is_host_with_voids(&self, entity_id: u32) -> bool {
230 self.host_to_voids.contains_key(&entity_id)
231 }
232}
233
234impl Default for VoidIndex {
235 fn default() -> Self {
236 Self::new()
237 }
238}
239
240#[derive(Debug, Clone)]
242pub struct VoidStatistics {
243 pub hosts_with_voids: usize,
245 pub total_voids: usize,
247 pub max_voids_per_host: usize,
249 pub avg_voids_per_host: f64,
251 pub hosts_with_many_voids: usize,
253}
254
255impl VoidStatistics {
256 pub fn from_index(index: &VoidIndex) -> Self {
258 let hosts_with_voids = index.host_count();
259 let total_voids = index.total_relationships();
260
261 let max_voids_per_host = index
262 .host_to_voids
263 .values()
264 .map(|v| v.len())
265 .max()
266 .unwrap_or(0);
267
268 let avg_voids_per_host = if hosts_with_voids > 0 {
269 total_voids as f64 / hosts_with_voids as f64
270 } else {
271 0.0
272 };
273
274 let hosts_with_many_voids = index
275 .host_to_voids
276 .values()
277 .filter(|v| v.len() > 10)
278 .count();
279
280 Self {
281 hosts_with_voids,
282 total_voids,
283 max_voids_per_host,
284 avg_voids_per_host,
285 hosts_with_many_voids,
286 }
287 }
288}
289
290#[cfg(test)]
291mod tests {
292 use super::*;
293
294 #[test]
295 fn test_void_index_basic() {
296 let mut index = VoidIndex::new();
297
298 index.add_relationship(100, 200);
300 index.add_relationship(100, 201);
301 index.add_relationship(101, 202);
302
303 assert_eq!(index.get_voids(100), &[200, 201]);
305 assert_eq!(index.get_voids(101), &[202]);
306 assert!(index.get_voids(999).is_empty());
307
308 assert_eq!(index.get_host(200), Some(100));
310 assert_eq!(index.get_host(202), Some(101));
311 assert_eq!(index.get_host(999), None);
312
313 assert_eq!(index.void_count(100), 2);
315 assert_eq!(index.void_count(101), 1);
316 assert_eq!(index.host_count(), 2);
317 assert_eq!(index.total_relationships(), 3);
318 }
319
320 #[test]
321 fn test_void_index_has_voids() {
322 let mut index = VoidIndex::new();
323 index.add_relationship(100, 200);
324
325 assert!(index.has_voids(100));
326 assert!(!index.has_voids(999));
327 }
328
329 #[test]
330 fn test_void_index_is_void() {
331 let mut index = VoidIndex::new();
332 index.add_relationship(100, 200);
333
334 assert!(index.is_void(200));
335 assert!(!index.is_void(100));
336 assert!(!index.is_void(999));
337 }
338
339 #[test]
340 fn test_void_index_hosts_with_voids() {
341 let mut index = VoidIndex::new();
342 index.add_relationship(100, 200);
343 index.add_relationship(101, 201);
344 index.add_relationship(102, 202);
345
346 let hosts = index.hosts_with_voids();
347 assert_eq!(hosts.len(), 3);
348 assert!(hosts.contains(&100));
349 assert!(hosts.contains(&101));
350 assert!(hosts.contains(&102));
351 }
352
353 #[test]
354 fn test_void_statistics() {
355 let mut index = VoidIndex::new();
356
357 index.add_relationship(100, 200);
359 index.add_relationship(100, 201);
360 index.add_relationship(100, 202);
361
362 index.add_relationship(101, 203);
364
365 let stats = VoidStatistics::from_index(&index);
366
367 assert_eq!(stats.hosts_with_voids, 2);
368 assert_eq!(stats.total_voids, 4);
369 assert_eq!(stats.max_voids_per_host, 3);
370 assert!((stats.avg_voids_per_host - 2.0).abs() < 0.01);
371 assert_eq!(stats.hosts_with_many_voids, 0);
372 }
373
374 #[test]
375 fn test_void_statistics_many_voids() {
376 let mut index = VoidIndex::new();
377
378 for i in 0..15 {
380 index.add_relationship(100, 200 + i);
381 }
382
383 let stats = VoidStatistics::from_index(&index);
384 assert_eq!(stats.hosts_with_many_voids, 1);
385 }
386}