use ifc_model::Model;
use ifc_schema::{for_version, Schema, SchemaVersion};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum SpatialKind {
Project,
Site,
Building,
Storey,
Space,
OtherContainer,
Element,
}
impl SpatialKind {
#[must_use]
pub fn classify(type_name: &str) -> Self {
Classifier::any_release().classify(type_name)
}
#[must_use]
pub fn classify_in(type_name: &str, release: SchemaVersion) -> Self {
Classifier::for_release(release).classify(type_name)
}
#[must_use]
pub const fn is_container(self) -> bool {
!matches!(self, Self::Element)
}
}
pub(crate) struct Classifier {
release: Option<SchemaVersion>,
tables: Vec<&'static Schema>,
}
impl Classifier {
pub(crate) fn for_model(model: &Model) -> Self {
match model.header().schema.as_slice() {
[token] => match SchemaVersion::from_header_token(token) {
Some(release) => Self::for_release(release),
None => Self::any_release(),
},
_ => Self::any_release(),
}
}
fn for_release(release: SchemaVersion) -> Self {
Self {
release: Some(release),
tables: for_version(release).into_iter().collect(),
}
}
fn any_release() -> Self {
let tables = [
SchemaVersion::Ifc2x3,
SchemaVersion::Ifc4,
SchemaVersion::Ifc4x3,
]
.into_iter()
.filter_map(for_version)
.collect();
Self {
release: None,
tables,
}
}
pub(crate) fn bound_release(&self) -> Option<SchemaVersion> {
self.release
}
pub(crate) fn classify(&self, type_name: &str) -> SpatialKind {
let upper = type_name.to_ascii_uppercase();
if !self.tables.iter().any(|table| is_spatial(table, &upper)) {
return SpatialKind::Element;
}
match upper.as_str() {
"IFCPROJECT" => SpatialKind::Project,
"IFCSITE" => SpatialKind::Site,
"IFCBUILDING" => SpatialKind::Building,
"IFCBUILDINGSTOREY" => SpatialKind::Storey,
"IFCSPACE" => SpatialKind::Space,
_ => SpatialKind::OtherContainer,
}
}
}
fn is_spatial(table: &Schema, upper: &str) -> bool {
if upper == "IFCPROJECT" {
return table.is_a(upper, "IFCPROJECT");
}
table.is_a(upper, "IFCSPATIALELEMENT") || table.is_a(upper, "IFCSPATIALSTRUCTUREELEMENT")
}