1use anyhow::Result;
4use kcl_derive_docs::stdlib;
5use kcmc::{each_cmd as mcmd, length_unit::LengthUnit, shared::Angle, ModelingCmd};
6use kittycad_modeling_cmds as kcmc;
7use schemars::JsonSchema;
8use serde::{Deserialize, Serialize};
9
10use crate::{
11 errors::KclError,
12 execution::{ExecState, Helix as HelixValue, KclValue, Solid},
13 std::{axis_or_reference::Axis3dOrEdgeReference, Args},
14};
15
16pub async fn helix(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
18 let angle_start = args.get_kw_arg("angleStart")?;
19 let revolutions = args.get_kw_arg("revolutions")?;
20 let ccw = args.get_kw_arg_opt("ccw")?;
21 let radius = args.get_kw_arg("radius")?;
22 let axis = args.get_kw_arg("axis")?;
23 let length = args.get_kw_arg_opt("length")?;
24
25 let value = inner_helix(revolutions, angle_start, ccw, radius, axis, length, exec_state, args).await?;
26 Ok(KclValue::Helix { value })
27}
28
29#[stdlib {
92 name = "helix",
93 keywords = true,
94 unlabeled_first = false,
95 args = {
96 revolutions = { docs = "Number of revolutions."},
97 angle_start = { docs = "Start angle (in degrees)."},
98 ccw = { docs = "Is the helix rotation counter clockwise? The default is `false`.", include_in_snippet = false},
99 radius = { docs = "Radius of the helix."},
100 axis = { docs = "Axis to use for the helix."},
101 length = { docs = "Length of the helix. This is not necessary if the helix is created around an edge. If not given the length of the edge is used.", include_in_snippet = true},
102 },
103 feature_tree_operation = true,
104}]
105#[allow(clippy::too_many_arguments)]
106async fn inner_helix(
107 revolutions: f64,
108 angle_start: f64,
109 ccw: Option<bool>,
110 radius: f64,
111 axis: Axis3dOrEdgeReference,
112 length: Option<f64>,
113 exec_state: &mut ExecState,
114 args: Args,
115) -> Result<Box<HelixValue>, KclError> {
116 let id = exec_state.next_uuid();
117
118 let helix_result = Box::new(HelixValue {
119 value: id,
120 artifact_id: id.into(),
121 revolutions,
122 angle_start,
123 ccw: ccw.unwrap_or(false),
124 units: exec_state.length_unit(),
125 meta: vec![args.source_range.into()],
126 });
127
128 if args.ctx.no_engine_commands().await {
129 return Ok(helix_result);
130 }
131
132 match axis {
133 Axis3dOrEdgeReference::Axis(axis) => {
134 let (axis, origin) = axis.axis_and_origin()?;
135
136 let Some(length) = length else {
138 return Err(KclError::Semantic(crate::errors::KclErrorDetails {
139 message: "Length is required when creating a helix around an axis.".to_string(),
140 source_ranges: vec![args.source_range],
141 }));
142 };
143
144 args.batch_modeling_cmd(
145 id,
146 ModelingCmd::from(mcmd::EntityMakeHelixFromParams {
147 radius: LengthUnit(radius),
148 is_clockwise: !helix_result.ccw,
149 length: LengthUnit(length),
150 revolutions,
151 start_angle: Angle::from_degrees(angle_start),
152 axis,
153 center: origin,
154 }),
155 )
156 .await?;
157 }
158 Axis3dOrEdgeReference::Edge(edge) => {
159 let edge_id = edge.get_engine_id(exec_state, &args)?;
160
161 args.batch_modeling_cmd(
162 id,
163 ModelingCmd::from(mcmd::EntityMakeHelixFromEdge {
164 radius: LengthUnit(radius),
165 is_clockwise: !helix_result.ccw,
166 length: length.map(LengthUnit),
167 revolutions,
168 start_angle: Angle::from_degrees(angle_start),
169 edge_id,
170 }),
171 )
172 .await?;
173 }
174 };
175
176 Ok(helix_result)
177}
178
179#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
181#[ts(export)]
182pub struct HelixRevolutionsData {
183 pub revolutions: f64,
185 #[serde(rename = "angleStart")]
187 pub angle_start: f64,
188 #[serde(default)]
191 pub ccw: bool,
192 pub length: Option<f64>,
195}
196
197pub async fn helix_revolutions(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
199 let (data, solid): (HelixRevolutionsData, Box<Solid>) = args.get_data_and_solid(exec_state)?;
200
201 let value = inner_helix_revolutions(data, solid, exec_state, args).await?;
202 Ok(KclValue::Solid { value })
203}
204
205#[stdlib {
218 name = "helixRevolutions",
219 feature_tree_operation = true,
220}]
221async fn inner_helix_revolutions(
222 data: HelixRevolutionsData,
223 solid: Box<Solid>,
224 exec_state: &mut ExecState,
225 args: Args,
226) -> Result<Box<Solid>, KclError> {
227 let id = exec_state.next_uuid();
228 args.batch_modeling_cmd(
229 id,
230 ModelingCmd::from(mcmd::EntityMakeHelix {
231 cylinder_id: solid.id,
232 is_clockwise: !data.ccw,
233 length: LengthUnit(data.length.unwrap_or(solid.height)),
234 revolutions: data.revolutions,
235 start_angle: Angle::from_degrees(data.angle_start),
236 }),
237 )
238 .await?;
239
240 Ok(solid)
241}