Skip to main content

ifc_geometry/authoring/
profile.rs

1//! Profiles: the 2D sections that swept solids extrude.
2//!
3//! Slots come from [`crate::slots::profile_slot`], the same definition the
4//! lowerer reads, so a layout correction serves both directions.
5
6use ifc_model::{Entity, EntityId, Transaction, Value};
7
8use crate::curve::polyline::polyline_slot;
9use crate::error::GeometryError;
10use crate::slots::profile_slot as slot;
11
12use super::{invalid, refs, require_finite};
13
14/// `IfcProfileTypeEnum`: whether the profile bounds an area or is a bare curve.
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub enum ProfileType {
17    /// `.AREA.` -- a closed section that can be swept into a solid.
18    Area,
19    /// `.CURVE.` -- an open section; sweeping it yields a surface, not a solid.
20    Curve,
21}
22
23impl ProfileType {
24    /// The EXPRESS token.
25    pub(super) fn token(self) -> &'static str {
26        match self {
27            Self::Area => "AREA",
28            Self::Curve => "CURVE",
29        }
30    }
31}
32
33/// Stage an `IfcPolyline`.
34///
35/// # Errors
36///
37/// Refuses fewer than two points: a polyline through one point has no
38/// length and no direction, and every consumer would have to special-case it.
39pub fn polyline(tx: &mut Transaction, points: &[EntityId]) -> Result<EntityId, GeometryError> {
40    if points.len() < 2 {
41        return Err(invalid(
42            "IFCPOLYLINE",
43            "Points",
44            format!("expected at least 2 points, got {}", points.len()),
45        ));
46    }
47    let mut attrs = vec![Value::Null];
48    attrs[polyline_slot::POINTS] = refs(points);
49    Ok(tx.create(Entity::new("IFCPOLYLINE", attrs)))
50}
51
52/// Stage an `IfcRectangleProfileDef`.
53///
54/// # Errors
55///
56/// Refuses a non-positive or non-finite dimension. IFC declares these as
57/// `IfcPositiveLengthMeasure`, so zero is invalid, not merely degenerate.
58pub fn rectangle_profile(
59    tx: &mut Transaction,
60    name: Option<&str>,
61    position: Option<EntityId>,
62    x_dim: f64,
63    y_dim: f64,
64) -> Result<EntityId, GeometryError> {
65    positive("IFCRECTANGLEPROFILEDEF", "XDim", x_dim)?;
66    positive("IFCRECTANGLEPROFILEDEF", "YDim", y_dim)?;
67    let mut attrs = vec![Value::Null; 5];
68    attrs[slot::PROFILE_TYPE] = Value::Enum(ProfileType::Area.token().into());
69    attrs[slot::PROFILE_NAME] = name.map_or(Value::Null, |n| Value::Text(n.into()));
70    attrs[slot::POSITION] = position.map_or(Value::Null, Value::Ref);
71    attrs[slot::X_DIM] = Value::Real(x_dim);
72    attrs[slot::Y_DIM] = Value::Real(y_dim);
73    Ok(tx.create(Entity::new("IFCRECTANGLEPROFILEDEF", attrs)))
74}
75
76/// Stage an `IfcCircleProfileDef`.
77///
78/// # Errors
79///
80/// Refuses a non-positive or non-finite radius.
81pub fn circle_profile(
82    tx: &mut Transaction,
83    name: Option<&str>,
84    position: Option<EntityId>,
85    radius: f64,
86) -> Result<EntityId, GeometryError> {
87    positive("IFCCIRCLEPROFILEDEF", "Radius", radius)?;
88    let mut attrs = vec![Value::Null; 4];
89    attrs[slot::PROFILE_TYPE] = Value::Enum(ProfileType::Area.token().into());
90    attrs[slot::PROFILE_NAME] = name.map_or(Value::Null, |n| Value::Text(n.into()));
91    attrs[slot::POSITION] = position.map_or(Value::Null, Value::Ref);
92    attrs[slot::RADIUS] = Value::Real(radius);
93    Ok(tx.create(Entity::new("IFCCIRCLEPROFILEDEF", attrs)))
94}
95
96/// Stage an `IfcArbitraryClosedProfileDef` from an outer curve.
97///
98/// The curve must already be closed. Closing it here would fabricate a
99/// segment the caller never described -- the same reason the lowerer refuses
100/// to close an open curve when reading.
101pub fn arbitrary_closed_profile(
102    tx: &mut Transaction,
103    name: Option<&str>,
104    outer_curve: EntityId,
105) -> EntityId {
106    let mut attrs = vec![Value::Null; 3];
107    attrs[slot::PROFILE_TYPE] = Value::Enum(ProfileType::Area.token().into());
108    attrs[slot::PROFILE_NAME] = name.map_or(Value::Null, |n| Value::Text(n.into()));
109    attrs[slot::OUTER_CURVE] = Value::Ref(outer_curve);
110    tx.create(Entity::new("IFCARBITRARYCLOSEDPROFILEDEF", attrs))
111}
112
113/// Refuse a measure IFC declares as `IfcPositiveLengthMeasure`.
114fn positive(
115    type_name: &'static str,
116    attribute: &'static str,
117    value: f64,
118) -> Result<(), GeometryError> {
119    require_finite(type_name, attribute, &[value])?;
120    if value <= 0.0 {
121        return Err(invalid(
122            type_name,
123            attribute,
124            format!("expected a positive length, got {value}"),
125        ));
126    }
127    Ok(())
128}