Skip to main content

ifc_schedule/authoring/
procedure.rs

1//! Staging `IfcProcedure`.
2//!
3//! A procedure is a process, not a task: it describes how work is
4//! carried out rather than when. It is split from the task and
5//! calendar writers because it shares none of their time slots.
6
7use ifc_model::guid::Guid;
8use ifc_model::{Entity, EntityId, Transaction, Value};
9
10use super::{optional_text, ScheduleAuthoringError, ScheduleAuthoringResult};
11/// `IfcProcedureTypeEnum`.
12///
13/// Closed: a token outside it names a procedure kind the schema
14/// does not define.
15const PROCEDURE_KIND: &[&str] = &[
16    "ADVICE_CAUTION",
17    "ADVICE_NOTE",
18    "ADVICE_WARNING",
19    "CALIBRATION",
20    "DIAGNOSTIC",
21    "SHUTDOWN",
22    "STARTUP",
23    "USERDEFINED",
24    "NOTDEFINED",
25];
26
27/// Attributes of an `IfcProcedure`.
28#[derive(Debug, Clone, Copy, Default)]
29#[non_exhaustive]
30pub struct ProcedureDraft<'a> {
31    /// `GlobalId`, a compressed IFC GUID.
32    pub global_id: &'a str,
33    /// `Name`. Required by `HasName`.
34    pub name: Option<&'a str>,
35    /// `Description`.
36    pub description: Option<&'a str>,
37    /// `ObjectType`. Required when `predefined_type` is `USERDEFINED`.
38    pub object_type: Option<&'a str>,
39    /// `Identification`.
40    pub identification: Option<&'a str>,
41    /// `LongDescription`.
42    pub long_description: Option<&'a str>,
43    /// `PredefinedType`, an `IfcProcedureTypeEnum` token.
44    pub predefined_type: Option<&'a str>,
45    /// IFC2X3 `UserDefinedProcedureType`, which `WR4` requires when
46    /// `predefined_type` is `USERDEFINED`. IFC4 and IFC4X3 do not declare
47    /// it (they name the kind in `object_type`), so only
48    /// [`create_procedure_with_owner_history`](super::create_procedure_with_owner_history)
49    /// in an IFC2X3 model writes it.
50    pub user_defined_procedure_type: Option<&'a str>,
51}
52
53/// Stage an `IfcProcedure`.
54///
55/// `HasName` makes `Name` mandatory even though the slot is
56/// OPTIONAL: a procedure nothing can name cannot be referred to by
57/// the work that must follow it.
58///
59/// # Errors
60///
61/// Refuses a malformed GlobalId, a blank name (`HasName`), a token
62/// outside `IfcProcedureTypeEnum`, `USERDEFINED` without
63/// `ObjectType` (`CorrectPredefinedType`), and a
64/// `user_defined_procedure_type`, which IFC4 and IFC4X3 do not declare.
65///
66/// `OwnerHistory` is written `$`, which IFC4 and IFC4X3 allow, in their
67/// shared layout. This writer takes no model, so it cannot see the declared
68/// release: it is for IFC4 and IFC4X3 only. IFC2X3 requires
69/// `OwnerHistory` and lays the record out differently; use
70/// [`create_procedure_with_owner_history`](super::create_procedure_with_owner_history) there, which binds the release (#202).
71pub fn create_procedure(
72    tx: &mut Transaction,
73    draft: ProcedureDraft<'_>,
74) -> ScheduleAuthoringResult<EntityId> {
75    check(&draft)?;
76    if draft.user_defined_procedure_type.is_some() {
77        return Err(ScheduleAuthoringError::InvalidValue {
78            entity: ENTITY,
79            attribute: "UserDefinedProcedureType",
80            expected: "no value: IFC4 and IFC4X3 declare no UserDefinedProcedureType",
81        });
82    }
83    let mut attributes = vec![Value::Null; 8];
84    attributes[0] = Value::Text(draft.global_id.into());
85    attributes[2] = optional_text(draft.name);
86    attributes[3] = optional_text(draft.description);
87    attributes[4] = optional_text(draft.object_type);
88    attributes[5] = optional_text(draft.identification);
89    attributes[6] = optional_text(draft.long_description);
90    attributes[7] = draft
91        .predefined_type
92        .map_or(Value::Null, |t| Value::Enum(t.into()));
93    Ok(tx.create(Entity::new(ENTITY, attributes)))
94}
95
96/// The entity [`create_procedure`] stages.
97pub(super) const ENTITY: &str = "IFCPROCEDURE";
98
99/// The draft rules [`create_procedure`] and its owner-history variant share.
100pub(super) fn check(draft: &ProcedureDraft<'_>) -> ScheduleAuthoringResult<()> {
101    if Guid::parse(draft.global_id).is_none() {
102        return Err(ScheduleAuthoringError::InvalidValue {
103            entity: ENTITY,
104            attribute: "GlobalId",
105            expected: "an IFC compressed GUID",
106        });
107    }
108    if draft.name.is_none_or(|value| value.trim().is_empty()) {
109        return Err(ScheduleAuthoringError::InvalidValue {
110            entity: ENTITY,
111            attribute: "Name",
112            expected: "a name, required by HasName",
113        });
114    }
115    if let Some(token) = draft.predefined_type {
116        if !PROCEDURE_KIND.contains(&token) {
117            return Err(ScheduleAuthoringError::InvalidValue {
118                entity: ENTITY,
119                attribute: "PredefinedType",
120                expected: "a token of IfcProcedureTypeEnum",
121            });
122        }
123        if token == "USERDEFINED"
124            && draft
125                .object_type
126                .is_none_or(|value| value.trim().is_empty())
127        {
128            return Err(ScheduleAuthoringError::InvalidValue {
129                entity: ENTITY,
130                attribute: "ObjectType",
131                expected: "a name, required by USERDEFINED",
132            });
133        }
134    }
135    Ok(())
136}