1use anyhow::Result;
4use kcmc::ModelingCmd;
5use kcmc::each_cmd as mcmd;
6use kcmc::length_unit::LengthUnit;
7use kcmc::shared::Angle;
8use kittycad_modeling_cmds::shared::Point3d;
9use kittycad_modeling_cmds::{self as kcmc};
10
11use super::args::TyF64;
12use crate::errors::KclError;
13use crate::errors::KclErrorDetails;
14use crate::execution::ExecState;
15use crate::execution::Helix as HelixValue;
16use crate::execution::KclValue;
17use crate::execution::ModelingCmdMeta;
18use crate::execution::Solid;
19use crate::execution::types::PrimitiveType;
20use crate::execution::types::RuntimeType;
21use crate::std::Args;
22use crate::std::axis_or_reference::Axis3dOrEdgeReference;
23
24pub async fn helix(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
26 let angle_start: TyF64 = args.get_kw_arg("angleStart", &RuntimeType::degrees(), exec_state)?;
27 let revolutions: TyF64 = args.get_kw_arg("revolutions", &RuntimeType::count(), exec_state)?;
28 let ccw = args.get_kw_arg_opt("ccw", &RuntimeType::bool(), exec_state)?;
29 let radius: Option<TyF64> = args.get_kw_arg_opt("radius", &RuntimeType::length(), exec_state)?;
30 let axis: Option<Axis3dOrEdgeReference> = args.get_kw_arg_opt(
31 "axis",
32 &RuntimeType::Union(vec![
33 RuntimeType::Primitive(PrimitiveType::Edge),
34 RuntimeType::Primitive(PrimitiveType::Axis3d),
35 RuntimeType::segment(),
36 ]),
37 exec_state,
38 )?;
39 let length: Option<TyF64> = args.get_kw_arg_opt("length", &RuntimeType::length(), exec_state)?;
40 let cylinder = args.get_kw_arg_opt("cylinder", &RuntimeType::solid(), exec_state)?;
41
42 if radius.is_none() && cylinder.is_none() {
44 return Err(KclError::new_semantic(crate::errors::KclErrorDetails::new(
45 "Radius is required when creating a helix without a cylinder.".to_string(),
46 vec![args.source_range],
47 )));
48 }
49
50 if radius.is_some() && cylinder.is_some() {
52 return Err(KclError::new_semantic(crate::errors::KclErrorDetails::new(
53 "Radius is not allowed when creating a helix with a cylinder.".to_string(),
54 vec![args.source_range],
55 )));
56 }
57
58 if axis.is_none() && cylinder.is_none() {
60 return Err(KclError::new_semantic(crate::errors::KclErrorDetails::new(
61 "Axis is required when creating a helix without a cylinder.".to_string(),
62 vec![args.source_range],
63 )));
64 }
65
66 if axis.is_some() && cylinder.is_some() {
68 return Err(KclError::new_semantic(crate::errors::KclErrorDetails::new(
69 "Axis is not allowed when creating a helix with a cylinder.".to_string(),
70 vec![args.source_range],
71 )));
72 }
73
74 if radius.is_none() && axis.is_some() {
76 return Err(KclError::new_semantic(crate::errors::KclErrorDetails::new(
77 "Radius is required when creating a helix around an axis.".to_string(),
78 vec![args.source_range],
79 )));
80 }
81
82 if axis.is_none() && radius.is_some() {
84 return Err(KclError::new_semantic(crate::errors::KclErrorDetails::new(
85 "Axis is required when creating a helix around an axis.".to_string(),
86 vec![args.source_range],
87 )));
88 }
89
90 let value = inner_helix(
91 revolutions.n,
92 angle_start.n,
93 ccw,
94 radius,
95 axis,
96 length,
97 cylinder,
98 exec_state,
99 args,
100 )
101 .await?;
102 Ok(KclValue::Helix { value })
103}
104
105#[allow(clippy::too_many_arguments)]
106async fn inner_helix(
107 revolutions: f64,
108 angle_start: f64,
109 ccw: Option<bool>,
110 radius: Option<TyF64>,
111 axis: Option<Axis3dOrEdgeReference>,
112 length: Option<TyF64>,
113 cylinder: Option<Solid>,
114 exec_state: &mut ExecState,
115 args: Args,
116) -> Result<Box<HelixValue>, KclError> {
117 let id = exec_state.next_uuid();
118
119 let helix_result = Box::new(HelixValue {
120 value: id,
121 artifact_id: id.into(),
122 revolutions,
123 angle_start,
124 cylinder_id: cylinder.as_ref().map(|c| c.id),
125 ccw: ccw.unwrap_or(false),
126 units: exec_state.length_unit(),
127 meta: vec![args.source_range.into()],
128 });
129
130 if args.ctx.no_engine_commands().await {
131 return Ok(helix_result);
132 }
133
134 if let Some(cylinder) = cylinder {
135 let cmd = if let Some(length) = length {
136 mcmd::EntityMakeHelix::builder()
137 .cylinder_id(cylinder.id)
138 .is_clockwise(!helix_result.ccw)
139 .revolutions(revolutions)
140 .start_angle(Angle::from_degrees(angle_start))
141 .length(LengthUnit(length.to_mm()))
142 .build()
143 } else {
144 mcmd::EntityMakeHelix::builder()
145 .cylinder_id(cylinder.id)
146 .is_clockwise(!helix_result.ccw)
147 .revolutions(revolutions)
148 .start_angle(Angle::from_degrees(angle_start))
149 .build()
150 };
151 exec_state
152 .batch_modeling_cmd(
153 ModelingCmdMeta::from_args_id(exec_state, &args, id),
154 ModelingCmd::from(cmd),
155 )
156 .await?;
157 } else if let (Some(axis), Some(radius)) = (axis, radius) {
158 match axis {
159 Axis3dOrEdgeReference::Axis { direction, origin } => {
160 let Some(length) = length else {
162 return Err(KclError::new_semantic(KclErrorDetails::new(
163 "Length is required when creating a helix around an axis.".to_owned(),
164 vec![args.source_range],
165 )));
166 };
167
168 exec_state
169 .batch_modeling_cmd(
170 ModelingCmdMeta::from_args_id(exec_state, &args, id),
171 ModelingCmd::from(
172 mcmd::EntityMakeHelixFromParams::builder()
173 .radius(LengthUnit(radius.to_mm()))
174 .is_clockwise(!helix_result.ccw)
175 .length(LengthUnit(length.to_mm()))
176 .revolutions(revolutions)
177 .start_angle(Angle::from_degrees(angle_start))
178 .axis(Point3d {
179 x: direction[0].to_mm(),
180 y: direction[1].to_mm(),
181 z: direction[2].to_mm(),
182 })
183 .center(Point3d {
184 x: LengthUnit(origin[0].to_mm()),
185 y: LengthUnit(origin[1].to_mm()),
186 z: LengthUnit(origin[2].to_mm()),
187 })
188 .build(),
189 ),
190 )
191 .await?;
192 }
193 Axis3dOrEdgeReference::Edge(edge) => {
194 let edge_id = edge.get_engine_id(exec_state, &args)?;
195
196 let cmd = if let Some(length) = length {
197 mcmd::EntityMakeHelixFromEdge::builder()
198 .radius(LengthUnit(radius.to_mm()))
199 .is_clockwise(!helix_result.ccw)
200 .revolutions(revolutions)
201 .start_angle(Angle::from_degrees(angle_start))
202 .edge_id(edge_id)
203 .length(LengthUnit(length.to_mm()))
204 .build()
205 } else {
206 mcmd::EntityMakeHelixFromEdge::builder()
207 .radius(LengthUnit(radius.to_mm()))
208 .is_clockwise(!helix_result.ccw)
209 .revolutions(revolutions)
210 .start_angle(Angle::from_degrees(angle_start))
211 .edge_id(edge_id)
212 .build()
213 };
214 exec_state
215 .batch_modeling_cmd(
216 ModelingCmdMeta::from_args_id(exec_state, &args, id),
217 ModelingCmd::from(cmd),
218 )
219 .await?;
220 }
221 };
222 }
223
224 Ok(helix_result)
225}