Skip to main content

ifc_style/assignment/
resolution.rs

1//! Deterministic direct-item and presentation-layer style resolution.
2
3use ifc_model::EntityId;
4
5use crate::assignment::StyledItem;
6use crate::error::{StyleError, StyleResult};
7use crate::layer::PresentationLayer;
8use crate::StyleView;
9
10/// Which mechanism, if any, supplied a resolved item's effective styles.
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum StyleSource {
13    /// Neither a direct `IfcStyledItem` nor a presentation layer contributed
14    /// any style to this item.
15    None,
16    /// The styles came from the unique direct `IfcStyledItem` bound to this
17    /// item; carries that `IfcStyledItem`'s entity id.
18    DirectStyledItem(EntityId),
19    /// The styles came from an `IfcPresentationLayerAssignment` covering
20    /// this item, in the absence of any direct assignment; carries that
21    /// layer assignment's entity id.
22    PresentationLayer(EntityId),
23}
24
25/// The outcome of resolving an item's effective presentation style through
26/// the direct-assignment-over-layer-style cascade.
27#[derive(Debug, Clone, PartialEq, Eq)]
28pub struct ResolvedStyle {
29    source: StyleSource,
30    effective_styles: Vec<EntityId>,
31    direct_styles: Vec<EntityId>,
32    layer_styles: Vec<(EntityId, Vec<EntityId>)>,
33}
34
35impl ResolvedStyle {
36    /// Which mechanism produced `effective_styles`.
37    pub fn source(&self) -> StyleSource {
38        self.source
39    }
40
41    /// The styles that actually apply after the cascade: the direct
42    /// assignment's styles if one exists, otherwise the union of all
43    /// covering layer styles.
44    pub fn effective_styles(&self) -> &[EntityId] {
45        &self.effective_styles
46    }
47
48    /// The styles from the unique direct `IfcStyledItem`, if any; empty
49    /// when resolution fell back to layer styles.
50    pub fn direct_styles(&self) -> &[EntityId] {
51        &self.direct_styles
52    }
53
54    /// Every presentation layer assignment covering the item, paired with
55    /// its (deduplicated) flattened styles, regardless of whether a direct
56    /// assignment took precedence.
57    pub fn layer_styles(&self) -> &[(EntityId, Vec<EntityId>)] {
58        &self.layer_styles
59    }
60}
61
62impl<'m, 's> StyleView<'m, 's> {
63    /// Resolves the effective style for `item` by scanning every
64    /// `IfcStyledItem` and `IfcPresentationLayerAssignment` in the model.
65    ///
66    /// A unique direct `IfcStyledItem` binding wins over any layer style.
67    /// Returns `Err(StyleError::AmbiguousStyleAssignment)` if more than one
68    /// direct `IfcStyledItem` targets the same item.
69    pub fn resolve_item_style(&self, item: EntityId) -> StyleResult<ResolvedStyle> {
70        let mut direct = Vec::new();
71        for (id, entity) in self.model.iter() {
72            if !self.schema.is_a(&entity.type_name, "IfcStyledItem") {
73                continue;
74            }
75            let styled = StyledItem::from_record(self.record(id, "IfcStyledItem")?);
76            if styled.item()? == Some(item) {
77                direct.push((id, self.flatten_assignments(styled.styles()?)?));
78            }
79        }
80        direct.sort_by_key(|(id, _)| *id);
81        if direct.len() > 1 {
82            return Err(StyleError::AmbiguousStyleAssignment {
83                item,
84                count: direct.len(),
85            });
86        }
87
88        let mut layer_styles = Vec::new();
89        for (id, entity) in self.model.iter() {
90            if !self
91                .schema
92                .is_a(&entity.type_name, "IfcPresentationLayerAssignment")
93            {
94                continue;
95            }
96            let layer =
97                PresentationLayer::from_record(self.record(id, "IfcPresentationLayerAssignment")?);
98            if layer.assigned_items()?.contains(&item) {
99                let mut styles = self.flatten_assignments(layer.layer_styles()?)?;
100                styles.sort_unstable();
101                styles.dedup();
102                layer_styles.push((id, styles));
103            }
104        }
105        layer_styles.sort_by_key(|(id, _)| *id);
106
107        let (source, direct_styles, effective_styles) = if let Some((id, styles)) = direct.pop() {
108            (StyleSource::DirectStyledItem(id), styles.clone(), styles)
109        } else {
110            let mut styles: Vec<_> = layer_styles
111                .iter()
112                .flat_map(|(_, styles)| styles.iter().copied())
113                .collect();
114            styles.sort_unstable();
115            styles.dedup();
116            let source = layer_styles
117                .iter()
118                .find(|(_, styles)| !styles.is_empty())
119                .map_or(StyleSource::None, |(id, _)| {
120                    StyleSource::PresentationLayer(*id)
121                });
122            (source, Vec::new(), styles)
123        };
124
125        Ok(ResolvedStyle {
126            source,
127            effective_styles,
128            direct_styles,
129            layer_styles,
130        })
131    }
132
133    /// Replaces each IFC2x3 `IfcPresentationStyleAssignment` wrapper id in
134    /// `ids` with its member styles; direct `IfcPresentationStyle` ids pass
135    /// through unchanged.
136    fn flatten_assignments(&self, ids: Vec<EntityId>) -> StyleResult<Vec<EntityId>> {
137        let mut out = Vec::new();
138        for id in ids {
139            let entity = self.model.get(id).ok_or(StyleError::UnknownEntity { id })?;
140            if entity.is_type("IfcPresentationStyleAssignment") {
141                out.extend(self.presentation_style_assignment(id)?.styles()?);
142            } else {
143                out.push(id);
144            }
145        }
146        Ok(out)
147    }
148}