1use ifc_model::EntityId;
4use ifc_schema::SchemaVersion;
5
6use crate::error::{StructuralError, StructuralResult};
7use crate::view::Record;
8
9mod curve;
10mod surface;
11mod varying;
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum MemberKind {
16 Curve,
18 CurveVarying,
20 Surface,
22 SurfaceVarying,
24}
25
26#[derive(Debug, Clone, Copy)]
28pub struct Member<'m, 's> {
29 record: Record<'m, 's>,
30 kind: MemberKind,
31}
32
33impl<'m, 's> Member<'m, 's> {
34 pub(crate) fn from_record(record: Record<'m, 's>) -> StructuralResult<Self> {
39 let kind = if record
40 .schema
41 .is_a(&record.entity.type_name, "IfcStructuralCurveMemberVarying")
42 {
43 MemberKind::CurveVarying
44 } else if record.schema.is_a(
45 &record.entity.type_name,
46 "IfcStructuralSurfaceMemberVarying",
47 ) {
48 MemberKind::SurfaceVarying
49 } else if record
50 .schema
51 .is_a(&record.entity.type_name, "IfcStructuralCurveMember")
52 {
53 MemberKind::Curve
54 } else if record
55 .schema
56 .is_a(&record.entity.type_name, "IfcStructuralSurfaceMember")
57 {
58 MemberKind::Surface
59 } else {
60 return Err(StructuralError::WrongType {
61 id: record.id,
62 expected: "IfcStructuralCurveMember or IfcStructuralSurfaceMember",
63 actual: record.entity.type_name.to_string(),
64 });
65 };
66 Ok(Self { record, kind })
67 }
68
69 #[must_use]
70 pub fn id(&self) -> EntityId {
72 self.record.id
73 }
74
75 #[must_use]
76 pub fn kind(&self) -> MemberKind {
78 self.kind
79 }
80
81 pub fn name(&self) -> StructuralResult<Option<&'m str>> {
83 self.validate_semantics()?;
84 self.record.optional_text("Name")
85 }
86
87 pub fn predefined_type(&self) -> StructuralResult<&'m str> {
93 self.validate_semantics()?;
94 self.record.required_enum("PredefinedType")
95 }
96
97 pub fn axis(&self) -> StructuralResult<Option<EntityId>> {
100 if !matches!(self.kind, MemberKind::Curve | MemberKind::CurveVarying)
101 || !self.record.has_attribute("Axis")
102 {
103 return Ok(None);
104 }
105 self.record.required_ref("Axis", "IfcDirection").map(Some)
106 }
107
108 pub fn thickness(&self) -> StructuralResult<Option<f64>> {
113 if !matches!(self.kind, MemberKind::Surface | MemberKind::SurfaceVarying)
114 || !self.record.has_attribute("Thickness")
115 {
116 return Ok(None);
117 }
118 let thickness = self.record.optional_number("Thickness")?;
119 if thickness.is_some_and(|value| !value.is_finite() || value <= 0.0) {
120 return Err(StructuralError::SemanticViolation {
121 entity: Some(self.record.id),
122 rule: "structural surface member Thickness must be positive and finite",
123 });
124 }
125 Ok(thickness)
126 }
127
128 fn validate_semantics(&self) -> StructuralResult<()> {
129 if self.record.schema.version() == Some(SchemaVersion::Ifc2x3) {
130 return Ok(());
131 }
132 let user_defined = self
133 .record
134 .required_enum("PredefinedType")?
135 .eq_ignore_ascii_case("USERDEFINED");
136 self.record.require_object_type_if(
137 user_defined,
138 "USERDEFINED structural member requires ObjectType",
139 )
140 }
141}