use ifc_model::guid::Guid;
use ifc_model::{Entity, EntityId, Transaction, Value};
mod connections;
mod error;
mod external;
mod owned;
mod owned_relationships;
pub(crate) mod release;
pub(crate) use error::invalid;
pub use error::{SpatialAuthoringError, SpatialAuthoringResult};
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, connect_path_elements_with_owner_history, create_space_boundary,
create_space_boundary_with_owner_history, BoundaryDraft, BoundaryLevel,
};
pub use owned::{
aggregate_with_owner_history, contain_with_owner_history,
create_external_spatial_element_with_owner_history, create_project_library_with_owner_history,
create_project_with_owner_history, create_spatial_element_with_owner_history,
};
pub use owned_relationships::{
adhere_to_element_with_owner_history, assign_to_actor_with_owner_history,
assign_to_group_by_factor_with_owner_history, assign_to_process_with_owner_history,
assign_to_product_with_owner_history, assign_to_resource_with_owner_history,
associate_profile_def_with_owner_history, connect_elements_with_owner_history,
connect_with_realizing_elements_with_owner_history, control_flow_element_with_owner_history,
cover_elements_with_owner_history, cover_spaces_with_owner_history, declare_with_owner_history,
define_by_object_with_owner_history, fill_element_with_owner_history,
interfere_elements_with_owner_history, position_products_with_owner_history,
project_element_with_owner_history, serve_buildings_with_owner_history,
void_element_with_owner_history,
};
use crate::tree::SpatialKind;
pub use connections::{connect_elements, connect_with_realizing_elements, interfere_elements};
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, control_flow_element,
cover_elements, cover_spaces, declare, define_by_object, fill_element, position_products,
project_element, serve_buildings, void_element,
};
#[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> {
let (type_name, width) = container(kind, global_id)?;
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)))
}
fn container(kind: SpatialKind, global_id: &str) -> SpatialAuthoringResult<(&'static str, usize)> {
if Guid::parse(global_id).is_none() {
return Err(invalid("IFCSPATIALSTRUCTUREELEMENT", "GlobalId", global_id));
}
Ok(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:?}"),
))
}
})
}
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> {
check_relate(rel, global_id, parent, children)?;
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> {
check_relate_one(rel, global_id, relating, related)?;
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)))
}
fn check_relate(
rel: RelSlots,
global_id: &str,
parent: EntityId,
children: &[EntityId],
) -> SpatialAuthoringResult<()> {
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",
));
}
Ok(())
}
fn check_relate_one(
rel: RelSlots,
global_id: &str,
relating: EntityId,
related: EntityId,
) -> SpatialAuthoringResult<()> {
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",
));
}
Ok(())
}
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)
}