use ifc_model::{Entity, EntityId, Transaction, Value};
use crate::error::GeometryError;
use crate::solid::swept::{
directrix_slot, disk_slot, extruded_slot, revolved_slot, swept_area_slot,
};
use super::release::Release;
use super::std_profile::positive;
use super::{invalid, refs, require_finite};
#[derive(Debug, Default, Clone, Copy)]
pub struct SweepTrim {
pub start: Option<f64>,
pub end: Option<f64>,
}
#[derive(Debug, Clone, Copy)]
pub(super) enum ParamForm {
Bare,
Select,
Release(Release),
}
fn put_param(
attrs: &mut [Value],
index: usize,
value: Option<f64>,
form: ParamForm,
type_name: &'static str,
attribute: &'static str,
) -> Result<(), GeometryError> {
if let Some(value) = value {
require_finite(type_name, attribute, &[value])?;
}
attrs[index] = match (form, value) {
(ParamForm::Release(release), value) => release.parameter(type_name, attribute, value)?,
(_, None) => Value::Null,
(ParamForm::Bare, Some(value)) => Value::Real(value),
(ParamForm::Select, Some(value)) => Value::Typed {
type_name: "IFCPARAMETERVALUE".into(),
value: Box::new(Value::Real(value)),
},
};
Ok(())
}
pub fn extruded_area_solid_tapered(
tx: &mut Transaction,
swept_area: EntityId,
position: Option<EntityId>,
extruded_direction: EntityId,
depth: f64,
end_swept_area: EntityId,
) -> Result<EntityId, GeometryError> {
const T: &str = "IFCEXTRUDEDAREASOLIDTAPERED";
positive(T, "Depth", depth)?;
let mut attrs = vec![Value::Null; 5];
attrs[swept_area_slot::SWEPT_AREA] = Value::Ref(swept_area);
attrs[swept_area_slot::POSITION] = position.map_or(Value::Null, Value::Ref);
attrs[extruded_slot::EXTRUDED_DIRECTION] = Value::Ref(extruded_direction);
attrs[extruded_slot::DEPTH] = Value::Real(depth);
attrs[extruded_slot::END_SWEPT_AREA] = Value::Ref(end_swept_area);
Ok(tx.create(Entity::new(T, attrs)))
}
pub fn revolved_area_solid_tapered(
tx: &mut Transaction,
swept_area: EntityId,
position: Option<EntityId>,
axis: EntityId,
angle: f64,
end_swept_area: EntityId,
) -> Result<EntityId, GeometryError> {
const T: &str = "IFCREVOLVEDAREASOLIDTAPERED";
require_finite(T, "Angle", &[angle])?;
let mut attrs = vec![Value::Null; 5];
attrs[swept_area_slot::SWEPT_AREA] = Value::Ref(swept_area);
attrs[swept_area_slot::POSITION] = position.map_or(Value::Null, Value::Ref);
attrs[revolved_slot::AXIS] = Value::Ref(axis);
attrs[revolved_slot::ANGLE] = Value::Real(angle);
attrs[revolved_slot::END_SWEPT_AREA] = Value::Ref(end_swept_area);
Ok(tx.create(Entity::new(T, attrs)))
}
pub(super) fn directrix_attrs(
type_name: &'static str,
swept_area: EntityId,
position: Option<EntityId>,
directrix: EntityId,
trim: SweepTrim,
form: ParamForm,
) -> Result<Vec<Value>, GeometryError> {
let mut attrs = vec![Value::Null; 6];
attrs[swept_area_slot::SWEPT_AREA] = Value::Ref(swept_area);
attrs[swept_area_slot::POSITION] = position.map_or(Value::Null, Value::Ref);
attrs[directrix_slot::DIRECTRIX] = Value::Ref(directrix);
put_param(
&mut attrs,
directrix_slot::START_PARAM,
trim.start,
form,
type_name,
"StartParam",
)?;
put_param(
&mut attrs,
directrix_slot::END_PARAM,
trim.end,
form,
type_name,
"EndParam",
)?;
Ok(attrs)
}
pub fn swept_disk_solid_polygonal(
tx: &mut Transaction,
directrix: EntityId,
radius: f64,
inner_radius: Option<f64>,
trim: SweepTrim,
fillet_radius: Option<f64>,
) -> Result<EntityId, GeometryError> {
const T: &str = "IFCSWEPTDISKSOLIDPOLYGONAL";
let mut attrs = disk_attrs(T, directrix, radius, inner_radius, trim, ParamForm::Bare)?;
attrs.push(Value::Null);
if let Some(fillet) = fillet_radius {
require_finite(T, "FilletRadius", &[fillet])?;
if fillet < 0.0 {
return Err(invalid(
T,
"FilletRadius",
format!("expected a non-negative length, got {fillet}"),
));
}
attrs[disk_slot::FILLET_RADIUS] = Value::Real(fillet);
}
Ok(tx.create(Entity::new(T, attrs)))
}
pub(super) fn disk_attrs(
type_name: &'static str,
directrix: EntityId,
radius: f64,
inner_radius: Option<f64>,
trim: SweepTrim,
form: ParamForm,
) -> Result<Vec<Value>, GeometryError> {
positive(type_name, "Radius", radius)?;
let mut attrs = vec![Value::Null; 5];
attrs[disk_slot::DIRECTRIX] = Value::Ref(directrix);
attrs[disk_slot::RADIUS] = Value::Real(radius);
if let Some(inner) = inner_radius {
positive(type_name, "InnerRadius", inner)?;
if inner >= radius {
return Err(invalid(
type_name,
"InnerRadius",
format!("{inner} is not below the outer radius {radius}"),
));
}
attrs[disk_slot::INNER_RADIUS] = Value::Real(inner);
}
put_param(
&mut attrs,
disk_slot::START_PARAM,
trim.start,
form,
type_name,
"StartParam",
)?;
put_param(
&mut attrs,
disk_slot::END_PARAM,
trim.end,
form,
type_name,
"EndParam",
)?;
Ok(attrs)
}
pub fn surface_of_linear_extrusion(
tx: &mut Transaction,
swept_curve: EntityId,
position: Option<EntityId>,
extruded_direction: EntityId,
depth: f64,
) -> Result<EntityId, GeometryError> {
const T: &str = "IFCSURFACEOFLINEAREXTRUSION";
require_finite(T, "Depth", &[depth])?;
let attrs = vec![
Value::Ref(swept_curve),
position.map_or(Value::Null, Value::Ref),
Value::Ref(extruded_direction),
Value::Real(depth),
];
Ok(tx.create(Entity::new(T, attrs)))
}
pub fn surface_of_revolution(
tx: &mut Transaction,
swept_curve: EntityId,
position: Option<EntityId>,
axis_position: EntityId,
) -> EntityId {
let attrs = vec![
Value::Ref(swept_curve),
position.map_or(Value::Null, Value::Ref),
Value::Ref(axis_position),
];
tx.create(Entity::new("IFCSURFACEOFREVOLUTION", attrs))
}
pub fn plane(tx: &mut Transaction, position: EntityId) -> EntityId {
tx.create(Entity::new("IFCPLANE", vec![Value::Ref(position)]))
}
pub fn cylindrical_surface(
tx: &mut Transaction,
position: EntityId,
radius: f64,
) -> Result<EntityId, GeometryError> {
const T: &str = "IFCCYLINDRICALSURFACE";
positive(T, "Radius", radius)?;
let attrs = vec![Value::Ref(position), Value::Real(radius)];
Ok(tx.create(Entity::new(T, attrs)))
}
pub fn axis1_placement(
tx: &mut Transaction,
location: EntityId,
axis: Option<EntityId>,
) -> EntityId {
let attrs = vec![Value::Ref(location), axis.map_or(Value::Null, Value::Ref)];
tx.create(Entity::new("IFCAXIS1PLACEMENT", attrs))
}
pub fn directrix_derived_reference_swept_area_solid(
tx: &mut Transaction,
swept_area: EntityId,
position: Option<EntityId>,
directrix: EntityId,
trim: SweepTrim,
fixed_reference: EntityId,
) -> Result<EntityId, GeometryError> {
const T: &str = "IFCDIRECTRIXDERIVEDREFERENCESWEPTAREASOLID";
let mut attrs = directrix_attrs(T, swept_area, position, directrix, trim, ParamForm::Select)?;
attrs[directrix_slot::FIXED_REFERENCE] = Value::Ref(fixed_reference);
Ok(tx.create(Entity::new(T, attrs)))
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SectionedKind {
SolidHorizontal,
Surface,
}
pub fn sectioned(
tx: &mut Transaction,
kind: SectionedKind,
directrix: EntityId,
cross_sections: &[EntityId],
cross_section_positions: &[EntityId],
) -> Result<EntityId, GeometryError> {
let entity = match kind {
SectionedKind::SolidHorizontal => "IFCSECTIONEDSOLIDHORIZONTAL",
SectionedKind::Surface => "IFCSECTIONEDSURFACE",
};
if cross_sections.len() < 2 {
return Err(invalid(entity, "CrossSections", "expected LIST [2:?]"));
}
if cross_section_positions.len() < 2 {
return Err(invalid(
entity,
"CrossSectionPositions",
"expected LIST [2:?]",
));
}
if cross_sections.len() != cross_section_positions.len() {
return Err(invalid(
entity,
"CrossSectionPositions",
format!(
"expected one position per section: {} sections, {} positions",
cross_sections.len(),
cross_section_positions.len()
),
));
}
let attrs = match kind {
SectionedKind::SolidHorizontal => vec![
Value::Ref(directrix),
refs(cross_sections),
refs(cross_section_positions),
],
SectionedKind::Surface => vec![
Value::Ref(directrix),
refs(cross_section_positions),
refs(cross_sections),
],
};
Ok(tx.create(Entity::new(entity, attrs)))
}