1use ifc_model::{Entity, EntityId, Transaction, Value};
13
14use crate::error::{GeorefError, GeorefResult};
15
16mod operation;
17
18pub use operation::{
19 create_geographic_crs, create_map_conversion, create_map_conversion_scaled,
20 create_rigid_operation, create_well_known_text, GeographicCrsDraft, MapConversionDraft,
21};
22
23fn invalid(entity: &'static str, attribute: &'static str, value: impl Into<String>) -> GeorefError {
24 GeorefError::AuthoringInvalid {
25 entity,
26 attribute,
27 value: value.into(),
28 }
29}
30
31fn optional_text(value: Option<&str>) -> Value {
32 value.map_or(Value::Null, |t| Value::Text(t.into()))
33}
34
35pub fn create_direction(tx: &mut Transaction, ratios: &[f64]) -> GeorefResult<EntityId> {
43 if !(2..=3).contains(&ratios.len()) {
44 return Err(invalid(
45 "IFCDIRECTION",
46 "DirectionRatios",
47 format!("{} ratios", ratios.len()),
48 ));
49 }
50 if ratios.iter().any(|r| !r.is_finite()) {
51 return Err(invalid("IFCDIRECTION", "DirectionRatios", "not finite"));
52 }
53 if ratios.iter().all(|r| *r == 0.0) {
54 return Err(invalid("IFCDIRECTION", "DirectionRatios", "zero length"));
55 }
56 Ok(tx.create(Entity::new(
57 "IFCDIRECTION",
58 vec![Value::List(
59 ratios.iter().copied().map(Value::Real).collect(),
60 )],
61 )))
62}
63
64#[derive(Debug, Clone, Copy, Default)]
66pub struct ProjectedCrsDraft<'a> {
67 pub name: &'a str,
69 pub description: Option<&'a str>,
71 pub geodetic_datum: Option<&'a str>,
73 pub vertical_datum: Option<&'a str>,
75 pub map_projection: Option<&'a str>,
77 pub map_zone: Option<&'a str>,
79 pub map_unit: Option<EntityId>,
81}
82
83pub fn create_projected_crs(
93 tx: &mut Transaction,
94 draft: ProjectedCrsDraft<'_>,
95) -> GeorefResult<EntityId> {
96 if draft.name.trim().is_empty() {
97 return Err(invalid("IFCPROJECTEDCRS", "Name", draft.name));
98 }
99 Ok(tx.create(Entity::new(
100 "IFCPROJECTEDCRS",
101 vec![
102 Value::Text(draft.name.into()),
103 optional_text(draft.description),
104 optional_text(draft.geodetic_datum),
105 optional_text(draft.vertical_datum),
106 optional_text(draft.map_projection),
107 optional_text(draft.map_zone),
108 draft.map_unit.map_or(Value::Null, Value::Ref),
109 ],
110 )))
111}
112
113pub fn create_representation_context(
120 tx: &mut Transaction,
121 context_type: Option<&str>,
122 dimension: i64,
123 precision: Option<f64>,
124 world_coordinate_system: EntityId,
125 true_north: Option<EntityId>,
126) -> GeorefResult<EntityId> {
127 if !(1..=3).contains(&dimension) {
128 return Err(invalid(
129 "IFCGEOMETRICREPRESENTATIONCONTEXT",
130 "CoordinateSpaceDimension",
131 dimension.to_string(),
132 ));
133 }
134 if precision.is_some_and(|p| !p.is_finite()) {
135 return Err(invalid(
136 "IFCGEOMETRICREPRESENTATIONCONTEXT",
137 "Precision",
138 "not finite",
139 ));
140 }
141 Ok(tx.create(Entity::new(
142 "IFCGEOMETRICREPRESENTATIONCONTEXT",
143 vec![
144 Value::Null,
146 optional_text(context_type),
147 Value::Integer(dimension),
148 precision.map_or(Value::Null, Value::Real),
149 Value::Ref(world_coordinate_system),
150 true_north.map_or(Value::Null, Value::Ref),
151 ],
152 )))
153}
154
155pub fn create_representation_subcontext(
170 tx: &mut Transaction,
171 parent: EntityId,
172 parent_is_subcontext: bool,
173 context_identifier: Option<&str>,
174 target_view: &str,
175 user_defined_target_view: Option<&str>,
176) -> GeorefResult<EntityId> {
177 if parent_is_subcontext {
178 return Err(invalid(
179 "IFCGEOMETRICREPRESENTATIONSUBCONTEXT",
180 "ParentContext",
181 "a subcontext cannot parent a subcontext",
182 ));
183 }
184 if target_view.trim().is_empty() {
185 return Err(invalid(
186 "IFCGEOMETRICREPRESENTATIONSUBCONTEXT",
187 "TargetView",
188 target_view,
189 ));
190 }
191 let named = user_defined_target_view.is_some_and(|v| !v.trim().is_empty());
192 if target_view == "USERDEFINED" && !named {
193 return Err(invalid(
194 "IFCGEOMETRICREPRESENTATIONSUBCONTEXT",
195 "UserDefinedTargetView",
196 "required when TargetView is USERDEFINED",
197 ));
198 }
199 Ok(tx.create(Entity::new(
200 "IFCGEOMETRICREPRESENTATIONSUBCONTEXT",
201 vec![
202 optional_text(context_identifier),
203 Value::Null,
204 Value::Derived,
207 Value::Derived,
208 Value::Derived,
209 Value::Derived,
210 Value::Ref(parent),
211 Value::Null,
212 Value::Enum(target_view.into()),
213 optional_text(user_defined_target_view),
214 ],
215 )))
216}