ifc_properties/pset/
set.rs1use std::collections::BTreeMap;
25use std::sync::Arc;
26
27use ifc_model::{EntityId, Model, Value};
28use ifc_schema::ifc4;
29
30use crate::error::PropertyAnomaly;
31use crate::nesting::Nesting;
32use crate::pset::scalar::{read_property, Property};
33
34const REL_RELATED_OBJECTS: usize = 4;
36const REL_RELATING_DEFINITION: usize = 5;
37const TYPE_HAS_PROPERTY_SETS: usize = 5;
39const SET_HAS_PROPERTIES: usize = 4;
41const ROOT_NAME: usize = 2;
43const ROOT_DESCRIPTION: usize = 3;
44
45#[derive(Debug, Clone, Copy, PartialEq, Eq)]
47pub enum Attachment {
48 Occurrence,
50 Type,
52}
53
54#[derive(Debug, Clone, PartialEq)]
56pub struct PropertySet {
57 pub id: EntityId,
59 pub name: Option<Arc<str>>,
61 pub description: Option<Arc<str>>,
63 pub properties: Vec<Property>,
65}
66
67impl PropertySet {
68 pub fn property(&self, name: &str) -> Option<&Property> {
77 self.properties
78 .iter()
79 .find(|p| p.name.as_deref() == Some(name))
80 }
81}
82
83pub fn property_set(model: &Model, id: EntityId) -> Option<PropertySet> {
94 property_set_checked(model, id).map(|(set, _)| set)
95}
96
97pub fn property_set_checked(
107 model: &Model,
108 id: EntityId,
109) -> Option<(PropertySet, Vec<PropertyAnomaly>)> {
110 let entity = model.get(id)?;
111 if !entity.type_name.eq_ignore_ascii_case("IFCPROPERTYSET") {
112 return None;
113 }
114 let mut anomalies = Vec::new();
115 let mut nesting = Nesting::new(&mut anomalies);
116 let mut properties = Vec::new();
117 let members = nesting.members(
118 id,
119 "HasProperties",
120 entity.attributes.get(SET_HAS_PROPERTIES),
121 );
122 for member in members {
123 if !nesting.admit(model, id, member) {
124 continue;
125 }
126 if let Some(property) = read_property(model, member, &mut nesting) {
127 properties.push(property);
128 }
129 }
130 let set = PropertySet {
131 id,
132 name: entity.attributes.get(ROOT_NAME).and_then(text),
133 description: entity.attributes.get(ROOT_DESCRIPTION).and_then(text),
134 properties,
135 };
136 Some((set, anomalies))
137}
138
139#[derive(Default)]
142struct SetCache {
143 sets: BTreeMap<EntityId, Option<PropertySet>>,
144 anomalies: BTreeMap<EntityId, Vec<PropertyAnomaly>>,
145}
146
147impl SetCache {
148 fn get(&mut self, model: &Model, id: EntityId) -> Option<PropertySet> {
149 if let Some(set) = self.sets.get(&id) {
150 return set.clone();
151 }
152 let read = property_set_checked(model, id).map(|(set, anomalies)| {
153 self.anomalies.insert(id, anomalies);
154 set
155 });
156 self.sets.insert(id, read.clone());
157 read
158 }
159}
160
161pub type AttachedSets = BTreeMap<EntityId, Vec<(Attachment, PropertySet)>>;
163
164pub fn property_sets_by_object(model: &Model) -> (AttachedSets, Vec<PropertyAnomaly>) {
174 let mut out: AttachedSets = BTreeMap::new();
175 let mut anomalies = Vec::new();
176 let mut cache = SetCache::default();
177 let schema = ifc4();
178
179 for &id in model.ids_of_type("IFCRELDEFINESBYPROPERTIES") {
181 let Some(rel) = model.get(id) else { continue };
182 let Some(definition) = rel
183 .attributes
184 .get(REL_RELATING_DEFINITION)
185 .and_then(one_ref)
186 else {
187 continue;
188 };
189 let Some(set) = cache.get(model, definition) else {
190 if model.get(definition).is_none() {
193 anomalies.push(PropertyAnomaly::MissingDefinition {
194 relationship: id,
195 definition,
196 });
197 }
198 continue;
199 };
200 for object in rel
201 .attributes
202 .get(REL_RELATED_OBJECTS)
203 .and_then(refs)
204 .unwrap_or_default()
205 {
206 let Some(target) = model.get(object) else {
207 anomalies.push(PropertyAnomaly::MissingObject {
208 relationship: id,
209 object,
210 });
211 continue;
212 };
213 if schema.is_a(&target.type_name.to_ascii_uppercase(), "IFCTYPEOBJECT") {
215 anomalies.push(PropertyAnomaly::TypeAttachedByRelationship {
216 relationship: id,
217 type_object: object,
218 });
219 }
220 out.entry(object)
221 .or_default()
222 .push((Attachment::Occurrence, set.clone()));
223 }
224 }
225
226 for (type_name, _) in model.type_histogram() {
228 let upper = type_name.to_ascii_uppercase();
229 if !schema.is_a(&upper, "IFCTYPEOBJECT") {
230 continue;
231 }
232 for &id in model.ids_of_type(&upper) {
233 let Some(entity) = model.get(id) else {
234 continue;
235 };
236 for set_id in entity
237 .attributes
238 .get(TYPE_HAS_PROPERTY_SETS)
239 .and_then(refs)
240 .unwrap_or_default()
241 {
242 if let Some(set) = cache.get(model, set_id) {
243 out.entry(id).or_default().push((Attachment::Type, set));
244 }
245 }
246 }
247 }
248
249 for sets in out.values_mut() {
250 sets.sort_by_key(|(_, set)| set.id);
251 }
252 for found in cache.anomalies.into_values() {
254 anomalies.extend(found);
255 }
256 let mut unique: BTreeMap<EntityId, &PropertySet> = BTreeMap::new();
258 for (_, set) in out.values().flatten() {
259 unique.entry(set.id).or_insert(set);
260 }
261 for set in unique.values() {
262 duplicate_property_names(set, &mut anomalies);
263 }
264 (out, anomalies)
265}
266
267fn duplicate_property_names(set: &PropertySet, anomalies: &mut Vec<PropertyAnomaly>) {
270 let mut first: BTreeMap<&str, EntityId> = BTreeMap::new();
271 for property in &set.properties {
272 let Some(name) = property.name.as_deref() else {
273 continue;
274 };
275 match first.get(name) {
276 Some(&kept) => anomalies.push(PropertyAnomaly::DuplicatePropertyName {
277 set: set.id,
278 kept,
279 rejected: property.id,
280 }),
281 None => {
282 first.insert(name, property.id);
283 }
284 }
285 }
286}
287
288fn text(value: &Value) -> Option<Arc<str>> {
289 match value.unwrap_typed() {
290 Value::Text(t) => Some(t.clone()),
291 _ => None,
292 }
293}
294
295fn one_ref(value: &Value) -> Option<EntityId> {
296 match value.unwrap_typed() {
297 Value::Ref(id) => Some(*id),
298 _ => None,
299 }
300}
301
302fn refs(value: &Value) -> Option<Vec<EntityId>> {
303 match value {
304 Value::List(items) => Some(items.iter().filter_map(one_ref).collect()),
305 _ => None,
306 }
307}