Allow the origin of rotation to be specified (#7608)

* pass axis origin to endpoint

* fmt

* fix lint errors

* update sim tests with new transform endpoint

* added missed files

* revert cargo.toml

* implement review requests

* fmt

* revert unnecessary custom origin
This commit is contained in:
Ben Crabbe
2025-06-27 00:38:18 +01:00
committed by GitHub
parent 4356885aa2
commit 107adc77b3
45 changed files with 635 additions and 166 deletions

View File

@ -58,4 +58,11 @@ impl Axis3dOrPoint3d {
Axis3dOrPoint3d::Point(point) => point.clone(),
}
}
pub fn axis_origin(&self) -> Option<[TyF64; 3]> {
match self {
Axis3dOrPoint3d::Axis { origin, .. } => Some(origin.clone()),
Axis3dOrPoint3d::Point(..) => None,
}
}
}

View File

@ -5,7 +5,7 @@ use kcmc::{
ModelingCmd, each_cmd as mcmd,
length_unit::LengthUnit,
shared,
shared::{Point3d, Point4d},
shared::{OriginType, Point3d},
};
use kittycad_modeling_cmds as kcmc;
@ -18,6 +18,16 @@ use crate::{
std::{Args, args::TyF64, axis_or_reference::Axis3dOrPoint3d},
};
fn transform_by<T>(property: T, set: bool, is_local: bool, origin: Option<OriginType>) -> shared::TransformBy<T> {
shared::TransformBy {
property,
set,
#[expect(deprecated)]
is_local,
origin,
}
}
/// Scale a solid or a sketch.
pub async fn scale(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let objects = args.get_unlabeled_kw_arg(
@ -70,6 +80,13 @@ async fn inner_scale(
exec_state.flush_batch_for_solids((&args).into(), solids).await?;
}
let is_global = global.unwrap_or(false);
let origin = if is_global {
Some(OriginType::Global)
} else {
Some(OriginType::Local)
};
let mut objects = objects.clone();
for object_id in objects.ids(&args.ctx).await? {
exec_state
@ -78,15 +95,16 @@ async fn inner_scale(
ModelingCmd::from(mcmd::SetObjectTransform {
object_id,
transforms: vec![shared::ComponentTransform {
scale: Some(shared::TransformBy::<Point3d<f64>> {
property: Point3d {
scale: Some(transform_by(
Point3d {
x: x.unwrap_or(1.0),
y: y.unwrap_or(1.0),
z: z.unwrap_or(1.0),
},
set: false,
is_local: !global.unwrap_or(false),
}),
false,
!is_global,
origin,
)),
translate: None,
rotate_rpy: None,
rotate_angle_axis: None,
@ -142,6 +160,13 @@ async fn inner_translate(
exec_state.flush_batch_for_solids((&args).into(), solids).await?;
}
let is_global = global.unwrap_or(false);
let origin = if is_global {
Some(OriginType::Global)
} else {
Some(OriginType::Local)
};
let mut objects = objects.clone();
for object_id in objects.ids(&args.ctx).await? {
exec_state
@ -150,15 +175,16 @@ async fn inner_translate(
ModelingCmd::from(mcmd::SetObjectTransform {
object_id,
transforms: vec![shared::ComponentTransform {
translate: Some(shared::TransformBy::<Point3d<LengthUnit>> {
property: shared::Point3d {
translate: Some(transform_by(
shared::Point3d {
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),
}),
false,
!is_global,
origin,
)),
scale: None,
rotate_rpy: None,
rotate_angle_axis: None,
@ -193,6 +219,7 @@ pub async fn rotate(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
]),
exec_state,
)?;
let origin = axis.clone().map(|a| a.axis_origin()).unwrap_or_default();
let axis = axis.map(|a| a.to_point3d());
let angle: Option<TyF64> = args.get_kw_arg_opt("angle", &RuntimeType::degrees(), exec_state)?;
let global = args.get_kw_arg_opt("global", &RuntimeType::bool(), exec_state)?;
@ -286,6 +313,7 @@ pub async fn rotate(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
// 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]),
origin.map(|a| [a[0].n, a[1].n, a[2].n]),
angle.map(|t| t.n),
global,
exec_state,
@ -302,6 +330,7 @@ async fn inner_rotate(
pitch: Option<f64>,
yaw: Option<f64>,
axis: Option<[f64; 3]>,
origin: Option<[f64; 3]>,
angle: Option<f64>,
global: Option<bool>,
exec_state: &mut ExecState,
@ -313,6 +342,20 @@ async fn inner_rotate(
exec_state.flush_batch_for_solids((&args).into(), solids).await?;
}
let origin = if let Some(origin) = origin {
Some(OriginType::Custom {
origin: shared::Point3d {
x: origin[0],
y: origin[1],
z: origin[2],
},
})
} else if global.unwrap_or(false) {
Some(OriginType::Global)
} else {
Some(OriginType::Local)
};
let mut objects = objects.clone();
for object_id in objects.ids(&args.ctx).await? {
if let (Some(axis), Some(angle)) = (&axis, angle) {
@ -322,16 +365,17 @@ async fn inner_rotate(
ModelingCmd::from(mcmd::SetObjectTransform {
object_id,
transforms: vec![shared::ComponentTransform {
rotate_angle_axis: Some(shared::TransformBy::<Point4d<f64>> {
property: shared::Point4d {
rotate_angle_axis: Some(transform_by(
shared::Point4d {
x: axis[0],
y: axis[1],
z: axis[2],
w: angle,
},
set: false,
is_local: !global.unwrap_or(false),
}),
false,
!global.unwrap_or(false),
origin,
)),
scale: None,
rotate_rpy: None,
translate: None,
@ -347,15 +391,16 @@ async fn inner_rotate(
ModelingCmd::from(mcmd::SetObjectTransform {
object_id,
transforms: vec![shared::ComponentTransform {
rotate_rpy: Some(shared::TransformBy::<Point3d<f64>> {
property: shared::Point3d {
rotate_rpy: Some(transform_by(
shared::Point3d {
x: roll.unwrap_or(0.0),
y: pitch.unwrap_or(0.0),
z: yaw.unwrap_or(0.0),
},
set: false,
is_local: !global.unwrap_or(false),
}),
false,
!global.unwrap_or(false),
origin,
)),
scale: None,
rotate_angle_axis: None,
translate: None,