Move helix revolutions into the main helix args (#5942)

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* docs

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updaes

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* fix

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
This commit is contained in:
Jess Frazelle
2025-03-21 15:38:08 -07:00
committed by GitHub
parent 5aed80e930
commit eef1a28ebb
23 changed files with 767 additions and 2357 deletions

View File

@ -4,8 +4,6 @@ use anyhow::Result;
use kcl_derive_docs::stdlib;
use kcmc::{each_cmd as mcmd, length_unit::LengthUnit, shared::Angle, ModelingCmd};
use kittycad_modeling_cmds as kcmc;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use crate::{
errors::KclError,
@ -18,11 +16,71 @@ pub async fn helix(exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
let angle_start = args.get_kw_arg("angleStart")?;
let revolutions = args.get_kw_arg("revolutions")?;
let ccw = args.get_kw_arg_opt("ccw")?;
let radius = args.get_kw_arg("radius")?;
let axis = args.get_kw_arg("axis")?;
let radius = args.get_kw_arg_opt("radius")?;
let axis = args.get_kw_arg_opt("axis")?;
let length = args.get_kw_arg_opt("length")?;
let cylinder = args.get_kw_arg_opt("cylinder")?;
let value = inner_helix(revolutions, angle_start, ccw, radius, axis, length, exec_state, args).await?;
// Make sure we have a radius if we don't have a cylinder.
if radius.is_none() && cylinder.is_none() {
return Err(KclError::Semantic(crate::errors::KclErrorDetails {
message: "Radius is required when creating a helix without a cylinder.".to_string(),
source_ranges: vec![args.source_range],
}));
}
// Make sure we don't have a radius if we have a cylinder.
if radius.is_some() && cylinder.is_some() {
return Err(KclError::Semantic(crate::errors::KclErrorDetails {
message: "Radius is not allowed when creating a helix with a cylinder.".to_string(),
source_ranges: vec![args.source_range],
}));
}
// Make sure we have an axis if we don't have a cylinder.
if axis.is_none() && cylinder.is_none() {
return Err(KclError::Semantic(crate::errors::KclErrorDetails {
message: "Axis is required when creating a helix without a cylinder.".to_string(),
source_ranges: vec![args.source_range],
}));
}
// Make sure we don't have an axis if we have a cylinder.
if axis.is_some() && cylinder.is_some() {
return Err(KclError::Semantic(crate::errors::KclErrorDetails {
message: "Axis is not allowed when creating a helix with a cylinder.".to_string(),
source_ranges: vec![args.source_range],
}));
}
// Make sure we have a radius if we have an axis.
if radius.is_none() && axis.is_some() {
return Err(KclError::Semantic(crate::errors::KclErrorDetails {
message: "Radius is required when creating a helix around an axis.".to_string(),
source_ranges: vec![args.source_range],
}));
}
// Make sure we have an axis if we have a radius.
if axis.is_none() && radius.is_some() {
return Err(KclError::Semantic(crate::errors::KclErrorDetails {
message: "Axis is required when creating a helix around an axis.".to_string(),
source_ranges: vec![args.source_range],
}));
}
let value = inner_helix(
revolutions,
angle_start,
ccw,
radius,
axis,
length,
cylinder,
exec_state,
args,
)
.await?;
Ok(KclValue::Helix { value })
}
@ -88,6 +146,23 @@ pub async fn helix(exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
/// |> circle( center = [0, 0], radius = 1 )
/// |> sweep(path = helixPath)
/// ```
///
///
///
/// ```no_run
/// // Create a helix on a cylinder.
///
/// part001 = startSketchOn('XY')
/// |> circle( center= [5, 5], radius= 10 )
/// |> extrude(length = 10)
///
/// helix(
/// angleStart = 0,
/// ccw = true,
/// revolutions = 16,
/// cylinder = part001,
/// )
/// ```
#[stdlib {
name = "helix",
keywords = true,
@ -96,9 +171,10 @@ pub async fn helix(exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
revolutions = { docs = "Number of revolutions."},
angle_start = { docs = "Start angle (in degrees)."},
ccw = { docs = "Is the helix rotation counter clockwise? The default is `false`.", include_in_snippet = false},
radius = { docs = "Radius of the helix."},
axis = { docs = "Axis to use for the helix."},
radius = { docs = "Radius of the helix.", include_in_snippet = true},
axis = { docs = "Axis to use for the helix.", include_in_snippet = true},
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},
cylinder = { docs = "Cylinder to create the helix on.", include_in_snippet = false},
},
feature_tree_operation = true,
}]
@ -107,9 +183,10 @@ async fn inner_helix(
revolutions: f64,
angle_start: f64,
ccw: Option<bool>,
radius: f64,
axis: Axis3dOrEdgeReference,
radius: Option<f64>,
axis: Option<Axis3dOrEdgeReference>,
length: Option<f64>,
cylinder: Option<Solid>,
exec_state: &mut ExecState,
args: Args,
) -> Result<Box<HelixValue>, KclError> {
@ -120,6 +197,7 @@ async fn inner_helix(
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()],
@ -129,113 +207,63 @@ async fn inner_helix(
return Ok(helix_result);
}
match axis {
Axis3dOrEdgeReference::Axis(axis) => {
let (axis, origin) = axis.axis_and_origin()?;
if let Some(cylinder) = cylinder {
args.batch_modeling_cmd(
id,
ModelingCmd::from(mcmd::EntityMakeHelix {
cylinder_id: cylinder.id,
is_clockwise: !helix_result.ccw,
length: LengthUnit(length.unwrap_or(cylinder.height)),
revolutions,
start_angle: Angle::from_degrees(angle_start),
}),
)
.await?;
} else if let (Some(axis), Some(radius)) = (axis, radius) {
match axis {
Axis3dOrEdgeReference::Axis(axis) => {
let (axis, origin) = axis.axis_and_origin()?;
// Make sure they gave us a length.
let Some(length) = length else {
return Err(KclError::Semantic(crate::errors::KclErrorDetails {
message: "Length is required when creating a helix around an axis.".to_string(),
source_ranges: vec![args.source_range],
}));
};
// Make sure they gave us a length.
let Some(length) = length else {
return Err(KclError::Semantic(crate::errors::KclErrorDetails {
message: "Length is required when creating a helix around an axis.".to_string(),
source_ranges: vec![args.source_range],
}));
};
args.batch_modeling_cmd(
id,
ModelingCmd::from(mcmd::EntityMakeHelixFromParams {
radius: LengthUnit(radius),
is_clockwise: !helix_result.ccw,
length: LengthUnit(length),
revolutions,
start_angle: Angle::from_degrees(angle_start),
axis,
center: origin,
}),
)
.await?;
}
Axis3dOrEdgeReference::Edge(edge) => {
let edge_id = edge.get_engine_id(exec_state, &args)?;
args.batch_modeling_cmd(
id,
ModelingCmd::from(mcmd::EntityMakeHelixFromParams {
radius: LengthUnit(radius),
is_clockwise: !helix_result.ccw,
length: LengthUnit(length),
revolutions,
start_angle: Angle::from_degrees(angle_start),
axis,
center: origin,
}),
)
.await?;
}
Axis3dOrEdgeReference::Edge(edge) => {
let edge_id = edge.get_engine_id(exec_state, &args)?;
args.batch_modeling_cmd(
id,
ModelingCmd::from(mcmd::EntityMakeHelixFromEdge {
radius: LengthUnit(radius),
is_clockwise: !helix_result.ccw,
length: length.map(LengthUnit),
revolutions,
start_angle: Angle::from_degrees(angle_start),
edge_id,
}),
)
.await?;
}
};
args.batch_modeling_cmd(
id,
ModelingCmd::from(mcmd::EntityMakeHelixFromEdge {
radius: LengthUnit(radius),
is_clockwise: !helix_result.ccw,
length: length.map(LengthUnit),
revolutions,
start_angle: Angle::from_degrees(angle_start),
edge_id,
}),
)
.await?;
}
};
}
Ok(helix_result)
}
/// Data for helix revolutions.
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
pub struct HelixRevolutionsData {
/// Number of revolutions.
pub revolutions: f64,
/// Start angle (in degrees).
#[serde(rename = "angleStart")]
pub angle_start: f64,
/// Is the helix rotation counter clockwise?
/// The default is `false`.
#[serde(default)]
pub ccw: bool,
/// Length of the helix. If this argument is not provided, the height of
/// the solid is used.
pub length: Option<f64>,
}
/// Create a helix on a cylinder.
pub async fn helix_revolutions(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let (data, solid): (HelixRevolutionsData, Box<Solid>) = args.get_data_and_solid(exec_state)?;
let value = inner_helix_revolutions(data, solid, exec_state, args).await?;
Ok(KclValue::Solid { value })
}
/// Create a helix on a cylinder.
///
/// ```no_run
/// part001 = startSketchOn('XY')
/// |> circle( center= [5, 5], radius= 10 )
/// |> extrude(length = 10)
/// |> helixRevolutions({
/// angleStart = 0,
/// ccw = true,
/// revolutions = 16,
/// }, %)
/// ```
#[stdlib {
name = "helixRevolutions",
feature_tree_operation = true,
}]
async fn inner_helix_revolutions(
data: HelixRevolutionsData,
solid: Box<Solid>,
exec_state: &mut ExecState,
args: Args,
) -> Result<Box<Solid>, KclError> {
let id = exec_state.next_uuid();
args.batch_modeling_cmd(
id,
ModelingCmd::from(mcmd::EntityMakeHelix {
cylinder_id: solid.id,
is_clockwise: !data.ccw,
length: LengthUnit(data.length.unwrap_or(solid.height)),
revolutions: data.revolutions,
start_angle: Angle::from_degrees(data.angle_start),
}),
)
.await?;
Ok(solid)
}