use ifc_model::guid::Guid;
use ifc_model::{EntityId, Model, Transaction, Value};
use crate::authoring::invalid;
use crate::error::ElementTypeResult;
use crate::release::{bind, require_owner_history, Layout};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SupertypeKind {
pub type_name: &'static str,
pub arity: usize,
}
pub const TYPE_OBJECT: SupertypeKind = SupertypeKind {
type_name: "IFCTYPEOBJECT",
arity: 6,
};
pub const TYPE_PRODUCT: SupertypeKind = SupertypeKind {
type_name: "IFCTYPEPRODUCT",
arity: 8,
};
pub const BUILT_ELEMENT_TYPE: SupertypeKind = SupertypeKind {
type_name: "IFCBUILTELEMENTTYPE",
arity: 9,
};
pub const CIVIL_ELEMENT_TYPE: SupertypeKind = SupertypeKind {
type_name: "IFCCIVILELEMENTTYPE",
arity: 9,
};
pub const DEEP_FOUNDATION_TYPE: SupertypeKind = SupertypeKind {
type_name: "IFCDEEPFOUNDATIONTYPE",
arity: 9,
};
pub const DISTRIBUTION_ELEMENT_TYPE: SupertypeKind = SupertypeKind {
type_name: "IFCDISTRIBUTIONELEMENTTYPE",
arity: 9,
};
pub const FURNISHING_ELEMENT_TYPE: SupertypeKind = SupertypeKind {
type_name: "IFCFURNISHINGELEMENTTYPE",
arity: 9,
};
pub const ALL_SUPERTYPES: &[SupertypeKind] = &[
TYPE_OBJECT,
TYPE_PRODUCT,
BUILT_ELEMENT_TYPE,
CIVIL_ELEMENT_TYPE,
DEEP_FOUNDATION_TYPE,
DISTRIBUTION_ELEMENT_TYPE,
FURNISHING_ELEMENT_TYPE,
];
#[derive(Debug, Clone, Copy, Default)]
pub struct SupertypeDraft<'a> {
pub description: Option<&'a str>,
pub applicable_occurrence: Option<&'a str>,
pub property_sets: &'a [(&'a str, EntityId)],
pub representation_maps: &'a [EntityId],
pub tag: Option<&'a str>,
pub element_type: Option<&'a str>,
}
fn text(value: Option<&str>) -> Value {
value.map_or(Value::Null, |v| Value::Text(v.into()))
}
pub fn create_supertype(
tx: &mut Transaction,
kind: SupertypeKind,
global_id: &str,
name: &str,
draft: SupertypeDraft<'_>,
) -> ElementTypeResult<EntityId> {
let request = Request {
kind,
global_id,
name,
draft,
};
author(tx, Layout::catalogue()?, request, None)
}
pub fn create_supertype_in(
tx: &mut Transaction,
model: &Model,
kind: SupertypeKind,
global_id: &str,
name: &str,
draft: SupertypeDraft<'_>,
) -> ElementTypeResult<EntityId> {
let request = Request {
kind,
global_id,
name,
draft,
};
author(tx, bind(model)?, request, None)
}
pub fn create_supertype_with_owner_history(
tx: &mut Transaction,
model: &Model,
kind: SupertypeKind,
global_id: &str,
name: &str,
draft: SupertypeDraft<'_>,
owner_history: EntityId,
) -> ElementTypeResult<EntityId> {
let layout = bind(model)?;
require_owner_history(tx, model, kind.type_name, owner_history)?;
let request = Request {
kind,
global_id,
name,
draft,
};
author(tx, layout, request, Some(owner_history))
}
struct Request<'a> {
kind: SupertypeKind,
global_id: &'a str,
name: &'a str,
draft: SupertypeDraft<'a>,
}
fn author(
tx: &mut Transaction,
layout: Layout,
request: Request<'_>,
owner_history: Option<EntityId>,
) -> ElementTypeResult<EntityId> {
let Request {
kind,
global_id,
name,
draft,
} = request;
let entity = kind.type_name;
if Guid::parse(global_id).is_none() {
return Err(invalid(entity, "GlobalId", global_id));
}
if name.trim().is_empty() {
return Err(invalid(entity, "Name", "NameRequired"));
}
layout.require_entity(entity)?;
let mut values = vec![
("GlobalId", Value::Text(global_id.into())),
(
"OwnerHistory",
owner_history.map_or(Value::Null, Value::Ref),
),
("Name", Value::Text(name.into())),
("Description", text(draft.description)),
("ApplicableOccurrence", text(draft.applicable_occurrence)),
];
if !draft.property_sets.is_empty() {
for (index, (set_name, _)) in draft.property_sets.iter().enumerate() {
if set_name.trim().is_empty() {
return Err(invalid(entity, "HasPropertySets", "blank name"));
}
if draft.property_sets[..index]
.iter()
.any(|(seen, _)| seen == set_name)
{
return Err(invalid(entity, "HasPropertySets", (*set_name).to_owned()));
}
}
let sets = draft.property_sets.iter().map(|(_, id)| Value::Ref(*id));
values.push(("HasPropertySets", Value::List(sets.collect())));
}
let declared = |attribute| layout.attribute(entity, attribute).is_some();
if !draft.representation_maps.is_empty() {
if !declared("RepresentationMaps") {
return Err(invalid(entity, "RepresentationMaps", "not declared"));
}
let maps = draft.representation_maps.iter().copied().map(Value::Ref);
values.push(("RepresentationMaps", Value::List(maps.collect())));
}
if draft.tag.is_some() {
if !declared("Tag") {
return Err(invalid(entity, "Tag", "not declared"));
}
values.push(("Tag", text(draft.tag)));
}
if draft.element_type.is_some() {
if !declared("ElementType") {
return Err(invalid(entity, "ElementType", "not declared"));
}
values.push(("ElementType", text(draft.element_type)));
}
Ok(tx.create(layout.named_record(entity, values)?))
}