Skip to main content

ifc_material/profile/
usage.rs

1//! `IfcMaterialProfileSetUsage` and tapering authored fields.
2
3use ifc_model::EntityId;
4
5use crate::view::{borrowed_entity, optional_integer, optional_number, required_ref, MaterialView};
6use crate::{CardinalPointReference, MaterialError, MaterialResult};
7
8// `IfcMaterialProfileSetUsageTapering` is `SUBTYPE OF
9// (IfcMaterialProfileSetUsage)` in IFC4 and IFC4X3 and keeps its three
10// attributes at slots 0..2, so the supertype projection accepts it (#136).
11borrowed_entity!(
12    MaterialProfileSetUsage,
13    "IFCMATERIALPROFILESETUSAGE",
14    ["IFCMATERIALPROFILESETUSAGETAPERING"]
15);
16borrowed_entity!(
17    MaterialProfileSetUsageTapering,
18    "IFCMATERIALPROFILESETUSAGETAPERING"
19);
20
21fn cardinal(
22    entity_type: &'static str,
23    id: EntityId,
24    entity: &ifc_model::Entity,
25    slot: usize,
26    attribute: &'static str,
27) -> MaterialResult<Option<CardinalPointReference>> {
28    let Some(value) = optional_integer(entity_type, id, entity, slot, attribute)? else {
29        return Ok(None);
30    };
31    CardinalPointReference::new(value)
32        .map(Some)
33        .ok_or_else(|| MaterialError::InvalidValue {
34            entity: entity_type,
35            id,
36            attribute,
37            value: value.to_string(),
38        })
39}
40
41fn positive_extent(
42    entity_type: &'static str,
43    id: EntityId,
44    entity: &ifc_model::Entity,
45    slot: usize,
46) -> MaterialResult<Option<f64>> {
47    let value = optional_number(entity_type, id, entity, slot, "ReferenceExtent")?;
48    if value.is_some_and(|value| value <= 0.0) {
49        return Err(MaterialError::InvalidValue {
50            entity: entity_type,
51            id,
52            attribute: "ReferenceExtent",
53            value: "expected a positive length".to_owned(),
54        });
55    }
56    Ok(value)
57}
58
59macro_rules! usage_accessors {
60    ($type:ident, $ifc_name:literal) => {
61        impl $type<'_> {
62            /// `ForProfileSet`. Required.
63            pub fn profile_set_id(self) -> MaterialResult<EntityId> {
64                required_ref(
65                    $ifc_name,
66                    self.id(),
67                    self.entity(),
68                    self.slot("ForProfileSet")?,
69                    "ForProfileSet",
70                )
71            }
72
73            /// `CardinalPoint`, if given. Must decode to a positive
74            /// `IfcCardinalPointReference`.
75            pub fn cardinal_point(self) -> MaterialResult<Option<CardinalPointReference>> {
76                cardinal(
77                    $ifc_name,
78                    self.id(),
79                    self.entity(),
80                    self.slot("CardinalPoint")?,
81                    "CardinalPoint",
82                )
83            }
84
85            /// `ReferenceExtent`, if given. Must be strictly positive when
86            /// present.
87            pub fn reference_extent(self) -> MaterialResult<Option<f64>> {
88                positive_extent(
89                    $ifc_name,
90                    self.id(),
91                    self.entity(),
92                    self.slot("ReferenceExtent")?,
93                )
94            }
95        }
96    };
97}
98usage_accessors!(MaterialProfileSetUsage, "IFCMATERIALPROFILESETUSAGE");
99usage_accessors!(
100    MaterialProfileSetUsageTapering,
101    "IFCMATERIALPROFILESETUSAGETAPERING"
102);
103
104impl<'m> MaterialProfileSetUsage<'m> {
105    /// The tapering projection of this usage when the record is an
106    /// `IfcMaterialProfileSetUsageTapering`, giving its `ForProfileEndSet`
107    /// and `CardinalEndPoint`; `None` for a plain usage.
108    pub fn tapering(self) -> Option<MaterialProfileSetUsageTapering<'m>> {
109        self.entity()
110            .is_type("IFCMATERIALPROFILESETUSAGETAPERING")
111            .then(|| {
112                MaterialProfileSetUsageTapering::from_known(
113                    self.id(),
114                    self.entity(),
115                    self.release(),
116                )
117            })
118    }
119}
120
121impl MaterialProfileSetUsageTapering<'_> {
122    /// `IfcMaterialProfileSetUsageTapering.ForProfileEndSet`. Required.
123    pub fn end_profile_set_id(self) -> MaterialResult<EntityId> {
124        required_ref(
125            "IFCMATERIALPROFILESETUSAGETAPERING",
126            self.id(),
127            self.entity(),
128            self.slot("ForProfileEndSet")?,
129            "ForProfileEndSet",
130        )
131    }
132
133    /// `IfcMaterialProfileSetUsageTapering.CardinalEndPoint`, if given.
134    /// Must decode to a positive `IfcCardinalPointReference`.
135    pub fn cardinal_end_point(self) -> MaterialResult<Option<CardinalPointReference>> {
136        cardinal(
137            "IFCMATERIALPROFILESETUSAGETAPERING",
138            self.id(),
139            self.entity(),
140            self.slot("CardinalEndPoint")?,
141            "CardinalEndPoint",
142        )
143    }
144}
145
146impl<'m> MaterialView<'m> {
147    /// Iterates every `IfcMaterialProfileSetUsage` in the model, including
148    /// its subtype `IfcMaterialProfileSetUsageTapering` (#136), in entity-id
149    /// order.
150    ///
151    /// A tapering usage is a profile-set usage, so a caller asking for usages
152    /// does not miss it; [`MaterialProfileSetUsage::tapering`] reaches its end
153    /// set and end cardinal point. [`Self::tapering_profile_set_usages`]
154    /// yields the subtype alone.
155    pub fn profile_set_usages(self) -> impl Iterator<Item = MaterialProfileSetUsage<'m>> + 'm {
156        let release = self.release();
157        let mut usages: Vec<_> = self
158            .model()
159            .of_type("IFCMATERIALPROFILESETUSAGE")
160            .chain(self.model().of_type("IFCMATERIALPROFILESETUSAGETAPERING"))
161            .collect();
162        usages.sort_unstable_by_key(|(id, _)| *id);
163        usages
164            .into_iter()
165            .map(move |(id, entity)| MaterialProfileSetUsage::from_known(id, entity, release))
166    }
167
168    /// Iterates every `IfcMaterialProfileSetUsageTapering` instance in the
169    /// model.
170    pub fn tapering_profile_set_usages(
171        self,
172    ) -> impl Iterator<Item = MaterialProfileSetUsageTapering<'m>> + 'm {
173        let release = self.release();
174        self.model()
175            .of_type("IFCMATERIALPROFILESETUSAGETAPERING")
176            .map(move |(id, entity)| {
177                MaterialProfileSetUsageTapering::from_known(id, entity, release)
178            })
179    }
180}