1use ifc_model::guid::Guid;
27use ifc_model::{Entity, EntityId, Model, Transaction, Value};
28use ifc_schema::Schema;
29
30use super::release::bind;
31use super::{invalid, SystemAuthoringResult};
32
33const BUILDING: &[&str] = &[
35 "FENESTRATION",
36 "FOUNDATION",
37 "LOADBEARING",
38 "OUTERSHELL",
39 "SHADING",
40 "TRANSPORT",
41 "USERDEFINED",
42 "NOTDEFINED",
43];
44
45const BUILT: &[&str] = &[
50 "EROSIONPREVENTION",
51 "FENESTRATION",
52 "FOUNDATION",
53 "LOADBEARING",
54 "MOORING",
55 "OUTERSHELL",
56 "PRESTRESSING",
57 "RAILWAYLINE",
58 "RAILWAYTRACK",
59 "REINFORCING",
60 "SHADING",
61 "TRACKCIRCUIT",
62 "TRANSPORT",
63 "USERDEFINED",
64 "NOTDEFINED",
65];
66
67const DISTRIBUTION_X3: &[&str] = &[
73 "AIRCONDITIONING",
74 "AUDIOVISUAL",
75 "CATENARY_SYSTEM",
76 "CHEMICAL",
77 "CHILLEDWATER",
78 "COMMUNICATION",
79 "COMPRESSEDAIR",
80 "CONDENSERWATER",
81 "CONTROL",
82 "CONVEYING",
83 "DATA",
84 "DISPOSAL",
85 "DOMESTICCOLDWATER",
86 "DOMESTICHOTWATER",
87 "DRAINAGE",
88 "EARTHING",
89 "ELECTRICAL",
90 "ELECTROACOUSTIC",
91 "EXHAUST",
92 "FIREPROTECTION",
93 "FIXEDTRANSMISSIONNETWORK",
94 "FUEL",
95 "GAS",
96 "HAZARDOUS",
97 "HEATING",
98 "LIGHTING",
99 "LIGHTNINGPROTECTION",
100 "MOBILENETWORK",
101 "MONITORINGSYSTEM",
102 "MUNICIPALSOLIDWASTE",
103 "OIL",
104 "OPERATIONAL",
105 "OPERATIONALTELEPHONYSYSTEM",
106 "OVERHEAD_CONTACTLINE_SYSTEM",
107 "POWERGENERATION",
108 "RAINWATER",
109 "REFRIGERATION",
110 "RETURN_CIRCUIT",
111 "SECURITY",
112 "SEWAGE",
113 "SIGNAL",
114 "STORMWATER",
115 "TELEPHONE",
116 "TV",
117 "VACUUM",
118 "VENT",
119 "VENTILATION",
120 "WASTEWATER",
121 "WATERSUPPLY",
122 "USERDEFINED",
123 "NOTDEFINED",
124];
125
126const DISTRIBUTION_IFC4: &[&str] = &[
128 "AIRCONDITIONING",
129 "AUDIOVISUAL",
130 "CHEMICAL",
131 "CHILLEDWATER",
132 "COMMUNICATION",
133 "COMPRESSEDAIR",
134 "CONDENSERWATER",
135 "CONTROL",
136 "CONVEYING",
137 "DATA",
138 "DISPOSAL",
139 "DOMESTICCOLDWATER",
140 "DOMESTICHOTWATER",
141 "DRAINAGE",
142 "EARTHING",
143 "ELECTRICAL",
144 "ELECTROACOUSTIC",
145 "EXHAUST",
146 "FIREPROTECTION",
147 "FUEL",
148 "GAS",
149 "HAZARDOUS",
150 "HEATING",
151 "LIGHTING",
152 "LIGHTNINGPROTECTION",
153 "MUNICIPALSOLIDWASTE",
154 "OIL",
155 "OPERATIONAL",
156 "POWERGENERATION",
157 "RAINWATER",
158 "REFRIGERATION",
159 "SECURITY",
160 "SEWAGE",
161 "SIGNAL",
162 "STORMWATER",
163 "TELEPHONE",
164 "TV",
165 "VACUUM",
166 "VENT",
167 "VENTILATION",
168 "WASTEWATER",
169 "WATERSUPPLY",
170 "USERDEFINED",
171 "NOTDEFINED",
172];
173
174#[derive(Debug, Clone, Copy, PartialEq, Eq)]
176#[non_exhaustive]
177pub enum SystemKind {
178 Building,
181 Built,
183 DistributionCircuit,
185 DistributionSystem,
187}
188
189impl SystemKind {
190 const fn type_name(self) -> &'static str {
192 match self {
193 Self::Building => "IFCBUILDINGSYSTEM",
194 Self::Built => "IFCBUILTSYSTEM",
195 Self::DistributionCircuit => "IFCDISTRIBUTIONCIRCUIT",
196 Self::DistributionSystem => "IFCDISTRIBUTIONSYSTEM",
197 }
198 }
199
200 fn members(self, schema: &Schema) -> &'static [&'static str] {
205 match self {
206 Self::Building => BUILDING,
207 Self::Built => BUILT,
208 Self::DistributionCircuit | Self::DistributionSystem => {
209 if schema.name().eq_ignore_ascii_case("IFC4") {
210 DISTRIBUTION_IFC4
211 } else {
212 DISTRIBUTION_X3
213 }
214 }
215 }
216 }
217 const fn predefined_slot(self) -> usize {
222 match self {
223 Self::Building | Self::Built => 5,
224 Self::DistributionCircuit | Self::DistributionSystem => 6,
225 }
226 }
227
228 const fn long_name_slot(self) -> usize {
230 match self {
231 Self::Building | Self::Built => 6,
232 Self::DistributionCircuit | Self::DistributionSystem => 5,
233 }
234 }
235}
236
237#[derive(Debug, Clone, Copy, Default)]
239#[non_exhaustive]
240pub struct ClassifiedSystemDraft<'a> {
241 pub description: Option<&'a str>,
243 pub object_type: Option<&'a str>,
245 pub predefined_type: Option<&'a str>,
247 pub long_name: Option<&'a str>,
249}
250
251impl<'a> ClassifiedSystemDraft<'a> {
252 #[must_use]
254 pub fn new() -> Self {
255 Self::default()
256 }
257
258 #[must_use]
260 pub const fn description(mut self, value: &'a str) -> Self {
261 self.description = Some(value);
262 self
263 }
264
265 #[must_use]
267 pub const fn object_type(mut self, value: &'a str) -> Self {
268 self.object_type = Some(value);
269 self
270 }
271
272 #[must_use]
274 pub const fn predefined_type(mut self, value: &'a str) -> Self {
275 self.predefined_type = Some(value);
276 self
277 }
278
279 #[must_use]
281 pub const fn long_name(mut self, value: &'a str) -> Self {
282 self.long_name = Some(value);
283 self
284 }
285}
286pub fn create_classified_system(
305 tx: &mut Transaction,
306 schema: &Schema,
307 kind: SystemKind,
308 global_id: &str,
309 name: Option<&str>,
310 draft: ClassifiedSystemDraft<'_>,
311) -> SystemAuthoringResult<EntityId> {
312 let entity = kind.type_name();
313 if Guid::parse(global_id).is_none() {
314 return Err(invalid(entity, "GlobalId", global_id));
315 }
316
317 let declared = schema.attributes(entity);
320 if declared.is_empty() {
321 return Err(invalid(entity, "Entity", schema.name().to_owned()));
322 }
323 check_predefined(schema, kind, draft)?;
324
325 let mut attributes = vec![Value::Null; declared.len()];
326 attributes[0] = Value::Text(global_id.into());
327 attributes[2] = text(name);
328 attributes[3] = text(draft.description);
329 attributes[4] = text(draft.object_type);
330 attributes[kind.predefined_slot()] = draft
331 .predefined_type
332 .map_or(Value::Null, |t| Value::Enum(t.into()));
333 attributes[kind.long_name_slot()] = text(draft.long_name);
334 Ok(tx.create(Entity::new(entity, attributes)))
335}
336
337fn text(value: Option<&str>) -> Value {
338 value.map_or(Value::Null, |v| Value::Text(v.into()))
339}
340
341fn blank(value: Option<&str>) -> bool {
342 value.is_none_or(|v| v.trim().is_empty())
343}
344
345fn check_predefined(
347 schema: &Schema,
348 kind: SystemKind,
349 draft: ClassifiedSystemDraft<'_>,
350) -> SystemAuthoringResult<()> {
351 let entity = kind.type_name();
352 if let Some(token) = draft.predefined_type {
353 if !kind.members(schema).contains(&token) {
354 return Err(invalid(entity, "PredefinedType", token));
355 }
356 if token == "USERDEFINED" && blank(draft.object_type) {
357 return Err(invalid(entity, "ObjectType", "required by USERDEFINED"));
358 }
359 }
360 Ok(())
361}
362
363pub fn create_classified_system_with_owner_history(
379 tx: &mut Transaction,
380 model: &Model,
381 kind: SystemKind,
382 global_id: &str,
383 name: Option<&str>,
384 draft: ClassifiedSystemDraft<'_>,
385 owner_history: EntityId,
386) -> SystemAuthoringResult<EntityId> {
387 let release = bind(model)?;
388 let entity = kind.type_name();
389 if Guid::parse(global_id).is_none() {
390 return Err(invalid(entity, "GlobalId", global_id));
391 }
392 release.require_entity(entity)?;
393 check_predefined(release.schema(), kind, draft)?;
394 let values = vec![
395 ("GlobalId", Value::Text(global_id.into())),
396 ("Name", text(name)),
397 ("Description", text(draft.description)),
398 ("ObjectType", text(draft.object_type)),
399 (
400 "PredefinedType",
401 draft
402 .predefined_type
403 .map_or(Value::Null, |t| Value::Enum(t.into())),
404 ),
405 ("LongName", text(draft.long_name)),
406 ];
407 release.stage(tx, model, entity, values, owner_history)
408}