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(
34 view: ClassificationView<'_>,
35 source: EntityId,
36 target: EntityId,
37) -> ClassificationResult<()> {
38 let entity = view
39 .model()
40 .get(target)
41 .ok_or(ClassificationError::DanglingReference {
42 entity: "IFCRELASSOCIATESCLASSIFICATION",
43 id: source,
44 attribute: "RelatingClassification",
45 target,
46 })?;
47 if entity.is_type("IFCCLASSIFICATION") || entity.is_type("IFCCLASSIFICATIONREFERENCE") {
48 Ok(())
49 } else {
50 Err(ClassificationError::ReferenceType {
51 entity: "IFCRELASSOCIATESCLASSIFICATION",
52 id: source,
53 attribute: "RelatingClassification",
54 target,
55 expected: "IfcClassificationSelect",
56 actual: entity.type_name.to_string(),
57 })
58 }
59}
60
61impl<'m> ClassificationView<'m> {
62 pub fn hierarchy_from(
64 self,
65 leaf: EntityId,
66 budget: Budget,
67 ) -> ClassificationResult<ClassificationHierarchy<'m>> {
68 let mut current = leaf;
69 let mut from_reference = None;
70 let mut references: Vec<ClassificationReference<'m>> = Vec::new();
71 let mut positions = HashMap::new();
72 let mut visited_nodes = 0usize;
73 let mut edges_followed = 0usize;
74
75 loop {
76 if let Some(&start) = positions.get(¤t) {
77 let mut path: Vec<_> = references[start..].iter().map(|r| r.id()).collect();
78 path.push(current);
79 return Err(ClassificationError::Cycle { path });
80 }
81 if visited_nodes >= budget.max_nodes {
82 return Err(ClassificationError::BudgetExceeded {
83 max_depth: budget.max_depth,
84 max_nodes: budget.max_nodes,
85 });
86 }
87 let entity = self.model().get(current).ok_or_else(|| {
88 from_reference.map_or(
89 ClassificationError::UnknownEntity { id: current },
90 |source| ClassificationError::DanglingReference {
91 entity: "IFCCLASSIFICATIONREFERENCE",
92 id: source,
93 attribute: "ReferencedSource",
94 target: current,
95 },
96 )
97 })?;
98 visited_nodes += 1;
99
100 if entity.is_type("IFCCLASSIFICATION") && from_reference.is_some() {
101 return Ok(ClassificationHierarchy {
102 references,
103 system: Some(ClassificationSystem::try_new(current, entity)?),
104 });
105 }
106 if !entity.is_type("IFCCLASSIFICATIONREFERENCE") {
107 if let Some(source) = from_reference {
108 return Err(ClassificationError::ReferenceType {
109 entity: "IFCCLASSIFICATIONREFERENCE",
110 id: source,
111 attribute: "ReferencedSource",
112 target: current,
113 expected: "IfcClassificationReferenceSelect",
114 actual: entity.type_name.to_string(),
115 });
116 }
117 return Err(ClassificationError::WrongEntityType {
118 expected: "IFCCLASSIFICATIONREFERENCE",
119 actual: entity.type_name.to_string(),
120 });
121 }
122
123 positions.insert(current, references.len());
124 let reference = ClassificationReference::try_new(current, entity)?;
125 let source = reference.referenced_source_id()?;
126 references.push(reference);
127 let Some(source) = source else {
128 return Ok(ClassificationHierarchy {
129 references,
130 system: None,
131 });
132 };
133 if edges_followed >= budget.max_depth {
134 return Err(ClassificationError::BudgetExceeded {
135 max_depth: budget.max_depth,
136 max_nodes: budget.max_nodes,
137 });
138 }
139 edges_followed += 1;
140 from_reference = Some(current);
141 current = source;
142 }
143 }
144
145 pub fn children_of(
147 self,
148 source: EntityId,
149 ) -> ClassificationResult<Vec<ClassificationReference<'m>>> {
150 let source_entity = self
151 .model()
152 .get(source)
153 .ok_or(ClassificationError::UnknownEntity { id: source })?;
154 if !(source_entity.is_type("IFCCLASSIFICATION")
155 || source_entity.is_type("IFCCLASSIFICATIONREFERENCE"))
156 {
157 return Err(ClassificationError::ReferenceType {
158 entity: "IFCCLASSIFICATIONREFERENCE",
159 id: source,
160 attribute: "ReferencedSource",
161 target: source,
162 expected: "IfcClassificationReferenceSelect",
163 actual: source_entity.type_name.to_string(),
164 });
165 }
166 let mut out = Vec::new();
167 for reference in self.references() {
168 if reference.referenced_source_id()? == Some(source) {
169 out.push(reference);
170 }
171 }
172 out.sort_by_key(|reference| reference.id());
173 Ok(out)
174 }
175
176 pub fn effective_classifications(
178 self,
179 object: EntityId,
180 ) -> ClassificationResult<EffectiveClassifications<'m>> {
181 if self.model().get(object).is_none() {
182 return Err(ClassificationError::UnknownEntity { id: object });
183 }
184 let occurrence = self.classification_assignments_for(object)?;
185 for assignment in &occurrence {
186 require_select(
187 self,
188 assignment.id(),
189 assignment.relating_classification_id()?,
190 )?;
191 }
192
193 let mut types = Vec::new();
194 for (relationship_id, entity) in self.model().of_type("IFCRELDEFINESBYTYPE") {
195 if required_refs(
196 "IFCRELDEFINESBYTYPE",
197 relationship_id,
198 entity,
199 4,
200 "RelatedObjects",
201 )?
202 .contains(&object)
203 {
204 let type_id = required_ref(
205 "IFCRELDEFINESBYTYPE",
206 relationship_id,
207 entity,
208 5,
209 "RelatingType",
210 )?;
211 self.model()
212 .get(type_id)
213 .ok_or(ClassificationError::DanglingReference {
214 entity: "IFCRELDEFINESBYTYPE",
215 id: relationship_id,
216 attribute: "RelatingType",
217 target: type_id,
218 })?;
219 types.push(type_id);
220 }
221 }
222 if types.len() > 1 {
223 return Err(ClassificationError::AmbiguousType {
224 object,
225 count: types.len(),
226 });
227 }
228 let type_id = types.first().copied();
229 let inherited_from_type = if let Some(type_id) = type_id {
230 let assignments = self.classification_assignments_for(type_id)?;
231 for assignment in &assignments {
232 require_select(
233 self,
234 assignment.id(),
235 assignment.relating_classification_id()?,
236 )?;
237 }
238 assignments
239 } else {
240 Vec::new()
241 };
242 Ok(EffectiveClassifications {
243 occurrence,
244 type_object: type_id,
245 inherited: inherited_from_type,
246 })
247 }
248}