Skip to main content

ifc_material/layer/
set.rs

1//! Ordered `IfcMaterialLayerSet` composition and total thickness.
2//!
3//! IFC2X3 declares `(MaterialLayers, LayerSetName)`; IFC4 and IFC4X3 append
4//! `Description`.
5
6use ifc_model::EntityId;
7
8use crate::layer::definition::layer_thickness;
9use crate::view::{borrowed_entity, optional_text, required_refs, MaterialView};
10use crate::{MaterialError, MaterialResult};
11
12borrowed_entity!(MaterialLayerSet, "IFCMATERIALLAYERSET");
13
14impl<'m> MaterialLayerSet<'m> {
15    /// `IfcMaterialLayerSet.MaterialLayers`, in set order. Required and
16    /// must be non-empty.
17    pub fn layer_ids(self) -> MaterialResult<Vec<EntityId>> {
18        required_refs(
19            "IFCMATERIALLAYERSET",
20            self.id(),
21            self.entity(),
22            self.slot("MaterialLayers")?,
23            "MaterialLayers",
24            1,
25        )
26    }
27
28    /// `IfcMaterialLayerSet.LayerSetName`, if given.
29    pub fn name(self) -> MaterialResult<Option<&'m str>> {
30        optional_text(
31            "IFCMATERIALLAYERSET",
32            self.id(),
33            self.entity(),
34            self.slot("LayerSetName")?,
35            "LayerSetName",
36        )
37    }
38
39    /// `IfcMaterialLayerSet.Description`, if given. `NotInSchema` for
40    /// IFC2X3.
41    pub fn description(self) -> MaterialResult<Option<&'m str>> {
42        optional_text(
43            "IFCMATERIALLAYERSET",
44            self.id(),
45            self.entity(),
46            self.slot("Description")?,
47            "Description",
48        )
49    }
50}
51
52impl<'m> MaterialView<'m> {
53    /// Iterates every `IfcMaterialLayerSet` instance in the model.
54    pub fn layer_sets(self) -> impl Iterator<Item = MaterialLayerSet<'m>> + 'm {
55        let release = self.release();
56        self.model()
57            .of_type("IFCMATERIALLAYERSET")
58            .map(move |(id, entity)| MaterialLayerSet::from_known(id, entity, release))
59    }
60
61    /// Evaluate the normative `IfcMlsTotalThickness` function.
62    ///
63    /// Each member is read against the set's release: an
64    /// `IfcMaterialLayerWithOffsets` member of an IFC2X3 set is
65    /// `EntityNotInSchema`, and a zero IFC2X3 thickness is invalid.
66    pub fn total_thickness(self, set: MaterialLayerSet<'m>) -> MaterialResult<f64> {
67        let mut total = 0.0;
68        for layer_id in set.layer_ids()? {
69            let layer = self.entity(set.id(), layer_id)?;
70            let entity_type = if layer.is_type("IFCMATERIALLAYER") {
71                "IFCMATERIALLAYER"
72            } else if layer.is_type("IFCMATERIALLAYERWITHOFFSETS") {
73                "IFCMATERIALLAYERWITHOFFSETS"
74            } else {
75                return Err(MaterialError::ReferenceType {
76                    source_id: set.id(),
77                    target: layer_id,
78                    expected: "IFCMATERIALLAYER",
79                    actual: layer.type_name.to_string(),
80                });
81            };
82            total += layer_thickness(entity_type, layer_id, layer, set.release())?;
83            if !total.is_finite() {
84                return Err(MaterialError::InvalidValue {
85                    entity: "IFCMATERIALLAYERSET",
86                    id: set.id(),
87                    attribute: "TotalThickness",
88                    value: "finite layer thicknesses overflowed the aggregate".to_owned(),
89                });
90            }
91        }
92        Ok(total)
93    }
94}