1use ifc_model::{EntityId, Transaction, Value};
4use ifc_schema::Schema;
5
6use crate::error::StyleResult;
7
8use super::{build_named, invalid_authoring, optional_text, schema_attribute};
9
10pub fn create_curve_style_font_pattern(
18 tx: &mut Transaction,
19 schema: &Schema,
20 visible: f64,
21 invisible: f64,
22) -> StyleResult<EntityId> {
23 const ENTITY: &str = "IfcCurveStyleFontPattern";
24 if !visible.is_finite() || visible < 0.0 {
25 return Err(invalid_authoring(
26 ENTITY,
27 "VisibleSegmentLength",
28 format!("{visible}"),
29 ));
30 }
31 if !invisible.is_finite() || invisible <= 0.0 {
32 return Err(invalid_authoring(
33 ENTITY,
34 "InvisibleSegmentLength",
35 format!("{invisible}"),
36 ));
37 }
38 let values = vec![
39 ("VisibleSegmentLength", Value::Real(visible)),
40 ("InvisibleSegmentLength", Value::Real(invisible)),
41 ];
42 Ok(tx.create(build_named(schema, ENTITY, values)?))
43}
44
45pub fn create_curve_style_font(
51 tx: &mut Transaction,
52 schema: &Schema,
53 name: Option<&str>,
54 patterns: &[EntityId],
55) -> StyleResult<EntityId> {
56 const ENTITY: &str = "IfcCurveStyleFont";
57 if patterns.is_empty() {
58 return Err(invalid_authoring(ENTITY, "PatternList", "empty"));
59 }
60 let mut values = Vec::new();
61 optional_text(&mut values, "Name", name);
62 values.push((
63 "PatternList",
64 Value::List(patterns.iter().copied().map(Value::Ref).collect()),
65 ));
66 Ok(tx.create(build_named(schema, ENTITY, values)?))
67}
68
69pub fn create_curve_style_font_and_scaling(
76 tx: &mut Transaction,
77 schema: &Schema,
78 name: Option<&str>,
79 font: EntityId,
80 scaling: f64,
81) -> StyleResult<EntityId> {
82 const ENTITY: &str = "IfcCurveStyleFontAndScaling";
83 if !scaling.is_finite() || scaling <= 0.0 {
84 return Err(invalid_authoring(
85 ENTITY,
86 "CurveFontScaling",
87 format!("{scaling}"),
88 ));
89 }
90 let mut values = Vec::new();
91 optional_text(&mut values, "Name", name);
92 let font_attribute = schema_attribute(schema, ENTITY, ["CurveStyleFont", "CurveFont"]);
94 values.push((font_attribute, Value::Ref(font)));
95 values.push(("CurveFontScaling", Value::Real(scaling)));
96 Ok(tx.create(build_named(schema, ENTITY, values)?))
97}
98
99pub fn create_curve_style(
111 tx: &mut Transaction,
112 schema: &Schema,
113 draft: CurveStyleDraft<'_>,
114) -> StyleResult<EntityId> {
115 const ENTITY: &str = "IfcCurveStyle";
116 if draft.curve_font.is_none() && draft.curve_width.is_none() && draft.curve_colour.is_none() {
117 return Err(invalid_authoring(
118 ENTITY,
119 "CurveFont",
120 "a curve style must carry a font, a width or a colour",
121 ));
122 }
123 let mut values = Vec::new();
124 optional_text(&mut values, "Name", draft.name);
125 if let Some(font) = draft.curve_font {
126 values.push(("CurveFont", Value::Ref(font)));
127 }
128 if let Some(width) = draft.curve_width {
129 values.push(("CurveWidth", width.into_value(ENTITY)?));
130 }
131 if let Some(colour) = draft.curve_colour {
132 values.push(("CurveColour", Value::Ref(colour)));
133 }
134 if let Some(flag) = draft.model_or_draughting {
135 values.push(("ModelOrDraughting", Value::Bool(flag)));
136 }
137 Ok(tx.create(build_named(schema, ENTITY, values)?))
138}
139
140#[derive(Debug, Clone, Copy, PartialEq)]
146pub enum CurveWidth {
147 PositiveLength(f64),
149 ByLayer,
151}
152
153impl CurveWidth {
154 fn into_value(self, entity: &'static str) -> StyleResult<Value> {
155 match self {
156 Self::PositiveLength(length) if length.is_finite() && length > 0.0 => {
157 Ok(Value::Typed {
158 type_name: "IFCPOSITIVELENGTHMEASURE".into(),
159 value: Box::new(Value::Real(length)),
160 })
161 }
162 Self::PositiveLength(length) => {
163 Err(invalid_authoring(entity, "CurveWidth", format!("{length}")))
164 }
165 Self::ByLayer => Ok(Value::Typed {
166 type_name: "IFCDESCRIPTIVEMEASURE".into(),
167 value: Box::new(Value::Text("by layer".into())),
168 }),
169 }
170 }
171}
172
173#[derive(Debug, Clone, Copy, Default)]
175#[non_exhaustive]
176pub struct CurveStyleDraft<'a> {
177 pub name: Option<&'a str>,
179 pub curve_font: Option<EntityId>,
181 pub curve_width: Option<CurveWidth>,
183 pub curve_colour: Option<EntityId>,
185 pub model_or_draughting: Option<bool>,
187}
188
189impl<'a> CurveStyleDraft<'a> {
190 #[must_use]
192 pub fn new() -> Self {
193 Self::default()
194 }
195
196 #[must_use]
200 pub fn name(mut self, value: &'a str) -> Self {
201 self.name = Some(value);
202 self
203 }
204
205 #[must_use]
209 pub fn curve_font(mut self, value: EntityId) -> Self {
210 self.curve_font = Some(value);
211 self
212 }
213
214 #[must_use]
218 pub fn curve_width(mut self, value: CurveWidth) -> Self {
219 self.curve_width = Some(value);
220 self
221 }
222
223 #[must_use]
227 pub fn curve_colour(mut self, value: EntityId) -> Self {
228 self.curve_colour = Some(value);
229 self
230 }
231
232 #[must_use]
236 pub fn model_or_draughting(mut self, value: bool) -> Self {
237 self.model_or_draughting = Some(value);
238 self
239 }
240}
241
242#[derive(Debug, Clone, Copy, PartialEq, Eq)]
248pub enum FillStyleKind {
249 Colour,
251 ExternalHatchStyle,
253 Hatching,
255 Tiles,
257}
258
259pub fn create_fill_area_style(
266 tx: &mut Transaction,
267 schema: &Schema,
268 name: Option<&str>,
269 styles: &[(EntityId, FillStyleKind)],
270 model_or_draughting: Option<bool>,
271) -> StyleResult<EntityId> {
272 const ENTITY: &str = "IfcFillAreaStyle";
273 if styles.is_empty() {
274 return Err(invalid_authoring(ENTITY, "FillStyles", "empty"));
275 }
276 for (kind, attribute) in [
277 (FillStyleKind::Colour, "MaxOneColour"),
278 (FillStyleKind::ExternalHatchStyle, "MaxOneExtHatchStyle"),
279 ] {
280 let found = styles.iter().filter(|(_, k)| *k == kind).count();
281 if found > 1 {
282 return Err(invalid_authoring(ENTITY, attribute, format!("{found}")));
283 }
284 }
285 let mut values = Vec::new();
286 optional_text(&mut values, "Name", name);
287 values.push((
288 "FillStyles",
289 Value::List(styles.iter().map(|(id, _)| Value::Ref(*id)).collect()),
290 ));
291 if let Some(flag) = model_or_draughting {
292 let flag_attribute = "ModelOrDraughting";
294 values.push((flag_attribute, Value::Bool(flag)));
295 }
296 Ok(tx.create(build_named(schema, ENTITY, values)?))
297}
298
299pub fn create_text_style_for_defined_font(
301 tx: &mut Transaction,
302 schema: &Schema,
303 colour: EntityId,
304 background: Option<EntityId>,
305) -> StyleResult<EntityId> {
306 let mut values = vec![("Colour", Value::Ref(colour))];
307 if let Some(background) = background {
308 values.push(("BackgroundColour", Value::Ref(background)));
309 }
310 Ok(tx.create(build_named(schema, "IfcTextStyleForDefinedFont", values)?))
311}
312
313pub fn create_text_style_font_model(
321 tx: &mut Transaction,
322 schema: &Schema,
323 name: &str,
324 font_family: &[&str],
325 font_size: f64,
326) -> StyleResult<EntityId> {
327 const ENTITY: &str = "IfcTextStyleFontModel";
328 if name.trim().is_empty() {
329 return Err(invalid_authoring(ENTITY, "Name", name));
330 }
331 if font_family.is_empty() {
332 return Err(invalid_authoring(ENTITY, "FontFamily", "empty"));
333 }
334 if !font_size.is_finite() || font_size <= 0.0 {
335 return Err(invalid_authoring(
336 ENTITY,
337 "FontSize",
338 format!("{font_size}"),
339 ));
340 }
341 let values = vec![
342 ("Name", Value::Text(name.into())),
343 (
344 "FontFamily",
345 Value::List(
346 font_family
347 .iter()
348 .map(|family| Value::Text((*family).into()))
349 .collect(),
350 ),
351 ),
352 (
353 "FontSize",
354 Value::Typed {
355 type_name: "IFCLENGTHMEASURE".into(),
356 value: Box::new(Value::Real(font_size)),
357 },
358 ),
359 ];
360 Ok(tx.create(build_named(schema, ENTITY, values)?))
361}
362
363pub const PREDEFINED_COLOUR_NAMES: &[&str] = &[
365 "black", "red", "green", "blue", "yellow", "magenta", "cyan", "white", "by layer",
366];
367
368pub const PREDEFINED_CURVE_FONT_NAMES: &[&str] = &[
370 "continuous",
371 "chain",
372 "chain double dash",
373 "dashed",
374 "dotted",
375 "by layer",
376];
377
378pub fn create_draughting_predefined_colour(
386 tx: &mut Transaction,
387 schema: &Schema,
388 name: &str,
389) -> StyleResult<EntityId> {
390 const ENTITY: &str = "IfcDraughtingPreDefinedColour";
391 if !PREDEFINED_COLOUR_NAMES.contains(&name) {
392 return Err(invalid_authoring(ENTITY, "Name", name));
393 }
394 let values = vec![("Name", Value::Text(name.into()))];
395 Ok(tx.create(build_named(schema, ENTITY, values)?))
396}
397
398pub fn create_draughting_predefined_curve_font(
404 tx: &mut Transaction,
405 schema: &Schema,
406 name: &str,
407) -> StyleResult<EntityId> {
408 const ENTITY: &str = "IfcDraughtingPreDefinedCurveFont";
409 if !PREDEFINED_CURVE_FONT_NAMES.contains(&name) {
410 return Err(invalid_authoring(ENTITY, "Name", name));
411 }
412 let values = vec![("Name", Value::Text(name.into()))];
413 Ok(tx.create(build_named(schema, ENTITY, values)?))
414}
415
416pub fn create_pixel_texture(
429 tx: &mut Transaction,
430 schema: &Schema,
431 draft: PixelTextureDraft<'_>,
432) -> StyleResult<EntityId> {
433 const ENTITY: &str = "IfcPixelTexture";
434 if draft.width < 1 {
435 return Err(invalid_authoring(
436 ENTITY,
437 "Width",
438 format!("{}", draft.width),
439 ));
440 }
441 if draft.height < 1 {
442 return Err(invalid_authoring(
443 ENTITY,
444 "Height",
445 format!("{}", draft.height),
446 ));
447 }
448 if !(1..=4).contains(&draft.colour_components) {
449 return Err(invalid_authoring(
450 ENTITY,
451 "ColourComponents",
452 format!("{}", draft.colour_components),
453 ));
454 }
455 let expected = i64::from(draft.width) * i64::from(draft.height);
456 if draft.pixel.len() as i64 != expected {
457 return Err(invalid_authoring(
458 ENTITY,
459 "SizeOfPixelList",
460 format!("{} pixels for {expected} cells", draft.pixel.len()),
461 ));
462 }
463 let first = draft.pixel[0].len();
466 for pixel in draft.pixel {
467 if pixel.len() % 2 != 0 || pixel.len() != first {
468 return Err(invalid_authoring(
469 ENTITY,
470 "PixelAsByteAndSameLength",
471 pixel.to_string(),
472 ));
473 }
474 }
475 let mut values = vec![
476 ("RepeatS", Value::Bool(draft.repeat_s)),
477 ("RepeatT", Value::Bool(draft.repeat_t)),
478 ];
479 optional_text(&mut values, "Mode", draft.mode);
480 values.extend([
481 ("Width", Value::Integer(i64::from(draft.width))),
482 ("Height", Value::Integer(i64::from(draft.height))),
483 (
484 "ColourComponents",
485 Value::Integer(i64::from(draft.colour_components)),
486 ),
487 (
488 "Pixel",
489 Value::List(
490 draft
491 .pixel
492 .iter()
493 .map(|pixel| Value::Binary((*pixel).into()))
494 .collect(),
495 ),
496 ),
497 ]);
498 Ok(tx.create(build_named(schema, ENTITY, values)?))
499}
500
501#[derive(Debug, Clone, Copy)]
503#[non_exhaustive]
504pub struct PixelTextureDraft<'a> {
505 pub repeat_s: bool,
507 pub repeat_t: bool,
509 pub mode: Option<&'a str>,
511 pub width: i32,
513 pub height: i32,
515 pub colour_components: i32,
517 pub pixel: &'a [&'a str],
519}
520
521impl<'a> PixelTextureDraft<'a> {
522 #[must_use]
525 pub fn new(width: i32, height: i32, colour_components: i32, pixel: &'a [&'a str]) -> Self {
526 Self {
527 repeat_s: false,
528 repeat_t: false,
529 mode: None,
530 width,
531 height,
532 colour_components,
533 pixel,
534 }
535 }
536
537 #[must_use]
541 pub fn repeat_s(mut self, value: bool) -> Self {
542 self.repeat_s = value;
543 self
544 }
545
546 #[must_use]
550 pub fn repeat_t(mut self, value: bool) -> Self {
551 self.repeat_t = value;
552 self
553 }
554
555 #[must_use]
559 pub fn mode(mut self, value: &'a str) -> Self {
560 self.mode = Some(value);
561 self
562 }
563}
564
565pub fn create_texture_vertex(
572 tx: &mut Transaction,
573 schema: &Schema,
574 coordinates: [f64; 2],
575) -> StyleResult<EntityId> {
576 const ENTITY: &str = "IfcTextureVertex";
577 for value in coordinates {
578 if !value.is_finite() {
579 return Err(invalid_authoring(ENTITY, "Coordinates", format!("{value}")));
580 }
581 }
582 let values = vec![(
583 "Coordinates",
584 Value::List(coordinates.into_iter().map(Value::Real).collect()),
585 )];
586 Ok(tx.create(build_named(schema, ENTITY, values)?))
587}
588
589pub fn create_texture_vertex_list(
595 tx: &mut Transaction,
596 schema: &Schema,
597 coordinates: &[[f64; 2]],
598) -> StyleResult<EntityId> {
599 const ENTITY: &str = "IfcTextureVertexList";
600 if coordinates.is_empty() {
601 return Err(invalid_authoring(ENTITY, "TexCoordsList", "empty"));
602 }
603 for pair in coordinates {
604 for value in pair {
605 if !value.is_finite() {
606 return Err(invalid_authoring(
607 ENTITY,
608 "TexCoordsList",
609 format!("{value}"),
610 ));
611 }
612 }
613 }
614 let values = vec![(
615 "TexCoordsList",
616 Value::List(
617 coordinates
618 .iter()
619 .map(|pair| Value::List(pair.iter().copied().map(Value::Real).collect()))
620 .collect(),
621 ),
622 )];
623 Ok(tx.create(build_named(schema, ENTITY, values)?))
624}
625
626pub fn create_colour_rgb_list(
633 tx: &mut Transaction,
634 schema: &Schema,
635 colours: &[[f64; 3]],
636) -> StyleResult<EntityId> {
637 const ENTITY: &str = "IfcColourRgbList";
638 if colours.is_empty() {
639 return Err(invalid_authoring(ENTITY, "ColourList", "empty"));
640 }
641 for colour in colours {
642 for channel in colour {
643 if !channel.is_finite() || *channel < 0.0 || *channel > 1.0 {
644 return Err(invalid_authoring(
645 ENTITY,
646 "ColourList",
647 format!("{channel}"),
648 ));
649 }
650 }
651 }
652 let values = vec![(
653 "ColourList",
654 Value::List(
655 colours
656 .iter()
657 .map(|colour| Value::List(colour.iter().copied().map(Value::Real).collect()))
658 .collect(),
659 ),
660 )];
661 Ok(tx.create(build_named(schema, ENTITY, values)?))
662}
663
664pub fn create_indexed_colour_map(
672 tx: &mut Transaction,
673 schema: &Schema,
674 mapped_to: EntityId,
675 colours: EntityId,
676 colour_index: &[i64],
677 opacity: Option<f64>,
678) -> StyleResult<EntityId> {
679 const ENTITY: &str = "IfcIndexedColourMap";
680 if colour_index.is_empty() {
681 return Err(invalid_authoring(ENTITY, "ColourIndex", "empty"));
682 }
683 if let Some(index) = colour_index.iter().find(|index| **index < 1) {
684 return Err(invalid_authoring(ENTITY, "ColourIndex", format!("{index}")));
685 }
686 if let Some(value) = opacity {
687 if !value.is_finite() || !(0.0..=1.0).contains(&value) {
688 return Err(invalid_authoring(ENTITY, "Opacity", format!("{value}")));
689 }
690 }
691 let mut values = vec![("MappedTo", Value::Ref(mapped_to))];
692 if let Some(value) = opacity {
693 values.push(("Opacity", Value::Real(value)));
694 }
695 values.extend([
696 ("Colours", Value::Ref(colours)),
697 (
698 "ColourIndex",
699 Value::List(colour_index.iter().copied().map(Value::Integer).collect()),
700 ),
701 ]);
702 Ok(tx.create(build_named(schema, ENTITY, values)?))
703}
704
705pub fn create_presentation_layer_assignment(
711 tx: &mut Transaction,
712 schema: &Schema,
713 name: &str,
714 description: Option<&str>,
715 assigned_items: &[EntityId],
716 identifier: Option<&str>,
717) -> StyleResult<EntityId> {
718 const ENTITY: &str = "IfcPresentationLayerAssignment";
719 if name.trim().is_empty() {
720 return Err(invalid_authoring(ENTITY, "Name", name));
721 }
722 if assigned_items.is_empty() {
723 return Err(invalid_authoring(ENTITY, "AssignedItems", "empty"));
724 }
725 let mut values = vec![("Name", Value::Text(name.into()))];
726 optional_text(&mut values, "Description", description);
727 values.push((
728 "AssignedItems",
729 Value::List(assigned_items.iter().copied().map(Value::Ref).collect()),
730 ));
731 optional_text(&mut values, "Identifier", identifier);
732 Ok(tx.create(build_named(schema, ENTITY, values)?))
733}