ifc_classification/query/
hierarchy.rs1use std::collections::HashMap;
4
5use ifc_model::{Budget, EntityId};
6
7use crate::view::{required_ref, required_refs};
8use crate::{
9 ClassificationAssignment, ClassificationError, ClassificationReference, ClassificationResult,
10 ClassificationSystem, ClassificationView,
11};
12
13#[derive(Debug, Clone)]
15pub struct ClassificationHierarchy<'m> {
16 pub references: Vec<ClassificationReference<'m>>,
18 pub system: Option<ClassificationSystem<'m>>,
20}
21
22#[derive(Debug, Clone)]
24pub struct EffectiveClassifications<'m> {
25 pub occurrence: Vec<ClassificationAssignment<'m>>,
27 pub type_object: Option<EntityId>,
29 pub inherited: Vec<ClassificationAssignment<'m>>,
31}
32
33fn require_select(
37 view: ClassificationView<'_>,
38 source: EntityId,
39 target: EntityId,
40) -> ClassificationResult<()> {
41 const RELATION: &str = "IFCRELASSOCIATESCLASSIFICATION";
42 let entity = view
43 .model()
44 .get(target)
45 .ok_or(ClassificationError::DanglingReference {
46 entity: RELATION,
47 id: source,
48 attribute: "RelatingClassification",
49 target,
50 })?;
51 let release = view.release();
52 if release.accepts(
53 RELATION,
54 source,
55 "RelatingClassification",
56 &entity.type_name,
57 )? {
58 Ok(())
59 } else {
60 Err(ClassificationError::ReferenceType {
61 entity: RELATION,
62 id: source,
63 attribute: "RelatingClassification",
64 target,
65 expected: release.declared_type(RELATION, source, "RelatingClassification")?,
66 actual: entity.type_name.to_string(),
67 })
68 }
69}
70
71impl<'m> ClassificationView<'m> {
72 pub fn hierarchy_from(
74 self,
75 leaf: EntityId,
76 budget: Budget,
77 ) -> ClassificationResult<ClassificationHierarchy<'m>> {
78 let mut current = leaf;
79 let mut from_reference = None;
80 let mut references: Vec<ClassificationReference<'m>> = Vec::new();
81 let mut positions = HashMap::new();
82 let mut visited_nodes = 0usize;
83 let mut edges_followed = 0usize;
84
85 loop {
86 if let Some(&start) = positions.get(¤t) {
87 let mut path: Vec<_> = references[start..].iter().map(|r| r.id()).collect();
88 path.push(current);
89 return Err(ClassificationError::Cycle { path });
90 }
91 if visited_nodes >= budget.max_nodes {
92 return Err(ClassificationError::BudgetExceeded {
93 max_depth: budget.max_depth,
94 max_nodes: budget.max_nodes,
95 });
96 }
97 let entity = self.model().get(current).ok_or_else(|| {
98 from_reference.map_or(
99 ClassificationError::UnknownEntity { id: current },
100 |source| ClassificationError::DanglingReference {
101 entity: "IFCCLASSIFICATIONREFERENCE",
102 id: source,
103 attribute: "ReferencedSource",
104 target: current,
105 },
106 )
107 })?;
108 visited_nodes += 1;
109
110 if let Some(source) = from_reference {
115 let release = self.release();
116 if !release.accepts(
117 "IFCCLASSIFICATIONREFERENCE",
118 source,
119 "ReferencedSource",
120 &entity.type_name,
121 )? {
122 return Err(ClassificationError::ReferenceType {
123 entity: "IFCCLASSIFICATIONREFERENCE",
124 id: source,
125 attribute: "ReferencedSource",
126 target: current,
127 expected: release.declared_type(
128 "IFCCLASSIFICATIONREFERENCE",
129 source,
130 "ReferencedSource",
131 )?,
132 actual: entity.type_name.to_string(),
133 });
134 }
135 }
136 if entity.is_type("IFCCLASSIFICATION") && from_reference.is_some() {
137 return Ok(ClassificationHierarchy {
138 references,
139 system: Some(ClassificationSystem::try_bound(
140 current,
141 entity,
142 self.release(),
143 )?),
144 });
145 }
146 if !entity.is_type("IFCCLASSIFICATIONREFERENCE") {
147 if let Some(source) = from_reference {
148 return Err(ClassificationError::ReferenceType {
149 entity: "IFCCLASSIFICATIONREFERENCE",
150 id: source,
151 attribute: "ReferencedSource",
152 target: current,
153 expected: "IfcClassificationReferenceSelect",
154 actual: entity.type_name.to_string(),
155 });
156 }
157 return Err(ClassificationError::WrongEntityType {
158 expected: "IFCCLASSIFICATIONREFERENCE",
159 actual: entity.type_name.to_string(),
160 });
161 }
162
163 positions.insert(current, references.len());
164 let reference = ClassificationReference::try_bound(current, entity, self.release())?;
165 let source = reference.referenced_source_id()?;
166 references.push(reference);
167 let Some(source) = source else {
168 return Ok(ClassificationHierarchy {
169 references,
170 system: None,
171 });
172 };
173 if edges_followed >= budget.max_depth {
174 return Err(ClassificationError::BudgetExceeded {
175 max_depth: budget.max_depth,
176 max_nodes: budget.max_nodes,
177 });
178 }
179 edges_followed += 1;
180 from_reference = Some(current);
181 current = source;
182 }
183 }
184
185 pub fn children_of(
187 self,
188 source: EntityId,
189 ) -> ClassificationResult<Vec<ClassificationReference<'m>>> {
190 let source_entity = self
191 .model()
192 .get(source)
193 .ok_or(ClassificationError::UnknownEntity { id: source })?;
194 let release = self.release();
195 if !release.accepts(
196 "IFCCLASSIFICATIONREFERENCE",
197 source,
198 "ReferencedSource",
199 &source_entity.type_name,
200 )? {
201 return Err(ClassificationError::ReferenceType {
202 entity: "IFCCLASSIFICATIONREFERENCE",
203 id: source,
204 attribute: "ReferencedSource",
205 target: source,
206 expected: release.declared_type(
207 "IFCCLASSIFICATIONREFERENCE",
208 source,
209 "ReferencedSource",
210 )?,
211 actual: source_entity.type_name.to_string(),
212 });
213 }
214 let mut out = Vec::new();
215 for reference in self.references() {
216 if reference.referenced_source_id()? == Some(source) {
217 out.push(reference);
218 }
219 }
220 out.sort_by_key(|reference| reference.id());
221 Ok(out)
222 }
223
224 pub fn effective_classifications(
226 self,
227 object: EntityId,
228 ) -> ClassificationResult<EffectiveClassifications<'m>> {
229 if self.model().get(object).is_none() {
230 return Err(ClassificationError::UnknownEntity { id: object });
231 }
232 let occurrence = self.classification_assignments_for(object)?;
233 for assignment in &occurrence {
234 require_select(
235 self,
236 assignment.id(),
237 assignment.relating_classification_id()?,
238 )?;
239 }
240
241 let mut types = Vec::new();
242 for (relationship_id, entity) in self.model().of_type("IFCRELDEFINESBYTYPE") {
243 if required_refs(
244 "IFCRELDEFINESBYTYPE",
245 relationship_id,
246 entity,
247 4,
248 "RelatedObjects",
249 )?
250 .contains(&object)
251 {
252 let type_id = required_ref(
253 "IFCRELDEFINESBYTYPE",
254 relationship_id,
255 entity,
256 5,
257 "RelatingType",
258 )?;
259 self.model()
260 .get(type_id)
261 .ok_or(ClassificationError::DanglingReference {
262 entity: "IFCRELDEFINESBYTYPE",
263 id: relationship_id,
264 attribute: "RelatingType",
265 target: type_id,
266 })?;
267 types.push(type_id);
268 }
269 }
270 if types.len() > 1 {
271 return Err(ClassificationError::AmbiguousType {
272 object,
273 count: types.len(),
274 });
275 }
276 let type_id = types.first().copied();
277 let inherited_from_type = if let Some(type_id) = type_id {
278 let assignments = self.classification_assignments_for(type_id)?;
279 for assignment in &assignments {
280 require_select(
281 self,
282 assignment.id(),
283 assignment.relating_classification_id()?,
284 )?;
285 }
286 assignments
287 } else {
288 Vec::new()
289 };
290 Ok(EffectiveClassifications {
291 occurrence,
292 type_object: type_id,
293 inherited: inherited_from_type,
294 })
295 }
296}