2024-03-25 17:07:53 -07:00
|
|
|
//! Standard library helices.
|
|
|
|
|
|
|
|
use anyhow::Result;
|
|
|
|
use derive_docs::stdlib;
|
2024-09-19 14:06:29 -07:00
|
|
|
use kcmc::{each_cmd as mcmd, length_unit::LengthUnit, shared::Angle, ModelingCmd};
|
2024-09-18 17:04:04 -05:00
|
|
|
use kittycad_modeling_cmds as kcmc;
|
2024-03-25 17:07:53 -07:00
|
|
|
use schemars::JsonSchema;
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
|
|
|
|
use crate::{
|
|
|
|
errors::KclError,
|
2025-01-07 19:10:53 -08:00
|
|
|
execution::{ExecState, Helix as HelixValue, KclValue, Solid},
|
|
|
|
std::{axis_or_reference::Axis3dOrEdgeReference, Args},
|
2024-03-25 17:07:53 -07:00
|
|
|
};
|
|
|
|
|
2025-01-07 19:10:53 -08:00
|
|
|
/// Data for a helix.
|
2024-03-25 17:07:53 -07:00
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
|
|
|
#[ts(export)]
|
|
|
|
pub struct HelixData {
|
2025-01-07 19:10:53 -08:00
|
|
|
/// 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,
|
2025-01-13 15:34:43 -08:00
|
|
|
/// 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.
|
|
|
|
pub length: Option<f64>,
|
2025-01-07 19:10:53 -08:00
|
|
|
/// Radius of the helix.
|
|
|
|
pub radius: f64,
|
|
|
|
/// Axis to use as mirror.
|
|
|
|
pub axis: Axis3dOrEdgeReference,
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Create a helix.
|
|
|
|
pub async fn helix(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
|
|
|
let data: HelixData = args.get_data()?;
|
|
|
|
|
|
|
|
let helix = inner_helix(data, exec_state, args).await?;
|
|
|
|
Ok(KclValue::Helix(helix))
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Create a helix.
|
|
|
|
///
|
|
|
|
/// ```no_run
|
|
|
|
/// // Create a helix around the Z axis.
|
|
|
|
/// helixPath = helix({
|
|
|
|
/// angleStart = 0,
|
|
|
|
/// ccw = true,
|
|
|
|
/// revolutions = 16,
|
|
|
|
/// length = 10,
|
|
|
|
/// radius = 5,
|
|
|
|
/// axis = 'Z',
|
|
|
|
/// })
|
|
|
|
///
|
|
|
|
///
|
|
|
|
/// // Create a spring by sweeping around the helix path.
|
|
|
|
/// springSketch = startSketchOn('YZ')
|
|
|
|
/// |> circle({ center = [0, 0], radius = 1 }, %)
|
|
|
|
/// //|> sweep({ path = helixPath }, %)
|
|
|
|
/// ```
|
|
|
|
///
|
|
|
|
/// ```no_run
|
|
|
|
/// // Create a helix around an edge.
|
2025-01-13 15:34:43 -08:00
|
|
|
/// helper001 = startSketchOn('XZ')
|
2025-01-07 19:10:53 -08:00
|
|
|
/// |> startProfileAt([0, 0], %)
|
|
|
|
/// |> line([0, 10], %, $edge001)
|
|
|
|
///
|
|
|
|
/// helixPath = helix({
|
|
|
|
/// angleStart = 0,
|
|
|
|
/// ccw = true,
|
|
|
|
/// revolutions = 16,
|
|
|
|
/// length = 10,
|
|
|
|
/// radius = 5,
|
|
|
|
/// axis = edge001,
|
|
|
|
/// })
|
|
|
|
///
|
|
|
|
/// // Create a spring by sweeping around the helix path.
|
|
|
|
/// springSketch = startSketchOn('XY')
|
|
|
|
/// |> circle({ center = [0, 0], radius = 1 }, %)
|
2025-01-13 15:34:43 -08:00
|
|
|
/// //|> sweep({ path = helixPath }, %)
|
2025-01-14 12:05:36 -08:00
|
|
|
/// ```
|
|
|
|
///
|
|
|
|
/// ```no_run
|
|
|
|
/// // Create a helix around a custom axis.
|
|
|
|
/// helixPath = helix({
|
|
|
|
/// angleStart = 0,
|
|
|
|
/// ccw = true,
|
|
|
|
/// revolutions = 16,
|
|
|
|
/// length = 10,
|
|
|
|
/// radius = 5,
|
|
|
|
/// axis = {
|
|
|
|
/// custom = {
|
|
|
|
/// axis = [0, 0, 1.0],
|
|
|
|
/// origin = [0, 0.25, 0]
|
|
|
|
/// }
|
|
|
|
/// }
|
|
|
|
/// })
|
|
|
|
///
|
|
|
|
/// // Create a spring by sweeping around the helix path.
|
|
|
|
/// springSketch = startSketchOn('XY')
|
|
|
|
/// |> circle({ center = [0, 0], radius = 1 }, %)
|
|
|
|
/// //|> sweep({ path = helixPath }, %)
|
2025-01-07 19:10:53 -08:00
|
|
|
/// ```
|
|
|
|
#[stdlib {
|
|
|
|
name = "helix",
|
|
|
|
feature_tree_operation = true,
|
|
|
|
}]
|
|
|
|
async fn inner_helix(data: HelixData, exec_state: &mut ExecState, args: Args) -> Result<Box<HelixValue>, KclError> {
|
|
|
|
let id = exec_state.next_uuid();
|
|
|
|
|
|
|
|
let helix_result = Box::new(HelixValue {
|
|
|
|
value: id,
|
|
|
|
revolutions: data.revolutions,
|
|
|
|
angle_start: data.angle_start,
|
|
|
|
ccw: data.ccw,
|
|
|
|
meta: vec![args.source_range.into()],
|
|
|
|
});
|
|
|
|
|
|
|
|
if args.ctx.is_mock() {
|
|
|
|
return Ok(helix_result);
|
|
|
|
}
|
|
|
|
|
|
|
|
match data.axis {
|
|
|
|
Axis3dOrEdgeReference::Axis(axis) => {
|
|
|
|
let (axis, origin) = axis.axis_and_origin()?;
|
|
|
|
|
2025-01-13 15:34:43 -08:00
|
|
|
// Make sure they gave us a length.
|
|
|
|
let Some(length) = data.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],
|
|
|
|
}));
|
|
|
|
};
|
|
|
|
|
2025-01-07 19:10:53 -08:00
|
|
|
args.batch_modeling_cmd(
|
|
|
|
exec_state.next_uuid(),
|
|
|
|
ModelingCmd::from(mcmd::EntityMakeHelixFromParams {
|
|
|
|
radius: data.radius,
|
|
|
|
is_clockwise: !data.ccw,
|
2025-01-13 15:34:43 -08:00
|
|
|
length: LengthUnit(length),
|
2025-01-07 19:10:53 -08:00
|
|
|
revolutions: data.revolutions,
|
|
|
|
start_angle: Angle::from_degrees(data.angle_start),
|
|
|
|
axis,
|
|
|
|
center: origin,
|
|
|
|
}),
|
|
|
|
)
|
|
|
|
.await?;
|
|
|
|
}
|
2025-01-13 15:34:43 -08:00
|
|
|
Axis3dOrEdgeReference::Edge(edge) => {
|
|
|
|
let edge_id = edge.get_engine_id(exec_state, &args)?;
|
2025-01-07 19:10:53 -08:00
|
|
|
|
|
|
|
args.batch_modeling_cmd(
|
|
|
|
exec_state.next_uuid(),
|
|
|
|
ModelingCmd::from(mcmd::EntityMakeHelixFromEdge {
|
|
|
|
radius: data.radius,
|
|
|
|
is_clockwise: !data.ccw,
|
2025-01-13 15:34:43 -08:00
|
|
|
length: data.length.map(LengthUnit),
|
2025-01-07 19:10:53 -08:00
|
|
|
revolutions: data.revolutions,
|
|
|
|
start_angle: Angle::from_degrees(data.angle_start),
|
|
|
|
edge_id,
|
|
|
|
}),
|
|
|
|
)
|
2025-01-13 15:34:43 -08:00
|
|
|
.await?;
|
2025-01-07 19:10:53 -08:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
Ok(helix_result)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Data for helix revolutions.
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
|
|
|
#[ts(export)]
|
|
|
|
pub struct HelixRevolutionsData {
|
2024-03-25 17:07:53 -07:00
|
|
|
/// Number of revolutions.
|
|
|
|
pub revolutions: f64,
|
|
|
|
/// Start angle (in degrees).
|
2025-01-05 17:49:16 -08:00
|
|
|
#[serde(rename = "angleStart")]
|
2024-03-25 17:07:53 -07:00
|
|
|
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
|
2024-09-27 15:44:44 -07:00
|
|
|
/// the solid is used.
|
2024-03-25 17:07:53 -07:00
|
|
|
pub length: Option<f64>,
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Create a helix on a cylinder.
|
2025-01-07 19:10:53 -08:00
|
|
|
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()?;
|
2024-03-25 17:07:53 -07:00
|
|
|
|
2025-01-07 19:10:53 -08:00
|
|
|
let solid = inner_helix_revolutions(data, solid, exec_state, args).await?;
|
2024-09-27 15:44:44 -07:00
|
|
|
Ok(KclValue::Solid(solid))
|
2024-03-25 17:07:53 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Create a helix on a cylinder.
|
|
|
|
///
|
|
|
|
/// ```no_run
|
2024-12-12 11:33:37 -05:00
|
|
|
/// part001 = startSketchOn('XY')
|
2024-09-23 22:42:51 +10:00
|
|
|
/// |> circle({ center: [5, 5], radius: 10 }, %)
|
2024-05-14 17:10:47 -07:00
|
|
|
/// |> extrude(10, %)
|
2025-01-07 19:10:53 -08:00
|
|
|
/// |> helixRevolutions({
|
2024-11-25 09:21:55 +13:00
|
|
|
/// angleStart = 0,
|
|
|
|
/// ccw = true,
|
|
|
|
/// revolutions = 16,
|
2024-05-14 17:10:47 -07:00
|
|
|
/// }, %)
|
2024-03-25 17:07:53 -07:00
|
|
|
/// ```
|
|
|
|
#[stdlib {
|
2025-01-07 19:10:53 -08:00
|
|
|
name = "helixRevolutions",
|
2024-12-16 13:10:31 -05:00
|
|
|
feature_tree_operation = true,
|
2024-03-25 17:07:53 -07:00
|
|
|
}]
|
2025-01-07 19:10:53 -08:00
|
|
|
async fn inner_helix_revolutions(
|
|
|
|
data: HelixRevolutionsData,
|
2024-10-09 19:38:40 -04:00
|
|
|
solid: Box<Solid>,
|
|
|
|
exec_state: &mut ExecState,
|
|
|
|
args: Args,
|
|
|
|
) -> Result<Box<Solid>, KclError> {
|
2024-12-17 09:38:32 +13:00
|
|
|
let id = exec_state.next_uuid();
|
2024-06-19 13:57:50 -07:00
|
|
|
args.batch_modeling_cmd(
|
2024-03-25 17:07:53 -07:00
|
|
|
id,
|
2024-09-18 17:04:04 -05:00
|
|
|
ModelingCmd::from(mcmd::EntityMakeHelix {
|
2024-09-27 15:44:44 -07:00
|
|
|
cylinder_id: solid.id,
|
2024-03-25 17:07:53 -07:00
|
|
|
is_clockwise: !data.ccw,
|
2024-09-27 15:44:44 -07:00
|
|
|
length: LengthUnit(data.length.unwrap_or(solid.height)),
|
2024-03-25 17:07:53 -07:00
|
|
|
revolutions: data.revolutions,
|
2024-09-18 17:04:04 -05:00
|
|
|
start_angle: Angle::from_degrees(data.angle_start),
|
|
|
|
}),
|
2024-03-25 17:07:53 -07:00
|
|
|
)
|
|
|
|
.await?;
|
|
|
|
|
2024-09-27 15:44:44 -07:00
|
|
|
Ok(solid)
|
2024-03-25 17:07:53 -07:00
|
|
|
}
|