use std::collections::HashSet;
use ifc_model::{EntityId, Model};
use super::{AlignmentView, RelationSlots};
use crate::error::{AlignmentError, AlignmentResult};
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct AlignmentHierarchy {
pub alignment: EntityId,
pub parent: Option<EntityId>,
pub horizontal: Vec<EntityId>,
pub vertical: Vec<EntityId>,
pub cant: Vec<EntityId>,
pub referents: Vec<EntityId>,
pub children: Vec<EntityId>,
pub positioned: Vec<EntityId>,
}
impl AlignmentHierarchy {
pub fn sole_horizontal(&self) -> AlignmentResult<Option<EntityId>> {
sole(
self.alignment,
&self.horizontal,
"an alignment nesting several horizontal layouts is ambiguous",
)
}
pub fn sole_vertical(&self) -> AlignmentResult<Option<EntityId>> {
sole(
self.alignment,
&self.vertical,
"an alignment nesting several vertical layouts is ambiguous",
)
}
pub fn sole_cant(&self) -> AlignmentResult<Option<EntityId>> {
sole(
self.alignment,
&self.cant,
"an alignment nesting several cant layouts is ambiguous",
)
}
}
fn sole(
alignment: EntityId,
layouts: &[EntityId],
rule: &'static str,
) -> AlignmentResult<Option<EntityId>> {
match layouts {
[] => Ok(None),
[only] => Ok(Some(*only)),
_ => Err(AlignmentError::SemanticViolation {
entity: Some(alignment),
rule,
}),
}
}
const MAX_PARENT_DEPTH: usize = 64;
impl<'m> AlignmentView<'m> {
#[must_use]
pub fn model(&self) -> &'m Model {
self.model
}
#[must_use]
pub fn alignments(&self) -> Vec<EntityId> {
self.ids_of_ancestor("IfcAlignment")
}
pub fn hierarchy(&self, alignment: EntityId) -> AlignmentResult<AlignmentHierarchy> {
self.require(alignment, "IfcAlignment")?;
let nested = self.related_by("IfcRelNests", RelationSlots::DECOMPOSES, alignment)?;
let mut seen = HashSet::with_capacity(nested.len());
if let Some(twice) = nested.iter().find(|id| !seen.insert(**id)) {
return Err(AlignmentError::SemanticViolation {
entity: Some(*twice),
rule: "IfcRelNests must not list the same object twice",
});
}
let aggregated =
self.related_by("IfcRelAggregates", RelationSlots::DECOMPOSES, alignment)?;
let positioned = self.related_by("IfcRelPositions", RelationSlots::POSITIONS, alignment)?;
Ok(AlignmentHierarchy {
alignment,
parent: self.parent_alignment(alignment)?,
horizontal: self.filter_is_a(nested.clone(), "IfcAlignmentHorizontal"),
vertical: self.filter_is_a(nested.clone(), "IfcAlignmentVertical"),
cant: self.filter_is_a(nested.clone(), "IfcAlignmentCant"),
referents: self.filter_is_a(nested, "IfcReferent"),
children: self.filter_is_a(aggregated, "IfcAlignment"),
positioned,
})
}
pub fn parent_alignment(&self, alignment: EntityId) -> AlignmentResult<Option<EntityId>> {
self.require(alignment, "IfcAlignment")?;
let parents = self.relating_of("IfcRelAggregates", RelationSlots::DECOMPOSES, alignment)?;
match parents.as_slice() {
[] => Ok(None),
[only] => Ok(self
.model
.get(*only)
.filter(|entity| self.schema.is_a(&entity.type_name, "IfcAlignment"))
.map(|_| *only)),
_ => Err(AlignmentError::SemanticViolation {
entity: Some(alignment),
rule:
"an object is aggregated by at most one IfcRelAggregates (Decomposes SET [0:1])",
}),
}
}
pub fn governing_horizontal(&self, alignment: EntityId) -> AlignmentResult<Option<EntityId>> {
let mut current = alignment;
let mut visited = HashSet::new();
for _ in 0..MAX_PARENT_DEPTH {
if !visited.insert(current) {
return Err(AlignmentError::SemanticViolation {
entity: Some(current),
rule: "IfcAlignment aggregation must not form a cycle",
});
}
let hierarchy = self.hierarchy(current)?;
if let Some(horizontal) = hierarchy.sole_horizontal()? {
return Ok(Some(horizontal));
}
match hierarchy.parent {
Some(parent) => current = parent,
None => return Ok(None),
}
}
Err(AlignmentError::BudgetExceeded {
max_depth: MAX_PARENT_DEPTH,
max_nodes: MAX_PARENT_DEPTH,
})
}
pub fn layout_segments(&self, layout: EntityId) -> AlignmentResult<Vec<EntityId>> {
let entity = self
.model
.get(layout)
.ok_or(AlignmentError::MissingEntity { entity: layout })?;
let family = [
("IfcAlignmentHorizontal", "IfcAlignmentHorizontalSegment"),
("IfcAlignmentVertical", "IfcAlignmentVerticalSegment"),
("IfcAlignmentCant", "IfcAlignmentCantSegment"),
]
.into_iter()
.find(|(kind, _)| self.schema.is_a(&entity.type_name, kind))
.map(|(_, parameters)| parameters)
.ok_or_else(|| AlignmentError::WrongType {
entity: layout,
expected: "IfcAlignmentHorizontal, IfcAlignmentVertical or IfcAlignmentCant",
actual: entity.type_name.to_string(),
})?;
self.segment_chain(layout, family)
}
pub(crate) fn require(&self, id: EntityId, expected: &'static str) -> AlignmentResult<()> {
let entity = self
.model
.get(id)
.ok_or(AlignmentError::MissingEntity { entity: id })?;
if self.schema.is_a(&entity.type_name, expected) {
Ok(())
} else {
Err(AlignmentError::WrongType {
entity: id,
expected,
actual: entity.type_name.to_string(),
})
}
}
}