use crate::error::GeometryResult;
use crate::slots::Slots;
use ifc_model::{Entity, EntityId};
pub(crate) mod csg_solid_slot {
pub const TREE_ROOT_EXPRESSION: usize = 0;
}
pub(crate) mod primitive_slot {
pub const POSITION: usize = 0;
pub const DIM_0: usize = 1;
pub const DIM_1: usize = 2;
pub const DIM_2: usize = 3;
}
#[derive(Debug, Clone, Copy)]
pub struct CsgSolid<'m> {
slots: Slots<'m>,
}
impl<'m> CsgSolid<'m> {
pub fn new(id: EntityId, entity: &'m Entity) -> Self {
Self {
slots: Slots::new(id, entity),
}
}
pub fn id(&self) -> EntityId {
self.slots.id()
}
pub fn tree_root_expression(&self) -> GeometryResult<EntityId> {
self.slots
.req_ref(csg_solid_slot::TREE_ROOT_EXPRESSION, "TreeRootExpression")
}
}
#[derive(Debug, Clone, Copy)]
pub struct CsgPrimitive3D<'m> {
slots: Slots<'m>,
}
impl<'m> CsgPrimitive3D<'m> {
pub fn new(id: EntityId, entity: &'m Entity) -> Self {
Self {
slots: Slots::new(id, entity),
}
}
pub fn id(&self) -> EntityId {
self.slots.id()
}
pub fn type_name(&self) -> &'m str {
self.slots.type_name()
}
pub fn position(&self) -> GeometryResult<EntityId> {
self.slots.req_ref(primitive_slot::POSITION, "Position")
}
}
macro_rules! csg_primitive {
(
$(#[$meta:meta])*
$name:ident { $( $(#[$field_meta:meta])* $method:ident => $slot:expr , $express:literal );+ $(;)? }
) => {
$(#[$meta])*
#[derive(Debug, Clone, Copy)]
pub struct $name<'m> {
slots: Slots<'m>,
}
impl<'m> $name<'m> {
pub fn new(id: EntityId, entity: &'m Entity) -> Self {
Self { slots: Slots::new(id, entity) }
}
pub fn id(&self) -> EntityId {
self.slots.id()
}
pub fn base(&self) -> CsgPrimitive3D<'m> {
CsgPrimitive3D { slots: self.slots }
}
$(
$(#[$field_meta])*
pub fn $method(&self) -> GeometryResult<f64> {
self.slots.req_f64($slot, $express)
}
)+
}
};
}
csg_primitive! {
Block {
x_length => primitive_slot::DIM_0, "XLength";
y_length => primitive_slot::DIM_1, "YLength";
z_length => primitive_slot::DIM_2, "ZLength";
}
}
csg_primitive! {
RectangularPyramid {
x_length => primitive_slot::DIM_0, "XLength";
y_length => primitive_slot::DIM_1, "YLength";
height => primitive_slot::DIM_2, "Height";
}
}
csg_primitive! {
RightCircularCone {
height => primitive_slot::DIM_0, "Height";
bottom_radius => primitive_slot::DIM_1, "BottomRadius";
}
}
csg_primitive! {
RightCircularCylinder {
height => primitive_slot::DIM_0, "Height";
radius => primitive_slot::DIM_1, "Radius";
}
}
csg_primitive! {
Sphere {
radius => primitive_slot::DIM_0, "Radius";
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::solid::testkit::{entity, n, r};
#[test]
fn csg_solid_exposes_its_tree_root_without_resolving_it() {
let e = entity("IFCCSGSOLID", vec![r(55)]);
let view = CsgSolid::new(EntityId(1), &e);
assert_eq!(view.tree_root_expression().unwrap(), EntityId(55));
}
#[test]
fn a_csg_solid_without_a_root_expression_is_an_error_not_an_empty_tree() {
let e = entity("IFCCSGSOLID", vec![]);
let err = CsgSolid::new(EntityId(3), &e)
.tree_root_expression()
.unwrap_err();
assert_eq!(err.entity(), Some(EntityId(3)));
}
#[test]
fn primitive_position_precedes_the_dimension_attributes() {
let e = entity("IFCBLOCK", vec![r(9), n(1.0), n(2.0), n(3.0)]);
let block = Block::new(EntityId(1), &e);
assert_eq!(block.base().position().unwrap(), EntityId(9));
assert_eq!(block.x_length().unwrap(), 1.0);
assert_eq!(block.y_length().unwrap(), 2.0);
assert_eq!(block.z_length().unwrap(), 3.0);
}
#[test]
fn primitive_position_is_required_and_has_no_identity_default() {
let e = entity("IFCSPHERE", vec![ifc_model::Value::Null, n(2.0)]);
let sphere = Sphere::new(EntityId(4), &e);
assert!(sphere.base().position().is_err());
assert_eq!(sphere.radius().unwrap(), 2.0);
}
#[test]
fn pyramid_reads_height_where_block_reads_z_length() {
let attrs = vec![r(9), n(4.0), n(5.0), n(6.0)];
let pyramid = entity("IFCRECTANGULARPYRAMID", attrs.clone());
let block = entity("IFCBLOCK", attrs);
assert_eq!(
RectangularPyramid::new(EntityId(1), &pyramid)
.height()
.unwrap(),
6.0
);
assert_eq!(Block::new(EntityId(1), &block).z_length().unwrap(), 6.0);
}
#[test]
fn cylinder_and_cone_declare_height_before_radius() {
let cyl = entity("IFCRIGHTCIRCULARCYLINDER", vec![r(9), n(10.0), n(0.5)]);
let view = RightCircularCylinder::new(EntityId(1), &cyl);
assert_eq!(view.height().unwrap(), 10.0);
assert_eq!(view.radius().unwrap(), 0.5);
let cone = entity("IFCRIGHTCIRCULARCONE", vec![r(9), n(10.0), n(0.5)]);
let view = RightCircularCone::new(EntityId(1), &cone);
assert_eq!(view.height().unwrap(), 10.0);
assert_eq!(view.bottom_radius().unwrap(), 0.5);
}
#[test]
fn every_primitive_reports_its_concrete_type_name() {
for name in [
"IFCBLOCK",
"IFCRECTANGULARPYRAMID",
"IFCRIGHTCIRCULARCONE",
"IFCRIGHTCIRCULARCYLINDER",
"IFCSPHERE",
] {
let e = entity(name, vec![r(1), n(1.0), n(1.0), n(1.0)]);
assert_eq!(CsgPrimitive3D::new(EntityId(1), &e).type_name(), name);
}
}
}