Skip to main content

ifc_style/authoring/
fill.rs

1//! Authoring fill-area patterns.
2//!
3//! Split from `external`: hatching and tiling are one concern -- how an
4//! area is filled -- and the module was over the monolith limit.
5
6use ifc_model::{EntityId, Model, Transaction, Value};
7use ifc_schema::Schema;
8
9use crate::error::StyleResult;
10
11use super::{build_named, invalid_authoring, validate_ref};
12
13/// `IfcHatchLineDistanceSelect`: the spacing between hatch lines.
14#[derive(Debug, Clone, Copy)]
15pub enum HatchLineDistance {
16    /// An `IfcPositiveLengthMeasure`: constant perpendicular spacing.
17    Length(f64),
18    /// An `IfcVector`: spacing and direction, for a skewed pattern.
19    Vector(EntityId),
20}
21
22/// Stage an `IfcFillAreaStyleHatching`.
23///
24/// One family of parallel hatch lines. `HatchLineAngle` is in radians,
25/// like every other `IfcPlaneAngleMeasure` in the schema.
26///
27/// # Errors
28///
29/// Refuses a non-positive or non-finite line distance, a non-finite
30/// angle, and a reference of the wrong type -- including a point that
31/// is not 2D, which `PatternStart2D` and `RefHatchLine2D` require.
32pub fn create_fill_area_style_hatching(
33    tx: &mut Transaction,
34    model: &Model,
35    schema: &Schema,
36    appearance: EntityId,
37    distance: HatchLineDistance,
38    reference: (Option<EntityId>, Option<EntityId>),
39    angle: f64,
40) -> StyleResult<EntityId> {
41    const ENTITY: &str = "IfcFillAreaStyleHatching";
42    let (point_of_reference, pattern_start) = reference;
43    validate_ref(tx, model, schema, appearance, "IfcCurveStyle")?;
44    if !angle.is_finite() {
45        return Err(invalid_authoring(ENTITY, "HatchLineAngle", "not finite"));
46    }
47    let start = match distance {
48        HatchLineDistance::Length(value) => {
49            if !value.is_finite() || value <= 0.0 {
50                return Err(invalid_authoring(
51                    ENTITY,
52                    "StartOfNextHatchLine",
53                    format!("expected a positive length, got {value}"),
54                ));
55            }
56            Value::Real(value)
57        }
58        HatchLineDistance::Vector(id) => {
59            validate_ref(tx, model, schema, id, "IfcVector")?;
60            Value::Ref(id)
61        }
62    };
63    for point in [point_of_reference, pattern_start].into_iter().flatten() {
64        validate_ref(tx, model, schema, point, "IfcCartesianPoint")?;
65    }
66    let mut values = vec![
67        ("HatchLineAppearance", Value::Ref(appearance)),
68        ("StartOfNextHatchLine", start),
69    ];
70    if let Some(point) = point_of_reference {
71        values.push(("PointOfReferenceHatchLine", Value::Ref(point)));
72    }
73    if let Some(point) = pattern_start {
74        values.push(("PatternStart", Value::Ref(point)));
75    }
76    values.push(("HatchLineAngle", Value::Real(angle)));
77    Ok(tx.create(build_named(schema, ENTITY, values)?))
78}
79
80/// Stage an `IfcFillAreaStyleTiles`.
81///
82/// A repeating tiled fill. `TilingPattern` is `LIST [2:2] OF IfcVector`
83/// -- exactly two, because two vectors define the lattice the tile
84/// repeats on; one would leave the repetition direction undetermined.
85///
86/// # Errors
87///
88/// Refuses a tiling pattern that is not exactly two vectors, an empty
89/// tile set (`SET [1:?]`), a non-positive tiling scale, and a
90/// reference of the wrong type.
91pub fn create_fill_area_style_tiles(
92    tx: &mut Transaction,
93    model: &Model,
94    schema: &Schema,
95    tiling_pattern: &[EntityId],
96    tiles: &[EntityId],
97    tiling_scale: f64,
98) -> StyleResult<EntityId> {
99    const ENTITY: &str = "IfcFillAreaStyleTiles";
100    if tiling_pattern.len() != 2 {
101        return Err(invalid_authoring(
102            ENTITY,
103            "TilingPattern",
104            format!("expected exactly two vectors, got {}", tiling_pattern.len()),
105        ));
106    }
107    if tiles.is_empty() {
108        return Err(invalid_authoring(ENTITY, "Tiles", "empty"));
109    }
110    if !tiling_scale.is_finite() || tiling_scale <= 0.0 {
111        return Err(invalid_authoring(
112            ENTITY,
113            "TilingScale",
114            format!("expected a positive ratio, got {tiling_scale}"),
115        ));
116    }
117    for vector in tiling_pattern {
118        validate_ref(tx, model, schema, *vector, "IfcVector")?;
119    }
120    for tile in tiles {
121        validate_ref(tx, model, schema, *tile, "IfcStyledItem")?;
122    }
123    let values = vec![
124        (
125            "TilingPattern",
126            Value::List(tiling_pattern.iter().copied().map(Value::Ref).collect()),
127        ),
128        (
129            "Tiles",
130            Value::List(tiles.iter().copied().map(Value::Ref).collect()),
131        ),
132        ("TilingScale", Value::Real(tiling_scale)),
133    ];
134    Ok(tx.create(build_named(schema, ENTITY, values)?))
135}