1mod table;
17
18use ifc_model::guid::Guid;
19use ifc_model::{Entity, EntityId, Model, Transaction, Value};
20
21use crate::authoring::release::stage;
22use crate::authoring::SpatialAuthoringError;
23
24pub use table::{ALL, IFCBRIDGE, IFCBRIDGEPART, IFCFACILITY, IFCFACILITYPARTCOMMON};
25pub use table::{IFCMARINEFACILITY, IFCMARINEPART, IFCRAILWAY, IFCRAILWAYPART};
26pub use table::{IFCROAD, IFCROADPART};
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30pub struct Facility {
31 pub type_name: &'static str,
33 pub arity: usize,
35 pub predefined_slot: Option<usize>,
37 pub usage_slot: Option<usize>,
39 pub members: &'static [&'static str],
41}
42
43impl Facility {
44 #[must_use]
48 pub const fn is_part(&self) -> bool {
49 self.usage_slot.is_some()
50 }
51}
52
53#[derive(Debug, Clone, PartialEq, Eq)]
55#[non_exhaustive]
56pub enum FacilityError {
57 MalformedGuid {
59 entity: &'static str,
61 offered: String,
63 },
64 UnknownToken {
66 entity: &'static str,
68 attribute: &'static str,
70 offered: String,
72 },
73 NoPredefinedType {
75 entity: &'static str,
77 },
78 MissingUsageType {
80 entity: &'static str,
82 },
83 UnexpectedUsageType {
85 entity: &'static str,
87 },
88 UserDefinedWithoutObjectType {
90 entity: &'static str,
92 attribute: &'static str,
94 },
95 Authoring(SpatialAuthoringError),
98}
99
100impl core::fmt::Display for FacilityError {
101 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
102 match self {
103 Self::MalformedGuid { entity, offered } => {
104 write!(f, "{entity}: GlobalId {offered:?} is not an IFC GUID")
105 }
106 Self::UnknownToken {
107 entity,
108 attribute,
109 offered,
110 } => {
111 write!(f, "{entity}: {offered:?} is not a {attribute} of {entity}")
112 }
113 Self::NoPredefinedType { entity } => {
114 write!(f, "{entity} declares no PredefinedType")
115 }
116 Self::MissingUsageType { entity } => {
117 write!(f, "{entity}: UsageType is mandatory on a facility part")
118 }
119 Self::UnexpectedUsageType { entity } => {
120 write!(f, "{entity} declares no UsageType")
121 }
122 Self::Authoring(error) => error.fmt(f),
123 Self::UserDefinedWithoutObjectType { entity, attribute } => {
124 write!(
125 f,
126 "{entity}: {attribute} is USERDEFINED without an ObjectType naming it"
127 )
128 }
129 }
130 }
131}
132
133impl std::error::Error for FacilityError {}
134
135pub type FacilityResult<T> = Result<T, FacilityError>;
137
138#[derive(Debug, Clone, Copy, Default)]
140pub struct FacilityDraft<'a> {
141 pub name: Option<&'a str>,
143 pub description: Option<&'a str>,
145 pub object_type: Option<&'a str>,
147 pub placement: Option<EntityId>,
149 pub long_name: Option<&'a str>,
151 pub composition: Option<&'a str>,
153 pub usage: Option<&'a str>,
155}
156
157const USAGE_TOKENS: &[&str] = &[
158 "LATERAL",
159 "LONGITUDINAL",
160 "REGION",
161 "VERTICAL",
162 "USERDEFINED",
163 "NOTDEFINED",
164];
165
166fn names_itself(draft: &FacilityDraft<'_>) -> bool {
167 draft.object_type.is_some_and(|s| !s.trim().is_empty())
168}
169
170pub fn create_facility(
187 tx: &mut Transaction,
188 kind: Facility,
189 global_id: &str,
190 predefined_type: Option<&str>,
191 draft: FacilityDraft<'_>,
192) -> FacilityResult<EntityId> {
193 check_facility(kind, global_id, predefined_type, &draft)?;
194 let mut attributes = vec![Value::Null; kind.arity];
195 attributes[0] = Value::Text(global_id.into());
196 attributes[2] = text(draft.name);
197 attributes[3] = text(draft.description);
198 attributes[4] = text(draft.object_type);
199 attributes[5] = draft.placement.map_or(Value::Null, Value::Ref);
200 attributes[7] = text(draft.long_name);
201 attributes[8] = enumeration(draft.composition);
202 if let Some(slot) = kind.usage_slot {
203 attributes[slot] = enumeration(draft.usage);
204 }
205 if let Some(slot) = kind.predefined_slot {
206 attributes[slot] = enumeration(predefined_type);
207 }
208 Ok(tx.create(Entity::new(kind.type_name, attributes)))
209}
210
211pub fn create_facility_with_owner_history(
225 tx: &mut Transaction,
226 model: &Model,
227 kind: Facility,
228 global_id: &str,
229 predefined_type: Option<&str>,
230 draft: FacilityDraft<'_>,
231 owner_history: EntityId,
232) -> FacilityResult<EntityId> {
233 check_facility(kind, global_id, predefined_type, &draft)?;
234 let values = vec![
235 ("GlobalId", Value::Text(global_id.into())),
236 ("Name", text(draft.name)),
237 ("Description", text(draft.description)),
238 ("ObjectType", text(draft.object_type)),
239 (
240 "ObjectPlacement",
241 draft.placement.map_or(Value::Null, Value::Ref),
242 ),
243 ("LongName", text(draft.long_name)),
244 ("CompositionType", enumeration(draft.composition)),
245 ("UsageType", enumeration(draft.usage)),
246 ("PredefinedType", enumeration(predefined_type)),
247 ];
248 stage(tx, model, kind.type_name, values, Some(owner_history)).map_err(FacilityError::Authoring)
249}
250
251fn check_facility(
253 kind: Facility,
254 global_id: &str,
255 predefined_type: Option<&str>,
256 draft: &FacilityDraft<'_>,
257) -> FacilityResult<()> {
258 let entity = kind.type_name;
259 if Guid::parse(global_id).is_none() {
260 return Err(FacilityError::MalformedGuid {
261 entity,
262 offered: global_id.to_owned(),
263 });
264 }
265 match (kind.usage_slot, draft.usage) {
266 (Some(_), Some(token)) => {
267 if !USAGE_TOKENS.contains(&token) {
268 return Err(FacilityError::UnknownToken {
269 entity,
270 attribute: "UsageType",
271 offered: token.to_owned(),
272 });
273 }
274 if token == "USERDEFINED" && !names_itself(draft) {
275 return Err(FacilityError::UserDefinedWithoutObjectType {
276 entity,
277 attribute: "UsageType",
278 });
279 }
280 }
281 (Some(_), None) => return Err(FacilityError::MissingUsageType { entity }),
282 (None, Some(_)) => return Err(FacilityError::UnexpectedUsageType { entity }),
283 (None, None) => {}
284 }
285 match (kind.predefined_slot, predefined_type) {
286 (Some(_), Some(token)) => {
287 if !kind.members.contains(&token) {
288 return Err(FacilityError::UnknownToken {
289 entity,
290 attribute: "PredefinedType",
291 offered: token.to_owned(),
292 });
293 }
294 if token == "USERDEFINED" && !names_itself(draft) {
295 return Err(FacilityError::UserDefinedWithoutObjectType {
296 entity,
297 attribute: "PredefinedType",
298 });
299 }
300 }
301 (None, Some(_)) => return Err(FacilityError::NoPredefinedType { entity }),
302 (Some(_), None) | (None, None) => {}
303 }
304 Ok(())
305}
306
307fn text(value: Option<&str>) -> Value {
308 value.map_or(Value::Null, |t| Value::Text(t.into()))
309}
310
311fn enumeration(value: Option<&str>) -> Value {
312 value.map_or(Value::Null, |t| Value::Enum(t.into()))
313}