Skip to main content

ifc_structural/
query.rs

1//! Bounded structural relationship queries.
2
3use std::collections::HashSet;
4
5use ifc_model::EntityId;
6
7use crate::error::StructuralResult;
8use crate::view::{Record, StructuralView};
9
10/// A resolved `IfcRelConnectsStructuralMember` between one member and its connection.
11#[derive(Debug, Clone, PartialEq)]
12#[non_exhaustive]
13pub struct MemberConnection {
14    /// The `IfcRelConnectsStructuralMember` relation entity itself.
15    pub relation: EntityId,
16    /// `RelatingStructuralMember`.
17    pub member: EntityId,
18    /// `RelatedStructuralConnection`.
19    pub connection: EntityId,
20    /// `AppliedCondition`, an `IfcBoundaryCondition` overriding the connection's own condition. Legally absent.
21    pub applied_condition: Option<EntityId>,
22    /// `AdditionalConditions`, an `IfcStructuralConnectionCondition`. Legally absent.
23    pub additional_conditions: Option<EntityId>,
24    /// `SupportedLength`, the tributary length this connection carries along the member. Legally absent.
25    pub supported_length: Option<f64>,
26    /// `ConditionCoordinateSystem`, an `IfcAxis2Placement3D` local to the connection. Legally absent.
27    pub coordinate_system: Option<EntityId>,
28}
29
30impl MemberConnection {
31    #[must_use]
32    /// The `IfcRelConnectsStructuralMember` entity id backing this connection.
33    pub fn relation_id(&self) -> EntityId {
34        self.relation
35    }
36
37    /// The connected `IfcStructuralMember` (`RelatingStructuralMember`). Always present.
38    pub fn member(&self) -> StructuralResult<EntityId> {
39        Ok(self.member)
40    }
41
42    /// The connected `IfcStructuralConnection` (`RelatedStructuralConnection`). Always present.
43    pub fn connection(&self) -> StructuralResult<EntityId> {
44        Ok(self.connection)
45    }
46
47    /// `SupportedLength`, legally absent when the whole member is supported.
48    pub fn supported_length(&self) -> StructuralResult<Option<f64>> {
49        Ok(self.supported_length)
50    }
51}
52
53/// A resolved `IfcRelConnectsStructuralActivity` attaching one activity (action or reaction) to its target.
54#[derive(Debug, Clone, PartialEq, Eq)]
55#[non_exhaustive]
56pub struct ActivityAssignment {
57    /// The `IfcRelConnectsStructuralActivity` relation entity itself.
58    pub relation: EntityId,
59    /// `RelatingElement`, the `IfcStructuralActivityAssignmentSelect` (an `IfcElement` or `IfcStructuralItem`) the activity is attached to.
60    pub target: EntityId,
61    /// `RelatedStructuralActivity`, the attached `IfcStructuralActivity` (action or reaction).
62    pub activity: EntityId,
63}
64
65impl<'m, 's> StructuralView<'m, 's> {
66    /// Members of `analysis_model`'s `IsGroupedBy` `IfcRelAssignsToGroup` relations, in file order.
67    ///
68    /// Fails with [`crate::StructuralError::SemanticViolation`] if a relation
69    /// assigns the analysis model to itself.
70    pub fn analysis_items(&self, analysis_model: EntityId) -> StructuralResult<Vec<EntityId>> {
71        self.analysis_model(analysis_model)?;
72        let mut items = Vec::new();
73        for relation in self.ids_of_ancestor("IfcRelAssignsToGroup") {
74            let record = Record::new(self.model, self.schema, relation, "IfcRelAssignsToGroup")?;
75            if record.required_ref("RelatingGroup", "IfcGroup")? != analysis_model {
76                continue;
77            }
78            let related = record.required_set_refs_select(
79                "RelatedObjects",
80                "IfcObjectDefinition",
81                &["IfcObjectDefinition"],
82                1,
83            )?;
84            if related.contains(&analysis_model) {
85                return Err(crate::StructuralError::SemanticViolation {
86                    entity: Some(relation),
87                    rule: "IfcRelAssignsToGroup must not assign its RelatingGroup to itself",
88                });
89            }
90            items.extend(related);
91        }
92        Ok(items)
93    }
94
95    /// Reactions assigned to one structural result group in relation/file order.
96    pub fn reactions_for_result_group(
97        &self,
98        result_group: EntityId,
99    ) -> StructuralResult<Vec<EntityId>> {
100        self.result_group(result_group)?;
101        let mut reactions = Vec::new();
102        for relation in self.ids_of_ancestor("IfcRelAssignsToGroup") {
103            let record = Record::new(self.model, self.schema, relation, "IfcRelAssignsToGroup")?;
104            if record.required_ref("RelatingGroup", "IfcGroup")? != result_group {
105                continue;
106            }
107            reactions.extend(record.required_set_refs_select(
108                "RelatedObjects",
109                "IfcStructuralReaction",
110                &["IfcStructuralReaction"],
111                1,
112            )?);
113        }
114        Ok(reactions)
115    }
116
117    /// `IfcRelConnectsStructuralMember` relations attaching connections to `member`, in file order.
118    pub fn member_connections(&self, member: EntityId) -> StructuralResult<Vec<MemberConnection>> {
119        self.member(member)?;
120        let mut connections = Vec::new();
121        for relation in self.ids_of_ancestor("IfcRelConnectsStructuralMember") {
122            let record = Record::new(
123                self.model,
124                self.schema,
125                relation,
126                "IfcRelConnectsStructuralMember",
127            )?;
128            let relating =
129                record.required_ref("RelatingStructuralMember", "IfcStructuralMember")?;
130            if relating != member {
131                continue;
132            }
133            connections.push(MemberConnection {
134                relation,
135                member: relating,
136                connection: record
137                    .required_ref("RelatedStructuralConnection", "IfcStructuralConnection")?,
138                applied_condition: record
139                    .optional_ref("AppliedCondition", "IfcBoundaryCondition")?,
140                additional_conditions: record
141                    .optional_ref("AdditionalConditions", "IfcStructuralConnectionCondition")?,
142                supported_length: record.optional_number("SupportedLength")?,
143                coordinate_system: record
144                    .optional_ref("ConditionCoordinateSystem", "IfcAxis2Placement3D")?,
145            });
146        }
147        Ok(connections)
148    }
149
150    /// `IfcRelConnectsStructuralActivity` relations attaching activities to `target`, in file order.
151    ///
152    /// Fails with [`crate::StructuralError::WrongType`] if `target` is neither
153    /// an `IfcElement` nor an `IfcStructuralItem`, and with
154    /// [`crate::StructuralError::SemanticViolation`] if an `IfcStructuralActivity`
155    /// is attached by more than one relation.
156    pub fn activities_for(&self, target: EntityId) -> StructuralResult<Vec<ActivityAssignment>> {
157        let target_entity = self
158            .model
159            .get(target)
160            .ok_or(crate::StructuralError::EntityNotFound { id: target })?;
161        if !self.schema.is_a(&target_entity.type_name, "IfcElement")
162            && !self
163                .schema
164                .is_a(&target_entity.type_name, "IfcStructuralItem")
165        {
166            return Err(crate::StructuralError::WrongType {
167                id: target,
168                expected: "IfcStructuralActivityAssignmentSelect",
169                actual: target_entity.type_name.to_string(),
170            });
171        }
172        let mut assignments = Vec::new();
173        let mut attached_activities = HashSet::new();
174        for relation in self.ids_of_ancestor("IfcRelConnectsStructuralActivity") {
175            let record = Record::new(
176                self.model,
177                self.schema,
178                relation,
179                "IfcRelConnectsStructuralActivity",
180            )?;
181            let relating = record.required_ref_select(
182                "RelatingElement",
183                "IfcStructuralActivityAssignmentSelect",
184                &["IfcElement", "IfcStructuralItem"],
185            )?;
186            let activity =
187                record.required_ref("RelatedStructuralActivity", "IfcStructuralActivity")?;
188            if !attached_activities.insert(activity) {
189                return Err(crate::StructuralError::SemanticViolation {
190                    entity: Some(relation),
191                    rule: "IfcStructuralActivity must have at most one attachment relation",
192                });
193            }
194            if relating != target {
195                continue;
196            }
197            assignments.push(ActivityAssignment {
198                relation,
199                target: relating,
200                activity,
201            });
202        }
203        Ok(assignments)
204    }
205}