Skip to main content

ifc_style/
surface_style.rs

1//! Surface presentation styles.
2
3use ifc_model::EntityId;
4use ifc_schema::Schema;
5
6use crate::error::{StyleError, StyleResult};
7use crate::view::Record;
8
9mod lighting;
10mod refraction;
11mod rendering;
12mod shading;
13
14pub use lighting::SurfaceStyleLighting;
15pub use refraction::SurfaceStyleRefraction;
16pub use rendering::SurfaceStyleRendering;
17pub use shading::SurfaceStyleShading;
18
19pub(crate) const SURFACE_STYLE_ELEMENT_MEMBERS: &[&str] = &[
20    "IfcSurfaceStyleShading",
21    "IfcSurfaceStyleLighting",
22    "IfcSurfaceStyleRefraction",
23    "IfcSurfaceStyleWithTextures",
24    "IfcExternallyDefinedSurfaceStyle",
25];
26
27pub(crate) fn duplicate_surface_element_category<'a>(
28    schema: &Schema,
29    actual_types: impl IntoIterator<Item = &'a str>,
30) -> Option<&'static str> {
31    let mut seen = [false; SURFACE_STYLE_ELEMENT_MEMBERS.len()];
32    for actual in actual_types {
33        let category = SURFACE_STYLE_ELEMENT_MEMBERS
34            .iter()
35            .position(|member| schema.is_a(actual, member))?;
36        if seen[category] {
37            return Some(SURFACE_STYLE_ELEMENT_MEMBERS[category]);
38        }
39        seen[category] = true;
40    }
41    None
42}
43
44/// The `IfcSurfaceSide` enumeration: which face(s) of a surface a style applies to.
45#[derive(Debug, Clone, Copy, PartialEq, Eq)]
46pub enum SurfaceSide {
47    /// `POSITIVE`: the surface's positive normal side only.
48    Positive,
49    /// `NEGATIVE`: the surface's negative normal side only.
50    Negative,
51    /// `BOTH`: both sides of the surface.
52    Both,
53}
54
55impl SurfaceSide {
56    pub(crate) fn as_ifc(self) -> &'static str {
57        match self {
58            Self::Positive => "POSITIVE",
59            Self::Negative => "NEGATIVE",
60            Self::Both => "BOTH",
61        }
62    }
63}
64
65/// Borrowed projection of `IfcSurfaceStyle`.
66#[derive(Debug, Clone, Copy)]
67pub struct SurfaceStyle<'m, 's> {
68    record: Record<'m, 's>,
69}
70
71impl<'m, 's> SurfaceStyle<'m, 's> {
72    pub(crate) fn from_record(record: Record<'m, 's>) -> Self {
73        Self { record }
74    }
75
76    /// The `Name` attribute, when authored.
77    pub fn name(&self) -> StyleResult<Option<&'m str>> {
78        self.record.optional_text("Name")
79    }
80
81    /// The `Side` attribute: which face(s) the contained styles apply to. Mandatory.
82    pub fn side(&self) -> StyleResult<SurfaceSide> {
83        match self.record.required_enum("Side")? {
84            value if value.eq_ignore_ascii_case("POSITIVE") => Ok(SurfaceSide::Positive),
85            value if value.eq_ignore_ascii_case("NEGATIVE") => Ok(SurfaceSide::Negative),
86            value if value.eq_ignore_ascii_case("BOTH") => Ok(SurfaceSide::Both),
87            value => Err(StyleError::InvalidValue {
88                entity: "IFCSURFACESTYLE".to_owned(),
89                id: self.record.id,
90                attribute: "Side",
91                value: value.to_owned(),
92            }),
93        }
94    }
95
96    /// The `Styles` select: the surface-style elements attached to this
97    /// style (shading, lighting, refraction, textures, or an externally
98    /// defined style), one to five members. Rejects a duplicate element
99    /// category (e.g. two `IfcSurfaceStyleShading` members).
100    pub fn elements(&self) -> StyleResult<Vec<EntityId>> {
101        let elements = self.record.required_refs_select(
102            "Styles",
103            "IfcSurfaceStyleElementSelect",
104            SURFACE_STYLE_ELEMENT_MEMBERS,
105            1,
106            Some(5),
107        )?;
108        let actual_types = elements.iter().filter_map(|id| {
109            self.record
110                .model
111                .get(*id)
112                .map(|entity| entity.type_name.as_ref())
113        });
114        if let Some(category) = duplicate_surface_element_category(self.record.schema, actual_types)
115        {
116            return Err(StyleError::InvalidValue {
117                entity: "IFCSURFACESTYLE".to_owned(),
118                id: self.record.id,
119                attribute: "Styles",
120                value: format!("duplicate {category} category"),
121            });
122        }
123        Ok(elements)
124    }
125}
126
127/// Borrowed projection of `IfcSurfaceStyleWithTextures`.
128#[derive(Debug, Clone, Copy)]
129pub struct SurfaceStyleWithTextures<'m, 's> {
130    record: Record<'m, 's>,
131}
132
133impl<'m, 's> SurfaceStyleWithTextures<'m, 's> {
134    pub(crate) fn from_record(record: Record<'m, 's>) -> Self {
135        Self { record }
136    }
137
138    /// The `Textures` list: one or more `IfcSurfaceTexture` references.
139    pub fn textures(&self) -> StyleResult<Vec<EntityId>> {
140        self.record
141            .required_refs("Textures", "IfcSurfaceTexture", 1, None)
142    }
143}