ifc-structural 0.2.1

Structural analysis model: members, connections, actions, reactions, loads.
Documentation
//! Bounded structural relationship queries.

use std::collections::HashSet;

use ifc_model::EntityId;

use crate::error::StructuralResult;
use crate::view::{Record, StructuralView};

/// A resolved `IfcRelConnectsStructuralMember` between one member and its connection.
#[derive(Debug, Clone, PartialEq)]
pub struct MemberConnection {
    /// The `IfcRelConnectsStructuralMember` relation entity itself.
    pub relation: EntityId,
    /// `RelatingStructuralMember`.
    pub member: EntityId,
    /// `RelatedStructuralConnection`.
    pub connection: EntityId,
    /// `AppliedCondition`, an `IfcBoundaryCondition` overriding the connection's own condition. Legally absent.
    pub applied_condition: Option<EntityId>,
    /// `AdditionalConditions`, an `IfcStructuralConnectionCondition`. Legally absent.
    pub additional_conditions: Option<EntityId>,
    /// `SupportedLength`, the tributary length this connection carries along the member. Legally absent.
    pub supported_length: Option<f64>,
    /// `ConditionCoordinateSystem`, an `IfcAxis2Placement3D` local to the connection. Legally absent.
    pub coordinate_system: Option<EntityId>,
}

impl MemberConnection {
    #[must_use]
    /// The `IfcRelConnectsStructuralMember` entity id backing this connection.
    pub fn relation_id(&self) -> EntityId {
        self.relation
    }

    /// The connected `IfcStructuralMember` (`RelatingStructuralMember`). Always present.
    pub fn member(&self) -> StructuralResult<EntityId> {
        Ok(self.member)
    }

    /// The connected `IfcStructuralConnection` (`RelatedStructuralConnection`). Always present.
    pub fn connection(&self) -> StructuralResult<EntityId> {
        Ok(self.connection)
    }

    /// `SupportedLength`, legally absent when the whole member is supported.
    pub fn supported_length(&self) -> StructuralResult<Option<f64>> {
        Ok(self.supported_length)
    }
}

/// A resolved `IfcRelConnectsStructuralActivity` attaching one activity (action or reaction) to its target.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ActivityAssignment {
    /// The `IfcRelConnectsStructuralActivity` relation entity itself.
    pub relation: EntityId,
    /// `RelatingElement`, the `IfcStructuralActivityAssignmentSelect` (an `IfcElement` or `IfcStructuralItem`) the activity is attached to.
    pub target: EntityId,
    /// `RelatedStructuralActivity`, the attached `IfcStructuralActivity` (action or reaction).
    pub activity: EntityId,
}

impl<'m, 's> StructuralView<'m, 's> {
    /// Members of `analysis_model`'s `IsGroupedBy` `IfcRelAssignsToGroup` relations, in file order.
    ///
    /// Fails with [`crate::StructuralError::SemanticViolation`] if a relation
    /// assigns the analysis model to itself.
    pub fn analysis_items(&self, analysis_model: EntityId) -> StructuralResult<Vec<EntityId>> {
        self.analysis_model(analysis_model)?;
        let mut items = Vec::new();
        for relation in self.ids_of_ancestor("IfcRelAssignsToGroup") {
            let record = Record::new(self.model, self.schema, relation, "IfcRelAssignsToGroup")?;
            if record.required_ref("RelatingGroup", "IfcGroup")? != analysis_model {
                continue;
            }
            let related = record.required_set_refs_select(
                "RelatedObjects",
                "IfcObjectDefinition",
                &["IfcObjectDefinition"],
                1,
            )?;
            if related.contains(&analysis_model) {
                return Err(crate::StructuralError::SemanticViolation {
                    entity: Some(relation),
                    rule: "IfcRelAssignsToGroup must not assign its RelatingGroup to itself",
                });
            }
            items.extend(related);
        }
        Ok(items)
    }

