mod table;
use ifc_model::guid::Guid;
use ifc_model::{Entity, EntityId, Model, Transaction, Value};
use crate::authoring::release::stage;
use crate::authoring::SpatialAuthoringError;
pub use table::{ALL, IFCBRIDGE, IFCBRIDGEPART, IFCFACILITY, IFCFACILITYPARTCOMMON};
pub use table::{IFCMARINEFACILITY, IFCMARINEPART, IFCRAILWAY, IFCRAILWAYPART};
pub use table::{IFCROAD, IFCROADPART};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub struct Facility {
pub type_name: &'static str,
pub arity: usize,
pub predefined_slot: Option<usize>,
pub usage_slot: Option<usize>,
pub members: &'static [&'static str],
}
impl Facility {
#[must_use]
pub const fn is_part(&self) -> bool {
self.usage_slot.is_some()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum FacilityError {
MalformedGuid {
entity: &'static str,
offered: String,
},
UnknownToken {
entity: &'static str,
attribute: &'static str,
offered: String,
},
NoPredefinedType {
entity: &'static str,
},
MissingUsageType {
entity: &'static str,
},
UnexpectedUsageType {
entity: &'static str,
},
UserDefinedWithoutObjectType {
entity: &'static str,
attribute: &'static str,
},
Authoring(SpatialAuthoringError),
}
impl core::fmt::Display for FacilityError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::MalformedGuid { entity, offered } => {
write!(f, "{entity}: GlobalId {offered:?} is not an IFC GUID")
}
Self::UnknownToken {
entity,
attribute,
offered,
} => {
write!(f, "{entity}: {offered:?} is not a {attribute} of {entity}")
}
Self::NoPredefinedType { entity } => {
write!(f, "{entity} declares no PredefinedType")
}
Self::MissingUsageType { entity } => {
write!(f, "{entity}: UsageType is mandatory on a facility part")
}
Self::UnexpectedUsageType { entity } => {
write!(f, "{entity} declares no UsageType")
}
Self::Authoring(error) => error.fmt(f),
Self::UserDefinedWithoutObjectType { entity, attribute } => {
write!(
f,
"{entity}: {attribute} is USERDEFINED without an ObjectType naming it"
)
}
}
}
}
impl std::error::Error for FacilityError {}
pub type FacilityResult<T> = Result<T, FacilityError>;
#[derive(Debug, Clone, Copy, Default)]
#[non_exhaustive]
pub struct FacilityDraft<'a> {
pub name: Option<&'a str>,
pub description: Option<&'a str>,
pub object_type: Option<&'a str>,
pub placement: Option<EntityId>,
pub long_name: Option<&'a str>,
pub composition: Option<&'a str>,
pub usage: Option<&'a str>,
}
impl<'a> FacilityDraft<'a> {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn name(mut self, value: &'a str) -> Self {
self.name = Some(value);
self
}
#[must_use]
pub fn description(mut self, value: &'a str) -> Self {
self.description = Some(value);
self
}
#[must_use]
pub fn object_type(mut self, value: &'a str) -> Self {
self.object_type = Some(value);
self
}
#[must_use]
pub fn placement(mut self, value: EntityId) -> Self {
self.placement = Some(value);
self
}
#[must_use]
pub fn long_name(mut self, value: &'a str) -> Self {
self.long_name = Some(value);
self
}
#[must_use]
pub fn composition(mut self, value: &'a str) -> Self {
self.composition = Some(value);
self
}
#[must_use]
pub fn usage(mut self, value: &'a str) -> Self {
self.usage = Some(value);
self
}
}
const USAGE_TOKENS: &[&str] = &[
"LATERAL",
"LONGITUDINAL",
"REGION",
"VERTICAL",
"USERDEFINED",
"NOTDEFINED",
];
fn names_itself(draft: &FacilityDraft<'_>) -> bool {
draft.object_type.is_some_and(|s| !s.trim().is_empty())
}
pub fn create_facility(
tx: &mut Transaction,
kind: Facility,
global_id: &str,
predefined_type: Option<&str>,
draft: FacilityDraft<'_>,
) -> FacilityResult<EntityId> {
check_facility(kind, global_id, predefined_type, &draft)?;
let mut attributes = vec![Value::Null; kind.arity];
attributes[0] = Value::Text(global_id.into());
attributes[2] = text(draft.name);
attributes[3] = text(draft.description);
attributes[4] = text(draft.object_type);
attributes[5] = draft.placement.map_or(Value::Null, Value::Ref);
attributes[7] = text(draft.long_name);
attributes[8] = enumeration(draft.composition);
if let Some(slot) = kind.usage_slot {
attributes[slot] = enumeration(draft.usage);
}
if let Some(slot) = kind.predefined_slot {
attributes[slot] = enumeration(predefined_type);
}
Ok(tx.create(Entity::new(kind.type_name, attributes)))
}
pub fn create_facility_with_owner_history(
tx: &mut Transaction,
model: &Model,
kind: Facility,
global_id: &str,
predefined_type: Option<&str>,
draft: FacilityDraft<'_>,
owner_history: EntityId,
) -> FacilityResult<EntityId> {
check_facility(kind, global_id, predefined_type, &draft)?;
let values = vec![
("GlobalId", Value::Text(global_id.into())),
("Name", text(draft.name)),
("Description", text(draft.description)),
("ObjectType", text(draft.object_type)),
(
"ObjectPlacement",
draft.placement.map_or(Value::Null, Value::Ref),
),
("LongName", text(draft.long_name)),
("CompositionType", enumeration(draft.composition)),
("UsageType", enumeration(draft.usage)),
("PredefinedType", enumeration(predefined_type)),
];
stage(tx, model, kind.type_name, values, Some(owner_history)).map_err(FacilityError::Authoring)
}
fn check_facility(
kind: Facility,
global_id: &str,
predefined_type: Option<&str>,
draft: &FacilityDraft<'_>,
) -> FacilityResult<()> {
let entity = kind.type_name;
if Guid::parse(global_id).is_none() {
return Err(FacilityError::MalformedGuid {
entity,
offered: global_id.to_owned(),
});
}
match (kind.usage_slot, draft.usage) {
(Some(_), Some(token)) => {
if !USAGE_TOKENS.contains(&token) {
return Err(FacilityError::UnknownToken {
entity,
attribute: "UsageType",
offered: token.to_owned(),
});
}
if token == "USERDEFINED" && !names_itself(draft) {
return Err(FacilityError::UserDefinedWithoutObjectType {
entity,
attribute: "UsageType",
});
}
}
(Some(_), None) => return Err(FacilityError::MissingUsageType { entity }),
(None, Some(_)) => return Err(FacilityError::UnexpectedUsageType { entity }),
(None, None) => {}
}
match (kind.predefined_slot, predefined_type) {
(Some(_), Some(token)) => {
if !kind.members.contains(&token) {
return Err(FacilityError::UnknownToken {
entity,
attribute: "PredefinedType",
offered: token.to_owned(),
});
}
if token == "USERDEFINED" && !names_itself(draft) {
return Err(FacilityError::UserDefinedWithoutObjectType {
entity,
attribute: "PredefinedType",
});
}
}
(None, Some(_)) => return Err(FacilityError::NoPredefinedType { entity }),
(Some(_), None) | (None, None) => {}
}
Ok(())
}
fn text(value: Option<&str>) -> Value {
value.map_or(Value::Null, |t| Value::Text(t.into()))
}
fn enumeration(value: Option<&str>) -> Value {
value.map_or(Value::Null, |t| Value::Enum(t.into()))
}