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)]
20#[non_exhaustive]
21pub struct ColourRgbDraft<'a> {
22 pub name: Option<&'a str>,
24 pub red: f64,
26 pub green: f64,
28 pub blue: f64,
30}
31
32impl<'a> ColourRgbDraft<'a> {
33 #[must_use]
36 pub fn new(red: f64, green: f64, blue: f64) -> Self {
37 Self {
38 name: None,
39 red,
40 green,
41 blue,
42 }
43 }
44
45 #[must_use]
49 pub fn name(mut self, value: &'a str) -> Self {
50 self.name = Some(value);
51 self
52 }
53}
54
55#[derive(Debug, Clone, Copy)]
58#[non_exhaustive]
59pub struct SurfaceStyleShadingDraft {
60 pub surface_colour: EntityId,
62 pub transparency: Option<f64>,
65}
66
67impl SurfaceStyleShadingDraft {
68 #[must_use]
71 pub fn new(surface_colour: EntityId) -> Self {
72 Self {
73 surface_colour,
74 transparency: None,
75 }
76 }
77
78 #[must_use]
83 pub fn transparency(mut self, value: f64) -> Self {
84 self.transparency = Some(value);
85 self
86 }
87}
88
89#[derive(Debug, Clone)]
92#[non_exhaustive]
93pub struct SurfaceStyleDraft<'a> {
94 pub name: Option<&'a str>,
96 pub side: crate::SurfaceSide,
98 pub elements: Vec<EntityId>,
102}
103
104impl<'a> SurfaceStyleDraft<'a> {
105 #[must_use]
108 pub fn new(side: crate::SurfaceSide, elements: Vec<EntityId>) -> Self {
109 Self {
110 name: None,
111 side,
112 elements,
113 }
114 }
115
116 #[must_use]
120 pub fn name(mut self, value: &'a str) -> Self {
121 self.name = Some(value);
122 self
123 }
124}
125
126#[derive(Debug, Clone)]
129#[non_exhaustive]
130pub struct StyledItemDraft<'a> {
131 pub item: Option<EntityId>,
133 pub styles: Vec<EntityId>,
136 pub name: Option<&'a str>,
138}
139
140impl<'a> StyledItemDraft<'a> {
141 #[must_use]
143 pub fn new(styles: Vec<EntityId>) -> Self {
144 Self {
145 item: None,
146 styles,
147 name: None,
148 }
149 }
150
151 #[must_use]
155 pub fn item(mut self, value: EntityId) -> Self {
156 self.item = Some(value);
157 self
158 }
159
160 #[must_use]
164 pub fn name(mut self, value: &'a str) -> Self {
165 self.name = Some(value);
166 self
167 }
168}
169
170#[derive(Debug, Clone)]
173#[non_exhaustive]
174pub struct PresentationLayerDraft<'a> {
175 pub name: &'a str,
177 pub description: Option<&'a str>,
179 pub assigned_items: Vec<EntityId>,
182 pub identifier: Option<&'a str>,
184 pub layer_on: Option<bool>,
186 pub layer_frozen: Option<bool>,
188 pub layer_blocked: Option<bool>,
190 pub layer_styles: Vec<EntityId>,
192}
193
194impl<'a> PresentationLayerDraft<'a> {
195 #[must_use]
198 pub fn new(name: &'a str, assigned_items: Vec<EntityId>) -> Self {
199 Self {
200 name,
201 description: None,
202 assigned_items,
203 identifier: None,
204 layer_on: None,
205 layer_frozen: None,
206 layer_blocked: None,
207 layer_styles: Vec::new(),
208 }
209 }
210
211 #[must_use]
215 pub fn description(mut self, value: &'a str) -> Self {
216 self.description = Some(value);
217 self
218 }
219
220 #[must_use]
224 pub fn identifier(mut self, value: &'a str) -> Self {
225 self.identifier = Some(value);
226 self
227 }
228
229 #[must_use]
233 pub fn layer_on(mut self, value: bool) -> Self {
234 self.layer_on = Some(value);
235 self
236 }
237
238 #[must_use]
242 pub fn layer_frozen(mut self, value: bool) -> Self {
243 self.layer_frozen = Some(value);
244 self
245 }
246
247 #[must_use]
251 pub fn layer_blocked(mut self, value: bool) -> Self {
252 self.layer_blocked = Some(value);
253 self
254 }
255
256 #[must_use]
260 pub fn layer_styles(mut self, value: Vec<EntityId>) -> Self {
261 self.layer_styles = value;
262 self
263 }
264}
265
266pub fn create_colour_rgb(
269 tx: &mut Transaction,
270 schema: &Schema,
271 draft: ColourRgbDraft<'_>,
272) -> StyleResult<EntityId> {
273 validate_ratio("IfcColourRgb", "Red", draft.red)?;
274 validate_ratio("IfcColourRgb", "Green", draft.green)?;
275 validate_ratio("IfcColourRgb", "Blue", draft.blue)?;
276 let mut values = Vec::new();
277 optional_text(&mut values, "Name", draft.name);
278 values.extend([
279 ("Red", Value::Real(draft.red)),
280 ("Green", Value::Real(draft.green)),
281 ("Blue", Value::Real(draft.blue)),
282 ]);
283 Ok(tx.create(build_named(schema, "IfcColourRgb", values)?))
284}
285
286pub fn create_surface_style_shading(
290 tx: &mut Transaction,
291 model: &Model,
292 schema: &Schema,
293 draft: SurfaceStyleShadingDraft,
294) -> StyleResult<EntityId> {
295 validate_ref(tx, model, schema, draft.surface_colour, "IfcColourRgb")?;
296 if let Some(value) = draft.transparency {
297 validate_ratio("IfcSurfaceStyleShading", "Transparency", value)?;
298 }
299 let mut values = vec![("SurfaceColour", Value::Ref(draft.surface_colour))];
300 if let Some(value) = draft.transparency {
301 values.push(("Transparency", Value::Real(value)));
302 }
303 Ok(tx.create(build_named(schema, "IfcSurfaceStyleShading", values)?))
304}
305
306pub fn create_surface_style(
310 tx: &mut Transaction,
311 model: &Model,
312 schema: &Schema,
313 draft: SurfaceStyleDraft<'_>,
314) -> StyleResult<EntityId> {
315 if draft.elements.is_empty() || draft.elements.len() > 5 {
316 return Err(invalid_authoring(
317 "IfcSurfaceStyle",
318 "Styles",
319 format!("expected 1..=5 elements, found {}", draft.elements.len()),
320 ));
321 }
322 let mut element_types = Vec::with_capacity(draft.elements.len());
323 for element in &draft.elements {
324 element_types.push(validate_surface_element(tx, model, schema, *element)?);
325 }
326 if let Some(category) =
327 duplicate_surface_element_category(schema, element_types.iter().map(String::as_str))
328 {
329 return Err(invalid_authoring(
330 "IfcSurfaceStyle",
331 "Styles",
332 format!("duplicate {category} category"),
333 ));
334 }
335 let mut values = Vec::new();
336 optional_text(&mut values, "Name", draft.name);
337 values.push(("Side", enumeration(draft.side.as_ifc())));
338 values.push((
339 "Styles",
340 Value::List(draft.elements.into_iter().map(Value::Ref).collect()),
341 ));
342 Ok(tx.create(build_named(schema, "IfcSurfaceStyle", values)?))
343}
344
345pub fn create_styled_item(
349 tx: &mut Transaction,
350 model: &Model,
351 schema: &Schema,
352 draft: StyledItemDraft<'_>,
353) -> StyleResult<EntityId> {
354 validate_optional_ref(tx, model, schema, draft.item, "IfcRepresentationItem")?;
355 if draft.styles.is_empty() {
356 return Err(invalid_authoring(
357 "IfcStyledItem",
358 "Styles",
359 "at least one style is required",
360 ));
361 }
362 for style in &draft.styles {
363 validate_ref(tx, model, schema, *style, "IfcPresentationStyle")?;
364 }
365
366 let mut staged = tx.clone();
367 let style_values = if schema.version() == Some(ifc_schema::SchemaVersion::Ifc2x3) {
368 let wrapper = build_named(
369 schema,
370 "IfcPresentationStyleAssignment",
371 vec![(
372 "Styles",
373 Value::List(draft.styles.into_iter().map(Value::Ref).collect()),
374 )],
375 )?;
376 vec![Value::Ref(staged.create(wrapper))]
377 } else {
378 draft.styles.into_iter().map(Value::Ref).collect()
379 };
380 let mut values = vec![("Styles", Value::List(style_values))];
381 optional_reference(&mut values, "Item", draft.item);
382 optional_text(&mut values, "Name", draft.name);
383 let id = staged.create(build_named(schema, "IfcStyledItem", values)?);
384 *tx = staged;
385 Ok(id)
386}
387
388pub fn create_presentation_layer_with_style(
392 tx: &mut Transaction,
393 model: &Model,
394 schema: &Schema,
395 draft: PresentationLayerDraft<'_>,
396) -> StyleResult<EntityId> {
397 if draft.name.is_empty() {
398 return Err(invalid_authoring(
399 "IfcPresentationLayerWithStyle",
400 "Name",
401 "must not be empty",
402 ));
403 }
404 if draft.assigned_items.is_empty() {
405 return Err(invalid_authoring(
406 "IfcPresentationLayerWithStyle",
407 "AssignedItems",
408 "at least one item is required",
409 ));
410 }
411 for item in &draft.assigned_items {
412 validate_layered_item(tx, model, schema, *item)?;
413 }
414 for style in &draft.layer_styles {
415 validate_ref(tx, model, schema, *style, "IfcPresentationStyle")?;
416 }
417 let mut values = vec![
418 ("Name", text(draft.name)),
419 (
420 "AssignedItems",
421 Value::List(draft.assigned_items.into_iter().map(Value::Ref).collect()),
422 ),
423 (
424 "LayerStyles",
425 Value::List(draft.layer_styles.into_iter().map(Value::Ref).collect()),
426 ),
427 ];
428 optional_text(&mut values, "Description", draft.description);
429 optional_text(&mut values, "Identifier", draft.identifier);
430 if let Some(value) = draft.layer_on {
431 values.push(("LayerOn", Value::Bool(value)));
432 }
433 if let Some(value) = draft.layer_frozen {
434 values.push(("LayerFrozen", Value::Bool(value)));
435 }
436 if let Some(value) = draft.layer_blocked {
437 values.push(("LayerBlocked", Value::Bool(value)));
438 }
439 Ok(tx.create(build_named(
440 schema,
441 "IfcPresentationLayerWithStyle",
442 values,
443 )?))
444}
445
446fn staged_type(tx: &Transaction, model: &Model, target: EntityId) -> Option<String> {
447 tx.edits()
448 .iter()
449 .rev()
450 .find_map(|edit| match edit {
451 Edit::Create { id, entity } if *id == target => Some(entity.type_name.to_string()),
452 _ => None,
453 })
454 .or_else(|| model.get(target).map(|entity| entity.type_name.to_string()))
455}
456
457fn validate_surface_element(
458 tx: &Transaction,
459 model: &Model,
460 schema: &Schema,
461 target: EntityId,
462) -> StyleResult<String> {
463 let actual = staged_type(tx, model, target).ok_or(StyleError::DanglingReference {
464 source_id: EntityId(0),
465 target,
466 })?;
467 if SURFACE_STYLE_ELEMENT_MEMBERS
468 .iter()
469 .any(|member| schema.is_a(&actual, member))
470 {
471 Ok(actual)
472 } else {
473 Err(StyleError::ReferenceType {
474 target,
475 expected: "IfcSurfaceStyleElementSelect",
476 actual,
477 })
478 }
479}
480
481fn validate_layered_item(
482 tx: &Transaction,
483 model: &Model,
484 schema: &Schema,
485 target: EntityId,
486) -> StyleResult<()> {
487 let actual = staged_type(tx, model, target).ok_or(StyleError::DanglingReference {
488 source_id: EntityId(0),
489 target,
490 })?;
491 if schema.is_a(&actual, "IfcRepresentationItem") || schema.is_a(&actual, "IfcRepresentation") {
492 Ok(())
493 } else {
494 Err(StyleError::ReferenceType {
495 target,
496 expected: "IfcLayeredItem",
497 actual,
498 })
499 }
500}
501
502#[derive(Debug, Clone, Copy)]
509#[non_exhaustive]
510pub struct SurfaceStyleRenderingDraft {
511 pub surface_colour: EntityId,
513 pub transparency: Option<f64>,
515 pub diffuse: Option<ColourOrFactor>,
517 pub transmission: Option<ColourOrFactor>,
519 pub diffuse_transmission: Option<ColourOrFactor>,
521 pub reflection: Option<ColourOrFactor>,
523 pub specular: Option<ColourOrFactor>,
525 pub reflectance_method: &'static str,
527}
528
529impl SurfaceStyleRenderingDraft {
530 #[must_use]
533 pub fn new(surface_colour: EntityId, reflectance_method: &'static str) -> Self {
534 Self {
535 surface_colour,
536 transparency: None,
537 diffuse: None,
538 transmission: None,
539 diffuse_transmission: None,
540 reflection: None,
541 specular: None,
542 reflectance_method,
543 }
544 }
545
546 #[must_use]
550 pub fn transparency(mut self, value: f64) -> Self {
551 self.transparency = Some(value);
552 self
553 }
554
555 #[must_use]
559 pub fn diffuse(mut self, value: ColourOrFactor) -> Self {
560 self.diffuse = Some(value);
561 self
562 }
563
564 #[must_use]
568 pub fn transmission(mut self, value: ColourOrFactor) -> Self {
569 self.transmission = Some(value);
570 self
571 }
572
573 #[must_use]
577 pub fn diffuse_transmission(mut self, value: ColourOrFactor) -> Self {
578 self.diffuse_transmission = Some(value);
579 self
580 }
581
582 #[must_use]
586 pub fn reflection(mut self, value: ColourOrFactor) -> Self {
587 self.reflection = Some(value);
588 self
589 }
590
591 #[must_use]
595 pub fn specular(mut self, value: ColourOrFactor) -> Self {
596 self.specular = Some(value);
597 self
598 }
599}
600
601pub fn create_surface_style_rendering(
609 tx: &mut Transaction,
610 model: &Model,
611 schema: &Schema,
612 draft: SurfaceStyleRenderingDraft,
613) -> StyleResult<EntityId> {
614 const ENTITY: &str = "IfcSurfaceStyleRendering";
615 validate_ref(tx, model, schema, draft.surface_colour, "IfcColourRgb")?;
616 if let Some(value) = draft.transparency {
617 validate_ratio(ENTITY, "Transparency", value)?;
618 }
619 let mut values = vec![("SurfaceColour", Value::Ref(draft.surface_colour))];
620 if let Some(value) = draft.transparency {
621 values.push(("Transparency", Value::Real(value)));
622 }
623 for (attribute, member) in [
624 ("DiffuseColour", draft.diffuse),
625 ("TransmissionColour", draft.transmission),
626 ("DiffuseTransmissionColour", draft.diffuse_transmission),
627 ("ReflectionColour", draft.reflection),
628 ("SpecularColour", draft.specular),
629 ] {
630 let Some(member) = member else { continue };
631 let value = match member {
632 ColourOrFactor::Colour(id) => {
633 validate_ref(tx, model, schema, id, "IfcColourRgb")?;
634 Value::Ref(id)
635 }
636 ColourOrFactor::Factor(factor) => {
637 validate_ratio(ENTITY, attribute, factor)?;
638 Value::Real(factor)
639 }
640 };
641 values.push((attribute, value));
642 }
643 let declared = schema
646 .attributes(ENTITY)
647 .iter()
648 .find(|a| a.name.eq_ignore_ascii_case("ReflectanceMethod"))
649 .and_then(|a| schema.type_def(&a.type_name))
650 .is_some_and(|def| match &def.kind {
651 ifc_schema::TypeKind::Enumeration(values) => values
652 .iter()
653 .any(|v| v.eq_ignore_ascii_case(draft.reflectance_method)),
654 _ => false,
655 });
656 if !declared {
657 return Err(invalid_authoring(
658 ENTITY,
659 "ReflectanceMethod",
660 draft.reflectance_method,
661 ));
662 }
663 values.push((
664 "ReflectanceMethod",
665 Value::Enum(draft.reflectance_method.into()),
666 ));
667 Ok(tx.create(build_named(schema, ENTITY, values)?))
668}