1use ifc_model::{Entity, EntityId, Transaction, Value};
28
29use crate::error::GeometryError;
30use crate::solid::swept::{
31 directrix_slot, disk_slot, extruded_slot, revolved_slot, swept_area_slot,
32};
33
34use super::release::Release;
35use super::std_profile::positive;
36use super::{invalid, refs, require_finite};
37
38#[derive(Debug, Default, Clone, Copy)]
49pub struct SweepTrim {
50 pub start: Option<f64>,
52 pub end: Option<f64>,
54}
55
56#[derive(Debug, Clone, Copy)]
58pub(super) enum ParamForm {
59 Bare,
61 Select,
63 Release(Release),
65}
66
67fn put_param(
69 attrs: &mut [Value],
70 index: usize,
71 value: Option<f64>,
72 form: ParamForm,
73 type_name: &'static str,
74 attribute: &'static str,
75) -> Result<(), GeometryError> {
76 if let Some(value) = value {
77 require_finite(type_name, attribute, &[value])?;
78 }
79 attrs[index] = match (form, value) {
80 (ParamForm::Release(release), value) => release.parameter(type_name, attribute, value)?,
81 (_, None) => Value::Null,
82 (ParamForm::Bare, Some(value)) => Value::Real(value),
83 (ParamForm::Select, Some(value)) => Value::Typed {
84 type_name: "IFCPARAMETERVALUE".into(),
85 value: Box::new(Value::Real(value)),
86 },
87 };
88 Ok(())
89}
90
91pub fn extruded_area_solid_tapered(
97 tx: &mut Transaction,
98 swept_area: EntityId,
99 position: Option<EntityId>,
100 extruded_direction: EntityId,
101 depth: f64,
102 end_swept_area: EntityId,
103) -> Result<EntityId, GeometryError> {
104 const T: &str = "IFCEXTRUDEDAREASOLIDTAPERED";
105 positive(T, "Depth", depth)?;
106 let mut attrs = vec![Value::Null; 5];
107 attrs[swept_area_slot::SWEPT_AREA] = Value::Ref(swept_area);
108 attrs[swept_area_slot::POSITION] = position.map_or(Value::Null, Value::Ref);
109 attrs[extruded_slot::EXTRUDED_DIRECTION] = Value::Ref(extruded_direction);
110 attrs[extruded_slot::DEPTH] = Value::Real(depth);
111 attrs[extruded_slot::END_SWEPT_AREA] = Value::Ref(end_swept_area);
112 Ok(tx.create(Entity::new(T, attrs)))
113}
114
115pub fn revolved_area_solid_tapered(
125 tx: &mut Transaction,
126 swept_area: EntityId,
127 position: Option<EntityId>,
128 axis: EntityId,
129 angle: f64,
130 end_swept_area: EntityId,
131) -> Result<EntityId, GeometryError> {
132 const T: &str = "IFCREVOLVEDAREASOLIDTAPERED";
133 require_finite(T, "Angle", &[angle])?;
134 let mut attrs = vec![Value::Null; 5];
135 attrs[swept_area_slot::SWEPT_AREA] = Value::Ref(swept_area);
136 attrs[swept_area_slot::POSITION] = position.map_or(Value::Null, Value::Ref);
137 attrs[revolved_slot::AXIS] = Value::Ref(axis);
138 attrs[revolved_slot::ANGLE] = Value::Real(angle);
139 attrs[revolved_slot::END_SWEPT_AREA] = Value::Ref(end_swept_area);
140 Ok(tx.create(Entity::new(T, attrs)))
141}
142
143pub(super) fn directrix_attrs(
149 type_name: &'static str,
150 swept_area: EntityId,
151 position: Option<EntityId>,
152 directrix: EntityId,
153 trim: SweepTrim,
154 form: ParamForm,
155) -> Result<Vec<Value>, GeometryError> {
156 let mut attrs = vec![Value::Null; 6];
157 attrs[swept_area_slot::SWEPT_AREA] = Value::Ref(swept_area);
158 attrs[swept_area_slot::POSITION] = position.map_or(Value::Null, Value::Ref);
159 attrs[directrix_slot::DIRECTRIX] = Value::Ref(directrix);
160 put_param(
161 &mut attrs,
162 directrix_slot::START_PARAM,
163 trim.start,
164 form,
165 type_name,
166 "StartParam",
167 )?;
168 put_param(
169 &mut attrs,
170 directrix_slot::END_PARAM,
171 trim.end,
172 form,
173 type_name,
174 "EndParam",
175 )?;
176 Ok(attrs)
177}
178
179pub fn swept_disk_solid_polygonal(
194 tx: &mut Transaction,
195 directrix: EntityId,
196 radius: f64,
197 inner_radius: Option<f64>,
198 trim: SweepTrim,
199 fillet_radius: Option<f64>,
200) -> Result<EntityId, GeometryError> {
201 const T: &str = "IFCSWEPTDISKSOLIDPOLYGONAL";
202 let mut attrs = disk_attrs(T, directrix, radius, inner_radius, trim, ParamForm::Bare)?;
203 attrs.push(Value::Null);
204 if let Some(fillet) = fillet_radius {
205 require_finite(T, "FilletRadius", &[fillet])?;
206 if fillet < 0.0 {
207 return Err(invalid(
208 T,
209 "FilletRadius",
210 format!("expected a non-negative length, got {fillet}"),
211 ));
212 }
213 attrs[disk_slot::FILLET_RADIUS] = Value::Real(fillet);
214 }
215 Ok(tx.create(Entity::new(T, attrs)))
216}
217
218pub(super) fn disk_attrs(
220 type_name: &'static str,
221 directrix: EntityId,
222 radius: f64,
223 inner_radius: Option<f64>,
224 trim: SweepTrim,
225 form: ParamForm,
226) -> Result<Vec<Value>, GeometryError> {
227 positive(type_name, "Radius", radius)?;
228 let mut attrs = vec![Value::Null; 5];
229 attrs[disk_slot::DIRECTRIX] = Value::Ref(directrix);
230 attrs[disk_slot::RADIUS] = Value::Real(radius);
231 if let Some(inner) = inner_radius {
232 positive(type_name, "InnerRadius", inner)?;
233 if inner >= radius {
237 return Err(invalid(
238 type_name,
239 "InnerRadius",
240 format!("{inner} is not below the outer radius {radius}"),
241 ));
242 }
243 attrs[disk_slot::INNER_RADIUS] = Value::Real(inner);
244 }
245 put_param(
247 &mut attrs,
248 disk_slot::START_PARAM,
249 trim.start,
250 form,
251 type_name,
252 "StartParam",
253 )?;
254 put_param(
255 &mut attrs,
256 disk_slot::END_PARAM,
257 trim.end,
258 form,
259 type_name,
260 "EndParam",
261 )?;
262 Ok(attrs)
263}
264
265pub fn surface_of_linear_extrusion(
275 tx: &mut Transaction,
276 swept_curve: EntityId,
277 position: Option<EntityId>,
278 extruded_direction: EntityId,
279 depth: f64,
280) -> Result<EntityId, GeometryError> {
281 const T: &str = "IFCSURFACEOFLINEAREXTRUSION";
282 require_finite(T, "Depth", &[depth])?;
283 let attrs = vec![
284 Value::Ref(swept_curve),
285 position.map_or(Value::Null, Value::Ref),
286 Value::Ref(extruded_direction),
287 Value::Real(depth),
288 ];
289 Ok(tx.create(Entity::new(T, attrs)))
290}
291
292pub fn surface_of_revolution(
297 tx: &mut Transaction,
298 swept_curve: EntityId,
299 position: Option<EntityId>,
300 axis_position: EntityId,
301) -> EntityId {
302 let attrs = vec![
303 Value::Ref(swept_curve),
304 position.map_or(Value::Null, Value::Ref),
305 Value::Ref(axis_position),
306 ];
307 tx.create(Entity::new("IFCSURFACEOFREVOLUTION", attrs))
308}
309
310pub fn plane(tx: &mut Transaction, position: EntityId) -> EntityId {
312 tx.create(Entity::new("IFCPLANE", vec![Value::Ref(position)]))
313}
314
315pub fn cylindrical_surface(
321 tx: &mut Transaction,
322 position: EntityId,
323 radius: f64,
324) -> Result<EntityId, GeometryError> {
325 const T: &str = "IFCCYLINDRICALSURFACE";
326 positive(T, "Radius", radius)?;
327 let attrs = vec![Value::Ref(position), Value::Real(radius)];
328 Ok(tx.create(Entity::new(T, attrs)))
329}
330
331pub fn axis1_placement(
336 tx: &mut Transaction,
337 location: EntityId,
338 axis: Option<EntityId>,
339) -> EntityId {
340 let attrs = vec![Value::Ref(location), axis.map_or(Value::Null, Value::Ref)];
341 tx.create(Entity::new("IFCAXIS1PLACEMENT", attrs))
342}
343
344pub fn directrix_derived_reference_swept_area_solid(
358 tx: &mut Transaction,
359 swept_area: EntityId,
360 position: Option<EntityId>,
361 directrix: EntityId,
362 trim: SweepTrim,
363 fixed_reference: EntityId,
364) -> Result<EntityId, GeometryError> {
365 const T: &str = "IFCDIRECTRIXDERIVEDREFERENCESWEPTAREASOLID";
366 let mut attrs = directrix_attrs(T, swept_area, position, directrix, trim, ParamForm::Select)?;
367 attrs[directrix_slot::FIXED_REFERENCE] = Value::Ref(fixed_reference);
368 Ok(tx.create(Entity::new(T, attrs)))
369}
370
371#[derive(Debug, Clone, Copy, PartialEq, Eq)]
380pub enum SectionedKind {
381 SolidHorizontal,
383 Surface,
385}
386
387pub fn sectioned(
399 tx: &mut Transaction,
400 kind: SectionedKind,
401 directrix: EntityId,
402 cross_sections: &[EntityId],
403 cross_section_positions: &[EntityId],
404) -> Result<EntityId, GeometryError> {
405 let entity = match kind {
406 SectionedKind::SolidHorizontal => "IFCSECTIONEDSOLIDHORIZONTAL",
407 SectionedKind::Surface => "IFCSECTIONEDSURFACE",
408 };
409 if cross_sections.len() < 2 {
410 return Err(invalid(entity, "CrossSections", "expected LIST [2:?]"));
411 }
412 if cross_section_positions.len() < 2 {
413 return Err(invalid(
414 entity,
415 "CrossSectionPositions",
416 "expected LIST [2:?]",
417 ));
418 }
419 if cross_sections.len() != cross_section_positions.len() {
420 return Err(invalid(
421 entity,
422 "CrossSectionPositions",
423 format!(
424 "expected one position per section: {} sections, {} positions",
425 cross_sections.len(),
426 cross_section_positions.len()
427 ),
428 ));
429 }
430 let attrs = match kind {
431 SectionedKind::SolidHorizontal => vec![
432 Value::Ref(directrix),
433 refs(cross_sections),
434 refs(cross_section_positions),
435 ],
436 SectionedKind::Surface => vec![
437 Value::Ref(directrix),
438 refs(cross_section_positions),
439 refs(cross_sections),
440 ],
441 };
442 Ok(tx.create(Entity::new(entity, attrs)))
443}