    /// Reactions assigned to one structural result group in relation/file order.
    pub fn reactions_for_result_group(
        &self,
        result_group: EntityId,
    ) -> StructuralResult<Vec<EntityId>> {
        self.result_group(result_group)?;
        let mut reactions = Vec::new();
        for relation in self.ids_of_ancestor("IfcRelAssignsToGroup") {
            let record = Record::new(self.model, self.schema, relation, "IfcRelAssignsToGroup")?;
            if record.required_ref("RelatingGroup", "IfcGroup")? != result_group {
                continue;
            }
            reactions.extend(record.required_set_refs_select(
                "RelatedObjects",
                "IfcStructuralReaction",
                &["IfcStructuralReaction"],
                1,
            )?);
        }
        Ok(reactions)
    }

    /// `IfcRelConnectsStructuralMember` relations attaching connections to `member`, in file order.
    pub fn member_connections(&self, member: EntityId) -> StructuralResult<Vec<MemberConnection>> {
        self.member(member)?;
        let mut connections = Vec::new();
        for relation in self.ids_of_ancestor("IfcRelConnectsStructuralMember") {
            let record = Record::new(
                self.model,
                self.schema,
                relation,
                "IfcRelConnectsStructuralMember",
            )?;
            let relating =
                record.required_ref("RelatingStructuralMember", "IfcStructuralMember")?;
            if relating != member {
                continue;
            }
            connections.push(MemberConnection {
                relation,
                member: relating,
                connection: record
                    .required_ref("RelatedStructuralConnection", "IfcStructuralConnection")?,
                applied_condition: record
                    .optional_ref("AppliedCondition", "IfcBoundaryCondition")?,
                additional_conditions: record
                    .optional_ref("AdditionalConditions", "IfcStructuralConnectionCondition")?,
                supported_length: record.optional_number("SupportedLength")?,
                coordinate_system: record
                    .optional_ref("ConditionCoordinateSystem", "IfcAxis2Placement3D")?,
            });
        }
        Ok(connections)
    }

    /// `IfcRelConnectsStructuralActivity` relations attaching activities to `target`, in file order.
    ///
    /// Fails with [`crate::StructuralError::WrongType`] if `target` is neither
    /// an `IfcElement` nor an `IfcStructuralItem`, and with
    /// [`crate::StructuralError::SemanticViolation`] if an `IfcStructuralActivity`
    /// is attached by more than one relation.
    pub fn activities_for(&self, target: EntityId) -> StructuralResult<Vec<ActivityAssignment>> {
        let target_entity = self
            .model
            .get(target)
            .ok_or(crate::StructuralError::EntityNotFound { id: target })?;
        if !self.schema.is_a(&target_entity.type_name, "IfcElement")
            && !self
                .schema
                .is_a(&target_entity.type_name, "IfcStructuralItem")
        {
            return Err(crate::StructuralError::WrongType {
                id: target,
                expected: "IfcStructuralActivityAssignmentSelect",
                actual: target_entity.type_name.to_string(),
            });
        }
        let mut assignments = Vec::new();
        let mut attached_activities = HashSet::new();
        for relation in self.ids_of_ancestor("IfcRelConnectsStructuralActivity") {
            let record = Record::new(
                self.model,
                self.schema,
                relation,
                "IfcRelConnectsStructuralActivity",
            )?;
            let relating = record.required_ref_select(
                "RelatingElement",
                "IfcStructuralActivityAssignmentSelect",
                &["IfcElement", "IfcStructuralItem"],
            )?;
            let activity =
                record.required_ref("RelatedStructuralActivity", "IfcStructuralActivity")?;
            if !attached_activities.insert(activity) {
                return Err(crate::StructuralError::SemanticViolation {
                    entity: Some(relation),
                    rule: "IfcStructuralActivity must have at most one attachment relation",
                });
            }
            if relating != target {
                continue;
            }
            assignments.push(ActivityAssignment {
                relation,
                target: relating,
                activity,
            });
        }
        Ok(assignments)
    }
}