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