Skip to main content

ifc_structural/
connection.rs

1//! Idealized point, curve and surface connections.
2
3use ifc_model::EntityId;
4
5use crate::error::{StructuralError, StructuralResult};
6use crate::view::Record;
7
8/// Which `IfcStructuralConnection` application geometry a connection carries.
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub enum ConnectionKind {
11    /// Point support: `IfcStructuralPointConnection`.
12    Point,
13    /// Line support: `IfcStructuralCurveConnection`.
14    Curve,
15    /// Surface support: `IfcStructuralSurfaceConnection`.
16    Surface,
17}
18
19/// Borrowed projection of an `IfcStructuralConnection` (point, curve, or surface).
20#[derive(Debug, Clone, Copy)]
21pub struct StructuralConnection<'m, 's> {
22    record: Record<'m, 's>,
23    kind: ConnectionKind,
24}
25
26impl<'m, 's> StructuralConnection<'m, 's> {
27    /// Classify `record`'s [`ConnectionKind`] from its declared IFC type.
28    ///
29    /// Fails with [`StructuralError::WrongType`] if the type is none of
30    /// `IfcStructuralPointConnection`, `IfcStructuralCurveConnection`, or
31    /// `IfcStructuralSurfaceConnection`.
32    pub(crate) fn from_record(record: Record<'m, 's>) -> StructuralResult<Self> {
33        let kind = if record
34            .schema
35            .is_a(&record.entity.type_name, "IfcStructuralPointConnection")
36        {
37            ConnectionKind::Point
38        } else if record
39            .schema
40            .is_a(&record.entity.type_name, "IfcStructuralCurveConnection")
41        {
42            ConnectionKind::Curve
43        } else if record
44            .schema
45            .is_a(&record.entity.type_name, "IfcStructuralSurfaceConnection")
46        {
47            ConnectionKind::Surface
48        } else {
49            return Err(StructuralError::WrongType {
50                id: record.id,
51                expected: "point, curve or surface structural connection",
52                actual: record.entity.type_name.to_string(),
53            });
54        };
55        Ok(Self { record, kind })
56    }
57
58    #[must_use]
59    /// The `IfcStructuralConnection` entity id.
60    pub fn id(&self) -> EntityId {
61        self.record.id
62    }
63
64    #[must_use]
65    /// Which application geometry this connection carries.
66    pub fn kind(&self) -> ConnectionKind {
67        self.kind
68    }
69
70    /// `Name`, inherited from `IfcRoot`. Legally absent.
71    pub fn name(&self) -> StructuralResult<Option<&'m str>> {
72        self.record.optional_text("Name")
73    }
74
75    /// `AppliedCondition`, the `IfcBoundaryCondition` fixing this connection's degrees of freedom. Legally absent.
76    pub fn applied_condition(&self) -> StructuralResult<Option<EntityId>> {
77        self.record
78            .optional_ref("AppliedCondition", "IfcBoundaryCondition")
79    }
80
81    /// The local axis orientation: `AxisDirection`/`Axis` for a curve connection, or
82    /// `ConditionCoordinateSystem` for a point connection. Surface connections have
83    /// no comparable attribute here and always return `Ok(None)`.
84    pub fn axis(&self) -> StructuralResult<Option<EntityId>> {
85        if self.kind == ConnectionKind::Curve {
86            for attribute in ["AxisDirection", "Axis"] {
87                if self.record.has_attribute(attribute) {
88                    return self
89                        .record
90                        .required_ref(attribute, "IfcDirection")
91                        .map(Some);
92                }
93            }
94        }
95        if self.kind == ConnectionKind::Point
96            && self.record.has_attribute("ConditionCoordinateSystem")
97        {
98            return self
99                .record
100                .optional_ref("ConditionCoordinateSystem", "IfcAxis2Placement3D");
101        }
102        Ok(None)
103    }
104}