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