1use ifc_model::{Edit, EntityId, Model, Transaction, Value};
7use ifc_schema::Schema;
8
9use super::{
10 build_named, enumeration, invalid_authoring, optional_reference, optional_text, text,
11 validate_optional_ref, validate_ratio, validate_ref,
12};
13use crate::colour::ColourOrFactor;
14use crate::error::{StyleError, StyleResult};
15use crate::surface_style::{duplicate_surface_element_category, SURFACE_STYLE_ELEMENT_MEMBERS};
16
17#[derive(Debug, Clone, Copy)]
20pub struct ColourRgbDraft<'a> {
21 pub name: Option<&'a str>,
23 pub red: f64,
25 pub green: f64,
27 pub blue: f64,
29}
30
31#[derive(Debug, Clone, Copy)]
34pub struct SurfaceStyleShadingDraft {
35 pub surface_colour: EntityId,
37 pub transparency: Option<f64>,
40}
41
42#[derive(Debug, Clone)]
45pub struct SurfaceStyleDraft<'a> {
46 pub name: Option<&'a str>,
48 pub side: crate::SurfaceSide,
50 pub elements: Vec<EntityId>,
54}
55
56#[derive(Debug, Clone)]
59pub struct StyledItemDraft<'a> {
60 pub item: Option<EntityId>,
62 pub styles: Vec<EntityId>,
65 pub name: Option<&'a str>,
67}
68
69#[derive(Debug, Clone)]
72pub struct PresentationLayerDraft<'a> {
73 pub name: &'a str,
75 pub description: Option<&'a str>,
77 pub assigned_items: Vec<EntityId>,
80 pub identifier: Option<&'a str>,
82 pub layer_on: Option<bool>,
84 pub layer_frozen: Option<bool>,
86 pub layer_blocked: Option<bool>,
88 pub layer_styles: Vec<EntityId>,
90}
91
92pub fn create_colour_rgb(
95 tx: &mut Transaction,
96 schema: &Schema,
97 draft: ColourRgbDraft<'_>,
98) -> StyleResult<EntityId> {
99 validate_ratio("IfcColourRgb", "Red", draft.red)?;
100 validate_ratio("IfcColourRgb", "Green", draft.green)?;
101 validate_ratio("IfcColourRgb", "Blue", draft.blue)?;
102 let mut values = Vec::new();
103 optional_text(&mut values, "Name", draft.name);
104 values.extend([
105 ("Red", Value::Real(draft.red)),
106 ("Green", Value::Real(draft.green)),
107 ("Blue", Value::Real(draft.blue)),
108 ]);
109 Ok(tx.create(build_named(schema, "IfcColourRgb", values)?))
110}
111
112pub fn create_surface_style_shading(
116 tx: &mut Transaction,
117 model: &Model,
118 schema: &Schema,
119 draft: SurfaceStyleShadingDraft,
120) -> StyleResult<EntityId> {
121 validate_ref(tx, model, schema, draft.surface_colour, "IfcColourRgb")?;
122 if let Some(value) = draft.transparency {
123 validate_ratio("IfcSurfaceStyleShading", "Transparency", value)?;
124 }
125 let mut values = vec![("SurfaceColour", Value::Ref(draft.surface_colour))];
126 if let Some(value) = draft.transparency {
127 values.push(("Transparency", Value::Real(value)));
128 }
129 Ok(tx.create(build_named(schema, "IfcSurfaceStyleShading", values)?))
130}
131
132pub fn create_surface_style(
136 tx: &mut Transaction,
137 model: &Model,
138 schema: &Schema,
139 draft: SurfaceStyleDraft<'_>,
140) -> StyleResult<EntityId> {
141 if draft.elements.is_empty() || draft.elements.len() > 5 {
142 return Err(invalid_authoring(
143 "IfcSurfaceStyle",
144 "Styles",
145 format!("expected 1..=5 elements, found {}", draft.elements.len()),
146 ));
147 }
148 let mut element_types = Vec::with_capacity(draft.elements.len());
149 for element in &draft.elements {
150 element_types.push(validate_surface_element(tx, model, schema, *element)?);
151 }
152 if let Some(category) =
153 duplicate_surface_element_category(schema, element_types.iter().map(String::as_str))
154 {
155 return Err(invalid_authoring(
156 "IfcSurfaceStyle",
157 "Styles",
158 format!("duplicate {category} category"),
159 ));
160 }
161 let mut values = Vec::new();
162 optional_text(&mut values, "Name", draft.name);
163 values.push(("Side", enumeration(draft.side.as_ifc())));
164 values.push((
165 "Styles",
166 Value::List(draft.elements.into_iter().map(Value::Ref).collect()),
167 ));
168 Ok(tx.create(build_named(schema, "IfcSurfaceStyle", values)?))
169}
170
171pub fn create_styled_item(
175 tx: &mut Transaction,
176 model: &Model,
177 schema: &Schema,
178 draft: StyledItemDraft<'_>,
179) -> StyleResult<EntityId> {
180 validate_optional_ref(tx, model, schema, draft.item, "IfcRepresentationItem")?;
181 if draft.styles.is_empty() {
182 return Err(invalid_authoring(
183 "IfcStyledItem",
184 "Styles",
185 "at least one style is required",
186 ));
187 }
188 for style in &draft.styles {
189 validate_ref(tx, model, schema, *style, "IfcPresentationStyle")?;
190 }
191
192 let mut staged = tx.clone();
193 let style_values = if schema.version() == Some(ifc_schema::SchemaVersion::Ifc2x3) {
194 let wrapper = build_named(
195 schema,
196 "IfcPresentationStyleAssignment",
197 vec![(
198 "Styles",
199 Value::List(draft.styles.into_iter().map(Value::Ref).collect()),
200 )],
201 )?;
202 vec![Value::Ref(staged.create(wrapper))]
203 } else {
204 draft.styles.into_iter().map(Value::Ref).collect()
205 };
206 let mut values = vec![("Styles", Value::List(style_values))];
207 optional_reference(&mut values, "Item", draft.item);
208 optional_text(&mut values, "Name", draft.name);
209 let id = staged.create(build_named(schema, "IfcStyledItem", values)?);
210 *tx = staged;
211 Ok(id)
212}
213
214pub fn create_presentation_layer_with_style(
218 tx: &mut Transaction,
219 model: &Model,
220 schema: &Schema,
221 draft: PresentationLayerDraft<'_>,
222) -> StyleResult<EntityId> {
223 if draft.name.is_empty() {
224 return Err(invalid_authoring(
225 "IfcPresentationLayerWithStyle",
226 "Name",
227 "must not be empty",
228 ));
229 }
230 if draft.assigned_items.is_empty() {
231 return Err(invalid_authoring(
232 "IfcPresentationLayerWithStyle",
233 "AssignedItems",
234 "at least one item is required",
235 ));
236 }
237 for item in &draft.assigned_items {
238 validate_layered_item(tx, model, schema, *item)?;
239 }
240 for style in &draft.layer_styles {
241 validate_ref(tx, model, schema, *style, "IfcPresentationStyle")?;
242 }
243 let mut values = vec![
244 ("Name", text(draft.name)),
245 (
246 "AssignedItems",
247 Value::List(draft.assigned_items.into_iter().map(Value::Ref).collect()),
248 ),
249 (
250 "LayerStyles",
251 Value::List(draft.layer_styles.into_iter().map(Value::Ref).collect()),
252 ),
253 ];
254 optional_text(&mut values, "Description", draft.description);
255 optional_text(&mut values, "Identifier", draft.identifier);
256 if let Some(value) = draft.layer_on {
257 values.push(("LayerOn", Value::Bool(value)));
258 }
259 if let Some(value) = draft.layer_frozen {
260 values.push(("LayerFrozen", Value::Bool(value)));
261 }
262 if let Some(value) = draft.layer_blocked {
263 values.push(("LayerBlocked", Value::Bool(value)));
264 }
265 Ok(tx.create(build_named(
266 schema,
267 "IfcPresentationLayerWithStyle",
268 values,
269 )?))
270}
271
272fn staged_type(tx: &Transaction, model: &Model, target: EntityId) -> Option<String> {
273 tx.edits()
274 .iter()
275 .rev()
276 .find_map(|edit| match edit {
277 Edit::Create { id, entity } if *id == target => Some(entity.type_name.to_string()),
278 _ => None,
279 })
280 .or_else(|| model.get(target).map(|entity| entity.type_name.to_string()))
281}
282
283fn validate_surface_element(
284 tx: &Transaction,
285 model: &Model,
286 schema: &Schema,
287 target: EntityId,
288) -> StyleResult<String> {
289 let actual = staged_type(tx, model, target).ok_or(StyleError::DanglingReference {
290 source_id: EntityId(0),
291 target,
292 })?;
293 if SURFACE_STYLE_ELEMENT_MEMBERS
294 .iter()
295 .any(|member| schema.is_a(&actual, member))
296 {
297 Ok(actual)
298 } else {
299 Err(StyleError::ReferenceType {
300 target,
301 expected: "IfcSurfaceStyleElementSelect",
302 actual,
303 })
304 }
305}
306
307fn validate_layered_item(
308 tx: &Transaction,
309 model: &Model,
310 schema: &Schema,
311 target: EntityId,
312) -> StyleResult<()> {
313 let actual = staged_type(tx, model, target).ok_or(StyleError::DanglingReference {
314 source_id: EntityId(0),
315 target,
316 })?;
317 if schema.is_a(&actual, "IfcRepresentationItem") || schema.is_a(&actual, "IfcRepresentation") {
318 Ok(())
319 } else {
320 Err(StyleError::ReferenceType {
321 target,
322 expected: "IfcLayeredItem",
323 actual,
324 })
325 }
326}
327
328#[derive(Debug, Clone, Copy)]
335pub struct SurfaceStyleRenderingDraft {
336 pub surface_colour: EntityId,
338 pub transparency: Option<f64>,
340 pub diffuse: Option<ColourOrFactor>,
342 pub transmission: Option<ColourOrFactor>,
344 pub diffuse_transmission: Option<ColourOrFactor>,
346 pub reflection: Option<ColourOrFactor>,
348 pub specular: Option<ColourOrFactor>,
350 pub reflectance_method: &'static str,
352}
353
354pub fn create_surface_style_rendering(
362 tx: &mut Transaction,
363 model: &Model,
364 schema: &Schema,
365 draft: SurfaceStyleRenderingDraft,
366) -> StyleResult<EntityId> {
367 const ENTITY: &str = "IfcSurfaceStyleRendering";
368 validate_ref(tx, model, schema, draft.surface_colour, "IfcColourRgb")?;
369 if let Some(value) = draft.transparency {
370 validate_ratio(ENTITY, "Transparency", value)?;
371 }
372 let mut values = vec![("SurfaceColour", Value::Ref(draft.surface_colour))];
373 if let Some(value) = draft.transparency {
374 values.push(("Transparency", Value::Real(value)));
375 }
376 for (attribute, member) in [
377 ("DiffuseColour", draft.diffuse),
378 ("TransmissionColour", draft.transmission),
379 ("DiffuseTransmissionColour", draft.diffuse_transmission),
380 ("ReflectionColour", draft.reflection),
381 ("SpecularColour", draft.specular),
382 ] {
383 let Some(member) = member else { continue };
384 let value = match member {
385 ColourOrFactor::Colour(id) => {
386 validate_ref(tx, model, schema, id, "IfcColourRgb")?;
387 Value::Ref(id)
388 }
389 ColourOrFactor::Factor(factor) => {
390 validate_ratio(ENTITY, attribute, factor)?;
391 Value::Real(factor)
392 }
393 };
394 values.push((attribute, value));
395 }
396 let declared = schema
399 .attributes(ENTITY)
400 .iter()
401 .find(|a| a.name.eq_ignore_ascii_case("ReflectanceMethod"))
402 .and_then(|a| schema.type_def(&a.type_name))
403 .is_some_and(|def| match &def.kind {
404 ifc_schema::TypeKind::Enumeration(values) => values
405 .iter()
406 .any(|v| v.eq_ignore_ascii_case(draft.reflectance_method)),
407 _ => false,
408 });
409 if !declared {
410 return Err(invalid_authoring(
411 ENTITY,
412 "ReflectanceMethod",
413 draft.reflectance_method,
414 ));
415 }
416 values.push((
417 "ReflectanceMethod",
418 Value::Enum(draft.reflectance_method.into()),
419 ));
420 Ok(tx.create(build_named(schema, ENTITY, values)?))
421}