Skip to main content

ifc_resource/resource/
nesting.rs

1//! Bounded construction-resource composition through `IfcRelNests`.
2
3use std::collections::{HashMap, HashSet};
4
5use ifc_model::{Budget, EntityId};
6
7use crate::error::{ResourceError, ResourceResult};
8use crate::view::ResourceView;
9
10struct NestingIndex {
11    children: HashMap<EntityId, Vec<EntityId>>,
12    parent: HashMap<EntityId, EntityId>,
13}
14
15impl<'m, 's> ResourceView<'m, 's> {
16    fn nesting_index(&self) -> ResourceResult<NestingIndex> {
17        let mut children: HashMap<EntityId, Vec<EntityId>> = HashMap::new();
18        let mut parent = HashMap::new();
19
20        for relation in self.ids_of_ancestor("IfcRelNests") {
21            let record = self.record(relation, "IfcRelNests")?;
22            let relating = record.required_ref_select(
23                "RelatingObject",
24                "IfcObjectDefinition",
25                &["IfcObjectDefinition"],
26            )?;
27            let relating_entity =
28                self.model
29                    .get(relating)
30                    .ok_or(ResourceError::DanglingReference {
31                        entity: relation,
32                        attribute: "RelatingObject",
33                        target: relating,
34                    })?;
35            if !self
36                .schema
37                .is_a(&relating_entity.type_name, "IfcConstructionResource")
38            {
39                continue;
40            }
41
42            let related =
43                record.refs("RelatedObjects", "IfcConstructionResource", 1, false, false)?;
44            for child in related {
45                if child == relating {
46                    return Err(ResourceError::SemanticViolation {
47                        entity: Some(relation),
48                        rule: "IfcRelNests must not nest a resource under itself",
49                    });
50                }
51                if let Some(existing) = parent.insert(child, relating) {
52                    if existing == relating {
53                        return Err(ResourceError::DuplicateReference {
54                            entity: relation,
55                            attribute: "RelatedObjects",
56                            target: child,
57                        });
58                    }
59                    return Err(ResourceError::SemanticViolation {
60                        entity: Some(child),
61                        rule: "IfcObject.Nests permits at most one resource parent",
62                    });
63                }
64                children.entry(relating).or_default().push(child);
65            }
66        }
67        Ok(NestingIndex { children, parent })
68    }
69
70    /// Return the authored construction-resource parent, if any.
71    pub fn parent_resource(&self, child: EntityId) -> ResourceResult<Option<EntityId>> {
72        self.resource(child)?;
73        Ok(self.nesting_index()?.parent.get(&child).copied())
74    }
75
76    /// Return direct authored members in relation/LIST order.
77    pub fn direct_members(&self, parent: EntityId) -> ResourceResult<Vec<EntityId>> {
78        self.resource(parent)?;
79        Ok(self
80            .nesting_index()?
81            .children
82            .remove(&parent)
83            .unwrap_or_default())
84    }
85
86    /// Return depth-first authored descendants within explicit limits.
87    pub fn descendants(&self, root: EntityId, budget: Budget) -> ResourceResult<Vec<EntityId>> {
88        self.resource(root)?;
89        if budget.max_nodes == 0 {
90            return Err(ResourceError::BudgetExceeded {
91                max_depth: budget.max_depth,
92                max_nodes: budget.max_nodes,
93            });
94        }
95
96        let index = self.nesting_index()?;
97        let mut result = Vec::new();
98        let mut visited = HashSet::from([root]);
99        let mut stack = vec![(root, 0_usize)];
100        while let Some((parent, depth)) = stack.pop() {
101            let children = index
102                .children
103                .get(&parent)
104                .map(Vec::as_slice)
105                .unwrap_or_default();
106            if !children.is_empty() && depth >= budget.max_depth {
107                return Err(ResourceError::BudgetExceeded {
108                    max_depth: budget.max_depth,
109                    max_nodes: budget.max_nodes,
110                });
111            }
112            for child in children {
113                if !visited.insert(*child) {
114                    return Err(ResourceError::Cycle { at: *child });
115                }
116                if visited.len() > budget.max_nodes {
117                    return Err(ResourceError::BudgetExceeded {
118                        max_depth: budget.max_depth,
119                        max_nodes: budget.max_nodes,
120                    });
121                }
122                result.push(*child);
123            }
124            stack.extend(children.iter().rev().map(|child| (*child, depth + 1)));
125        }
126        Ok(result)
127    }
128}