use std::sync::Arc;
use ifc_model::{EntityId, Model};
use crate::value::MeasureValue;
const NAME: usize = 0;
const DESCRIPTION: usize = 1;
#[derive(Debug, Clone, PartialEq)]
pub enum PropertyValue {
Single {
value: Option<MeasureValue>,
unit: Option<EntityId>,
},
Enumerated {
values: Vec<MeasureValue>,
enumeration: Option<EntityId>,
},
Bounded {
upper: Option<MeasureValue>,
lower: Option<MeasureValue>,
set_point: Option<MeasureValue>,
unit: Option<EntityId>,
},
List {
values: Vec<MeasureValue>,
unit: Option<EntityId>,
},
Table {
defining: Vec<MeasureValue>,
defined: Vec<MeasureValue>,
interpolation: Option<Arc<str>>,
},
Reference {
usage: Option<Arc<str>>,
reference: Option<EntityId>,
},
Complex {
usage: Option<Arc<str>>,
properties: Vec<Property>,
},
Unsupported {
type_name: Arc<str>,
},
}
#[derive(Debug, Clone, PartialEq)]
pub struct Property {
pub id: EntityId,
pub name: Option<Arc<str>>,
pub description: Option<Arc<str>>,
pub value: PropertyValue,
}
pub fn property(model: &Model, id: EntityId) -> Option<Property> {
read_property(model, id, 0)
}
const MAX_COMPLEX_DEPTH: usize = 16;
fn read_property(model: &Model, id: EntityId, depth: usize) -> Option<Property> {
let entity = model.get(id)?;
let ty = entity.type_name.to_ascii_uppercase();
let name = entity.attributes.get(NAME).and_then(text);
let description = entity.attributes.get(DESCRIPTION).and_then(text);
let value = match ty.as_str() {
"IFCPROPERTYSINGLEVALUE" => PropertyValue::Single {
value: entity.attributes.get(2).and_then(MeasureValue::read),
unit: entity.attributes.get(3).and_then(entity_ref),
},
"IFCPROPERTYENUMERATEDVALUE" => PropertyValue::Enumerated {
values: entity
.attributes
.get(2)
.and_then(MeasureValue::read_list)
.unwrap_or_default(),
enumeration: entity.attributes.get(3).and_then(entity_ref),
},
"IFCPROPERTYBOUNDEDVALUE" => PropertyValue::Bounded {
upper: entity.attributes.get(2).and_then(MeasureValue::read),
lower: entity.attributes.get(3).and_then(MeasureValue::read),
unit: entity.attributes.get(4).and_then(entity_ref),
set_point: entity.attributes.get(5).and_then(MeasureValue::read),
},
"IFCPROPERTYLISTVALUE" => PropertyValue::List {
values: entity
.attributes
.get(2)
.and_then(MeasureValue::read_list)
.unwrap_or_default(),
unit: entity.attributes.get(3).and_then(entity_ref),
},
"IFCPROPERTYTABLEVALUE" => PropertyValue::Table {
defining: entity
.attributes
.get(2)
.and_then(MeasureValue::read_list)
.unwrap_or_default(),
defined: entity
.attributes
.get(3)
.and_then(MeasureValue::read_list)
.unwrap_or_default(),
interpolation: entity.attributes.get(7).and_then(enum_text),
},
"IFCPROPERTYREFERENCEVALUE" => PropertyValue::Reference {
usage: entity.attributes.get(2).and_then(text),
reference: entity.attributes.get(3).and_then(entity_ref),
},
"IFCCOMPLEXPROPERTY" => {
let properties = if depth >= MAX_COMPLEX_DEPTH {
Vec::new()
} else {
entity
.attributes
.get(3)
.and_then(refs)
.unwrap_or_default()
.into_iter()
.filter(|child| *child != id)
.filter_map(|child| read_property(model, child, depth + 1))
.collect()
};
PropertyValue::Complex {
usage: entity.attributes.get(2).and_then(text),
properties,
}
}
_ => PropertyValue::Unsupported {
type_name: ty.as_str().into(),
},
};
Some(Property {
id,
name,
description,
value,
})
}
fn enum_text(value: &ifc_model::Value) -> Option<Arc<str>> {
match value.unwrap_typed() {
ifc_model::Value::Enum(t) | ifc_model::Value::Text(t) => Some(t.clone()),
_ => None,
}
}
fn text(value: &ifc_model::Value) -> Option<Arc<str>> {
match value.unwrap_typed() {
ifc_model::Value::Text(t) => Some(t.clone()),
_ => None,
}
}
fn entity_ref(value: &ifc_model::Value) -> Option<EntityId> {
match value.unwrap_typed() {
ifc_model::Value::Ref(id) => Some(*id),
_ => None,
}
}
fn refs(value: &ifc_model::Value) -> Option<Vec<EntityId>> {
match value {
ifc_model::Value::List(items) => Some(items.iter().filter_map(entity_ref).collect()),
_ => None,
}
}