Skip to main content

ifc_schedule/schedule/
work_control.rs

1//! `IfcWorkPlan` and `IfcWorkSchedule`: the documents that hold tasks.
2//!
3//! # Slots, verified against IFC4 EXPRESS
4//!
5//! Both are `IfcWorkControl` subtypes, which is `IfcControl` -> `IfcObject`
6//! -> `IfcRoot`. The inherited chain contributes six slots before either type
7//! adds anything:
8//!
9//! ```text
10//! 0 GlobalId        1 OwnerHistory    2 Name
11//! 3 Description     4 ObjectType      5 Identification   (IfcControl)
12//! -- IfcWorkControl --
13//! 6 CreationDate    7 Creators        8 Purpose
14//! 9 Duration       10 TotalFloat     11 StartTime
15//! 12 FinishTime
16//! -- subtype --
17//! 13 PredefinedType
18//! ```
19//!
20//! `StartTime` is slot 11 and required by the schema; `FinishTime` is 12 and
21//! optional. A reader that assumes the subtype's `PredefinedType` sits right
22//! after `Identification` -- as it does on most `IfcControl` subtypes -- lands
23//! on `CreationDate` instead and reports a date as a type token.
24//!
25//! # Dates are returned as authored
26//!
27//! `IfcDateTime` is an ISO 8601 string. This crate does not parse it into a
28//! calendar type: doing so would force a date library into a crate whose only
29//! dependency is `ifc-model`, and would have to decide what to do with the
30//! offsets and partial dates real files carry. The string is returned intact
31//! and a caller that needs arithmetic parses it with the library it already
32//! uses.
33
34use ifc_model::{Entity, EntityId, Model, Value};
35
36/// `IfcWorkControl` slots, shared by plans and schedules.
37pub mod slot {
38    /// `GlobalId` (from `IfcRoot`).
39    pub const GLOBAL_ID: usize = 0;
40    /// `Name` (from `IfcRoot`).
41    pub const NAME: usize = 2;
42    /// `Description` (from `IfcRoot`).
43    pub const DESCRIPTION: usize = 3;
44    /// `Identification` (from `IfcControl`).
45    pub const IDENTIFICATION: usize = 5;
46    /// `CreationDate`.
47    pub const CREATION_DATE: usize = 6;
48    /// `Purpose`.
49    pub const PURPOSE: usize = 8;
50    /// `Duration`, an ISO 8601 duration.
51    pub const DURATION: usize = 9;
52    /// `TotalFloat`, an ISO 8601 duration.
53    pub const TOTAL_FLOAT: usize = 10;
54    /// `StartTime`, required by the schema.
55    pub const START_TIME: usize = 11;
56    /// `FinishTime`.
57    pub const FINISH_TIME: usize = 12;
58    /// `PredefinedType`, contributed by the subtype.
59    pub const PREDEFINED_TYPE: usize = 13;
60}
61
62/// Whether a work control is a plan or a schedule.
63#[derive(Debug, Clone, Copy, PartialEq, Eq)]
64pub enum WorkControlKind {
65    /// `IfcWorkPlan`: a container for schedules.
66    Plan,
67    /// `IfcWorkSchedule`: a container for tasks.
68    Schedule,
69}
70
71impl WorkControlKind {
72    fn from_type(name: &str) -> Option<Self> {
73        if name.eq_ignore_ascii_case("IFCWORKPLAN") {
74            Some(Self::Plan)
75        } else if name.eq_ignore_ascii_case("IFCWORKSCHEDULE") {
76            Some(Self::Schedule)
77        } else {
78            None
79        }
80    }
81}
82
83/// A borrowed view of an `IfcWorkPlan` or `IfcWorkSchedule`.
84#[derive(Debug, Clone, Copy)]
85pub struct WorkControl<'m> {
86    id: EntityId,
87    entity: &'m Entity,
88    kind: WorkControlKind,
89}
90
91impl<'m> WorkControl<'m> {
92    /// Wrap an entity if it is a work plan or work schedule.
93    #[must_use]
94    pub fn new(id: EntityId, entity: &'m Entity) -> Option<Self> {
95        let kind = WorkControlKind::from_type(&entity.type_name)?;
96        Some(Self { id, entity, kind })
97    }
98
99    /// The entity id in the file.
100    #[must_use]
101    pub fn id(&self) -> EntityId {
102        self.id
103    }
104
105    /// Whether this is a plan or a schedule.
106    #[must_use]
107    pub fn kind(&self) -> WorkControlKind {
108        self.kind
109    }
110
111    /// The `GlobalId` string.
112    #[must_use]
113    pub fn global_id(&self) -> Option<&'m str> {
114        self.entity.text(slot::GLOBAL_ID)
115    }
116
117    /// The name.
118    #[must_use]
119    pub fn name(&self) -> Option<&'m str> {
120        self.entity.text(slot::NAME)
121    }
122
123    /// The description.
124    #[must_use]
125    pub fn description(&self) -> Option<&'m str> {
126        self.entity.text(slot::DESCRIPTION)
127    }
128
129    /// The user-facing identification code.
130    #[must_use]
131    pub fn identification(&self) -> Option<&'m str> {
132        self.entity.text(slot::IDENTIFICATION)
133    }
134
135    /// Why the plan or schedule exists, as authored.
136    #[must_use]
137    pub fn purpose(&self) -> Option<&'m str> {
138        self.entity.text(slot::PURPOSE)
139    }
140
141    /// When it was created, as an authored ISO 8601 string.
142    #[must_use]
143    pub fn creation_date(&self) -> Option<&'m str> {
144        self.entity.text(slot::CREATION_DATE)
145    }
146
147    /// The planned start, as authored. Required by the schema.
148    #[must_use]
149    pub fn start_time(&self) -> Option<&'m str> {
150        self.entity.text(slot::START_TIME)
151    }
152
153    /// The planned finish, as authored.
154    #[must_use]
155    pub fn finish_time(&self) -> Option<&'m str> {
156        self.entity.text(slot::FINISH_TIME)
157    }
158
159    /// The overall duration, as an authored ISO 8601 duration.
160    #[must_use]
161    pub fn duration(&self) -> Option<&'m str> {
162        self.entity.text(slot::DURATION)
163    }
164
165    /// The total float, as an authored ISO 8601 duration.
166    #[must_use]
167    pub fn total_float(&self) -> Option<&'m str> {
168        self.entity.text(slot::TOTAL_FLOAT)
169    }
170
171    /// The predefined type token, without its dots.
172    #[must_use]
173    pub fn predefined_type(&self) -> Option<&'m str> {
174        match self.entity.attribute(slot::PREDEFINED_TYPE)? {
175            Value::Enum(token) => Some(token),
176            _ => None,
177        }
178    }
179}
180
181/// Every work plan in the model, in file order.
182#[must_use]
183pub fn work_plans(model: &Model) -> Vec<WorkControl<'_>> {
184    model
185        .of_type("IFCWORKPLAN")
186        .filter_map(|(id, entity)| WorkControl::new(id, entity))
187        .collect()
188}
189
190/// Every work schedule in the model, in file order.
191#[must_use]
192pub fn work_schedules(model: &Model) -> Vec<WorkControl<'_>> {
193    model
194        .of_type("IFCWORKSCHEDULE")
195        .filter_map(|(id, entity)| WorkControl::new(id, entity))
196        .collect()
197}