use ifc_model::{Entity, EntityId, Transaction, Value};
use super::assignment::prefix_exponent;
use crate::{PropertyError, PropertyResult};
#[derive(Debug, Clone, Copy)]
pub struct SiUnitDraft<'a> {
pub unit_type: &'a str,
pub name: &'a str,
pub prefix: Option<&'a str>,
}
#[derive(Debug, Clone, Copy)]
pub struct MonetaryUnitDraft<'a> {
pub currency: &'a str,
}
pub fn add_si_unit(tx: &mut Transaction, draft: SiUnitDraft<'_>) -> PropertyResult<EntityId> {
require_enum("IFCSIUNIT", "UnitType", draft.unit_type)?;
require_enum("IFCSIUNIT", "Name", draft.name)?;
if let Some(prefix) = draft.prefix {
if prefix_exponent(prefix).is_none() {
return Err(authoring_invalid("IFCSIUNIT", "Prefix", prefix));
}
}
Ok(tx.create(Entity::new(
"IFCSIUNIT",
vec![
Value::Derived,
enum_value(draft.unit_type),
draft.prefix.map_or(Value::Null, enum_value),
enum_value(draft.name),
],
)))
}
pub fn add_monetary_unit(
tx: &mut Transaction,
draft: MonetaryUnitDraft<'_>,
) -> PropertyResult<EntityId> {
if draft.currency.trim().is_empty() {
return Err(authoring_invalid(
"IFCMONETARYUNIT",
"Currency",
"expected a currency code",
));
}
Ok(tx.create(Entity::new(
"IFCMONETARYUNIT",
vec![Value::Text(draft.currency.into())],
)))
}
#[derive(Debug, Clone, Copy)]
pub struct ConversionBasedUnitDraft<'a> {
pub unit_type: &'a str,
pub name: &'a str,
pub conversion_factor: EntityId,
pub dimensions: EntityId,
}
pub fn add_conversion_based_unit(
tx: &mut Transaction,
draft: ConversionBasedUnitDraft<'_>,
) -> PropertyResult<EntityId> {
require_enum("IFCCONVERSIONBASEDUNIT", "UnitType", draft.unit_type)?;
if draft.name.trim().is_empty() {
return Err(authoring_invalid(
"IFCCONVERSIONBASEDUNIT",
"Name",
"expected a non-empty name",
));
}
Ok(tx.create(Entity::new(
"IFCCONVERSIONBASEDUNIT",
vec![
Value::Ref(draft.dimensions),
enum_value(draft.unit_type),
Value::Text(draft.name.into()),
Value::Ref(draft.conversion_factor),
],
)))
}
pub fn add_dimensional_exponents(tx: &mut Transaction, exponents: [i64; 7]) -> EntityId {
tx.create(Entity::new(
"IFCDIMENSIONALEXPONENTS",
exponents.iter().copied().map(Value::Integer).collect(),
))
}
pub fn add_derived_unit_element(
tx: &mut Transaction,
unit: EntityId,
exponent: i64,
) -> PropertyResult<EntityId> {
if exponent == 0 {
return Err(authoring_invalid(
"IFCDERIVEDUNITELEMENT",
"Exponent",
"expected a non-zero exponent",
));
}
Ok(tx.create(Entity::new(
"IFCDERIVEDUNITELEMENT",
vec![Value::Ref(unit), Value::Integer(exponent)],
)))
}
pub fn add_derived_unit(
tx: &mut Transaction,
elements: &[EntityId],
unit_type: &str,
user_defined_type: Option<&str>,
) -> PropertyResult<EntityId> {
if elements.is_empty() {
return Err(authoring_invalid(
"IFCDERIVEDUNIT",
"Elements",
"expected at least one derived unit element",
));
}
require_enum("IFCDERIVEDUNIT", "UnitType", unit_type)?;
Ok(tx.create(Entity::new(
"IFCDERIVEDUNIT",
vec![
Value::List(elements.iter().copied().map(Value::Ref).collect()),
enum_value(unit_type),
user_defined_type.map_or(Value::Null, |v| Value::Text(v.into())),
],
)))
}
pub fn assign_units(tx: &mut Transaction, units: &[EntityId]) -> PropertyResult<EntityId> {
if units.is_empty() {
return Err(authoring_invalid(
"IFCUNITASSIGNMENT",
"Units",
"expected at least one unit",
));
}
Ok(tx.create(Entity::new(
"IFCUNITASSIGNMENT",
vec![Value::List(units.iter().copied().map(Value::Ref).collect())],
)))
}
pub fn add_measure_with_unit(
tx: &mut Transaction,
value: Value,
unit: EntityId,
) -> PropertyResult<EntityId> {
if matches!(value, Value::Null) {
return Err(authoring_invalid(
"IFCMEASUREWITHUNIT",
"ValueComponent",
"expected a value",
));
}
Ok(tx.create(Entity::new(
"IFCMEASUREWITHUNIT",
vec![value, Value::Ref(unit)],
)))
}
pub fn add_context_dependent_unit(
tx: &mut Transaction,
dimensions: EntityId,
unit_type: &str,
name: &str,
) -> PropertyResult<EntityId> {
require_enum("IFCCONTEXTDEPENDENTUNIT", "UnitType", unit_type)?;
if name.trim().is_empty() {
return Err(authoring_invalid(
"IFCCONTEXTDEPENDENTUNIT",
"Name",
"expected a non-empty name",
));
}
Ok(tx.create(Entity::new(
"IFCCONTEXTDEPENDENTUNIT",
vec![
Value::Ref(dimensions),
enum_value(unit_type),
Value::Text(name.into()),
],
)))
}
fn require_enum(entity: &'static str, attribute: &'static str, value: &str) -> PropertyResult<()> {
if value.trim().is_empty() || !value.bytes().all(|b| b.is_ascii_uppercase() || b == b'_') {
return Err(authoring_invalid(entity, attribute, value));
}
Ok(())
}
fn enum_value(value: &str) -> Value {
Value::Enum(value.into())
}
fn authoring_invalid(
entity: &'static str,
attribute: &'static str,
value: impl Into<String>,
) -> PropertyError {
PropertyError::AuthoringInvalid {
entity,
attribute,
value: value.into(),
}
}
pub fn add_conversion_based_unit_with_offset(
tx: &mut Transaction,
draft: ConversionBasedUnitDraft<'_>,
conversion_offset: f64,
) -> PropertyResult<EntityId> {
const ENTITY: &str = "IFCCONVERSIONBASEDUNITWITHOFFSET";
require_enum(ENTITY, "UnitType", draft.unit_type)?;
if draft.name.trim().is_empty() {
return Err(authoring_invalid(
ENTITY,
"Name",
"expected a non-empty name",
));
}
if !conversion_offset.is_finite() {
return Err(authoring_invalid(
ENTITY,
"ConversionOffset",
"expected a finite offset",
));
}
Ok(tx.create(Entity::new(
ENTITY,
vec![
Value::Ref(draft.dimensions),
enum_value(draft.unit_type),
Value::Text(draft.name.into()),
Value::Ref(draft.conversion_factor),
Value::Real(conversion_offset),
],
)))
}