2023-08-24 15:34:51 -07:00
//! Functions related to sketching.
2023-08-29 14:12:48 -07:00
use anyhow ::Result ;
2023-08-25 13:41:04 -07:00
use derive_docs ::stdlib ;
2023-10-24 16:32:41 -07:00
use kittycad ::types ::{ Angle , ModelingCmd , Point3D } ;
Grackle (KCL to EP compiler) (#1270)
* Start Grackle (KCL-to-EP compiler)
This begins work on a second, different executor. The old executor is a tree-walk interpreter, this executor compiles the KCL programs into the Execution Plan virtual machine defined in its [own crate](https://github.com/KittyCAD/modeling-api/tree/main/execution-plan). This executor is called "Grackle", after an Austin bird, and it's got its own module in wasm-lib so that I can keep merging small PRs and developing incrementally, rather than building a complete executor which replaces the old executor in one PR.
Grackle's "Planner" walks the AST, like the tree-walk executor. But it doesn't actually execute code. Instead, as it walks each AST node, it outputs a sequence of Execution Plan instructions which, when run, can compute that node's value. It also notes which Execution Plan virtual machine address will eventually contain each KCL variable.
Done:
- Storing KCL variables
- Computing primitives, literals, binary expressions
- Calling native (i.e. Rust) functions from KCL
- Storing arrays
Todo:
- KCL functions (i.e. user-defined functions)
- Member expressions
- Port over existing executor's native funtions (e.g. `lineTo`, `extrude` and `startSketchAt`)
2024-01-11 09:25:10 -06:00
use kittycad_execution_plan_macros ::ExecutionPlanValue ;
2023-08-25 13:41:04 -07:00
use schemars ::JsonSchema ;
2023-08-24 15:34:51 -07:00
use serde ::{ Deserialize , Serialize } ;
Remove just one enum (#1096)
# Problem
This is my proposal for fixing #1107 . I've only done it for one stdlib function, `tangentialArcTo` -- if y'all like it, I'll apply this idea to the rest of the stdlib.
Previously, if users want to put a tag on the arc, the function's parameters change type.
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is object
tangentialArcTo({to: [x, y], tag: "myTag"}, %)
```
# Solution
My proposal in #1006 is that KCL should have optional values. This means we can change the stdlib `tangentialArcTo` function to use them. In this PR, the calls are now like
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is array still, but we now pass a tag at the end.
tangentialArcTo([x, y], %, "myTag")
```
This adds an "option" type to KCL typesystem, but it's not really revealed to users (no KCL types are revealed to users right now, they write untyped code and only interact with types when they get type errors upon executing programs). Also adds a None type, which is the default case of the Optional enum.
2023-12-18 23:49:32 -06:00
use crate ::executor ::SourceRange ;
2023-08-24 15:34:51 -07:00
use crate ::{
errors ::{ KclError , KclErrorDetails } ,
2023-10-05 14:27:48 -07:00
executor ::{
BasePath , GeoMeta , MemoryItem , Path , Plane , PlaneType , Point2d , Point3d , Position , Rotation , SketchGroup ,
} ,
2023-08-24 15:34:51 -07:00
std ::{
2023-08-31 22:19:23 -07:00
utils ::{ arc_angles , arc_center_and_end , get_x_component , get_y_component , intersection_with_parallel_line } ,
2023-08-24 15:34:51 -07:00
Args ,
} ,
} ;
2023-08-25 13:41:04 -07:00
/// Data to draw a line to a point.
#[ derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema) ]
2023-08-24 15:34:51 -07:00
#[ ts(export) ]
#[ serde(rename_all = " camelCase " , untagged) ]
pub enum LineToData {
2023-08-25 13:41:04 -07:00
/// A point with a tag.
PointWithTag {
/// The to point.
to : [ f64 ; 2 ] ,
/// The tag.
tag : String ,
} ,
/// A point.
2023-08-24 15:34:51 -07:00
Point ( [ f64 ; 2 ] ) ,
}
/// Draw a line to a point.
2023-09-20 18:27:08 -07:00
pub async fn line_to ( args : Args ) -> Result < MemoryItem , KclError > {
2023-09-19 14:20:14 -07:00
let ( data , sketch_group ) : ( LineToData , Box < SketchGroup > ) = args . get_data_and_sketch_group ( ) ? ;
2023-08-24 15:34:51 -07:00
2023-09-20 18:27:08 -07:00
let new_sketch_group = inner_line_to ( data , sketch_group , args ) . await ? ;
2023-08-24 15:34:51 -07:00
Ok ( MemoryItem ::SketchGroup ( new_sketch_group ) )
}
/// Draw a line to a point.
2023-08-25 13:41:04 -07:00
#[ stdlib {
name = " lineTo " ,
} ]
2023-09-20 18:27:08 -07:00
async fn inner_line_to (
2023-09-19 14:20:14 -07:00
data : LineToData ,
sketch_group : Box < SketchGroup > ,
2023-09-20 18:27:08 -07:00
args : Args ,
2023-09-19 14:20:14 -07:00
) -> Result < Box < SketchGroup > , KclError > {
2023-08-24 15:34:51 -07:00
let from = sketch_group . get_coords_from_paths ( ) ? ;
let to = match data {
LineToData ::PointWithTag { to , .. } = > to ,
LineToData ::Point ( to ) = > to ,
} ;
let id = uuid ::Uuid ::new_v4 ( ) ;
2023-08-31 22:19:23 -07:00
args . send_modeling_cmd (
id ,
ModelingCmd ::ExtendPath {
path : sketch_group . id ,
segment : kittycad ::types ::PathSegment ::Line {
end : Point3D {
x : to [ 0 ] ,
y : to [ 1 ] ,
z : 0.0 ,
} ,
2023-09-20 17:36:26 -07:00
relative : false ,
2023-08-31 22:19:23 -07:00
} ,
} ,
2023-09-20 18:27:08 -07:00
)
. await ? ;
2023-08-31 22:19:23 -07:00
2023-08-24 15:34:51 -07:00
let current_path = Path ::ToPoint {
base : BasePath {
from : from . into ( ) ,
to ,
name : if let LineToData ::PointWithTag { tag , .. } = data {
tag . to_string ( )
} else {
" " . to_string ( )
} ,
geo_meta : GeoMeta {
id ,
metadata : args . source_range . into ( ) ,
} ,
} ,
} ;
let mut new_sketch_group = sketch_group . clone ( ) ;
new_sketch_group . value . push ( current_path ) ;
Ok ( new_sketch_group )
}
2023-08-25 13:41:04 -07:00
/// Data to draw a line to a point on an axis.
#[ derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema) ]
2023-08-24 15:34:51 -07:00
#[ ts(export) ]
#[ serde(rename_all = " camelCase " , untagged) ]
pub enum AxisLineToData {
2023-08-25 13:41:04 -07:00
/// A point with a tag.
PointWithTag {
/// The to point.
to : f64 ,
/// The tag.
tag : String ,
} ,
/// A point.
2023-08-24 15:34:51 -07:00
Point ( f64 ) ,
}
/// Draw a line to a point on the x-axis.
2023-09-20 18:27:08 -07:00
pub async fn x_line_to ( args : Args ) -> Result < MemoryItem , KclError > {
2023-09-19 14:20:14 -07:00
let ( data , sketch_group ) : ( AxisLineToData , Box < SketchGroup > ) = args . get_data_and_sketch_group ( ) ? ;
2023-08-25 13:41:04 -07:00
2023-09-20 18:27:08 -07:00
let new_sketch_group = inner_x_line_to ( data , sketch_group , args ) . await ? ;
2023-08-25 13:41:04 -07:00
Ok ( MemoryItem ::SketchGroup ( new_sketch_group ) )
}
/// Draw a line to a point on the x-axis.
#[ stdlib {
name = " xLineTo " ,
} ]
2023-09-20 18:27:08 -07:00
async fn inner_x_line_to (
2023-09-19 14:20:14 -07:00
data : AxisLineToData ,
sketch_group : Box < SketchGroup > ,
2023-09-20 18:27:08 -07:00
args : Args ,
2023-09-19 14:20:14 -07:00
) -> Result < Box < SketchGroup > , KclError > {
2023-08-24 15:34:51 -07:00
let from = sketch_group . get_coords_from_paths ( ) ? ;
let line_to_data = match data {
2023-08-29 14:12:48 -07:00
AxisLineToData ::PointWithTag { to , tag } = > LineToData ::PointWithTag { to : [ to , from . y ] , tag } ,
2023-08-24 15:34:51 -07:00
AxisLineToData ::Point ( data ) = > LineToData ::Point ( [ data , from . y ] ) ,
} ;
2023-09-20 18:27:08 -07:00
let new_sketch_group = inner_line_to ( line_to_data , sketch_group , args ) . await ? ;
2023-08-25 13:41:04 -07:00
Ok ( new_sketch_group )
2023-08-24 15:34:51 -07:00
}
/// Draw a line to a point on the y-axis.
2023-09-20 18:27:08 -07:00
pub async fn y_line_to ( args : Args ) -> Result < MemoryItem , KclError > {
2023-09-19 14:20:14 -07:00
let ( data , sketch_group ) : ( AxisLineToData , Box < SketchGroup > ) = args . get_data_and_sketch_group ( ) ? ;
2023-08-25 13:41:04 -07:00
2023-09-20 18:27:08 -07:00
let new_sketch_group = inner_y_line_to ( data , sketch_group , args ) . await ? ;
2023-08-25 13:41:04 -07:00
Ok ( MemoryItem ::SketchGroup ( new_sketch_group ) )
}
/// Draw a line to a point on the y-axis.
#[ stdlib {
name = " yLineTo " ,
} ]
2023-09-20 18:27:08 -07:00
async fn inner_y_line_to (
2023-09-19 14:20:14 -07:00
data : AxisLineToData ,
sketch_group : Box < SketchGroup > ,
2023-09-20 18:27:08 -07:00
args : Args ,
2023-09-19 14:20:14 -07:00
) -> Result < Box < SketchGroup > , KclError > {
2023-08-24 15:34:51 -07:00
let from = sketch_group . get_coords_from_paths ( ) ? ;
let line_to_data = match data {
2023-08-29 14:12:48 -07:00
AxisLineToData ::PointWithTag { to , tag } = > LineToData ::PointWithTag { to : [ from . x , to ] , tag } ,
2023-08-24 15:34:51 -07:00
AxisLineToData ::Point ( data ) = > LineToData ::Point ( [ from . x , data ] ) ,
} ;
2023-09-20 18:27:08 -07:00
let new_sketch_group = inner_line_to ( line_to_data , sketch_group , args ) . await ? ;
2023-08-25 13:41:04 -07:00
Ok ( new_sketch_group )
2023-08-24 15:34:51 -07:00
}
2023-08-25 13:41:04 -07:00
/// Data to draw a line.
#[ derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema) ]
2023-08-24 15:34:51 -07:00
#[ ts(export) ]
#[ serde(rename_all = " camelCase " , untagged) ]
pub enum LineData {
2023-08-25 13:41:04 -07:00
/// A point with a tag.
PointWithTag {
/// The to point.
2023-09-13 07:23:14 -07:00
to : [ f64 ; 2 ] ,
2023-08-25 13:41:04 -07:00
/// The tag.
tag : String ,
} ,
/// A point.
2023-08-24 15:34:51 -07:00
Point ( [ f64 ; 2 ] ) ,
}
/// Draw a line.
2023-09-20 18:27:08 -07:00
pub async fn line ( args : Args ) -> Result < MemoryItem , KclError > {
2023-09-19 14:20:14 -07:00
let ( data , sketch_group ) : ( LineData , Box < SketchGroup > ) = args . get_data_and_sketch_group ( ) ? ;
2023-08-24 15:34:51 -07:00
2023-09-20 18:27:08 -07:00
let new_sketch_group = inner_line ( data , sketch_group , args ) . await ? ;
2023-08-24 15:34:51 -07:00
Ok ( MemoryItem ::SketchGroup ( new_sketch_group ) )
}
2023-08-25 13:41:04 -07:00
/// Draw a line.
#[ stdlib {
name = " line " ,
} ]
2023-09-20 18:27:08 -07:00
async fn inner_line ( data : LineData , sketch_group : Box < SketchGroup > , args : Args ) -> Result < Box < SketchGroup > , KclError > {
2023-08-24 15:34:51 -07:00
let from = sketch_group . get_coords_from_paths ( ) ? ;
let inner_args = match & data {
2023-09-13 07:23:14 -07:00
LineData ::PointWithTag { to , .. } = > * to ,
2023-08-24 15:34:51 -07:00
LineData ::Point ( to ) = > * to ,
} ;
2023-09-20 17:36:26 -07:00
let delta = inner_args ;
2023-08-24 15:34:51 -07:00
let to = [ from . x + inner_args [ 0 ] , from . y + inner_args [ 1 ] ] ;
let id = uuid ::Uuid ::new_v4 ( ) ;
args . send_modeling_cmd (
id ,
ModelingCmd ::ExtendPath {
path : sketch_group . id ,
segment : kittycad ::types ::PathSegment ::Line {
end : Point3D {
2023-09-20 17:36:26 -07:00
x : delta [ 0 ] ,
y : delta [ 1 ] ,
2023-08-24 15:34:51 -07:00
z : 0.0 ,
} ,
2023-09-20 17:36:26 -07:00
relative : true ,
2023-08-24 15:34:51 -07:00
} ,
} ,
2023-09-20 18:27:08 -07:00
)
. await ? ;
2023-08-24 15:34:51 -07:00
let current_path = Path ::ToPoint {
base : BasePath {
from : from . into ( ) ,
to ,
name : if let LineData ::PointWithTag { tag , .. } = data {
tag . to_string ( )
} else {
" " . to_string ( )
} ,
geo_meta : GeoMeta {
id ,
metadata : args . source_range . into ( ) ,
} ,
} ,
} ;
let mut new_sketch_group = sketch_group . clone ( ) ;
new_sketch_group . value . push ( current_path ) ;
Ok ( new_sketch_group )
}
2023-08-25 13:41:04 -07:00
/// Data to draw a line on an axis.
#[ derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema) ]
2023-08-24 15:34:51 -07:00
#[ ts(export) ]
#[ serde(rename_all = " camelCase " , untagged) ]
pub enum AxisLineData {
2023-08-25 13:41:04 -07:00
/// The length with a tag.
LengthWithTag {
/// The length of the line.
length : f64 ,
/// The tag.
tag : String ,
} ,
/// The length.
Length ( f64 ) ,
2023-08-24 15:34:51 -07:00
}
/// Draw a line on the x-axis.
2023-09-20 18:27:08 -07:00
pub async fn x_line ( args : Args ) -> Result < MemoryItem , KclError > {
2023-09-19 14:20:14 -07:00
let ( data , sketch_group ) : ( AxisLineData , Box < SketchGroup > ) = args . get_data_and_sketch_group ( ) ? ;
2023-08-24 15:34:51 -07:00
2023-09-20 18:27:08 -07:00
let new_sketch_group = inner_x_line ( data , sketch_group , args ) . await ? ;
2023-08-25 13:41:04 -07:00
Ok ( MemoryItem ::SketchGroup ( new_sketch_group ) )
}
/// Draw a line on the x-axis.
#[ stdlib {
name = " xLine " ,
} ]
2023-09-20 18:27:08 -07:00
async fn inner_x_line (
2023-09-19 14:20:14 -07:00
data : AxisLineData ,
sketch_group : Box < SketchGroup > ,
2023-09-20 18:27:08 -07:00
args : Args ,
2023-09-19 14:20:14 -07:00
) -> Result < Box < SketchGroup > , KclError > {
2023-08-24 15:34:51 -07:00
let line_data = match data {
2023-09-13 07:23:14 -07:00
AxisLineData ::LengthWithTag { length , tag } = > LineData ::PointWithTag { to : [ length , 0.0 ] , tag } ,
2023-08-25 13:41:04 -07:00
AxisLineData ::Length ( length ) = > LineData ::Point ( [ length , 0.0 ] ) ,
2023-08-24 15:34:51 -07:00
} ;
2023-09-20 18:27:08 -07:00
let new_sketch_group = inner_line ( line_data , sketch_group , args ) . await ? ;
2023-08-25 13:41:04 -07:00
Ok ( new_sketch_group )
2023-08-24 15:34:51 -07:00
}
/// Draw a line on the y-axis.
2023-09-20 18:27:08 -07:00
pub async fn y_line ( args : Args ) -> Result < MemoryItem , KclError > {
2023-09-19 14:20:14 -07:00
let ( data , sketch_group ) : ( AxisLineData , Box < SketchGroup > ) = args . get_data_and_sketch_group ( ) ? ;
2023-08-24 15:34:51 -07:00
2023-09-20 18:27:08 -07:00
let new_sketch_group = inner_y_line ( data , sketch_group , args ) . await ? ;
2023-08-25 13:41:04 -07:00
Ok ( MemoryItem ::SketchGroup ( new_sketch_group ) )
}
/// Draw a line on the y-axis.
#[ stdlib {
name = " yLine " ,
} ]
2023-09-20 18:27:08 -07:00
async fn inner_y_line (
2023-09-19 14:20:14 -07:00
data : AxisLineData ,
sketch_group : Box < SketchGroup > ,
2023-09-20 18:27:08 -07:00
args : Args ,
2023-09-19 14:20:14 -07:00
) -> Result < Box < SketchGroup > , KclError > {
2023-08-24 15:34:51 -07:00
let line_data = match data {
2023-09-13 07:23:14 -07:00
AxisLineData ::LengthWithTag { length , tag } = > LineData ::PointWithTag { to : [ 0.0 , length ] , tag } ,
2023-08-25 13:41:04 -07:00
AxisLineData ::Length ( length ) = > LineData ::Point ( [ 0.0 , length ] ) ,
2023-08-24 15:34:51 -07:00
} ;
2023-09-20 18:27:08 -07:00
let new_sketch_group = inner_line ( line_data , sketch_group , args ) . await ? ;
2023-08-25 13:41:04 -07:00
Ok ( new_sketch_group )
2023-08-24 15:34:51 -07:00
}
2023-08-25 13:41:04 -07:00
/// Data to draw an angled line.
#[ derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema) ]
2023-08-24 15:34:51 -07:00
#[ ts(export) ]
#[ serde(rename_all = " camelCase " , untagged) ]
pub enum AngledLineData {
2023-08-25 13:41:04 -07:00
/// An angle and length with a tag.
2023-08-24 15:34:51 -07:00
AngleWithTag {
2023-08-25 13:41:04 -07:00
/// The angle of the line.
2023-08-24 15:34:51 -07:00
angle : f64 ,
2023-08-25 13:41:04 -07:00
/// The length of the line.
2023-08-24 15:34:51 -07:00
length : f64 ,
2023-08-25 13:41:04 -07:00
/// The tag.
2023-08-24 15:34:51 -07:00
tag : String ,
} ,
2023-08-25 13:41:04 -07:00
/// An angle and length.
2023-08-24 15:34:51 -07:00
AngleAndLength ( [ f64 ; 2 ] ) ,
}
2023-10-24 10:30:14 -07:00
impl AngledLineData {
pub fn into_inner_line ( self , to : [ f64 ; 2 ] ) -> LineData {
if let AngledLineData ::AngleWithTag { tag , .. } = self {
LineData ::PointWithTag { to , tag }
} else {
LineData ::Point ( to )
}
}
}
2023-08-24 15:34:51 -07:00
/// Draw an angled line.
2023-09-20 18:27:08 -07:00
pub async fn angled_line ( args : Args ) -> Result < MemoryItem , KclError > {
2023-09-19 14:20:14 -07:00
let ( data , sketch_group ) : ( AngledLineData , Box < SketchGroup > ) = args . get_data_and_sketch_group ( ) ? ;
2023-08-24 15:34:51 -07:00
2023-09-20 18:27:08 -07:00
let new_sketch_group = inner_angled_line ( data , sketch_group , args ) . await ? ;
2023-08-25 13:41:04 -07:00
Ok ( MemoryItem ::SketchGroup ( new_sketch_group ) )
}
/// Draw an angled line.
#[ stdlib {
name = " angledLine " ,
} ]
2023-09-20 18:27:08 -07:00
async fn inner_angled_line (
2023-08-25 13:41:04 -07:00
data : AngledLineData ,
2023-09-19 14:20:14 -07:00
sketch_group : Box < SketchGroup > ,
2023-09-20 18:27:08 -07:00
args : Args ,
2023-09-19 14:20:14 -07:00
) -> Result < Box < SketchGroup > , KclError > {
2023-08-24 15:34:51 -07:00
let from = sketch_group . get_coords_from_paths ( ) ? ;
let ( angle , length ) = match & data {
AngledLineData ::AngleWithTag { angle , length , .. } = > ( * angle , * length ) ,
2023-08-29 14:12:48 -07:00
AngledLineData ::AngleAndLength ( angle_and_length ) = > ( angle_and_length [ 0 ] , angle_and_length [ 1 ] ) ,
2023-08-24 15:34:51 -07:00
} ;
2023-09-20 17:36:26 -07:00
//double check me on this one - mike
let delta : [ f64 ; 2 ] = [
length * f64 ::cos ( angle . to_radians ( ) ) ,
length * f64 ::sin ( angle . to_radians ( ) ) ,
2023-08-24 15:34:51 -07:00
] ;
2023-09-20 17:36:26 -07:00
let relative = true ;
let to : [ f64 ; 2 ] = [ from . x + delta [ 0 ] , from . y + delta [ 1 ] ] ;
2023-08-24 15:34:51 -07:00
let id = uuid ::Uuid ::new_v4 ( ) ;
let current_path = Path ::ToPoint {
base : BasePath {
from : from . into ( ) ,
to ,
name : if let AngledLineData ::AngleWithTag { tag , .. } = data {
tag . to_string ( )
} else {
" " . to_string ( )
} ,
geo_meta : GeoMeta {
id ,
metadata : args . source_range . into ( ) ,
} ,
} ,
} ;
2023-09-11 17:14:41 -07:00
args . send_modeling_cmd (
id ,
ModelingCmd ::ExtendPath {
path : sketch_group . id ,
segment : kittycad ::types ::PathSegment ::Line {
end : Point3D {
2023-09-20 17:36:26 -07:00
x : delta [ 0 ] ,
y : delta [ 1 ] ,
2023-09-11 17:14:41 -07:00
z : 0.0 ,
} ,
2023-09-20 17:36:26 -07:00
relative ,
2023-09-11 17:14:41 -07:00
} ,
} ,
2023-09-20 18:27:08 -07:00
)
. await ? ;
2023-09-11 17:14:41 -07:00
2023-08-24 15:34:51 -07:00
let mut new_sketch_group = sketch_group . clone ( ) ;
new_sketch_group . value . push ( current_path ) ;
2023-08-25 13:41:04 -07:00
Ok ( new_sketch_group )
2023-08-24 15:34:51 -07:00
}
/// Draw an angled line of a given x length.
2023-09-20 18:27:08 -07:00
pub async fn angled_line_of_x_length ( args : Args ) -> Result < MemoryItem , KclError > {
2023-09-19 14:20:14 -07:00
let ( data , sketch_group ) : ( AngledLineData , Box < SketchGroup > ) = args . get_data_and_sketch_group ( ) ? ;
2023-08-24 15:34:51 -07:00
2023-09-20 18:27:08 -07:00
let new_sketch_group = inner_angled_line_of_x_length ( data , sketch_group , args ) . await ? ;
2023-08-25 13:41:04 -07:00
Ok ( MemoryItem ::SketchGroup ( new_sketch_group ) )
}
/// Draw an angled line of a given x length.
#[ stdlib {
name = " angledLineOfXLength " ,
} ]
2023-09-20 18:27:08 -07:00
async fn inner_angled_line_of_x_length (
2023-08-25 13:41:04 -07:00
data : AngledLineData ,
2023-09-19 14:20:14 -07:00
sketch_group : Box < SketchGroup > ,
2023-09-20 18:27:08 -07:00
args : Args ,
2023-09-19 14:20:14 -07:00
) -> Result < Box < SketchGroup > , KclError > {
2023-08-24 15:34:51 -07:00
let ( angle , length ) = match & data {
AngledLineData ::AngleWithTag { angle , length , .. } = > ( * angle , * length ) ,
2023-08-29 14:12:48 -07:00
AngledLineData ::AngleAndLength ( angle_and_length ) = > ( angle_and_length [ 0 ] , angle_and_length [ 1 ] ) ,
2023-08-24 15:34:51 -07:00
} ;
2023-09-14 15:51:26 -06:00
let to = get_y_component ( Angle ::from_degrees ( angle ) , length ) ;
2023-08-24 15:34:51 -07:00
2023-10-24 10:30:14 -07:00
let new_sketch_group = inner_line ( data . into_inner_line ( to . into ( ) ) , sketch_group , args ) . await ? ;
2023-08-24 15:34:51 -07:00
2023-08-25 13:41:04 -07:00
Ok ( new_sketch_group )
2023-08-24 15:34:51 -07:00
}
2023-08-25 13:41:04 -07:00
/// Data to draw an angled line to a point.
#[ derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema) ]
2023-08-24 15:34:51 -07:00
#[ ts(export) ]
#[ serde(rename_all = " camelCase " , untagged) ]
pub enum AngledLineToData {
2023-08-25 13:41:04 -07:00
/// An angle and point with a tag.
AngleWithTag {
/// The angle of the line.
angle : f64 ,
/// The point to draw to.
to : f64 ,
/// The tag.
tag : String ,
} ,
/// An angle and point to draw to.
2023-08-24 15:34:51 -07:00
AngleAndPoint ( [ f64 ; 2 ] ) ,
}
2023-10-24 10:30:14 -07:00
impl AngledLineToData {
pub fn into_inner_line ( self , x_to : f64 , y_to : f64 ) -> LineToData {
if let AngledLineToData ::AngleWithTag { tag , .. } = self {
LineToData ::PointWithTag { to : [ x_to , y_to ] , tag }
} else {
LineToData ::Point ( [ x_to , y_to ] )
}
}
}
2023-08-24 15:34:51 -07:00
/// Draw an angled line to a given x coordinate.
2023-09-20 18:27:08 -07:00
pub async fn angled_line_to_x ( args : Args ) -> Result < MemoryItem , KclError > {
2023-09-19 14:20:14 -07:00
let ( data , sketch_group ) : ( AngledLineToData , Box < SketchGroup > ) = args . get_data_and_sketch_group ( ) ? ;
2023-08-24 15:34:51 -07:00
2023-09-20 18:27:08 -07:00
let new_sketch_group = inner_angled_line_to_x ( data , sketch_group , args ) . await ? ;
2023-08-25 13:41:04 -07:00
Ok ( MemoryItem ::SketchGroup ( new_sketch_group ) )
}
/// Draw an angled line to a given x coordinate.
#[ stdlib {
name = " angledLineToX " ,
} ]
2023-09-20 18:27:08 -07:00
async fn inner_angled_line_to_x (
2023-08-25 13:41:04 -07:00
data : AngledLineToData ,
2023-09-19 14:20:14 -07:00
sketch_group : Box < SketchGroup > ,
2023-09-20 18:27:08 -07:00
args : Args ,
2023-09-19 14:20:14 -07:00
) -> Result < Box < SketchGroup > , KclError > {
2023-08-24 15:34:51 -07:00
let from = sketch_group . get_coords_from_paths ( ) ? ;
let ( angle , x_to ) = match & data {
AngledLineToData ::AngleWithTag { angle , to , .. } = > ( * angle , * to ) ,
AngledLineToData ::AngleAndPoint ( angle_and_to ) = > ( angle_and_to [ 0 ] , angle_and_to [ 1 ] ) ,
} ;
let x_component = x_to - from . x ;
2023-09-13 22:25:41 -06:00
let y_component = x_component * f64 ::tan ( angle . to_radians ( ) ) ;
2023-08-24 15:34:51 -07:00
let y_to = from . y + y_component ;
2023-10-24 10:30:14 -07:00
let new_sketch_group = inner_line_to ( data . into_inner_line ( x_to , y_to ) , sketch_group , args ) . await ? ;
2023-08-25 13:41:04 -07:00
Ok ( new_sketch_group )
2023-08-24 15:34:51 -07:00
}
/// Draw an angled line of a given y length.
2023-09-20 18:27:08 -07:00
pub async fn angled_line_of_y_length ( args : Args ) -> Result < MemoryItem , KclError > {
2023-09-19 14:20:14 -07:00
let ( data , sketch_group ) : ( AngledLineData , Box < SketchGroup > ) = args . get_data_and_sketch_group ( ) ? ;
2023-08-24 15:34:51 -07:00
2023-09-20 18:27:08 -07:00
let new_sketch_group = inner_angled_line_of_y_length ( data , sketch_group , args ) . await ? ;
2023-08-25 13:41:04 -07:00
Ok ( MemoryItem ::SketchGroup ( new_sketch_group ) )
}
/// Draw an angled line of a given y length.
#[ stdlib {
name = " angledLineOfYLength " ,
} ]
2023-09-20 18:27:08 -07:00
async fn inner_angled_line_of_y_length (
2023-08-25 13:41:04 -07:00
data : AngledLineData ,
2023-09-19 14:20:14 -07:00
sketch_group : Box < SketchGroup > ,
2023-09-20 18:27:08 -07:00
args : Args ,
2023-09-19 14:20:14 -07:00
) -> Result < Box < SketchGroup > , KclError > {
2023-08-24 15:34:51 -07:00
let ( angle , length ) = match & data {
AngledLineData ::AngleWithTag { angle , length , .. } = > ( * angle , * length ) ,
2023-08-29 14:12:48 -07:00
AngledLineData ::AngleAndLength ( angle_and_length ) = > ( angle_and_length [ 0 ] , angle_and_length [ 1 ] ) ,
2023-08-24 15:34:51 -07:00
} ;
2023-09-14 15:51:26 -06:00
let to = get_x_component ( Angle ::from_degrees ( angle ) , length ) ;
2023-08-24 15:34:51 -07:00
2023-10-24 10:30:14 -07:00
let new_sketch_group = inner_line ( data . into_inner_line ( to . into ( ) ) , sketch_group , args ) . await ? ;
2023-08-24 15:34:51 -07:00
2023-08-25 13:41:04 -07:00
Ok ( new_sketch_group )
2023-08-24 15:34:51 -07:00
}
/// Draw an angled line to a given y coordinate.
2023-09-20 18:27:08 -07:00
pub async fn angled_line_to_y ( args : Args ) -> Result < MemoryItem , KclError > {
2023-09-19 14:20:14 -07:00
let ( data , sketch_group ) : ( AngledLineToData , Box < SketchGroup > ) = args . get_data_and_sketch_group ( ) ? ;
2023-08-24 15:34:51 -07:00
2023-09-20 18:27:08 -07:00
let new_sketch_group = inner_angled_line_to_y ( data , sketch_group , args ) . await ? ;
2023-08-25 13:41:04 -07:00
Ok ( MemoryItem ::SketchGroup ( new_sketch_group ) )
}
/// Draw an angled line to a given y coordinate.
#[ stdlib {
name = " angledLineToY " ,
} ]
2023-09-20 18:27:08 -07:00
async fn inner_angled_line_to_y (
2023-08-25 13:41:04 -07:00
data : AngledLineToData ,
2023-09-19 14:20:14 -07:00
sketch_group : Box < SketchGroup > ,
2023-09-20 18:27:08 -07:00
args : Args ,
2023-09-19 14:20:14 -07:00
) -> Result < Box < SketchGroup > , KclError > {
2023-08-24 15:34:51 -07:00
let from = sketch_group . get_coords_from_paths ( ) ? ;
let ( angle , y_to ) = match & data {
AngledLineToData ::AngleWithTag { angle , to , .. } = > ( * angle , * to ) ,
AngledLineToData ::AngleAndPoint ( angle_and_to ) = > ( angle_and_to [ 0 ] , angle_and_to [ 1 ] ) ,
} ;
let y_component = y_to - from . y ;
2023-09-13 22:25:41 -06:00
let x_component = y_component / f64 ::tan ( angle . to_radians ( ) ) ;
2023-08-24 15:34:51 -07:00
let x_to = from . x + x_component ;
2023-10-24 10:30:14 -07:00
let new_sketch_group = inner_line_to ( data . into_inner_line ( x_to , y_to ) , sketch_group , args ) . await ? ;
2023-08-25 13:41:04 -07:00
Ok ( new_sketch_group )
2023-08-24 15:34:51 -07:00
}
2023-08-25 13:41:04 -07:00
/// Data for drawing an angled line that intersects with a given line.
#[ derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema) ]
2023-08-24 15:34:51 -07:00
#[ ts(export) ]
#[ serde(rename_all = " camelCase " ) ]
// TODO: make sure the docs on the args below are correct.
2023-10-24 10:30:14 -07:00
pub struct AngledLineThatIntersectsData {
2023-08-24 15:34:51 -07:00
/// The angle of the line.
pub angle : f64 ,
/// The tag of the line to intersect with.
pub intersect_tag : String ,
/// The offset from the intersecting line.
pub offset : Option < f64 > ,
/// The tag.
pub tag : Option < String > ,
}
/// Draw an angled line that intersects with a given line.
2023-09-20 18:27:08 -07:00
pub async fn angled_line_that_intersects ( args : Args ) -> Result < MemoryItem , KclError > {
2023-10-24 10:30:14 -07:00
let ( data , sketch_group ) : ( AngledLineThatIntersectsData , Box < SketchGroup > ) = args . get_data_and_sketch_group ( ) ? ;
2023-09-20 18:27:08 -07:00
let new_sketch_group = inner_angled_line_that_intersects ( data , sketch_group , args ) . await ? ;
2023-08-25 13:41:04 -07:00
Ok ( MemoryItem ::SketchGroup ( new_sketch_group ) )
}
/// Draw an angled line that intersects with a given line.
#[ stdlib {
name = " angledLineThatIntersects " ,
} ]
2023-09-20 18:27:08 -07:00
async fn inner_angled_line_that_intersects (
2023-10-24 10:30:14 -07:00
data : AngledLineThatIntersectsData ,
2023-09-19 14:20:14 -07:00
sketch_group : Box < SketchGroup > ,
2023-09-20 18:27:08 -07:00
args : Args ,
2023-09-19 14:20:14 -07:00
) -> Result < Box < SketchGroup > , KclError > {
2023-08-24 15:34:51 -07:00
let intersect_path = sketch_group
. get_path_by_name ( & data . intersect_tag )
. ok_or_else ( | | {
KclError ::Type ( KclErrorDetails {
message : format ! (
" Expected a line that exists in the given SketchGroup, found `{}` " ,
data . intersect_tag
) ,
source_ranges : vec ! [ args . source_range ] ,
} )
} ) ?
. get_base ( ) ;
let from = sketch_group . get_coords_from_paths ( ) ? ;
let to = intersection_with_parallel_line (
2023-09-14 15:51:26 -06:00
& [ intersect_path . from . into ( ) , intersect_path . to . into ( ) ] ,
2023-08-24 15:34:51 -07:00
data . offset . unwrap_or_default ( ) ,
data . angle ,
2023-09-14 15:51:26 -06:00
from ,
2023-08-24 15:34:51 -07:00
) ;
let line_to_data = if let Some ( tag ) = data . tag {
2023-09-14 15:51:26 -06:00
LineToData ::PointWithTag { to : to . into ( ) , tag }
2023-08-24 15:34:51 -07:00
} else {
2023-09-14 15:51:26 -06:00
LineToData ::Point ( to . into ( ) )
2023-08-24 15:34:51 -07:00
} ;
2023-09-20 18:27:08 -07:00
let new_sketch_group = inner_line_to ( line_to_data , sketch_group , args ) . await ? ;
2023-08-25 13:41:04 -07:00
Ok ( new_sketch_group )
2023-08-24 15:34:51 -07:00
}
/// Start a sketch at a given point.
2023-09-20 18:27:08 -07:00
pub async fn start_sketch_at ( args : Args ) -> Result < MemoryItem , KclError > {
2023-08-24 15:34:51 -07:00
let data : LineData = args . get_data ( ) ? ;
2023-09-20 18:27:08 -07:00
let sketch_group = inner_start_sketch_at ( data , args ) . await ? ;
2023-08-25 13:41:04 -07:00
Ok ( MemoryItem ::SketchGroup ( sketch_group ) )
}
2023-10-05 14:27:48 -07:00
/// Start a sketch at a given point on the 'XY' plane.
2023-08-25 13:41:04 -07:00
#[ stdlib {
name = " startSketchAt " ,
} ]
2023-09-20 18:27:08 -07:00
async fn inner_start_sketch_at ( data : LineData , args : Args ) -> Result < Box < SketchGroup > , KclError > {
2023-10-05 14:27:48 -07:00
// Let's assume it's the XY plane for now, this is just for backwards compatibility.
let xy_plane = PlaneData ::XY ;
let plane = inner_start_sketch_on ( xy_plane , args . clone ( ) ) . await ? ;
let sketch_group = inner_start_profile_at ( data , plane , args ) . await ? ;
Ok ( sketch_group )
}
/// Data for a plane.
Grackle (KCL to EP compiler) (#1270)
* Start Grackle (KCL-to-EP compiler)
This begins work on a second, different executor. The old executor is a tree-walk interpreter, this executor compiles the KCL programs into the Execution Plan virtual machine defined in its [own crate](https://github.com/KittyCAD/modeling-api/tree/main/execution-plan). This executor is called "Grackle", after an Austin bird, and it's got its own module in wasm-lib so that I can keep merging small PRs and developing incrementally, rather than building a complete executor which replaces the old executor in one PR.
Grackle's "Planner" walks the AST, like the tree-walk executor. But it doesn't actually execute code. Instead, as it walks each AST node, it outputs a sequence of Execution Plan instructions which, when run, can compute that node's value. It also notes which Execution Plan virtual machine address will eventually contain each KCL variable.
Done:
- Storing KCL variables
- Computing primitives, literals, binary expressions
- Calling native (i.e. Rust) functions from KCL
- Storing arrays
Todo:
- KCL functions (i.e. user-defined functions)
- Member expressions
- Port over existing executor's native funtions (e.g. `lineTo`, `extrude` and `startSketchAt`)
2024-01-11 09:25:10 -06:00
#[ derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, ExecutionPlanValue) ]
2023-10-05 14:27:48 -07:00
#[ ts(export) ]
#[ serde(rename_all = " camelCase " ) ]
pub enum PlaneData {
/// The XY plane.
#[ serde(rename = " XY " , alias = " xy " ) ]
XY ,
/// The opposite side of the XY plane.
#[ serde(rename = " -XY " , alias = " -xy " ) ]
NegXY ,
/// The XZ plane.
#[ serde(rename = " XZ " , alias = " xz " ) ]
XZ ,
/// The opposite side of the XZ plane.
#[ serde(rename = " -XZ " , alias = " -xz " ) ]
NegXZ ,
/// The YZ plane.
#[ serde(rename = " YZ " , alias = " yz " ) ]
YZ ,
/// The opposite side of the YZ plane.
#[ serde(rename = " -YZ " , alias = " -yz " ) ]
NegYZ ,
/// A defined plane.
Plane {
/// Origin of the plane.
origin : Box < Point3d > ,
/// What should the plane’ s X axis be?
x_axis : Box < Point3d > ,
/// What should the plane’ s Y axis be?
y_axis : Box < Point3d > ,
/// The z-axis (normal).
z_axis : Box < Point3d > ,
} ,
}
impl From < PlaneData > for Plane {
fn from ( value : PlaneData ) -> Self {
let id = uuid ::Uuid ::new_v4 ( ) ;
match value {
PlaneData ::XY = > Plane {
id ,
origin : Point3d ::new ( 0.0 , 0.0 , 0.0 ) ,
x_axis : Point3d ::new ( 1.0 , 0.0 , 0.0 ) ,
y_axis : Point3d ::new ( 0.0 , 1.0 , 0.0 ) ,
z_axis : Point3d ::new ( 0.0 , 0.0 , 1.0 ) ,
value : PlaneType ::XY ,
meta : vec ! [ ] ,
} ,
PlaneData ::NegXY = > Plane {
id ,
origin : Point3d ::new ( 0.0 , 0.0 , 0.0 ) ,
x_axis : Point3d ::new ( 1.0 , 0.0 , 0.0 ) ,
y_axis : Point3d ::new ( 0.0 , 1.0 , 0.0 ) ,
z_axis : Point3d ::new ( 0.0 , 0.0 , - 1.0 ) ,
value : PlaneType ::XY ,
meta : vec ! [ ] ,
} ,
PlaneData ::XZ = > Plane {
id ,
origin : Point3d ::new ( 0.0 , 0.0 , 0.0 ) ,
x_axis : Point3d ::new ( 1.0 , 0.0 , 0.0 ) ,
y_axis : Point3d ::new ( 0.0 , 0.0 , 1.0 ) ,
z_axis : Point3d ::new ( 0.0 , 1.0 , 0.0 ) ,
value : PlaneType ::XZ ,
meta : vec ! [ ] ,
} ,
PlaneData ::NegXZ = > Plane {
id ,
origin : Point3d ::new ( 0.0 , 0.0 , 0.0 ) ,
x_axis : Point3d ::new ( 1.0 , 0.0 , 0.0 ) ,
y_axis : Point3d ::new ( 0.0 , 0.0 , 1.0 ) ,
z_axis : Point3d ::new ( 0.0 , - 1.0 , 0.0 ) ,
value : PlaneType ::XZ ,
meta : vec ! [ ] ,
} ,
PlaneData ::YZ = > Plane {
id ,
origin : Point3d ::new ( 0.0 , 0.0 , 0.0 ) ,
x_axis : Point3d ::new ( 0.0 , 1.0 , 0.0 ) ,
y_axis : Point3d ::new ( 0.0 , 0.0 , 1.0 ) ,
z_axis : Point3d ::new ( 1.0 , 0.0 , 0.0 ) ,
value : PlaneType ::YZ ,
meta : vec ! [ ] ,
} ,
PlaneData ::NegYZ = > Plane {
id ,
origin : Point3d ::new ( 0.0 , 0.0 , 0.0 ) ,
x_axis : Point3d ::new ( 0.0 , 1.0 , 0.0 ) ,
y_axis : Point3d ::new ( 0.0 , 0.0 , 1.0 ) ,
z_axis : Point3d ::new ( - 1.0 , 0.0 , 0.0 ) ,
value : PlaneType ::YZ ,
meta : vec ! [ ] ,
} ,
PlaneData ::Plane {
origin ,
x_axis ,
y_axis ,
z_axis ,
} = > Plane {
id ,
origin : * origin ,
x_axis : * x_axis ,
y_axis : * y_axis ,
z_axis : * z_axis ,
value : PlaneType ::Custom ,
meta : vec ! [ ] ,
} ,
}
}
}
/// Start a sketch on a specific plane.
pub async fn start_sketch_on ( args : Args ) -> Result < MemoryItem , KclError > {
let data : PlaneData = args . get_data ( ) ? ;
let plane = inner_start_sketch_on ( data , args ) . await ? ;
Ok ( MemoryItem ::Plane ( plane ) )
}
/// Start a sketch at a given point.
#[ stdlib {
name = " startSketchOn " ,
} ]
async fn inner_start_sketch_on ( data : PlaneData , args : Args ) -> Result < Box < Plane > , KclError > {
let mut plane : Plane = data . clone ( ) . into ( ) ;
plane . id = match data {
PlaneData ::XY | PlaneData ::NegXY = > args . ctx . planes . xy ,
PlaneData ::XZ | PlaneData ::NegXZ = > args . ctx . planes . xz ,
PlaneData ::YZ | PlaneData ::NegYZ = > args . ctx . planes . yz ,
PlaneData ::Plane {
origin ,
x_axis ,
y_axis ,
z_axis : _ ,
} = > {
let id = uuid ::Uuid ::new_v4 ( ) ;
// Create the plane.
args . send_modeling_cmd (
id ,
ModelingCmd ::MakePlane {
clobber : false ,
origin : ( * origin ) . into ( ) ,
size : 60.0 ,
x_axis : ( * x_axis ) . into ( ) ,
y_axis : ( * y_axis ) . into ( ) ,
2023-10-05 19:54:31 -07:00
hide : Some ( true ) ,
2023-10-05 14:27:48 -07:00
} ,
)
. await ? ;
id
}
} ;
// Enter sketch mode on the plane.
args . send_modeling_cmd (
uuid ::Uuid ::new_v4 ( ) ,
ModelingCmd ::SketchModeEnable {
animated : false ,
ortho : false ,
plane_id : plane . id ,
// We pass in the normal for the plane here.
disable_camera_with_plane : Some ( plane . z_axis . clone ( ) . into ( ) ) ,
} ,
)
. await ? ;
Ok ( Box ::new ( plane ) )
}
/// Start a profile at a given point.
pub async fn start_profile_at ( args : Args ) -> Result < MemoryItem , KclError > {
let ( data , plane ) : ( LineData , Box < Plane > ) = args . get_data_and_plane ( ) ? ;
let sketch_group = inner_start_profile_at ( data , plane , args ) . await ? ;
Ok ( MemoryItem ::SketchGroup ( sketch_group ) )
}
/// Start a profile at a given point.
#[ stdlib {
name = " startProfileAt " ,
} ]
async fn inner_start_profile_at ( data : LineData , plane : Box < Plane > , args : Args ) -> Result < Box < SketchGroup > , KclError > {
2023-08-24 15:34:51 -07:00
let to = match & data {
2023-09-13 07:23:14 -07:00
LineData ::PointWithTag { to , .. } = > * to ,
2023-08-24 15:34:51 -07:00
LineData ::Point ( to ) = > * to ,
} ;
let id = uuid ::Uuid ::new_v4 ( ) ;
let path_id = uuid ::Uuid ::new_v4 ( ) ;
2023-09-20 18:27:08 -07:00
args . send_modeling_cmd ( path_id , ModelingCmd ::StartPath { } ) . await ? ;
2023-08-24 15:34:51 -07:00
args . send_modeling_cmd (
id ,
ModelingCmd ::MovePathPen {
path : path_id ,
to : Point3D {
x : to [ 0 ] ,
y : to [ 1 ] ,
z : 0.0 ,
} ,
} ,
2023-09-20 18:27:08 -07:00
)
. await ? ;
2023-08-24 15:34:51 -07:00
let current_path = BasePath {
from : to ,
to ,
name : if let LineData ::PointWithTag { tag , .. } = data {
tag . to_string ( )
} else {
" " . to_string ( )
} ,
geo_meta : GeoMeta {
id ,
metadata : args . source_range . into ( ) ,
} ,
} ;
let sketch_group = SketchGroup {
id : path_id ,
position : Position ( [ 0.0 , 0.0 , 0.0 ] ) ,
rotation : Rotation ( [ 0.0 , 0.0 , 0.0 , 1.0 ] ) ,
2023-10-05 14:27:48 -07:00
plane_id : Some ( plane . id ) ,
2023-08-24 15:34:51 -07:00
value : vec ! [ ] ,
start : current_path ,
meta : vec ! [ args . source_range . into ( ) ] ,
} ;
2023-09-19 14:20:14 -07:00
Ok ( Box ::new ( sketch_group ) )
2023-08-24 15:34:51 -07:00
}
/// Close the current sketch.
2023-09-20 18:27:08 -07:00
pub async fn close ( args : Args ) -> Result < MemoryItem , KclError > {
2023-08-24 15:34:51 -07:00
let sketch_group = args . get_sketch_group ( ) ? ;
2023-08-25 13:41:04 -07:00
2023-09-20 18:27:08 -07:00
let new_sketch_group = inner_close ( sketch_group , args ) . await ? ;
2023-08-25 13:41:04 -07:00
Ok ( MemoryItem ::SketchGroup ( new_sketch_group ) )
}
/// Close the current sketch.
#[ stdlib {
name = " close " ,
} ]
2023-09-20 18:27:08 -07:00
async fn inner_close ( sketch_group : Box < SketchGroup > , args : Args ) -> Result < Box < SketchGroup > , KclError > {
2023-08-24 15:34:51 -07:00
let from = sketch_group . get_coords_from_paths ( ) ? ;
let to : Point2d = sketch_group . start . from . into ( ) ;
let id = uuid ::Uuid ::new_v4 ( ) ;
args . send_modeling_cmd (
id ,
ModelingCmd ::ClosePath {
path_id : sketch_group . id ,
} ,
2023-09-20 18:27:08 -07:00
)
. await ? ;
2023-08-24 15:34:51 -07:00
2023-10-05 14:27:48 -07:00
// Exit sketch mode, since if we were in a plane we'd want to disable the sketch mode after.
if sketch_group . plane_id . is_some ( ) {
// We were on a plane, disable the sketch mode.
args . send_modeling_cmd ( uuid ::Uuid ::new_v4 ( ) , ModelingCmd ::SketchModeDisable { } )
. await ? ;
}
2023-08-24 15:34:51 -07:00
let mut new_sketch_group = sketch_group . clone ( ) ;
new_sketch_group . value . push ( Path ::ToPoint {
base : BasePath {
from : from . into ( ) ,
to : to . into ( ) ,
// TODO: should we use a different name?
name : " " . into ( ) ,
geo_meta : GeoMeta {
id ,
metadata : args . source_range . into ( ) ,
} ,
} ,
} ) ;
2023-08-25 13:41:04 -07:00
Ok ( new_sketch_group )
2023-08-24 15:34:51 -07:00
}
2023-08-31 22:19:23 -07:00
/// Data to draw an arc.
#[ derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema) ]
#[ ts(export) ]
#[ serde(rename_all = " camelCase " , untagged) ]
pub enum ArcData {
/// Angles and radius with a tag.
AnglesAndRadiusWithTag {
/// The start angle.
angle_start : f64 ,
/// The end angle.
angle_end : f64 ,
/// The radius.
radius : f64 ,
/// The tag.
tag : String ,
} ,
/// Angles and radius.
AnglesAndRadius {
/// The start angle.
angle_start : f64 ,
/// The end angle.
angle_end : f64 ,
/// The radius.
radius : f64 ,
} ,
/// Center, to and radius with a tag.
CenterToRadiusWithTag {
/// The center.
center : [ f64 ; 2 ] ,
/// The to point.
to : [ f64 ; 2 ] ,
/// The radius.
radius : f64 ,
/// The tag.
tag : String ,
} ,
/// Center, to and radius.
CenterToRadius {
/// The center.
center : [ f64 ; 2 ] ,
/// The to point.
to : [ f64 ; 2 ] ,
/// The radius.
radius : f64 ,
} ,
}
/// Draw an arc.
2023-09-20 18:27:08 -07:00
pub async fn arc ( args : Args ) -> Result < MemoryItem , KclError > {
2023-09-19 14:20:14 -07:00
let ( data , sketch_group ) : ( ArcData , Box < SketchGroup > ) = args . get_data_and_sketch_group ( ) ? ;
2023-08-31 22:19:23 -07:00
2023-09-20 18:27:08 -07:00
let new_sketch_group = inner_arc ( data , sketch_group , args ) . await ? ;
2023-08-31 22:19:23 -07:00
Ok ( MemoryItem ::SketchGroup ( new_sketch_group ) )
}
/// Draw an arc.
#[ stdlib {
name = " arc " ,
} ]
2023-09-20 18:27:08 -07:00
async fn inner_arc ( data : ArcData , sketch_group : Box < SketchGroup > , args : Args ) -> Result < Box < SketchGroup > , KclError > {
2023-09-14 15:51:26 -06:00
let from : Point2d = sketch_group . get_coords_from_paths ( ) ? ;
2023-08-31 22:19:23 -07:00
let ( center , angle_start , angle_end , radius , end ) = match & data {
ArcData ::AnglesAndRadiusWithTag {
angle_start ,
angle_end ,
radius ,
..
} = > {
2023-09-14 15:51:26 -06:00
let a_start = Angle ::from_degrees ( * angle_start ) ;
let a_end = Angle ::from_degrees ( * angle_end ) ;
let ( center , end ) = arc_center_and_end ( from , a_start , a_end , * radius ) ;
( center , a_start , a_end , * radius , end )
2023-08-31 22:19:23 -07:00
}
ArcData ::AnglesAndRadius {
angle_start ,
angle_end ,
radius ,
} = > {
2023-09-14 15:51:26 -06:00
let a_start = Angle ::from_degrees ( * angle_start ) ;
let a_end = Angle ::from_degrees ( * angle_end ) ;
let ( center , end ) = arc_center_and_end ( from , a_start , a_end , * radius ) ;
( center , a_start , a_end , * radius , end )
2023-08-31 22:19:23 -07:00
}
ArcData ::CenterToRadiusWithTag { center , to , radius , .. } = > {
2023-09-14 15:51:26 -06:00
let ( angle_start , angle_end ) = arc_angles ( from , center . into ( ) , to . into ( ) , * radius , args . source_range ) ? ;
2023-08-31 22:19:23 -07:00
( center . into ( ) , angle_start , angle_end , * radius , to . into ( ) )
}
ArcData ::CenterToRadius { center , to , radius } = > {
2023-09-14 15:51:26 -06:00
let ( angle_start , angle_end ) = arc_angles ( from , center . into ( ) , to . into ( ) , * radius , args . source_range ) ? ;
2023-08-31 22:19:23 -07:00
( center . into ( ) , angle_start , angle_end , * radius , to . into ( ) )
}
} ;
let id = uuid ::Uuid ::new_v4 ( ) ;
args . send_modeling_cmd (
id ,
ModelingCmd ::ExtendPath {
path : sketch_group . id ,
segment : kittycad ::types ::PathSegment ::Arc {
2024-01-02 13:13:41 -06:00
start : angle_start ,
end : angle_end ,
2023-08-31 22:19:23 -07:00
center : center . into ( ) ,
radius ,
2023-09-20 17:36:26 -07:00
relative : false ,
2023-08-31 22:19:23 -07:00
} ,
} ,
2023-09-20 18:27:08 -07:00
)
. await ? ;
2023-09-20 17:36:26 -07:00
2023-08-31 22:19:23 -07:00
let current_path = Path ::ToPoint {
base : BasePath {
from : from . into ( ) ,
to : end . into ( ) ,
name : match data {
ArcData ::AnglesAndRadiusWithTag { tag , .. } = > tag . to_string ( ) ,
ArcData ::AnglesAndRadius { .. } = > " " . to_string ( ) ,
ArcData ::CenterToRadiusWithTag { tag , .. } = > tag . to_string ( ) ,
ArcData ::CenterToRadius { .. } = > " " . to_string ( ) ,
} ,
geo_meta : GeoMeta {
id ,
metadata : args . source_range . into ( ) ,
} ,
} ,
} ;
let mut new_sketch_group = sketch_group . clone ( ) ;
new_sketch_group . value . push ( current_path ) ;
Ok ( new_sketch_group )
}
2023-10-12 11:50:54 -05:00
/// Data to draw a tangential arc.
2023-09-29 14:41:14 -07:00
#[ derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema, ts_rs::TS) ]
#[ ts(export) ]
#[ serde(rename_all = " camelCase " , untagged) ]
2023-10-12 11:50:54 -05:00
pub enum TangentialArcData {
2023-09-29 14:41:14 -07:00
RadiusAndOffset {
/// Radius of the arc.
/// Not to be confused with Raiders of the Lost Ark.
radius : f64 ,
/// Offset of the arc, in degrees.
offset : f64 ,
} ,
/// A point with a tag.
PointWithTag {
/// Where the arc should end. Must lie in the same plane as the current path pen position. Must not be colinear with current path pen position.
to : [ f64 ; 2 ] ,
/// The tag.
tag : String ,
} ,
/// A point where the arc should end. Must lie in the same plane as the current path pen position. Must not be colinear with current path pen position.
Point ( [ f64 ; 2 ] ) ,
}
2023-10-12 11:50:54 -05:00
/// Draw a tangential arc.
pub async fn tangential_arc ( args : Args ) -> Result < MemoryItem , KclError > {
let ( data , sketch_group ) : ( TangentialArcData , Box < SketchGroup > ) = args . get_data_and_sketch_group ( ) ? ;
2023-09-29 14:41:14 -07:00
2023-10-12 11:50:54 -05:00
let new_sketch_group = inner_tangential_arc ( data , sketch_group , args ) . await ? ;
2023-09-29 14:41:14 -07:00
Ok ( MemoryItem ::SketchGroup ( new_sketch_group ) )
}
/// Draw an arc.
#[ stdlib {
2023-10-12 11:50:54 -05:00
name = " tangentialArc " ,
2023-09-29 14:41:14 -07:00
} ]
2023-10-12 11:50:54 -05:00
async fn inner_tangential_arc (
data : TangentialArcData ,
2023-09-29 14:41:14 -07:00
sketch_group : Box < SketchGroup > ,
args : Args ,
) -> Result < Box < SketchGroup > , KclError > {
let from : Point2d = sketch_group . get_coords_from_paths ( ) ? ;
let id = uuid ::Uuid ::new_v4 ( ) ;
let to = match & data {
2023-10-12 11:50:54 -05:00
TangentialArcData ::RadiusAndOffset { radius , offset } = > {
2023-09-29 14:41:14 -07:00
// Calculate the end point from the angle and radius.
let end_angle = Angle ::from_degrees ( * offset ) ;
let start_angle = Angle ::from_degrees ( 0.0 ) ;
let ( _ , to ) = arc_center_and_end ( from , start_angle , end_angle , * radius ) ;
args . send_modeling_cmd (
id ,
ModelingCmd ::ExtendPath {
path : sketch_group . id ,
segment : kittycad ::types ::PathSegment ::TangentialArc {
radius : * radius ,
Remove just one enum (#1096)
# Problem
This is my proposal for fixing #1107 . I've only done it for one stdlib function, `tangentialArcTo` -- if y'all like it, I'll apply this idea to the rest of the stdlib.
Previously, if users want to put a tag on the arc, the function's parameters change type.
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is object
tangentialArcTo({to: [x, y], tag: "myTag"}, %)
```
# Solution
My proposal in #1006 is that KCL should have optional values. This means we can change the stdlib `tangentialArcTo` function to use them. In this PR, the calls are now like
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is array still, but we now pass a tag at the end.
tangentialArcTo([x, y], %, "myTag")
```
This adds an "option" type to KCL typesystem, but it's not really revealed to users (no KCL types are revealed to users right now, they write untyped code and only interact with types when they get type errors upon executing programs). Also adds a None type, which is the default case of the Optional enum.
2023-12-18 23:49:32 -06:00
offset : Angle {
2023-09-29 14:41:14 -07:00
unit : kittycad ::types ::UnitAngle ::Degrees ,
value : * offset ,
} ,
} ,
} ,
)
. await ? ;
to . into ( )
}
2023-10-12 11:50:54 -05:00
TangentialArcData ::PointWithTag { to , .. } = > {
2023-10-24 10:30:14 -07:00
args . send_modeling_cmd ( id , tan_arc_to ( & sketch_group , to ) ) . await ? ;
2023-09-29 14:41:14 -07:00
* to
}
2023-10-12 11:50:54 -05:00
TangentialArcData ::Point ( to ) = > {
2023-10-24 10:30:14 -07:00
args . send_modeling_cmd ( id , tan_arc_to ( & sketch_group , to ) ) . await ? ;
2023-09-29 14:41:14 -07:00
* to
}
} ;
let to = [ from . x + to [ 0 ] , from . y + to [ 1 ] ] ;
let current_path = Path ::ToPoint {
base : BasePath {
from : from . into ( ) ,
to ,
name : " " . to_string ( ) ,
geo_meta : GeoMeta {
id ,
metadata : args . source_range . into ( ) ,
} ,
} ,
} ;
let mut new_sketch_group = sketch_group . clone ( ) ;
new_sketch_group . value . push ( current_path ) ;
Ok ( new_sketch_group )
}
2023-10-24 10:30:14 -07:00
fn tan_arc_to ( sketch_group : & SketchGroup , to : & [ f64 ; 2 ] ) -> ModelingCmd {
ModelingCmd ::ExtendPath {
path : sketch_group . id ,
segment : kittycad ::types ::PathSegment ::TangentialArcTo {
angle_snap_increment : None ,
to : Point3D {
x : to [ 0 ] ,
y : to [ 1 ] ,
z : 0.0 ,
} ,
} ,
}
}
Remove just one enum (#1096)
# Problem
This is my proposal for fixing #1107 . I've only done it for one stdlib function, `tangentialArcTo` -- if y'all like it, I'll apply this idea to the rest of the stdlib.
Previously, if users want to put a tag on the arc, the function's parameters change type.
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is object
tangentialArcTo({to: [x, y], tag: "myTag"}, %)
```
# Solution
My proposal in #1006 is that KCL should have optional values. This means we can change the stdlib `tangentialArcTo` function to use them. In this PR, the calls are now like
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is array still, but we now pass a tag at the end.
tangentialArcTo([x, y], %, "myTag")
```
This adds an "option" type to KCL typesystem, but it's not really revealed to users (no KCL types are revealed to users right now, they write untyped code and only interact with types when they get type errors upon executing programs). Also adds a None type, which is the default case of the Optional enum.
2023-12-18 23:49:32 -06:00
fn too_few_args ( source_range : SourceRange ) -> KclError {
KclError ::Syntax ( KclErrorDetails {
source_ranges : vec ! [ source_range ] ,
message : " too few arguments " . to_owned ( ) ,
} )
}
fn get_arg < I : Iterator > ( it : & mut I , src : SourceRange ) -> Result < I ::Item , KclError > {
it . next ( ) . ok_or_else ( | | too_few_args ( src ) )
2023-09-29 14:41:14 -07:00
}
2023-10-12 11:50:54 -05:00
/// Draw a tangential arc to a specific point.
pub async fn tangential_arc_to ( args : Args ) -> Result < MemoryItem , KclError > {
Remove just one enum (#1096)
# Problem
This is my proposal for fixing #1107 . I've only done it for one stdlib function, `tangentialArcTo` -- if y'all like it, I'll apply this idea to the rest of the stdlib.
Previously, if users want to put a tag on the arc, the function's parameters change type.
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is object
tangentialArcTo({to: [x, y], tag: "myTag"}, %)
```
# Solution
My proposal in #1006 is that KCL should have optional values. This means we can change the stdlib `tangentialArcTo` function to use them. In this PR, the calls are now like
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is array still, but we now pass a tag at the end.
tangentialArcTo([x, y], %, "myTag")
```
This adds an "option" type to KCL typesystem, but it's not really revealed to users (no KCL types are revealed to users right now, they write untyped code and only interact with types when they get type errors upon executing programs). Also adds a None type, which is the default case of the Optional enum.
2023-12-18 23:49:32 -06:00
let src = args . source_range ;
// Get arguments to function call
let mut it = args . args . iter ( ) ;
let to : [ f64 ; 2 ] = get_arg ( & mut it , src ) ? . get_json ( ) ? ;
let sketch_group : Box < SketchGroup > = get_arg ( & mut it , src ) ? . get_json ( ) ? ;
let tag = if let Ok ( memory_item ) = get_arg ( & mut it , src ) {
memory_item . get_json_opt ( ) ?
} else {
None
} ;
2023-09-29 14:41:14 -07:00
Remove just one enum (#1096)
# Problem
This is my proposal for fixing #1107 . I've only done it for one stdlib function, `tangentialArcTo` -- if y'all like it, I'll apply this idea to the rest of the stdlib.
Previously, if users want to put a tag on the arc, the function's parameters change type.
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is object
tangentialArcTo({to: [x, y], tag: "myTag"}, %)
```
# Solution
My proposal in #1006 is that KCL should have optional values. This means we can change the stdlib `tangentialArcTo` function to use them. In this PR, the calls are now like
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is array still, but we now pass a tag at the end.
tangentialArcTo([x, y], %, "myTag")
```
This adds an "option" type to KCL typesystem, but it's not really revealed to users (no KCL types are revealed to users right now, they write untyped code and only interact with types when they get type errors upon executing programs). Also adds a None type, which is the default case of the Optional enum.
2023-12-18 23:49:32 -06:00
let new_sketch_group = inner_tangential_arc_to ( to , sketch_group , tag , args ) . await ? ;
2023-09-29 14:41:14 -07:00
Ok ( MemoryItem ::SketchGroup ( new_sketch_group ) )
}
/// Draw an arc.
#[ stdlib {
2023-10-12 11:50:54 -05:00
name = " tangentialArcTo " ,
2023-09-29 14:41:14 -07:00
} ]
2023-10-12 11:50:54 -05:00
async fn inner_tangential_arc_to (
Remove just one enum (#1096)
# Problem
This is my proposal for fixing #1107 . I've only done it for one stdlib function, `tangentialArcTo` -- if y'all like it, I'll apply this idea to the rest of the stdlib.
Previously, if users want to put a tag on the arc, the function's parameters change type.
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is object
tangentialArcTo({to: [x, y], tag: "myTag"}, %)
```
# Solution
My proposal in #1006 is that KCL should have optional values. This means we can change the stdlib `tangentialArcTo` function to use them. In this PR, the calls are now like
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is array still, but we now pass a tag at the end.
tangentialArcTo([x, y], %, "myTag")
```
This adds an "option" type to KCL typesystem, but it's not really revealed to users (no KCL types are revealed to users right now, they write untyped code and only interact with types when they get type errors upon executing programs). Also adds a None type, which is the default case of the Optional enum.
2023-12-18 23:49:32 -06:00
to : [ f64 ; 2 ] ,
2023-09-29 14:41:14 -07:00
sketch_group : Box < SketchGroup > ,
Remove just one enum (#1096)
# Problem
This is my proposal for fixing #1107 . I've only done it for one stdlib function, `tangentialArcTo` -- if y'all like it, I'll apply this idea to the rest of the stdlib.
Previously, if users want to put a tag on the arc, the function's parameters change type.
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is object
tangentialArcTo({to: [x, y], tag: "myTag"}, %)
```
# Solution
My proposal in #1006 is that KCL should have optional values. This means we can change the stdlib `tangentialArcTo` function to use them. In this PR, the calls are now like
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is array still, but we now pass a tag at the end.
tangentialArcTo([x, y], %, "myTag")
```
This adds an "option" type to KCL typesystem, but it's not really revealed to users (no KCL types are revealed to users right now, they write untyped code and only interact with types when they get type errors upon executing programs). Also adds a None type, which is the default case of the Optional enum.
2023-12-18 23:49:32 -06:00
tag : Option < String > ,
2023-09-29 14:41:14 -07:00
args : Args ,
) -> Result < Box < SketchGroup > , KclError > {
let from : Point2d = sketch_group . get_coords_from_paths ( ) ? ;
Remove just one enum (#1096)
# Problem
This is my proposal for fixing #1107 . I've only done it for one stdlib function, `tangentialArcTo` -- if y'all like it, I'll apply this idea to the rest of the stdlib.
Previously, if users want to put a tag on the arc, the function's parameters change type.
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is object
tangentialArcTo({to: [x, y], tag: "myTag"}, %)
```
# Solution
My proposal in #1006 is that KCL should have optional values. This means we can change the stdlib `tangentialArcTo` function to use them. In this PR, the calls are now like
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is array still, but we now pass a tag at the end.
tangentialArcTo([x, y], %, "myTag")
```
This adds an "option" type to KCL typesystem, but it's not really revealed to users (no KCL types are revealed to users right now, they write untyped code and only interact with types when they get type errors upon executing programs). Also adds a None type, which is the default case of the Optional enum.
2023-12-18 23:49:32 -06:00
let [ to_x , to_y ] = to ;
2023-09-29 14:41:14 -07:00
Remove just one enum (#1096)
# Problem
This is my proposal for fixing #1107 . I've only done it for one stdlib function, `tangentialArcTo` -- if y'all like it, I'll apply this idea to the rest of the stdlib.
Previously, if users want to put a tag on the arc, the function's parameters change type.
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is object
tangentialArcTo({to: [x, y], tag: "myTag"}, %)
```
# Solution
My proposal in #1006 is that KCL should have optional values. This means we can change the stdlib `tangentialArcTo` function to use them. In this PR, the calls are now like
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is array still, but we now pass a tag at the end.
tangentialArcTo([x, y], %, "myTag")
```
This adds an "option" type to KCL typesystem, but it's not really revealed to users (no KCL types are revealed to users right now, they write untyped code and only interact with types when they get type errors upon executing programs). Also adds a None type, which is the default case of the Optional enum.
2023-12-18 23:49:32 -06:00
let delta = [ to_x - from . x , to_y - from . y ] ;
2023-09-29 14:41:14 -07:00
let id = uuid ::Uuid ::new_v4 ( ) ;
2023-10-24 10:30:14 -07:00
args . send_modeling_cmd ( id , tan_arc_to ( & sketch_group , & delta ) ) . await ? ;
2023-09-29 14:41:14 -07:00
let current_path = Path ::ToPoint {
base : BasePath {
from : from . into ( ) ,
Remove just one enum (#1096)
# Problem
This is my proposal for fixing #1107 . I've only done it for one stdlib function, `tangentialArcTo` -- if y'all like it, I'll apply this idea to the rest of the stdlib.
Previously, if users want to put a tag on the arc, the function's parameters change type.
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is object
tangentialArcTo({to: [x, y], tag: "myTag"}, %)
```
# Solution
My proposal in #1006 is that KCL should have optional values. This means we can change the stdlib `tangentialArcTo` function to use them. In this PR, the calls are now like
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is array still, but we now pass a tag at the end.
tangentialArcTo([x, y], %, "myTag")
```
This adds an "option" type to KCL typesystem, but it's not really revealed to users (no KCL types are revealed to users right now, they write untyped code and only interact with types when they get type errors upon executing programs). Also adds a None type, which is the default case of the Optional enum.
2023-12-18 23:49:32 -06:00
to ,
name : tag . unwrap_or_default ( ) ,
2023-09-29 14:41:14 -07:00
geo_meta : GeoMeta {
id ,
metadata : args . source_range . into ( ) ,
} ,
} ,
} ;
let mut new_sketch_group = sketch_group . clone ( ) ;
new_sketch_group . value . push ( current_path ) ;
Ok ( new_sketch_group )
}
2023-08-31 22:19:23 -07:00
/// Data to draw a bezier curve.
#[ derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema) ]
#[ ts(export) ]
#[ serde(rename_all = " camelCase " , untagged) ]
pub enum BezierData {
/// Points with a tag.
PointsWithTag {
/// The to point.
to : [ f64 ; 2 ] ,
/// The first control point.
control1 : [ f64 ; 2 ] ,
/// The second control point.
control2 : [ f64 ; 2 ] ,
/// The tag.
tag : String ,
} ,
/// Points.
Points {
/// The to point.
to : [ f64 ; 2 ] ,
/// The first control point.
control1 : [ f64 ; 2 ] ,
/// The second control point.
control2 : [ f64 ; 2 ] ,
} ,
}
/// Draw a bezier curve.
2023-09-20 18:27:08 -07:00
pub async fn bezier_curve ( args : Args ) -> Result < MemoryItem , KclError > {
2023-09-19 14:20:14 -07:00
let ( data , sketch_group ) : ( BezierData , Box < SketchGroup > ) = args . get_data_and_sketch_group ( ) ? ;
2023-08-31 22:19:23 -07:00
2023-09-20 18:27:08 -07:00
let new_sketch_group = inner_bezier_curve ( data , sketch_group , args ) . await ? ;
2023-08-31 22:19:23 -07:00
Ok ( MemoryItem ::SketchGroup ( new_sketch_group ) )
}
/// Draw a bezier curve.
#[ stdlib {
name = " bezierCurve " ,
} ]
2023-09-20 18:27:08 -07:00
async fn inner_bezier_curve (
2023-09-19 14:20:14 -07:00
data : BezierData ,
sketch_group : Box < SketchGroup > ,
2023-09-20 18:27:08 -07:00
args : Args ,
2023-09-19 14:20:14 -07:00
) -> Result < Box < SketchGroup > , KclError > {
2023-08-31 22:19:23 -07:00
let from = sketch_group . get_coords_from_paths ( ) ? ;
let ( to , control1 , control2 ) = match & data {
BezierData ::PointsWithTag {
to , control1 , control2 , ..
} = > ( to , control1 , control2 ) ,
BezierData ::Points { to , control1 , control2 } = > ( to , control1 , control2 ) ,
} ;
2023-09-20 17:36:26 -07:00
let relative = true ;
let delta = to ;
2023-08-31 22:19:23 -07:00
let to = [ from . x + to [ 0 ] , from . y + to [ 1 ] ] ;
let id = uuid ::Uuid ::new_v4 ( ) ;
args . send_modeling_cmd (
id ,
ModelingCmd ::ExtendPath {
path : sketch_group . id ,
segment : kittycad ::types ::PathSegment ::Bezier {
control1 : Point3D {
2023-09-20 17:36:26 -07:00
x : control1 [ 0 ] ,
y : control1 [ 1 ] ,
2023-08-31 22:19:23 -07:00
z : 0.0 ,
} ,
control2 : Point3D {
2023-09-20 17:36:26 -07:00
x : control2 [ 0 ] ,
y : control2 [ 1 ] ,
2023-08-31 22:19:23 -07:00
z : 0.0 ,
} ,
end : Point3D {
2023-09-20 17:36:26 -07:00
x : delta [ 0 ] ,
y : delta [ 1 ] ,
2023-08-31 22:19:23 -07:00
z : 0.0 ,
} ,
2023-09-20 17:36:26 -07:00
relative ,
2023-08-31 22:19:23 -07:00
} ,
} ,
2023-09-20 18:27:08 -07:00
)
. await ? ;
2023-08-31 22:19:23 -07:00
let current_path = Path ::ToPoint {
base : BasePath {
from : from . into ( ) ,
to ,
name : if let BezierData ::PointsWithTag { tag , .. } = data {
tag . to_string ( )
} else {
" " . to_string ( )
} ,
geo_meta : GeoMeta {
id ,
metadata : args . source_range . into ( ) ,
} ,
} ,
} ;
let mut new_sketch_group = sketch_group . clone ( ) ;
new_sketch_group . value . push ( current_path ) ;
Ok ( new_sketch_group )
}
2023-10-13 12:02:46 -07:00
/// Use a sketch to cut a hole in another sketch.
pub async fn hole ( args : Args ) -> Result < MemoryItem , KclError > {
let ( hole_sketch_group , sketch_group ) : ( Box < SketchGroup > , Box < SketchGroup > ) = args . get_sketch_groups ( ) ? ;
let new_sketch_group = inner_hole ( hole_sketch_group , sketch_group , args ) . await ? ;
Ok ( MemoryItem ::SketchGroup ( new_sketch_group ) )
}
/// Use a sketch to cut a hole in another sketch.
#[ stdlib {
name = " hole " ,
} ]
async fn inner_hole (
hole_sketch_group : Box < SketchGroup > ,
sketch_group : Box < SketchGroup > ,
args : Args ,
) -> Result < Box < SketchGroup > , KclError > {
//TODO: batch these (once we have batch)
args . send_modeling_cmd (
uuid ::Uuid ::new_v4 ( ) ,
ModelingCmd ::Solid2DAddHole {
object_id : sketch_group . id ,
hole_id : hole_sketch_group . id ,
} ,
)
. await ? ;
//suggestion (mike)
//we also hide the source hole since its essentially "consumed" by this operation
args . send_modeling_cmd (
uuid ::Uuid ::new_v4 ( ) ,
ModelingCmd ::ObjectVisible {
object_id : hole_sketch_group . id ,
hidden : true ,
} ,
)
. await ? ;
// TODO: should we modify the sketch group to include the hole data, probably?
Ok ( sketch_group )
}
2023-08-24 15:34:51 -07:00
#[ cfg(test) ]
mod tests {
use pretty_assertions ::assert_eq ;
2023-10-05 14:27:48 -07:00
use crate ::std ::sketch ::{ LineData , PlaneData } ;
2023-08-29 14:12:48 -07:00
2023-08-24 15:34:51 -07:00
#[ test ]
fn test_deserialize_line_data ( ) {
let data = LineData ::Point ( [ 0.0 , 1.0 ] ) ;
2023-09-13 07:23:14 -07:00
let mut str_json = serde_json ::to_string ( & data ) . unwrap ( ) ;
2023-08-24 15:34:51 -07:00
assert_eq! ( str_json , " [0.0,1.0] " ) ;
str_json = " [0, 1] " . to_string ( ) ;
let data : LineData = serde_json ::from_str ( & str_json ) . unwrap ( ) ;
assert_eq! ( data , LineData ::Point ( [ 0.0 , 1.0 ] ) ) ;
str_json = " { \" to \" : [0.0, 1.0], \" tag \" : \" thing \" } " . to_string ( ) ;
let data : LineData = serde_json ::from_str ( & str_json ) . unwrap ( ) ;
assert_eq! (
data ,
LineData ::PointWithTag {
2023-09-13 07:23:14 -07:00
to : [ 0.0 , 1.0 ] ,
2023-08-24 15:34:51 -07:00
tag : " thing " . to_string ( )
}
) ;
}
2023-10-05 14:27:48 -07:00
#[ test ]
fn test_deserialize_plane_data ( ) {
let data = PlaneData ::XY ;
let mut str_json = serde_json ::to_string ( & data ) . unwrap ( ) ;
assert_eq! ( str_json , " \" XY \" " ) ;
str_json = " \" YZ \" " . to_string ( ) ;
let data : PlaneData = serde_json ::from_str ( & str_json ) . unwrap ( ) ;
assert_eq! ( data , PlaneData ::YZ ) ;
str_json = " \" -YZ \" " . to_string ( ) ;
let data : PlaneData = serde_json ::from_str ( & str_json ) . unwrap ( ) ;
assert_eq! ( data , PlaneData ::NegYZ ) ;
str_json = " \" -xz \" " . to_string ( ) ;
let data : PlaneData = serde_json ::from_str ( & str_json ) . unwrap ( ) ;
assert_eq! ( data , PlaneData ::NegXZ ) ;
}
2023-08-24 15:34:51 -07:00
}