use crate::error::GeometryResult;
use crate::slots::Slots;
use ifc_model::{Entity, EntityId};
pub(crate) mod slot {
pub const SWEPT_CURVE: usize = 0;
pub const POSITION: usize = 1;
pub const EXTRUDED_DIRECTION: usize = 2;
pub const DEPTH: usize = 3;
pub const AXIS_POSITION: usize = 2;
}
#[derive(Debug, Clone, Copy)]
struct SweptBase<'m> {
slots: Slots<'m>,
}
impl<'m> SweptBase<'m> {
fn swept_curve_ref(&self) -> GeometryResult<EntityId> {
self.slots.req_ref(slot::SWEPT_CURVE, "SweptCurve")
}
fn position_ref(&self) -> Option<EntityId> {
self.slots.opt_ref(slot::POSITION)
}
}
#[derive(Debug, Clone, Copy)]
pub struct SurfaceOfLinearExtrusion<'m> {
base: SweptBase<'m>,
}
impl<'m> SurfaceOfLinearExtrusion<'m> {
pub fn new(id: EntityId, entity: &'m Entity) -> Self {
Self {
base: SweptBase {
slots: Slots::new(id, entity),
},
}
}
pub fn id(&self) -> EntityId {
self.base.slots.id()
}
pub fn swept_curve_ref(&self) -> GeometryResult<EntityId> {
self.base.swept_curve_ref()
}
pub fn position_ref(&self) -> Option<EntityId> {
self.base.position_ref()
}
pub fn extruded_direction_ref(&self) -> GeometryResult<EntityId> {
self.base
.slots
.req_ref(slot::EXTRUDED_DIRECTION, "ExtrudedDirection")
}
pub fn depth(&self) -> GeometryResult<f64> {
let depth = self.base.slots.req_f64(slot::DEPTH, "Depth")?;
if depth == 0.0 {
return Err(self
.base
.slots
.degenerate("Depth is zero; the surface collapses to its generating curve"));
}
Ok(depth)
}
}
#[derive(Debug, Clone, Copy)]
pub struct SurfaceOfRevolution<'m> {
base: SweptBase<'m>,
}
impl<'m> SurfaceOfRevolution<'m> {
pub fn new(id: EntityId, entity: &'m Entity) -> Self {
Self {
base: SweptBase {
slots: Slots::new(id, entity),
},
}
}
pub fn id(&self) -> EntityId {
self.base.slots.id()
}
pub fn swept_curve_ref(&self) -> GeometryResult<EntityId> {
self.base.swept_curve_ref()
}
pub fn position_ref(&self) -> Option<EntityId> {
self.base.position_ref()
}
pub fn axis_position_ref(&self) -> GeometryResult<EntityId> {
self.base.slots.req_ref(slot::AXIS_POSITION, "AxisPosition")
}
}
#[cfg(test)]
mod tests {
use super::*;
use ifc_model::Value;
fn extrusion(position: Value, depth: Value) -> Entity {
Entity::new(
"IFCSURFACEOFLINEAREXTRUSION",
vec![
Value::Ref(EntityId(80)),
position,
Value::Ref(EntityId(82)),
depth,
],
)
}
#[test]
fn extrusion_reads_inherited_swept_curve_before_its_own_direction_and_depth() {
let e = extrusion(Value::Ref(EntityId(81)), Value::Real(3.0));
let view = SurfaceOfLinearExtrusion::new(EntityId(1), &e);
assert_eq!(view.swept_curve_ref().unwrap(), EntityId(80));
assert_eq!(view.position_ref(), Some(EntityId(81)));
assert_eq!(view.extruded_direction_ref().unwrap(), EntityId(82));
assert_eq!(view.depth().unwrap(), 3.0);
}
#[test]
fn an_absent_position_is_reported_as_none_rather_than_as_an_error() {
let e = extrusion(Value::Null, Value::Real(1.0));
let view = SurfaceOfLinearExtrusion::new(EntityId(1), &e);
assert_eq!(view.position_ref(), None);
assert_eq!(view.extruded_direction_ref().unwrap(), EntityId(82));
assert_eq!(view.depth().unwrap(), 1.0);
}
#[test]
fn a_negative_depth_is_accepted_because_it_only_reverses_the_sweep() {
let e = extrusion(Value::Ref(EntityId(81)), Value::Real(-2.0));
assert_eq!(
SurfaceOfLinearExtrusion::new(EntityId(1), &e)
.depth()
.unwrap(),
-2.0
);
}
#[test]
fn a_zero_depth_is_degenerate() {
let e = extrusion(Value::Ref(EntityId(81)), Value::Real(0.0));
let err = SurfaceOfLinearExtrusion::new(EntityId(5), &e)
.depth()
.unwrap_err();
assert!(err.to_string().contains("#5"), "got: {err}");
assert!(err.to_string().contains("zero"), "got: {err}");
}
#[test]
fn depth_reads_through_a_length_measure_wrapper() {
let e = extrusion(
Value::Ref(EntityId(81)),
Value::Typed {
type_name: "IFCLENGTHMEASURE".into(),
value: Box::new(Value::Real(1.5)),
},
);
assert_eq!(
SurfaceOfLinearExtrusion::new(EntityId(1), &e)
.depth()
.unwrap(),
1.5
);
}
#[test]
fn revolution_reads_its_axis_from_the_slot_after_the_inherited_two() {
let e = Entity::new(
"IFCSURFACEOFREVOLUTION",
vec![
Value::Ref(EntityId(90)),
Value::Ref(EntityId(91)),
Value::Ref(EntityId(92)),
],
);
let view = SurfaceOfRevolution::new(EntityId(1), &e);
assert_eq!(view.swept_curve_ref().unwrap(), EntityId(90));
assert_eq!(view.position_ref(), Some(EntityId(91)));
assert_eq!(view.axis_position_ref().unwrap(), EntityId(92));
}
#[test]
fn a_revolution_without_an_axis_reports_the_attribute_by_name() {
let e = Entity::new(
"IFCSURFACEOFREVOLUTION",
vec![Value::Ref(EntityId(90)), Value::Null],
);
let err = SurfaceOfRevolution::new(EntityId(1), &e)
.axis_position_ref()
.unwrap_err();
assert!(err.to_string().contains("AxisPosition"), "got: {err}");
}
#[test]
fn a_swept_surface_without_its_profile_reports_the_attribute_by_name() {
let e = Entity::new("IFCSURFACEOFLINEAREXTRUSION", vec![]);
let err = SurfaceOfLinearExtrusion::new(EntityId(1), &e)
.swept_curve_ref()
.unwrap_err();
assert!(err.to_string().contains("SweptCurve"), "got: {err}");
}
}