1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
//! Idealized curve and surface structural members.
use ifc_model::EntityId;
use ifc_schema::SchemaVersion;
use crate::error::{StructuralError, StructuralResult};
use crate::view::Record;
mod curve;
mod surface;
mod varying;
/// Which `IfcStructuralMember` subtype (and mutable-cross-section variant) a member carries.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MemberKind {
/// `IfcStructuralCurveMember` with constant axis properties.
Curve,
/// `IfcStructuralCurveMemberVarying`: axis properties vary along the curve.
CurveVarying,
/// `IfcStructuralSurfaceMember` with constant thickness.
Surface,
/// `IfcStructuralSurfaceMemberVarying`: thickness varies over the surface.
SurfaceVarying,
}
/// Borrowed projection of an `IfcStructuralMember` (curve or surface, constant or varying).
#[derive(Debug, Clone, Copy)]
pub struct Member<'m, 's> {
record: Record<'m, 's>,
kind: MemberKind,
}
impl<'m, 's> Member<'m, 's> {
/// Classify `record`'s [`MemberKind`] from its declared IFC type.
///
/// Fails with [`StructuralError::WrongType`] if the type is not a subtype
/// of `IfcStructuralCurveMember` or `IfcStructuralSurfaceMember`.
pub(crate) fn from_record(record: Record<'m, 's>) -> StructuralResult<Self> {
let kind = if record
.schema
.is_a(&record.entity.type_name, "IfcStructuralCurveMemberVarying")
{
MemberKind::CurveVarying
} else if record.schema.is_a(
&record.entity.type_name,
"IfcStructuralSurfaceMemberVarying",
) {
MemberKind::SurfaceVarying
} else if record
.schema
.is_a(&record.entity.type_name, "IfcStructuralCurveMember")
{
MemberKind::Curve
} else if record
.schema
.is_a(&record.entity.type_name, "IfcStructuralSurfaceMember")
{
MemberKind::Surface
} else {
return Err(StructuralError::WrongType {
id: record.id,
expected: "IfcStructuralCurveMember or IfcStructuralSurfaceMember",
actual: record.entity.type_name.to_string(),
});
};
Ok(Self { record, kind })
}
#[must_use]
/// The `IfcStructuralMember` entity id.
pub fn id(&self) -> EntityId {
self.record.id
}
#[must_use]
/// Which curve/surface, constant/varying subtype this member carries.
pub fn kind(&self) -> MemberKind {
self.kind
}
/// `Name`, inherited from `IfcRoot`. Legally absent.
pub fn name(&self) -> StructuralResult<Option<&'m str>> {
self.validate_semantics()?;
self.record.optional_text("Name")
}
/// `PredefinedType`, always mandatory on `IfcStructuralMember`.
///
/// Fails with [`StructuralError::SemanticViolation`] if the value is
/// `USERDEFINED` but `ObjectType` is unset (IFC4/IFC4X3 only; IFC2X3
/// declares no `PredefinedType` semantic constraint here).
pub fn predefined_type(&self) -> StructuralResult<&'m str> {
self.validate_semantics()?;
self.record.required_enum("PredefinedType")
}
/// `Axis`, the local member axis direction. Only meaningful for curve
/// members; returns `None` for surface members or when the attribute is unset.
pub fn axis(&self) -> StructuralResult<Option<EntityId>> {
if !matches!(self.kind, MemberKind::Curve | MemberKind::CurveVarying)
|| !self.record.has_attribute("Axis")
{
return Ok(None);
}
self.record.required_ref("Axis", "IfcDirection").map(Some)
}
/// `Thickness`, the constant surface thickness. Only meaningful for
/// surface members; returns `None` for curve members or when the
/// attribute is unset. Fails with [`StructuralError::SemanticViolation`]
/// if present but not a positive finite value.
pub fn thickness(&self) -> StructuralResult<Option<f64>> {
if !matches!(self.kind, MemberKind::Surface | MemberKind::SurfaceVarying)
|| !self.record.has_attribute("Thickness")
{
return Ok(None);
}
let thickness = self.record.optional_number("Thickness")?;
if thickness.is_some_and(|value| !value.is_finite() || value <= 0.0) {
return Err(StructuralError::SemanticViolation {
entity: Some(self.record.id),
rule: "structural surface member Thickness must be positive and finite",
});
}
Ok(thickness)
}
fn validate_semantics(&self) -> StructuralResult<()> {
if self.record.schema.version() == Some(SchemaVersion::Ifc2x3) {
return Ok(());
}
let user_defined = self
.record
.required_enum("PredefinedType")?
.eq_ignore_ascii_case("USERDEFINED");
self.record.require_object_type_if(
user_defined,
"USERDEFINED structural member requires ObjectType",
)
}
}