1use ifc_model::{Entity, EntityId, Transaction, Value};
22
23use crate::error::GeometryError;
24use crate::solid::swept::{
25 directrix_slot, disk_slot, extruded_slot, revolved_slot, swept_area_slot,
26};
27
28use super::std_profile::positive;
29use super::{invalid, refs, require_finite};
30
31#[derive(Debug, Default, Clone, Copy)]
36pub struct SweepTrim {
37 pub start: Option<f64>,
39 pub end: Option<f64>,
41}
42
43fn put_param(
45 attrs: &mut [Value],
46 index: usize,
47 value: Option<f64>,
48 type_name: &'static str,
49 attribute: &'static str,
50) -> Result<(), GeometryError> {
51 if let Some(value) = value {
52 require_finite(type_name, attribute, &[value])?;
53 attrs[index] = Value::Typed {
54 type_name: "IFCPARAMETERVALUE".into(),
55 value: Box::new(Value::Real(value)),
56 };
57 }
58 Ok(())
59}
60
61pub fn extruded_area_solid_tapered(
67 tx: &mut Transaction,
68 swept_area: EntityId,
69 position: Option<EntityId>,
70 extruded_direction: EntityId,
71 depth: f64,
72 end_swept_area: EntityId,
73) -> Result<EntityId, GeometryError> {
74 const T: &str = "IFCEXTRUDEDAREASOLIDTAPERED";
75 positive(T, "Depth", depth)?;
76 let mut attrs = vec![Value::Null; 5];
77 attrs[swept_area_slot::SWEPT_AREA] = Value::Ref(swept_area);
78 attrs[swept_area_slot::POSITION] = position.map_or(Value::Null, Value::Ref);
79 attrs[extruded_slot::EXTRUDED_DIRECTION] = Value::Ref(extruded_direction);
80 attrs[extruded_slot::DEPTH] = Value::Real(depth);
81 attrs[extruded_slot::END_SWEPT_AREA] = Value::Ref(end_swept_area);
82 Ok(tx.create(Entity::new(T, attrs)))
83}
84
85pub fn revolved_area_solid_tapered(
95 tx: &mut Transaction,
96 swept_area: EntityId,
97 position: Option<EntityId>,
98 axis: EntityId,
99 angle: f64,
100 end_swept_area: EntityId,
101) -> Result<EntityId, GeometryError> {
102 const T: &str = "IFCREVOLVEDAREASOLIDTAPERED";
103 require_finite(T, "Angle", &[angle])?;
104 let mut attrs = vec![Value::Null; 5];
105 attrs[swept_area_slot::SWEPT_AREA] = Value::Ref(swept_area);
106 attrs[swept_area_slot::POSITION] = position.map_or(Value::Null, Value::Ref);
107 attrs[revolved_slot::AXIS] = Value::Ref(axis);
108 attrs[revolved_slot::ANGLE] = Value::Real(angle);
109 attrs[revolved_slot::END_SWEPT_AREA] = Value::Ref(end_swept_area);
110 Ok(tx.create(Entity::new(T, attrs)))
111}
112
113pub fn surface_curve_swept_area_solid(
122 tx: &mut Transaction,
123 swept_area: EntityId,
124 position: Option<EntityId>,
125 directrix: EntityId,
126 trim: SweepTrim,
127 reference_surface: EntityId,
128) -> Result<EntityId, GeometryError> {
129 const T: &str = "IFCSURFACECURVESWEPTAREASOLID";
130 let mut attrs = directrix_attrs(T, swept_area, position, directrix, trim)?;
131 attrs[directrix_slot::REFERENCE_SURFACE] = Value::Ref(reference_surface);
132 Ok(tx.create(Entity::new(T, attrs)))
133}
134
135pub fn fixed_reference_swept_area_solid(
144 tx: &mut Transaction,
145 swept_area: EntityId,
146 position: Option<EntityId>,
147 directrix: EntityId,
148 trim: SweepTrim,
149 fixed_reference: EntityId,
150) -> Result<EntityId, GeometryError> {
151 const T: &str = "IFCFIXEDREFERENCESWEPTAREASOLID";
152 let mut attrs = directrix_attrs(T, swept_area, position, directrix, trim)?;
153 attrs[directrix_slot::FIXED_REFERENCE] = Value::Ref(fixed_reference);
154 Ok(tx.create(Entity::new(T, attrs)))
155}
156
157fn directrix_attrs(
162 type_name: &'static str,
163 swept_area: EntityId,
164 position: Option<EntityId>,
165 directrix: EntityId,
166 trim: SweepTrim,
167) -> Result<Vec<Value>, GeometryError> {
168 let mut attrs = vec![Value::Null; 6];
169 attrs[swept_area_slot::SWEPT_AREA] = Value::Ref(swept_area);
170 attrs[swept_area_slot::POSITION] = position.map_or(Value::Null, Value::Ref);
171 attrs[directrix_slot::DIRECTRIX] = Value::Ref(directrix);
172 put_param(
173 &mut attrs,
174 directrix_slot::START_PARAM,
175 trim.start,
176 type_name,
177 "StartParam",
178 )?;
179 put_param(
180 &mut attrs,
181 directrix_slot::END_PARAM,
182 trim.end,
183 type_name,
184 "EndParam",
185 )?;
186 Ok(attrs)
187}
188
189pub fn swept_disk_solid(
200 tx: &mut Transaction,
201 directrix: EntityId,
202 radius: f64,
203 inner_radius: Option<f64>,
204 trim: SweepTrim,
205) -> Result<EntityId, GeometryError> {
206 const T: &str = "IFCSWEPTDISKSOLID";
207 let attrs = disk_attrs(T, directrix, radius, inner_radius, trim)?;
208 Ok(tx.create(Entity::new(T, attrs)))
209}
210
211pub fn swept_disk_solid_polygonal(
222 tx: &mut Transaction,
223 directrix: EntityId,
224 radius: f64,
225 inner_radius: Option<f64>,
226 trim: SweepTrim,
227 fillet_radius: Option<f64>,
228) -> Result<EntityId, GeometryError> {
229 const T: &str = "IFCSWEPTDISKSOLIDPOLYGONAL";
230 let mut attrs = disk_attrs(T, directrix, radius, inner_radius, trim)?;
231 attrs.push(Value::Null);
232 if let Some(fillet) = fillet_radius {
233 require_finite(T, "FilletRadius", &[fillet])?;
234 if fillet < 0.0 {
235 return Err(invalid(
236 T,
237 "FilletRadius",
238 format!("expected a non-negative length, got {fillet}"),
239 ));
240 }
241 attrs[disk_slot::FILLET_RADIUS] = Value::Real(fillet);
242 }
243 Ok(tx.create(Entity::new(T, attrs)))
244}
245
246fn disk_attrs(
248 type_name: &'static str,
249 directrix: EntityId,
250 radius: f64,
251 inner_radius: Option<f64>,
252 trim: SweepTrim,
253) -> Result<Vec<Value>, GeometryError> {
254 positive(type_name, "Radius", radius)?;
255 let mut attrs = vec![Value::Null; 5];
256 attrs[disk_slot::DIRECTRIX] = Value::Ref(directrix);
257 attrs[disk_slot::RADIUS] = Value::Real(radius);
258 if let Some(inner) = inner_radius {
259 positive(type_name, "InnerRadius", inner)?;
260 if inner >= radius {
264 return Err(invalid(
265 type_name,
266 "InnerRadius",
267 format!("{inner} is not below the outer radius {radius}"),
268 ));
269 }
270 attrs[disk_slot::INNER_RADIUS] = Value::Real(inner);
271 }
272 put_param(
273 &mut attrs,
274 disk_slot::START_PARAM,
275 trim.start,
276 type_name,
277 "StartParam",
278 )?;
279 put_param(
280 &mut attrs,
281 disk_slot::END_PARAM,
282 trim.end,
283 type_name,
284 "EndParam",
285 )?;
286 Ok(attrs)
287}
288
289pub fn surface_of_linear_extrusion(
299 tx: &mut Transaction,
300 swept_curve: EntityId,
301 position: Option<EntityId>,
302 extruded_direction: EntityId,
303 depth: f64,
304) -> Result<EntityId, GeometryError> {
305 const T: &str = "IFCSURFACEOFLINEAREXTRUSION";
306 require_finite(T, "Depth", &[depth])?;
307 let attrs = vec![
308 Value::Ref(swept_curve),
309 position.map_or(Value::Null, Value::Ref),
310 Value::Ref(extruded_direction),
311 Value::Real(depth),
312 ];
313 Ok(tx.create(Entity::new(T, attrs)))
314}
315
316pub fn surface_of_revolution(
321 tx: &mut Transaction,
322 swept_curve: EntityId,
323 position: Option<EntityId>,
324 axis_position: EntityId,
325) -> EntityId {
326 let attrs = vec![
327 Value::Ref(swept_curve),
328 position.map_or(Value::Null, Value::Ref),
329 Value::Ref(axis_position),
330 ];
331 tx.create(Entity::new("IFCSURFACEOFREVOLUTION", attrs))
332}
333
334pub fn plane(tx: &mut Transaction, position: EntityId) -> EntityId {
336 tx.create(Entity::new("IFCPLANE", vec![Value::Ref(position)]))
337}
338
339pub fn cylindrical_surface(
345 tx: &mut Transaction,
346 position: EntityId,
347 radius: f64,
348) -> Result<EntityId, GeometryError> {
349 const T: &str = "IFCCYLINDRICALSURFACE";
350 positive(T, "Radius", radius)?;
351 let attrs = vec![Value::Ref(position), Value::Real(radius)];
352 Ok(tx.create(Entity::new(T, attrs)))
353}
354
355pub fn axis1_placement(
360 tx: &mut Transaction,
361 location: EntityId,
362 axis: Option<EntityId>,
363) -> EntityId {
364 let attrs = vec![Value::Ref(location), axis.map_or(Value::Null, Value::Ref)];
365 tx.create(Entity::new("IFCAXIS1PLACEMENT", attrs))
366}
367
368pub fn directrix_derived_reference_swept_area_solid(
379 tx: &mut Transaction,
380 swept_area: EntityId,
381 position: Option<EntityId>,
382 directrix: EntityId,
383 trim: SweepTrim,
384 fixed_reference: EntityId,
385) -> Result<EntityId, GeometryError> {
386 const T: &str = "IFCDIRECTRIXDERIVEDREFERENCESWEPTAREASOLID";
387 let mut attrs = directrix_attrs(T, swept_area, position, directrix, trim)?;
388 attrs[directrix_slot::FIXED_REFERENCE] = Value::Ref(fixed_reference);
389 Ok(tx.create(Entity::new(T, attrs)))
390}
391
392#[derive(Debug, Clone, Copy, PartialEq, Eq)]
401pub enum SectionedKind {
402 SolidHorizontal,
404 Surface,
406}
407
408pub fn sectioned(
420 tx: &mut Transaction,
421 kind: SectionedKind,
422 directrix: EntityId,
423 cross_sections: &[EntityId],
424 cross_section_positions: &[EntityId],
425) -> Result<EntityId, GeometryError> {
426 let entity = match kind {
427 SectionedKind::SolidHorizontal => "IFCSECTIONEDSOLIDHORIZONTAL",
428 SectionedKind::Surface => "IFCSECTIONEDSURFACE",
429 };
430 if cross_sections.len() < 2 {
431 return Err(invalid(entity, "CrossSections", "expected LIST [2:?]"));
432 }
433 if cross_section_positions.len() < 2 {
434 return Err(invalid(
435 entity,
436 "CrossSectionPositions",
437 "expected LIST [2:?]",
438 ));
439 }
440 if cross_sections.len() != cross_section_positions.len() {
441 return Err(invalid(
442 entity,
443 "CrossSectionPositions",
444 format!(
445 "expected one position per section: {} sections, {} positions",
446 cross_sections.len(),
447 cross_section_positions.len()
448 ),
449 ));
450 }
451 let attrs = match kind {
452 SectionedKind::SolidHorizontal => vec![
453 Value::Ref(directrix),
454 refs(cross_sections),
455 refs(cross_section_positions),
456 ],
457 SectionedKind::Surface => vec![
458 Value::Ref(directrix),
459 refs(cross_section_positions),
460 refs(cross_sections),
461 ],
462 };
463 Ok(tx.create(Entity::new(entity, attrs)))
464}