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(
80 self,
81 leaf: EntityId,
82 budget: Budget,
83 ) -> ClassificationResult<ClassificationHierarchy<'m>> {
84 let mut current = leaf;
85 let mut from_reference = None;
86 let mut references: Vec<ClassificationReference<'m>> = Vec::new();
87 let mut positions = HashMap::new();
88 let mut visited_nodes = 0usize;
89 let mut edges_followed = 0usize;
90
91 loop {
92 if let Some(&start) = positions.get(¤t) {
93 let mut path: Vec<_> = references[start..].iter().map(|r| r.id()).collect();
94 path.push(current);
95 return Err(ClassificationError::Cycle { path });
96 }
97 if visited_nodes >= budget.max_nodes {
98 return Err(ClassificationError::BudgetExceeded {
99 max_depth: budget.max_depth,
100 max_nodes: budget.max_nodes,
101 });
102 }
103 let entity = self.model().get(current).ok_or_else(|| {
104 from_reference.map_or(
105 ClassificationError::UnknownEntity { id: current },
106 |source| ClassificationError::DanglingReference {
107 entity: "IFCCLASSIFICATIONREFERENCE",
108 id: source,
109 attribute: "ReferencedSource",
110 target: current,
111 },
112 )
113 })?;
114 visited_nodes += 1;
115
116 if let Some(source) = from_reference {
121 let release = self.release();
122 if !release.accepts(
123 "IFCCLASSIFICATIONREFERENCE",
124 source,
125 "ReferencedSource",
126 &entity.type_name,
127 )? {
128 return Err(ClassificationError::ReferenceType {
129 entity: "IFCCLASSIFICATIONREFERENCE",
130 id: source,
131 attribute: "ReferencedSource",
132 target: current,
133 expected: release.declared_type(
134 "IFCCLASSIFICATIONREFERENCE",
135 source,
136 "ReferencedSource",
137 )?,
138 actual: entity.type_name.to_string(),
139 });
140 }
141 }
142 if entity.is_type("IFCCLASSIFICATION") && from_reference.is_some() {
143 return Ok(ClassificationHierarchy {
144 references,
145 system: Some(ClassificationSystem::try_bound(
146 current,
147 entity,
148 self.release(),
149 )?),
150 });
151 }
152 if !entity.is_type("IFCCLASSIFICATIONREFERENCE") {
153 if let Some(source) = from_reference {
154 return Err(ClassificationError::ReferenceType {
155 entity: "IFCCLASSIFICATIONREFERENCE",
156 id: source,
157 attribute: "ReferencedSource",
158 target: current,
159 expected: "IfcClassificationReferenceSelect",
160 actual: entity.type_name.to_string(),
161 });
162 }
163 return Err(ClassificationError::WrongEntityType {
164 expected: "IFCCLASSIFICATIONREFERENCE",
165 actual: entity.type_name.to_string(),
166 });
167 }
168
169 positions.insert(current, references.len());
170 let reference = ClassificationReference::try_bound(current, entity, self.release())?;
171 let source = reference.referenced_source_id()?;
172 references.push(reference);
173 let Some(source) = source else {
174 return Ok(ClassificationHierarchy {
175 references,
176 system: None,
177 });
178 };
179 if edges_followed >= budget.max_depth {
180 return Err(ClassificationError::BudgetExceeded {
181 max_depth: budget.max_depth,
182 max_nodes: budget.max_nodes,
183 });
184 }
185 edges_followed += 1;
186 from_reference = Some(current);
187 current = source;
188 }
189 }
190
191 pub fn children_of(
193 self,
194 source: EntityId,
195 ) -> ClassificationResult<Vec<ClassificationReference<'m>>> {
196 let source_entity = self
197 .model()
198 .get(source)
199 .ok_or(ClassificationError::UnknownEntity { id: source })?;
200 let release = self.release();
201 if !release.accepts(
202 "IFCCLASSIFICATIONREFERENCE",
203 source,
204 "ReferencedSource",
205 &source_entity.type_name,
206 )? {
207 return Err(ClassificationError::ReferenceType {
208 entity: "IFCCLASSIFICATIONREFERENCE",
209 id: source,
210 attribute: "ReferencedSource",
211 target: source,
212 expected: release.declared_type(
213 "IFCCLASSIFICATIONREFERENCE",
214 source,
215 "ReferencedSource",
216 )?,
217 actual: source_entity.type_name.to_string(),
218 });
219 }
220 let mut out = Vec::new();
221 for reference in self.references() {
222 if reference.referenced_source_id()? == Some(source) {
223 out.push(reference);
224 }
225 }
226 out.sort_by_key(|reference| reference.id());
227 Ok(out)
228 }
229
230 pub fn effective_classifications(
232 self,
233 object: EntityId,
234 ) -> ClassificationResult<EffectiveClassifications<'m>> {
235 if self.model().get(object).is_none() {
236 return Err(ClassificationError::UnknownEntity { id: object });
237 }
238 let occurrence = self.classification_assignments_for(object)?;
239 for assignment in &occurrence {
240 require_select(
241 self,
242 assignment.id(),
243 assignment.relating_classification_id()?,
244 )?;
245 }
246
247 const TYPING: &str = "IFCRELDEFINESBYTYPE";
248 let release = self.release();
249 let mut types = Vec::new();
250 for (relationship_id, entity) in self.model().of_type(TYPING) {
251 if required_refs(
252 TYPING,
253 relationship_id,
254 entity,
255 release.slot(TYPING, relationship_id, "RelatedObjects")?,
256 "RelatedObjects",
257 )?
258 .contains(&object)
259 {
260 let type_id = required_ref(
261 TYPING,
262 relationship_id,
263 entity,
264 release.slot(TYPING, relationship_id, "RelatingType")?,
265 "RelatingType",
266 )?;
267 self.model()
268 .get(type_id)
269 .ok_or(ClassificationError::DanglingReference {
270 entity: "IFCRELDEFINESBYTYPE",
271 id: relationship_id,
272 attribute: "RelatingType",
273 target: type_id,
274 })?;
275 types.push(type_id);
276 }
277 }
278 if types.len() > 1 {
279 return Err(ClassificationError::AmbiguousType {
280 object,
281 count: types.len(),
282 });
283 }
284 let type_id = types.first().copied();
285 let inherited_from_type = if let Some(type_id) = type_id {
286 let assignments = self.classification_assignments_for(type_id)?;
287 for assignment in &assignments {
288 require_select(
289 self,
290 assignment.id(),
291 assignment.relating_classification_id()?,
292 )?;
293 }
294 assignments
295 } else {
296 Vec::new()
297 };
298 Ok(EffectiveClassifications {
299 occurrence,
300 type_object: type_id,
301 inherited: inherited_from_type,
302 })
303 }
304}