KCL: Track Z axis of planes

KCL should track which way a plane is facing. If it only tracks a plane's
X and Y axes, it could identify two separate planes (overlapping
perfectly in space, but one plane's "up" is the other's down).

This is helpful for knowing which way to extrude on a given plane, or
which way to cut "into" a given solid.
This commit is contained in:
Adam Chalmers
2025-07-03 11:22:50 -05:00
parent df6256266c
commit 61cae3026e
8 changed files with 94 additions and 35 deletions

View File

@ -26,6 +26,8 @@ myXY = {
```
Any object with appropriate `origin`, `xAxis`, and `yAxis` fields can be used as a plane.
The plane's Z axis (i.e. which way is "up") will be the cross product X x Y. In other words,
KCL planes follow the right-hand rule.

View File

@ -49,37 +49,60 @@ lazy_static::lazy_static! {
pub static ref GRID_SCALE_TEXT_OBJECT_ID: uuid::Uuid = uuid::Uuid::parse_str("10782f33-f588-4668-8bcd-040502d26590").unwrap();
pub static ref DEFAULT_PLANE_INFO: IndexMap<PlaneName, PlaneInfo> = IndexMap::from([
(PlaneName::Xy,PlaneInfo{
(
PlaneName::Xy,
PlaneInfo {
origin: Point3d::new(0.0, 0.0, 0.0, UnitLen::Mm),
x_axis: Point3d::new(1.0, 0.0, 0.0, UnitLen::Unknown),
y_axis: Point3d::new(0.0, 1.0, 0.0, UnitLen::Unknown),
}),
(PlaneName::NegXy,
z_axis: Point3d::new(0.0, 0.0, 1.0, UnitLen::Unknown),
},
),
(
PlaneName::NegXy,
PlaneInfo {
origin: Point3d::new( 0.0, 0.0, 0.0, UnitLen::Mm),
x_axis: Point3d::new(-1.0, 0.0, 0.0, UnitLen::Unknown),
y_axis: Point3d::new( 0.0, 1.0, 0.0, UnitLen::Unknown),
}),
(PlaneName::Xz, PlaneInfo{
z_axis: Point3d::new( 0.0, 0.0, -1.0, UnitLen::Unknown),
},
),
(
PlaneName::Xz,
PlaneInfo {
origin: Point3d::new(0.0, 0.0, 0.0, UnitLen::Mm),
x_axis: Point3d::new(1.0, 0.0, 0.0, UnitLen::Unknown),
y_axis: Point3d::new(0.0, 0.0, 1.0, UnitLen::Unknown),
}),
(PlaneName::NegXz, PlaneInfo{
z_axis: Point3d::new(1.0, 0.0, 0.0, UnitLen::Unknown),
},
),
(
PlaneName::NegXz,
PlaneInfo {
origin: Point3d::new( 0.0, 0.0, 0.0, UnitLen::Mm),
x_axis: Point3d::new(-1.0, 0.0, 0.0, UnitLen::Unknown),
y_axis: Point3d::new( 0.0, 0.0, 1.0, UnitLen::Unknown),
}),
(PlaneName::Yz, PlaneInfo{
z_axis: Point3d::new(-1.0, 0.0, 0.0, UnitLen::Unknown),
},
),
(
PlaneName::Yz,
PlaneInfo {
origin: Point3d::new(0.0, 0.0, 0.0, UnitLen::Mm),
x_axis: Point3d::new(0.0, 1.0, 0.0, UnitLen::Unknown),
y_axis: Point3d::new(0.0, 0.0, 1.0, UnitLen::Unknown),
}),
(PlaneName::NegYz, PlaneInfo{
z_axis: Point3d::new(0.0, 1.0, 0.0, UnitLen::Unknown),
},
),
(
PlaneName::NegYz,
PlaneInfo {
origin: Point3d::new(0.0, 0.0, 0.0, UnitLen::Mm),
x_axis: Point3d::new(0.0, -1.0, 0.0, UnitLen::Unknown),
y_axis: Point3d::new(0.0, 0.0, 1.0, UnitLen::Unknown),
}),
z_axis: Point3d::new(0.0, -1.0, 0.0, UnitLen::Unknown),
},
),
]);
}

View File

@ -299,6 +299,8 @@ pub struct PlaneInfo {
pub x_axis: Point3d,
/// What should the plane's Y axis be?
pub y_axis: Point3d,
/// What should the plane's Z axis be?
pub z_axis: Point3d,
}
impl PlaneInfo {
@ -327,6 +329,7 @@ impl PlaneInfo {
z: 0.0,
units: _,
},
z_axis: _,
} => return PlaneData::XY,
Self {
origin:
@ -350,6 +353,7 @@ impl PlaneInfo {
z: 0.0,
units: _,
},
z_axis: _,
} => return PlaneData::NegXY,
Self {
origin:
@ -373,6 +377,7 @@ impl PlaneInfo {
z: 1.0,
units: _,
},
z_axis: _,
} => return PlaneData::XZ,
Self {
origin:
@ -396,6 +401,7 @@ impl PlaneInfo {
z: 1.0,
units: _,
},
z_axis: _,
} => return PlaneData::NegXZ,
Self {
origin:
@ -419,6 +425,7 @@ impl PlaneInfo {
z: 1.0,
units: _,
},
z_axis: _,
} => return PlaneData::YZ,
Self {
origin:
@ -442,6 +449,7 @@ impl PlaneInfo {
z: 1.0,
units: _,
},
z_axis: _,
} => return PlaneData::NegYZ,
_ => {}
}
@ -451,6 +459,7 @@ impl PlaneInfo {
origin: self.origin,
x_axis: self.x_axis,
y_axis: self.y_axis,
z_axis: self.z_axis,
})
}
}

View File

@ -1182,6 +1182,7 @@ impl KclValue {
.get("yAxis")
.and_then(Point3d::from_kcl_val)
.ok_or(CoercionError::from(self))?;
let z_axis = x_axis.axes_cross_product(&y_axis);
if value.get("zAxis").is_some() {
exec_state.warn(CompilationError::err(
@ -1198,6 +1199,7 @@ impl KclValue {
origin,
x_axis: x_axis.normalize(),
y_axis: y_axis.normalize(),
z_axis: z_axis.normalize(),
},
value: super::PlaneType::Uninit,
meta: meta.clone(),

View File

@ -212,6 +212,7 @@ pub fn common(
origin,
x_axis: x_vec,
y_axis: y_vec,
z_axis: x_vec.axes_cross_product(&y_vec),
};
// Return early if we have a default plane.

View File

@ -674,6 +674,7 @@ impl<'a> FromKclValue<'a> for super::sketch::PlaneData {
origin: value.info.origin,
x_axis: value.info.x_axis,
y_axis: value.info.y_axis,
z_axis: value.info.z_axis,
}));
}
// Case 1: predefined plane
@ -692,9 +693,15 @@ impl<'a> FromKclValue<'a> for super::sketch::PlaneData {
let obj = arg.as_object()?;
let_field_of!(obj, plane, &KclObjectFields);
let origin = plane.get("origin").and_then(FromKclValue::from_kcl_val)?;
let x_axis = plane.get("xAxis").and_then(FromKclValue::from_kcl_val)?;
let x_axis: crate::execution::Point3d = plane.get("xAxis").and_then(FromKclValue::from_kcl_val)?;
let y_axis = plane.get("yAxis").and_then(FromKclValue::from_kcl_val)?;
Some(Self::Plane(PlaneInfo { origin, x_axis, y_axis }))
let z_axis = x_axis.axes_cross_product(&y_axis);
Some(Self::Plane(PlaneInfo {
origin,
x_axis,
y_axis,
z_axis,
}))
}
}

View File

@ -51,6 +51,7 @@ async fn inner_plane_of(
origin: Default::default(),
x_axis: Default::default(),
y_axis: Default::default(),
z_axis: Default::default(),
},
meta: vec![Metadata {
source_range: args.source_range,
@ -81,6 +82,7 @@ async fn inner_plane_of(
)));
let Some(x_axis) = planar.x_axis else { return not_planar };
let Some(y_axis) = planar.y_axis else { return not_planar };
let Some(z_axis) = planar.z_axis else { return not_planar };
let Some(origin) = planar.origin else { return not_planar };
// Engine always returns measurements in mm.
@ -97,6 +99,12 @@ async fn inner_plane_of(
z: y_axis.z,
units: engine_units,
};
let z_axis = crate::execution::Point3d {
x: z_axis.x,
y: z_axis.y,
z: z_axis.z,
units: engine_units,
};
let origin = crate::execution::Point3d {
x: origin.x.0,
y: origin.y.0,
@ -111,7 +119,12 @@ async fn inner_plane_of(
id: plane_id,
// Engine doesn't know about the ID we created, so set this to Uninit.
value: PlaneType::Uninit,
info: crate::execution::PlaneInfo { origin, x_axis, y_axis },
info: crate::execution::PlaneInfo {
origin,
x_axis,
y_axis,
z_axis,
},
meta: vec![Metadata {
source_range: args.source_range,
}],

View File

@ -210,6 +210,8 @@ export type fn
/// ```
///
/// Any object with appropriate `origin`, `xAxis`, and `yAxis` fields can be used as a plane.
/// The plane's Z axis (i.e. which way is "up") will be the cross product X x Y. In other words,
/// KCL planes follow the right-hand rule.
@(impl = std_rust)
export type Plane