Skip to main content

ifc_tabular/
series.rs

1//! `IfcTimeSeries` subtypes and their value records.
2
3use ifc_model::{Entity, EntityId, Transaction, Value};
4
5use crate::error::{TabularError, TabularResult};
6
7const REGULAR: &str = "IFCREGULARTIMESERIES";
8const IRREGULAR: &str = "IFCIRREGULARTIMESERIES";
9const VALUE: &str = "IFCTIMESERIESVALUE";
10const IRREGULAR_VALUE: &str = "IFCIRREGULARTIMESERIESVALUE";
11
12/// Stage an `IfcTimeSeriesValue`.
13///
14/// # Errors
15///
16/// Refuses an empty value list: `ListValues` is `LIST [1:?]`.
17pub fn add_time_series_value(tx: &mut Transaction, values: Vec<Value>) -> TabularResult<EntityId> {
18    if values.is_empty() {
19        return Err(TabularError::EmptyList {
20            entity: VALUE,
21            attribute: "ListValues",
22        });
23    }
24    Ok(tx.create(Entity::new(VALUE, vec![Value::List(values)])))
25}
26
27/// Stage an `IfcIrregularTimeSeriesValue`.
28///
29/// Carries its own `TimeStamp`: an irregular series has no step to derive
30/// the instant from, so each value states when it happened.
31///
32/// # Errors
33///
34/// Refuses a blank timestamp and an empty value list.
35pub fn add_irregular_value(
36    tx: &mut Transaction,
37    timestamp: &str,
38    values: Vec<Value>,
39) -> TabularResult<EntityId> {
40    if timestamp.trim().is_empty() {
41        return Err(TabularError::BlankRequired {
42            entity: IRREGULAR_VALUE,
43            attribute: "TimeStamp",
44        });
45    }
46    if values.is_empty() {
47        return Err(TabularError::EmptyList {
48            entity: IRREGULAR_VALUE,
49            attribute: "ListValues",
50        });
51    }
52    let attributes = vec![Value::Text(timestamp.into()), Value::List(values)];
53    Ok(tx.create(Entity::new(IRREGULAR_VALUE, attributes)))
54}
55
56/// The `IfcTimeSeries` supertype attributes, shared by both subtypes.
57#[derive(Debug, Clone, Copy, Default)]
58#[non_exhaustive]
59pub struct SeriesDraft<'a> {
60    /// `Name`. Required.
61    pub name: &'a str,
62    /// `Description`.
63    pub description: Option<&'a str>,
64    /// `StartTime`. Required.
65    pub start_time: &'a str,
66    /// `EndTime`. Required.
67    pub end_time: &'a str,
68    /// `TimeSeriesDataType`. Required enumeration token.
69    pub data_type: &'a str,
70    /// `DataOrigin`. Required enumeration token.
71    pub data_origin: &'a str,
72    /// `UserDefinedDataOrigin`.
73    pub user_defined_data_origin: Option<&'a str>,
74    /// `Unit`, an `IfcUnit` reference.
75    pub unit: Option<EntityId>,
76}
77
78impl<'a> SeriesDraft<'a> {
79    /// Starts a draft with its required fields; the rest are unset.
80    #[must_use]
81    pub fn new(
82        name: &'a str,
83        start_time: &'a str,
84        end_time: &'a str,
85        data_type: &'a str,
86        data_origin: &'a str,
87    ) -> Self {
88        Self {
89            name,
90            description: None,
91            start_time,
92            end_time,
93            data_type,
94            data_origin,
95            user_defined_data_origin: None,
96            unit: None,
97        }
98    }
99
100    /// Sets [`Self::description`]: `Description`.
101    #[must_use]
102    pub fn description(mut self, value: &'a str) -> Self {
103        self.description = Some(value);
104        self
105    }
106
107    /// Sets [`Self::user_defined_data_origin`]: `UserDefinedDataOrigin`.
108    #[must_use]
109    pub fn user_defined_data_origin(mut self, value: &'a str) -> Self {
110        self.user_defined_data_origin = Some(value);
111        self
112    }
113
114    /// Sets [`Self::unit`]: `Unit`, an `IfcUnit` reference.
115    #[must_use]
116    pub fn unit(mut self, value: EntityId) -> Self {
117        self.unit = Some(value);
118        self
119    }
120}
121
122fn supertype_slots(entity: &'static str, draft: SeriesDraft<'_>) -> TabularResult<Vec<Value>> {
123    for (attribute, text) in [
124        ("Name", draft.name),
125        ("StartTime", draft.start_time),
126        ("EndTime", draft.end_time),
127        ("TimeSeriesDataType", draft.data_type),
128        ("DataOrigin", draft.data_origin),
129    ] {
130        if text.trim().is_empty() {
131            return Err(TabularError::BlankRequired { entity, attribute });
132        }
133    }
134    Ok(vec![
135        Value::Text(draft.name.into()),
136        draft
137            .description
138            .map_or(Value::Null, |text| Value::Text(text.into())),
139        Value::Text(draft.start_time.into()),
140        Value::Text(draft.end_time.into()),
141        Value::Enum(draft.data_type.into()),
142        Value::Enum(draft.data_origin.into()),
143        draft
144            .user_defined_data_origin
145            .map_or(Value::Null, |text| Value::Text(text.into())),
146        draft.unit.map_or(Value::Null, Value::Ref),
147    ])
148}
149
150/// Stage an `IfcRegularTimeSeries`.
151///
152/// A regular series states one `TimeStep` and derives every instant from
153/// it, so its values carry no timestamps of their own.
154///
155/// # Errors
156///
157/// Refuses blank required text, a non-finite or non-positive step, and an
158/// empty value list.
159pub fn add_regular_time_series(
160    tx: &mut Transaction,
161    draft: SeriesDraft<'_>,
162    time_step: f64,
163    values: &[EntityId],
164) -> TabularResult<EntityId> {
165    let mut attributes = supertype_slots(REGULAR, draft)?;
166    if !time_step.is_finite() || time_step <= 0.0 {
167        return Err(TabularError::NotFinite {
168            entity: REGULAR,
169            attribute: "TimeStep",
170        });
171    }
172    if values.is_empty() {
173        return Err(TabularError::EmptyList {
174            entity: REGULAR,
175            attribute: "Values",
176        });
177    }
178    attributes.push(Value::Real(time_step));
179    attributes.push(Value::List(
180        values.iter().copied().map(Value::Ref).collect(),
181    ));
182    Ok(tx.create(Entity::new(REGULAR, attributes)))
183}
184
185/// Stage an `IfcIrregularTimeSeries`.
186///
187/// # Errors
188///
189/// Refuses blank required text and an empty value list.
190pub fn add_irregular_time_series(
191    tx: &mut Transaction,
192    draft: SeriesDraft<'_>,
193    values: &[EntityId],
194) -> TabularResult<EntityId> {
195    let mut attributes = supertype_slots(IRREGULAR, draft)?;
196    if values.is_empty() {
197        return Err(TabularError::EmptyList {
198            entity: IRREGULAR,
199            attribute: "Values",
200        });
201    }
202    attributes.push(Value::List(
203        values.iter().copied().map(Value::Ref).collect(),
204    ));
205    Ok(tx.create(Entity::new(IRREGULAR, attributes)))
206}