use ifc_model::{Entity, EntityId, Model, Transaction, Value};
use ifc_model::guid::Guid;
use ifc_schema::ifc4;
use crate::{PropertyError, PropertyResult};
pub mod pset_slot {
pub const GLOBAL_ID: usize = 0;
pub const NAME: usize = 2;
pub const DESCRIPTION: usize = 3;
pub const HAS_PROPERTIES: usize = 4;
}
pub mod single_value_slot {
pub const NAME: usize = 0;
pub const SPECIFICATION: usize = 1;
pub const NOMINAL_VALUE: usize = 2;
pub const UNIT: usize = 3;
}
pub mod defines_slot {
pub const GLOBAL_ID: usize = 0;
pub const RELATED_OBJECTS: usize = 4;
pub const RELATING_DEFINITION: usize = 5;
}
pub fn add_property_single_value(
tx: &mut Transaction,
name: &str,
specification: Option<&str>,
value: Option<Value>,
unit: Option<EntityId>,
) -> PropertyResult<EntityId> {
if name.trim().is_empty() {
return Err(PropertyError::AuthoringInvalid {
entity: "IFCPROPERTYSINGLEVALUE",
attribute: "Name",
value: name.to_owned(),
});
}
let mut attributes = vec![Value::Null; single_value_slot::UNIT + 1];
attributes[single_value_slot::NAME] = Value::Text(name.into());
attributes[single_value_slot::SPECIFICATION] = optional_text(specification);
attributes[single_value_slot::NOMINAL_VALUE] = value.unwrap_or(Value::Null);
attributes[single_value_slot::UNIT] = unit.map_or(Value::Null, Value::Ref);
Ok(tx.create(Entity::new("IFCPROPERTYSINGLEVALUE", attributes)))
}
pub fn add_property_set(
tx: &mut Transaction,
global_id: &str,
name: &str,
description: Option<&str>,
properties: &[(&str, EntityId)],
) -> PropertyResult<EntityId> {
if Guid::parse(global_id).is_none() {
return Err(PropertyError::AuthoringInvalid {
entity: "IFCPROPERTYSET",
attribute: "GlobalId",
value: global_id.to_owned(),
});
}
if name.trim().is_empty() {
return Err(PropertyError::AuthoringInvalid {
entity: "IFCPROPERTYSET",
attribute: "Name",
value: name.to_owned(),
});
}
if properties.is_empty() {
return Err(PropertyError::AuthoringInvalid {
entity: "IFCPROPERTYSET",
attribute: "HasProperties",
value: String::from("an empty set"),
});
}
let mut seen = std::collections::BTreeSet::new();
for (property_name, _) in properties {
if !seen.insert(*property_name) {
return Err(PropertyError::AuthoringInvalid {
entity: "IFCPROPERTYSET",
attribute: "HasProperties",
value: (*property_name).to_owned(),
});
}
}
let refs = properties.iter().map(|(_, id)| Value::Ref(*id)).collect();
let mut attributes = vec![Value::Null; pset_slot::HAS_PROPERTIES + 1];
attributes[pset_slot::GLOBAL_ID] = Value::Text(global_id.into());
attributes[pset_slot::NAME] = Value::Text(name.into());
attributes[pset_slot::DESCRIPTION] = optional_text(description);
attributes[pset_slot::HAS_PROPERTIES] = Value::List(refs);
Ok(tx.create(Entity::new("IFCPROPERTYSET", attributes)))
}
pub(super) fn optional_text(value: Option<&str>) -> Value {
value.map_or(Value::Null, |text| Value::Text(text.into()))
}
pub fn attach_property_set(
tx: &mut Transaction,
model: &Model,
global_id: &str,
objects: &[EntityId],
property_set: EntityId,
) -> PropertyResult<EntityId> {
if Guid::parse(global_id).is_none() {
return Err(PropertyError::AuthoringInvalid {
entity: "IFCRELDEFINESBYPROPERTIES",
attribute: "GlobalId",
value: global_id.to_owned(),
});
}
if objects.is_empty() {
return Err(PropertyError::AuthoringInvalid {
entity: "IFCRELDEFINESBYPROPERTIES",
attribute: "RelatedObjects",
value: String::from("an empty set"),
});
}
let schema = ifc4();
for object in objects {
let Some(entity) = model.get(*object) else {
return Err(PropertyError::MissingEntity { id: *object });
};
if schema.is_a(entity.type_name.as_ref(), "IFCTYPEOBJECT") {
return Err(PropertyError::AuthoringInvalid {
entity: "IFCRELDEFINESBYPROPERTIES",
attribute: "RelatedObjects",
value: entity.type_name.to_string(),
});
}
}
let refs = objects.iter().copied().map(Value::Ref).collect();
let mut attributes = vec![Value::Null; defines_slot::RELATING_DEFINITION + 1];
attributes[defines_slot::GLOBAL_ID] = Value::Text(global_id.into());
attributes[defines_slot::RELATED_OBJECTS] = Value::List(refs);
attributes[defines_slot::RELATING_DEFINITION] = Value::Ref(property_set);
Ok(tx.create(Entity::new("IFCRELDEFINESBYPROPERTIES", attributes)))
}
pub mod enumerated_slot {
pub const NAME: usize = 0;
pub const SPECIFICATION: usize = 1;
pub const VALUES: usize = 2;
pub const REFERENCE: usize = 3;
}
pub mod bounded_slot {
pub const NAME: usize = 0;
pub const SPECIFICATION: usize = 1;
pub const UPPER: usize = 2;
pub const LOWER: usize = 3;
pub const UNIT: usize = 4;
pub const SET_POINT: usize = 5;
}
pub mod list_slot {
pub const NAME: usize = 0;
pub const SPECIFICATION: usize = 1;
pub const VALUES: usize = 2;
pub const UNIT: usize = 3;
}
pub mod table_slot {
pub const NAME: usize = 0;
pub const SPECIFICATION: usize = 1;
pub const DEFINING: usize = 2;
pub const DEFINED: usize = 3;
pub const EXPRESSION: usize = 4;
pub const DEFINING_UNIT: usize = 5;
pub const DEFINED_UNIT: usize = 6;
pub const INTERPOLATION: usize = 7;
}
pub mod reference_slot {
pub const NAME: usize = 0;
pub const SPECIFICATION: usize = 1;
pub const USAGE_NAME: usize = 2;
pub const REFERENCE: usize = 3;
}
pub mod complex_slot {
pub const NAME: usize = 0;
pub const SPECIFICATION: usize = 1;
pub const USAGE_NAME: usize = 2;
pub const HAS_PROPERTIES: usize = 3;
}
pub(super) fn require_name(entity: &'static str, name: &str) -> PropertyResult<()> {
if name.trim().is_empty() {
return Err(PropertyError::AuthoringInvalid {
entity,
attribute: "Name",
value: name.to_owned(),
});
}
Ok(())
}
pub fn add_property_enumerated_value(
tx: &mut Transaction,
name: &str,
specification: Option<&str>,
values: Option<Vec<Value>>,
enumeration: Option<EntityId>,
) -> PropertyResult<EntityId> {
require_name("IFCPROPERTYENUMERATEDVALUE", name)?;
if let Some(values) = values.as_ref() {
if values.is_empty() {
return Err(PropertyError::AuthoringInvalid {
entity: "IFCPROPERTYENUMERATEDVALUE",
attribute: "EnumerationValues",
value: "empty".to_owned(),
});
}
}
let mut attributes = vec![Value::Null; enumerated_slot::REFERENCE + 1];
attributes[enumerated_slot::NAME] = Value::Text(name.into());
attributes[enumerated_slot::SPECIFICATION] = optional_text(specification);
attributes[enumerated_slot::VALUES] = values.map_or(Value::Null, Value::List);
attributes[enumerated_slot::REFERENCE] = enumeration.map_or(Value::Null, Value::Ref);
Ok(tx.create(Entity::new("IFCPROPERTYENUMERATEDVALUE", attributes)))
}
fn measure_of(value: &Value) -> Option<&str> {
match value {
Value::Typed { type_name, .. } => Some(type_name.as_ref()),
_ => None,
}
}
pub fn add_property_bounded_value(
tx: &mut Transaction,
name: &str,
specification: Option<&str>,
upper: Option<Value>,
lower: Option<Value>,
set_point: Option<Value>,
unit: Option<EntityId>,
) -> PropertyResult<EntityId> {
require_name("IFCPROPERTYBOUNDEDVALUE", name)?;
let stated: Vec<(&str, &Value)> = [
("UpperBoundValue", upper.as_ref()),
("LowerBoundValue", lower.as_ref()),
("SetPointValue", set_point.as_ref()),
]
.into_iter()
.filter_map(|(label, value)| value.map(|value| (label, value)))
.collect();
if let Some(((_, first), rest)) = stated.split_first() {
let want = measure_of(first);
for (label, value) in rest {
if measure_of(value) != want {
return Err(PropertyError::AuthoringInvalid {
entity: "IFCPROPERTYBOUNDEDVALUE",
attribute: "SameUnit",
value: (*label).to_owned(),
});
}
}
}
let mut attributes = vec![Value::Null; bounded_slot::SET_POINT + 1];
attributes[bounded_slot::NAME] = Value::Text(name.into());
attributes[bounded_slot::SPECIFICATION] = optional_text(specification);
attributes[bounded_slot::UPPER] = upper.unwrap_or(Value::Null);
attributes[bounded_slot::LOWER] = lower.unwrap_or(Value::Null);
attributes[bounded_slot::UNIT] = unit.map_or(Value::Null, Value::Ref);
attributes[bounded_slot::SET_POINT] = set_point.unwrap_or(Value::Null);
Ok(tx.create(Entity::new("IFCPROPERTYBOUNDEDVALUE", attributes)))
}
pub fn add_property_list_value(
tx: &mut Transaction,
name: &str,
specification: Option<&str>,
values: Option<Vec<Value>>,
unit: Option<EntityId>,
) -> PropertyResult<EntityId> {
require_name("IFCPROPERTYLISTVALUE", name)?;
if let Some(values) = values.as_ref() {
if values.is_empty() {
return Err(PropertyError::AuthoringInvalid {
entity: "IFCPROPERTYLISTVALUE",
attribute: "ListValues",
value: "empty".to_owned(),
});
}
}
let mut attributes = vec![Value::Null; list_slot::UNIT + 1];
attributes[list_slot::NAME] = Value::Text(name.into());
attributes[list_slot::SPECIFICATION] = optional_text(specification);
attributes[list_slot::VALUES] = values.map_or(Value::Null, Value::List);
attributes[list_slot::UNIT] = unit.map_or(Value::Null, Value::Ref);
Ok(tx.create(Entity::new("IFCPROPERTYLISTVALUE", attributes)))
}
#[derive(Debug, Default)]
pub struct TableValueDraft<'a> {
pub name: &'a str,
pub specification: Option<&'a str>,
pub defining: Option<Vec<Value>>,
pub defined: Option<Vec<Value>>,
pub defining_unit: Option<EntityId>,
pub defined_unit: Option<EntityId>,
pub expression: Option<&'a str>,
pub interpolation: Option<&'a str>,
}
pub fn add_property_table_value(
tx: &mut Transaction,
draft: TableValueDraft<'_>,
) -> PropertyResult<EntityId> {
let TableValueDraft {
name,
specification,
defining,
defined,
defining_unit,
defined_unit,
expression,
interpolation,
} = draft;
require_name("IFCPROPERTYTABLEVALUE", name)?;
let sizes = (
defining.as_ref().map(Vec::len),
defined.as_ref().map(Vec::len),
);
if let (Some(left), Some(right)) = sizes {
if left != right {
return Err(PropertyError::AuthoringInvalid {
entity: "IFCPROPERTYTABLEVALUE",
attribute: "DefiningValues",
value: format!("{left} defining against {right} defined"),
});
}
}
for (label, column) in [
("DefiningValues", defining.as_ref()),
("DefinedValues", defined.as_ref()),
] {
let Some(column) = column else { continue };
if column.is_empty() {
return Err(PropertyError::AuthoringInvalid {
entity: "IFCPROPERTYTABLEVALUE",
attribute: label,
value: "empty".to_owned(),
});
}
let want = measure_of(&column[0]);
if column.iter().any(|value| measure_of(value) != want) {
return Err(PropertyError::AuthoringInvalid {
entity: "IFCPROPERTYTABLEVALUE",
attribute: label,
value: "mixed measures".to_owned(),
});
}
}
let mut attributes = vec![Value::Null; table_slot::INTERPOLATION + 1];
attributes[table_slot::NAME] = Value::Text(name.into());
attributes[table_slot::SPECIFICATION] = optional_text(specification);
attributes[table_slot::DEFINING] = defining.map_or(Value::Null, Value::List);
attributes[table_slot::DEFINED] = defined.map_or(Value::Null, Value::List);
attributes[table_slot::EXPRESSION] = optional_text(expression);
attributes[table_slot::DEFINING_UNIT] = defining_unit.map_or(Value::Null, Value::Ref);
attributes[table_slot::DEFINED_UNIT] = defined_unit.map_or(Value::Null, Value::Ref);
attributes[table_slot::INTERPOLATION] =
interpolation.map_or(Value::Null, |token| Value::Enum(token.into()));
Ok(tx.create(Entity::new("IFCPROPERTYTABLEVALUE", attributes)))
}
pub fn add_property_reference_value(
tx: &mut Transaction,
name: &str,
specification: Option<&str>,
usage_name: Option<&str>,
reference: Option<EntityId>,
) -> PropertyResult<EntityId> {
require_name("IFCPROPERTYREFERENCEVALUE", name)?;
let mut attributes = vec![Value::Null; reference_slot::REFERENCE + 1];
attributes[reference_slot::NAME] = Value::Text(name.into());
attributes[reference_slot::SPECIFICATION] = optional_text(specification);
attributes[reference_slot::USAGE_NAME] = optional_text(usage_name);
attributes[reference_slot::REFERENCE] = reference.map_or(Value::Null, Value::Ref);
Ok(tx.create(Entity::new("IFCPROPERTYREFERENCEVALUE", attributes)))
}
pub fn add_complex_property(
tx: &mut Transaction,
name: &str,
specification: Option<&str>,
usage_name: &str,
properties: &[(&str, EntityId)],
) -> PropertyResult<EntityId> {
require_name("IFCCOMPLEXPROPERTY", name)?;
if usage_name.trim().is_empty() {
return Err(PropertyError::AuthoringInvalid {
entity: "IFCCOMPLEXPROPERTY",
attribute: "UsageName",
value: usage_name.to_owned(),
});
}
if properties.is_empty() {
return Err(PropertyError::AuthoringInvalid {
entity: "IFCCOMPLEXPROPERTY",
attribute: "HasProperties",
value: "empty".to_owned(),
});
}
let mut seen = std::collections::BTreeSet::new();
for (property_name, _) in properties {
if !seen.insert(*property_name) {
return Err(PropertyError::AuthoringInvalid {
entity: "IFCCOMPLEXPROPERTY",
attribute: "HasProperties",
value: (*property_name).to_owned(),
});
}
}
let refs = properties.iter().map(|(_, id)| Value::Ref(*id)).collect();
let mut attributes = vec![Value::Null; complex_slot::HAS_PROPERTIES + 1];
attributes[complex_slot::NAME] = Value::Text(name.into());
attributes[complex_slot::SPECIFICATION] = optional_text(specification);
attributes[complex_slot::USAGE_NAME] = Value::Text(usage_name.into());
attributes[complex_slot::HAS_PROPERTIES] = Value::List(refs);
let own = tx.create(Entity::new("IFCCOMPLEXPROPERTY", attributes));
if properties.iter().any(|(_, id)| *id == own) {
return Err(PropertyError::AuthoringInvalid {
entity: "IFCCOMPLEXPROPERTY",
attribute: "HasProperties",
value: "self reference".to_owned(),
});
}
Ok(own)
}
pub mod element_quantity_slot {
pub const GLOBAL_ID: usize = 0;
pub const NAME: usize = 2;
pub const DESCRIPTION: usize = 3;
pub const METHOD: usize = 4;
pub const QUANTITIES: usize = 5;
}
pub mod complex_quantity_slot {
pub const NAME: usize = 0;
pub const DESCRIPTION: usize = 1;
pub const HAS_QUANTITIES: usize = 2;
pub const DISCRIMINATION: usize = 3;
pub const QUALITY: usize = 4;
pub const USAGE: usize = 5;
}
pub fn add_element_quantity(
tx: &mut Transaction,
global_id: &str,
name: &str,
method_of_measurement: Option<&str>,
quantities: &[EntityId],
) -> PropertyResult<EntityId> {
if Guid::parse(global_id).is_none() {
return Err(PropertyError::AuthoringInvalid {
entity: "IFCELEMENTQUANTITY",
attribute: "GlobalId",
value: global_id.to_owned(),
});
}
require_name("IFCELEMENTQUANTITY", name)?;
if quantities.is_empty() {
return Err(PropertyError::AuthoringInvalid {
entity: "IFCELEMENTQUANTITY",
attribute: "Quantities",
value: "empty".to_owned(),
});
}
let mut attributes = vec![Value::Null; element_quantity_slot::QUANTITIES + 1];
attributes[element_quantity_slot::GLOBAL_ID] = Value::Text(global_id.into());
attributes[element_quantity_slot::NAME] = Value::Text(name.into());
attributes[element_quantity_slot::METHOD] = optional_text(method_of_measurement);
attributes[element_quantity_slot::QUANTITIES] =
Value::List(quantities.iter().copied().map(Value::Ref).collect());
Ok(tx.create(Entity::new("IFCELEMENTQUANTITY", attributes)))
}
pub fn add_physical_complex_quantity(
tx: &mut Transaction,
name: &str,
description: Option<&str>,
quantities: &[EntityId],
discrimination: &str,
) -> PropertyResult<EntityId> {
require_name("IFCPHYSICALCOMPLEXQUANTITY", name)?;
if discrimination.trim().is_empty() {
return Err(PropertyError::AuthoringInvalid {
entity: "IFCPHYSICALCOMPLEXQUANTITY",
attribute: "Discrimination",
value: discrimination.to_owned(),
});
}
if quantities.is_empty() {
return Err(PropertyError::AuthoringInvalid {
entity: "IFCPHYSICALCOMPLEXQUANTITY",
attribute: "HasQuantities",
value: "empty".to_owned(),
});
}
let mut attributes = vec![Value::Null; complex_quantity_slot::USAGE + 1];
attributes[complex_quantity_slot::NAME] = Value::Text(name.into());
attributes[complex_quantity_slot::DESCRIPTION] = optional_text(description);
attributes[complex_quantity_slot::HAS_QUANTITIES] =
Value::List(quantities.iter().copied().map(Value::Ref).collect());
attributes[complex_quantity_slot::DISCRIMINATION] = Value::Text(discrimination.into());
Ok(tx.create(Entity::new("IFCPHYSICALCOMPLEXQUANTITY", attributes)))
}