Turn on units of measure (BREAKING CHANGE) (#6343)

* Turn on uom checks

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Convert all lengths to mm for engine calls

Signed-off-by: Nick Cameron <nrc@ncameron.org>

---------

Signed-off-by: Nick Cameron <nrc@ncameron.org>
This commit is contained in:
Nick Cameron
2025-04-23 10:58:35 +12:00
committed by GitHub
parent 3d22f6cd66
commit b7385d5f25
339 changed files with 35471 additions and 17237 deletions

View File

@ -11,10 +11,7 @@ use serde::Serialize;
use crate::{
errors::{KclError, KclErrorDetails},
execution::{
types::{NumericType, PrimitiveType, RuntimeType},
ExecState, KclValue, SolidOrImportedGeometry,
},
execution::{types::RuntimeType, ExecState, KclValue, SolidOrImportedGeometry},
std::Args,
};
@ -50,9 +47,8 @@ pub async fn appearance(exec_state: &mut ExecState, args: Args) -> Result<KclVal
)?;
let color: String = args.get_kw_arg("color")?;
let count_ty = RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()));
let metalness: Option<TyF64> = args.get_kw_arg_opt_typed("metalness", &count_ty, exec_state)?;
let roughness: Option<TyF64> = args.get_kw_arg_opt_typed("roughness", &count_ty, exec_state)?;
let metalness: Option<TyF64> = args.get_kw_arg_opt_typed("metalness", &RuntimeType::count(), exec_state)?;
let roughness: Option<TyF64> = args.get_kw_arg_opt_typed("roughness", &RuntimeType::count(), exec_state)?;
let data = AppearanceData {
color,
metalness,

View File

@ -14,7 +14,7 @@ use crate::{
errors::{KclError, KclErrorDetails},
execution::{
kcl_value::FunctionSource,
types::{NumericType, PrimitiveType, RuntimeType, UnitLen},
types::{NumericType, PrimitiveType, RuntimeType, UnitAngle, UnitLen, UnitType},
ExecState, ExecutorContext, ExtrudeSurface, Helix, KclObjectFields, KclValue, Metadata, Sketch, SketchSurface,
Solid, TagIdentifier,
},
@ -88,6 +88,34 @@ impl TyF64 {
Self { n, ty }
}
pub fn to_mm(&self) -> f64 {
self.to_length_units(UnitLen::Mm)
}
pub fn to_length_units(&self, units: UnitLen) -> f64 {
let len = match &self.ty {
NumericType::Default { len, .. } => *len,
NumericType::Known(UnitType::Length(len)) => *len,
t => unreachable!("expected length, found {t:?}"),
};
assert_ne!(len, UnitLen::Unknown);
len.adjust_to(self.n, units).0
}
pub fn to_degrees(&self) -> f64 {
let angle = match self.ty {
NumericType::Default { angle, .. } => angle,
NumericType::Known(UnitType::Angle(angle)) => angle,
_ => unreachable!(),
};
assert_ne!(angle, UnitAngle::Unknown);
angle.adjust_to(self.n, UnitAngle::Degrees).0
}
pub fn count(n: f64) -> Self {
Self {
n,
@ -547,6 +575,29 @@ impl Args {
FromArgs::from_args(self, 0)
}
pub(crate) fn get_number_typed(&self, ty: &RuntimeType, exec_state: &mut ExecState) -> Result<f64, KclError> {
let Some(arg) = self.args.first() else {
return Err(KclError::Semantic(KclErrorDetails {
message: "Expected an argument".to_owned(),
source_ranges: vec![self.source_range],
}));
};
arg.value.coerce(ty, exec_state).map_err(|_| {
let actual_type_name = arg.value.human_friendly_type();
let message = format!(
"This function expected the input argument to be {} but it's actually of type {actual_type_name}",
ty.human_friendly_type(),
);
KclError::Semantic(KclErrorDetails {
source_ranges: arg.source_ranges(),
message,
})
})?;
Ok(TyF64::from_kcl_val(&arg.value).unwrap().n)
}
pub(crate) fn get_number_array_with_types(&self) -> Result<Vec<TyF64>, KclError> {
let numbers = self
.args
@ -577,7 +628,7 @@ impl Args {
let mut numbers = numbers.into_iter();
let a = numbers.next().unwrap();
let b = numbers.next().unwrap();
Ok(NumericType::combine_eq(a, b))
Ok(NumericType::combine_eq_coerce(a, b))
}
pub(crate) fn get_sketches(&self, exec_state: &mut ExecState) -> Result<(Vec<Sketch>, Sketch), KclError> {
@ -657,8 +708,24 @@ impl Args {
FromArgs::from_args(self, 0)
}
pub(crate) fn get_data_and_solid(&self, exec_state: &mut ExecState) -> Result<(TyF64, Box<Solid>), KclError> {
let data = FromArgs::from_args(self, 0)?;
pub(crate) fn get_length_and_solid(&self, exec_state: &mut ExecState) -> Result<(TyF64, Box<Solid>), KclError> {
let Some(arg0) = self.args.first() else {
return Err(KclError::Semantic(KclErrorDetails {
message: "Expected a `number(Length)` for first argument".to_owned(),
source_ranges: vec![self.source_range],
}));
};
let val0 = arg0.value.coerce(&RuntimeType::length(), exec_state).map_err(|_| {
KclError::Type(KclErrorDetails {
message: format!(
"Expected a `number(Length)` for first argument, found {}",
arg0.value.human_friendly_type()
),
source_ranges: vec![self.source_range],
})
})?;
let data = TyF64::from_kcl_val(&val0).unwrap();
let Some(arg1) = self.args.get(1) else {
return Err(KclError::Semantic(KclErrorDetails {
message: "Expected a solid for second argument".to_owned(),

View File

@ -137,7 +137,7 @@ async fn inner_chamfer(
ModelingCmd::from(mcmd::Solid3dFilletEdge {
edge_id,
object_id: solid.id,
radius: LengthUnit(length.n),
radius: LengthUnit(length.to_mm()),
tolerance: LengthUnit(DEFAULT_TOLERANCE), // We can let the user set this in the future.
cut_type: CutType::Chamfer,
}),

View File

@ -40,9 +40,9 @@ pub async fn extrude(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
let result = inner_extrude(
sketches,
length.n,
length,
symmetric,
bidirectional_length.map(|t| t.n),
bidirectional_length,
tag_start,
tag_end,
exec_state,
@ -164,9 +164,9 @@ pub async fn extrude(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
#[allow(clippy::too_many_arguments)]
async fn inner_extrude(
sketches: Vec<Sketch>,
length: f64,
length: TyF64,
symmetric: Option<bool>,
bidirectional_length: Option<f64>,
bidirectional_length: Option<TyF64>,
tag_start: Option<TagNode>,
tag_end: Option<TagNode>,
exec_state: &mut ExecState,
@ -183,7 +183,7 @@ async fn inner_extrude(
}));
}
let bidirection = bidirectional_length.map(LengthUnit);
let bidirection = bidirectional_length.map(|l| LengthUnit(l.to_mm()));
let opposite = match (symmetric, bidirection) {
(Some(true), _) => Opposite::Symmetric,
@ -201,7 +201,7 @@ async fn inner_extrude(
cmd_id: id.into(),
cmd: ModelingCmd::from(mcmd::Extrude {
target: sketch.id.into(),
distance: LengthUnit(length),
distance: LengthUnit(length.to_mm()),
faces: Default::default(),
opposite: opposite.clone(),
}),
@ -213,7 +213,7 @@ async fn inner_extrude(
do_post_extrude(
sketch,
id.into(),
length,
length.clone(),
false,
&NamedCapTags {
start: tag_start.as_ref(),
@ -238,7 +238,7 @@ pub(crate) struct NamedCapTags<'a> {
pub(crate) async fn do_post_extrude<'a>(
sketch: &Sketch,
solid_id: ArtifactId,
length: f64,
length: TyF64,
sectional: bool,
named_cap_tags: &'a NamedCapTags<'a>,
exec_state: &mut ExecState,
@ -470,8 +470,8 @@ pub(crate) async fn do_post_extrude<'a>(
value: new_value,
meta: sketch.meta.clone(),
units: sketch.units,
height: length.to_length_units(sketch.units),
sketch,
height: length,
start_cap_id,
end_cap_id,
edge_cuts: vec![],

View File

@ -64,14 +64,14 @@ pub(super) fn validate_unique<T: Eq + std::hash::Hash>(tags: &[(T, SourceRange)]
pub async fn fillet(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let solid = args.get_unlabeled_kw_arg_typed("solid", &RuntimeType::solid(), exec_state)?;
let radius: TyF64 = args.get_kw_arg_typed("radius", &RuntimeType::length(), exec_state)?;
let tolerance: Option<TyF64> = args.get_kw_arg_opt_typed("tolerance", &RuntimeType::count(), exec_state)?;
let tolerance: Option<TyF64> = args.get_kw_arg_opt_typed("tolerance", &RuntimeType::length(), exec_state)?;
let tags = args.kw_arg_array_and_source::<EdgeReference>("tags")?;
let tag = args.get_kw_arg_opt("tag")?;
// Run the function.
validate_unique(&tags)?;
let tags: Vec<EdgeReference> = tags.into_iter().map(|item| item.0).collect();
let value = inner_fillet(solid, radius, tags, tolerance.map(|t| t.n), tag, exec_state, args).await?;
let value = inner_fillet(solid, radius, tags, tolerance, tag, exec_state, args).await?;
Ok(KclValue::Solid { value })
}
@ -148,7 +148,7 @@ async fn inner_fillet(
solid: Box<Solid>,
radius: TyF64,
tags: Vec<EdgeReference>,
tolerance: Option<f64>,
tolerance: Option<TyF64>,
tag: Option<TagNode>,
exec_state: &mut ExecState,
args: Args,
@ -163,8 +163,8 @@ async fn inner_fillet(
ModelingCmd::from(mcmd::Solid3dFilletEdge {
edge_id,
object_id: solid.id,
radius: LengthUnit(radius.n),
tolerance: LengthUnit(tolerance.unwrap_or(DEFAULT_TOLERANCE)),
radius: LengthUnit(radius.to_mm()),
tolerance: LengthUnit(tolerance.as_ref().map(|t| t.to_mm()).unwrap_or(DEFAULT_TOLERANCE)),
cut_type: CutType::Fillet,
}),
)

View File

@ -17,7 +17,7 @@ use super::args::TyF64;
/// Create a helix.
pub async fn helix(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let angle_start: TyF64 = args.get_kw_arg_typed("angleStart", &RuntimeType::angle(), exec_state)?;
let angle_start: TyF64 = args.get_kw_arg_typed("angleStart", &RuntimeType::degrees(), exec_state)?;
let revolutions: TyF64 = args.get_kw_arg_typed("revolutions", &RuntimeType::count(), exec_state)?;
let ccw = args.get_kw_arg_opt("ccw")?;
let radius: Option<TyF64> = args.get_kw_arg_opt_typed("radius", &RuntimeType::length(), exec_state)?;
@ -84,9 +84,9 @@ pub async fn helix(exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
revolutions.n,
angle_start.n,
ccw,
radius.map(|t| t.n),
radius,
axis,
length.map(|t| t.n),
length,
cylinder,
exec_state,
args,
@ -100,9 +100,9 @@ async fn inner_helix(
revolutions: f64,
angle_start: f64,
ccw: Option<bool>,
radius: Option<f64>,
radius: Option<TyF64>,
axis: Option<Axis3dOrEdgeReference>,
length: Option<f64>,
length: Option<TyF64>,
cylinder: Option<Solid>,
exec_state: &mut ExecState,
args: Args,
@ -130,7 +130,7 @@ async fn inner_helix(
ModelingCmd::from(mcmd::EntityMakeHelix {
cylinder_id: cylinder.id,
is_clockwise: !helix_result.ccw,
length: LengthUnit(length.unwrap_or(cylinder.height)),
length: LengthUnit(length.as_ref().map(|t| t.to_mm()).unwrap_or(cylinder.height_in_mm())),
revolutions,
start_angle: Angle::from_degrees(angle_start),
}),
@ -150,20 +150,20 @@ async fn inner_helix(
args.batch_modeling_cmd(
id,
ModelingCmd::from(mcmd::EntityMakeHelixFromParams {
radius: LengthUnit(radius),
radius: LengthUnit(radius.to_mm()),
is_clockwise: !helix_result.ccw,
length: LengthUnit(length),
length: LengthUnit(length.to_mm()),
revolutions,
start_angle: Angle::from_degrees(angle_start),
axis: Point3d {
x: direction[0].n,
y: direction[1].n,
z: direction[2].n,
x: direction[0].to_mm(),
y: direction[1].to_mm(),
z: direction[2].to_mm(),
},
center: Point3d {
x: LengthUnit(origin[0].n),
y: LengthUnit(origin[1].n),
z: LengthUnit(origin[2].n),
x: LengthUnit(origin[0].to_mm()),
y: LengthUnit(origin[1].to_mm()),
z: LengthUnit(origin[2].to_mm()),
},
}),
)
@ -175,9 +175,9 @@ async fn inner_helix(
args.batch_modeling_cmd(
id,
ModelingCmd::from(mcmd::EntityMakeHelixFromEdge {
radius: LengthUnit(radius),
radius: LengthUnit(radius.to_mm()),
is_clockwise: !helix_result.ccw,
length: length.map(LengthUnit),
length: length.map(|t| LengthUnit(t.to_mm())),
revolutions,
start_angle: Angle::from_degrees(angle_start),
edge_id,

View File

@ -10,7 +10,10 @@ use kittycad_modeling_cmds as kcmc;
use super::{args::TyF64, DEFAULT_TOLERANCE};
use crate::{
errors::{KclError, KclErrorDetails},
execution::{types::RuntimeType, ExecState, KclValue, Sketch, Solid},
execution::{
types::{NumericType, RuntimeType},
ExecState, KclValue, Sketch, Solid,
},
parsing::ast::types::TagNode,
std::{extrude::do_post_extrude, Args},
};
@ -30,7 +33,7 @@ pub async fn loft(exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kc
// This can be set to override the automatically determined topological base curve, which is usually the first section encountered.
let base_curve_index: Option<u32> = args.get_kw_arg_opt("baseCurveIndex")?;
// Tolerance for the loft operation.
let tolerance: Option<TyF64> = args.get_kw_arg_opt_typed("tolerance", &RuntimeType::count(), exec_state)?;
let tolerance: Option<TyF64> = args.get_kw_arg_opt_typed("tolerance", &RuntimeType::length(), exec_state)?;
let tag_start = args.get_kw_arg_opt("tagStart")?;
let tag_end = args.get_kw_arg_opt("tagEnd")?;
@ -39,7 +42,7 @@ pub async fn loft(exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kc
v_degree,
bez_approximate_rational,
base_curve_index,
tolerance.map(|t| t.n),
tolerance,
tag_start,
tag_end,
exec_state,
@ -136,7 +139,7 @@ async fn inner_loft(
v_degree: NonZeroU32,
bez_approximate_rational: bool,
base_curve_index: Option<u32>,
tolerance: Option<f64>,
tolerance: Option<TyF64>,
tag_start: Option<TagNode>,
tag_end: Option<TagNode>,
exec_state: &mut ExecState,
@ -160,7 +163,7 @@ async fn inner_loft(
section_ids: sketches.iter().map(|group| group.id).collect(),
base_curve_index,
bez_approximate_rational,
tolerance: LengthUnit(tolerance.unwrap_or(DEFAULT_TOLERANCE)),
tolerance: LengthUnit(tolerance.as_ref().map(|t| t.to_mm()).unwrap_or(DEFAULT_TOLERANCE)),
v_degree,
}),
)
@ -174,7 +177,7 @@ async fn inner_loft(
do_post_extrude(
&sketch,
id.into(),
0.0,
TyF64::new(0.0, NumericType::mm()),
false,
&super::extrude::NamedCapTags {
start: tag_start.as_ref(),

View File

@ -6,7 +6,7 @@ use kcl_derive_docs::stdlib;
use crate::{
errors::{KclError, KclErrorDetails},
execution::{
types::{self, NumericType, RuntimeType},
types::{NumericType, RuntimeType, UnitAngle, UnitType},
ExecState, KclValue,
},
std::args::{Args, TyF64},
@ -20,11 +20,10 @@ pub async fn rem(exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kcl
let d: TyF64 = args.get_kw_arg_typed("divisor", &RuntimeType::num_any(), exec_state)?;
let (n, d, ty) = NumericType::combine_div(n, d);
if *types::CHECK_NUMERIC_TYPES && ty == NumericType::Unknown {
// TODO suggest how to fix this
if ty == NumericType::Unknown {
exec_state.warn(CompilationError::err(
args.source_range,
"Remainder of numbers which have unknown or incompatible units.",
"Calling `rem` on numbers which have unknown or incompatible units.\n\nYou may need to add information about the type of the argument, for example:\n using a numeric suffix: `42{ty}`\n or using type ascription: `foo(): number({ty})`"
));
}
let remainder = inner_rem(n, d);
@ -59,20 +58,63 @@ fn inner_rem(num: f64, divisor: f64) -> f64 {
/// Compute the cosine of a number (in radians).
pub async fn cos(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let num: TyF64 = args.get_unlabeled_kw_arg_typed("input", &RuntimeType::radians(), exec_state)?;
Ok(args.make_user_val_from_f64_with_type(TyF64::count(num.n.cos())))
let num: TyF64 = args.get_unlabeled_kw_arg_typed("input", &RuntimeType::angle(), exec_state)?;
let num = match num.ty {
NumericType::Default {
angle: UnitAngle::Degrees,
..
} => {
exec_state.warn(CompilationError::err(
args.source_range,
"`cos` requires its input in radians, but the input is assumed to be in degrees. You can use a numeric suffix (e.g., `0rad`) or type ascription (e.g., `(1/2): number(rad)`) to show the number is in radians, or `toRadians` to convert from degrees to radians",
));
num.n
}
NumericType::Known(UnitType::Angle(UnitAngle::Degrees)) => num.n.to_radians(),
_ => num.n,
};
Ok(args.make_user_val_from_f64_with_type(TyF64::count(num.cos())))
}
/// Compute the sine of a number (in radians).
pub async fn sin(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let num: TyF64 = args.get_unlabeled_kw_arg_typed("input", &RuntimeType::radians(), exec_state)?;
Ok(args.make_user_val_from_f64_with_type(TyF64::count(num.n.sin())))
let num: TyF64 = args.get_unlabeled_kw_arg_typed("input", &RuntimeType::angle(), exec_state)?;
let num = match num.ty {
NumericType::Default {
angle: UnitAngle::Degrees,
..
} => {
exec_state.warn(CompilationError::err(
args.source_range,
"`sin` requires its input in radians, but the input is assumed to be in degrees. You can use a numeric suffix (e.g., `0rad`) or type ascription (e.g., `(1/2): number(rad)`) to show the number is in radians, or `toRadians` to convert from degrees to radians",
));
num.n
}
NumericType::Known(UnitType::Angle(UnitAngle::Degrees)) => num.n.to_radians(),
_ => num.n,
};
Ok(args.make_user_val_from_f64_with_type(TyF64::count(num.sin())))
}
/// Compute the tangent of a number (in radians).
pub async fn tan(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let num: TyF64 = args.get_unlabeled_kw_arg_typed("input", &RuntimeType::radians(), exec_state)?;
Ok(args.make_user_val_from_f64_with_type(TyF64::count(num.n.tan())))
let num: TyF64 = args.get_unlabeled_kw_arg_typed("input", &RuntimeType::angle(), exec_state)?;
let num = match num.ty {
NumericType::Default {
angle: UnitAngle::Degrees,
..
} => {
exec_state.warn(CompilationError::err(
args.source_range,
"`tan` requires its input in radians, but the input is assumed to be in degrees. You can use a numeric suffix (e.g., `0rad`) or type ascription (e.g., `(1/2): number(rad)`) to show the number is in radians, or `toRadians` to convert from degrees to radians",
));
num.n
}
NumericType::Known(UnitType::Angle(UnitAngle::Degrees)) => num.n.to_radians(),
_ => num.n,
};
Ok(args.make_user_val_from_f64_with_type(TyF64::count(num.tan())))
}
/// Return the value of `pi`. Archimedes constant (π).
@ -258,11 +300,10 @@ fn inner_ceil(num: f64) -> Result<f64, KclError> {
pub async fn min(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let nums = args.get_number_array_with_types()?;
let (nums, ty) = NumericType::combine_eq_array(&nums);
if *types::CHECK_NUMERIC_TYPES && ty == NumericType::Unknown {
// TODO suggest how to fix this
if ty == NumericType::Unknown {
exec_state.warn(CompilationError::err(
args.source_range,
"Calling `min` on numbers which have unknown or incompatible units.",
"Calling `min` on numbers which have unknown or incompatible units.\n\nYou may need to add information about the type of the argument, for example:\n using a numeric suffix: `42{ty}`\n or using type ascription: `foo(): number({ty})`",
));
}
let result = inner_min(nums);
@ -303,11 +344,10 @@ fn inner_min(args: Vec<f64>) -> f64 {
pub async fn max(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let nums = args.get_number_array_with_types()?;
let (nums, ty) = NumericType::combine_eq_array(&nums);
if *types::CHECK_NUMERIC_TYPES && ty == NumericType::Unknown {
// TODO suggest how to fix this
if ty == NumericType::Unknown {
exec_state.warn(CompilationError::err(
args.source_range,
"Calling `max` on numbers which have unknown or incompatible units.",
"Calling `max` on numbers which have unknown or incompatible units.\n\nYou may need to add information about the type of the argument, for example:\n using a numeric suffix: `42{ty}`\n or using type ascription: `foo(): number({ty})`",
));
}
let result = inner_max(nums);
@ -389,8 +429,20 @@ fn inner_pow(num: f64, pow: f64) -> Result<f64, KclError> {
}
/// Compute the arccosine of a number (in radians).
pub async fn acos(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
pub async fn acos(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let num = args.get_number_with_type()?;
if matches!(
num.ty,
NumericType::Default {
angle: UnitAngle::Degrees,
..
}
) {
exec_state.warn(CompilationError::err(
args.source_range,
"`acos` requires its input in radians, but the input is assumed to be in degrees. You can use a numeric suffix (e.g., `0rad`) or type ascription (e.g., `(1/2): number(rad)`) to show the number is in radians, or `toRadians` to convert from degrees to radians",
));
}
let result = inner_acos(num.n)?;
Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, NumericType::radians())))
@ -420,8 +472,20 @@ fn inner_acos(num: f64) -> Result<f64, KclError> {
}
/// Compute the arcsine of a number (in radians).
pub async fn asin(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
pub async fn asin(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let num = args.get_number_with_type()?;
if matches!(
num.ty,
NumericType::Default {
angle: UnitAngle::Degrees,
..
}
) {
exec_state.warn(CompilationError::err(
args.source_range,
"`asin` requires its input in radians, but the input is assumed to be in degrees. You can use a numeric suffix (e.g., `0rad`) or type ascription (e.g., `(1/2): number(rad)`) to show the number is in radians, or `toRadians` to convert from degrees to radians",
));
}
let result = inner_asin(num.n)?;
Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, NumericType::radians())))
@ -450,8 +514,20 @@ fn inner_asin(num: f64) -> Result<f64, KclError> {
}
/// Compute the arctangent of a number (in radians).
pub async fn atan(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
pub async fn atan(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let num = args.get_number_with_type()?;
if matches!(
num.ty,
NumericType::Default {
angle: UnitAngle::Degrees,
..
}
) {
exec_state.warn(CompilationError::err(
args.source_range,
"`atan` requires its input in radians, but the input is assumed to be in degrees. You can use a numeric suffix (e.g., `0rad`) or type ascription (e.g., `(1/2): number(rad)`) to show the number is in radians, or `toRadians` to convert from degrees to radians",
));
}
let result = inner_atan(num.n)?;
Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, NumericType::radians())))
@ -483,7 +559,7 @@ fn inner_atan(num: f64) -> Result<f64, KclError> {
pub async fn atan2(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let y = args.get_kw_arg_typed("y", &RuntimeType::length(), exec_state)?;
let x = args.get_kw_arg_typed("x", &RuntimeType::length(), exec_state)?;
let (y, x, _) = NumericType::combine_eq(y, x);
let (y, x, _) = NumericType::combine_eq_coerce(y, x);
let result = inner_atan2(y, x)?;
Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, NumericType::radians())))
@ -714,66 +790,6 @@ fn inner_tau() -> Result<f64, KclError> {
Ok(std::f64::consts::TAU)
}
/// Converts a number from degrees to radians.
pub async fn to_radians(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let num = args.get_number_with_type()?;
let result = inner_to_radians(num.n)?;
Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, NumericType::radians())))
}
/// Converts a number from degrees to radians.
///
/// ```no_run
/// exampleSketch = startSketchOn("XZ")
/// |> startProfileAt([0, 0], %)
/// |> angledLine(
/// angle = 50,
/// length = 70 * cos(toRadians(45)),
/// )
/// |> yLine(endAbsolute = 0)
/// |> close()
///
/// example = extrude(exampleSketch, length = 5)
/// ```
#[stdlib {
name = "toRadians",
tags = ["math"],
}]
fn inner_to_radians(num: f64) -> Result<f64, KclError> {
Ok(num.to_radians())
}
/// Converts a number from radians to degrees.
pub async fn to_degrees(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let num = args.get_number_with_type()?;
let result = inner_to_degrees(num.n)?;
Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, NumericType::degrees())))
}
/// Converts a number from radians to degrees.
///
/// ```no_run
/// exampleSketch = startSketchOn("XZ")
/// |> startProfileAt([0, 0], %)
/// |> angledLine(
/// angle = 50,
/// length = 70 * cos(toDegrees(pi()/4)),
/// )
/// |> yLine(endAbsolute = 0)
/// |> close()
///
/// example = extrude(exampleSketch, length = 5)
/// ```
#[stdlib {
name = "toDegrees",
tags = ["math"],
}]
fn inner_to_degrees(num: f64) -> Result<f64, KclError> {
Ok(num.to_degrees())
}
#[cfg(test)]
mod tests {
use pretty_assertions::assert_eq;

View File

@ -61,13 +61,13 @@ async fn inner_mirror_2d(
ModelingCmd::from(mcmd::EntityMirror {
ids: starting_sketches.iter().map(|sketch| sketch.id).collect(),
axis: Point3d {
x: direction[0].n,
y: direction[1].n,
x: direction[0].to_mm(),
y: direction[1].to_mm(),
z: 0.0,
},
point: Point3d {
x: LengthUnit(origin[0].n),
y: LengthUnit(origin[1].n),
x: LengthUnit(origin[0].to_mm()),
y: LengthUnit(origin[1].to_mm()),
z: LengthUnit(0.0),
},
}),

View File

@ -128,8 +128,6 @@ lazy_static! {
Box::new(crate::std::math::Log2),
Box::new(crate::std::math::Log10),
Box::new(crate::std::math::Ln),
Box::new(crate::std::math::ToDegrees),
Box::new(crate::std::math::ToRadians),
Box::new(crate::std::units::FromMm),
Box::new(crate::std::units::FromInches),
Box::new(crate::std::units::FromFt),

View File

@ -13,12 +13,12 @@ use kittycad_modeling_cmds::{
shared::{Angle, OriginType, Rotation},
};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use serde::Serialize;
use uuid::Uuid;
use super::{
args::Arg,
utils::{untype_point, untype_point_3d},
utils::{point_3d_to_mm, point_to_mm},
};
use crate::{
errors::{KclError, KclErrorDetails},
@ -34,7 +34,7 @@ use crate::{
const MUST_HAVE_ONE_INSTANCE: &str = "There must be at least 1 instance of your geometry";
/// Data for a linear pattern on a 3D model.
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
pub struct LinearPattern3dData {
@ -44,9 +44,9 @@ pub struct LinearPattern3dData {
/// If instances is 1, this has no effect.
pub instances: u32,
/// The distance between each repetition. This can also be referred to as spacing.
pub distance: f64,
pub distance: TyF64,
/// The axis of the pattern.
pub axis: [f64; 3],
pub axis: [TyF64; 3],
}
/// Repeat some 3D solid, changing each repetition slightly.
@ -219,7 +219,7 @@ pub async fn pattern_transform_2d(exec_state: &mut ExecState, args: Args) -> Res
/// // Defines how to modify each layer of the vase.
/// // Each replica is shifted up the Z axis, and has a smoothly-varying radius
/// fn transform(replicaId) {
/// scale = r * abs(1 - (t * replicaId)) * (5 + cos(replicaId / 8))
/// scale = r * abs(1 - (t * replicaId)) * (5 + cos((replicaId / 8): number(rad)))
/// return {
/// translate = [0, 0, replicaId * 10],
/// scale = [scale, scale, 0],
@ -498,15 +498,13 @@ fn transform_from_obj_fields<T: GeometryTrait>(
};
let scale = match transform.get("scale") {
Some(x) => untype_point_3d(T::array_to_point3d(x, source_ranges.clone(), exec_state)?)
.0
.into(),
Some(x) => point_3d_to_mm(T::array_to_point3d(x, source_ranges.clone(), exec_state)?).into(),
None => kcmc::shared::Point3d { x: 1.0, y: 1.0, z: 1.0 },
};
let translate = match transform.get("translate") {
Some(x) => {
let (arr, _) = untype_point_3d(T::array_to_point3d(x, source_ranges.clone(), exec_state)?);
let arr = point_3d_to_mm(T::array_to_point3d(x, source_ranges.clone(), exec_state)?);
kcmc::shared::Point3d::<LengthUnit> {
x: LengthUnit(arr[0]),
y: LengthUnit(arr[1]),
@ -530,9 +528,7 @@ fn transform_from_obj_fields<T: GeometryTrait>(
}));
};
if let Some(axis) = rot.get("axis") {
rotation.axis = untype_point_3d(T::array_to_point3d(axis, source_ranges.clone(), exec_state)?)
.0
.into();
rotation.axis = point_3d_to_mm(T::array_to_point3d(axis, source_ranges.clone(), exec_state)?).into();
}
if let Some(angle) = rot.get("angle") {
match angle {
@ -552,9 +548,7 @@ fn transform_from_obj_fields<T: GeometryTrait>(
KclValue::String { value: s, meta: _ } if s == "local" => OriginType::Local,
KclValue::String { value: s, meta: _ } if s == "global" => OriginType::Global,
other => {
let origin = untype_point_3d(T::array_to_point3d(other, source_ranges.clone(), exec_state)?)
.0
.into();
let origin = point_3d_to_mm(T::array_to_point3d(other, source_ranges.clone(), exec_state)?).into();
OriginType::Custom { origin }
}
};
@ -721,8 +715,7 @@ pub async fn pattern_linear_2d(exec_state: &mut ExecState, args: Args) -> Result
let axis: [TyF64; 2] = args.get_kw_arg_typed("axis", &RuntimeType::point2d(), exec_state)?;
let use_original: Option<bool> = args.get_kw_arg_opt("useOriginal")?;
let axis = untype_point(axis).0;
if axis == [0.0, 0.0] {
if axis[0].n == 0.0 && axis[1].n == 0.0 {
return Err(KclError::Semantic(KclErrorDetails {
message:
"The axis of the linear pattern cannot be the zero vector. Otherwise they will just duplicate in place."
@ -731,8 +724,7 @@ pub async fn pattern_linear_2d(exec_state: &mut ExecState, args: Args) -> Result
}));
}
let sketches =
inner_pattern_linear_2d(sketches, instances, distance.n, axis, use_original, exec_state, args).await?;
let sketches = inner_pattern_linear_2d(sketches, instances, distance, axis, use_original, exec_state, args).await?;
Ok(sketches.into())
}
@ -765,18 +757,18 @@ pub async fn pattern_linear_2d(exec_state: &mut ExecState, args: Args) -> Result
async fn inner_pattern_linear_2d(
sketches: Vec<Sketch>,
instances: u32,
distance: f64,
axis: [f64; 2],
distance: TyF64,
axis: [TyF64; 2],
use_original: Option<bool>,
exec_state: &mut ExecState,
args: Args,
) -> Result<Vec<Sketch>, KclError> {
let [x, y] = axis;
let [x, y] = point_to_mm(axis);
let axis_len = f64::sqrt(x * x + y * y);
let normalized_axis = kcmc::shared::Point2d::from([x / axis_len, y / axis_len]);
let transforms: Vec<_> = (1..instances)
.map(|i| {
let d = distance * (i as f64);
let d = distance.to_mm() * (i as f64);
let translate = (normalized_axis * d).with_z(0.0).map(LengthUnit);
vec![Transform {
translate,
@ -802,8 +794,7 @@ pub async fn pattern_linear_3d(exec_state: &mut ExecState, args: Args) -> Result
let axis: [TyF64; 3] = args.get_kw_arg_typed("axis", &RuntimeType::point3d(), exec_state)?;
let use_original: Option<bool> = args.get_kw_arg_opt("useOriginal")?;
let (axis, _) = untype_point_3d(axis);
if axis == [0.0, 0.0, 0.0] {
if axis[0].n == 0.0 && axis[1].n == 0.0 && axis[2].n == 0.0 {
return Err(KclError::Semantic(KclErrorDetails {
message:
"The axis of the linear pattern cannot be the zero vector. Otherwise they will just duplicate in place."
@ -812,7 +803,7 @@ pub async fn pattern_linear_3d(exec_state: &mut ExecState, args: Args) -> Result
}));
}
let solids = inner_pattern_linear_3d(solids, instances, distance.n, axis, use_original, exec_state, args).await?;
let solids = inner_pattern_linear_3d(solids, instances, distance, axis, use_original, exec_state, args).await?;
Ok(solids.into())
}
@ -903,18 +894,18 @@ pub async fn pattern_linear_3d(exec_state: &mut ExecState, args: Args) -> Result
async fn inner_pattern_linear_3d(
solids: Vec<Solid>,
instances: u32,
distance: f64,
axis: [f64; 3],
distance: TyF64,
axis: [TyF64; 3],
use_original: Option<bool>,
exec_state: &mut ExecState,
args: Args,
) -> Result<Vec<Solid>, KclError> {
let [x, y, z] = axis;
let [x, y, z] = point_3d_to_mm(axis);
let axis_len = f64::sqrt(x * x + y * y + z * z);
let normalized_axis = kcmc::shared::Point3d::from([x / axis_len, y / axis_len, z / axis_len]);
let transforms: Vec<_> = (1..instances)
.map(|i| {
let d = distance * (i as f64);
let d = distance.to_mm() * (i as f64);
let translate = (normalized_axis * d).map(LengthUnit);
vec![Transform {
translate,
@ -926,7 +917,7 @@ async fn inner_pattern_linear_3d(
}
/// Data for a circular pattern on a 2D sketch.
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
struct CircularPattern2dData {
@ -936,7 +927,7 @@ struct CircularPattern2dData {
/// If instances is 1, this has no effect.
pub instances: u32,
/// The center about which to make the pattern. This is a 2D vector.
pub center: [f64; 2],
pub center: [TyF64; 2],
/// The arc angle (in degrees) to place the repetitions. Must be greater than 0.
pub arc_degrees: f64,
/// Whether or not to rotate the duplicates as they are copied.
@ -948,7 +939,7 @@ struct CircularPattern2dData {
}
/// Data for a circular pattern on a 3D model.
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
pub struct CircularPattern3dData {
@ -958,9 +949,10 @@ pub struct CircularPattern3dData {
/// If instances is 1, this has no effect.
pub instances: u32,
/// The axis around which to make the pattern. This is a 3D vector.
// Only the direction should matter, not the magnitude so don't adjust units to avoid normalisation issues.
pub axis: [f64; 3],
/// The center about which to make the pattern. This is a 3D vector.
pub center: [f64; 3],
pub center: [TyF64; 3],
/// The arc angle (in degrees) to place the repetitions. Must be greater than 0.
pub arc_degrees: f64,
/// Whether or not to rotate the duplicates as they are copied.
@ -971,6 +963,7 @@ pub struct CircularPattern3dData {
pub use_original: Option<bool>,
}
#[allow(clippy::large_enum_variant)]
enum CircularPattern {
ThreeD(CircularPattern3dData),
TwoD(CircularPattern2dData),
@ -999,14 +992,14 @@ impl CircularPattern {
pub fn axis(&self) -> [f64; 3] {
match self {
CircularPattern::TwoD(_lp) => [0.0, 0.0, 0.0],
CircularPattern::ThreeD(lp) => lp.axis,
CircularPattern::ThreeD(lp) => [lp.axis[0], lp.axis[1], lp.axis[2]],
}
}
pub fn center(&self) -> [f64; 3] {
pub fn center_mm(&self) -> [f64; 3] {
match self {
CircularPattern::TwoD(lp) => [lp.center[0], lp.center[1], 0.0],
CircularPattern::ThreeD(lp) => lp.center,
CircularPattern::TwoD(lp) => [lp.center[0].to_mm(), lp.center[1].to_mm(), 0.0],
CircularPattern::ThreeD(lp) => [lp.center[0].to_mm(), lp.center[1].to_mm(), lp.center[2].to_mm()],
}
}
@ -1045,14 +1038,14 @@ pub async fn pattern_circular_2d(exec_state: &mut ExecState, args: Args) -> Resu
let sketches = args.get_unlabeled_kw_arg_typed("sketches", &RuntimeType::sketches(), exec_state)?;
let instances: u32 = args.get_kw_arg("instances")?;
let center: [TyF64; 2] = args.get_kw_arg_typed("center", &RuntimeType::point2d(), exec_state)?;
let arc_degrees: TyF64 = args.get_kw_arg_typed("arcDegrees", &RuntimeType::angle(), exec_state)?;
let arc_degrees: TyF64 = args.get_kw_arg_typed("arcDegrees", &RuntimeType::degrees(), exec_state)?;
let rotate_duplicates: bool = args.get_kw_arg("rotateDuplicates")?;
let use_original: Option<bool> = args.get_kw_arg_opt("useOriginal")?;
let sketches = inner_pattern_circular_2d(
sketches,
instances,
untype_point(center).0,
center,
arc_degrees.n,
rotate_duplicates,
use_original,
@ -1101,7 +1094,7 @@ pub async fn pattern_circular_2d(exec_state: &mut ExecState, args: Args) -> Resu
async fn inner_pattern_circular_2d(
sketch_set: Vec<Sketch>,
instances: u32,
center: [f64; 2],
center: [TyF64; 2],
arc_degrees: f64,
rotate_duplicates: bool,
use_original: Option<bool>,
@ -1151,13 +1144,13 @@ pub async fn pattern_circular_3d(exec_state: &mut ExecState, args: Args) -> Resu
// This includes the original entity. For example, if instances is 2,
// there will be two copies -- the original, and one new copy.
// If instances is 1, this has no effect.
let instances: u32 = args.get_kw_arg("instances")?;
let instances: u32 = args.get_kw_arg_typed("instances", &RuntimeType::count(), exec_state)?;
// The axis around which to make the pattern. This is a 3D vector.
let axis: [TyF64; 3] = args.get_kw_arg_typed("axis", &RuntimeType::point3d(), exec_state)?;
// The center about which to make the pattern. This is a 3D vector.
let center: [TyF64; 3] = args.get_kw_arg_typed("center", &RuntimeType::point3d(), exec_state)?;
// The arc angle (in degrees) to place the repetitions. Must be greater than 0.
let arc_degrees: TyF64 = args.get_kw_arg_typed("arcDegrees", &RuntimeType::angle(), exec_state)?;
let arc_degrees: TyF64 = args.get_kw_arg_typed("arcDegrees", &RuntimeType::degrees(), exec_state)?;
// Whether or not to rotate the duplicates as they are copied.
let rotate_duplicates: bool = args.get_kw_arg("rotateDuplicates")?;
// If the target being patterned is itself a pattern, then, should you use the original solid,
@ -1167,8 +1160,8 @@ pub async fn pattern_circular_3d(exec_state: &mut ExecState, args: Args) -> Resu
let solids = inner_pattern_circular_3d(
solids,
instances,
untype_point_3d(axis).0,
untype_point_3d(center).0,
[axis[0].n, axis[1].n, axis[2].n],
center,
arc_degrees.n,
rotate_duplicates,
use_original,
@ -1217,7 +1210,7 @@ async fn inner_pattern_circular_3d(
solids: Vec<Solid>,
instances: u32,
axis: [f64; 3],
center: [f64; 3],
center: [TyF64; 3],
arc_degrees: f64,
rotate_duplicates: bool,
use_original: Option<bool>,
@ -1286,7 +1279,7 @@ async fn pattern_circular(
}
};
let center = data.center();
let center = data.center_mm();
let resp = args
.send_modeling_cmd(
id,

View File

@ -15,8 +15,7 @@ use crate::{
pub async fn offset_plane(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let std_plane = args.get_unlabeled_kw_arg("plane")?;
let offset: TyF64 = args.get_kw_arg_typed("offset", &RuntimeType::length(), exec_state)?;
let plane = inner_offset_plane(std_plane, offset.n, exec_state).await?;
make_offset_plane_in_engine(&plane, exec_state, &args).await?;
let plane = inner_offset_plane(std_plane, offset, exec_state, &args).await?;
Ok(KclValue::Plane { value: Box::new(plane) })
}
@ -112,13 +111,19 @@ pub async fn offset_plane(exec_state: &mut ExecState, args: Args) -> Result<KclV
offset = { docs = "Distance from the standard plane this new plane will be created at." },
}
}]
async fn inner_offset_plane(plane: PlaneData, offset: f64, exec_state: &mut ExecState) -> Result<Plane, KclError> {
async fn inner_offset_plane(
plane: PlaneData,
offset: TyF64,
exec_state: &mut ExecState,
args: &Args,
) -> Result<Plane, KclError> {
let mut plane = Plane::from_plane_data(plane, exec_state);
// Though offset planes might be derived from standard planes, they are not
// standard planes themselves.
plane.value = PlaneType::Custom;
plane.origin += plane.z_axis * offset;
plane.origin += plane.z_axis * offset.to_length_units(plane.origin.units);
make_offset_plane_in_engine(&plane, exec_state, args).await?;
Ok(plane)
}

View File

@ -13,7 +13,7 @@ use super::{args::TyF64, DEFAULT_TOLERANCE};
use crate::{
errors::{KclError, KclErrorDetails},
execution::{
types::{PrimitiveType, RuntimeType},
types::{NumericType, PrimitiveType, RuntimeType},
ExecState, KclValue, Sketch, Solid,
},
parsing::ast::types::TagNode,
@ -31,8 +31,8 @@ pub async fn revolve(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
]),
exec_state,
)?;
let angle: Option<TyF64> = args.get_kw_arg_opt_typed("angle", &RuntimeType::angle(), exec_state)?;
let tolerance: Option<TyF64> = args.get_kw_arg_opt_typed("tolerance", &RuntimeType::count(), exec_state)?;
let angle: Option<TyF64> = args.get_kw_arg_opt_typed("angle", &RuntimeType::degrees(), exec_state)?;
let tolerance: Option<TyF64> = args.get_kw_arg_opt_typed("tolerance", &RuntimeType::length(), exec_state)?;
let tag_start = args.get_kw_arg_opt("tagStart")?;
let tag_end = args.get_kw_arg_opt("tagEnd")?;
let symmetric = args.get_kw_arg_opt("symmetric")?;
@ -43,7 +43,7 @@ pub async fn revolve(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
sketches,
axis,
angle.map(|t| t.n),
tolerance.map(|t| t.n),
tolerance,
tag_start,
tag_end,
symmetric,
@ -60,7 +60,7 @@ async fn inner_revolve(
sketches: Vec<Sketch>,
axis: Axis2dOrEdgeReference,
angle: Option<f64>,
tolerance: Option<f64>,
tolerance: Option<TyF64>,
tag_start: Option<TagNode>,
tag_end: Option<TagNode>,
symmetric: Option<bool>,
@ -140,16 +140,16 @@ async fn inner_revolve(
angle,
target: sketch.id.into(),
axis: Point3d {
x: direction[0].n,
y: direction[1].n,
x: direction[0].to_mm(),
y: direction[1].to_mm(),
z: 0.0,
},
origin: Point3d {
x: LengthUnit(origin[0].n),
y: LengthUnit(origin[1].n),
x: LengthUnit(origin[0].to_mm()),
y: LengthUnit(origin[1].to_mm()),
z: LengthUnit(0.0),
},
tolerance: LengthUnit(tolerance.unwrap_or(DEFAULT_TOLERANCE)),
tolerance: LengthUnit(tolerance.as_ref().map(|t| t.to_mm()).unwrap_or(DEFAULT_TOLERANCE)),
axis_is_2d: true,
opposite: opposite.clone(),
}),
@ -164,7 +164,7 @@ async fn inner_revolve(
angle,
target: sketch.id.into(),
edge_id,
tolerance: LengthUnit(tolerance.unwrap_or(DEFAULT_TOLERANCE)),
tolerance: LengthUnit(tolerance.as_ref().map(|t| t.to_mm()).unwrap_or(DEFAULT_TOLERANCE)),
opposite: opposite.clone(),
}),
)
@ -176,7 +176,7 @@ async fn inner_revolve(
do_post_extrude(
sketch,
id.into(),
0.0,
TyF64::new(0.0, NumericType::mm()),
false,
&super::extrude::NamedCapTags {
start: tag_start.as_ref(),

View File

@ -464,7 +464,7 @@ fn inner_segment_angle(tag: &TagIdentifier, exec_state: &mut ExecState, args: Ar
})
})?;
let result = between(path.get_from().into(), path.get_to().into());
let result = between(path.get_base().from, path.get_base().to);
Ok(result.to_degrees())
}
@ -584,7 +584,7 @@ async fn inner_tangent_to_end(tag: &TagIdentifier, exec_state: &mut ExecState, a
/// Returns the angle to match the given length for x.
pub async fn angle_to_match_length_x(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let (tag, to, sketch) = args.get_tag_to_number_sketch()?;
let result = inner_angle_to_match_length_x(&tag, to.n, sketch, exec_state, args.clone())?;
let result = inner_angle_to_match_length_x(&tag, to, sketch, exec_state, args.clone())?;
Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, NumericType::degrees())))
}
@ -607,7 +607,7 @@ pub async fn angle_to_match_length_x(exec_state: &mut ExecState, args: Args) ->
}]
fn inner_angle_to_match_length_x(
tag: &TagIdentifier,
to: f64,
to: TyF64,
sketch: Sketch,
exec_state: &mut ExecState,
args: Args,
@ -633,8 +633,7 @@ fn inner_angle_to_match_length_x(
})?
.get_base();
// TODO assumption about the units of to
let diff = (to - last_line.to[0]).abs();
let diff = (to.to_length_units(sketch.units) - last_line.to[0]).abs();
let angle_r = (diff / length).acos();
@ -648,7 +647,7 @@ fn inner_angle_to_match_length_x(
/// Returns the angle to match the given length for y.
pub async fn angle_to_match_length_y(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let (tag, to, sketch) = args.get_tag_to_number_sketch()?;
let result = inner_angle_to_match_length_y(&tag, to.n, sketch, exec_state, args.clone())?;
let result = inner_angle_to_match_length_y(&tag, to, sketch, exec_state, args.clone())?;
Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, NumericType::degrees())))
}
@ -672,7 +671,7 @@ pub async fn angle_to_match_length_y(exec_state: &mut ExecState, args: Args) ->
}]
fn inner_angle_to_match_length_y(
tag: &TagIdentifier,
to: f64,
to: TyF64,
sketch: Sketch,
exec_state: &mut ExecState,
args: Args,
@ -698,8 +697,7 @@ fn inner_angle_to_match_length_y(
})?
.get_base();
// TODO assumption about the units of to
let diff = (to - last_line.to[1]).abs();
let diff = (to.to_length_units(sketch.units) - last_line.to[1]).abs();
let angle_r = (diff / length).asin();

View File

@ -13,10 +13,16 @@ use kittycad_modeling_cmds::shared::PathSegment;
use schemars::JsonSchema;
use serde::Serialize;
use super::{args::TyF64, utils::untype_point};
use super::{
args::TyF64,
utils::{point_to_len_unit, point_to_mm, point_to_typed, untype_point, untyped_point_to_mm},
};
use crate::{
errors::{KclError, KclErrorDetails},
execution::{types::RuntimeType, BasePath, ExecState, GeoMeta, KclValue, Path, Sketch, SketchSurface},
execution::{
types::{RuntimeType, UnitLen},
BasePath, ExecState, GeoMeta, KclValue, Path, Sketch, SketchSurface,
},
parsing::ast::types::TagNode,
std::{
sketch::NEW_TAG_KW,
@ -41,15 +47,7 @@ pub async fn circle(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
let radius: TyF64 = args.get_kw_arg_typed("radius", &RuntimeType::length(), exec_state)?;
let tag = args.get_kw_arg_opt(NEW_TAG_KW)?;
let sketch = inner_circle(
sketch_or_surface,
untype_point(center).0,
radius.n,
tag,
exec_state,
args,
)
.await?;
let sketch = inner_circle(sketch_or_surface, center, radius, tag, exec_state, args).await?;
Ok(KclValue::Sketch {
value: Box::new(sketch),
})
@ -57,8 +55,8 @@ pub async fn circle(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
async fn inner_circle(
sketch_or_surface: SketchOrSurface,
center: [f64; 2],
radius: f64,
center: [TyF64; 2],
radius: TyF64,
tag: Option<TagNode>,
exec_state: &mut ExecState,
args: Args,
@ -67,17 +65,15 @@ async fn inner_circle(
SketchOrSurface::SketchSurface(surface) => surface,
SketchOrSurface::Sketch(s) => s.on,
};
let units = sketch_surface.units();
let sketch = crate::std::sketch::inner_start_profile_at(
[center[0] + radius, center[1]],
sketch_surface,
None,
exec_state,
args.clone(),
)
.await?;
let (center_u, ty) = untype_point(center.clone());
let units = ty.expect_length();
let from = [center_u[0] + radius.to_length_units(units), center_u[1]];
let from_t = [TyF64::new(from[0], ty.clone()), TyF64::new(from[1], ty)];
let sketch =
crate::std::sketch::inner_start_profile_at(from_t, sketch_surface, None, exec_state, args.clone()).await?;
let from = [center[0] + radius, center[1]];
let angle_start = Angle::zero();
let angle_end = Angle::turn();
@ -90,8 +86,8 @@ async fn inner_circle(
segment: PathSegment::Arc {
start: angle_start,
end: angle_end,
center: KPoint2d::from(center).map(LengthUnit),
radius: radius.into(),
center: KPoint2d::from(point_to_mm(center)).map(LengthUnit),
radius: LengthUnit(radius.to_mm()),
relative: false,
},
}),
@ -109,8 +105,8 @@ async fn inner_circle(
metadata: args.source_range.into(),
},
},
radius,
center,
radius: radius.to_length_units(units),
center: center_u,
ccw: angle_start < angle_end,
};
@ -135,16 +131,7 @@ pub async fn circle_three_point(exec_state: &mut ExecState, args: Args) -> Resul
let p3 = args.get_kw_arg_typed("p3", &RuntimeType::point2d(), exec_state)?;
let tag = args.get_kw_arg_opt("tag")?;
let sketch = inner_circle_three_point(
sketch_surface_or_group,
untype_point(p1).0,
untype_point(p2).0,
untype_point(p3).0,
tag,
exec_state,
args,
)
.await?;
let sketch = inner_circle_three_point(sketch_surface_or_group, p1, p2, p3, tag, exec_state, args).await?;
Ok(KclValue::Sketch {
value: Box::new(sketch),
})
@ -174,13 +161,20 @@ pub async fn circle_three_point(exec_state: &mut ExecState, args: Args) -> Resul
// path so it can be used for other features, otherwise it's lost.
async fn inner_circle_three_point(
sketch_surface_or_group: SketchOrSurface,
p1: [f64; 2],
p2: [f64; 2],
p3: [f64; 2],
p1: [TyF64; 2],
p2: [TyF64; 2],
p3: [TyF64; 2],
tag: Option<TagNode>,
exec_state: &mut ExecState,
args: Args,
) -> Result<Sketch, KclError> {
let ty = p1[0].ty.clone();
let units = ty.expect_length();
let p1 = point_to_len_unit(p1, units);
let p2 = point_to_len_unit(p2, units);
let p3 = point_to_len_unit(p3, units);
let center = calculate_circle_center(p1, p2, p3);
// It can be the distance to any of the 3 points - they all lay on the circumference.
let radius = distance(center, p2);
@ -189,16 +183,15 @@ async fn inner_circle_three_point(
SketchOrSurface::SketchSurface(surface) => surface,
SketchOrSurface::Sketch(group) => group.on,
};
let sketch = crate::std::sketch::inner_start_profile_at(
[center[0] + radius, center[1]],
sketch_surface,
None,
exec_state,
args.clone(),
)
.await?;
let from = [center[0] + radius, center[1]];
let from = [
TyF64::new(center[0] + radius, ty.clone()),
TyF64::new(center[1], ty.clone()),
];
let sketch =
crate::std::sketch::inner_start_profile_at(from.clone(), sketch_surface, None, exec_state, args.clone())
.await?;
let angle_start = Angle::zero();
let angle_end = Angle::turn();
@ -211,8 +204,8 @@ async fn inner_circle_three_point(
segment: PathSegment::Arc {
start: angle_start,
end: angle_end,
center: KPoint2d::from(center).map(LengthUnit),
radius: radius.into(),
center: KPoint2d::from(untyped_point_to_mm(center, units)).map(LengthUnit),
radius: units.adjust_to(radius, UnitLen::Mm).0.into(),
relative: false,
},
}),
@ -221,10 +214,11 @@ async fn inner_circle_three_point(
let current_path = Path::CircleThreePoint {
base: BasePath {
from,
to: from,
// It's fine to untype here because we know `from` has units as its units.
from: untype_point(from.clone()).0,
to: untype_point(from).0,
tag: tag.clone(),
units: sketch.units,
units,
geo_meta: GeoMeta {
id,
metadata: args.source_range.into(),
@ -270,7 +264,7 @@ pub async fn polygon(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
sketch_surface_or_group,
radius,
num_sides.n as u64,
untype_point(center).0,
center,
inscribed,
exec_state,
args,
@ -324,7 +318,7 @@ async fn inner_polygon(
sketch_surface_or_group: SketchOrSurface,
radius: TyF64,
num_sides: u64,
center: [f64; 2],
center: [TyF64; 2],
inscribed: Option<bool>,
exec_state: &mut ExecState,
args: Args,
@ -343,9 +337,9 @@ async fn inner_polygon(
}));
}
let sketch_surface = match sketch_surface_or_group {
SketchOrSurface::SketchSurface(surface) => surface,
SketchOrSurface::Sketch(group) => group.on,
let (sketch_surface, units) = match sketch_surface_or_group {
SketchOrSurface::SketchSurface(surface) => (surface, radius.ty.expect_length()),
SketchOrSurface::Sketch(group) => (group.on, group.units),
};
let half_angle = std::f64::consts::PI / num_sides as f64;
@ -360,18 +354,26 @@ async fn inner_polygon(
let angle_step = std::f64::consts::TAU / num_sides as f64;
let center_u = point_to_len_unit(center, units);
let vertices: Vec<[f64; 2]> = (0..num_sides)
.map(|i| {
let angle = angle_step * i as f64;
[
center[0] + radius_to_vertices * angle.cos(),
center[1] + radius_to_vertices * angle.sin(),
center_u[0] + radius_to_vertices * angle.cos(),
center_u[1] + radius_to_vertices * angle.sin(),
]
})
.collect();
let mut sketch =
crate::std::sketch::inner_start_profile_at(vertices[0], sketch_surface, None, exec_state, args.clone()).await?;
let mut sketch = crate::std::sketch::inner_start_profile_at(
point_to_typed(vertices[0], units),
sketch_surface,
None,
exec_state,
args.clone(),
)
.await?;
// Draw all the lines with unique IDs and modified tags
for vertex in vertices.iter().skip(1) {
@ -383,7 +385,9 @@ async fn inner_polygon(
ModelingCmd::from(mcmd::ExtendPath {
path: sketch.id.into(),
segment: PathSegment::Line {
end: KPoint2d::from(*vertex).with_z(0.0).map(LengthUnit),
end: KPoint2d::from(untyped_point_to_mm(*vertex, units))
.with_z(0.0)
.map(LengthUnit),
relative: false,
},
}),
@ -392,7 +396,7 @@ async fn inner_polygon(
let current_path = Path::ToPoint {
base: BasePath {
from: from.into(),
from: from.ignore_units(),
to: *vertex,
tag: None,
units: sketch.units,
@ -415,7 +419,9 @@ async fn inner_polygon(
ModelingCmd::from(mcmd::ExtendPath {
path: sketch.id.into(),
segment: PathSegment::Line {
end: KPoint2d::from(vertices[0]).with_z(0.0).map(LengthUnit),
end: KPoint2d::from(untyped_point_to_mm(vertices[0], units))
.with_z(0.0)
.map(LengthUnit),
relative: false,
},
}),
@ -424,7 +430,7 @@ async fn inner_polygon(
let current_path = Path::ToPoint {
base: BasePath {
from: from.into(),
from: from.ignore_units(),
to: vertices[0],
tag: None,
units: sketch.units,

View File

@ -16,10 +16,10 @@ use super::args::TyF64;
/// Create a shell.
pub async fn shell(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let solids = args.get_unlabeled_kw_arg_typed("solids", &RuntimeType::solids(), exec_state)?;
let thickness: TyF64 = args.get_kw_arg_typed("thickness", &RuntimeType::count(), exec_state)?;
let thickness: TyF64 = args.get_kw_arg_typed("thickness", &RuntimeType::length(), exec_state)?;
let faces = args.get_kw_arg("faces")?;
let result = inner_shell(solids, thickness.n, faces, exec_state, args).await?;
let result = inner_shell(solids, thickness, faces, exec_state, args).await?;
Ok(result.into())
}
@ -182,7 +182,7 @@ pub async fn shell(exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
}]
async fn inner_shell(
solids: Vec<Solid>,
thickness: f64,
thickness: TyF64,
faces: Vec<FaceTag>,
exec_state: &mut ExecState,
args: Args,
@ -237,7 +237,7 @@ async fn inner_shell(
hollow: false,
face_ids,
object_id: solids[0].id,
shell_thickness: LengthUnit(thickness),
shell_thickness: LengthUnit(thickness.to_mm()),
}),
)
.await?;
@ -247,9 +247,9 @@ async fn inner_shell(
/// Make the inside of a 3D object hollow.
pub async fn hollow(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let (thickness, solid) = args.get_data_and_solid(exec_state)?;
let (thickness, solid) = args.get_length_and_solid(exec_state)?;
let value = inner_hollow(thickness.n, solid, exec_state, args).await?;
let value = inner_hollow(thickness, solid, exec_state, args).await?;
Ok(KclValue::Solid { value })
}
@ -308,7 +308,7 @@ pub async fn hollow(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
feature_tree_operation = true,
}]
async fn inner_hollow(
thickness: f64,
thickness: TyF64,
solid: Box<Solid>,
exec_state: &mut ExecState,
args: Args,
@ -323,7 +323,7 @@ async fn inner_hollow(
hollow: true,
face_ids: Vec::new(), // This is empty because we want to hollow the entire object.
object_id: solid.id,
shell_thickness: LengthUnit(thickness),
shell_thickness: LengthUnit(thickness.to_mm()),
}),
)
.await?;

View File

@ -12,11 +12,11 @@ use parse_display::{Display, FromStr};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use super::utils::untype_point;
use super::utils::{point_to_len_unit, point_to_mm, untype_point, untyped_point_to_mm};
use crate::{
errors::{KclError, KclErrorDetails},
execution::{
types::{PrimitiveType, RuntimeType, UnitLen},
types::{NumericType, PrimitiveType, RuntimeType, UnitLen},
Artifact, ArtifactId, BasePath, CodeRef, ExecState, Face, GeoMeta, KclValue, Path, Plane, Point2d, Point3d,
Sketch, SketchSurface, Solid, StartSketchOnFace, StartSketchOnPlane, TagEngineInfo, TagIdentifier,
},
@ -200,7 +200,7 @@ async fn inner_involute_circular(
let current_path = Path::ToPoint {
base: BasePath {
from: from.into(),
from: from.ignore_units(),
to: [end.x, end.y],
tag: tag.clone(),
units: sketch.units,
@ -226,15 +226,7 @@ pub async fn line(exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kc
let end_absolute = args.get_kw_arg_opt_typed("endAbsolute", &RuntimeType::point2d(), exec_state)?;
let tag = args.get_kw_arg_opt(NEW_TAG_KW)?;
let new_sketch = inner_line(
sketch,
end_absolute.map(|p| untype_point(p).0),
end.map(|p| untype_point(p).0),
tag,
exec_state,
args,
)
.await?;
let new_sketch = inner_line(sketch, end_absolute, end, tag, exec_state, args).await?;
Ok(KclValue::Sketch {
value: Box::new(new_sketch),
})
@ -277,8 +269,8 @@ pub async fn line(exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kc
}]
async fn inner_line(
sketch: Sketch,
end_absolute: Option<[f64; 2]>,
end: Option<[f64; 2]>,
end_absolute: Option<[TyF64; 2]>,
end: Option<[TyF64; 2]>,
tag: Option<TagNode>,
exec_state: &mut ExecState,
args: Args,
@ -298,13 +290,13 @@ async fn inner_line(
struct StraightLineParams {
sketch: Sketch,
end_absolute: Option<[f64; 2]>,
end: Option<[f64; 2]>,
end_absolute: Option<[TyF64; 2]>,
end: Option<[TyF64; 2]>,
tag: Option<TagNode>,
}
impl StraightLineParams {
fn relative(p: [f64; 2], sketch: Sketch, tag: Option<TagNode>) -> Self {
fn relative(p: [TyF64; 2], sketch: Sketch, tag: Option<TagNode>) -> Self {
Self {
sketch,
tag,
@ -312,7 +304,7 @@ impl StraightLineParams {
end_absolute: None,
}
}
fn absolute(p: [f64; 2], sketch: Sketch, tag: Option<TagNode>) -> Self {
fn absolute(p: [TyF64; 2], sketch: Sketch, tag: Option<TagNode>) -> Self {
Self {
sketch,
tag,
@ -357,7 +349,7 @@ async fn straight_line(
ModelingCmd::from(mcmd::ExtendPath {
path: sketch.id.into(),
segment: PathSegment::Line {
end: KPoint2d::from(point).with_z(0.0).map(LengthUnit),
end: KPoint2d::from(point_to_mm(point.clone())).with_z(0.0).map(LengthUnit),
relative: !is_absolute,
},
}),
@ -365,15 +357,16 @@ async fn straight_line(
.await?;
let end = if is_absolute {
point
point_to_len_unit(point, from.units)
} else {
let from = sketch.current_pen_position()?;
let point = point_to_len_unit(point, from.units);
[from.x + point[0], from.y + point[1]]
};
let current_path = Path::ToPoint {
base: BasePath {
from: from.into(),
from: from.ignore_units(),
to: end,
tag: tag.clone(),
units: sketch.units,
@ -402,15 +395,7 @@ pub async fn x_line(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
let end_absolute: Option<TyF64> = args.get_kw_arg_opt_typed("endAbsolute", &RuntimeType::length(), exec_state)?;
let tag = args.get_kw_arg_opt(NEW_TAG_KW)?;
let new_sketch = inner_x_line(
sketch,
length.map(|t| t.n),
end_absolute.map(|t| t.n),
tag,
exec_state,
args,
)
.await?;
let new_sketch = inner_x_line(sketch, length, end_absolute, tag, exec_state, args).await?;
Ok(KclValue::Sketch {
value: Box::new(new_sketch),
})
@ -451,8 +436,8 @@ pub async fn x_line(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
}]
async fn inner_x_line(
sketch: Sketch,
length: Option<f64>,
end_absolute: Option<f64>,
length: Option<TyF64>,
end_absolute: Option<TyF64>,
tag: Option<TagNode>,
exec_state: &mut ExecState,
args: Args,
@ -461,8 +446,8 @@ async fn inner_x_line(
straight_line(
StraightLineParams {
sketch,
end_absolute: end_absolute.map(|x| [x, from.y]),
end: length.map(|x| [x, 0.0]),
end_absolute: end_absolute.map(|x| [x, from.into_y()]),
end: length.map(|x| [x, TyF64::new(0.0, NumericType::mm())]),
tag,
},
exec_state,
@ -479,15 +464,7 @@ pub async fn y_line(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
let end_absolute: Option<TyF64> = args.get_kw_arg_opt_typed("endAbsolute", &RuntimeType::length(), exec_state)?;
let tag = args.get_kw_arg_opt(NEW_TAG_KW)?;
let new_sketch = inner_y_line(
sketch,
length.map(|t| t.n),
end_absolute.map(|t| t.n),
tag,
exec_state,
args,
)
.await?;
let new_sketch = inner_y_line(sketch, length, end_absolute, tag, exec_state, args).await?;
Ok(KclValue::Sketch {
value: Box::new(new_sketch),
})
@ -523,8 +500,8 @@ pub async fn y_line(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
}]
async fn inner_y_line(
sketch: Sketch,
length: Option<f64>,
end_absolute: Option<f64>,
length: Option<TyF64>,
end_absolute: Option<TyF64>,
tag: Option<TagNode>,
exec_state: &mut ExecState,
args: Args,
@ -533,8 +510,8 @@ async fn inner_y_line(
straight_line(
StraightLineParams {
sketch,
end_absolute: end_absolute.map(|y| [from.x, y]),
end: length.map(|y| [0.0, y]),
end_absolute: end_absolute.map(|y| [from.into_x(), y]),
end: length.map(|y| [TyF64::new(0.0, NumericType::mm()), y]),
tag,
},
exec_state,
@ -559,11 +536,11 @@ pub async fn angled_line(exec_state: &mut ExecState, args: Args) -> Result<KclVa
let new_sketch = inner_angled_line(
sketch,
angle.n,
length.map(|t| t.n),
length_x.map(|t| t.n),
length_y.map(|t| t.n),
end_absolute_x.map(|t| t.n),
end_absolute_y.map(|t| t.n),
length,
length_x,
length_y,
end_absolute_x,
end_absolute_y,
tag,
exec_state,
args,
@ -610,16 +587,16 @@ pub async fn angled_line(exec_state: &mut ExecState, args: Args) -> Result<KclVa
async fn inner_angled_line(
sketch: Sketch,
angle: f64,
length: Option<f64>,
length_x: Option<f64>,
length_y: Option<f64>,
end_absolute_x: Option<f64>,
end_absolute_y: Option<f64>,
length: Option<TyF64>,
length_x: Option<TyF64>,
length_y: Option<TyF64>,
end_absolute_x: Option<TyF64>,
end_absolute_y: Option<TyF64>,
tag: Option<TagNode>,
exec_state: &mut ExecState,
args: Args,
) -> Result<Sketch, KclError> {
let options_given = [length, length_x, length_y, end_absolute_x, end_absolute_y]
let options_given = [&length, &length_x, &length_y, &end_absolute_x, &end_absolute_y]
.iter()
.filter(|x| x.is_some())
.count();
@ -667,12 +644,13 @@ async fn inner_angled_line(
async fn inner_angled_line_length(
sketch: Sketch,
angle_degrees: f64,
length: f64,
length: TyF64,
tag: Option<TagNode>,
exec_state: &mut ExecState,
args: Args,
) -> Result<Sketch, KclError> {
let from = sketch.current_pen_position()?;
let length = length.to_length_units(from.units);
//double check me on this one - mike
let delta: [f64; 2] = [
@ -690,7 +668,9 @@ async fn inner_angled_line_length(
ModelingCmd::from(mcmd::ExtendPath {
path: sketch.id.into(),
segment: PathSegment::Line {
end: KPoint2d::from(delta).with_z(0.0).map(LengthUnit),
end: KPoint2d::from(untyped_point_to_mm(delta, from.units))
.with_z(0.0)
.map(LengthUnit),
relative,
},
}),
@ -699,7 +679,7 @@ async fn inner_angled_line_length(
let current_path = Path::ToPoint {
base: BasePath {
from: from.into(),
from: from.ignore_units(),
to,
tag: tag.clone(),
units: sketch.units,
@ -721,7 +701,7 @@ async fn inner_angled_line_length(
async fn inner_angled_line_of_x_length(
angle_degrees: f64,
length: f64,
length: TyF64,
sketch: Sketch,
tag: Option<TagNode>,
exec_state: &mut ExecState,
@ -741,7 +721,8 @@ async fn inner_angled_line_of_x_length(
}));
}
let to = get_y_component(Angle::from_degrees(angle_degrees), length);
let to = get_y_component(Angle::from_degrees(angle_degrees), length.n);
let to = [TyF64::new(to[0], length.ty.clone()), TyF64::new(to[1], length.ty)];
let new_sketch = straight_line(StraightLineParams::relative(to, sketch, tag), exec_state, args).await?;
@ -750,7 +731,7 @@ async fn inner_angled_line_of_x_length(
async fn inner_angled_line_to_x(
angle_degrees: f64,
x_to: f64,
x_to: TyF64,
sketch: Sketch,
tag: Option<TagNode>,
exec_state: &mut ExecState,
@ -772,12 +753,12 @@ async fn inner_angled_line_to_x(
}));
}
let x_component = x_to - from.x;
let x_component = x_to.to_length_units(from.units) - from.x;
let y_component = x_component * f64::tan(angle_degrees.to_radians());
let y_to = from.y + y_component;
let new_sketch = straight_line(
StraightLineParams::absolute([x_to, y_to], sketch, tag),
StraightLineParams::absolute([x_to, TyF64::new(y_to, from.units.into())], sketch, tag),
exec_state,
args,
)
@ -787,7 +768,7 @@ async fn inner_angled_line_to_x(
async fn inner_angled_line_of_y_length(
angle_degrees: f64,
length: f64,
length: TyF64,
sketch: Sketch,
tag: Option<TagNode>,
exec_state: &mut ExecState,
@ -807,7 +788,8 @@ async fn inner_angled_line_of_y_length(
}));
}
let to = get_x_component(Angle::from_degrees(angle_degrees), length);
let to = get_x_component(Angle::from_degrees(angle_degrees), length.n);
let to = [TyF64::new(to[0], length.ty.clone()), TyF64::new(to[1], length.ty)];
let new_sketch = straight_line(StraightLineParams::relative(to, sketch, tag), exec_state, args).await?;
@ -816,7 +798,7 @@ async fn inner_angled_line_of_y_length(
async fn inner_angled_line_to_y(
angle_degrees: f64,
y_to: f64,
y_to: TyF64,
sketch: Sketch,
tag: Option<TagNode>,
exec_state: &mut ExecState,
@ -838,12 +820,12 @@ async fn inner_angled_line_to_y(
}));
}
let y_component = y_to - from.y;
let y_component = y_to.to_length_units(from.units) - from.y;
let x_component = y_component / f64::tan(angle_degrees.to_radians());
let x_to = from.x + x_component;
let new_sketch = straight_line(
StraightLineParams::absolute([x_to, y_to], sketch, tag),
StraightLineParams::absolute([TyF64::new(x_to, from.units.into()), y_to], sketch, tag),
exec_state,
args,
)
@ -916,11 +898,18 @@ pub async fn inner_angled_line_that_intersects(
let from = sketch.current_pen_position()?;
let to = intersection_with_parallel_line(
&[untype_point(path.get_from()).0, untype_point(path.get_to()).0],
offset.map(|t| t.n).unwrap_or_default(),
angle.n,
from.into(),
&[
point_to_len_unit(path.get_from(), from.units),
point_to_len_unit(path.get_to(), from.units),
],
offset.map(|t| t.to_length_units(from.units)).unwrap_or_default(),
angle.to_degrees(),
from.ignore_units(),
);
let to = [
TyF64::new(to[0], from.units.into()),
TyF64::new(to[1], from.units.into()),
];
straight_line(StraightLineParams::absolute(to, sketch, tag), exec_state, args).await
}
@ -1309,7 +1298,7 @@ async fn make_sketch_plane_from_orientation(
pub async fn start_profile_at(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let (start, sketch_surface, tag) = args.get_data_and_sketch_surface()?;
let sketch = inner_start_profile_at([start[0].n, start[1].n], sketch_surface, tag, exec_state, args).await?;
let sketch = inner_start_profile_at(start, sketch_surface, tag, exec_state, args).await?;
Ok(KclValue::Sketch {
value: Box::new(sketch),
})
@ -1353,7 +1342,7 @@ pub async fn start_profile_at(exec_state: &mut ExecState, args: Args) -> Result<
name = "startProfileAt",
}]
pub(crate) async fn inner_start_profile_at(
to: [f64; 2],
to: [TyF64; 2],
sketch_surface: SketchSurface,
tag: Option<TagNode>,
exec_state: &mut ExecState,
@ -1409,7 +1398,7 @@ pub(crate) async fn inner_start_profile_at(
ModelingCmdReq {
cmd: ModelingCmd::from(mcmd::MovePathPen {
path: path_id.into(),
to: KPoint2d::from(to).with_z(0.0).map(LengthUnit),
to: KPoint2d::from(point_to_mm(to.clone())).with_z(0.0).map(LengthUnit),
}),
cmd_id: move_pen_id.into(),
},
@ -1420,11 +1409,12 @@ pub(crate) async fn inner_start_profile_at(
])
.await?;
let (to, ty) = untype_point(to);
let current_path = BasePath {
from: to,
to,
tag: tag.clone(),
units: sketch_surface.units(),
units: ty.expect_length(),
geo_meta: GeoMeta {
id: move_pen_id,
metadata: args.source_range.into(),
@ -1437,7 +1427,7 @@ pub(crate) async fn inner_start_profile_at(
artifact_id: path_id.into(),
on: sketch_surface.clone(),
paths: vec![],
units: sketch_surface.units(),
units: ty.expect_length(),
mirror: Default::default(),
meta: vec![args.source_range.into()],
tags: if let Some(tag) = &tag {
@ -1586,7 +1576,7 @@ pub(crate) async fn inner_close(
args: Args,
) -> Result<Sketch, KclError> {
let from = sketch.current_pen_position()?;
let to: Point2d = sketch.start.get_from().into();
let to = point_to_len_unit(sketch.start.get_from(), from.units);
let id = exec_state.next_uuid();
@ -1595,8 +1585,8 @@ pub(crate) async fn inner_close(
let current_path = Path::ToPoint {
base: BasePath {
from: from.into(),
to: to.into(),
from: from.ignore_units(),
to,
tag: tag.clone(),
units: sketch.units,
geo_meta: GeoMeta {
@ -1708,7 +1698,6 @@ pub(crate) async fn inner_arc(
let from: Point2d = sketch.current_pen_position()?;
let id = exec_state.next_uuid();
// Relative case
match (angle_start, angle_end, radius, interior_absolute, end_absolute) {
(Some(angle_start), Some(angle_end), Some(radius), None, None) => {
relative_arc(&args, id, exec_state, sketch, from, angle_start, angle_end, radius, tag).await
@ -1745,13 +1734,13 @@ pub async fn absolute_arc(
path: sketch.id.into(),
segment: PathSegment::ArcTo {
end: kcmc::shared::Point3d {
x: LengthUnit(end_absolute[0].n),
y: LengthUnit(end_absolute[1].n),
x: LengthUnit(end_absolute[0].to_mm()),
y: LengthUnit(end_absolute[1].to_mm()),
z: LengthUnit(0.0),
},
interior: kcmc::shared::Point3d {
x: LengthUnit(interior_absolute[0].n),
y: LengthUnit(interior_absolute[1].n),
x: LengthUnit(interior_absolute[0].to_mm()),
y: LengthUnit(interior_absolute[1].to_mm()),
z: LengthUnit(0.0),
},
relative: false,
@ -1761,13 +1750,12 @@ pub async fn absolute_arc(
.await?;
let start = [from.x, from.y];
let end = end_absolute.clone();
let untyped_end = untype_point(end);
let end = point_to_len_unit(end_absolute, from.units);
let current_path = Path::ArcThreePoint {
base: BasePath {
from: from.into(),
to: untyped_end.0,
from: from.ignore_units(),
to: end,
tag: tag.clone(),
units: sketch.units,
geo_meta: GeoMeta {
@ -1776,8 +1764,8 @@ pub async fn absolute_arc(
},
},
p1: start,
p2: untype_point(interior_absolute).0,
p3: untyped_end.0,
p2: point_to_len_unit(interior_absolute, from.units),
p3: end,
};
let mut new_sketch = sketch.clone();
@ -1802,16 +1790,17 @@ pub async fn relative_arc(
radius: TyF64,
tag: Option<TagNode>,
) -> Result<Sketch, KclError> {
let a_start = Angle::from_degrees(angle_start.n);
let a_end = Angle::from_degrees(angle_end.n);
let (center, end) = arc_center_and_end(from.into(), a_start, a_end, radius.n);
if angle_start == angle_end {
let a_start = Angle::from_degrees(angle_start.to_degrees());
let a_end = Angle::from_degrees(angle_end.to_degrees());
let radius = radius.to_length_units(from.units);
let (center, end) = arc_center_and_end(from.ignore_units(), a_start, a_end, radius);
if a_start == a_end {
return Err(KclError::Type(KclErrorDetails {
message: "Arc start and end angles must be different".to_string(),
source_ranges: vec![args.source_range],
}));
}
let ccw = angle_start.n < angle_end.n;
let ccw = a_start < a_end;
args.batch_modeling_cmd(
id,
@ -1820,8 +1809,8 @@ pub async fn relative_arc(
segment: PathSegment::Arc {
start: a_start,
end: a_end,
center: KPoint2d::from(center).map(LengthUnit),
radius: LengthUnit(radius.n),
center: KPoint2d::from(untyped_point_to_mm(center, from.units)).map(LengthUnit),
radius: LengthUnit(from.units.adjust_to(radius, UnitLen::Mm).0),
relative: false,
},
}),
@ -1830,17 +1819,17 @@ pub async fn relative_arc(
let current_path = Path::Arc {
base: BasePath {
from: from.into(),
from: from.ignore_units(),
to: end,
tag: tag.clone(),
units: sketch.units,
units: from.units,
geo_meta: GeoMeta {
id,
metadata: args.source_range.into(),
},
},
center,
radius: radius.n,
radius,
ccw,
};
@ -1853,6 +1842,7 @@ pub async fn relative_arc(
Ok(new_sketch)
}
/// Draw a tangential arc to a specific point.
pub async fn tangential_arc(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let sketch =
@ -1863,17 +1853,7 @@ pub async fn tangential_arc(exec_state: &mut ExecState, args: Args) -> Result<Kc
let angle = args.get_kw_arg_opt_typed("angle", &RuntimeType::angle(), exec_state)?;
let tag = args.get_kw_arg_opt(NEW_TAG_KW)?;
let new_sketch = inner_tangential_arc(
sketch,
end_absolute.map(|p| untype_point(p).0),
end.map(|p| untype_point(p).0),
radius,
angle,
tag,
exec_state,
args,
)
.await?;
let new_sketch = inner_tangential_arc(sketch, end_absolute, end, radius, angle, tag, exec_state, args).await?;
Ok(KclValue::Sketch {
value: Box::new(new_sketch),
})
@ -1949,8 +1929,8 @@ pub async fn tangential_arc(exec_state: &mut ExecState, args: Args) -> Result<Kc
#[allow(clippy::too_many_arguments)]
async fn inner_tangential_arc(
sketch: Sketch,
end_absolute: Option<[f64; 2]>,
end: Option<[f64; 2]>,
end_absolute: Option<[TyF64; 2]>,
end: Option<[TyF64; 2]>,
radius: Option<TyF64>,
angle: Option<TyF64>,
tag: Option<TagNode>,
@ -2014,14 +1994,14 @@ async fn inner_tangential_arc_radius_angle(
let from: Point2d = sketch.current_pen_position()?;
// next set of lines is some undocumented voodoo from get_tangential_arc_to_info
let tangent_info = sketch.get_tangential_info_from_paths(); //this function desperately needs some documentation
let tan_previous_point = tangent_info.tan_previous_point(from.into());
let tan_previous_point = tangent_info.tan_previous_point(from.ignore_units());
let id = exec_state.next_uuid();
let (center, to, ccw) = match data {
TangentialArcData::RadiusAndOffset { radius, offset } => {
// KCL stdlib types use degrees.
let offset = Angle::from_degrees(offset.n);
let offset = Angle::from_degrees(offset.to_degrees());
// Calculate the end point from the angle and radius.
// atan2 outputs radians.
@ -2043,14 +2023,19 @@ async fn inner_tangential_arc_radius_angle(
// but the above logic *should* capture that behavior
let start_angle = previous_end_tangent + tangent_to_arc_start_angle;
let end_angle = start_angle + offset;
let (center, to) = arc_center_and_end(from.into(), start_angle, end_angle, radius.n);
let (center, to) = arc_center_and_end(
from.ignore_units(),
start_angle,
end_angle,
radius.to_length_units(from.units),
);
args.batch_modeling_cmd(
id,
ModelingCmd::from(mcmd::ExtendPath {
path: sketch.id.into(),
segment: PathSegment::TangentialArc {
radius: LengthUnit(radius.n),
radius: LengthUnit(radius.to_mm()),
offset,
},
}),
@ -2064,7 +2049,7 @@ async fn inner_tangential_arc_radius_angle(
ccw,
center,
base: BasePath {
from: from.into(),
from: from.ignore_units(),
to,
tag: tag.clone(),
units: sketch.units,
@ -2085,19 +2070,22 @@ async fn inner_tangential_arc_radius_angle(
Ok(new_sketch)
}
fn tan_arc_to(sketch: &Sketch, to: &[f64; 2]) -> ModelingCmd {
// `to` must be in sketch.units
fn tan_arc_to(sketch: &Sketch, to: [f64; 2]) -> ModelingCmd {
ModelingCmd::from(mcmd::ExtendPath {
path: sketch.id.into(),
segment: PathSegment::TangentialArcTo {
angle_snap_increment: None,
to: KPoint2d::from(*to).with_z(0.0).map(LengthUnit),
to: KPoint2d::from(untyped_point_to_mm(to, sketch.units))
.with_z(0.0)
.map(LengthUnit),
},
})
}
async fn inner_tangential_arc_to_point(
sketch: Sketch,
point: [f64; 2],
point: [TyF64; 2],
is_absolute: bool,
tag: Option<TagNode>,
exec_state: &mut ExecState,
@ -2105,7 +2093,9 @@ async fn inner_tangential_arc_to_point(
) -> Result<Sketch, KclError> {
let from: Point2d = sketch.current_pen_position()?;
let tangent_info = sketch.get_tangential_info_from_paths();
let tan_previous_point = tangent_info.tan_previous_point(from.into());
let tan_previous_point = tangent_info.tan_previous_point(from.ignore_units());
let point = point_to_len_unit(point, from.units);
let to = if is_absolute {
point
@ -2115,7 +2105,7 @@ async fn inner_tangential_arc_to_point(
let [to_x, to_y] = to;
let result = get_tangential_arc_to_info(TangentialArcInfoInput {
arc_start_point: [from.x, from.y],
arc_end_point: to,
arc_end_point: [to_x, to_y],
tan_previous_point,
obtuse: true,
});
@ -2142,11 +2132,11 @@ async fn inner_tangential_arc_to_point(
point
};
let id = exec_state.next_uuid();
args.batch_modeling_cmd(id, tan_arc_to(&sketch, &delta)).await?;
args.batch_modeling_cmd(id, tan_arc_to(&sketch, delta)).await?;
let current_path = Path::TangentialArcTo {
base: BasePath {
from: from.into(),
from: from.ignore_units(),
to,
tag: tag.clone(),
units: sketch.units,
@ -2227,7 +2217,10 @@ async fn inner_bezier_curve(
let relative = true;
let delta = end.clone();
let to = [from.x + end[0].n, from.y + end[1].n];
let to = [
from.x + end[0].to_length_units(from.units),
from.y + end[1].to_length_units(from.units),
];
let id = exec_state.next_uuid();
@ -2236,9 +2229,9 @@ async fn inner_bezier_curve(
ModelingCmd::from(mcmd::ExtendPath {
path: sketch.id.into(),
segment: PathSegment::Bezier {
control1: KPoint2d::from(untype_point(control1).0).with_z(0.0).map(LengthUnit),
control2: KPoint2d::from(untype_point(control2).0).with_z(0.0).map(LengthUnit),
end: KPoint2d::from(untype_point(delta).0).with_z(0.0).map(LengthUnit),
control1: KPoint2d::from(point_to_mm(control1)).with_z(0.0).map(LengthUnit),
control2: KPoint2d::from(point_to_mm(control2)).with_z(0.0).map(LengthUnit),
end: KPoint2d::from(point_to_mm(delta)).with_z(0.0).map(LengthUnit),
relative,
},
}),
@ -2247,7 +2240,7 @@ async fn inner_bezier_curve(
let current_path = Path::ToPoint {
base: BasePath {
from: from.into(),
from: from.ignore_units(),
to,
tag: tag.clone(),
units: sketch.units,

View File

@ -10,7 +10,10 @@ use serde::Serialize;
use super::{args::TyF64, DEFAULT_TOLERANCE};
use crate::{
errors::KclError,
execution::{types::RuntimeType, ExecState, Helix, KclValue, Sketch, Solid},
execution::{
types::{NumericType, RuntimeType},
ExecState, Helix, KclValue, Sketch, Solid,
},
parsing::ast::types::TagNode,
std::{extrude::do_post_extrude, Args},
};
@ -27,21 +30,18 @@ pub enum SweepPath {
/// Extrude a sketch along a path.
pub async fn sweep(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let sketches = args.get_unlabeled_kw_arg_typed("sketches", &RuntimeType::sketches(), exec_state)?;
let path: SweepPath = args.get_kw_arg("path")?;
let path: SweepPath = args.get_kw_arg_typed(
"path",
&RuntimeType::Union(vec![RuntimeType::sketch(), RuntimeType::helix()]),
exec_state,
)?;
let sectional = args.get_kw_arg_opt("sectional")?;
let tolerance: Option<TyF64> = args.get_kw_arg_opt_typed("tolerance", &RuntimeType::count(), exec_state)?;
let tolerance: Option<TyF64> = args.get_kw_arg_opt_typed("tolerance", &RuntimeType::length(), exec_state)?;
let tag_start = args.get_kw_arg_opt("tagStart")?;
let tag_end = args.get_kw_arg_opt("tagEnd")?;
let value = inner_sweep(
sketches,
path,
sectional,
tolerance.map(|t| t.n),
tag_start,
tag_end,
exec_state,
args,
sketches, path, sectional, tolerance, tag_start, tag_end, exec_state, args,
)
.await?;
Ok(value.into())
@ -167,7 +167,7 @@ async fn inner_sweep(
sketches: Vec<Sketch>,
path: SweepPath,
sectional: Option<bool>,
tolerance: Option<f64>,
tolerance: Option<TyF64>,
tag_start: Option<TagNode>,
tag_end: Option<TagNode>,
exec_state: &mut ExecState,
@ -187,7 +187,7 @@ async fn inner_sweep(
target: sketch.id.into(),
trajectory,
sectional: sectional.unwrap_or(false),
tolerance: LengthUnit(tolerance.unwrap_or(DEFAULT_TOLERANCE)),
tolerance: LengthUnit(tolerance.as_ref().map(|t| t.to_mm()).unwrap_or(DEFAULT_TOLERANCE)),
}),
)
.await?;
@ -196,7 +196,7 @@ async fn inner_sweep(
do_post_extrude(
sketch,
id.into(),
0.0,
TyF64::new(0.0, NumericType::mm()),
sectional.unwrap_or(false),
&super::extrude::NamedCapTags {
start: tag_start.as_ref(),

View File

@ -225,16 +225,7 @@ pub async fn translate(exec_state: &mut ExecState, args: Args) -> Result<KclValu
}));
}
let objects = inner_translate(
objects,
translate_x.map(|t| t.n),
translate_y.map(|t| t.n),
translate_z.map(|t| t.n),
global,
exec_state,
args,
)
.await?;
let objects = inner_translate(objects, translate_x, translate_y, translate_z, global, exec_state, args).await?;
Ok(objects.into())
}
@ -397,9 +388,9 @@ pub async fn translate(exec_state: &mut ExecState, args: Args) -> Result<KclValu
}]
async fn inner_translate(
objects: SolidOrSketchOrImportedGeometry,
x: Option<f64>,
y: Option<f64>,
z: Option<f64>,
x: Option<TyF64>,
y: Option<TyF64>,
z: Option<TyF64>,
global: Option<bool>,
exec_state: &mut ExecState,
args: Args,
@ -421,9 +412,9 @@ async fn inner_translate(
transforms: vec![shared::ComponentTransform {
translate: Some(shared::TransformBy::<Point3d<LengthUnit>> {
property: shared::Point3d {
x: LengthUnit(x.unwrap_or_default()),
y: LengthUnit(y.unwrap_or_default()),
z: LengthUnit(z.unwrap_or_default()),
x: LengthUnit(x.as_ref().map(|t| t.to_mm()).unwrap_or_default()),
y: LengthUnit(y.as_ref().map(|t| t.to_mm()).unwrap_or_default()),
z: LengthUnit(z.as_ref().map(|t| t.to_mm()).unwrap_or_default()),
},
set: false,
is_local: !global.unwrap_or(false),
@ -451,11 +442,11 @@ pub async fn rotate(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
]),
exec_state,
)?;
let roll: Option<TyF64> = args.get_kw_arg_opt_typed("roll", &RuntimeType::angle(), exec_state)?;
let pitch: Option<TyF64> = args.get_kw_arg_opt_typed("pitch", &RuntimeType::angle(), exec_state)?;
let yaw: Option<TyF64> = args.get_kw_arg_opt_typed("yaw", &RuntimeType::angle(), exec_state)?;
let roll: Option<TyF64> = args.get_kw_arg_opt_typed("roll", &RuntimeType::degrees(), exec_state)?;
let pitch: Option<TyF64> = args.get_kw_arg_opt_typed("pitch", &RuntimeType::degrees(), exec_state)?;
let yaw: Option<TyF64> = args.get_kw_arg_opt_typed("yaw", &RuntimeType::degrees(), exec_state)?;
let axis: Option<[TyF64; 3]> = args.get_kw_arg_opt_typed("axis", &RuntimeType::point3d(), exec_state)?;
let angle: Option<TyF64> = args.get_kw_arg_opt_typed("angle", &RuntimeType::angle(), exec_state)?;
let angle: Option<TyF64> = args.get_kw_arg_opt_typed("angle", &RuntimeType::degrees(), exec_state)?;
let global = args.get_kw_arg_opt("global")?;
// Check if no rotation values are provided.
@ -544,7 +535,9 @@ pub async fn rotate(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
roll.map(|t| t.n),
pitch.map(|t| t.n),
yaw.map(|t| t.n),
axis.map(|p| [p[0].n, p[1].n, p[2].n]),
// Don't adjust axis units since the axis must be normalized and only the direction
// should be significant, not the magnitude.
axis.map(|a| [a[0].n, a[1].n, a[2].n]),
angle.map(|t| t.n),
global,
exec_state,
@ -780,7 +773,7 @@ async fn inner_rotate(
for object_id in objects.ids(&args.ctx).await? {
let id = exec_state.next_uuid();
if let (Some(axis), Some(angle)) = (axis, angle) {
if let (Some(axis), Some(angle)) = (&axis, angle) {
args.batch_modeling_cmd(
id,
ModelingCmd::from(mcmd::SetObjectTransform {

View File

@ -5,20 +5,28 @@ use kcl_derive_docs::stdlib;
use crate::{
errors::KclError,
execution::{types::UnitLen, ExecState, KclValue},
execution::{
types::{RuntimeType, UnitLen},
ExecState, KclValue,
},
std::{args::TyF64, Args},
};
/// Millimeters conversion factor for current files units.
pub async fn from_mm(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let input = args.get_number_with_type()?;
let result = inner_from_mm(input.n, exec_state)?;
let input = args.get_number_typed(&RuntimeType::known_length(UnitLen::Mm), exec_state)?;
let result = inner_from_mm(input, exec_state)?;
Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, exec_state.current_default_units())))
Ok(args.make_user_val_from_f64_with_type(TyF64::new(
result,
exec_state.current_default_units().expect_default_length(),
)))
}
/// Converts a number from mm to the current default unit.
///
/// *DEPRECATED* prefer using explicit numeric suffixes (e.g., `42mm`) or the `to...` conversion functions.
///
/// No matter what units the current file uses, this function will always return a number equivalent
/// to the input in millimeters.
///
@ -39,6 +47,7 @@ pub async fn from_mm(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
#[stdlib {
name = "fromMm",
tags = ["units"],
deprecated = true,
}]
fn inner_from_mm(input: f64, exec_state: &ExecState) -> Result<f64, KclError> {
Ok(match exec_state.length_unit() {
@ -54,14 +63,19 @@ fn inner_from_mm(input: f64, exec_state: &ExecState) -> Result<f64, KclError> {
/// Inches conversion factor for current files units.
pub async fn from_inches(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let input = args.get_number_with_type()?;
let result = inner_from_inches(input.n, exec_state)?;
let input = args.get_number_typed(&RuntimeType::known_length(UnitLen::Inches), exec_state)?;
let result = inner_from_inches(input, exec_state)?;
Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, exec_state.current_default_units())))
Ok(args.make_user_val_from_f64_with_type(TyF64::new(
result,
exec_state.current_default_units().expect_default_length(),
)))
}
/// Converts a number from inches to the current default unit.
///
/// *DEPRECATED* prefer using explicit numeric suffixes (e.g., `42inch`) or the `to...` conversion functions.
///
/// No matter what units the current file uses, this function will always return a number equivalent
/// to the input in inches.
///
@ -82,6 +96,7 @@ pub async fn from_inches(exec_state: &mut ExecState, args: Args) -> Result<KclVa
#[stdlib {
name = "fromInches",
tags = ["units"],
deprecated = true,
}]
fn inner_from_inches(input: f64, exec_state: &ExecState) -> Result<f64, KclError> {
match exec_state.length_unit() {
@ -97,14 +112,19 @@ fn inner_from_inches(input: f64, exec_state: &ExecState) -> Result<f64, KclError
/// Feet conversion factor for current files units.
pub async fn from_ft(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let input = args.get_number_with_type()?;
let result = inner_from_ft(input.n, exec_state)?;
let input = args.get_number_typed(&RuntimeType::known_length(UnitLen::Feet), exec_state)?;
let result = inner_from_ft(input, exec_state)?;
Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, exec_state.current_default_units())))
Ok(args.make_user_val_from_f64_with_type(TyF64::new(
result,
exec_state.current_default_units().expect_default_length(),
)))
}
/// Converts a number from feet to the current default unit.
///
/// *DEPRECATED* prefer using explicit numeric suffixes (e.g., `42ft`) or the `to...` conversion functions.
///
/// No matter what units the current file uses, this function will always return a number equivalent
/// to the input in feet.
///
@ -126,6 +146,7 @@ pub async fn from_ft(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
#[stdlib {
name = "fromFt",
tags = ["units"],
deprecated = true,
}]
fn inner_from_ft(input: f64, exec_state: &ExecState) -> Result<f64, KclError> {
match exec_state.length_unit() {
@ -141,14 +162,19 @@ fn inner_from_ft(input: f64, exec_state: &ExecState) -> Result<f64, KclError> {
/// Meters conversion factor for current files units.
pub async fn from_m(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let input = args.get_number_with_type()?;
let result = inner_from_m(input.n, exec_state)?;
let input = args.get_number_typed(&RuntimeType::known_length(UnitLen::M), exec_state)?;
let result = inner_from_m(input, exec_state)?;
Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, exec_state.current_default_units())))
Ok(args.make_user_val_from_f64_with_type(TyF64::new(
result,
exec_state.current_default_units().expect_default_length(),
)))
}
/// Converts a number from meters to the current default unit.
///
/// *DEPRECATED* prefer using explicit numeric suffixes (e.g., `42m`) or the `to...` conversion functions.
///
/// No matter what units the current file uses, this function will always return a number equivalent
/// to the input in meters.
///
@ -170,6 +196,7 @@ pub async fn from_m(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
#[stdlib {
name = "fromM",
tags = ["units"],
deprecated = true,
}]
fn inner_from_m(input: f64, exec_state: &ExecState) -> Result<f64, KclError> {
match exec_state.length_unit() {
@ -185,14 +212,19 @@ fn inner_from_m(input: f64, exec_state: &ExecState) -> Result<f64, KclError> {
/// Centimeters conversion factor for current files units.
pub async fn from_cm(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let input = args.get_number_with_type()?;
let result = inner_from_cm(input.n, exec_state)?;
let input = args.get_number_typed(&RuntimeType::known_length(UnitLen::Cm), exec_state)?;
let result = inner_from_cm(input, exec_state)?;
Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, exec_state.current_default_units())))
Ok(args.make_user_val_from_f64_with_type(TyF64::new(
result,
exec_state.current_default_units().expect_default_length(),
)))
}
/// Converts a number from centimeters to the current default unit.
///
/// *DEPRECATED* prefer using explicit numeric suffixes (e.g., `42cm`) or the `to...` conversion functions.
///
/// No matter what units the current file uses, this function will always return a number equivalent
/// to the input in centimeters.
///
@ -214,6 +246,7 @@ pub async fn from_cm(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
#[stdlib {
name = "fromCm",
tags = ["units"],
deprecated = true,
}]
fn inner_from_cm(input: f64, exec_state: &ExecState) -> Result<f64, KclError> {
match exec_state.length_unit() {
@ -229,14 +262,19 @@ fn inner_from_cm(input: f64, exec_state: &ExecState) -> Result<f64, KclError> {
/// Yards conversion factor for current files units.
pub async fn from_yd(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let input = args.get_number_with_type()?;
let result = inner_from_yd(input.n, exec_state)?;
let input = args.get_number_typed(&RuntimeType::known_length(UnitLen::Yards), exec_state)?;
let result = inner_from_yd(input, exec_state)?;
Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, exec_state.current_default_units())))
Ok(args.make_user_val_from_f64_with_type(TyF64::new(
result,
exec_state.current_default_units().expect_default_length(),
)))
}
/// Converts a number from yards to the current default unit.
///
/// *DEPRECATED* prefer using explicit numeric suffixes (e.g., `42yd`) or the `to...` conversion functions.
///
/// No matter what units the current file uses, this function will always return a number equivalent
/// to the input in yards.
///
@ -258,6 +296,7 @@ pub async fn from_yd(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
#[stdlib {
name = "fromYd",
tags = ["units"],
deprecated = true,
}]
fn inner_from_yd(input: f64, exec_state: &ExecState) -> Result<f64, KclError> {
match exec_state.length_unit() {

View File

@ -2,39 +2,54 @@ use std::f64::consts::PI;
use kittycad_modeling_cmds::shared::Angle;
use crate::{
errors::{KclError, KclErrorDetails},
execution::{types::NumericType, Point2d},
source_range::SourceRange,
};
use crate::execution::types::{NumericType, UnitLen};
use super::args::TyF64;
pub fn untype_point(p: [TyF64; 2]) -> ([f64; 2], NumericType) {
pub(crate) fn untype_point(p: [TyF64; 2]) -> ([f64; 2], NumericType) {
let (x, y, ty) = NumericType::combine_eq(p[0].clone(), p[1].clone());
([x, y], ty)
}
pub fn untype_point_3d(p: [TyF64; 3]) -> ([f64; 3], NumericType) {
let (arr, ty) = NumericType::combine_eq_array(&[p[0].clone(), p[1].clone(), p[2].clone()]);
let mut iter = arr.into_iter();
([iter.next().unwrap(), iter.next().unwrap(), iter.next().unwrap()], ty)
pub(crate) fn point_to_mm(p: [TyF64; 2]) -> [f64; 2] {
[p[0].to_mm(), p[1].to_mm()]
}
pub(crate) fn untyped_point_to_mm(p: [f64; 2], units: UnitLen) -> [f64; 2] {
assert_ne!(units, UnitLen::Unknown);
[
units.adjust_to(p[0], UnitLen::Mm).0,
units.adjust_to(p[1], UnitLen::Mm).0,
]
}
pub(crate) fn point_to_len_unit(p: [TyF64; 2], len: UnitLen) -> [f64; 2] {
[p[0].to_length_units(len), p[1].to_length_units(len)]
}
/// Precondition, `p` must be in `len` units (this function does no conversion).
pub(crate) fn point_to_typed(p: [f64; 2], len: UnitLen) -> [TyF64; 2] {
[TyF64::new(p[0], len.into()), TyF64::new(p[1], len.into())]
}
pub(crate) fn point_3d_to_mm(p: [TyF64; 3]) -> [f64; 3] {
[p[0].to_mm(), p[1].to_mm(), p[2].to_mm()]
}
/// Get the distance between two points.
pub fn distance(a: Coords2d, b: Coords2d) -> f64 {
pub(crate) fn distance(a: Coords2d, b: Coords2d) -> f64 {
((b[0] - a[0]).powi(2) + (b[1] - a[1]).powi(2)).sqrt()
}
/// Get the angle between these points
pub fn between(a: Point2d, b: Point2d) -> Angle {
let x = b.x - a.x;
let y = b.y - a.y;
pub(crate) fn between(a: Coords2d, b: Coords2d) -> Angle {
let x = b[0] - a[0];
let y = b[1] - a[1];
normalize(Angle::from_radians(y.atan2(x)))
}
/// Normalize the angle
pub fn normalize(angle: Angle) -> Angle {
pub(crate) fn normalize(angle: Angle) -> Angle {
let deg = angle.to_degrees();
let result = ((deg % 360.0) + 360.0) % 360.0;
Angle::from_degrees(if result > 180.0 { result - 360.0 } else { result })
@ -55,7 +70,7 @@ pub fn normalize(angle: Angle) -> Angle {
/// Angle::from_radians(PI / 8.0)
/// );
/// ```
pub fn delta(from_angle: Angle, to_angle: Angle) -> Angle {
pub(crate) fn delta(from_angle: Angle, to_angle: Angle) -> Angle {
let norm_from_angle = normalize_rad(from_angle.to_radians());
let norm_to_angle = normalize_rad(to_angle.to_radians());
let provisional = norm_to_angle - norm_from_angle;
@ -72,7 +87,7 @@ pub fn delta(from_angle: Angle, to_angle: Angle) -> Angle {
Angle::default()
}
pub fn normalize_rad(angle: f64) -> f64 {
pub(crate) fn normalize_rad(angle: f64) -> f64 {
let draft = angle % (2.0 * PI);
if draft < 0.0 {
draft + 2.0 * PI
@ -106,7 +121,7 @@ fn intersect(p1: Coords2d, p2: Coords2d, p3: Coords2d, p4: Coords2d) -> Coords2d
[x, y]
}
pub fn intersection_with_parallel_line(
pub(crate) fn intersection_with_parallel_line(
line1: &[Coords2d; 2],
line1_offset: f64,
line2_angle: f64,
@ -128,7 +143,7 @@ fn offset_line(offset: f64, p1: Coords2d, p2: Coords2d) -> [Coords2d; 2] {
[[p1[0] + x_offset, p1[1]], [p2[0] + x_offset, p2[1]]]
}
pub fn get_y_component(angle: Angle, x: f64) -> Coords2d {
pub(crate) fn get_y_component(angle: Angle, x: f64) -> Coords2d {
let normalised_angle = ((angle.to_degrees() % 360.0) + 360.0) % 360.0; // between 0 and 360
let y = x * f64::tan(normalised_angle.to_radians());
let sign = if normalised_angle > 90.0 && normalised_angle <= 270.0 {
@ -139,7 +154,7 @@ pub fn get_y_component(angle: Angle, x: f64) -> Coords2d {
[x * sign, y * sign]
}
pub fn get_x_component(angle: Angle, y: f64) -> Coords2d {
pub(crate) fn get_x_component(angle: Angle, y: f64) -> Coords2d {
let normalised_angle = ((angle.to_degrees() % 360.0) + 360.0) % 360.0; // between 0 and 360
let x = y / f64::tan(normalised_angle.to_radians());
let sign = if normalised_angle > 180.0 && normalised_angle <= 360.0 {
@ -150,7 +165,12 @@ pub fn get_x_component(angle: Angle, y: f64) -> Coords2d {
[x * sign, y * sign]
}
pub fn arc_center_and_end(from: Coords2d, start_angle: Angle, end_angle: Angle, radius: f64) -> (Coords2d, Coords2d) {
pub(crate) fn arc_center_and_end(
from: Coords2d,
start_angle: Angle,
end_angle: Angle,
radius: f64,
) -> (Coords2d, Coords2d) {
let start_angle = start_angle.to_radians();
let end_angle = end_angle.to_radians();
@ -167,56 +187,9 @@ pub fn arc_center_and_end(from: Coords2d, start_angle: Angle, end_angle: Angle,
(center, end)
}
pub fn arc_angles(
from: Coords2d,
to: Coords2d,
center: Coords2d,
radius: f64,
source_range: SourceRange,
) -> Result<(Angle, Angle), KclError> {
// First make sure that the points are on the circumference of the circle.
// If not, we'll return an error.
if !is_on_circumference(center, from, radius) {
return Err(KclError::Semantic(KclErrorDetails {
message: format!(
"Point {:?} is not on the circumference of the circle with center {:?} and radius {}.",
from, center, radius
),
source_ranges: vec![source_range],
}));
}
if !is_on_circumference(center, to, radius) {
return Err(KclError::Semantic(KclErrorDetails {
message: format!(
"Point {:?} is not on the circumference of the circle with center {:?} and radius {}.",
to, center, radius
),
source_ranges: vec![source_range],
}));
}
let start_angle = (from[1] - center[1]).atan2(from[0] - center[0]);
let end_angle = (to[1] - center[1]).atan2(to[0] - center[0]);
Ok((Angle::from_radians(start_angle), Angle::from_radians(end_angle)))
}
fn is_on_circumference(center: Coords2d, point: Coords2d, radius: f64) -> bool {
let dx = point[0] - center[0];
let dy = point[1] - center[1];
let distance_squared = dx.powi(2) + dy.powi(2);
// We'll check if the distance squared is approximately equal to radius squared.
// Due to potential floating point inaccuracies, we'll check if the difference
// is very small (e.g., 1e-9) rather than checking for strict equality.
(distance_squared - radius.powi(2)).abs() < 1e-9
}
// Calculate the center of 3 points using an algebraic method
// Handles if 3 points lie on the same line (collinear) by returning the average of the points (could return None instead..)
pub fn calculate_circle_center(p1: [f64; 2], p2: [f64; 2], p3: [f64; 2]) -> [f64; 2] {
pub(crate) fn calculate_circle_center(p1: [f64; 2], p2: [f64; 2], p3: [f64; 2]) -> [f64; 2] {
let (x1, y1) = (p1[0], p1[1]);
let (x2, y2) = (p2[0], p2[1]);
let (x3, y3) = (p3[0], p3[1]);
@ -268,7 +241,6 @@ mod tests {
use std::f64::consts::TAU;
use super::{calculate_circle_center, get_x_component, get_y_component, Angle};
use crate::SourceRange;
static EACH_QUAD: [(i32, [i32; 2]); 12] = [
(-315, [1, 1]),
@ -366,34 +338,6 @@ mod tests {
assert_eq!(end[1].round(), 0.0);
}
#[test]
fn test_arc_angles() {
let (angle_start, angle_end) =
super::arc_angles([0.0, 0.0], [-1.0, 1.0], [-1.0, 0.0], 1.0, SourceRange::default()).unwrap();
assert_eq!(angle_start.to_degrees().round(), 0.0);
assert_eq!(angle_end.to_degrees().round(), 90.0);
let (angle_start, angle_end) =
super::arc_angles([0.0, 0.0], [-2.0, 0.0], [-1.0, 0.0], 1.0, SourceRange::default()).unwrap();
assert_eq!(angle_start.to_degrees().round(), 0.0);
assert_eq!(angle_end.to_degrees().round(), 180.0);
let (angle_start, angle_end) =
super::arc_angles([0.0, 0.0], [-20.0, 0.0], [-10.0, 0.0], 10.0, SourceRange::default()).unwrap();
assert_eq!(angle_start.to_degrees().round(), 0.0);
assert_eq!(angle_end.to_degrees().round(), 180.0);
let result = super::arc_angles([0.0, 5.0], [5.0, 5.0], [10.0, -10.0], 10.0, SourceRange::default());
if let Err(err) = result {
assert!(err.to_string().contains("Point [0.0, 5.0] is not on the circumference of the circle with center [10.0, -10.0] and radius 10."), "found: `{}`", err);
} else {
panic!("Expected error");
}
assert_eq!(angle_start.to_degrees().round(), 0.0);
assert_eq!(angle_end.to_degrees().round(), 180.0);
}
#[test]
fn test_calculate_circle_center() {
const EPS: f64 = 1e-4;
@ -464,7 +408,7 @@ mod tests {
}
}
pub type Coords2d = [f64; 2];
pub(crate) type Coords2d = [f64; 2];
pub fn is_points_ccw_wasm(points: &[f64]) -> i32 {
// CCW is positive as that the Math convention
@ -478,7 +422,7 @@ pub fn is_points_ccw_wasm(points: &[f64]) -> i32 {
sum.signum() as i32
}
pub fn is_points_ccw(points: &[Coords2d]) -> i32 {
pub(crate) fn is_points_ccw(points: &[Coords2d]) -> i32 {
let flattened_points: Vec<f64> = points.iter().flat_map(|&p| vec![p[0], p[1]]).collect();
is_points_ccw_wasm(&flattened_points)
}
@ -587,7 +531,6 @@ pub struct TangentialArcInfoInput {
}
/// Structure to hold the output data from calculating tangential arc information.
#[allow(dead_code)]
pub struct TangentialArcInfoOutput {
/// The center point of the arc.
pub center: Coords2d,
@ -851,7 +794,7 @@ mod get_tangential_arc_to_info_tests {
}
}
pub fn get_tangent_point_from_previous_arc(
pub(crate) fn get_tangent_point_from_previous_arc(
last_arc_center: Coords2d,
last_arc_ccw: bool,
last_arc_end: Coords2d,