1use ifc_model::{Entity, EntityId};
9
10use crate::release::Release;
11use crate::view::{
12 borrowed_entity, optional_integer, optional_logical, optional_ref, optional_text,
13 required_enum, required_number, required_number_array_2, MaterialView,
14};
15use crate::{LayerSetDirection, LogicalValue, MaterialError, MaterialResult};
16
17borrowed_entity!(MaterialLayer, "IFCMATERIALLAYER");
18borrowed_entity!(MaterialLayerWithOffsets, "IFCMATERIALLAYERWITHOFFSETS");
19
20pub(crate) fn layer_thickness(
24 entity_type: &'static str,
25 id: EntityId,
26 entity: &Entity,
27 release: Release<'_>,
28) -> MaterialResult<f64> {
29 let (slot, declared) = release.attribute(entity_type, id, "LayerThickness")?;
30 let value = required_number(entity_type, id, entity, slot, "LayerThickness")?;
31 let positive = declared
32 .type_name
33 .eq_ignore_ascii_case("IfcPositiveLengthMeasure");
34 if value < 0.0 || (positive && value == 0.0) {
35 return Err(MaterialError::InvalidValue {
36 entity: entity_type,
37 id,
38 attribute: "LayerThickness",
39 value: if positive {
40 "expected a positive length".to_owned()
41 } else {
42 "expected a non-negative length".to_owned()
43 },
44 });
45 }
46 Ok(value)
47}
48
49macro_rules! layer_accessors {
50 ($type:ident, $ifc_name:literal) => {
51 impl<'m> $type<'m> {
52 pub fn material_id(self) -> MaterialResult<Option<EntityId>> {
54 optional_ref(
55 $ifc_name,
56 self.id(),
57 self.entity(),
58 self.slot("Material")?,
59 "Material",
60 )
61 }
62
63 pub fn thickness(self) -> MaterialResult<f64> {
66 layer_thickness($ifc_name, self.id(), self.entity(), self.release())
67 }
68
69 pub fn is_ventilated(self) -> MaterialResult<Option<LogicalValue>> {
71 optional_logical(
72 $ifc_name,
73 self.id(),
74 self.entity(),
75 self.slot("IsVentilated")?,
76 "IsVentilated",
77 )
78 }
79
80 pub fn name(self) -> MaterialResult<Option<&'m str>> {
82 optional_text(
83 $ifc_name,
84 self.id(),
85 self.entity(),
86 self.slot("Name")?,
87 "Name",
88 )
89 }
90
91 pub fn description(self) -> MaterialResult<Option<&'m str>> {
93 optional_text(
94 $ifc_name,
95 self.id(),
96 self.entity(),
97 self.slot("Description")?,
98 "Description",
99 )
100 }
101
102 pub fn category(self) -> MaterialResult<Option<&'m str>> {
104 optional_text(
105 $ifc_name,
106 self.id(),
107 self.entity(),
108 self.slot("Category")?,
109 "Category",
110 )
111 }
112
113 pub fn priority(self) -> MaterialResult<Option<i64>> {
116 let value = optional_integer(
117 $ifc_name,
118 self.id(),
119 self.entity(),
120 self.slot("Priority")?,
121 "Priority",
122 )?;
123 if value.is_some_and(|value| !(0..=100).contains(&value)) {
124 return Err(MaterialError::InvalidValue {
125 entity: $ifc_name,
126 id: self.id(),
127 attribute: "Priority",
128 value: "expected an integer in 0..=100".to_owned(),
129 });
130 }
131 Ok(value)
132 }
133 }
134 };
135}
136layer_accessors!(MaterialLayer, "IFCMATERIALLAYER");
137layer_accessors!(MaterialLayerWithOffsets, "IFCMATERIALLAYERWITHOFFSETS");
138
139impl MaterialLayerWithOffsets<'_> {
140 pub fn offset_direction(self) -> MaterialResult<LayerSetDirection> {
142 let token = required_enum(
143 "IFCMATERIALLAYERWITHOFFSETS",
144 self.id(),
145 self.entity(),
146 self.slot("OffsetDirection")?,
147 "OffsetDirection",
148 )?;
149 LayerSetDirection::parse(token).ok_or_else(|| MaterialError::InvalidValue {
150 entity: "IFCMATERIALLAYERWITHOFFSETS",
151 id: self.id(),
152 attribute: "OffsetDirection",
153 value: token.to_owned(),
154 })
155 }
156
157 pub fn offset_values(self) -> MaterialResult<[f64; 2]> {
160 required_number_array_2(
161 "IFCMATERIALLAYERWITHOFFSETS",
162 self.id(),
163 self.entity(),
164 self.slot("OffsetValues")?,
165 "OffsetValues",
166 )
167 }
168}
169
170impl<'m> MaterialView<'m> {
171 pub fn layers(self) -> impl Iterator<Item = MaterialLayer<'m>> + 'm {
173 let release = self.release();
174 self.model()
175 .of_type("IFCMATERIALLAYER")
176 .map(move |(id, entity)| MaterialLayer::from_known(id, entity, release))
177 }
178
179 pub fn layers_with_offsets(self) -> impl Iterator<Item = MaterialLayerWithOffsets<'m>> + 'm {
181 let release = self.release();
182 self.model()
183 .of_type("IFCMATERIALLAYERWITHOFFSETS")
184 .map(move |(id, entity)| MaterialLayerWithOffsets::from_known(id, entity, release))
185 }
186}