ifc_style/
surface_style.rs1use 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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
46pub enum SurfaceSide {
47 Positive,
49 Negative,
51 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#[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 pub fn name(&self) -> StyleResult<Option<&'m str>> {
78 self.record.optional_text("Name")
79 }
80
81 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 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#[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 pub fn textures(&self) -> StyleResult<Vec<EntityId>> {
140 self.record
141 .required_refs("Textures", "IfcSurfaceTexture", 1, None)
142 }
143}