use ifc_model::guid::Guid;
use ifc_model::{Entity, EntityId, Transaction, Value};
mod external;
pub use external::{
create_external_spatial_element, create_project_library, ExternalSpatialDraft,
ProjectLibraryDraft,
};
use crate::relation::slots::{RelSlots, AGGREGATES, CONTAINED_IN};
mod boundary;
mod relationships;
pub use boundary::{connect_path_elements, create_space_boundary, BoundaryDraft, BoundaryLevel};
use crate::tree::SpatialKind;
pub use relationships::{
adhere_to_element, assign_to_actor, assign_to_group_by_factor, assign_to_process,
assign_to_product, assign_to_resource, associate_profile_def, connect_elements,
connect_with_realizing_elements, control_flow_element, cover_elements, cover_spaces, declare,
define_by_object, fill_element, interfere_elements, position_products, project_element,
serve_buildings, void_element,
};
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum SpatialAuthoringError {
Invalid {
entity: &'static str,
attribute: &'static str,
value: String,
},
}
impl std::fmt::Display for SpatialAuthoringError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
let Self::Invalid {
entity,
attribute,
value,
} = self;
write!(f, "{entity}.{attribute}: {value}")
}
}
impl std::error::Error for SpatialAuthoringError {}
pub type SpatialAuthoringResult<T> = Result<T, SpatialAuthoringError>;
pub(crate) fn invalid(
entity: &'static str,
attribute: &'static str,
value: impl Into<String>,
) -> SpatialAuthoringError {
SpatialAuthoringError::Invalid {
entity,
attribute,
value: value.into(),
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct SpatialDraft<'a> {
pub name: Option<&'a str>,
pub description: Option<&'a str>,
pub long_name: Option<&'a str>,
pub composition: Option<&'a str>,
pub placement: Option<EntityId>,
}
pub fn create_spatial_element(
tx: &mut Transaction,
kind: SpatialKind,
global_id: &str,
draft: SpatialDraft<'_>,
) -> SpatialAuthoringResult<EntityId> {
if Guid::parse(global_id).is_none() {
return Err(invalid("IFCSPATIALSTRUCTUREELEMENT", "GlobalId", global_id));
}
let (type_name, width) = match kind {
SpatialKind::Site => ("IFCSITE", 14),
SpatialKind::Building => ("IFCBUILDING", 12),
SpatialKind::Storey => ("IFCBUILDINGSTOREY", 10),
SpatialKind::Space => ("IFCSPACE", 11),
other => {
return Err(invalid(
"IFCSPATIALSTRUCTUREELEMENT",
"kind",
format!("{other:?}"),
))
}
};
let mut attributes = vec![Value::Null; width];
attributes[0] = Value::Text(global_id.into());
attributes[2] = optional_text(draft.name);
attributes[3] = optional_text(draft.description);
attributes[5] = draft.placement.map_or(Value::Null, Value::Ref);
attributes[7] = optional_text(draft.long_name);
attributes[8] = draft
.composition
.map_or(Value::Null, |t| Value::Enum(t.into()));
Ok(tx.create(Entity::new(type_name, attributes)))
}
pub(crate) fn optional_text(value: Option<&str>) -> Value {
value.map_or(Value::Null, |t| Value::Text(t.into()))
}
pub fn create_project(
tx: &mut Transaction,
global_id: &str,
name: Option<&str>,
units: Option<EntityId>,
) -> SpatialAuthoringResult<EntityId> {
if Guid::parse(global_id).is_none() {
return Err(invalid("IFCPROJECT", "GlobalId", global_id));
}
let mut attributes = vec![Value::Null; 9];
attributes[0] = Value::Text(global_id.into());
attributes[2] = optional_text(name);
attributes[8] = units.map_or(Value::Null, Value::Ref);
Ok(tx.create(Entity::new("IFCPROJECT", attributes)))
}
fn relate(
tx: &mut Transaction,
rel: RelSlots,
global_id: &str,
parent: EntityId,
children: &[EntityId],
) -> SpatialAuthoringResult<EntityId> {
if Guid::parse(global_id).is_none() {
return Err(invalid(rel.type_name, "GlobalId", global_id));
}
if children.is_empty() {
return Err(invalid(rel.type_name, "RelatedObjects", "empty"));
}
if children.contains(&parent) {
return Err(invalid(
rel.type_name,
"RelatedObjects",
"contains the parent",
));
}
let width = rel.relating.max(rel.related) + 1;
let mut attributes = vec![Value::Null; width];
attributes[0] = Value::Text(global_id.into());
attributes[rel.relating] = Value::Ref(parent);
attributes[rel.related] = Value::List(children.iter().copied().map(Value::Ref).collect());
Ok(tx.create(Entity::new(rel.type_name, attributes)))
}
fn relate_one(
tx: &mut Transaction,
rel: RelSlots,
global_id: &str,
relating: EntityId,
related: EntityId,
) -> SpatialAuthoringResult<EntityId> {
if Guid::parse(global_id).is_none() {
return Err(invalid(rel.type_name, "GlobalId", global_id));
}
if relating == related {
return Err(invalid(
rel.type_name,
"RelatedElement",
"is the relating element",
));
}
let width = rel.relating.max(rel.related) + 1;
let mut attributes = vec![Value::Null; width];
attributes[0] = Value::Text(global_id.into());
attributes[rel.relating] = Value::Ref(relating);
attributes[rel.related] = Value::Ref(related);
Ok(tx.create(Entity::new(rel.type_name, attributes)))
}
pub fn aggregate(
tx: &mut Transaction,
global_id: &str,
parent: EntityId,
children: &[EntityId],
) -> SpatialAuthoringResult<EntityId> {
relate(tx, AGGREGATES, global_id, parent, children)
}
pub fn contain(
tx: &mut Transaction,
global_id: &str,
structure: EntityId,
elements: &[EntityId],
) -> SpatialAuthoringResult<EntityId> {
relate(tx, CONTAINED_IN, global_id, structure, elements)
}