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)]
175pub struct CurveStyleDraft<'a> {
176 pub name: Option<&'a str>,
178 pub curve_font: Option<EntityId>,
180 pub curve_width: Option<CurveWidth>,
182 pub curve_colour: Option<EntityId>,
184 pub model_or_draughting: Option<bool>,
186}
187
188#[derive(Debug, Clone, Copy, PartialEq, Eq)]
194pub enum FillStyleKind {
195 Colour,
197 ExternalHatchStyle,
199 Hatching,
201 Tiles,
203}
204
205pub fn create_fill_area_style(
212 tx: &mut Transaction,
213 schema: &Schema,
214 name: Option<&str>,
215 styles: &[(EntityId, FillStyleKind)],
216 model_or_draughting: Option<bool>,
217) -> StyleResult<EntityId> {
218 const ENTITY: &str = "IfcFillAreaStyle";
219 if styles.is_empty() {
220 return Err(invalid_authoring(ENTITY, "FillStyles", "empty"));
221 }
222 for (kind, attribute) in [
223 (FillStyleKind::Colour, "MaxOneColour"),
224 (FillStyleKind::ExternalHatchStyle, "MaxOneExtHatchStyle"),
225 ] {
226 let found = styles.iter().filter(|(_, k)| *k == kind).count();
227 if found > 1 {
228 return Err(invalid_authoring(ENTITY, attribute, format!("{found}")));
229 }
230 }
231 let mut values = Vec::new();
232 optional_text(&mut values, "Name", name);
233 values.push((
234 "FillStyles",
235 Value::List(styles.iter().map(|(id, _)| Value::Ref(*id)).collect()),
236 ));
237 if let Some(flag) = model_or_draughting {
238 let flag_attribute = "ModelOrDraughting";
240 values.push((flag_attribute, Value::Bool(flag)));
241 }
242 Ok(tx.create(build_named(schema, ENTITY, values)?))
243}
244
245pub fn create_text_style_for_defined_font(
247 tx: &mut Transaction,
248 schema: &Schema,
249 colour: EntityId,
250 background: Option<EntityId>,
251) -> StyleResult<EntityId> {
252 let mut values = vec![("Colour", Value::Ref(colour))];
253 if let Some(background) = background {
254 values.push(("BackgroundColour", Value::Ref(background)));
255 }
256 Ok(tx.create(build_named(schema, "IfcTextStyleForDefinedFont", values)?))
257}
258
259pub fn create_text_style_font_model(
267 tx: &mut Transaction,
268 schema: &Schema,
269 name: &str,
270 font_family: &[&str],
271 font_size: f64,
272) -> StyleResult<EntityId> {
273 const ENTITY: &str = "IfcTextStyleFontModel";
274 if name.trim().is_empty() {
275 return Err(invalid_authoring(ENTITY, "Name", name));
276 }
277 if font_family.is_empty() {
278 return Err(invalid_authoring(ENTITY, "FontFamily", "empty"));
279 }
280 if !font_size.is_finite() || font_size <= 0.0 {
281 return Err(invalid_authoring(
282 ENTITY,
283 "FontSize",
284 format!("{font_size}"),
285 ));
286 }
287 let values = vec![
288 ("Name", Value::Text(name.into())),
289 (
290 "FontFamily",
291 Value::List(
292 font_family
293 .iter()
294 .map(|family| Value::Text((*family).into()))
295 .collect(),
296 ),
297 ),
298 (
299 "FontSize",
300 Value::Typed {
301 type_name: "IFCLENGTHMEASURE".into(),
302 value: Box::new(Value::Real(font_size)),
303 },
304 ),
305 ];
306 Ok(tx.create(build_named(schema, ENTITY, values)?))
307}
308
309pub const PREDEFINED_COLOUR_NAMES: &[&str] = &[
311 "black", "red", "green", "blue", "yellow", "magenta", "cyan", "white", "by layer",
312];
313
314pub const PREDEFINED_CURVE_FONT_NAMES: &[&str] = &[
316 "continuous",
317 "chain",
318 "chain double dash",
319 "dashed",
320 "dotted",
321 "by layer",
322];
323
324pub fn create_draughting_predefined_colour(
332 tx: &mut Transaction,
333 schema: &Schema,
334 name: &str,
335) -> StyleResult<EntityId> {
336 const ENTITY: &str = "IfcDraughtingPreDefinedColour";
337 if !PREDEFINED_COLOUR_NAMES.contains(&name) {
338 return Err(invalid_authoring(ENTITY, "Name", name));
339 }
340 let values = vec![("Name", Value::Text(name.into()))];
341 Ok(tx.create(build_named(schema, ENTITY, values)?))
342}
343
344pub fn create_draughting_predefined_curve_font(
350 tx: &mut Transaction,
351 schema: &Schema,
352 name: &str,
353) -> StyleResult<EntityId> {
354 const ENTITY: &str = "IfcDraughtingPreDefinedCurveFont";
355 if !PREDEFINED_CURVE_FONT_NAMES.contains(&name) {
356 return Err(invalid_authoring(ENTITY, "Name", name));
357 }
358 let values = vec![("Name", Value::Text(name.into()))];
359 Ok(tx.create(build_named(schema, ENTITY, values)?))
360}
361
362pub fn create_pixel_texture(
375 tx: &mut Transaction,
376 schema: &Schema,
377 draft: PixelTextureDraft<'_>,
378) -> StyleResult<EntityId> {
379 const ENTITY: &str = "IfcPixelTexture";
380 if draft.width < 1 {
381 return Err(invalid_authoring(
382 ENTITY,
383 "Width",
384 format!("{}", draft.width),
385 ));
386 }
387 if draft.height < 1 {
388 return Err(invalid_authoring(
389 ENTITY,
390 "Height",
391 format!("{}", draft.height),
392 ));
393 }
394 if !(1..=4).contains(&draft.colour_components) {
395 return Err(invalid_authoring(
396 ENTITY,
397 "ColourComponents",
398 format!("{}", draft.colour_components),
399 ));
400 }
401 let expected = i64::from(draft.width) * i64::from(draft.height);
402 if draft.pixel.len() as i64 != expected {
403 return Err(invalid_authoring(
404 ENTITY,
405 "SizeOfPixelList",
406 format!("{} pixels for {expected} cells", draft.pixel.len()),
407 ));
408 }
409 let first = draft.pixel[0].len();
412 for pixel in draft.pixel {
413 if pixel.len() % 2 != 0 || pixel.len() != first {
414 return Err(invalid_authoring(
415 ENTITY,
416 "PixelAsByteAndSameLength",
417 pixel.to_string(),
418 ));
419 }
420 }
421 let mut values = vec![
422 ("RepeatS", Value::Bool(draft.repeat_s)),
423 ("RepeatT", Value::Bool(draft.repeat_t)),
424 ];
425 optional_text(&mut values, "Mode", draft.mode);
426 values.extend([
427 ("Width", Value::Integer(i64::from(draft.width))),
428 ("Height", Value::Integer(i64::from(draft.height))),
429 (
430 "ColourComponents",
431 Value::Integer(i64::from(draft.colour_components)),
432 ),
433 (
434 "Pixel",
435 Value::List(
436 draft
437 .pixel
438 .iter()
439 .map(|pixel| Value::Binary((*pixel).into()))
440 .collect(),
441 ),
442 ),
443 ]);
444 Ok(tx.create(build_named(schema, ENTITY, values)?))
445}
446
447#[derive(Debug, Clone, Copy)]
449pub struct PixelTextureDraft<'a> {
450 pub repeat_s: bool,
452 pub repeat_t: bool,
454 pub mode: Option<&'a str>,
456 pub width: i32,
458 pub height: i32,
460 pub colour_components: i32,
462 pub pixel: &'a [&'a str],
464}
465
466pub fn create_texture_vertex(
473 tx: &mut Transaction,
474 schema: &Schema,
475 coordinates: [f64; 2],
476) -> StyleResult<EntityId> {
477 const ENTITY: &str = "IfcTextureVertex";
478 for value in coordinates {
479 if !value.is_finite() {
480 return Err(invalid_authoring(ENTITY, "Coordinates", format!("{value}")));
481 }
482 }
483 let values = vec![(
484 "Coordinates",
485 Value::List(coordinates.into_iter().map(Value::Real).collect()),
486 )];
487 Ok(tx.create(build_named(schema, ENTITY, values)?))
488}
489
490pub fn create_texture_vertex_list(
496 tx: &mut Transaction,
497 schema: &Schema,
498 coordinates: &[[f64; 2]],
499) -> StyleResult<EntityId> {
500 const ENTITY: &str = "IfcTextureVertexList";
501 if coordinates.is_empty() {
502 return Err(invalid_authoring(ENTITY, "TexCoordsList", "empty"));
503 }
504 for pair in coordinates {
505 for value in pair {
506 if !value.is_finite() {
507 return Err(invalid_authoring(
508 ENTITY,
509 "TexCoordsList",
510 format!("{value}"),
511 ));
512 }
513 }
514 }
515 let values = vec![(
516 "TexCoordsList",
517 Value::List(
518 coordinates
519 .iter()
520 .map(|pair| Value::List(pair.iter().copied().map(Value::Real).collect()))
521 .collect(),
522 ),
523 )];
524 Ok(tx.create(build_named(schema, ENTITY, values)?))
525}
526
527pub fn create_colour_rgb_list(
534 tx: &mut Transaction,
535 schema: &Schema,
536 colours: &[[f64; 3]],
537) -> StyleResult<EntityId> {
538 const ENTITY: &str = "IfcColourRgbList";
539 if colours.is_empty() {
540 return Err(invalid_authoring(ENTITY, "ColourList", "empty"));
541 }
542 for colour in colours {
543 for channel in colour {
544 if !channel.is_finite() || *channel < 0.0 || *channel > 1.0 {
545 return Err(invalid_authoring(
546 ENTITY,
547 "ColourList",
548 format!("{channel}"),
549 ));
550 }
551 }
552 }
553 let values = vec![(
554 "ColourList",
555 Value::List(
556 colours
557 .iter()
558 .map(|colour| Value::List(colour.iter().copied().map(Value::Real).collect()))
559 .collect(),
560 ),
561 )];
562 Ok(tx.create(build_named(schema, ENTITY, values)?))
563}
564
565pub fn create_indexed_colour_map(
573 tx: &mut Transaction,
574 schema: &Schema,
575 mapped_to: EntityId,
576 colours: EntityId,
577 colour_index: &[i64],
578 opacity: Option<f64>,
579) -> StyleResult<EntityId> {
580 const ENTITY: &str = "IfcIndexedColourMap";
581 if colour_index.is_empty() {
582 return Err(invalid_authoring(ENTITY, "ColourIndex", "empty"));
583 }
584 if let Some(index) = colour_index.iter().find(|index| **index < 1) {
585 return Err(invalid_authoring(ENTITY, "ColourIndex", format!("{index}")));
586 }
587 if let Some(value) = opacity {
588 if !value.is_finite() || !(0.0..=1.0).contains(&value) {
589 return Err(invalid_authoring(ENTITY, "Opacity", format!("{value}")));
590 }
591 }
592 let mut values = vec![("MappedTo", Value::Ref(mapped_to))];
593 if let Some(value) = opacity {
594 values.push(("Opacity", Value::Real(value)));
595 }
596 values.extend([
597 ("Colours", Value::Ref(colours)),
598 (
599 "ColourIndex",
600 Value::List(colour_index.iter().copied().map(Value::Integer).collect()),
601 ),
602 ]);
603 Ok(tx.create(build_named(schema, ENTITY, values)?))
604}
605
606pub fn create_presentation_layer_assignment(
612 tx: &mut Transaction,
613 schema: &Schema,
614 name: &str,
615 description: Option<&str>,
616 assigned_items: &[EntityId],
617 identifier: Option<&str>,
618) -> StyleResult<EntityId> {
619 const ENTITY: &str = "IfcPresentationLayerAssignment";
620 if name.trim().is_empty() {
621 return Err(invalid_authoring(ENTITY, "Name", name));
622 }
623 if assigned_items.is_empty() {
624 return Err(invalid_authoring(ENTITY, "AssignedItems", "empty"));
625 }
626 let mut values = vec![("Name", Value::Text(name.into()))];
627 optional_text(&mut values, "Description", description);
628 values.push((
629 "AssignedItems",
630 Value::List(assigned_items.iter().copied().map(Value::Ref).collect()),
631 ));
632 optional_text(&mut values, "Identifier", identifier);
633 Ok(tx.create(build_named(schema, ENTITY, values)?))
634}