use axiolid_model::{GeometryNode, Instance, NodeId, SolidOperation};
use axiolid_primitive::Primitive;
use ifc_model::EntityId;
use crate::error::GeometryResult;
use crate::lower::curve::lower_sweep_directrix;
use crate::lower::profile::lower_profile_node;
use crate::lower::session::LoweringSession;
use crate::lower::surface::lower_surface_node;
use crate::resource::placement::axis_placement_transform;
use crate::slots::Slots;
use crate::solid::csg::{CsgPrimitive3D, CsgSolid};
use crate::solid::swept::directrix::{
SurfaceCurveSweptAreaSolid, SweptDiskSolid, SweptDiskSolidPolygonal,
};
use crate::transform::Transform;
const CSG: &str = "csg solid";
const DISK: &str = "swept disk";
pub fn lower_csg_solid_node(
session: &mut LoweringSession<'_>,
id: EntityId,
frame: Transform,
) -> GeometryResult<NodeId> {
if let Some(node) = session.memoized(id, CSG, frame) {
return Ok(node);
}
session.enter(id, CSG)?;
let result = (|| {
let entity = session.entity(id, id)?;
let root = CsgSolid::new(id, entity).tree_root_expression()?;
session.lower_operand(root, frame)
})();
session.exit(id);
let node = result?;
session.memoize(id, CSG, frame, node);
Ok(node)
}
pub fn lower_csg_primitive_node(
session: &mut LoweringSession<'_>,
id: EntityId,
frame: Transform,
) -> GeometryResult<NodeId> {
if let Some(node) = session.memoized(id, CSG, frame) {
return Ok(node);
}
session.enter(id, CSG)?;
let result = build_primitive(session, id, frame);
session.exit(id);
let node = result?;
session.memoize(id, CSG, frame, node);
Ok(node)
}
fn build_primitive(
session: &mut LoweringSession<'_>,
id: EntityId,
frame: Transform,
) -> GeometryResult<NodeId> {
let type_name = session.type_name(id)?;
let entity = session.entity(id, id)?;
let slots = Slots::new(id, entity);
let view = CsgPrimitive3D::new(id, entity);
let _ = &view;
let position_ref = slots.req_ref(0, "Position")?;
let position = session.entity(id, position_ref)?;
let local = axis_placement_transform(session.model(), position_ref, position)?
.to_metres(session.units());
let length = |value: f64| session.units().length(value);
let primitive = match type_name.as_str() {
"IFCBLOCK" => Primitive::Block {
x: length(slots.req_f64(1, "XLength")?),
y: length(slots.req_f64(2, "YLength")?),
z: length(slots.req_f64(3, "ZLength")?),
},
"IFCSPHERE" => Primitive::Sphere {
radius: length(slots.req_f64(1, "Radius")?),
},
"IFCRIGHTCIRCULARCYLINDER" => Primitive::Cylinder {
height: length(slots.req_f64(1, "Height")?),
radius: length(slots.req_f64(2, "Radius")?),
},
"IFCRIGHTCIRCULARCONE" => Primitive::Cone {
height: length(slots.req_f64(1, "Height")?),
radius: length(slots.req_f64(2, "BottomRadius")?),
},
"IFCRECTANGULARPYRAMID" => Primitive::Pyramid {
x: length(slots.req_f64(1, "XLength")?),
y: length(slots.req_f64(2, "YLength")?),
height: length(slots.req_f64(3, "Height")?),
},
other => return Err(session.unsupported(id, other, "CSG primitive family")),
};
let source = session.node(GeometryNode::Primitive(primitive))?;
let placed = frame.compose(&local).compose(&anchor(&primitive));
session.node_for(
id,
GeometryNode::Instance(Instance {
source,
transform: placed.to_geom(),
}),
)
}
fn anchor(primitive: &Primitive) -> Transform {
match *primitive {
Primitive::Block { x, y, z } => Transform::translation([x / 2.0, y / 2.0, z / 2.0]),
_ => Transform::identity(),
}
}
pub fn lower_swept_disk_node(
session: &mut LoweringSession<'_>,
id: EntityId,
frame: Transform,
) -> GeometryResult<NodeId> {
if let Some(node) = session.memoized(id, DISK, frame) {
return Ok(node);
}
session.enter(id, DISK)?;
let result = build_disk(session, id, frame);
session.exit(id);
let node = result?;
session.memoize(id, DISK, frame, node);
Ok(node)
}
fn build_disk(
session: &mut LoweringSession<'_>,
id: EntityId,
frame: Transform,
) -> GeometryResult<NodeId> {
let entity = session.entity(id, id)?;
let view = SweptDiskSolid::new(id, entity);
let fillet_radius = if session
.type_name(id)?
.eq_ignore_ascii_case("IFCSWEPTDISKSOLIDPOLYGONAL")
{
SweptDiskSolidPolygonal::new(id, entity)
.fillet_radius()
.map(|value| session.units().length(value))
} else {
None
};
let (radius_raw, inner_raw) = view.checked_radii()?;
let radius = session.units().length(radius_raw);
let inner_radius = inner_raw.map(|value| session.units().length(value));
let (directrix, parameter_range) = lower_sweep_directrix(
session,
id,
"IFCSWEPTDISKSOLID",
view.directrix()?,
frame,
view.start_param(),
view.end_param(),
)?;
session.node_for(
id,
GeometryNode::SolidOperation(SolidOperation::SweptDisk {
directrix,
radius,
inner_radius,
parameter_range,
fillet_radius,
}),
)
}
#[cfg(test)]
mod tests;
pub fn lower_surface_curve_swept_area_solid_node(
session: &mut LoweringSession<'_>,
id: EntityId,
frame: Transform,
) -> GeometryResult<NodeId> {
if let Some(node) = session.memoized(id, CSG, frame) {
return Ok(node);
}
let entity = session.entity(id, id)?;
let view = SurfaceCurveSweptAreaSolid::new(id, entity);
let base = view.base();
let placed = match base.position() {
Some(position_id) => {
let position = session.entity(id, position_id)?;
let local = axis_placement_transform(session.model(), position_id, position)?
.to_metres(session.units());
frame.compose(&local)
}
None => frame,
};
let profile = lower_profile_node(session, base.swept_area()?)?;
let (directrix, parameter_range) = lower_sweep_directrix(
session,
id,
"IFCSURFACECURVESWEPTAREASOLID",
view.directrix()?,
placed,
view.start_param(),
view.end_param(),
)?;
let reference_surface = lower_surface_node(session, view.reference_surface()?, placed)?;
let node = session.node_for(
id,
GeometryNode::SolidOperation(SolidOperation::SurfaceCurveSweep {
profile,
directrix,
reference_surface,
parameter_range,
}),
)?;
session.memoize(id, CSG, frame, node);
Ok(node)
}