1use ifc_model::{Entity, EntityId, Transaction, Value};
20
21use crate::error::GeometryError;
22use crate::resource::operator::slot;
23
24use super::{invalid, refs, require_finite};
25
26#[derive(Debug, Default, Clone, Copy)]
31pub struct Transform {
32 pub axis1: Option<EntityId>,
34 pub axis2: Option<EntityId>,
36 pub scale: Option<f64>,
38}
39
40fn check_scale(
42 type_name: &'static str,
43 attribute: &'static str,
44 scale: Option<f64>,
45) -> Result<(), GeometryError> {
46 let Some(scale) = scale else {
47 return Ok(());
49 };
50 require_finite(type_name, attribute, &[scale])?;
51 if scale <= 0.0 {
52 return Err(invalid(
53 type_name,
54 attribute,
55 format!("expected a scale above zero, got {scale}"),
56 ));
57 }
58 Ok(())
59}
60
61fn base(
63 type_name: &'static str,
64 width: usize,
65 local_origin: EntityId,
66 transform: Transform,
67) -> Result<Vec<Value>, GeometryError> {
68 check_scale(type_name, "Scale", transform.scale)?;
69 let mut attrs = vec![Value::Null; width];
70 attrs[slot::AXIS1] = transform.axis1.map_or(Value::Null, Value::Ref);
71 attrs[slot::AXIS2] = transform.axis2.map_or(Value::Null, Value::Ref);
72 attrs[slot::LOCAL_ORIGIN] = Value::Ref(local_origin);
73 attrs[slot::SCALE] = transform.scale.map_or(Value::Null, Value::Real);
74 Ok(attrs)
75}
76
77pub fn transformation_operator_2d(
83 tx: &mut Transaction,
84 local_origin: EntityId,
85 transform: Transform,
86) -> Result<EntityId, GeometryError> {
87 const T: &str = "IFCCARTESIANTRANSFORMATIONOPERATOR2D";
88 let attrs = base(T, 4, local_origin, transform)?;
89 Ok(tx.create(Entity::new(T, attrs)))
90}
91
92pub fn transformation_operator_2d_non_uniform(
101 tx: &mut Transaction,
102 local_origin: EntityId,
103 transform: Transform,
104 scale2: Option<f64>,
105) -> Result<EntityId, GeometryError> {
106 const T: &str = "IFCCARTESIANTRANSFORMATIONOPERATOR2DNONUNIFORM";
107 check_scale(T, "Scale2", scale2)?;
108 let mut attrs = base(T, 5, local_origin, transform)?;
109 attrs[slot::SCALE2_2D] = scale2.map_or(Value::Null, Value::Real);
110 Ok(tx.create(Entity::new(T, attrs)))
111}
112
113pub fn transformation_operator_3d(
119 tx: &mut Transaction,
120 local_origin: EntityId,
121 transform: Transform,
122 axis3: Option<EntityId>,
123) -> Result<EntityId, GeometryError> {
124 const T: &str = "IFCCARTESIANTRANSFORMATIONOPERATOR3D";
125 let mut attrs = base(T, 5, local_origin, transform)?;
126 attrs[slot::AXIS3] = axis3.map_or(Value::Null, Value::Ref);
127 Ok(tx.create(Entity::new(T, attrs)))
128}
129
130pub fn transformation_operator_3d_non_uniform(
139 tx: &mut Transaction,
140 local_origin: EntityId,
141 transform: Transform,
142 axis3: Option<EntityId>,
143 scale2: Option<f64>,
144 scale3: Option<f64>,
145) -> Result<EntityId, GeometryError> {
146 const T: &str = "IFCCARTESIANTRANSFORMATIONOPERATOR3DNONUNIFORM";
147 check_scale(T, "Scale2", scale2)?;
148 check_scale(T, "Scale3", scale3)?;
149 let mut attrs = base(T, 7, local_origin, transform)?;
150 attrs[slot::AXIS3] = axis3.map_or(Value::Null, Value::Ref);
151 attrs[slot::SCALE2_3D] = scale2.map_or(Value::Null, Value::Real);
152 attrs[slot::SCALE3_3D] = scale3.map_or(Value::Null, Value::Real);
153 Ok(tx.create(Entity::new(T, attrs)))
154}
155
156pub fn representation_map(
162 tx: &mut Transaction,
163 mapping_origin: EntityId,
164 mapped_representation: EntityId,
165) -> EntityId {
166 let attrs = vec![
167 Value::Ref(mapping_origin),
168 Value::Ref(mapped_representation),
169 ];
170 tx.create(Entity::new("IFCREPRESENTATIONMAP", attrs))
171}
172
173pub fn mapped_item(
178 tx: &mut Transaction,
179 mapping_source: EntityId,
180 mapping_target: EntityId,
181) -> EntityId {
182 let attrs = vec![Value::Ref(mapping_source), Value::Ref(mapping_target)];
183 tx.create(Entity::new("IFCMAPPEDITEM", attrs))
184}
185
186pub fn topology_representation(
195 tx: &mut Transaction,
196 context: EntityId,
197 identifier: Option<&str>,
198 representation_type: Option<&str>,
199 items: &[EntityId],
200) -> Result<EntityId, GeometryError> {
201 const T: &str = "IFCTOPOLOGYREPRESENTATION";
202 if items.is_empty() {
203 return Err(invalid(T, "Items", "expected at least one item"));
204 }
205 let attrs = vec![
206 Value::Ref(context),
207 identifier.map_or(Value::Null, |v| Value::Text(v.into())),
208 representation_type.map_or(Value::Null, |v| Value::Text(v.into())),
209 refs(items),
210 ];
211 Ok(tx.create(Entity::new(T, attrs)))
212}
213
214pub fn shape_aspect(
230 tx: &mut Transaction,
231 shape_representations: &[EntityId],
232 name: Option<&str>,
233 description: Option<&str>,
234 product_definitional: Option<bool>,
235 part_of_product_definition_shape: Option<EntityId>,
236) -> Result<EntityId, GeometryError> {
237 const ENTITY: &str = "IFCSHAPEASPECT";
238 if shape_representations.is_empty() {
239 return Err(invalid(
240 ENTITY,
241 "ShapeRepresentations",
242 "expected at least one representation, per LIST [1:?]",
243 ));
244 }
245 let attrs = vec![
246 refs(shape_representations),
247 name.map_or(Value::Null, |v| Value::Text(v.into())),
248 description.map_or(Value::Null, |v| Value::Text(v.into())),
249 product_definitional.map_or(Value::LogicalUnknown, Value::Bool),
250 part_of_product_definition_shape.map_or(Value::Null, Value::Ref),
251 ];
252 Ok(tx.create(Entity::new(ENTITY, attrs)))
253}