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