Skip to main content

ifc_properties/pset/
lining.rs

1//! Door and window lining properties.
2//!
3//! # Paired attributes
4//!
5//! The lining rules are pairing constraints, and door and window state
6//! them differently. A door's transom and casing pairs are XOR: both or
7//! neither. A window's transom and mullion offsets are ordered: the
8//! second may only appear when the first does. Writing one half of
9//! either pair produces a file that parses and reports a dimension
10//! nothing can interpret.
11//!
12//! # Release
13//!
14//! [`add_door_lining_properties`] and [`add_window_lining_properties`]
15//! take no model and write the IFC4 layout; the `*_with_owner_history`
16//! variants write the model's declared release (see `owned.rs`). IFC2X3
17//! declares neither `LiningToPanelOffsetX/Y` nor, on a window,
18//! `LiningOffset`, and types the thicknesses as positive lengths.
19
20use ifc_model::{EntityId, Model, Transaction, Value};
21
22use crate::PropertyResult;
23
24use super::owned::{stage_ifc4, stage_owned, Rooted};
25use super::predefined::{invalid, measure, ratio, require_guid, Measure};
26
27/// Attributes of an `IfcDoorLiningProperties`.
28#[derive(Debug, Clone, Copy, Default)]
29pub struct DoorLiningDraft<'a> {
30    /// `Name`.
31    pub name: Option<&'a str>,
32    /// `Description`.
33    pub description: Option<&'a str>,
34    /// `LiningDepth`, a positive length. Requires `lining_thickness` (WR31).
35    pub lining_depth: Option<f64>,
36    /// `LiningThickness`, a non-negative length.
37    pub lining_thickness: Option<f64>,
38    /// `ThresholdDepth`, a positive length. Requires `threshold_thickness` (WR32).
39    pub threshold_depth: Option<f64>,
40    /// `ThresholdThickness`, a non-negative length.
41    pub threshold_thickness: Option<f64>,
42    /// `TransomThickness`, a non-negative length. Paired with `transom_offset` (WR33).
43    pub transom_thickness: Option<f64>,
44    /// `TransomOffset`, a length. Paired with `transom_thickness` (WR33).
45    pub transom_offset: Option<f64>,
46    /// `LiningOffset`, a length.
47    pub lining_offset: Option<f64>,
48    /// `ThresholdOffset`, a length.
49    pub threshold_offset: Option<f64>,
50    /// `CasingThickness`, a positive length. Paired with `casing_depth` (WR34).
51    pub casing_thickness: Option<f64>,
52    /// `CasingDepth`, a positive length. Paired with `casing_thickness` (WR34).
53    pub casing_depth: Option<f64>,
54    /// `ShapeAspectStyle`.
55    pub shape_aspect_style: Option<EntityId>,
56    /// `LiningToPanelOffsetX`, a length.
57    pub lining_to_panel_offset_x: Option<f64>,
58    /// `LiningToPanelOffsetY`, a length.
59    pub lining_to_panel_offset_y: Option<f64>,
60}
61
62/// Stage an `IfcDoorLiningProperties`.
63///
64/// Takes no model, so it writes the IFC4 layout with `OwnerHistory` `$`:
65/// valid IFC4 and IFC4X3, never valid IFC2X3. Use
66/// [`add_door_lining_properties_with_owner_history`] to write the model's
67/// declared release.
68///
69/// # Errors
70///
71/// Refuses a malformed GlobalId; a depth without its thickness (WR31,
72/// WR32); a transom or casing pair with exactly one half set (WR33,
73/// WR34); and any measure that violates its schema measure type.
74///
75/// WR35 requires the set to define an `IfcDoorType`. That is a property
76/// of the attachment, not of this record, so it is enforced where the
77/// set is attached rather than invented here.
78pub fn add_door_lining_properties(
79    tx: &mut Transaction,
80    global_id: &str,
81    draft: DoorLiningDraft<'_>,
82) -> PropertyResult<EntityId> {
83    stage_ifc4(tx, door_lining(global_id, draft)?)
84}
85
86/// [`add_door_lining_properties`] in the model's declared release, with a
87/// caller-supplied `IfcOwnerHistory`, which IFC2X3 requires (#202).
88///
89/// # Errors
90///
91/// Those of [`add_door_lining_properties`], and: a model that binds no
92/// single known release; an `owner_history` that is not in the model or
93/// staged ([`MissingEntity`](crate::PropertyError::MissingEntity)) or is
94/// not an `IfcOwnerHistory`
95/// ([`AuthoringInvalid`](crate::PropertyError::AuthoringInvalid)); in
96/// IFC2X3 a `lining_to_panel_offset_x/y`
97/// ([`AuthoringNotInSchema`](crate::PropertyError::AuthoringNotInSchema))
98/// and a zero thickness, which IFC2X3 types as `IfcPositiveLengthMeasure`.
99/// Nothing is staged on an error.
100pub fn add_door_lining_properties_with_owner_history(
101    tx: &mut Transaction,
102    model: &Model,
103    global_id: &str,
104    draft: DoorLiningDraft<'_>,
105    owner_history: EntityId,
106) -> PropertyResult<EntityId> {
107    stage_owned(tx, model, door_lining(global_id, draft)?, owner_history)
108}
109
110fn door_lining<'a>(global_id: &'a str, draft: DoorLiningDraft<'a>) -> PropertyResult<Rooted<'a>> {
111    const ENTITY: &str = "IFCDOORLININGPROPERTIES";
112    if draft.lining_depth.is_some() && draft.lining_thickness.is_none() {
113        return Err(invalid(
114            ENTITY,
115            "LiningThickness",
116            "WR31: a lining depth needs its thickness",
117        ));
118    }
119    if draft.threshold_depth.is_some() && draft.threshold_thickness.is_none() {
120        return Err(invalid(
121            ENTITY,
122            "ThresholdThickness",
123            "WR32: a threshold depth needs its thickness",
124        ));
125    }
126    if draft.transom_offset.is_some() != draft.transom_thickness.is_some() {
127        return Err(invalid(
128            ENTITY,
129            "TransomOffset",
130            "WR33: transom offset and thickness are all or nothing",
131        ));
132    }
133    if draft.casing_depth.is_some() != draft.casing_thickness.is_some() {
134        return Err(invalid(
135            ENTITY,
136            "CasingDepth",
137            "WR34: casing depth and thickness are all or nothing",
138        ));
139    }
140    require_guid(ENTITY, global_id)?;
141    let m = |attribute, value, kind| measure(ENTITY, attribute, value, kind);
142    let values = vec![
143        (
144            "LiningDepth",
145            m("LiningDepth", draft.lining_depth, Measure::Positive)?,
146        ),
147        (
148            "LiningThickness",
149            m(
150                "LiningThickness",
151                draft.lining_thickness,
152                Measure::NonNegative,
153            )?,
154        ),
155        (
156            "ThresholdDepth",
157            m("ThresholdDepth", draft.threshold_depth, Measure::Positive)?,
158        ),
159        (
160            "ThresholdThickness",
161            m(
162                "ThresholdThickness",
163                draft.threshold_thickness,
164                Measure::NonNegative,
165            )?,
166        ),
167        (
168            "TransomThickness",
169            m(
170                "TransomThickness",
171                draft.transom_thickness,
172                Measure::NonNegative,
173            )?,
174        ),
175        (
176            "TransomOffset",
177            m("TransomOffset", draft.transom_offset, Measure::Length)?,
178        ),
179        (
180            "LiningOffset",
181            m("LiningOffset", draft.lining_offset, Measure::Length)?,
182        ),
183        (
184            "ThresholdOffset",
185            m("ThresholdOffset", draft.threshold_offset, Measure::Length)?,
186        ),
187        (
188            "CasingThickness",
189            m("CasingThickness", draft.casing_thickness, Measure::Positive)?,
190        ),
191        (
192            "CasingDepth",
193            m("CasingDepth", draft.casing_depth, Measure::Positive)?,
194        ),
195        (
196            "ShapeAspectStyle",
197            draft.shape_aspect_style.map_or(Value::Null, Value::Ref),
198        ),
199        (
200            "LiningToPanelOffsetX",
201            m(
202                "LiningToPanelOffsetX",
203                draft.lining_to_panel_offset_x,
204                Measure::Length,
205            )?,
206        ),
207        (
208            "LiningToPanelOffsetY",
209            m(
210                "LiningToPanelOffsetY",
211                draft.lining_to_panel_offset_y,
212                Measure::Length,
213            )?,
214        ),
215    ];
216    Ok(Rooted {
217        entity: ENTITY,
218        global_id,
219        name: draft.name,
220        description: draft.description,
221        values,
222    })
223}
224
225/// Attributes of an `IfcWindowLiningProperties`.
226#[derive(Debug, Clone, Copy, Default)]
227pub struct WindowLiningDraft<'a> {
228    /// `Name`.
229    pub name: Option<&'a str>,
230    /// `Description`.
231    pub description: Option<&'a str>,
232    /// `LiningDepth`, a positive length. Requires `lining_thickness` (WR31).
233    pub lining_depth: Option<f64>,
234    /// `LiningThickness`, a non-negative length.
235    pub lining_thickness: Option<f64>,
236    /// `TransomThickness`, a non-negative length.
237    pub transom_thickness: Option<f64>,
238    /// `MullionThickness`, a non-negative length.
239    pub mullion_thickness: Option<f64>,
240    /// `FirstTransomOffset`, a normalised ratio.
241    pub first_transom_offset: Option<f64>,
242    /// `SecondTransomOffset`, a normalised ratio. Needs the first (WR32).
243    pub second_transom_offset: Option<f64>,
244    /// `FirstMullionOffset`, a normalised ratio.
245    pub first_mullion_offset: Option<f64>,
246    /// `SecondMullionOffset`, a normalised ratio. Needs the first (WR33).
247    pub second_mullion_offset: Option<f64>,
248    /// `ShapeAspectStyle`.
249    pub shape_aspect_style: Option<EntityId>,
250    /// `LiningOffset`, a length.
251    pub lining_offset: Option<f64>,
252    /// `LiningToPanelOffsetX`, a length.
253    pub lining_to_panel_offset_x: Option<f64>,
254    /// `LiningToPanelOffsetY`, a length.
255    pub lining_to_panel_offset_y: Option<f64>,
256}
257
258/// Stage an `IfcWindowLiningProperties`.
259///
260/// Takes no model, so it writes the IFC4 layout with `OwnerHistory` `$`:
261/// valid IFC4 and IFC4X3, never valid IFC2X3. Use
262/// [`add_window_lining_properties_with_owner_history`] to write the
263/// model's declared release.
264///
265/// # Errors
266///
267/// Refuses a malformed GlobalId; a lining depth without its thickness
268/// (WR31); a second transom or mullion offset without the first (WR32,
269/// WR33); and any measure outside its schema type. The offsets are
270/// `IfcNormalisedRatioMeasure`, so they are bounded to `[0, 1]` rather
271/// than treated as free lengths.
272///
273/// Unlike the door rules, these are ordered rather than XOR: a first
274/// offset alone is legal, a second alone is not.
275pub fn add_window_lining_properties(
276    tx: &mut Transaction,
277    global_id: &str,
278    draft: WindowLiningDraft<'_>,
279) -> PropertyResult<EntityId> {
280    stage_ifc4(tx, window_lining(global_id, draft)?)
281}
282
283/// [`add_window_lining_properties`] in the model's declared release, with
284/// a caller-supplied `IfcOwnerHistory`, which IFC2X3 requires (#202).
285///
286/// # Errors
287///
288/// Those of [`add_window_lining_properties`] and the release and
289/// owner-history refusals of
290/// [`add_door_lining_properties_with_owner_history`]. IFC2X3 declares no
291/// `LiningOffset` and no `LiningToPanelOffsetX/Y` on a window lining.
292/// Nothing is staged on an error.
293pub fn add_window_lining_properties_with_owner_history(
294    tx: &mut Transaction,
295    model: &Model,
296    global_id: &str,
297    draft: WindowLiningDraft<'_>,
298    owner_history: EntityId,
299) -> PropertyResult<EntityId> {
300    stage_owned(tx, model, window_lining(global_id, draft)?, owner_history)
301}
302
303fn window_lining<'a>(
304    global_id: &'a str,
305    draft: WindowLiningDraft<'a>,
306) -> PropertyResult<Rooted<'a>> {
307    const ENTITY: &str = "IFCWINDOWLININGPROPERTIES";
308    if draft.lining_depth.is_some() && draft.lining_thickness.is_none() {
309        return Err(invalid(
310            ENTITY,
311            "LiningThickness",
312            "WR31: a lining depth needs its thickness",
313        ));
314    }
315    if draft.second_transom_offset.is_some() && draft.first_transom_offset.is_none() {
316        return Err(invalid(
317            ENTITY,
318            "SecondTransomOffset",
319            "WR32: a second transom offset needs the first",
320        ));
321    }
322    if draft.second_mullion_offset.is_some() && draft.first_mullion_offset.is_none() {
323        return Err(invalid(
324            ENTITY,
325            "SecondMullionOffset",
326            "WR33: a second mullion offset needs the first",
327        ));
328    }
329    require_guid(ENTITY, global_id)?;
330    let m = |attribute, value, kind| measure(ENTITY, attribute, value, kind);
331    let r = |attribute, value| ratio(ENTITY, attribute, value);
332    let values = vec![
333        (
334            "LiningDepth",
335            m("LiningDepth", draft.lining_depth, Measure::Positive)?,
336        ),
337        (
338            "LiningThickness",
339            m(
340                "LiningThickness",
341                draft.lining_thickness,
342                Measure::NonNegative,
343            )?,
344        ),
345        (
346            "TransomThickness",
347            m(
348                "TransomThickness",
349                draft.transom_thickness,
350                Measure::NonNegative,
351            )?,
352        ),
353        (
354            "MullionThickness",
355            m(
356                "MullionThickness",
357                draft.mullion_thickness,
358                Measure::NonNegative,
359            )?,
360        ),
361        (
362            "FirstTransomOffset",
363            r("FirstTransomOffset", draft.first_transom_offset)?,
364        ),
365        (
366            "SecondTransomOffset",
367            r("SecondTransomOffset", draft.second_transom_offset)?,
368        ),
369        (
370            "FirstMullionOffset",
371            r("FirstMullionOffset", draft.first_mullion_offset)?,
372        ),
373        (
374            "SecondMullionOffset",
375            r("SecondMullionOffset", draft.second_mullion_offset)?,
376        ),
377        (
378            "ShapeAspectStyle",
379            draft.shape_aspect_style.map_or(Value::Null, Value::Ref),
380        ),
381        (
382            "LiningOffset",
383            m("LiningOffset", draft.lining_offset, Measure::Length)?,
384        ),
385        (
386            "LiningToPanelOffsetX",
387            m(
388                "LiningToPanelOffsetX",
389                draft.lining_to_panel_offset_x,
390                Measure::Length,
391            )?,
392        ),
393        (
394            "LiningToPanelOffsetY",
395            m(
396                "LiningToPanelOffsetY",
397                draft.lining_to_panel_offset_y,
398                Measure::Length,
399            )?,
400        ),
401    ];
402    Ok(Rooted {
403        entity: ENTITY,
404        global_id,
405        name: draft.name,
406        description: draft.description,
407        values,
408    })
409}