2024-03-26 19:07:16 -07:00
|
|
|
//! Standard library revolution surfaces.
|
|
|
|
|
|
|
|
use anyhow::Result;
|
|
|
|
use derive_docs::stdlib;
|
2024-09-19 14:06:29 -07:00
|
|
|
use kcmc::{each_cmd as mcmd, length_unit::LengthUnit, shared::Angle, ModelingCmd};
|
2024-09-18 17:04:04 -05:00
|
|
|
use kittycad_modeling_cmds::{self as kcmc};
|
2024-03-26 19:07:16 -07:00
|
|
|
use schemars::JsonSchema;
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
|
|
|
|
use crate::{
|
|
|
|
errors::{KclError, KclErrorDetails},
|
2024-09-16 15:10:33 -04:00
|
|
|
executor::{ExecState, ExtrudeGroup, KclValue, SketchGroup},
|
2024-03-26 19:07:16 -07:00
|
|
|
std::{
|
|
|
|
extrude::do_post_extrude,
|
2024-08-12 21:39:49 -07:00
|
|
|
fillet::{default_tolerance, EdgeReference},
|
2024-03-26 19:07:16 -07:00
|
|
|
Args,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
/// Data for revolution surfaces.
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
|
|
|
#[ts(export)]
|
|
|
|
pub struct RevolveData {
|
|
|
|
/// Angle to revolve (in degrees). Default is 360.
|
|
|
|
#[serde(default)]
|
|
|
|
pub angle: Option<f64>,
|
|
|
|
/// Axis of revolution.
|
|
|
|
pub axis: RevolveAxis,
|
2024-08-12 21:39:49 -07:00
|
|
|
/// Tolerance for the revolve operation.
|
|
|
|
#[serde(default)]
|
|
|
|
pub tolerance: Option<f64>,
|
2024-03-26 19:07:16 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Axis of revolution or tagged edge.
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
|
|
|
#[ts(export)]
|
|
|
|
#[serde(untagged)]
|
|
|
|
pub enum RevolveAxis {
|
|
|
|
/// Axis of revolution.
|
|
|
|
Axis(RevolveAxisAndOrigin),
|
|
|
|
/// Tagged edge.
|
|
|
|
Edge(EdgeReference),
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Axis of revolution.
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
|
|
|
#[ts(export)]
|
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
pub enum RevolveAxisAndOrigin {
|
|
|
|
/// X-axis.
|
2024-04-12 13:28:58 -07:00
|
|
|
#[serde(rename = "X", alias = "x")]
|
2024-03-26 19:07:16 -07:00
|
|
|
X,
|
|
|
|
/// Y-axis.
|
2024-04-12 13:28:58 -07:00
|
|
|
#[serde(rename = "Y", alias = "y")]
|
2024-03-26 19:07:16 -07:00
|
|
|
Y,
|
|
|
|
/// Z-axis.
|
2024-04-12 13:28:58 -07:00
|
|
|
#[serde(rename = "Z", alias = "z")]
|
2024-03-26 19:07:16 -07:00
|
|
|
Z,
|
|
|
|
/// Flip the X-axis.
|
|
|
|
#[serde(rename = "-X", alias = "-x")]
|
|
|
|
NegX,
|
|
|
|
/// Flip the Y-axis.
|
|
|
|
#[serde(rename = "-Y", alias = "-y")]
|
|
|
|
NegY,
|
|
|
|
/// Flip the Z-axis.
|
|
|
|
#[serde(rename = "-Z", alias = "-z")]
|
|
|
|
NegZ,
|
|
|
|
Custom {
|
|
|
|
/// The axis.
|
|
|
|
axis: [f64; 3],
|
|
|
|
/// The origin.
|
|
|
|
origin: [f64; 3],
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
impl RevolveAxisAndOrigin {
|
|
|
|
/// Get the axis and origin.
|
2024-09-18 17:04:04 -05:00
|
|
|
pub fn axis_and_origin(&self) -> Result<(kcmc::shared::Point3d<f64>, kcmc::shared::Point3d<LengthUnit>), KclError> {
|
2024-03-26 19:07:16 -07:00
|
|
|
let (axis, origin) = match self {
|
|
|
|
RevolveAxisAndOrigin::X => ([1.0, 0.0, 0.0], [0.0, 0.0, 0.0]),
|
|
|
|
RevolveAxisAndOrigin::Y => ([0.0, 1.0, 0.0], [0.0, 0.0, 0.0]),
|
|
|
|
RevolveAxisAndOrigin::Z => ([0.0, 0.0, 1.0], [0.0, 0.0, 0.0]),
|
|
|
|
RevolveAxisAndOrigin::NegX => ([-1.0, 0.0, 0.0], [0.0, 0.0, 0.0]),
|
|
|
|
RevolveAxisAndOrigin::NegY => ([0.0, -1.0, 0.0], [0.0, 0.0, 0.0]),
|
|
|
|
RevolveAxisAndOrigin::NegZ => ([0.0, 0.0, -1.0], [0.0, 0.0, 0.0]),
|
|
|
|
RevolveAxisAndOrigin::Custom { axis, origin } => (*axis, *origin),
|
|
|
|
};
|
|
|
|
|
|
|
|
Ok((
|
2024-09-18 17:04:04 -05:00
|
|
|
kcmc::shared::Point3d {
|
2024-03-26 19:07:16 -07:00
|
|
|
x: axis[0],
|
|
|
|
y: axis[1],
|
|
|
|
z: axis[2],
|
|
|
|
},
|
2024-09-18 17:04:04 -05:00
|
|
|
kcmc::shared::Point3d {
|
|
|
|
x: LengthUnit(origin[0]),
|
|
|
|
y: LengthUnit(origin[1]),
|
|
|
|
z: LengthUnit(origin[2]),
|
2024-03-26 19:07:16 -07:00
|
|
|
},
|
|
|
|
))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Revolve a sketch around an axis.
|
2024-09-16 15:10:33 -04:00
|
|
|
pub async fn revolve(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
Remove KclValue::SketchGroup variant (#3446)
We can store Rust types like `SketchGroup` as their own variant of `KclValue`, or as `KclValue::UserVal`. Sometimes we store in one and try to read from the other, which fails. This causes bugs, like #3338.
Instead, we should use either ::SketchGroup or ::UserVal, and stop using the other. If we stopped using ::UserVal, we'd need a new variant for every Rust type we wanted to build, including user-defined types. So I don't think that's practical.
Instead, we should store every KCL value by de/serializing it into UserVal. This is a first step along that path, removing just the SketchGroup variants. If it goes well, we can remove the other specialized variants too.
My only concern is there might be performance implications from how frequently we convert between serde_json::Value and Rust types via Serde. But I'm not too worried -- there's no parsing JSON strings, just traversing serde_json::Value trees. This isn't great for performance but I think it'll probably be miniscule in comparison to doing all the API calls.
2024-08-21 11:06:48 -05:00
|
|
|
let (data, sketch_group): (RevolveData, SketchGroup) = args.get_data_and_sketch_group()?;
|
2024-03-26 19:07:16 -07:00
|
|
|
|
2024-09-16 15:10:33 -04:00
|
|
|
let extrude_group = inner_revolve(data, sketch_group, exec_state, args).await?;
|
2024-08-12 16:53:24 -05:00
|
|
|
Ok(KclValue::ExtrudeGroup(extrude_group))
|
2024-03-26 19:07:16 -07:00
|
|
|
}
|
|
|
|
|
2024-08-06 20:27:26 -04:00
|
|
|
/// Rotate a sketch around some provided axis, creating a solid from its extent.
|
|
|
|
///
|
|
|
|
/// This, like extrude, is able to create a 3-dimensional solid from a
|
|
|
|
/// 2-dimensional sketch. However, unlike extrude, this creates a solid
|
|
|
|
/// by using the extent of the sketch as its revolved around an axis rather
|
|
|
|
/// than using the extent of the sketch linearly translated through a third
|
|
|
|
/// dimension.
|
2024-03-26 19:07:16 -07:00
|
|
|
///
|
|
|
|
/// ```no_run
|
|
|
|
/// const part001 = startSketchOn('XY')
|
|
|
|
/// |> startProfileAt([4, 12], %)
|
|
|
|
/// |> line([2, 0], %)
|
|
|
|
/// |> line([0, -6], %)
|
|
|
|
/// |> line([4, -6], %)
|
|
|
|
/// |> line([0, -6], %)
|
|
|
|
/// |> line([-3.75, -4.5], %)
|
|
|
|
/// |> line([0, -5.5], %)
|
|
|
|
/// |> line([-2, 0], %)
|
|
|
|
/// |> close(%)
|
|
|
|
/// |> revolve({axis: 'y'}, %) // default angle is 360
|
|
|
|
/// ```
|
|
|
|
///
|
|
|
|
/// ```no_run
|
2024-03-27 09:46:13 -07:00
|
|
|
/// // A donut shape.
|
|
|
|
/// const sketch001 = startSketchOn('XY')
|
2024-09-23 22:42:51 +10:00
|
|
|
/// |> circle({ center: [15, 0], radius: 5 }, %)
|
2024-03-27 09:46:13 -07:00
|
|
|
/// |> revolve({
|
|
|
|
/// angle: 360,
|
|
|
|
/// axis: 'y'
|
|
|
|
/// }, %)
|
|
|
|
/// ```
|
|
|
|
///
|
|
|
|
/// ```no_run
|
2024-03-26 19:07:16 -07:00
|
|
|
/// const part001 = startSketchOn('XY')
|
|
|
|
/// |> startProfileAt([4, 12], %)
|
|
|
|
/// |> line([2, 0], %)
|
|
|
|
/// |> line([0, -6], %)
|
|
|
|
/// |> line([4, -6], %)
|
|
|
|
/// |> line([0, -6], %)
|
|
|
|
/// |> line([-3.75, -4.5], %)
|
|
|
|
/// |> line([0, -5.5], %)
|
|
|
|
/// |> line([-2, 0], %)
|
|
|
|
/// |> close(%)
|
|
|
|
/// |> revolve({axis: 'y', angle: 180}, %)
|
|
|
|
/// ```
|
|
|
|
///
|
|
|
|
/// ```no_run
|
2024-03-27 17:34:07 -07:00
|
|
|
/// const part001 = startSketchOn('XY')
|
|
|
|
/// |> startProfileAt([4, 12], %)
|
|
|
|
/// |> line([2, 0], %)
|
|
|
|
/// |> line([0, -6], %)
|
|
|
|
/// |> line([4, -6], %)
|
|
|
|
/// |> line([0, -6], %)
|
|
|
|
/// |> line([-3.75, -4.5], %)
|
|
|
|
/// |> line([0, -5.5], %)
|
|
|
|
/// |> line([-2, 0], %)
|
|
|
|
/// |> close(%)
|
|
|
|
/// |> revolve({axis: 'y', angle: 180}, %)
|
|
|
|
/// const part002 = startSketchOn(part001, 'end')
|
|
|
|
/// |> startProfileAt([4.5, -5], %)
|
|
|
|
/// |> line([0, 5], %)
|
|
|
|
/// |> line([5, 0], %)
|
|
|
|
/// |> line([0, -5], %)
|
|
|
|
/// |> close(%)
|
|
|
|
/// |> extrude(5, %)
|
|
|
|
/// ```
|
|
|
|
///
|
|
|
|
/// ```no_run
|
2024-03-26 19:07:16 -07:00
|
|
|
/// const box = startSketchOn('XY')
|
|
|
|
/// |> startProfileAt([0, 0], %)
|
|
|
|
/// |> line([0, 20], %)
|
|
|
|
/// |> line([20, 0], %)
|
|
|
|
/// |> line([0, -20], %)
|
|
|
|
/// |> close(%)
|
|
|
|
/// |> extrude(20, %)
|
|
|
|
///
|
|
|
|
/// const sketch001 = startSketchOn(box, "END")
|
2024-09-23 22:42:51 +10:00
|
|
|
/// |> circle({ center: [10,10], radius: 4 }, %)
|
2024-03-26 19:07:16 -07:00
|
|
|
/// |> revolve({
|
|
|
|
/// angle: -90,
|
|
|
|
/// axis: 'y'
|
|
|
|
/// }, %)
|
|
|
|
/// ```
|
|
|
|
///
|
|
|
|
/// ```no_run
|
|
|
|
/// const box = startSketchOn('XY')
|
|
|
|
/// |> startProfileAt([0, 0], %)
|
|
|
|
/// |> line([0, 20], %)
|
|
|
|
/// |> line([20, 0], %)
|
2024-07-27 17:59:41 -07:00
|
|
|
/// |> line([0, -20], %, $revolveAxis)
|
2024-03-26 19:07:16 -07:00
|
|
|
/// |> close(%)
|
|
|
|
/// |> extrude(20, %)
|
|
|
|
///
|
|
|
|
/// const sketch001 = startSketchOn(box, "END")
|
2024-09-23 22:42:51 +10:00
|
|
|
/// |> circle({ center: [10,10], radius: 4 }, %)
|
2024-03-26 19:07:16 -07:00
|
|
|
/// |> revolve({
|
|
|
|
/// angle: 90,
|
2024-07-27 22:56:46 -07:00
|
|
|
/// axis: getOppositeEdge(revolveAxis)
|
2024-03-26 19:07:16 -07:00
|
|
|
/// }, %)
|
|
|
|
/// ```
|
2024-06-05 15:48:59 -04:00
|
|
|
///
|
|
|
|
/// ```no_run
|
2024-08-12 21:39:49 -07:00
|
|
|
/// const box = startSketchOn('XY')
|
|
|
|
/// |> startProfileAt([0, 0], %)
|
|
|
|
/// |> line([0, 20], %)
|
|
|
|
/// |> line([20, 0], %)
|
|
|
|
/// |> line([0, -20], %, $revolveAxis)
|
|
|
|
/// |> close(%)
|
|
|
|
/// |> extrude(20, %)
|
|
|
|
///
|
|
|
|
/// const sketch001 = startSketchOn(box, "END")
|
2024-09-23 22:42:51 +10:00
|
|
|
/// |> circle({ center: [10,10], radius: 4 }, %)
|
2024-08-12 21:39:49 -07:00
|
|
|
/// |> revolve({
|
|
|
|
/// angle: 90,
|
|
|
|
/// axis: getOppositeEdge(revolveAxis),
|
|
|
|
/// tolerance: 0.0001
|
|
|
|
/// }, %)
|
|
|
|
/// ```
|
|
|
|
///
|
|
|
|
/// ```no_run
|
2024-06-05 15:48:59 -04:00
|
|
|
/// const sketch001 = startSketchOn('XY')
|
|
|
|
/// |> startProfileAt([10, 0], %)
|
|
|
|
/// |> line([5, -5], %)
|
|
|
|
/// |> line([5, 5], %)
|
|
|
|
/// |> lineTo([profileStartX(%), profileStartY(%)], %)
|
|
|
|
/// |> close(%)
|
|
|
|
///
|
|
|
|
/// const part001 = revolve({
|
|
|
|
/// axis: {
|
|
|
|
/// custom: {
|
|
|
|
/// axis: [0.0, 1.0, 0.0],
|
|
|
|
/// origin: [0.0, 0.0, 0.0]
|
|
|
|
/// }
|
|
|
|
/// }
|
|
|
|
/// }, sketch001)
|
|
|
|
/// ```
|
2024-03-26 19:07:16 -07:00
|
|
|
#[stdlib {
|
|
|
|
name = "revolve",
|
|
|
|
}]
|
|
|
|
async fn inner_revolve(
|
|
|
|
data: RevolveData,
|
Remove KclValue::SketchGroup variant (#3446)
We can store Rust types like `SketchGroup` as their own variant of `KclValue`, or as `KclValue::UserVal`. Sometimes we store in one and try to read from the other, which fails. This causes bugs, like #3338.
Instead, we should use either ::SketchGroup or ::UserVal, and stop using the other. If we stopped using ::UserVal, we'd need a new variant for every Rust type we wanted to build, including user-defined types. So I don't think that's practical.
Instead, we should store every KCL value by de/serializing it into UserVal. This is a first step along that path, removing just the SketchGroup variants. If it goes well, we can remove the other specialized variants too.
My only concern is there might be performance implications from how frequently we convert between serde_json::Value and Rust types via Serde. But I'm not too worried -- there's no parsing JSON strings, just traversing serde_json::Value trees. This isn't great for performance but I think it'll probably be miniscule in comparison to doing all the API calls.
2024-08-21 11:06:48 -05:00
|
|
|
sketch_group: SketchGroup,
|
2024-09-16 15:10:33 -04:00
|
|
|
exec_state: &mut ExecState,
|
2024-03-26 19:07:16 -07:00
|
|
|
args: Args,
|
|
|
|
) -> Result<Box<ExtrudeGroup>, KclError> {
|
|
|
|
if let Some(angle) = data.angle {
|
|
|
|
// Return an error if the angle is less than -360 or greater than 360.
|
|
|
|
if !(-360.0..=360.0).contains(&angle) {
|
|
|
|
return Err(KclError::Semantic(KclErrorDetails {
|
|
|
|
message: format!("Expected angle to be between -360 and 360, found `{}`", angle),
|
|
|
|
source_ranges: vec![args.source_range],
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-09-18 17:04:04 -05:00
|
|
|
let angle = Angle::from_degrees(data.angle.unwrap_or(360.0));
|
2024-03-26 19:07:16 -07:00
|
|
|
|
|
|
|
let id = uuid::Uuid::new_v4();
|
|
|
|
match data.axis {
|
|
|
|
RevolveAxis::Axis(axis) => {
|
|
|
|
let (axis, origin) = axis.axis_and_origin()?;
|
2024-06-19 13:57:50 -07:00
|
|
|
args.batch_modeling_cmd(
|
2024-03-26 19:07:16 -07:00
|
|
|
id,
|
2024-09-18 17:04:04 -05:00
|
|
|
ModelingCmd::from(mcmd::Revolve {
|
2024-03-26 19:07:16 -07:00
|
|
|
angle,
|
2024-09-18 17:04:04 -05:00
|
|
|
target: sketch_group.id.into(),
|
2024-03-26 19:07:16 -07:00
|
|
|
axis,
|
|
|
|
origin,
|
2024-09-18 17:04:04 -05:00
|
|
|
tolerance: LengthUnit(data.tolerance.unwrap_or(default_tolerance(&args.ctx.settings.units))),
|
2024-03-26 19:07:16 -07:00
|
|
|
axis_is_2d: true,
|
2024-09-18 17:04:04 -05:00
|
|
|
}),
|
2024-03-26 19:07:16 -07:00
|
|
|
)
|
|
|
|
.await?;
|
|
|
|
}
|
|
|
|
RevolveAxis::Edge(edge) => {
|
|
|
|
let edge_id = match edge {
|
|
|
|
EdgeReference::Uuid(uuid) => uuid,
|
2024-09-16 15:10:33 -04:00
|
|
|
EdgeReference::Tag(tag) => args.get_tag_engine_info(exec_state, &tag)?.id,
|
2024-03-26 19:07:16 -07:00
|
|
|
};
|
2024-06-19 13:57:50 -07:00
|
|
|
args.batch_modeling_cmd(
|
2024-03-26 19:07:16 -07:00
|
|
|
id,
|
2024-09-18 17:04:04 -05:00
|
|
|
ModelingCmd::from(mcmd::RevolveAboutEdge {
|
2024-03-26 19:07:16 -07:00
|
|
|
angle,
|
2024-09-18 17:04:04 -05:00
|
|
|
target: sketch_group.id.into(),
|
2024-03-26 19:07:16 -07:00
|
|
|
edge_id,
|
2024-09-18 17:04:04 -05:00
|
|
|
tolerance: LengthUnit(data.tolerance.unwrap_or(default_tolerance(&args.ctx.settings.units))),
|
|
|
|
}),
|
2024-03-26 19:07:16 -07:00
|
|
|
)
|
|
|
|
.await?;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
Batch extrudes (#3764)
Adds a new KCL executor benchmark which builds a `10` wide by `n` tall lego, with varying `n`. The benchmark runs a n = 1, 2, 3 etc build, so we can get an idea of how the speed changes with size.
This change improves execution speed by 25-36% depending on how many bumps there are. Tested by:
* Rust unit tests
* Open up modeling app, sketch a square, use the command palette to extrude it
* Open up the Bambu printer "poop chute" model, it all extrudes and works fine
Also fixes a bug: extrude, loft, revolve all trigger a GetExtrusionFaceInfo command. Due to a bug, the GetExtrusionFaceInfo command reused the Command ID from the previous extrude/loft/revolve. Fixed that.
2024-09-06 16:55:24 -05:00
|
|
|
do_post_extrude(sketch_group, 0.0, args).await
|
2024-03-26 19:07:16 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
|
|
|
|
use pretty_assertions::assert_eq;
|
|
|
|
|
|
|
|
use crate::std::revolve::{RevolveAxis, RevolveAxisAndOrigin};
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_deserialize_revolve_axis() {
|
|
|
|
let data = RevolveAxis::Axis(RevolveAxisAndOrigin::X);
|
|
|
|
let mut str_json = serde_json::to_string(&data).unwrap();
|
2024-04-12 13:28:58 -07:00
|
|
|
assert_eq!(str_json, "\"X\"");
|
2024-03-26 19:07:16 -07:00
|
|
|
|
|
|
|
str_json = "\"Y\"".to_string();
|
|
|
|
let data: RevolveAxis = serde_json::from_str(&str_json).unwrap();
|
|
|
|
assert_eq!(data, RevolveAxis::Axis(RevolveAxisAndOrigin::Y));
|
|
|
|
|
|
|
|
str_json = "\"-Y\"".to_string();
|
|
|
|
let data: RevolveAxis = serde_json::from_str(&str_json).unwrap();
|
|
|
|
assert_eq!(data, RevolveAxis::Axis(RevolveAxisAndOrigin::NegY));
|
|
|
|
|
|
|
|
str_json = "\"-x\"".to_string();
|
|
|
|
let data: RevolveAxis = serde_json::from_str(&str_json).unwrap();
|
|
|
|
assert_eq!(data, RevolveAxis::Axis(RevolveAxisAndOrigin::NegX));
|
|
|
|
|
|
|
|
let data = RevolveAxis::Axis(RevolveAxisAndOrigin::Custom {
|
|
|
|
axis: [0.0, -1.0, 0.0],
|
|
|
|
origin: [1.0, 0.0, 2.0],
|
|
|
|
});
|
|
|
|
str_json = serde_json::to_string(&data).unwrap();
|
|
|
|
assert_eq!(str_json, r#"{"custom":{"axis":[0.0,-1.0,0.0],"origin":[1.0,0.0,2.0]}}"#);
|
|
|
|
|
|
|
|
str_json = r#"{"custom": {"axis": [0,-1,0], "origin": [1,0,2.0]}}"#.to_string();
|
|
|
|
let data: RevolveAxis = serde_json::from_str(&str_json).unwrap();
|
|
|
|
assert_eq!(
|
|
|
|
data,
|
|
|
|
RevolveAxis::Axis(RevolveAxisAndOrigin::Custom {
|
|
|
|
axis: [0.0, -1.0, 0.0],
|
|
|
|
origin: [1.0, 0.0, 2.0]
|
|
|
|
})
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|