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}