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 } ;
2024-02-13 10:26:09 -08:00
use parse_display ::{ Display , FromStr } ;
2023-08-25 13:41:04 -07:00
use schemars ::JsonSchema ;
2023-08-24 15:34:51 -07:00
use serde ::{ Deserialize , Serialize } ;
use crate ::{
errors ::{ KclError , KclErrorDetails } ,
2023-10-05 14:27:48 -07:00
executor ::{
2024-02-12 18:08:42 -08:00
BasePath , ExtrudeGroup , ExtrudeSurface , Face , GeoMeta , MemoryItem , Path , Plane , PlaneType , Point2d , Point3d ,
2024-06-21 19:54:18 -07:00
SketchGroup , SketchGroupSet , SketchSurface , SourceRange , UserVal ,
2023-10-05 14:27:48 -07:00
} ,
2023-08-24 15:34:51 -07:00
std ::{
2024-02-11 15:08:54 -08:00
utils ::{
arc_angles , arc_center_and_end , get_tangent_point_from_previous_arc , get_tangential_arc_to_info ,
get_x_component , get_y_component , intersection_with_parallel_line , TangentialArcInfoInput ,
} ,
2023-08-24 15:34:51 -07:00
Args ,
} ,
} ;
/// Draw a line to a point.
2023-09-20 18:27:08 -07:00
pub async fn line_to ( args : Args ) -> Result < MemoryItem , KclError > {
2024-03-15 17:03:42 -04:00
let ( to , sketch_group , tag ) : ( [ f64 ; 2 ] , Box < SketchGroup > , Option < String > ) =
args . get_data_and_sketch_group_and_tag ( ) ? ;
2023-08-24 15:34:51 -07:00
2024-03-15 17:03:42 -04:00
let new_sketch_group = inner_line_to ( to , sketch_group , tag , args ) . await ? ;
2023-08-24 15:34:51 -07:00
Ok ( MemoryItem ::SketchGroup ( new_sketch_group ) )
}
/// Draw a line to a point.
2024-03-13 12:56:46 -07:00
///
/// ```no_run
2024-05-22 09:15:38 -07:00
/// const exampleSketch = startSketchOn("XZ")
2024-05-14 17:10:47 -07:00
/// |> startProfileAt([0, 0], %)
/// |> lineTo([10, 0], %)
/// |> lineTo([0, 10], %)
/// |> lineTo([-10, 0], %)
/// |> close(%)
2024-03-13 12:56:46 -07:00
///
2024-05-14 17:10:47 -07:00
/// const example = extrude(5, exampleSketch)
2024-03-13 12:56:46 -07:00
/// ```
2023-08-25 13:41:04 -07:00
#[ stdlib {
name = " lineTo " ,
} ]
2023-09-20 18:27:08 -07:00
async fn inner_line_to (
2024-03-15 17:03:42 -04:00
to : [ f64 ; 2 ] ,
2023-09-19 14:20:14 -07:00
sketch_group : Box < SketchGroup > ,
2024-03-15 17:03:42 -04:00
tag : Option < String > ,
2023-09-20 18:27:08 -07:00
args : Args ,
2023-09-19 14:20:14 -07:00
) -> Result < Box < SketchGroup > , KclError > {
2024-05-30 17:48:59 -05:00
let from = sketch_group . current_pen_position ( ) ? ;
2023-08-24 15:34:51 -07:00
let id = uuid ::Uuid ::new_v4 ( ) ;
2023-08-31 22:19:23 -07:00
2024-06-19 13:57:50 -07:00
args . batch_modeling_cmd (
2023-08-31 22:19:23 -07:00
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 ,
2024-03-15 17:03:42 -04:00
name : tag . unwrap_or ( " " . to_string ( ) ) ,
2023-08-24 15:34:51 -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 )
}
/// 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 > {
2024-03-15 17:03:42 -04:00
let ( to , sketch_group , tag ) : ( f64 , Box < SketchGroup > , Option < String > ) = args . get_data_and_sketch_group_and_tag ( ) ? ;
2023-08-25 13:41:04 -07:00
2024-03-15 17:03:42 -04:00
let new_sketch_group = inner_x_line_to ( to , sketch_group , tag , 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.
2024-03-13 12:56:46 -07:00
///
/// ```no_run
2024-05-22 09:15:38 -07:00
/// const exampleSketch = startSketchOn('XZ')
2024-05-14 17:10:47 -07:00
/// |> startProfileAt([0, 0], %)
/// |> xLineTo(15, %)
/// |> angledLine({
/// angle: 80,
/// length: 15,
/// }, %)
/// |> line([8, -10], %)
/// |> xLineTo(40, %)
/// |> angledLine({
/// angle: 135,
/// length: 30,
/// }, %)
/// |> xLineTo(10, %)
/// |> close(%)
///
/// const example = extrude(10, exampleSketch)
2024-03-13 12:56:46 -07:00
/// ```
2023-08-25 13:41:04 -07:00
#[ stdlib {
name = " xLineTo " ,
} ]
2023-09-20 18:27:08 -07:00
async fn inner_x_line_to (
2024-03-15 17:03:42 -04:00
to : f64 ,
2023-09-19 14:20:14 -07:00
sketch_group : Box < SketchGroup > ,
2024-03-15 17:03:42 -04:00
tag : Option < String > ,
2023-09-20 18:27:08 -07:00
args : Args ,
2023-09-19 14:20:14 -07:00
) -> Result < Box < SketchGroup > , KclError > {
2024-05-30 17:48:59 -05:00
let from = sketch_group . current_pen_position ( ) ? ;
2023-08-24 15:34:51 -07:00
2024-03-15 17:03:42 -04:00
let new_sketch_group = inner_line_to ( [ to , from . y ] , sketch_group , tag , 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 > {
2024-03-15 17:03:42 -04:00
let ( to , sketch_group , tag ) : ( f64 , Box < SketchGroup > , Option < String > ) = args . get_data_and_sketch_group_and_tag ( ) ? ;
2023-08-25 13:41:04 -07:00
2024-03-15 17:03:42 -04:00
let new_sketch_group = inner_y_line_to ( to , sketch_group , tag , 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.
2024-03-13 12:56:46 -07:00
///
/// ```no_run
2024-05-22 09:15:38 -07:00
/// const exampleSketch = startSketchOn("XZ")
2024-03-13 12:56:46 -07:00
/// |> startProfileAt([0, 0], %)
2024-05-22 09:15:38 -07:00
/// |> angledLine({
/// angle: 50,
/// length: 45,
/// }, %)
/// |> yLineTo(0, %)
/// |> close(%)
///
/// const example = extrude(5, exampleSketch)
2024-03-13 12:56:46 -07:00
/// ```
2023-08-25 13:41:04 -07:00
#[ stdlib {
name = " yLineTo " ,
} ]
2023-09-20 18:27:08 -07:00
async fn inner_y_line_to (
2024-03-15 17:03:42 -04:00
to : f64 ,
2023-09-19 14:20:14 -07:00
sketch_group : Box < SketchGroup > ,
2024-03-15 17:03:42 -04:00
tag : Option < String > ,
2023-09-20 18:27:08 -07:00
args : Args ,
2023-09-19 14:20:14 -07:00
) -> Result < Box < SketchGroup > , KclError > {
2024-05-30 17:48:59 -05:00
let from = sketch_group . current_pen_position ( ) ? ;
2023-08-24 15:34:51 -07:00
2024-03-15 17:03:42 -04:00
let new_sketch_group = inner_line_to ( [ from . x , to ] , sketch_group , tag , 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.
2023-09-20 18:27:08 -07:00
pub async fn line ( args : Args ) -> Result < MemoryItem , KclError > {
2024-03-15 17:03:42 -04:00
let ( delta , sketch_group , tag ) : ( [ f64 ; 2 ] , Box < SketchGroup > , Option < String > ) =
args . get_data_and_sketch_group_and_tag ( ) ? ;
2023-08-24 15:34:51 -07:00
2024-03-15 17:03:42 -04:00
let new_sketch_group = inner_line ( delta , sketch_group , tag , 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.
2024-03-13 12:56:46 -07:00
///
/// ```no_run
2024-05-22 09:15:38 -07:00
/// const exampleSketch = startSketchOn("XZ")
/// |> startProfileAt([0, 0], %)
/// |> line([25, 15], %)
/// |> line([5, -6], %)
/// |> line([-10, -10], %)
/// |> close(%)
///
/// const example = extrude(5, exampleSketch)
/// ```
///
/// ```no_run
/// const exampleSketch = startSketchOn("XZ")
2024-05-14 17:10:47 -07:00
/// |> startProfileAt([0, 0], %)
/// |> line([10, 0], %)
/// |> line([0, 10], %)
/// |> line([-10, 0], %)
/// |> close(%)
///
/// const example = extrude(5, exampleSketch)
2024-03-13 12:56:46 -07:00
/// ```
2023-08-25 13:41:04 -07:00
#[ stdlib {
name = " line " ,
} ]
2024-03-15 17:03:42 -04:00
async fn inner_line (
delta : [ f64 ; 2 ] ,
sketch_group : Box < SketchGroup > ,
tag : Option < String > ,
args : Args ,
) -> Result < Box < SketchGroup > , KclError > {
2024-05-30 17:48:59 -05:00
let from = sketch_group . current_pen_position ( ) ? ;
2024-03-15 17:03:42 -04:00
let to = [ from . x + delta [ 0 ] , from . y + delta [ 1 ] ] ;
2023-08-24 15:34:51 -07:00
let id = uuid ::Uuid ::new_v4 ( ) ;
2024-06-19 13:57:50 -07:00
args . batch_modeling_cmd (
2023-08-24 15:34:51 -07:00
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 ,
2024-03-15 17:03:42 -04:00
name : tag . unwrap_or ( " " . to_string ( ) ) ,
2023-08-24 15:34:51 -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 )
}
/// 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 > {
2024-03-15 17:03:42 -04:00
let ( length , sketch_group , tag ) : ( f64 , Box < SketchGroup > , Option < String > ) =
args . get_data_and_sketch_group_and_tag ( ) ? ;
2023-08-24 15:34:51 -07:00
2024-03-15 17:03:42 -04:00
let new_sketch_group = inner_x_line ( length , sketch_group , tag , args ) . await ? ;
2023-08-25 13:41:04 -07:00
Ok ( MemoryItem ::SketchGroup ( new_sketch_group ) )
}
/// Draw a line on the x-axis.
2024-03-13 12:56:46 -07:00
///
/// ```no_run
2024-05-22 09:15:38 -07:00
/// const exampleSketch = startSketchOn('XZ')
2024-05-14 17:10:47 -07:00
/// |> startProfileAt([0, 0], %)
/// |> xLine(15, %)
/// |> angledLine({
/// angle: 80,
/// length: 15,
/// }, %)
/// |> line([8, -10], %)
/// |> xLine(10, %)
/// |> angledLine({
/// angle: 120,
/// length: 30,
/// }, %)
/// |> xLine(-15, %)
/// |> close(%)
///
/// const example = extrude(10, exampleSketch)
2024-03-13 12:56:46 -07:00
/// ```
2023-08-25 13:41:04 -07:00
#[ stdlib {
name = " xLine " ,
} ]
2023-09-20 18:27:08 -07:00
async fn inner_x_line (
2024-03-15 17:03:42 -04:00
length : f64 ,
2023-09-19 14:20:14 -07:00
sketch_group : Box < SketchGroup > ,
2024-03-15 17:03:42 -04:00
tag : Option < String > ,
2023-09-20 18:27:08 -07:00
args : Args ,
2023-09-19 14:20:14 -07:00
) -> Result < Box < SketchGroup > , KclError > {
2024-03-15 17:03:42 -04:00
inner_line ( [ length , 0.0 ] , sketch_group , tag , args ) . await
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 > {
2024-03-15 17:03:42 -04:00
let ( length , sketch_group , tag ) : ( f64 , Box < SketchGroup > , Option < String > ) =
args . get_data_and_sketch_group_and_tag ( ) ? ;
2023-08-24 15:34:51 -07:00
2024-03-15 17:03:42 -04:00
let new_sketch_group = inner_y_line ( length , sketch_group , tag , args ) . await ? ;
2023-08-25 13:41:04 -07:00
Ok ( MemoryItem ::SketchGroup ( new_sketch_group ) )
}
/// Draw a line on the y-axis.
2024-03-13 12:56:46 -07:00
///
/// ```no_run
2024-05-22 09:15:38 -07:00
/// const exampleSketch = startSketchOn('XZ')
2024-05-14 17:10:47 -07:00
/// |> startProfileAt([0, 0], %)
/// |> yLine(15, %)
/// |> angledLine({
/// angle: 30,
/// length: 15,
/// }, %)
/// |> line([8, -10], %)
/// |> yLine(-5, %)
/// |> close(%)
///
/// const example = extrude(10, exampleSketch)
2024-03-13 12:56:46 -07:00
/// ```
2023-08-25 13:41:04 -07:00
#[ stdlib {
name = " yLine " ,
} ]
2023-09-20 18:27:08 -07:00
async fn inner_y_line (
2024-03-15 17:03:42 -04:00
length : f64 ,
2023-09-19 14:20:14 -07:00
sketch_group : Box < SketchGroup > ,
2024-03-15 17:03:42 -04:00
tag : Option < String > ,
2023-09-20 18:27:08 -07:00
args : Args ,
2023-09-19 14:20:14 -07:00
) -> Result < Box < SketchGroup > , KclError > {
2024-03-15 17:03:42 -04:00
inner_line ( [ 0.0 , length ] , sketch_group , tag , args ) . await
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 {
2024-03-15 17:03:42 -04:00
/// An angle and length with explicitly named parameters
AngleAndLengthNamed {
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 ,
} ,
2024-03-15 17:03:42 -04:00
/// An angle and length given as a pair
AngleAndLengthPair ( [ f64 ; 2 ] ) ,
2023-10-24 10:30:14 -07:00
}
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 > {
2024-03-15 17:03:42 -04:00
let ( data , sketch_group , tag ) : ( AngledLineData , Box < SketchGroup > , Option < String > ) =
args . get_data_and_sketch_group_and_tag ( ) ? ;
2023-08-24 15:34:51 -07:00
2024-03-15 17:03:42 -04:00
let new_sketch_group = inner_angled_line ( data , sketch_group , tag , args ) . await ? ;
2023-08-25 13:41:04 -07:00
Ok ( MemoryItem ::SketchGroup ( new_sketch_group ) )
}
/// Draw an angled line.
2024-03-13 12:56:46 -07:00
///
/// ```no_run
2024-05-22 09:15:38 -07:00
/// const exampleSketch = startSketchOn('XZ')
2024-03-15 17:03:42 -04:00
/// |> startProfileAt([0, 0], %)
2024-05-14 17:10:47 -07:00
/// |> yLineTo(15, %)
2024-03-15 17:03:42 -04:00
/// |> angledLine({
2024-05-14 17:10:47 -07:00
/// angle: 30,
/// length: 15,
/// }, %)
/// |> line([8, -10], %)
/// |> yLineTo(0, %)
/// |> close(%)
///
/// const example = extrude(10, exampleSketch)
2024-03-13 12:56:46 -07:00
/// ```
2023-08-25 13:41:04 -07:00
#[ 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 > ,
2024-03-15 17:03:42 -04:00
tag : Option < String > ,
2023-09-20 18:27:08 -07:00
args : Args ,
2023-09-19 14:20:14 -07:00
) -> Result < Box < SketchGroup > , KclError > {
2024-05-30 17:48:59 -05:00
let from = sketch_group . current_pen_position ( ) ? ;
2024-03-15 17:03:42 -04:00
let ( angle , length ) = match data {
AngledLineData ::AngleAndLengthNamed { angle , length } = > ( angle , length ) ,
AngledLineData ::AngleAndLengthPair ( pair ) = > ( pair [ 0 ] , pair [ 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 ,
2024-03-15 17:03:42 -04:00
name : tag . unwrap_or ( " " . to_string ( ) ) ,
2023-08-24 15:34:51 -07:00
geo_meta : GeoMeta {
id ,
metadata : args . source_range . into ( ) ,
} ,
} ,
} ;
2024-06-19 13:57:50 -07:00
args . batch_modeling_cmd (
2023-09-11 17:14:41 -07:00
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 > {
2024-03-15 17:03:42 -04:00
let ( data , sketch_group , tag ) : ( AngledLineData , Box < SketchGroup > , Option < String > ) =
args . get_data_and_sketch_group_and_tag ( ) ? ;
2023-08-24 15:34:51 -07:00
2024-03-15 17:03:42 -04:00
let new_sketch_group = inner_angled_line_of_x_length ( data , sketch_group , tag , 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.
2024-03-13 12:56:46 -07:00
///
/// ```no_run
2024-05-14 17:10:47 -07:00
/// const sketch001 = startSketchOn('XZ')
2024-03-15 17:03:42 -04:00
/// |> startProfileAt([0, 0], %)
2024-05-14 17:10:47 -07:00
/// |> angledLineOfXLength({ angle: 45, length: 10 }, %, "edge1")
/// |> angledLineOfXLength({ angle: -15, length: 20 }, %, "edge2")
/// |> line([0, -5], %)
/// |> close(%, "edge3")
///
/// const extrusion = extrude(10, sketch001)
2024-03-13 12:56:46 -07:00
/// ```
2023-08-25 13:41:04 -07:00
#[ 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 > ,
2024-03-15 17:03:42 -04:00
tag : Option < String > ,
2023-09-20 18:27:08 -07:00
args : Args ,
2023-09-19 14:20:14 -07:00
) -> Result < Box < SketchGroup > , KclError > {
2024-03-15 17:03:42 -04:00
let ( angle , length ) = match data {
AngledLineData ::AngleAndLengthNamed { angle , length } = > ( angle , length ) ,
AngledLineData ::AngleAndLengthPair ( pair ) = > ( pair [ 0 ] , pair [ 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
2024-03-15 17:03:42 -04:00
let new_sketch_group = inner_line ( to . into ( ) , sketch_group , tag , 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) ]
2024-03-15 17:03:42 -04:00
#[ serde(rename_all = " camelCase " ) ]
pub struct AngledLineToData {
/// The angle of the line.
angle : f64 ,
/// The point to draw to.
to : f64 ,
2023-10-24 10:30:14 -07:00
}
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 > {
2024-03-15 17:03:42 -04:00
let ( data , sketch_group , tag ) : ( AngledLineToData , Box < SketchGroup > , Option < String > ) =
args . get_data_and_sketch_group_and_tag ( ) ? ;
2023-08-24 15:34:51 -07:00
2024-03-15 17:03:42 -04:00
let new_sketch_group = inner_angled_line_to_x ( data , sketch_group , tag , 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.
2024-03-13 12:56:46 -07:00
///
/// ```no_run
2024-05-22 09:15:38 -07:00
/// const exampleSketch = startSketchOn('XZ')
2024-03-15 17:03:42 -04:00
/// |> startProfileAt([0, 0], %)
2024-05-14 17:10:47 -07:00
/// |> angledLineToX({ angle: 30, to: 10 }, %)
2024-03-15 17:03:42 -04:00
/// |> line([0, 10], %)
2024-05-14 17:10:47 -07:00
/// |> line([-10, 0], %)
/// |> close(%)
///
/// const example = extrude(10, exampleSketch)
2024-03-13 12:56:46 -07:00
/// ```
2023-08-25 13:41:04 -07:00
#[ 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 > ,
2024-03-15 17:03:42 -04:00
tag : Option < String > ,
2023-09-20 18:27:08 -07:00
args : Args ,
2023-09-19 14:20:14 -07:00
) -> Result < Box < SketchGroup > , KclError > {
2024-05-30 17:48:59 -05:00
let from = sketch_group . current_pen_position ( ) ? ;
2024-03-15 17:03:42 -04:00
let AngledLineToData { angle , to : x_to } = data ;
2023-08-24 15:34:51 -07:00
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 ;
2024-03-15 17:03:42 -04:00
let new_sketch_group = inner_line_to ( [ x_to , y_to ] , sketch_group , tag , 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 > {
2024-03-15 17:03:42 -04:00
let ( data , sketch_group , tag ) : ( AngledLineData , Box < SketchGroup > , Option < String > ) =
args . get_data_and_sketch_group_and_tag ( ) ? ;
2023-08-24 15:34:51 -07:00
2024-03-15 17:03:42 -04:00
let new_sketch_group = inner_angled_line_of_y_length ( data , sketch_group , tag , 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.
2024-03-13 12:56:46 -07:00
///
/// ```no_run
2024-05-14 17:10:47 -07:00
/// const exampleSketch = startSketchOn('XZ')
2024-03-15 17:03:42 -04:00
/// |> startProfileAt([0, 0], %)
2024-05-14 17:10:47 -07:00
/// |> line([10, 0], %)
/// |> angledLineOfYLength({ angle: 45, length: 10 }, %)
2024-03-13 12:56:46 -07:00
/// |> line([0, 10], %)
2024-05-14 17:10:47 -07:00
/// |> angledLineOfYLength({ angle: 135, length: 10 }, %)
/// |> line([-10, 0], %)
/// |> line([0, -30], %)
///
/// const example = extrude(10, exampleSketch)
2024-03-13 12:56:46 -07:00
/// ```
2023-08-25 13:41:04 -07:00
#[ 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 > ,
2024-03-15 17:03:42 -04:00
tag : Option < String > ,
2023-09-20 18:27:08 -07:00
args : Args ,
2023-09-19 14:20:14 -07:00
) -> Result < Box < SketchGroup > , KclError > {
2024-03-15 17:03:42 -04:00
let ( angle , length ) = match data {
AngledLineData ::AngleAndLengthNamed { angle , length } = > ( angle , length ) ,
AngledLineData ::AngleAndLengthPair ( pair ) = > ( pair [ 0 ] , pair [ 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
2024-03-15 17:03:42 -04:00
let new_sketch_group = inner_line ( to . into ( ) , sketch_group , tag , 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 > {
2024-03-15 17:03:42 -04:00
let ( data , sketch_group , tag ) : ( AngledLineToData , Box < SketchGroup > , Option < String > ) =
args . get_data_and_sketch_group_and_tag ( ) ? ;
2023-08-24 15:34:51 -07:00
2024-03-15 17:03:42 -04:00
let new_sketch_group = inner_angled_line_to_y ( data , sketch_group , tag , 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.
2024-03-13 12:56:46 -07:00
///
/// ```no_run
2024-05-22 09:15:38 -07:00
/// const exampleSketch = startSketchOn('XZ')
2024-03-15 17:03:42 -04:00
/// |> startProfileAt([0, 0], %)
2024-05-14 17:10:47 -07:00
/// |> angledLineToY({ angle: 60, to: 20 }, %)
/// |> line([-20, 0], %)
/// |> angledLineToY({ angle: 70, to: 10 }, %)
/// |> close(%)
///
/// const example = extrude(10, exampleSketch)
2024-03-13 12:56:46 -07:00
/// ```
2023-08-25 13:41:04 -07:00
#[ 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 > ,
2024-03-15 17:03:42 -04:00
tag : Option < String > ,
2023-09-20 18:27:08 -07:00
args : Args ,
2023-09-19 14:20:14 -07:00
) -> Result < Box < SketchGroup > , KclError > {
2024-05-30 17:48:59 -05:00
let from = sketch_group . current_pen_position ( ) ? ;
2024-03-15 17:03:42 -04:00
let AngledLineToData { angle , to : y_to } = data ;
2023-08-24 15:34:51 -07:00
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 ;
2024-03-15 17:03:42 -04:00
let new_sketch_group = inner_line_to ( [ x_to , y_to ] , sketch_group , tag , 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 > ,
}
/// 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 > {
2024-03-15 17:03:42 -04:00
let ( data , sketch_group , tag ) : ( AngledLineThatIntersectsData , Box < SketchGroup > , Option < String > ) =
args . get_data_and_sketch_group_and_tag ( ) ? ;
let new_sketch_group = inner_angled_line_that_intersects ( data , sketch_group , tag , 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.
2024-03-13 12:56:46 -07:00
///
/// ```no_run
2024-05-22 09:15:38 -07:00
/// const exampleSketch = startSketchOn('XZ')
2024-03-15 17:03:42 -04:00
/// |> startProfileAt([0, 0], %)
2024-05-14 17:10:47 -07:00
/// |> lineTo([5, 10], %)
/// |> lineTo([-10, 10], %, "lineToIntersect")
/// |> lineTo([0, 20], %)
2024-03-15 17:03:42 -04:00
/// |> angledLineThatIntersects({
2024-05-14 17:10:47 -07:00
/// angle: 80,
/// intersectTag: 'lineToIntersect',
/// offset: 10
/// }, %)
/// |> close(%)
///
/// const example = extrude(10, exampleSketch)
2024-03-13 12:56:46 -07:00
/// ```
2023-08-25 13:41:04 -07:00
#[ 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 > ,
2024-03-15 17:03:42 -04:00
tag : Option < String > ,
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 ( ) ;
2024-05-30 17:48:59 -05:00
let from = sketch_group . current_pen_position ( ) ? ;
2023-08-24 15:34:51 -07:00
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
) ;
2024-03-15 17:03:42 -04:00
let new_sketch_group = inner_line_to ( to . into ( ) , sketch_group , tag , 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 > {
2024-03-15 17:03:42 -04:00
let data : [ f64 ; 2 ] = args . get_data ( ) ? ;
2023-08-24 15:34:51 -07:00
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.
2024-03-13 12:56:46 -07:00
///
/// ```no_run
2024-05-14 17:10:47 -07:00
/// const exampleSketch = startSketchAt([0, 0])
/// |> line([10, 0], %)
/// |> line([0, 10], %)
/// |> line([-10, 0], %)
/// |> close(%)
///
/// const example = extrude(5, exampleSketch)
/// ```
///
/// ```no_run
/// const exampleSketch = startSketchAt([10, 10])
/// |> line([10, 0], %)
/// |> line([0, 10], %)
/// |> line([-10, 0], %)
/// |> close(%)
///
/// const example = extrude(5, exampleSketch)
/// ```
///
/// ```no_run
/// const exampleSketch = startSketchAt([-10, 23])
/// |> line([10, 0], %)
/// |> line([0, 10], %)
/// |> line([-10, 0], %)
/// |> close(%)
///
/// const example = extrude(5, exampleSketch)
2024-03-13 12:56:46 -07:00
/// ```
2023-08-25 13:41:04 -07:00
#[ stdlib {
name = " startSketchAt " ,
} ]
2024-03-15 17:03:42 -04:00
async fn inner_start_sketch_at ( data : [ f64 ; 2 ] , 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 ;
2024-02-12 18:08:42 -08:00
let sketch_surface = inner_start_sketch_on ( SketchData ::Plane ( xy_plane ) , None , args . clone ( ) ) . await ? ;
2024-03-15 17:03:42 -04:00
let sketch_group = inner_start_profile_at ( data , sketch_surface , None , args ) . await ? ;
2023-10-05 14:27:48 -07:00
Ok ( sketch_group )
}
2024-02-12 18:08:42 -08:00
/// Data for start sketch on.
/// You can start a sketch on a plane or an extrude group.
#[ derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema) ]
#[ ts(export) ]
#[ serde(rename_all = " camelCase " , untagged) ]
pub enum SketchData {
Plane ( PlaneData ) ,
ExtrudeGroup ( Box < ExtrudeGroup > ) ,
}
2023-10-05 14:27:48 -07:00
/// Data for a plane.
2024-06-04 16:28:32 -05:00
#[ derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema) ]
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 ) ,
2024-05-21 23:35:33 -07:00
z_axis : Point3d ::new ( 0.0 , - 1.0 , 0.0 ) ,
2023-10-05 14:27:48 -07:00
value : PlaneType ::XZ ,
meta : vec ! [ ] ,
} ,
PlaneData ::NegXZ = > Plane {
id ,
origin : Point3d ::new ( 0.0 , 0.0 , 0.0 ) ,
2024-05-21 23:35:33 -07:00
x_axis : Point3d ::new ( - 1.0 , 0.0 , 0.0 ) ,
2023-10-05 14:27:48 -07:00
y_axis : Point3d ::new ( 0.0 , 0.0 , 1.0 ) ,
2024-05-21 23:35:33 -07:00
z_axis : Point3d ::new ( 0.0 , 1.0 , 0.0 ) ,
2023-10-05 14:27:48 -07:00
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 ! [ ] ,
} ,
}
}
}
2024-02-12 18:08:42 -08:00
/// Start a sketch on a specific plane or face.
2023-10-05 14:27:48 -07:00
pub async fn start_sketch_on ( args : Args ) -> Result < MemoryItem , KclError > {
2024-02-13 10:26:09 -08:00
let ( data , tag ) : ( SketchData , Option < SketchOnFaceTag > ) = args . get_data_and_optional_tag ( ) ? ;
2023-10-05 14:27:48 -07:00
2024-02-12 18:08:42 -08:00
match inner_start_sketch_on ( data , tag , args ) . await ? {
SketchSurface ::Plane ( plane ) = > Ok ( MemoryItem ::Plane ( plane ) ) ,
SketchSurface ::Face ( face ) = > Ok ( MemoryItem ::Face ( face ) ) ,
}
2023-10-05 14:27:48 -07:00
}
2024-02-12 18:08:42 -08:00
/// Start a sketch on a specific plane or face.
2024-03-13 12:56:46 -07:00
///
/// ```no_run
2024-05-14 17:10:47 -07:00
/// const exampleSketch = startSketchOn("XY")
/// |> startProfileAt([0, 0], %)
/// |> line([10, 0], %)
/// |> line([0, 10], %)
/// |> line([-10, 0], %)
/// |> close(%)
///
/// const example = extrude(5, exampleSketch)
///
/// const exampleSketch002 = startSketchOn(example, 'end')
/// |> startProfileAt([1, 1], %)
/// |> line([8, 0], %)
/// |> line([0, 8], %)
/// |> line([-8, 0], %)
/// |> close(%)
///
/// const example002 = extrude(5, exampleSketch002)
///
/// const exampleSketch003 = startSketchOn(example002, 'end')
/// |> startProfileAt([2, 2], %)
/// |> line([6, 0], %)
/// |> line([0, 6], %)
/// |> line([-6, 0], %)
/// |> close(%)
///
/// const example003 = extrude(5, exampleSketch003)
2024-03-13 12:56:46 -07:00
/// ```
///
/// ```no_run
2024-05-14 17:10:47 -07:00
/// const exampleSketch = startSketchOn("XY")
/// |> startProfileAt([0, 0], %)
/// |> line([10, 0], %)
/// |> line([0, 10], %, 'sketchingFace')
/// |> line([-10, 0], %)
/// |> close(%)
2024-03-13 12:56:46 -07:00
///
2024-05-14 17:10:47 -07:00
/// const example = extrude(10, exampleSketch)
2024-03-13 12:56:46 -07:00
///
2024-05-14 17:10:47 -07:00
/// const exampleSketch002 = startSketchOn(example, 'sketchingFace')
/// |> startProfileAt([1, 1], %)
/// |> line([8, 0], %)
/// |> line([0, 8], %)
/// |> line([-8, 0], %)
/// |> close(%, 'sketchingFace002')
2024-03-13 12:56:46 -07:00
///
2024-05-14 17:10:47 -07:00
/// const example002 = extrude(10, exampleSketch002)
///
/// const exampleSketch003 = startSketchOn(example002, 'sketchingFace002')
/// |> startProfileAt([-8, 12], %)
/// |> line([0, 6], %)
/// |> line([6, 0], %)
/// |> line([0, -6], %)
/// |> close(%)
///
/// const example003 = extrude(5, exampleSketch003)
/// ```
///
/// ```no_run
/// const exampleSketch = 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(%)
///
/// const example = revolve({ axis: 'y', angle: 180 }, exampleSketch)
///
/// const exampleSketch002 = startSketchOn(example, 'end')
/// |> startProfileAt([4.5, -5], %)
/// |> line([0, 5], %)
/// |> line([5, 0], %)
/// |> line([0, -5], %)
/// |> close(%)
///
/// const example002 = extrude(5, exampleSketch002)
2024-03-13 12:56:46 -07:00
/// ```
2024-05-23 19:28:13 -07:00
///
/// ```no_run
/// const a1 = startSketchOn({
/// plane: {
/// origin: { x: 0, y: 0, z: 0 },
/// x_axis: { x: 1, y: 0, z: 0 },
/// y_axis: { x: 0, y: 1, z: 0 },
/// z_axis: { x: 0, y: 0, z: 1 }
/// }
/// })
/// |> startProfileAt([0, 0], %)
/// |> line([100.0, 0], %)
/// |> yLine(-100.0, %)
/// |> xLine(-100.0, %)
/// |> yLine(100.0, %)
/// |> close(%)
/// |> extrude(3.14, %)
/// ```
2023-10-05 14:27:48 -07:00
#[ stdlib {
name = " startSketchOn " ,
} ]
2024-02-13 10:26:09 -08:00
async fn inner_start_sketch_on (
data : SketchData ,
tag : Option < SketchOnFaceTag > ,
args : Args ,
) -> Result < SketchSurface , KclError > {
2024-02-12 18:08:42 -08:00
match data {
SketchData ::Plane ( plane_data ) = > {
let plane = start_sketch_on_plane ( plane_data , args ) . await ? ;
Ok ( SketchSurface ::Plane ( plane ) )
}
SketchData ::ExtrudeGroup ( extrude_group ) = > {
let Some ( tag ) = tag else {
return Err ( KclError ::Type ( KclErrorDetails {
message : " Expected a tag for the face to sketch on " . to_string ( ) ,
source_ranges : vec ! [ args . source_range ] ,
} ) ) ;
} ;
2024-02-13 10:26:09 -08:00
let face = start_sketch_on_face ( extrude_group , tag , args ) . await ? ;
2024-02-12 18:08:42 -08:00
Ok ( SketchSurface ::Face ( face ) )
}
}
}
2024-02-13 10:26:09 -08:00
/// A tag for sketch on face.
#[ derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, FromStr, Display) ]
#[ ts(export) ]
#[ serde(rename_all = " snake_case " , untagged) ]
#[ display( " {0} " ) ]
pub enum SketchOnFaceTag {
StartOrEnd ( StartOrEnd ) ,
/// A string tag for the face you want to sketch on.
String ( String ) ,
}
#[ derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, FromStr, Display) ]
#[ ts(export) ]
#[ serde(rename_all = " snake_case " ) ]
#[ display(style = " snake_case " ) ]
pub enum StartOrEnd {
/// The start face as in before you extruded. This could also be known as the bottom
/// face. But we do not call it bottom because it would be the top face if you
/// extruded it in the opposite direction or flipped the camera.
#[ serde(rename = " start " , alias = " START " ) ]
Start ,
/// The end face after you extruded. This could also be known as the top
/// face. But we do not call it top because it would be the bottom face if you
/// extruded it in the opposite direction or flipped the camera.
#[ serde(rename = " end " , alias = " END " ) ]
End ,
}
async fn start_sketch_on_face (
extrude_group : Box < ExtrudeGroup > ,
tag : SketchOnFaceTag ,
args : Args ,
) -> Result < Box < Face > , KclError > {
let extrude_plane_id = match tag {
2024-05-20 21:59:56 -07:00
SketchOnFaceTag ::String ( ref s ) = > {
if s . is_empty ( ) {
return Err ( KclError ::Type ( KclErrorDetails {
message : " Expected a non-empty tag for the face to sketch on " . to_string ( ) ,
2024-02-13 10:26:09 -08:00
source_ranges : vec ! [ args . source_range ] ,
2024-05-20 21:59:56 -07:00
} ) ) ;
}
extrude_group
. value
. iter ( )
. find_map ( | extrude_surface | match extrude_surface {
ExtrudeSurface ::ExtrudePlane ( extrude_plane ) if extrude_plane . name = = * s = > {
Some ( Ok ( extrude_plane . face_id ) )
}
ExtrudeSurface ::ExtrudeArc ( extrude_arc ) if extrude_arc . name = = * s = > {
Some ( Err ( KclError ::Type ( KclErrorDetails {
message : format ! ( " Cannot sketch on a non-planar surface: `{}` " , tag ) ,
source_ranges : vec ! [ args . source_range ] ,
} ) ) )
}
ExtrudeSurface ::ExtrudePlane ( _ ) | ExtrudeSurface ::ExtrudeArc ( _ ) = > None ,
2024-02-13 10:26:09 -08:00
} )
2024-05-20 21:59:56 -07:00
. ok_or_else ( | | {
KclError ::Type ( KclErrorDetails {
message : format ! ( " Expected a face with the tag `{}` " , tag ) ,
source_ranges : vec ! [ args . source_range ] ,
} )
} ) ? ?
}
2024-02-13 10:26:09 -08:00
SketchOnFaceTag ::StartOrEnd ( StartOrEnd ::Start ) = > extrude_group . start_cap_id . ok_or_else ( | | {
2024-02-12 18:08:42 -08:00
KclError ::Type ( KclErrorDetails {
2024-02-13 10:26:09 -08:00
message : " Expected a start face to sketch on " . to_string ( ) ,
2024-02-12 18:08:42 -08:00
source_ranges : vec ! [ args . source_range ] ,
} )
2024-02-13 10:26:09 -08:00
} ) ? ,
SketchOnFaceTag ::StartOrEnd ( StartOrEnd ::End ) = > extrude_group . end_cap_id . ok_or_else ( | | {
KclError ::Type ( KclErrorDetails {
message : " Expected an end face to sketch on " . to_string ( ) ,
source_ranges : vec ! [ args . source_range ] ,
} )
} ) ? ,
} ;
2024-02-12 18:08:42 -08:00
Ok ( Box ::new ( Face {
2024-06-21 19:54:18 -07:00
id : extrude_plane_id ,
2024-02-12 18:08:42 -08:00
value : tag . to_string ( ) ,
2024-02-26 14:54:42 -08:00
sketch_group_id : extrude_group . id ,
2024-02-12 18:08:42 -08:00
// TODO: get this from the extrude plane data.
2024-06-21 23:50:30 -07:00
x_axis : extrude_group . sketch_group . on . x_axis ( ) ,
y_axis : extrude_group . sketch_group . on . y_axis ( ) ,
z_axis : extrude_group . sketch_group . on . z_axis ( ) ,
2024-02-12 18:08:42 -08:00
meta : vec ! [ args . source_range . into ( ) ] ,
} ) )
}
async fn start_sketch_on_plane ( data : PlaneData , args : Args ) -> Result < Box < Plane > , KclError > {
2023-10-05 14:27:48 -07:00
let mut plane : Plane = data . clone ( ) . into ( ) ;
2024-04-15 17:18:32 -07:00
// Get the default planes.
let default_planes = args . ctx . engine . default_planes ( args . source_range ) . await ? ;
2023-10-05 14:27:48 -07:00
plane . id = match data {
2024-04-15 17:18:32 -07:00
PlaneData ::XY = > default_planes . xy ,
PlaneData ::XZ = > default_planes . xz ,
PlaneData ::YZ = > default_planes . yz ,
PlaneData ::NegXY = > default_planes . neg_xy ,
PlaneData ::NegXZ = > default_planes . neg_xz ,
PlaneData ::NegYZ = > default_planes . neg_yz ,
2023-10-05 14:27:48 -07:00
PlaneData ::Plane {
origin ,
x_axis ,
y_axis ,
z_axis : _ ,
} = > {
2024-04-15 17:18:32 -07:00
// Create the custom plane on the fly.
let id = uuid ::Uuid ::new_v4 ( ) ;
2024-06-19 13:57:50 -07:00
args . batch_modeling_cmd (
2023-10-05 14:27:48 -07:00
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 ? ;
2024-04-15 17:18:32 -07:00
2024-02-11 12:59:00 +11:00
id
}
2023-10-05 14:27:48 -07:00
} ;
Ok ( Box ::new ( plane ) )
}
/// Start a profile at a given point.
pub async fn start_profile_at ( args : Args ) -> Result < MemoryItem , KclError > {
2024-03-15 17:03:42 -04:00
let ( start , sketch_surface , tag ) : ( [ f64 ; 2 ] , SketchSurface , Option < String > ) = args . get_data_and_sketch_surface ( ) ? ;
2023-10-05 14:27:48 -07:00
2024-03-15 17:03:42 -04:00
let sketch_group = inner_start_profile_at ( start , sketch_surface , tag , args ) . await ? ;
2023-10-05 14:27:48 -07:00
Ok ( MemoryItem ::SketchGroup ( sketch_group ) )
}
/// Start a profile at a given point.
2024-03-13 12:56:46 -07:00
///
/// ```no_run
2024-05-22 09:15:38 -07:00
/// const exampleSketch = startSketchOn('XZ')
2024-05-14 17:10:47 -07:00
/// |> startProfileAt([0, 0], %)
/// |> line([10, 0], %)
/// |> line([0, 10], %)
/// |> line([-10, 0], %)
/// |> close(%)
///
/// const example = extrude(5, exampleSketch)
/// ```
///
/// ```no_run
/// const exampleSketch = startSketchOn('-XZ')
/// |> startProfileAt([10, 10], %)
/// |> line([10, 0], %)
/// |> line([0, 10], %)
/// |> line([-10, 0], %)
/// |> close(%)
///
/// const example = extrude(5, exampleSketch)
/// ```
///
/// ```no_run
/// const exampleSketch = startSketchOn('-XZ')
/// |> startProfileAt([-10, 23], %)
/// |> line([10, 0], %)
/// |> line([0, 10], %)
/// |> line([-10, 0], %)
/// |> close(%)
///
/// const example = extrude(5, exampleSketch)
2024-03-13 12:56:46 -07:00
/// ```
2023-10-05 14:27:48 -07:00
#[ stdlib {
name = " startProfileAt " ,
} ]
2024-03-13 17:16:57 -07:00
pub ( crate ) async fn inner_start_profile_at (
2024-03-15 17:03:42 -04:00
to : [ f64 ; 2 ] ,
2024-02-12 18:08:42 -08:00
sketch_surface : SketchSurface ,
2024-03-15 17:03:42 -04:00
tag : Option < String > ,
2024-02-12 18:08:42 -08:00
args : Args ,
) -> Result < Box < SketchGroup > , KclError > {
2024-06-21 19:54:18 -07:00
// Enter sketch mode on the surface.
// We call this here so you can reuse the sketch surface for multiple sketches.
let id = uuid ::Uuid ::new_v4 ( ) ;
args . batch_modeling_cmd (
id ,
ModelingCmd ::EnableSketchMode {
animated : false ,
ortho : false ,
entity_id : sketch_surface . id ( ) ,
adjust_camera : false ,
planar_normal : if let SketchSurface ::Plane ( plane ) = & sketch_surface {
// We pass in the normal for the plane here.
Some ( plane . z_axis . clone ( ) . into ( ) )
} else {
None
} ,
} ,
)
. await ? ;
2023-08-24 15:34:51 -07:00
let id = uuid ::Uuid ::new_v4 ( ) ;
let path_id = uuid ::Uuid ::new_v4 ( ) ;
2024-06-19 13:57:50 -07:00
args . batch_modeling_cmd ( path_id , ModelingCmd ::StartPath { } ) . await ? ;
args . batch_modeling_cmd (
2023-08-24 15:34:51 -07:00
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 ,
2024-03-15 17:03:42 -04:00
name : tag . unwrap_or ( " " . to_string ( ) ) ,
2023-08-24 15:34:51 -07:00
geo_meta : GeoMeta {
id ,
metadata : args . source_range . into ( ) ,
} ,
} ;
let sketch_group = SketchGroup {
id : path_id ,
2024-02-16 16:42:01 -08:00
on : sketch_surface . clone ( ) ,
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
}
2024-05-21 03:44:02 -04:00
/// Returns the X component of the sketch profile start point.
pub async fn profile_start_x ( args : Args ) -> Result < MemoryItem , KclError > {
let sketch_group : Box < SketchGroup > = args . get_sketch_group ( ) ? ;
let x = inner_profile_start_x ( sketch_group ) ? ;
args . make_user_val_from_f64 ( x )
}
/// ```no_run
/// const sketch001 = startSketchOn('XY')
/// |> startProfileAt([5, 2], %)
/// |> angledLine([-26.6, 50], %)
/// |> angledLine([90, 50], %)
/// |> angledLineToX({ angle: 30, to: profileStartX(%) }, %)
/// ```
#[ stdlib {
name = " profileStartX "
} ]
pub ( crate ) fn inner_profile_start_x ( sketch_group : Box < SketchGroup > ) -> Result < f64 , KclError > {
Ok ( sketch_group . start . to [ 0 ] )
}
/// Returns the Y component of the sketch profile start point.
pub async fn profile_start_y ( args : Args ) -> Result < MemoryItem , KclError > {
let sketch_group : Box < SketchGroup > = args . get_sketch_group ( ) ? ;
let x = inner_profile_start_y ( sketch_group ) ? ;
args . make_user_val_from_f64 ( x )
}
/// ```no_run
/// const sketch001 = startSketchOn('XY')
/// |> startProfileAt([5, 2], %)
/// |> angledLine({ angle: -60, length: 14 }, %)
/// |> angledLineToY({ angle: 30, to: profileStartY(%) }, %)
/// ```
#[ stdlib {
name = " profileStartY "
} ]
pub ( crate ) fn inner_profile_start_y ( sketch_group : Box < SketchGroup > ) -> Result < f64 , KclError > {
Ok ( sketch_group . start . to [ 1 ] )
}
/// Returns the sketch profile start point.
pub async fn profile_start ( args : Args ) -> Result < MemoryItem , KclError > {
let sketch_group : Box < SketchGroup > = args . get_sketch_group ( ) ? ;
let point = inner_profile_start ( sketch_group ) ? ;
Ok ( MemoryItem ::UserVal ( UserVal {
value : serde_json ::to_value ( point ) . map_err ( | e | {
KclError ::Type ( KclErrorDetails {
message : format ! ( " Failed to convert point to json: {} " , e ) ,
source_ranges : vec ! [ args . source_range ] ,
} )
} ) ? ,
meta : Default ::default ( ) ,
} ) )
}
/// ```no_run
/// const sketch001 = startSketchOn('XY')
/// |> startProfileAt([5, 2], %)
/// |> angledLine({ angle: 120, length: 50 }, %, 'seg01')
/// |> angledLine({ angle: segAng('seg01', %) + 120, length: 50 }, %)
/// |> lineTo(profileStart(%), %)
/// |> close(%)
/// |> extrude(20, %)
/// ```
#[ stdlib {
name = " profileStart "
} ]
pub ( crate ) fn inner_profile_start ( sketch_group : Box < SketchGroup > ) -> Result < [ f64 ; 2 ] , KclError > {
Ok ( sketch_group . start . to )
}
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 > {
2024-03-07 12:35:56 -08:00
let ( sketch_group , tag ) : ( Box < SketchGroup > , Option < String > ) = args . get_sketch_group_and_optional_tag ( ) ? ;
2023-08-25 13:41:04 -07:00
2024-03-07 12:35:56 -08:00
let new_sketch_group = inner_close ( sketch_group , tag , args ) . await ? ;
2023-08-25 13:41:04 -07:00
Ok ( MemoryItem ::SketchGroup ( new_sketch_group ) )
}
/// Close the current sketch.
2024-03-13 12:56:46 -07:00
///
/// ```no_run
/// startSketchOn('XZ')
/// |> startProfileAt([0, 0], %)
/// |> line([10, 10], %)
/// |> line([10, 0], %)
/// |> close(%)
2024-04-25 02:31:18 -07:00
/// |> extrude(10, %)
2024-03-13 12:56:46 -07:00
/// ```
///
/// ```no_run
2024-05-14 17:10:47 -07:00
/// const exampleSketch = startSketchOn('-XZ')
/// |> startProfileAt([0, 0], %)
/// |> line([10, 0], %)
/// |> line([0, 10], %)
/// |> close(%)
///
/// const example = extrude(10, exampleSketch)
2024-03-13 12:56:46 -07:00
/// ```
2023-08-25 13:41:04 -07:00
#[ stdlib {
name = " close " ,
} ]
2024-03-13 17:16:57 -07:00
pub ( crate ) async fn inner_close (
2024-03-07 12:35:56 -08:00
sketch_group : Box < SketchGroup > ,
tag : Option < String > ,
args : Args ,
) -> Result < Box < SketchGroup > , KclError > {
2024-05-30 17:48:59 -05:00
let from = sketch_group . current_pen_position ( ) ? ;
2023-08-24 15:34:51 -07:00
let to : Point2d = sketch_group . start . from . into ( ) ;
let id = uuid ::Uuid ::new_v4 ( ) ;
2024-06-19 13:57:50 -07:00
args . batch_modeling_cmd (
2023-08-24 15:34:51 -07:00
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
2024-02-16 16:42:01 -08:00
// If we are sketching on a plane we can close the sketch group now.
if let SketchSurface ::Plane ( _ ) = sketch_group . on {
2023-10-05 14:27:48 -07:00
// We were on a plane, disable the sketch mode.
2024-06-19 13:57:50 -07:00
args . batch_modeling_cmd ( uuid ::Uuid ::new_v4 ( ) , kittycad ::types ::ModelingCmd ::SketchModeDisable { } )
2023-10-05 14:27:48 -07:00
. 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 ( ) ,
2024-03-07 12:35:56 -08:00
name : tag . unwrap_or_default ( ) ,
2023-08-24 15:34:51 -07:00
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 {
2024-03-07 15:35:26 -08:00
/// Angles and radius with an optional tag.
2023-08-31 22:19:23 -07:00
AnglesAndRadius {
/// The start angle.
angle_start : f64 ,
/// The end angle.
angle_end : f64 ,
/// The radius.
radius : f64 ,
} ,
2024-03-07 15:35:26 -08:00
/// Center, to and radius with an optional tag.
2023-08-31 22:19:23 -07:00
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 > {
2024-03-15 17:03:42 -04:00
let ( data , sketch_group , tag ) : ( ArcData , Box < SketchGroup > , Option < String > ) =
args . get_data_and_sketch_group_and_tag ( ) ? ;
2023-08-31 22:19:23 -07:00
2024-03-15 17:03:42 -04:00
let new_sketch_group = inner_arc ( data , sketch_group , tag , args ) . await ? ;
2023-08-31 22:19:23 -07:00
Ok ( MemoryItem ::SketchGroup ( new_sketch_group ) )
}
/// Draw an arc.
2024-03-13 12:56:46 -07:00
///
/// ```no_run
2024-05-22 09:15:38 -07:00
/// const exampleSketch = startSketchOn('XZ')
2024-03-13 12:56:46 -07:00
/// |> startProfileAt([0, 0], %)
2024-05-14 17:10:47 -07:00
/// |> line([10, 0], %)
2024-03-13 12:56:46 -07:00
/// |> arc({
2024-05-14 17:10:47 -07:00
/// angle_start: 0,
/// angle_end: 280,
/// radius: 16
/// }, %)
/// |> close(%)
// const example = extrude(10, exampleSketch)
2024-03-13 12:56:46 -07:00
/// ```
2023-08-31 22:19:23 -07:00
#[ stdlib {
name = " arc " ,
} ]
2024-03-13 17:16:57 -07:00
pub ( crate ) async fn inner_arc (
data : ArcData ,
sketch_group : Box < SketchGroup > ,
2024-03-15 17:03:42 -04:00
tag : Option < String > ,
2024-03-13 17:16:57 -07:00
args : Args ,
) -> Result < Box < SketchGroup > , KclError > {
2024-05-30 17:48:59 -05:00
let from : Point2d = sketch_group . current_pen_position ( ) ? ;
2023-08-31 22:19:23 -07:00
let ( center , angle_start , angle_end , radius , end ) = match & data {
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
}
2024-03-15 17:03:42 -04:00
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 ( ) ;
2024-06-19 13:57:50 -07:00
args . batch_modeling_cmd (
2023-08-31 22:19:23 -07:00
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 ( ) ,
2024-03-15 17:03:42 -04:00
name : tag . unwrap_or ( " " . to_string ( ) ) ,
2023-08-31 22:19:23 -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-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 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 > {
2024-03-15 17:03:42 -04:00
let ( data , sketch_group , tag ) : ( TangentialArcData , Box < SketchGroup > , Option < String > ) =
args . get_data_and_sketch_group_and_tag ( ) ? ;
2023-09-29 14:41:14 -07:00
2024-03-15 17:03:42 -04:00
let new_sketch_group = inner_tangential_arc ( data , sketch_group , tag , args ) . await ? ;
2023-09-29 14:41:14 -07:00
Ok ( MemoryItem ::SketchGroup ( new_sketch_group ) )
}
/// Draw an arc.
2024-03-13 12:56:46 -07:00
///
/// ```no_run
2024-05-22 09:15:38 -07:00
/// const exampleSketch = startSketchOn('XZ')
2024-03-15 17:03:42 -04:00
/// |> startProfileAt([0, 0], %)
2024-05-14 17:10:47 -07:00
/// |> angledLine({
/// angle: 60,
/// length: 10,
/// }, %)
/// |> tangentialArc({ radius: 10, offset: -120 }, %)
/// |> angledLine({
/// angle: -60,
/// length: 10,
/// }, %)
2024-03-13 12:56:46 -07:00
/// |> close(%)
2024-05-14 17:10:47 -07:00
///
/// const example = extrude(10, exampleSketch)
2024-03-13 12:56:46 -07:00
/// ```
2023-09-29 14:41:14 -07:00
#[ 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 > ,
2024-03-15 17:03:42 -04:00
tag : Option < String > ,
2023-09-29 14:41:14 -07:00
args : Args ,
) -> Result < Box < SketchGroup > , KclError > {
2024-05-30 17:48:59 -05:00
let from : Point2d = sketch_group . current_pen_position ( ) ? ;
2023-09-29 14:41:14 -07:00
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 ) ;
2024-06-19 13:57:50 -07:00
args . batch_modeling_cmd (
2023-09-29 14:41:14 -07:00
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 ::Point ( to ) = > {
2024-06-19 13:57:50 -07:00
args . batch_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 ] ] ;
2024-02-22 19:07:17 -08:00
let current_path = Path ::TangentialArc {
2023-09-29 14:41:14 -07:00
base : BasePath {
from : from . into ( ) ,
to ,
2024-03-15 17:03:42 -04:00
name : tag . unwrap_or ( " " . to_string ( ) ) ,
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-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.
2024-03-13 12:56:46 -07:00
///
/// ```no_run
2024-05-22 09:15:38 -07:00
/// const exampleSketch = startSketchOn('XZ')
2024-05-14 17:10:47 -07:00
/// |> startProfileAt([0, 0], %)
/// |> angledLine({
/// angle: 60,
/// length: 10,
/// }, %)
/// |> tangentialArcTo([15, 15], %)
/// |> line([10, -15], %)
/// |> close(%)
///
/// const example = extrude(10, exampleSketch)
2024-03-13 12:56:46 -07:00
/// ```
2023-09-29 14:41:14 -07:00
#[ 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 > {
2024-05-30 17:48:59 -05:00
let from : Point2d = sketch_group . current_pen_position ( ) ? ;
2024-02-11 12:59:00 +11:00
let tangent_info = sketch_group . get_tangential_info_from_paths ( ) ;
let tan_previous_point = if tangent_info . is_center {
get_tangent_point_from_previous_arc ( tangent_info . center_or_tangent_point , tangent_info . ccw , from . into ( ) )
} else {
tangent_info . center_or_tangent_point
} ;
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 ;
2024-02-11 12:59:00 +11:00
let result = get_tangential_arc_to_info ( TangentialArcInfoInput {
arc_start_point : [ from . x , from . y ] ,
arc_end_point : to ,
tan_previous_point ,
obtuse : true ,
} ) ;
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 ( ) ;
2024-06-19 13:57:50 -07:00
args . batch_modeling_cmd ( id , tan_arc_to ( & sketch_group , & delta ) ) . await ? ;
2023-09-29 14:41:14 -07:00
2024-02-11 12:59:00 +11:00
let current_path = Path ::TangentialArcTo {
2023-09-29 14:41:14 -07:00
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 ( ) ,
} ,
} ,
2024-02-11 12:59:00 +11:00
center : result . center ,
ccw : result . ccw > 0 ,
2023-09-29 14:41:14 -07:00
} ;
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) ]
2024-03-07 15:35:26 -08:00
#[ serde(rename_all = " camelCase " ) ]
pub struct BezierData {
/// The to point.
to : [ f64 ; 2 ] ,
/// The first control point.
control1 : [ f64 ; 2 ] ,
/// The second control point.
control2 : [ f64 ; 2 ] ,
2023-08-31 22:19:23 -07:00
}
/// Draw a bezier curve.
2023-09-20 18:27:08 -07:00
pub async fn bezier_curve ( args : Args ) -> Result < MemoryItem , KclError > {
2024-03-15 17:03:42 -04:00
let ( data , sketch_group , tag ) : ( BezierData , Box < SketchGroup > , Option < String > ) =
args . get_data_and_sketch_group_and_tag ( ) ? ;
2023-08-31 22:19:23 -07:00
2024-03-15 17:03:42 -04:00
let new_sketch_group = inner_bezier_curve ( data , sketch_group , tag , args ) . await ? ;
2023-08-31 22:19:23 -07:00
Ok ( MemoryItem ::SketchGroup ( new_sketch_group ) )
}
/// Draw a bezier curve.
2024-03-13 12:56:46 -07:00
///
/// ```no_run
2024-05-22 09:15:38 -07:00
/// const exampleSketch = startSketchOn('XZ')
2024-05-14 17:10:47 -07:00
/// |> startProfileAt([0, 0], %)
/// |> line([0, 10], %)
/// |> bezierCurve({
/// to: [10, 10],
/// control1: [5, 0],
/// control2: [5, 10]
/// }, %)
/// |> lineTo([10, 0], %)
/// |> close(%)
///
/// const example = extrude(10, exampleSketch)
2024-03-13 12:56:46 -07:00
/// ```
2023-08-31 22:19:23 -07:00
#[ 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 > ,
2024-03-15 17:03:42 -04:00
tag : Option < String > ,
2023-09-20 18:27:08 -07:00
args : Args ,
2023-09-19 14:20:14 -07:00
) -> Result < Box < SketchGroup > , KclError > {
2024-05-30 17:48:59 -05:00
let from = sketch_group . current_pen_position ( ) ? ;
2023-08-31 22:19:23 -07:00
2023-09-20 17:36:26 -07:00
let relative = true ;
2024-03-07 15:35:26 -08:00
let delta = data . to ;
let to = [ from . x + data . to [ 0 ] , from . y + data . to [ 1 ] ] ;
2023-08-31 22:19:23 -07:00
let id = uuid ::Uuid ::new_v4 ( ) ;
2024-06-19 13:57:50 -07:00
args . batch_modeling_cmd (
2023-08-31 22:19:23 -07:00
id ,
ModelingCmd ::ExtendPath {
path : sketch_group . id ,
segment : kittycad ::types ::PathSegment ::Bezier {
2024-04-22 20:46:54 -07:00
control_1 : Point3D {
2024-03-07 15:35:26 -08:00
x : data . control1 [ 0 ] ,
y : data . control1 [ 1 ] ,
2023-08-31 22:19:23 -07:00
z : 0.0 ,
} ,
2024-04-22 20:46:54 -07:00
control_2 : Point3D {
2024-03-07 15:35:26 -08:00
x : data . control2 [ 0 ] ,
y : data . 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 ,
2024-03-15 17:03:42 -04:00
name : tag . unwrap_or_default ( ) . to_string ( ) ,
2023-08-31 22:19:23 -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-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 > {
2024-02-11 15:08:54 -08:00
let ( hole_sketch_group , sketch_group ) : ( SketchGroupSet , Box < SketchGroup > ) = args . get_sketch_groups ( ) ? ;
2023-10-13 12:02:46 -07:00
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.
2024-03-13 12:56:46 -07:00
///
/// ```no_run
2024-05-14 17:10:47 -07:00
/// const exampleSketch = startSketchOn('XY')
/// |> startProfileAt([0, 0], %)
/// |> line([0, 5], %)
/// |> line([5, 0], %)
/// |> line([0, -5], %)
/// |> close(%)
/// |> hole(circle([1, 1], .25, %), %)
/// |> hole(circle([1, 4], .25, %), %)
///
/// const example = extrude(1, exampleSketch)
/// ```
///
/// ```no_run
/// fn squareHoleSketch = () => {
/// const squareSketch = startSketchOn('-XZ')
/// |> startProfileAt([-1, -1], %)
/// |> line([2, 0], %)
/// |> line([0, 2], %)
/// |> line([-2, 0], %)
/// |> close(%)
/// return squareSketch
/// }
2024-05-22 09:15:38 -07:00
///
/// const exampleSketch = startSketchOn('-XZ')
2024-05-14 17:10:47 -07:00
/// |> circle([0, 0], 3, %)
/// |> hole(squareHoleSketch(), %)
2024-05-22 09:15:38 -07:00
/// const example = extrude(1, exampleSketch)
2024-03-13 12:56:46 -07:00
/// ```
2023-10-13 12:02:46 -07:00
#[ stdlib {
name = " hole " ,
} ]
async fn inner_hole (
2024-02-11 15:08:54 -08:00
hole_sketch_group : SketchGroupSet ,
2023-10-13 12:02:46 -07:00
sketch_group : Box < SketchGroup > ,
args : Args ,
) -> Result < Box < SketchGroup > , KclError > {
//TODO: batch these (once we have batch)
2024-02-11 15:08:54 -08:00
match hole_sketch_group {
SketchGroupSet ::SketchGroup ( hole_sketch_group ) = > {
2024-06-19 13:57:50 -07:00
args . batch_modeling_cmd (
2024-02-11 15:08:54 -08:00
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
2024-06-19 13:57:50 -07:00
args . batch_modeling_cmd (
2024-02-11 15:08:54 -08:00
uuid ::Uuid ::new_v4 ( ) ,
ModelingCmd ::ObjectVisible {
object_id : hole_sketch_group . id ,
hidden : true ,
} ,
)
. await ? ;
}
SketchGroupSet ::SketchGroups ( hole_sketch_groups ) = > {
for hole_sketch_group in hole_sketch_groups {
2024-06-19 13:57:50 -07:00
args . batch_modeling_cmd (
2024-02-11 15:08:54 -08:00
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
2024-06-19 13:57:50 -07:00
args . batch_modeling_cmd (
2024-02-11 15:08:54 -08:00
uuid ::Uuid ::new_v4 ( ) ,
ModelingCmd ::ObjectVisible {
object_id : hole_sketch_group . id ,
hidden : true ,
} ,
)
. await ? ;
}
}
}
2023-10-13 12:02:46 -07:00
// 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 ;
2024-03-15 17:03:42 -04:00
use crate ::std ::sketch ::PlaneData ;
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 ) ;
}
2024-02-13 10:26:09 -08:00
#[ test ]
fn test_deserialize_sketch_on_face_tag ( ) {
let data = " start " ;
let mut str_json = serde_json ::to_string ( & data ) . unwrap ( ) ;
assert_eq! ( str_json , " \" start \" " ) ;
str_json = " \" end \" " . to_string ( ) ;
let data : crate ::std ::sketch ::SketchOnFaceTag = serde_json ::from_str ( & str_json ) . unwrap ( ) ;
assert_eq! (
data ,
crate ::std ::sketch ::SketchOnFaceTag ::StartOrEnd ( crate ::std ::sketch ::StartOrEnd ::End )
) ;
str_json = " \" thing \" " . to_string ( ) ;
let data : crate ::std ::sketch ::SketchOnFaceTag = serde_json ::from_str ( & str_json ) . unwrap ( ) ;
assert_eq! ( data , crate ::std ::sketch ::SketchOnFaceTag ::String ( " thing " . to_string ( ) ) ) ;
str_json = " \" END \" " . to_string ( ) ;
let data : crate ::std ::sketch ::SketchOnFaceTag = serde_json ::from_str ( & str_json ) . unwrap ( ) ;
assert_eq! (
data ,
crate ::std ::sketch ::SketchOnFaceTag ::StartOrEnd ( crate ::std ::sketch ::StartOrEnd ::End )
) ;
str_json = " \" start \" " . to_string ( ) ;
let data : crate ::std ::sketch ::SketchOnFaceTag = serde_json ::from_str ( & str_json ) . unwrap ( ) ;
assert_eq! (
data ,
crate ::std ::sketch ::SketchOnFaceTag ::StartOrEnd ( crate ::std ::sketch ::StartOrEnd ::Start )
) ;
str_json = " \" START \" " . to_string ( ) ;
let data : crate ::std ::sketch ::SketchOnFaceTag = serde_json ::from_str ( & str_json ) . unwrap ( ) ;
assert_eq! (
data ,
crate ::std ::sketch ::SketchOnFaceTag ::StartOrEnd ( crate ::std ::sketch ::StartOrEnd ::Start )
) ;
}
2023-08-24 15:34:51 -07:00
}