use anyhow::Result;
use kcmc::ModelingCmd;
use kcmc::each_cmd as mcmd;
use kcmc::length_unit::LengthUnit;
use kcmc::shared::Angle;
use kittycad_modeling_cmds::shared::Point3d;
use kittycad_modeling_cmds::{self as kcmc};
use super::args::TyF64;
use crate::errors::KclError;
use crate::errors::KclErrorDetails;
use crate::execution::ExecState;
use crate::execution::Helix as HelixValue;
use crate::execution::KclValue;
use crate::execution::ModelingCmdMeta;
use crate::execution::Solid;
use crate::execution::types::PrimitiveType;
use crate::execution::types::RuntimeType;
use crate::std::Args;
use crate::std::axis_or_reference::Axis3dOrEdgeReference;
pub async fn helix(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let angle_start: TyF64 = args.get_kw_arg("angleStart", &RuntimeType::degrees(), exec_state)?;
let revolutions: TyF64 = args.get_kw_arg("revolutions", &RuntimeType::count(), exec_state)?;
let ccw = args.get_kw_arg_opt("ccw", &RuntimeType::bool(), exec_state)?;
let radius: Option<TyF64> = args.get_kw_arg_opt("radius", &RuntimeType::length(), exec_state)?;
let axis: Option<Axis3dOrEdgeReference> = args.get_kw_arg_opt(
"axis",
&RuntimeType::Union(vec![
RuntimeType::Primitive(PrimitiveType::Edge),
RuntimeType::Primitive(PrimitiveType::Axis3d),
RuntimeType::segment(),
]),
exec_state,
)?;
let length: Option<TyF64> = args.get_kw_arg_opt("length", &RuntimeType::length(), exec_state)?;
let cylinder = args.get_kw_arg_opt("cylinder", &RuntimeType::solid(), exec_state)?;
if radius.is_none() && cylinder.is_none() {
return Err(KclError::new_semantic(crate::errors::KclErrorDetails::new(
"Radius is required when creating a helix without a cylinder.".to_string(),
vec![args.source_range],
)));
}
if radius.is_some() && cylinder.is_some() {
return Err(KclError::new_semantic(crate::errors::KclErrorDetails::new(
"Radius is not allowed when creating a helix with a cylinder.".to_string(),
vec![args.source_range],
)));
}
if axis.is_none() && cylinder.is_none() {
return Err(KclError::new_semantic(crate::errors::KclErrorDetails::new(
"Axis is required when creating a helix without a cylinder.".to_string(),
vec![args.source_range],
)));
}
if axis.is_some() && cylinder.is_some() {
return Err(KclError::new_semantic(crate::errors::KclErrorDetails::new(
"Axis is not allowed when creating a helix with a cylinder.".to_string(),
vec![args.source_range],
)));
}
if radius.is_none() && axis.is_some() {
return Err(KclError::new_semantic(crate::errors::KclErrorDetails::new(
"Radius is required when creating a helix around an axis.".to_string(),
vec![args.source_range],
)));
}
if axis.is_none() && radius.is_some() {
return Err(KclError::new_semantic(crate::errors::KclErrorDetails::new(
"Axis is required when creating a helix around an axis.".to_string(),
vec![args.source_range],
)));
}
let value = inner_helix(
revolutions.n,
angle_start.n,
ccw,
radius,
axis,
length,
cylinder,
exec_state,
args,
)
.await?;
Ok(KclValue::Helix { value })
}
#[allow(clippy::too_many_arguments)]
async fn inner_helix(
revolutions: f64,
angle_start: f64,
ccw: Option<bool>,
radius: Option<TyF64>,
axis: Option<Axis3dOrEdgeReference>,
length: Option<TyF64>,
cylinder: Option<Solid>,
exec_state: &mut ExecState,
args: Args,
) -> Result<Box<HelixValue>, KclError> {
let id = exec_state.next_uuid();
let helix_result = Box::new(HelixValue {
value: id,
artifact_id: id.into(),
revolutions,
angle_start,
cylinder_id: cylinder.as_ref().map(|c| c.id),
ccw: ccw.unwrap_or(false),
units: exec_state.length_unit(),
meta: vec![args.source_range.into()],
});
if args.ctx.no_engine_commands().await {
return Ok(helix_result);
}
if let Some(cylinder) = cylinder {
let cmd = if let Some(length) = length {
mcmd::EntityMakeHelix::builder()
.cylinder_id(cylinder.id)
.is_clockwise(!helix_result.ccw)
.revolutions(revolutions)
.start_angle(Angle::from_degrees(angle_start))
.length(LengthUnit(length.to_mm()))
.build()
} else {
mcmd::EntityMakeHelix::builder()
.cylinder_id(cylinder.id)
.is_clockwise(!helix_result.ccw)
.revolutions(revolutions)
.start_angle(Angle::from_degrees(angle_start))
.build()
};
exec_state
.batch_modeling_cmd(
ModelingCmdMeta::from_args_id(exec_state, &args, id),
ModelingCmd::from(cmd),
)
.await?;
} else if let (Some(axis), Some(radius)) = (axis, radius) {
match axis {
Axis3dOrEdgeReference::Axis { direction, origin } => {
let Some(length) = length else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"Length is required when creating a helix around an axis.".to_owned(),
vec![args.source_range],
)));
};
exec_state
.batch_modeling_cmd(
ModelingCmdMeta::from_args_id(exec_state, &args, id),
ModelingCmd::from(
mcmd::EntityMakeHelixFromParams::builder()
.radius(LengthUnit(radius.to_mm()))
.is_clockwise(!helix_result.ccw)
.length(LengthUnit(length.to_mm()))
.revolutions(revolutions)
.start_angle(Angle::from_degrees(angle_start))
.axis(Point3d {
x: direction[0].to_mm(),
y: direction[1].to_mm(),
z: direction[2].to_mm(),
})
.center(Point3d {
x: LengthUnit(origin[0].to_mm()),
y: LengthUnit(origin[1].to_mm()),
z: LengthUnit(origin[2].to_mm()),
})
.build(),
),
)
.await?;
}
Axis3dOrEdgeReference::Edge(edge) => {
let edge_id = edge.get_engine_id(exec_state, &args)?;
let cmd = if let Some(length) = length {
mcmd::EntityMakeHelixFromEdge::builder()
.radius(LengthUnit(radius.to_mm()))
.is_clockwise(!helix_result.ccw)
.revolutions(revolutions)
.start_angle(Angle::from_degrees(angle_start))
.edge_id(edge_id)
.length(LengthUnit(length.to_mm()))
.build()
} else {
mcmd::EntityMakeHelixFromEdge::builder()
.radius(LengthUnit(radius.to_mm()))
.is_clockwise(!helix_result.ccw)
.revolutions(revolutions)
.start_angle(Angle::from_degrees(angle_start))
.edge_id(edge_id)
.build()
};
exec_state
.batch_modeling_cmd(
ModelingCmdMeta::from_args_id(exec_state, &args, id),
ModelingCmd::from(cmd),
)
.await?;
}
};
}
Ok(helix_result)
}