1use ifc_model::{Entity, EntityId, Transaction, Value};
19
20use crate::error::GeometryError;
21use crate::solid::swept::spine_slot;
22
23use super::{invalid, refs, require_finite};
24
25pub fn local_placement(
31 tx: &mut Transaction,
32 placement_rel_to: Option<EntityId>,
33 relative_placement: EntityId,
34) -> EntityId {
35 let attrs = vec![
36 placement_rel_to.map_or(Value::Null, Value::Ref),
37 Value::Ref(relative_placement),
38 ];
39 tx.create(Entity::new("IFCLOCALPLACEMENT", attrs))
40}
41
42pub fn grid_axis(
48 tx: &mut Transaction,
49 axis_tag: Option<&str>,
50 axis_curve: EntityId,
51 same_sense: bool,
52) -> EntityId {
53 let attrs = vec![
54 axis_tag.map_or(Value::Null, |t| Value::Text(t.into())),
55 Value::Ref(axis_curve),
56 Value::Bool(same_sense),
57 ];
58 tx.create(Entity::new("IFCGRIDAXIS", attrs))
59}
60
61pub fn virtual_grid_intersection(
69 tx: &mut Transaction,
70 intersecting_axes: &[EntityId],
71 offset_distances: &[f64],
72) -> Result<EntityId, GeometryError> {
73 const T: &str = "IFCVIRTUALGRIDINTERSECTION";
74 if intersecting_axes.len() != 2 {
75 return Err(invalid(
76 T,
77 "IntersectingAxes",
78 format!("expected exactly 2 axes, got {}", intersecting_axes.len()),
79 ));
80 }
81 if intersecting_axes[0] == intersecting_axes[1] {
82 return Err(invalid(
83 T,
84 "IntersectingAxes",
85 "the list is UNIQUE; an axis does not intersect itself",
86 ));
87 }
88 if offset_distances.len() < 2 || offset_distances.len() > 3 {
89 return Err(invalid(
90 T,
91 "OffsetDistances",
92 format!("expected 2 or 3 offsets, got {}", offset_distances.len()),
93 ));
94 }
95 require_finite(T, "OffsetDistances", offset_distances)?;
96 let attrs = vec![
97 refs(intersecting_axes),
98 Value::List(offset_distances.iter().copied().map(Value::Real).collect()),
99 ];
100 Ok(tx.create(Entity::new(T, attrs)))
101}
102
103pub fn grid_placement(
105 tx: &mut Transaction,
106 placement_rel_to: Option<EntityId>,
107 placement_location: EntityId,
108 placement_ref_direction: Option<EntityId>,
109) -> EntityId {
110 let attrs = vec![
111 placement_rel_to.map_or(Value::Null, Value::Ref),
112 Value::Ref(placement_location),
113 placement_ref_direction.map_or(Value::Null, Value::Ref),
114 ];
115 tx.create(Entity::new("IFCGRIDPLACEMENT", attrs))
116}
117
118#[derive(Debug, Clone, Copy, PartialEq, Eq)]
120pub enum ConnectionKind {
121 Point,
123 Curve,
125 Surface,
127 Volume,
129}
130
131impl ConnectionKind {
132 fn type_name(self) -> &'static str {
134 match self {
135 Self::Point => "IFCCONNECTIONPOINTGEOMETRY",
136 Self::Curve => "IFCCONNECTIONCURVEGEOMETRY",
137 Self::Surface => "IFCCONNECTIONSURFACEGEOMETRY",
138 Self::Volume => "IFCCONNECTIONVOLUMEGEOMETRY",
139 }
140 }
141}
142
143pub fn connection_geometry(
150 tx: &mut Transaction,
151 kind: ConnectionKind,
152 on_relating: EntityId,
153 on_related: Option<EntityId>,
154) -> EntityId {
155 let attrs = vec![
156 Value::Ref(on_relating),
157 on_related.map_or(Value::Null, Value::Ref),
158 ];
159 tx.create(Entity::new(kind.type_name(), attrs))
160}
161
162pub fn connection_point_eccentricity(
172 tx: &mut Transaction,
173 on_relating: EntityId,
174 on_related: Option<EntityId>,
175 eccentricity: [Option<f64>; 3],
176) -> Result<EntityId, GeometryError> {
177 const T: &str = "IFCCONNECTIONPOINTECCENTRICITY";
178 const NAMES: [&str; 3] = ["EccentricityInX", "EccentricityInY", "EccentricityInZ"];
179 let mut attrs = vec![Value::Null; 5];
180 attrs[0] = Value::Ref(on_relating);
181 attrs[1] = on_related.map_or(Value::Null, Value::Ref);
182 for (offset, value) in eccentricity.iter().enumerate() {
183 if let Some(value) = value {
184 require_finite(T, NAMES[offset], &[*value])?;
185 attrs[2 + offset] = Value::Real(*value);
186 }
187 }
188 Ok(tx.create(Entity::new(T, attrs)))
189}
190
191pub fn point_on_curve(
197 tx: &mut Transaction,
198 basis_curve: EntityId,
199 point_parameter: f64,
200) -> Result<EntityId, GeometryError> {
201 const T: &str = "IFCPOINTONCURVE";
202 require_finite(T, "PointParameter", &[point_parameter])?;
203 let attrs = vec![Value::Ref(basis_curve), parameter(point_parameter)];
204 Ok(tx.create(Entity::new(T, attrs)))
205}
206
207pub fn point_on_surface(
213 tx: &mut Transaction,
214 basis_surface: EntityId,
215 u: f64,
216 v: f64,
217) -> Result<EntityId, GeometryError> {
218 const T: &str = "IFCPOINTONSURFACE";
219 require_finite(T, "PointParameterU", &[u, v])?;
220 let attrs = vec![Value::Ref(basis_surface), parameter(u), parameter(v)];
221 Ok(tx.create(Entity::new(T, attrs)))
222}
223
224fn parameter(value: f64) -> Value {
229 Value::Real(value)
230}
231
232pub fn geometric_set(
242 tx: &mut Transaction,
243 curves_only: bool,
244 elements: &[EntityId],
245) -> Result<EntityId, GeometryError> {
246 let type_name = if curves_only {
247 "IFCGEOMETRICCURVESET"
248 } else {
249 "IFCGEOMETRICSET"
250 };
251 if elements.is_empty() {
252 return Err(invalid(
253 type_name,
254 "Elements",
255 "expected at least one element",
256 ));
257 }
258 Ok(tx.create(Entity::new(type_name, vec![refs(elements)])))
259}
260
261pub fn path(tx: &mut Transaction, edge_list: &[EntityId]) -> Result<EntityId, GeometryError> {
268 const T: &str = "IFCPATH";
269 if edge_list.is_empty() {
270 return Err(invalid(T, "EdgeList", "expected at least one edge"));
271 }
272 let mut seen = edge_list.to_vec();
273 seen.sort_unstable();
274 seen.dedup();
275 if seen.len() != edge_list.len() {
276 return Err(invalid(
277 T,
278 "EdgeList",
279 "the edge list is UNIQUE; a path does not repeat an edge",
280 ));
281 }
282 Ok(tx.create(Entity::new(T, vec![refs(edge_list)])))
283}
284
285pub fn boolean_clipping_result(
291 tx: &mut Transaction,
292 first_operand: EntityId,
293 second_operand: EntityId,
294) -> EntityId {
295 let attrs = vec![
296 Value::Enum("DIFFERENCE".into()),
297 Value::Ref(first_operand),
298 Value::Ref(second_operand),
299 ];
300 tx.create(Entity::new("IFCBOOLEANCLIPPINGRESULT", attrs))
301}
302
303pub fn sectioned_spine(
312 tx: &mut Transaction,
313 spine_curve: EntityId,
314 cross_sections: &[EntityId],
315 cross_section_positions: &[EntityId],
316) -> Result<EntityId, GeometryError> {
317 const T: &str = "IFCSECTIONEDSPINE";
318 if cross_sections.len() < 2 {
319 return Err(invalid(
320 T,
321 "CrossSections",
322 format!(
323 "expected at least 2 cross sections, got {}",
324 cross_sections.len()
325 ),
326 ));
327 }
328 if cross_sections.len() != cross_section_positions.len() {
329 return Err(invalid(
330 T,
331 "CrossSectionPositions",
332 format!(
333 "{} positions for {} cross sections",
334 cross_section_positions.len(),
335 cross_sections.len()
336 ),
337 ));
338 }
339 let mut attrs = vec![Value::Null; 3];
340 attrs[spine_slot::SPINE_CURVE] = Value::Ref(spine_curve);
341 attrs[spine_slot::CROSS_SECTIONS] = refs(cross_sections);
342 attrs[spine_slot::CROSS_SECTION_POSITIONS] = refs(cross_section_positions);
343 Ok(tx.create(Entity::new(T, attrs)))
344}