Skip to main content

ifc_geometry/authoring/
std_profile.rs

1//! Standard parameterized sections: the catalogue profiles.
2//!
3//! These are the sections a structural catalogue names -- an IPE beam,
4//! an angle, a channel, a square hollow tube. Each is a fixed list of
5//! dimensions, so authoring one is writing numbers into slots; no
6//! curve construction and no evaluator is involved.
7//!
8//! Slots come from [`crate::slots::section_slot`], the same
9//! constants the profile reader uses. A layout correction lands once and
10//! serves both directions.
11//!
12//! # Refusals are schema rules, not taste
13//!
14//! IFC declares the load-bearing dimensions as
15//! `IfcPositiveLengthMeasure`, so zero is invalid rather than merely
16//! degenerate, and each entity carries WHERE rules relating those
17//! dimensions -- a web thinner than the flange it joins, a girth under
18//! half the depth. This module enforces them at authoring time because
19//! `ifc-validate` does not implement profile WHERE rules: a section
20//! violating them is a record that parses and describes an impossible
21//! shape.
22//!
23//! Fillet and edge radii are `IfcNonNegativeLengthMeasure`, where zero
24//! is legal and means a sharp corner. They are checked for
25//! non-negativity, never for positivity.
26
27use ifc_model::{EntityId, Value};
28
29use crate::error::GeometryError;
30use crate::slots::profile_slot as base;
31
32use super::profile::ProfileType;
33use super::{invalid, require_finite};
34/// The two attributes every parameterized profile carries.
35///
36/// Bundled so a section's own dimensions are not separated from each
37/// other by two unrelated arguments at the call site.
38#[derive(Debug, Clone, Copy, Default)]
39pub struct ProfileHeader<'a> {
40    /// `ProfileName`.
41    pub name: Option<&'a str>,
42    /// `Position : IfcAxis2Placement2D`.
43    pub position: Option<EntityId>,
44}
45
46/// The four required dimensions of an I-section.
47#[derive(Debug, Clone, Copy)]
48pub struct IShapeDims {
49    /// `OverallWidth`.
50    pub width: f64,
51    /// `OverallDepth`.
52    pub depth: f64,
53    /// `WebThickness`.
54    pub web_thickness: f64,
55    /// `FlangeThickness`.
56    pub flange_thickness: f64,
57}
58
59/// The required dimensions of a T, U or Z section.
60///
61/// The three share a shape: a depth, a flange width, and the two
62/// thicknesses. They differ only in which WHERE rules bound them.
63#[derive(Debug, Clone, Copy)]
64pub struct FlangedDims {
65    /// `Depth`.
66    pub depth: f64,
67    /// `FlangeWidth`.
68    pub flange_width: f64,
69    /// `WebThickness`.
70    pub web_thickness: f64,
71    /// `FlangeThickness`.
72    pub flange_thickness: f64,
73}
74
75/// The required dimensions of a C-section.
76#[derive(Debug, Clone, Copy)]
77pub struct CShapeDims {
78    /// `Depth`.
79    pub depth: f64,
80    /// `Width`.
81    pub width: f64,
82    /// `WallThickness`.
83    pub wall_thickness: f64,
84    /// `Girth`.
85    pub girth: f64,
86}
87
88/// The required dimensions of an asymmetric I-section.
89#[derive(Debug, Clone, Copy)]
90pub struct AsymmetricIDims {
91    /// `BottomFlangeWidth`.
92    pub bottom_flange_width: f64,
93    /// `OverallDepth`.
94    pub overall_depth: f64,
95    /// `WebThickness`.
96    pub web_thickness: f64,
97    /// `BottomFlangeThickness`.
98    pub bottom_flange_thickness: f64,
99    /// `TopFlangeWidth`.
100    pub top_flange_width: f64,
101}
102
103/// The required dimensions of a rectangular hollow section.
104#[derive(Debug, Clone, Copy)]
105pub struct RectangleHollowDims {
106    /// `XDim`.
107    pub x_dim: f64,
108    /// `YDim`.
109    pub y_dim: f64,
110    /// `WallThickness`.
111    pub wall_thickness: f64,
112}
113
114/// The two optional fillet radii of a rectangular hollow section.
115#[derive(Debug, Clone, Copy, Default)]
116pub struct RectangleHollowFillets {
117    /// `InnerFilletRadius`.
118    pub inner: Option<f64>,
119    /// `OuterFilletRadius`.
120    pub outer: Option<f64>,
121}
122
123/// The required dimensions of a trapezium.
124#[derive(Debug, Clone, Copy)]
125pub struct TrapeziumDims {
126    /// `BottomXDim`.
127    pub bottom_x_dim: f64,
128    /// `TopXDim`.
129    pub top_x_dim: f64,
130    /// `YDim`.
131    pub y_dim: f64,
132    /// `TopXOffset`, which may be negative.
133    pub top_x_offset: f64,
134}
135
136/// Fill the three `IfcParameterizedProfileDef` slots every section shares.
137///
138/// `ProfileType` is always `.AREA.`: each entity in this module bounds a
139/// closed region, so `.CURVE.` would contradict the declaration.
140pub(super) fn parameterized(
141    type_name: &'static str,
142    arity: usize,
143    header: ProfileHeader<'_>,
144) -> Vec<Value> {
145    let mut attrs = vec![Value::Null; arity];
146    debug_assert!(!type_name.is_empty());
147    attrs[base::PROFILE_TYPE] = Value::Enum(ProfileType::Area.token().into());
148    attrs[base::PROFILE_NAME] = header.name.map_or(Value::Null, |n| Value::Text(n.into()));
149    attrs[base::POSITION] = header.position.map_or(Value::Null, Value::Ref);
150    attrs
151}
152
153/// Require a strictly positive, finite `IfcPositiveLengthMeasure`.
154pub(super) fn positive(
155    type_name: &'static str,
156    attribute: &'static str,
157    value: f64,
158) -> Result<(), GeometryError> {
159    require_finite(type_name, attribute, &[value])?;
160    if value <= 0.0 {
161        return Err(invalid(
162            type_name,
163            attribute,
164            format!("expected a positive length, got {value}"),
165        ));
166    }
167    Ok(())
168}
169
170/// Require a finite, non-negative `IfcNonNegativeLengthMeasure`.
171///
172/// Zero is accepted: a fillet radius of zero is a sharp corner, which is
173/// a real section, not a degenerate one.
174pub(super) fn non_negative(
175    type_name: &'static str,
176    attribute: &'static str,
177    value: Option<f64>,
178) -> Result<(), GeometryError> {
179    let Some(value) = value else { return Ok(()) };
180    require_finite(type_name, attribute, &[value])?;
181    if value < 0.0 {
182        return Err(invalid(
183            type_name,
184            attribute,
185            format!("expected a non-negative length, got {value}"),
186        ));
187    }
188    Ok(())
189}
190
191/// Refuse a WHERE-rule violation.
192pub(super) fn rule(
193    type_name: &'static str,
194    attribute: &'static str,
195    detail: String,
196) -> GeometryError {
197    invalid(type_name, attribute, detail)
198}
199
200/// Write an optional length into its slot when present.
201pub(super) fn put_opt(attrs: &mut [Value], index: usize, value: Option<f64>) {
202    if let Some(value) = value {
203        attrs[index] = Value::Real(value);
204    }
205}
206
207mod closed_section;
208mod flanged_section;
209
210pub use closed_section::{
211    circle_hollow_profile, ellipse_profile, rectangle_hollow_profile, trapezium_profile,
212};
213pub use flanged_section::{
214    asymmetric_i_shape, c_shape, i_shape, l_shape, t_shape, u_shape, z_shape, AsymmetricIExtras,
215    IShapeExtras, LShapeExtras, TShapeExtras, UShapeExtras, ZShapeExtras,
216};