1use ifc_model::guid::Guid;
20use ifc_model::{Entity, EntityId, Transaction, Value};
21
22use crate::error::GeometryError;
23use crate::resource::operator::slot;
24
25use super::{invalid, refs, require_finite};
26
27#[derive(Debug, Default, Clone, Copy)]
32pub struct Transform {
33 pub axis1: Option<EntityId>,
35 pub axis2: Option<EntityId>,
37 pub scale: Option<f64>,
39}
40
41fn check_scale(
43 type_name: &'static str,
44 attribute: &'static str,
45 scale: Option<f64>,
46) -> Result<(), GeometryError> {
47 let Some(scale) = scale else {
48 return Ok(());
50 };
51 require_finite(type_name, attribute, &[scale])?;
52 if scale <= 0.0 {
53 return Err(invalid(
54 type_name,
55 attribute,
56 format!("expected a scale above zero, got {scale}"),
57 ));
58 }
59 Ok(())
60}
61
62fn base(
64 type_name: &'static str,
65 width: usize,
66 local_origin: EntityId,
67 transform: Transform,
68) -> Result<Vec<Value>, GeometryError> {
69 check_scale(type_name, "Scale", transform.scale)?;
70 let mut attrs = vec![Value::Null; width];
71 attrs[slot::AXIS1] = transform.axis1.map_or(Value::Null, Value::Ref);
72 attrs[slot::AXIS2] = transform.axis2.map_or(Value::Null, Value::Ref);
73 attrs[slot::LOCAL_ORIGIN] = Value::Ref(local_origin);
74 attrs[slot::SCALE] = transform.scale.map_or(Value::Null, Value::Real);
75 Ok(attrs)
76}
77
78pub fn transformation_operator_2d(
84 tx: &mut Transaction,
85 local_origin: EntityId,
86 transform: Transform,
87) -> Result<EntityId, GeometryError> {
88 const T: &str = "IFCCARTESIANTRANSFORMATIONOPERATOR2D";
89 let attrs = base(T, 4, local_origin, transform)?;
90 Ok(tx.create(Entity::new(T, attrs)))
91}
92
93pub fn transformation_operator_2d_non_uniform(
102 tx: &mut Transaction,
103 local_origin: EntityId,
104 transform: Transform,
105 scale2: Option<f64>,
106) -> Result<EntityId, GeometryError> {
107 const T: &str = "IFCCARTESIANTRANSFORMATIONOPERATOR2DNONUNIFORM";
108 check_scale(T, "Scale2", scale2)?;
109 let mut attrs = base(T, 5, local_origin, transform)?;
110 attrs[slot::SCALE2_2D] = scale2.map_or(Value::Null, Value::Real);
111 Ok(tx.create(Entity::new(T, attrs)))
112}
113
114pub fn transformation_operator_3d(
120 tx: &mut Transaction,
121 local_origin: EntityId,
122 transform: Transform,
123 axis3: Option<EntityId>,
124) -> Result<EntityId, GeometryError> {
125 const T: &str = "IFCCARTESIANTRANSFORMATIONOPERATOR3D";
126 let mut attrs = base(T, 5, local_origin, transform)?;
127 attrs[slot::AXIS3] = axis3.map_or(Value::Null, Value::Ref);
128 Ok(tx.create(Entity::new(T, attrs)))
129}
130
131pub fn transformation_operator_3d_non_uniform(
140 tx: &mut Transaction,
141 local_origin: EntityId,
142 transform: Transform,
143 axis3: Option<EntityId>,
144 scale2: Option<f64>,
145 scale3: Option<f64>,
146) -> Result<EntityId, GeometryError> {
147 const T: &str = "IFCCARTESIANTRANSFORMATIONOPERATOR3DNONUNIFORM";
148 check_scale(T, "Scale2", scale2)?;
149 check_scale(T, "Scale3", scale3)?;
150 let mut attrs = base(T, 7, local_origin, transform)?;
151 attrs[slot::AXIS3] = axis3.map_or(Value::Null, Value::Ref);
152 attrs[slot::SCALE2_3D] = scale2.map_or(Value::Null, Value::Real);
153 attrs[slot::SCALE3_3D] = scale3.map_or(Value::Null, Value::Real);
154 Ok(tx.create(Entity::new(T, attrs)))
155}
156
157pub fn representation_map(
163 tx: &mut Transaction,
164 mapping_origin: EntityId,
165 mapped_representation: EntityId,
166) -> EntityId {
167 let attrs = vec![
168 Value::Ref(mapping_origin),
169 Value::Ref(mapped_representation),
170 ];
171 tx.create(Entity::new("IFCREPRESENTATIONMAP", attrs))
172}
173
174pub fn mapped_item(
179 tx: &mut Transaction,
180 mapping_source: EntityId,
181 mapping_target: EntityId,
182) -> EntityId {
183 let attrs = vec![Value::Ref(mapping_source), Value::Ref(mapping_target)];
184 tx.create(Entity::new("IFCMAPPEDITEM", attrs))
185}
186
187pub fn topology_representation(
196 tx: &mut Transaction,
197 context: EntityId,
198 identifier: Option<&str>,
199 representation_type: Option<&str>,
200 items: &[EntityId],
201) -> Result<EntityId, GeometryError> {
202 const T: &str = "IFCTOPOLOGYREPRESENTATION";
203 if items.is_empty() {
204 return Err(invalid(T, "Items", "expected at least one item"));
205 }
206 let attrs = vec![
207 Value::Ref(context),
208 identifier.map_or(Value::Null, |v| Value::Text(v.into())),
209 representation_type.map_or(Value::Null, |v| Value::Text(v.into())),
210 refs(items),
211 ];
212 Ok(tx.create(Entity::new(T, attrs)))
213}
214
215pub fn shape_aspect(
231 tx: &mut Transaction,
232 shape_representations: &[EntityId],
233 name: Option<&str>,
234 description: Option<&str>,
235 product_definitional: Option<bool>,
236 part_of_product_definition_shape: Option<EntityId>,
237) -> Result<EntityId, GeometryError> {
238 const ENTITY: &str = "IFCSHAPEASPECT";
239 if shape_representations.is_empty() {
240 return Err(invalid(
241 ENTITY,
242 "ShapeRepresentations",
243 "expected at least one representation, per LIST [1:?]",
244 ));
245 }
246 let attrs = vec![
247 refs(shape_representations),
248 name.map_or(Value::Null, |v| Value::Text(v.into())),
249 description.map_or(Value::Null, |v| Value::Text(v.into())),
250 product_definitional.map_or(Value::LogicalUnknown, Value::Bool),
251 part_of_product_definition_shape.map_or(Value::Null, Value::Ref),
252 ];
253 Ok(tx.create(Entity::new(ENTITY, attrs)))
254}
255
256pub fn grid(
269 tx: &mut Transaction,
270 global_id: &str,
271 placement: Option<EntityId>,
272 axes: (&[EntityId], &[EntityId], &[EntityId]),
273 predefined_type: Option<&str>,
274) -> Result<EntityId, GeometryError> {
275 const ENTITY: &str = "IFCGRID";
276 if Guid::parse(global_id).is_none() {
277 return Err(invalid(ENTITY, "GlobalId", global_id));
278 }
279 let (u_axes, v_axes, w_axes) = axes;
280 for (values, attribute) in [(u_axes, "UAxes"), (v_axes, "VAxes")] {
281 if values.is_empty() {
282 return Err(invalid(
283 ENTITY,
284 attribute,
285 "expected at least one axis, per LIST [1:?]",
286 ));
287 }
288 }
289 for (values, attribute) in [(u_axes, "UAxes"), (v_axes, "VAxes"), (w_axes, "WAxes")] {
290 let mut seen = std::collections::HashSet::new();
291 if let Some(repeat) = values.iter().find(|axis| !seen.insert(**axis)) {
292 return Err(invalid(
293 ENTITY,
294 attribute,
295 format!("axis {repeat:?} appears twice in a UNIQUE list"),
296 ));
297 }
298 }
299 let mut attrs = vec![Value::Null; 11];
300 attrs[0] = Value::Text(global_id.into());
301 attrs[5] = placement.map_or(Value::Null, Value::Ref);
302 attrs[7] = refs(u_axes);
303 attrs[8] = refs(v_axes);
304 if !w_axes.is_empty() {
305 attrs[9] = refs(w_axes);
306 }
307 attrs[10] = predefined_type.map_or(Value::Null, |t| Value::Enum(t.into()));
308 Ok(tx.create(Entity::new(ENTITY, attrs)))
309}