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)]
239pub struct ClassifiedSystemDraft<'a> {
240 pub description: Option<&'a str>,
242 pub object_type: Option<&'a str>,
244 pub predefined_type: Option<&'a str>,
246 pub long_name: Option<&'a str>,
248}
249pub fn create_classified_system(
268 tx: &mut Transaction,
269 schema: &Schema,
270 kind: SystemKind,
271 global_id: &str,
272 name: Option<&str>,
273 draft: ClassifiedSystemDraft<'_>,
274) -> SystemAuthoringResult<EntityId> {
275 let entity = kind.type_name();
276 if Guid::parse(global_id).is_none() {
277 return Err(invalid(entity, "GlobalId", global_id));
278 }
279
280 let declared = schema.attributes(entity);
283 if declared.is_empty() {
284 return Err(invalid(entity, "Entity", schema.name().to_owned()));
285 }
286 check_predefined(schema, kind, draft)?;
287
288 let mut attributes = vec![Value::Null; declared.len()];
289 attributes[0] = Value::Text(global_id.into());
290 attributes[2] = text(name);
291 attributes[3] = text(draft.description);
292 attributes[4] = text(draft.object_type);
293 attributes[kind.predefined_slot()] = draft
294 .predefined_type
295 .map_or(Value::Null, |t| Value::Enum(t.into()));
296 attributes[kind.long_name_slot()] = text(draft.long_name);
297 Ok(tx.create(Entity::new(entity, attributes)))
298}
299
300fn text(value: Option<&str>) -> Value {
301 value.map_or(Value::Null, |v| Value::Text(v.into()))
302}
303
304fn blank(value: Option<&str>) -> bool {
305 value.is_none_or(|v| v.trim().is_empty())
306}
307
308fn check_predefined(
310 schema: &Schema,
311 kind: SystemKind,
312 draft: ClassifiedSystemDraft<'_>,
313) -> SystemAuthoringResult<()> {
314 let entity = kind.type_name();
315 if let Some(token) = draft.predefined_type {
316 if !kind.members(schema).contains(&token) {
317 return Err(invalid(entity, "PredefinedType", token));
318 }
319 if token == "USERDEFINED" && blank(draft.object_type) {
320 return Err(invalid(entity, "ObjectType", "required by USERDEFINED"));
321 }
322 }
323 Ok(())
324}
325
326pub fn create_classified_system_with_owner_history(
342 tx: &mut Transaction,
343 model: &Model,
344 kind: SystemKind,
345 global_id: &str,
346 name: Option<&str>,
347 draft: ClassifiedSystemDraft<'_>,
348 owner_history: EntityId,
349) -> SystemAuthoringResult<EntityId> {
350 let release = bind(model)?;
351 let entity = kind.type_name();
352 if Guid::parse(global_id).is_none() {
353 return Err(invalid(entity, "GlobalId", global_id));
354 }
355 release.require_entity(entity)?;
356 check_predefined(release.schema(), kind, draft)?;
357 let values = vec![
358 ("GlobalId", Value::Text(global_id.into())),
359 ("Name", text(name)),
360 ("Description", text(draft.description)),
361 ("ObjectType", text(draft.object_type)),
362 (
363 "PredefinedType",
364 draft
365 .predefined_type
366 .map_or(Value::Null, |t| Value::Enum(t.into())),
367 ),
368 ("LongName", text(draft.long_name)),
369 ];
370 release.stage(tx, model, entity, values, owner_history)
371}