use crate::typing::{EntityLayout, InverseLayout, XsdForm};
use ifc_schema::{Schema, SchemaVersion};
const OMITTED: &[(&str, &str)] = &[
("IfcClassificationReference", "ReferencedSource"),
("IfcCoordinateOperation", "SourceCRS"),
("IfcGeometricRepresentationSubContext", "ParentContext"),
("IfcIndexedColourMap", "MappedTo"),
("IfcIndexedTextureMap", "MappedTo"),
("IfcMaterialDefinitionRepresentation", "RepresentedMaterial"),
("IfcMaterialProperties", "Material"),
("IfcProfileProperties", "ProfileDefinition"),
("IfcRelAggregates", "RelatingObject"),
("IfcRelContainedInSpatialStructure", "RelatingStructure"),
("IfcRelDeclares", "RelatingContext"),
("IfcRelDefinesByObject", "RelatingObject"),
("IfcRelDefinesByProperties", "RelatedObjects"),
("IfcRelDefinesByType", "RelatedObjects"),
("IfcRelFillsElement", "RelatingOpeningElement"),
("IfcRelNests", "RelatingObject"),
("IfcRelProjectsElement", "RelatingElement"),
("IfcRelReferencedInSpatialStructure", "RelatingStructure"),
("IfcRelVoidsElement", "RelatingBuildingElement"),
("IfcShapeAspect", "PartOfProductDefinitionShape"),
("IfcStyledItem", "Item"),
];
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum InverseForm {
Container,
Direct,
}
const INVERSES: &[(&str, &str, InverseForm)] = &[
("IfcClassification", "HasReferences", InverseForm::Container),
(
"IfcClassificationReference",
"HasReferences",
InverseForm::Container,
),
("IfcContext", "IsDefinedBy", InverseForm::Container),
("IfcContext", "Declares", InverseForm::Container),
("IfcElement", "HasProjections", InverseForm::Direct),
("IfcElement", "HasOpenings", InverseForm::Direct),
(
"IfcGeometricRepresentationContext",
"HasSubContexts",
InverseForm::Container,
),
(
"IfcGeometricRepresentationContext",
"HasCoordinateOperation",
InverseForm::Direct,
),
("IfcMaterial", "HasRepresentation", InverseForm::Direct),
(
"IfcMaterialDefinition",
"HasProperties",
InverseForm::Container,
),
("IfcObject", "IsDeclaredBy", InverseForm::Direct),
("IfcObject", "IsTypedBy", InverseForm::Direct),
("IfcObject", "IsDefinedBy", InverseForm::Container),
("IfcObjectDefinition", "IsNestedBy", InverseForm::Container),
(
"IfcObjectDefinition",
"IsDecomposedBy",
InverseForm::Container,
),
("IfcOpeningElement", "HasFillings", InverseForm::Direct),
(
"IfcProductDefinitionShape",
"HasShapeAspects",
InverseForm::Container,
),
("IfcProfileDef", "HasProperties", InverseForm::Container),
("IfcRepresentationItem", "StyledByItem", InverseForm::Direct),
(
"IfcSpatialElement",
"ContainsElements",
InverseForm::Container,
),
(
"IfcSpatialElement",
"ReferencesElements",
InverseForm::Container,
),
("IfcTessellatedFaceSet", "HasColours", InverseForm::Direct),
(
"IfcTessellatedFaceSet",
"HasTextures",
InverseForm::Container,
),
];
fn restrictions(
version: SchemaVersion,
) -> &'static [(&'static str, &'static str, Option<InverseForm>)] {
match version {
SchemaVersion::Ifc4 => &[
(
"IfcGeometricRepresentationSubContext",
"HasSubContexts",
None,
),
(
"IfcGeometricRepresentationSubContext",
"HasCoordinateOperation",
None,
),
("IfcMirroredProfileDef", "HasProperties", None),
("IfcOrientedEdge", "StyledByItem", None),
],
SchemaVersion::Ifc4x3 => &[(
"IfcOrientedEdge",
"StyledByItem",
Some(InverseForm::Container),
)],
_ => &[],
}
}
pub(crate) fn omitted(schema: &Schema, entity: &str, attribute: &str) -> bool {
OMITTED
.iter()
.any(|(declarer, name)| *name == attribute && schema.is_a(entity, declarer))
}
#[derive(Debug, Clone)]
pub(crate) enum Element {
Slot(usize),
Inverse {
inverse: InverseLayout,
form: InverseForm,
},
}
pub(crate) fn elements(schema: &Schema, layout: &EntityLayout) -> Vec<Element> {
let restricted = schema.version().map_or(&[][..], restrictions);
let mut chain: Vec<&str> = schema.supertypes(&layout.name);
chain.reverse();
chain.push(&layout.name);
let mut out = Vec::new();
for level in chain.iter().filter_map(|name| schema.entity(name)) {
for attribute in &level.attributes {
let Some(slot) = layout.slot(&attribute.name) else {
continue;
};
let declared = &layout.slots[slot];
if declared.derived
|| declared.form == XsdForm::Attribute
|| omitted(schema, &layout.name, &attribute.name)
{
continue;
}
out.push(Element::Slot(slot));
}
for inverse in &level.inverses {
let Some(mut form) = INVERSES
.iter()
.find(|(declarer, name, _)| *declarer == level.name && *name == inverse.name)
.map(|(_, _, form)| *form)
else {
continue;
};
let restriction = restricted.iter().find(|(entity, name, _)| {
*name == inverse.name && schema.is_a(&layout.name, entity)
});
if let Some((_, _, kept)) = restriction {
match kept {
Some(kept) => form = *kept,
None => continue,
}
}
let Some(inverse) = layout.inverse(&inverse.name) else {
continue;
};
out.push(Element::Inverse {
inverse: inverse.clone(),
form,
});
}
}
out
}