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 ;
2024-12-05 12:09:35 -05:00
use indexmap ::IndexMap ;
2024-09-18 17:04:04 -05:00
use kcmc ::shared ::Point2d as KPoint2d ; // Point2d is already defined in this pkg, to impl ts_rs traits.
2024-09-19 14:06:29 -07:00
use kcmc ::{ each_cmd as mcmd , length_unit ::LengthUnit , shared ::Angle , ModelingCmd } ;
2024-09-18 17:04:04 -05:00
use kittycad_modeling_cmds as kcmc ;
use kittycad_modeling_cmds ::shared ::PathSegment ;
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 } ,
2024-12-07 07:16:04 +13:00
execution ::{
2024-10-09 19:38:40 -04:00
BasePath , ExecState , Face , GeoMeta , KclValue , Path , Plane , Point2d , Point3d , Sketch , SketchSet , SketchSurface ,
2024-11-14 17:27:19 -06:00
Solid , TagEngineInfo , TagIdentifier ,
2023-10-05 14:27:48 -07:00
} ,
2024-12-05 17:56:49 +13:00
parsing ::ast ::types ::TagNode ,
2023-08-24 15:34:51 -07:00
std ::{
2024-02-11 15:08:54 -08:00
utils ::{
2025-01-04 12:18:29 -05:00
arc_angles , arc_center_and_end , calculate_circle_center , get_tangential_arc_to_info , get_x_component ,
get_y_component , intersection_with_parallel_line , TangentialArcInfoInput ,
2024-02-11 15:08:54 -08:00
} ,
2024-06-23 23:04:32 -07:00
Args ,
2023-08-24 15:34:51 -07:00
} ,
} ;
2024-06-23 23:04:32 -07:00
/// A tag for a face.
2024-07-27 22:56:46 -07:00
#[ derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema) ]
2024-06-23 23:04:32 -07:00
#[ ts(export) ]
#[ serde(rename_all = " snake_case " , untagged) ]
pub enum FaceTag {
StartOrEnd ( StartOrEnd ) ,
2024-06-24 14:45:07 -07:00
/// A tag for the face.
2024-07-27 22:56:46 -07:00
Tag ( Box < TagIdentifier > ) ,
}
impl std ::fmt ::Display for FaceTag {
fn fmt ( & self , f : & mut std ::fmt ::Formatter < '_ > ) -> std ::fmt ::Result {
match self {
FaceTag ::Tag ( t ) = > write! ( f , " {} " , t ) ,
FaceTag ::StartOrEnd ( StartOrEnd ::Start ) = > write! ( f , " start " ) ,
FaceTag ::StartOrEnd ( StartOrEnd ::End ) = > write! ( f , " end " ) ,
}
}
2024-06-23 23:04:32 -07:00
}
impl FaceTag {
/// Get the face id from the tag.
pub async fn get_face_id (
& self ,
2024-09-27 15:44:44 -07:00
solid : & Solid ,
2024-09-16 15:10:33 -04:00
exec_state : & mut ExecState ,
2024-06-23 23:04:32 -07:00
args : & Args ,
must_be_planar : bool ,
) -> Result < uuid ::Uuid , KclError > {
match self {
2024-09-16 15:10:33 -04:00
FaceTag ::Tag ( ref t ) = > args . get_adjacent_face_to_tag ( exec_state , t , must_be_planar ) . await ,
2024-09-27 15:44:44 -07:00
FaceTag ::StartOrEnd ( StartOrEnd ::Start ) = > solid . start_cap_id . ok_or_else ( | | {
2024-06-23 23:04:32 -07:00
KclError ::Type ( KclErrorDetails {
message : " Expected a start face " . to_string ( ) ,
source_ranges : vec ! [ args . source_range ] ,
} )
} ) ,
2024-09-27 15:44:44 -07:00
FaceTag ::StartOrEnd ( StartOrEnd ::End ) = > solid . end_cap_id . ok_or_else ( | | {
2024-06-23 23:04:32 -07:00
KclError ::Type ( KclErrorDetails {
message : " Expected an end face " . to_string ( ) ,
source_ranges : vec ! [ args . source_range ] ,
} )
} ) ,
}
}
}
#[ 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 ,
}
2023-08-24 15:34:51 -07:00
/// Draw a line to a point.
2024-10-09 19:38:40 -04:00
pub async fn line_to ( exec_state : & mut ExecState , args : Args ) -> Result < KclValue , KclError > {
2024-10-30 16:52:17 -04:00
let ( to , sketch , tag ) : ( [ f64 ; 2 ] , Sketch , Option < TagNode > ) = args . get_data_and_sketch_and_tag ( ) ? ;
2023-08-24 15:34:51 -07:00
2024-10-09 19:38:40 -04:00
let new_sketch = inner_line_to ( to , sketch , tag , exec_state , args ) . await ? ;
2024-11-14 17:27:19 -06:00
Ok ( KclValue ::Sketch {
value : Box ::new ( new_sketch ) ,
} )
2023-08-24 15:34:51 -07:00
}
2024-08-06 20:27:26 -04:00
/// Draw a line from the current origin to some absolute (x, y) point.
2024-03-13 12:56:46 -07:00
///
/// ```no_run
2024-12-12 11:33:37 -05:00
/// 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-12-12 11:33:37 -05:00
/// 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 ] ,
2024-09-27 15:44:44 -07:00
sketch : Sketch ,
2024-10-30 16:52:17 -04:00
tag : Option < TagNode > ,
2024-10-09 19:38:40 -04:00
exec_state : & mut ExecState ,
2023-09-20 18:27:08 -07:00
args : Args ,
2024-09-27 15:44:44 -07:00
) -> Result < Sketch , KclError > {
let from = sketch . current_pen_position ( ) ? ;
2024-12-17 09:38:32 +13:00
let id = exec_state . next_uuid ( ) ;
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 ,
2024-09-18 17:04:04 -05:00
ModelingCmd ::from ( mcmd ::ExtendPath {
2024-09-27 15:44:44 -07:00
path : sketch . id . into ( ) ,
2024-09-18 17:04:04 -05:00
segment : PathSegment ::Line {
end : KPoint2d ::from ( to ) . with_z ( 0.0 ) . map ( LengthUnit ) ,
2023-09-20 17:36:26 -07:00
relative : false ,
2023-08-31 22:19:23 -07:00
} ,
2024-09-18 17:04:04 -05: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-07-27 22:56:46 -07:00
tag : tag . clone ( ) ,
2023-08-24 15:34:51 -07:00
geo_meta : GeoMeta {
id ,
metadata : args . source_range . into ( ) ,
} ,
} ,
} ;
2024-09-27 15:44:44 -07:00
let mut new_sketch = sketch . clone ( ) ;
2024-07-27 22:56:46 -07:00
if let Some ( tag ) = & tag {
2024-09-27 15:44:44 -07:00
new_sketch . add_tag ( tag , & current_path ) ;
2024-07-27 22:56:46 -07:00
}
2024-10-23 12:42:54 -05:00
new_sketch . paths . push ( current_path ) ;
2023-08-24 15:34:51 -07:00
2024-09-27 15:44:44 -07:00
Ok ( new_sketch )
2023-08-24 15:34:51 -07:00
}
/// Draw a line to a point on the x-axis.
2024-10-09 19:38:40 -04:00
pub async fn x_line_to ( exec_state : & mut ExecState , args : Args ) -> Result < KclValue , KclError > {
2024-10-30 16:52:17 -04:00
let ( to , sketch , tag ) : ( f64 , Sketch , Option < TagNode > ) = args . get_data_and_sketch_and_tag ( ) ? ;
2023-08-25 13:41:04 -07:00
2024-10-09 19:38:40 -04:00
let new_sketch = inner_x_line_to ( to , sketch , tag , exec_state , args ) . await ? ;
2024-11-14 17:27:19 -06:00
Ok ( KclValue ::Sketch {
value : Box ::new ( new_sketch ) ,
} )
2023-08-25 13:41:04 -07:00
}
2024-08-05 23:26:19 -05:00
/// Draw a line parallel to the X axis, that ends at the given X.
/// E.g. if the previous line ended at (1, 1),
/// then xLineTo(4) draws a line from (1, 1) to (4, 1)
2024-03-13 12:56:46 -07:00
///
/// ```no_run
2024-12-12 11:33:37 -05:00
/// exampleSketch = startSketchOn('XZ')
2024-05-14 17:10:47 -07:00
/// |> startProfileAt([0, 0], %)
/// |> xLineTo(15, %)
/// |> angledLine({
2024-11-25 09:21:55 +13:00
/// angle = 80,
/// length = 15,
2024-05-14 17:10:47 -07:00
/// }, %)
/// |> line([8, -10], %)
/// |> xLineTo(40, %)
/// |> angledLine({
2024-11-25 09:21:55 +13:00
/// angle = 135,
/// length = 30,
2024-05-14 17:10:47 -07:00
/// }, %)
/// |> xLineTo(10, %)
/// |> close(%)
///
2024-12-12 11:33:37 -05:00
/// example = extrude(10, exampleSketch)
2024-03-13 12:56:46 -07:00
/// ```
2023-08-25 13:41:04 -07:00
#[ stdlib {
name = " xLineTo " ,
} ]
2024-10-09 19:38:40 -04:00
async fn inner_x_line_to (
to : f64 ,
sketch : Sketch ,
2024-10-30 16:52:17 -04:00
tag : Option < TagNode > ,
2024-10-09 19:38:40 -04:00
exec_state : & mut ExecState ,
args : Args ,
) -> Result < Sketch , KclError > {
2024-09-27 15:44:44 -07:00
let from = sketch . current_pen_position ( ) ? ;
2023-08-24 15:34:51 -07:00
2024-10-09 19:38:40 -04:00
let new_sketch = inner_line_to ( [ to , from . y ] , sketch , tag , exec_state , args ) . await ? ;
2023-08-25 13:41:04 -07:00
2024-09-27 15:44:44 -07:00
Ok ( new_sketch )
2023-08-24 15:34:51 -07:00
}
/// Draw a line to a point on the y-axis.
2024-10-09 19:38:40 -04:00
pub async fn y_line_to ( exec_state : & mut ExecState , args : Args ) -> Result < KclValue , KclError > {
2024-10-30 16:52:17 -04:00
let ( to , sketch , tag ) : ( f64 , Sketch , Option < TagNode > ) = args . get_data_and_sketch_and_tag ( ) ? ;
2023-08-25 13:41:04 -07:00
2024-10-09 19:38:40 -04:00
let new_sketch = inner_y_line_to ( to , sketch , tag , exec_state , args ) . await ? ;
2024-11-14 17:27:19 -06:00
Ok ( KclValue ::Sketch {
value : Box ::new ( new_sketch ) ,
} )
2023-08-25 13:41:04 -07:00
}
2024-08-05 23:26:19 -05:00
/// Draw a line parallel to the Y axis, that ends at the given Y.
/// E.g. if the previous line ended at (1, 1),
/// then yLineTo(4) draws a line from (1, 1) to (1, 4)
2024-03-13 12:56:46 -07:00
///
/// ```no_run
2024-12-12 11:33:37 -05:00
/// exampleSketch = startSketchOn("XZ")
2024-03-13 12:56:46 -07:00
/// |> startProfileAt([0, 0], %)
2024-05-22 09:15:38 -07:00
/// |> angledLine({
2024-11-25 09:21:55 +13:00
/// angle = 50,
/// length = 45,
2024-05-22 09:15:38 -07:00
/// }, %)
/// |> yLineTo(0, %)
/// |> close(%)
///
2024-12-12 11:33:37 -05:00
/// example = extrude(5, exampleSketch)
2024-03-13 12:56:46 -07:00
/// ```
2023-08-25 13:41:04 -07:00
#[ stdlib {
name = " yLineTo " ,
} ]
2024-10-09 19:38:40 -04:00
async fn inner_y_line_to (
to : f64 ,
sketch : Sketch ,
2024-10-30 16:52:17 -04:00
tag : Option < TagNode > ,
2024-10-09 19:38:40 -04:00
exec_state : & mut ExecState ,
args : Args ,
) -> Result < Sketch , KclError > {
2024-09-27 15:44:44 -07:00
let from = sketch . current_pen_position ( ) ? ;
2023-08-24 15:34:51 -07:00
2024-10-09 19:38:40 -04:00
let new_sketch = inner_line_to ( [ from . x , to ] , sketch , tag , exec_state , args ) . await ? ;
2024-09-27 15:44:44 -07:00
Ok ( new_sketch )
2023-08-24 15:34:51 -07:00
}
/// Draw a line.
2024-10-09 19:38:40 -04:00
pub async fn line ( exec_state : & mut ExecState , args : Args ) -> Result < KclValue , KclError > {
2024-10-30 16:52:17 -04:00
let ( delta , sketch , tag ) : ( [ f64 ; 2 ] , Sketch , Option < TagNode > ) = args . get_data_and_sketch_and_tag ( ) ? ;
2023-08-24 15:34:51 -07:00
2024-10-09 19:38:40 -04:00
let new_sketch = inner_line ( delta , sketch , tag , exec_state , args ) . await ? ;
2024-11-14 17:27:19 -06:00
Ok ( KclValue ::Sketch {
value : Box ::new ( new_sketch ) ,
} )
2023-08-24 15:34:51 -07:00
}
2024-08-06 20:27:26 -04:00
/// Draw a line relative to the current origin to a specified (x, y) away
/// from the current position.
2024-03-13 12:56:46 -07:00
///
/// ```no_run
2024-12-12 11:33:37 -05:00
/// exampleSketch = startSketchOn("XZ")
2024-05-22 09:15:38 -07:00
/// |> startProfileAt([0, 0], %)
/// |> line([25, 15], %)
/// |> line([5, -6], %)
/// |> line([-10, -10], %)
/// |> close(%)
///
2024-12-12 11:33:37 -05:00
/// example = extrude(5, exampleSketch)
2024-05-22 09:15:38 -07:00
/// ```
///
/// ```no_run
2024-12-12 11:33:37 -05:00
/// exampleSketch = startSketchOn("XZ")
2024-05-14 17:10:47 -07:00
/// |> startProfileAt([0, 0], %)
/// |> line([10, 0], %)
/// |> line([0, 10], %)
/// |> line([-10, 0], %)
/// |> close(%)
///
2024-12-12 11:33:37 -05:00
/// 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 ] ,
2024-09-27 15:44:44 -07:00
sketch : Sketch ,
2024-10-30 16:52:17 -04:00
tag : Option < TagNode > ,
2024-10-09 19:38:40 -04:00
exec_state : & mut ExecState ,
2024-03-15 17:03:42 -04:00
args : Args ,
2024-09-27 15:44:44 -07:00
) -> Result < Sketch , KclError > {
let from = sketch . 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
2024-12-17 09:38:32 +13:00
let id = exec_state . next_uuid ( ) ;
2023-08-24 15:34:51 -07:00
2024-06-19 13:57:50 -07:00
args . batch_modeling_cmd (
2023-08-24 15:34:51 -07:00
id ,
2024-09-18 17:04:04 -05:00
ModelingCmd ::from ( mcmd ::ExtendPath {
2024-09-27 15:44:44 -07:00
path : sketch . id . into ( ) ,
2024-09-18 17:04:04 -05:00
segment : PathSegment ::Line {
end : KPoint2d ::from ( delta ) . with_z ( 0.0 ) . map ( LengthUnit ) ,
2023-09-20 17:36:26 -07:00
relative : true ,
2023-08-24 15:34:51 -07:00
} ,
2024-09-18 17:04:04 -05: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-07-27 22:56:46 -07:00
tag : tag . clone ( ) ,
2023-08-24 15:34:51 -07:00
geo_meta : GeoMeta {
id ,
metadata : args . source_range . into ( ) ,
} ,
} ,
} ;
2024-09-27 15:44:44 -07:00
let mut new_sketch = sketch . clone ( ) ;
2024-07-27 22:56:46 -07:00
if let Some ( tag ) = & tag {
2024-09-27 15:44:44 -07:00
new_sketch . add_tag ( tag , & current_path ) ;
2024-07-27 22:56:46 -07:00
}
2024-10-23 12:42:54 -05:00
new_sketch . paths . push ( current_path ) ;
2023-08-24 15:34:51 -07:00
2024-09-27 15:44:44 -07:00
Ok ( new_sketch )
2023-08-24 15:34:51 -07:00
}
/// Draw a line on the x-axis.
2024-10-09 19:38:40 -04:00
pub async fn x_line ( exec_state : & mut ExecState , args : Args ) -> Result < KclValue , KclError > {
2024-10-30 16:52:17 -04:00
let ( length , sketch , tag ) : ( f64 , Sketch , Option < TagNode > ) = args . get_data_and_sketch_and_tag ( ) ? ;
2023-08-24 15:34:51 -07:00
2024-10-09 19:38:40 -04:00
let new_sketch = inner_x_line ( length , sketch , tag , exec_state , args ) . await ? ;
2024-11-14 17:27:19 -06:00
Ok ( KclValue ::Sketch {
value : Box ::new ( new_sketch ) ,
} )
2023-08-25 13:41:04 -07:00
}
2024-08-06 20:27:26 -04:00
/// Draw a line relative to the current origin to a specified distance away
/// from the current position along the 'x' axis.
2024-03-13 12:56:46 -07:00
///
/// ```no_run
2024-12-12 11:33:37 -05:00
/// exampleSketch = startSketchOn('XZ')
2024-05-14 17:10:47 -07:00
/// |> startProfileAt([0, 0], %)
/// |> xLine(15, %)
/// |> angledLine({
2024-11-25 09:21:55 +13:00
/// angle = 80,
/// length = 15,
2024-05-14 17:10:47 -07:00
/// }, %)
/// |> line([8, -10], %)
/// |> xLine(10, %)
/// |> angledLine({
2024-11-25 09:21:55 +13:00
/// angle = 120,
/// length = 30,
2024-05-14 17:10:47 -07:00
/// }, %)
/// |> xLine(-15, %)
/// |> close(%)
///
2024-12-12 11:33:37 -05:00
/// example = extrude(10, exampleSketch)
2024-03-13 12:56:46 -07:00
/// ```
2023-08-25 13:41:04 -07:00
#[ stdlib {
name = " xLine " ,
} ]
2024-10-09 19:38:40 -04:00
async fn inner_x_line (
length : f64 ,
sketch : Sketch ,
2024-10-30 16:52:17 -04:00
tag : Option < TagNode > ,
2024-10-09 19:38:40 -04:00
exec_state : & mut ExecState ,
args : Args ,
) -> Result < Sketch , KclError > {
inner_line ( [ length , 0.0 ] , sketch , tag , exec_state , args ) . await
2023-08-24 15:34:51 -07:00
}
/// Draw a line on the y-axis.
2024-10-09 19:38:40 -04:00
pub async fn y_line ( exec_state : & mut ExecState , args : Args ) -> Result < KclValue , KclError > {
2024-10-30 16:52:17 -04:00
let ( length , sketch , tag ) : ( f64 , Sketch , Option < TagNode > ) = args . get_data_and_sketch_and_tag ( ) ? ;
2023-08-24 15:34:51 -07:00
2024-10-09 19:38:40 -04:00
let new_sketch = inner_y_line ( length , sketch , tag , exec_state , args ) . await ? ;
2024-11-14 17:27:19 -06:00
Ok ( KclValue ::Sketch {
value : Box ::new ( new_sketch ) ,
} )
2023-08-25 13:41:04 -07:00
}
2024-08-06 20:27:26 -04:00
/// Draw a line relative to the current origin to a specified distance away
/// from the current position along the 'y' axis.
2024-03-13 12:56:46 -07:00
///
/// ```no_run
2024-12-12 11:33:37 -05:00
/// exampleSketch = startSketchOn('XZ')
2024-05-14 17:10:47 -07:00
/// |> startProfileAt([0, 0], %)
/// |> yLine(15, %)
/// |> angledLine({
2024-11-25 09:21:55 +13:00
/// angle = 30,
/// length = 15,
2024-05-14 17:10:47 -07:00
/// }, %)
/// |> line([8, -10], %)
/// |> yLine(-5, %)
/// |> close(%)
///
2024-12-12 11:33:37 -05:00
/// example = extrude(10, exampleSketch)
2024-03-13 12:56:46 -07:00
/// ```
2023-08-25 13:41:04 -07:00
#[ stdlib {
name = " yLine " ,
} ]
2024-10-09 19:38:40 -04:00
async fn inner_y_line (
length : f64 ,
sketch : Sketch ,
2024-10-30 16:52:17 -04:00
tag : Option < TagNode > ,
2024-10-09 19:38:40 -04:00
exec_state : & mut ExecState ,
args : Args ,
) -> Result < Sketch , KclError > {
inner_line ( [ 0.0 , length ] , sketch , tag , exec_state , 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 {
2024-07-28 22:45:40 -07:00
/// The angle of the line (in degrees).
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.
2024-10-09 19:38:40 -04:00
pub async fn angled_line ( exec_state : & mut ExecState , args : Args ) -> Result < KclValue , KclError > {
2024-10-30 16:52:17 -04:00
let ( data , sketch , tag ) : ( AngledLineData , Sketch , Option < TagNode > ) = args . get_data_and_sketch_and_tag ( ) ? ;
2023-08-24 15:34:51 -07:00
2024-10-09 19:38:40 -04:00
let new_sketch = inner_angled_line ( data , sketch , tag , exec_state , args ) . await ? ;
2024-11-14 17:27:19 -06:00
Ok ( KclValue ::Sketch {
value : Box ::new ( new_sketch ) ,
} )
2023-08-25 13:41:04 -07:00
}
2024-08-06 20:27:26 -04:00
/// Draw a line segment relative to the current origin using the polar
/// measure of some angle and distance.
2024-03-13 12:56:46 -07:00
///
/// ```no_run
2024-12-12 11:33:37 -05:00
/// 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-11-25 09:21:55 +13:00
/// angle = 30,
/// length = 15,
2024-05-14 17:10:47 -07:00
/// }, %)
/// |> line([8, -10], %)
/// |> yLineTo(0, %)
/// |> close(%)
///
2024-12-12 11:33:37 -05:00
/// 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 ,
2024-09-27 15:44:44 -07:00
sketch : Sketch ,
2024-10-30 16:52:17 -04:00
tag : Option < TagNode > ,
2024-10-09 19:38:40 -04:00
exec_state : & mut ExecState ,
2023-09-20 18:27:08 -07:00
args : Args ,
2024-09-27 15:44:44 -07:00
) -> Result < Sketch , KclError > {
let from = sketch . 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
2024-12-17 09:38:32 +13:00
let id = exec_state . next_uuid ( ) ;
2023-08-24 15:34:51 -07:00
2024-06-19 13:57:50 -07:00
args . batch_modeling_cmd (
2023-09-11 17:14:41 -07:00
id ,
2024-09-18 17:04:04 -05:00
ModelingCmd ::from ( mcmd ::ExtendPath {
2024-09-27 15:44:44 -07:00
path : sketch . id . into ( ) ,
2024-09-18 17:04:04 -05:00
segment : PathSegment ::Line {
end : KPoint2d ::from ( delta ) . with_z ( 0.0 ) . map ( LengthUnit ) ,
2023-09-20 17:36:26 -07:00
relative ,
2023-09-11 17:14:41 -07:00
} ,
2024-09-18 17:04:04 -05:00
} ) ,
2023-09-20 18:27:08 -07:00
)
. await ? ;
2023-09-11 17:14:41 -07:00
2024-07-05 16:53:13 -07:00
let current_path = Path ::ToPoint {
base : BasePath {
from : from . into ( ) ,
to ,
2024-07-27 22:56:46 -07:00
tag : tag . clone ( ) ,
2024-07-05 16:53:13 -07:00
geo_meta : GeoMeta {
id ,
metadata : args . source_range . into ( ) ,
} ,
} ,
} ;
2024-09-27 15:44:44 -07:00
let mut new_sketch = sketch . clone ( ) ;
2024-07-27 22:56:46 -07:00
if let Some ( tag ) = & tag {
2024-09-27 15:44:44 -07:00
new_sketch . add_tag ( tag , & current_path ) ;
2024-07-27 22:56:46 -07:00
}
2024-10-23 12:42:54 -05:00
new_sketch . paths . push ( current_path ) ;
2024-09-27 15:44:44 -07:00
Ok ( new_sketch )
2023-08-24 15:34:51 -07:00
}
/// Draw an angled line of a given x length.
2024-10-09 19:38:40 -04:00
pub async fn angled_line_of_x_length ( exec_state : & mut ExecState , args : Args ) -> Result < KclValue , KclError > {
2024-10-30 16:52:17 -04:00
let ( data , sketch , tag ) : ( AngledLineData , Sketch , Option < TagNode > ) = args . get_data_and_sketch_and_tag ( ) ? ;
2023-08-24 15:34:51 -07:00
2024-10-09 19:38:40 -04:00
let new_sketch = inner_angled_line_of_x_length ( data , sketch , tag , exec_state , args ) . await ? ;
2024-11-14 17:27:19 -06:00
Ok ( KclValue ::Sketch {
value : Box ::new ( new_sketch ) ,
} )
2023-08-25 13:41:04 -07:00
}
2024-08-06 20:27:26 -04:00
/// Create a line segment from the current 2-dimensional sketch origin
/// along some angle (in degrees) for some relative length in the 'x' dimension.
2024-03-13 12:56:46 -07:00
///
/// ```no_run
2024-12-12 11:33:37 -05:00
/// sketch001 = startSketchOn('XZ')
2024-03-15 17:03:42 -04:00
/// |> startProfileAt([0, 0], %)
2024-11-25 09:21:55 +13:00
/// |> angledLineOfXLength({ angle = 45, length = 10 }, %, $edge1)
/// |> angledLineOfXLength({ angle = -15, length = 20 }, %, $edge2)
2024-05-14 17:10:47 -07:00
/// |> line([0, -5], %)
2024-07-27 17:59:41 -07:00
/// |> close(%, $edge3)
2024-05-14 17:10:47 -07:00
///
2024-12-12 11:33:37 -05:00
/// 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 ,
2024-09-27 15:44:44 -07:00
sketch : Sketch ,
2024-10-30 16:52:17 -04:00
tag : Option < TagNode > ,
2024-10-09 19:38:40 -04:00
exec_state : & mut ExecState ,
2023-09-20 18:27:08 -07:00
args : Args ,
2024-09-27 15:44:44 -07:00
) -> Result < Sketch , 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
} ;
2024-07-28 20:36:18 -07:00
if angle . abs ( ) = = 270.0 {
return Err ( KclError ::Type ( KclErrorDetails {
message : " Cannot have an x constrained angle of 270 degrees " . to_string ( ) ,
source_ranges : vec ! [ args . source_range ] ,
} ) ) ;
}
if angle . abs ( ) = = 90.0 {
return Err ( KclError ::Type ( KclErrorDetails {
message : " Cannot have an x constrained angle of 90 degrees " . to_string ( ) ,
source_ranges : vec ! [ args . source_range ] ,
} ) ) ;
}
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-10-09 19:38:40 -04:00
let new_sketch = inner_line ( to . into ( ) , sketch , tag , exec_state , args ) . await ? ;
2023-08-24 15:34:51 -07:00
2024-09-27 15:44:44 -07:00
Ok ( new_sketch )
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.
2024-11-14 17:27:19 -06:00
pub angle : f64 ,
2024-03-15 17:03:42 -04:00
/// The point to draw to.
2024-11-14 17:27:19 -06:00
pub 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.
2024-10-09 19:38:40 -04:00
pub async fn angled_line_to_x ( exec_state : & mut ExecState , args : Args ) -> Result < KclValue , KclError > {
2024-10-30 16:52:17 -04:00
let ( data , sketch , tag ) : ( AngledLineToData , Sketch , Option < TagNode > ) = args . get_data_and_sketch_and_tag ( ) ? ;
2023-08-24 15:34:51 -07:00
2024-10-09 19:38:40 -04:00
let new_sketch = inner_angled_line_to_x ( data , sketch , tag , exec_state , args ) . await ? ;
2024-11-14 17:27:19 -06:00
Ok ( KclValue ::Sketch {
value : Box ::new ( new_sketch ) ,
} )
2023-08-25 13:41:04 -07:00
}
2024-08-06 20:27:26 -04:00
/// Create a line segment from the current 2-dimensional sketch origin
/// along some angle (in degrees) for some length, ending at the provided value
/// in the 'x' dimension.
2024-03-13 12:56:46 -07:00
///
/// ```no_run
2024-12-12 11:33:37 -05:00
/// exampleSketch = startSketchOn('XZ')
2024-03-15 17:03:42 -04:00
/// |> startProfileAt([0, 0], %)
2024-11-25 09:21:55 +13: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(%)
///
2024-12-12 11:33:37 -05:00
/// 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 ,
2024-09-27 15:44:44 -07:00
sketch : Sketch ,
2024-10-30 16:52:17 -04:00
tag : Option < TagNode > ,
2024-10-09 19:38:40 -04:00
exec_state : & mut ExecState ,
2023-09-20 18:27:08 -07:00
args : Args ,
2024-09-27 15:44:44 -07:00
) -> Result < Sketch , KclError > {
let from = sketch . 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
2024-07-28 20:36:18 -07:00
if angle . abs ( ) = = 270.0 {
return Err ( KclError ::Type ( KclErrorDetails {
message : " Cannot have an x constrained angle of 270 degrees " . to_string ( ) ,
source_ranges : vec ! [ args . source_range ] ,
} ) ) ;
}
if angle . abs ( ) = = 90.0 {
return Err ( KclError ::Type ( KclErrorDetails {
message : " Cannot have an x constrained angle of 90 degrees " . to_string ( ) ,
source_ranges : vec ! [ args . source_range ] ,
} ) ) ;
}
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-10-09 19:38:40 -04:00
let new_sketch = inner_line_to ( [ x_to , y_to ] , sketch , tag , exec_state , args ) . await ? ;
2024-09-27 15:44:44 -07:00
Ok ( new_sketch )
2023-08-24 15:34:51 -07:00
}
/// Draw an angled line of a given y length.
2024-10-09 19:38:40 -04:00
pub async fn angled_line_of_y_length ( exec_state : & mut ExecState , args : Args ) -> Result < KclValue , KclError > {
2024-10-30 16:52:17 -04:00
let ( data , sketch , tag ) : ( AngledLineData , Sketch , Option < TagNode > ) = args . get_data_and_sketch_and_tag ( ) ? ;
2023-08-24 15:34:51 -07:00
2024-10-09 19:38:40 -04:00
let new_sketch = inner_angled_line_of_y_length ( data , sketch , tag , exec_state , args ) . await ? ;
2023-08-25 13:41:04 -07:00
2024-11-14 17:27:19 -06:00
Ok ( KclValue ::Sketch {
value : Box ::new ( new_sketch ) ,
} )
2023-08-25 13:41:04 -07:00
}
2024-08-06 20:27:26 -04:00
/// Create a line segment from the current 2-dimensional sketch origin
/// along some angle (in degrees) for some relative length in the 'y' dimension.
2024-03-13 12:56:46 -07:00
///
/// ```no_run
2024-12-12 11:33:37 -05:00
/// 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], %)
2024-11-25 09:21:55 +13:00
/// |> angledLineOfYLength({ angle = 45, length = 10 }, %)
2024-03-13 12:56:46 -07:00
/// |> line([0, 10], %)
2024-11-25 09:21:55 +13:00
/// |> angledLineOfYLength({ angle = 135, length = 10 }, %)
2024-05-14 17:10:47 -07:00
/// |> line([-10, 0], %)
/// |> line([0, -30], %)
///
2024-12-12 11:33:37 -05:00
/// 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 ,
2024-09-27 15:44:44 -07:00
sketch : Sketch ,
2024-10-30 16:52:17 -04:00
tag : Option < TagNode > ,
2024-10-09 19:38:40 -04:00
exec_state : & mut ExecState ,
2023-09-20 18:27:08 -07:00
args : Args ,
2024-09-27 15:44:44 -07:00
) -> Result < Sketch , 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
} ;
2024-07-28 20:36:18 -07:00
if angle . abs ( ) = = 0.0 {
return Err ( KclError ::Type ( KclErrorDetails {
message : " Cannot have a y constrained angle of 0 degrees " . to_string ( ) ,
source_ranges : vec ! [ args . source_range ] ,
} ) ) ;
}
if angle . abs ( ) = = 180.0 {
return Err ( KclError ::Type ( KclErrorDetails {
message : " Cannot have a y constrained angle of 180 degrees " . to_string ( ) ,
source_ranges : vec ! [ args . source_range ] ,
} ) ) ;
}
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-10-09 19:38:40 -04:00
let new_sketch = inner_line ( to . into ( ) , sketch , tag , exec_state , args ) . await ? ;
2023-08-24 15:34:51 -07:00
2024-09-27 15:44:44 -07:00
Ok ( new_sketch )
2023-08-24 15:34:51 -07:00
}
/// Draw an angled line to a given y coordinate.
2024-10-09 19:38:40 -04:00
pub async fn angled_line_to_y ( exec_state : & mut ExecState , args : Args ) -> Result < KclValue , KclError > {
2024-10-30 16:52:17 -04:00
let ( data , sketch , tag ) : ( AngledLineToData , Sketch , Option < TagNode > ) = args . get_data_and_sketch_and_tag ( ) ? ;
2023-08-24 15:34:51 -07:00
2024-10-09 19:38:40 -04:00
let new_sketch = inner_angled_line_to_y ( data , sketch , tag , exec_state , args ) . await ? ;
2024-11-14 17:27:19 -06:00
Ok ( KclValue ::Sketch {
value : Box ::new ( new_sketch ) ,
} )
2023-08-25 13:41:04 -07:00
}
2024-08-06 20:27:26 -04:00
/// Create a line segment from the current 2-dimensional sketch origin
/// along some angle (in degrees) for some length, ending at the provided value
/// in the 'y' dimension.
2024-03-13 12:56:46 -07:00
///
/// ```no_run
2024-12-12 11:33:37 -05:00
/// exampleSketch = startSketchOn('XZ')
2024-03-15 17:03:42 -04:00
/// |> startProfileAt([0, 0], %)
2024-11-25 09:21:55 +13:00
/// |> angledLineToY({ angle = 60, to = 20 }, %)
2024-05-14 17:10:47 -07:00
/// |> line([-20, 0], %)
2024-11-25 09:21:55 +13:00
/// |> angledLineToY({ angle = 70, to = 10 }, %)
2024-05-14 17:10:47 -07:00
/// |> close(%)
///
2024-12-12 11:33:37 -05:00
/// 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 ,
2024-09-27 15:44:44 -07:00
sketch : Sketch ,
2024-10-30 16:52:17 -04:00
tag : Option < TagNode > ,
2024-10-09 19:38:40 -04:00
exec_state : & mut ExecState ,
2023-09-20 18:27:08 -07:00
args : Args ,
2024-09-27 15:44:44 -07:00
) -> Result < Sketch , KclError > {
let from = sketch . 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
2024-07-28 20:36:18 -07:00
if angle . abs ( ) = = 0.0 {
return Err ( KclError ::Type ( KclErrorDetails {
message : " Cannot have a y constrained angle of 0 degrees " . to_string ( ) ,
source_ranges : vec ! [ args . source_range ] ,
} ) ) ;
}
if angle . abs ( ) = = 180.0 {
return Err ( KclError ::Type ( KclErrorDetails {
message : " Cannot have a y constrained angle of 180 degrees " . to_string ( ) ,
source_ranges : vec ! [ args . source_range ] ,
} ) ) ;
}
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-10-09 19:38:40 -04:00
let new_sketch = inner_line_to ( [ x_to , y_to ] , sketch , tag , exec_state , args ) . await ? ;
2024-09-27 15:44:44 -07:00
Ok ( new_sketch )
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.
2024-06-24 14:45:07 -07:00
pub intersect_tag : TagIdentifier ,
2023-08-24 15:34:51 -07:00
/// The offset from the intersecting line.
pub offset : Option < f64 > ,
}
/// Draw an angled line that intersects with a given line.
2024-09-16 15:10:33 -04:00
pub async fn angled_line_that_intersects ( exec_state : & mut ExecState , args : Args ) -> Result < KclValue , KclError > {
2024-10-30 16:52:17 -04:00
let ( data , sketch , tag ) : ( AngledLineThatIntersectsData , Sketch , Option < TagNode > ) =
2024-09-27 15:44:44 -07:00
args . get_data_and_sketch_and_tag ( ) ? ;
let new_sketch = inner_angled_line_that_intersects ( data , sketch , tag , exec_state , args ) . await ? ;
2024-11-14 17:27:19 -06:00
Ok ( KclValue ::Sketch {
value : Box ::new ( new_sketch ) ,
} )
2023-08-25 13:41:04 -07:00
}
2024-08-06 20:27:26 -04:00
/// Draw an angled line from the current origin, constructing a line segment
/// such that the newly created line intersects the desired target line
/// segment.
2024-03-13 12:56:46 -07:00
///
/// ```no_run
2024-12-12 11:33:37 -05:00
/// 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], %)
2024-07-27 17:59:41 -07:00
/// |> lineTo([-10, 10], %, $lineToIntersect)
2024-05-14 17:10:47 -07:00
/// |> lineTo([0, 20], %)
2024-03-15 17:03:42 -04:00
/// |> angledLineThatIntersects({
2024-11-25 09:21:55 +13:00
/// angle = 80,
/// intersectTag = lineToIntersect,
/// offset = 10
2024-05-14 17:10:47 -07:00
/// }, %)
/// |> close(%)
///
2024-12-12 11:33:37 -05:00
/// 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 ,
2024-09-27 15:44:44 -07:00
sketch : Sketch ,
2024-10-30 16:52:17 -04:00
tag : Option < TagNode > ,
2024-09-16 15:10:33 -04:00
exec_state : & mut ExecState ,
2023-09-20 18:27:08 -07:00
args : Args ,
2024-09-27 15:44:44 -07:00
) -> Result < Sketch , KclError > {
2024-09-16 15:10:33 -04:00
let intersect_path = args . get_tag_engine_info ( exec_state , & data . intersect_tag ) ? ;
2024-10-28 20:20:45 -04:00
let path = intersect_path . path . clone ( ) . ok_or_else ( | | {
2024-07-28 00:30:04 -07:00
KclError ::Type ( KclErrorDetails {
message : format ! ( " Expected an intersect path with a path, found `{:?}` " , intersect_path ) ,
source_ranges : vec ! [ args . source_range ] ,
} )
} ) ? ;
2023-08-24 15:34:51 -07:00
2024-09-27 15:44:44 -07:00
let from = sketch . current_pen_position ( ) ? ;
2023-08-24 15:34:51 -07:00
let to = intersection_with_parallel_line (
2024-10-25 09:27:40 -05:00
& [ path . get_from ( ) . into ( ) , path . get_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-10-09 19:38:40 -04:00
let new_sketch = inner_line_to ( to . into ( ) , sketch , tag , exec_state , args ) . await ? ;
2024-09-27 15:44:44 -07:00
Ok ( new_sketch )
2023-08-24 15:34:51 -07:00
}
/// Start a sketch at a given point.
2024-09-16 15:10:33 -04:00
pub async fn start_sketch_at ( exec_state : & mut ExecState , args : Args ) -> Result < KclValue , KclError > {
2024-03-15 17:03:42 -04:00
let data : [ f64 ; 2 ] = args . get_data ( ) ? ;
2023-08-24 15:34:51 -07:00
2024-09-27 15:44:44 -07:00
let sketch = inner_start_sketch_at ( data , exec_state , args ) . await ? ;
2024-11-14 17:27:19 -06:00
Ok ( KclValue ::Sketch {
value : Box ::new ( sketch ) ,
} )
2023-08-25 13:41:04 -07:00
}
2024-08-06 20:27:26 -04:00
/// Start a new 2-dimensional sketch at a given point on the 'XY' plane.
2024-03-13 12:56:46 -07:00
///
/// ```no_run
2024-12-12 11:33:37 -05:00
/// exampleSketch = startSketchAt([0, 0])
2024-05-14 17:10:47 -07:00
/// |> line([10, 0], %)
/// |> line([0, 10], %)
/// |> line([-10, 0], %)
/// |> close(%)
///
2024-12-12 11:33:37 -05:00
/// example = extrude(5, exampleSketch)
2024-05-14 17:10:47 -07:00
/// ```
///
/// ```no_run
2024-12-12 11:33:37 -05:00
/// exampleSketch = startSketchAt([10, 10])
2024-05-14 17:10:47 -07:00
/// |> line([10, 0], %)
/// |> line([0, 10], %)
/// |> line([-10, 0], %)
/// |> close(%)
///
2024-12-12 11:33:37 -05:00
/// example = extrude(5, exampleSketch)
2024-05-14 17:10:47 -07:00
/// ```
///
/// ```no_run
2024-12-12 11:33:37 -05:00
/// exampleSketch = startSketchAt([-10, 23])
2024-05-14 17:10:47 -07:00
/// |> line([10, 0], %)
/// |> line([0, 10], %)
/// |> line([-10, 0], %)
/// |> close(%)
///
2024-12-12 11:33:37 -05:00
/// example = extrude(5, exampleSketch)
2024-03-13 12:56:46 -07:00
/// ```
2023-08-25 13:41:04 -07:00
#[ stdlib {
name = " startSketchAt " ,
2024-12-17 11:28:22 -05:00
deprecated = true ,
2023-08-25 13:41:04 -07:00
} ]
2024-09-27 15:44:44 -07:00
async fn inner_start_sketch_at ( data : [ f64 ; 2 ] , exec_state : & mut ExecState , args : Args ) -> Result < Sketch , 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-11-18 16:25:25 -05:00
let sketch_surface = inner_start_sketch_on ( SketchData ::PlaneOrientation ( xy_plane ) , None , exec_state , & args ) . await ? ;
2024-09-27 15:44:44 -07:00
let sketch = inner_start_profile_at ( data , sketch_surface , None , exec_state , args ) . await ? ;
Ok ( sketch )
2023-10-05 14:27:48 -07:00
}
2024-02-12 18:08:42 -08:00
/// Data for start sketch on.
2024-09-27 15:44:44 -07:00
/// You can start a sketch on a plane or an solid.
2024-02-12 18:08:42 -08:00
#[ derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema) ]
#[ ts(export) ]
#[ serde(rename_all = " camelCase " , untagged) ]
pub enum SketchData {
2024-11-18 16:25:25 -05:00
PlaneOrientation ( PlaneData ) ,
Plane ( Box < Plane > ) ,
2024-09-27 15:44:44 -07:00
Solid ( Box < Solid > ) ,
2024-02-12 18:08:42 -08:00
}
2024-11-18 16:25:25 -05:00
/// Orientation data that can be used to construct a plane, not a plane in itself.
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?
2025-01-05 17:49:16 -08:00
#[ serde(rename = " xAxis " ) ]
2023-10-05 14:27:48 -07:00
x_axis : Box < Point3d > ,
/// What should the plane’ s Y axis be?
2025-01-05 17:49:16 -08:00
#[ serde(rename = " yAxis " ) ]
2023-10-05 14:27:48 -07:00
y_axis : Box < Point3d > ,
/// The z-axis (normal).
2025-01-05 17:49:16 -08:00
#[ serde(rename = " zAxis " ) ]
2023-10-05 14:27:48 -07:00
z_axis : Box < Point3d > ,
} ,
}
2024-02-12 18:08:42 -08:00
/// Start a sketch on a specific plane or face.
2024-09-16 15:10:33 -04:00
pub async fn start_sketch_on ( exec_state : & mut ExecState , args : Args ) -> Result < KclValue , KclError > {
2024-06-23 23:04:32 -07:00
let ( data , tag ) : ( SketchData , Option < FaceTag > ) = args . get_data_and_optional_tag ( ) ? ;
2023-10-05 14:27:48 -07:00
2024-09-16 15:10:33 -04:00
match inner_start_sketch_on ( data , tag , exec_state , & args ) . await ? {
2024-08-12 16:53:24 -05:00
SketchSurface ::Plane ( plane ) = > Ok ( KclValue ::Plane ( plane ) ) ,
SketchSurface ::Face ( face ) = > Ok ( KclValue ::Face ( face ) ) ,
2024-02-12 18:08:42 -08:00
}
2023-10-05 14:27:48 -07:00
}
2024-08-06 20:27:26 -04:00
/// Start a new 2-dimensional sketch on a specific plane or face.
2024-03-13 12:56:46 -07:00
///
/// ```no_run
2024-12-12 11:33:37 -05:00
/// exampleSketch = startSketchOn("XY")
2024-05-14 17:10:47 -07:00
/// |> startProfileAt([0, 0], %)
/// |> line([10, 0], %)
/// |> line([0, 10], %)
/// |> line([-10, 0], %)
/// |> close(%)
///
2024-12-12 11:33:37 -05:00
/// example = extrude(5, exampleSketch)
2024-05-14 17:10:47 -07:00
///
2024-12-12 11:33:37 -05:00
/// exampleSketch002 = startSketchOn(example, 'end')
2024-05-14 17:10:47 -07:00
/// |> startProfileAt([1, 1], %)
/// |> line([8, 0], %)
/// |> line([0, 8], %)
/// |> line([-8, 0], %)
/// |> close(%)
///
2024-12-12 11:33:37 -05:00
/// example002 = extrude(5, exampleSketch002)
2024-05-14 17:10:47 -07:00
///
2024-12-12 11:33:37 -05:00
/// exampleSketch003 = startSketchOn(example002, 'end')
2024-05-14 17:10:47 -07:00
/// |> startProfileAt([2, 2], %)
/// |> line([6, 0], %)
/// |> line([0, 6], %)
/// |> line([-6, 0], %)
/// |> close(%)
///
2024-12-12 11:33:37 -05:00
/// example003 = extrude(5, exampleSketch003)
2024-03-13 12:56:46 -07:00
/// ```
///
/// ```no_run
2024-12-12 11:33:37 -05:00
/// exampleSketch = startSketchOn("XY")
2024-05-14 17:10:47 -07:00
/// |> startProfileAt([0, 0], %)
/// |> line([10, 0], %)
2024-07-27 17:59:41 -07:00
/// |> line([0, 10], %, $sketchingFace)
2024-05-14 17:10:47 -07:00
/// |> line([-10, 0], %)
/// |> close(%)
2024-03-13 12:56:46 -07:00
///
2024-12-12 11:33:37 -05:00
/// example = extrude(10, exampleSketch)
2024-03-13 12:56:46 -07:00
///
2024-12-12 11:33:37 -05:00
/// exampleSketch002 = startSketchOn(example, sketchingFace)
2024-05-14 17:10:47 -07:00
/// |> startProfileAt([1, 1], %)
/// |> line([8, 0], %)
/// |> line([0, 8], %)
/// |> line([-8, 0], %)
2024-07-27 17:59:41 -07:00
/// |> close(%, $sketchingFace002)
2024-03-13 12:56:46 -07:00
///
2024-12-12 11:33:37 -05:00
/// example002 = extrude(10, exampleSketch002)
2024-05-14 17:10:47 -07:00
///
2024-12-12 11:33:37 -05:00
/// exampleSketch003 = startSketchOn(example002, sketchingFace002)
2024-05-14 17:10:47 -07:00
/// |> startProfileAt([-8, 12], %)
/// |> line([0, 6], %)
/// |> line([6, 0], %)
/// |> line([0, -6], %)
/// |> close(%)
///
2024-12-12 11:33:37 -05:00
/// example003 = extrude(5, exampleSketch003)
2024-05-14 17:10:47 -07:00
/// ```
///
/// ```no_run
2024-12-12 11:33:37 -05:00
/// exampleSketch = startSketchOn('XY')
2024-05-14 17:10:47 -07:00
/// |> 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(%)
///
2024-12-12 11:33:37 -05:00
/// example = revolve({ axis: 'y', angle: 180 }, exampleSketch)
2024-05-14 17:10:47 -07:00
///
2024-12-12 11:33:37 -05:00
/// exampleSketch002 = startSketchOn(example, 'end')
2024-05-14 17:10:47 -07:00
/// |> startProfileAt([4.5, -5], %)
/// |> line([0, 5], %)
/// |> line([5, 0], %)
/// |> line([0, -5], %)
/// |> close(%)
///
2024-12-12 11:33:37 -05:00
/// example002 = extrude(5, exampleSketch002)
2024-03-13 12:56:46 -07:00
/// ```
2024-05-23 19:28:13 -07:00
///
/// ```no_run
2024-12-12 11:33:37 -05:00
/// a1 = startSketchOn({
2024-05-23 19:28:13 -07:00
/// plane: {
2024-11-25 09:21:55 +13:00
/// origin = { x = 0, y = 0, z = 0 },
/// xAxis = { x = 1, y = 0, z = 0 },
/// yAxis = { x = 0, y = 1, z = 0 },
/// zAxis = { x = 0, y = 0, z = 1 }
2024-05-23 19:28:13 -07:00
/// }
/// })
/// |> 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-12-16 13:10:31 -05:00
feature_tree_operation = true ,
2023-10-05 14:27:48 -07:00
} ]
2024-09-16 15:10:33 -04:00
async fn inner_start_sketch_on (
data : SketchData ,
tag : Option < FaceTag > ,
exec_state : & mut ExecState ,
args : & Args ,
) -> Result < SketchSurface , KclError > {
2024-02-12 18:08:42 -08:00
match data {
2024-11-18 16:25:25 -05:00
SketchData ::PlaneOrientation ( plane_data ) = > {
let plane = make_sketch_plane_from_orientation ( plane_data , exec_state , args ) . await ? ;
2024-02-12 18:08:42 -08:00
Ok ( SketchSurface ::Plane ( plane ) )
}
2024-11-18 16:25:25 -05:00
SketchData ::Plane ( plane ) = > Ok ( SketchSurface ::Plane ( plane ) ) ,
2024-09-27 15:44:44 -07:00
SketchData ::Solid ( solid ) = > {
2024-02-12 18:08:42 -08:00
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-09-27 15:44:44 -07:00
let face = start_sketch_on_face ( solid , tag , exec_state , args ) . await ? ;
2024-02-12 18:08:42 -08:00
Ok ( SketchSurface ::Face ( face ) )
}
}
}
2024-02-13 10:26:09 -08:00
async fn start_sketch_on_face (
2024-09-27 15:44:44 -07:00
solid : Box < Solid > ,
2024-06-23 23:04:32 -07:00
tag : FaceTag ,
2024-09-16 15:10:33 -04:00
exec_state : & mut ExecState ,
args : & Args ,
2024-02-13 10:26:09 -08:00
) -> Result < Box < Face > , KclError > {
2024-09-27 15:44:44 -07:00
let extrude_plane_id = tag . get_face_id ( & solid , exec_state , args , true ) . await ? ;
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 ( ) ,
// TODO: get this from the extrude plane data.
2024-09-27 15:44:44 -07:00
x_axis : solid . sketch . on . x_axis ( ) ,
y_axis : solid . sketch . on . y_axis ( ) ,
z_axis : solid . sketch . on . z_axis ( ) ,
solid ,
2024-02-12 18:08:42 -08:00
meta : vec ! [ args . source_range . into ( ) ] ,
} ) )
}
2024-11-18 16:25:25 -05:00
async fn make_sketch_plane_from_orientation (
2024-10-09 19:38:40 -04:00
data : PlaneData ,
exec_state : & mut ExecState ,
args : & Args ,
) -> Result < Box < Plane > , KclError > {
2024-12-04 16:06:02 -05:00
let plane = Plane ::from_plane_data ( data . clone ( ) , exec_state ) ;
// Create the plane on the fly.
let clobber = false ;
let size = LengthUnit ( 60.0 ) ;
let hide = Some ( true ) ;
match data {
PlaneData ::XY | PlaneData ::NegXY | PlaneData ::XZ | PlaneData ::NegXZ | PlaneData ::YZ | PlaneData ::NegYZ = > {
let x_axis = match data {
PlaneData ::NegXY = > Point3d ::new ( - 1.0 , 0.0 , 0.0 ) ,
PlaneData ::NegXZ = > Point3d ::new ( - 1.0 , 0.0 , 0.0 ) ,
PlaneData ::NegYZ = > Point3d ::new ( 0.0 , - 1.0 , 0.0 ) ,
_ = > plane . x_axis ,
} ;
args . batch_modeling_cmd (
plane . id ,
ModelingCmd ::from ( mcmd ::MakePlane {
clobber ,
origin : plane . origin . into ( ) ,
size ,
x_axis : x_axis . into ( ) ,
y_axis : plane . y_axis . into ( ) ,
hide ,
} ) ,
)
. await ? ;
}
2023-10-05 14:27:48 -07:00
PlaneData ::Plane {
origin ,
x_axis ,
y_axis ,
z_axis : _ ,
} = > {
2024-06-19 13:57:50 -07:00
args . batch_modeling_cmd (
2024-12-04 16:06:02 -05:00
plane . id ,
2024-09-18 17:04:04 -05:00
ModelingCmd ::from ( mcmd ::MakePlane {
2024-12-04 16:06:02 -05:00
clobber ,
2023-10-05 14:27:48 -07:00
origin : ( * origin ) . into ( ) ,
2024-12-04 16:06:02 -05:00
size ,
2023-10-05 14:27:48 -07:00
x_axis : ( * x_axis ) . into ( ) ,
y_axis : ( * y_axis ) . into ( ) ,
2024-12-04 16:06:02 -05:00
hide ,
2024-09-18 17:04:04 -05:00
} ) ,
2023-10-05 14:27:48 -07:00
)
. await ? ;
2024-02-11 12:59:00 +11:00
}
2024-12-04 16:06:02 -05:00
}
2023-10-05 14:27:48 -07:00
Ok ( Box ::new ( plane ) )
}
2024-08-06 20:27:26 -04:00
/// Start a new profile at a given point.
2024-09-16 15:10:33 -04:00
pub async fn start_profile_at ( exec_state : & mut ExecState , args : Args ) -> Result < KclValue , KclError > {
2024-10-30 16:52:17 -04:00
let ( start , sketch_surface , tag ) : ( [ f64 ; 2 ] , SketchSurface , Option < TagNode > ) =
2024-06-24 14:45:07 -07:00
args . get_data_and_sketch_surface ( ) ? ;
2023-10-05 14:27:48 -07:00
2024-09-27 15:44:44 -07:00
let sketch = inner_start_profile_at ( start , sketch_surface , tag , exec_state , args ) . await ? ;
2024-11-14 17:27:19 -06:00
Ok ( KclValue ::Sketch {
value : Box ::new ( sketch ) ,
} )
2023-10-05 14:27:48 -07:00
}
2024-08-06 20:27:26 -04:00
/// Start a new profile at a given point.
2024-03-13 12:56:46 -07:00
///
/// ```no_run
2024-12-12 11:33:37 -05:00
/// exampleSketch = startSketchOn('XZ')
2024-05-14 17:10:47 -07:00
/// |> startProfileAt([0, 0], %)
/// |> line([10, 0], %)
/// |> line([0, 10], %)
/// |> line([-10, 0], %)
/// |> close(%)
///
2024-12-12 11:33:37 -05:00
/// example = extrude(5, exampleSketch)
2024-05-14 17:10:47 -07:00
/// ```
///
/// ```no_run
2024-12-12 11:33:37 -05:00
/// exampleSketch = startSketchOn('-XZ')
2024-05-14 17:10:47 -07:00
/// |> startProfileAt([10, 10], %)
/// |> line([10, 0], %)
/// |> line([0, 10], %)
/// |> line([-10, 0], %)
/// |> close(%)
///
2024-12-12 11:33:37 -05:00
/// example = extrude(5, exampleSketch)
2024-05-14 17:10:47 -07:00
/// ```
///
/// ```no_run
2024-12-12 11:33:37 -05:00
/// exampleSketch = startSketchOn('-XZ')
2024-05-14 17:10:47 -07:00
/// |> startProfileAt([-10, 23], %)
/// |> line([10, 0], %)
/// |> line([0, 10], %)
/// |> line([-10, 0], %)
/// |> close(%)
///
2024-12-12 11:33:37 -05:00
/// 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-10-30 16:52:17 -04:00
tag : Option < TagNode > ,
2024-09-16 15:10:33 -04:00
exec_state : & mut ExecState ,
2024-02-12 18:08:42 -08:00
args : Args ,
2024-09-27 15:44:44 -07:00
) -> Result < Sketch , KclError > {
2024-11-18 16:25:25 -05:00
match & sketch_surface {
SketchSurface ::Face ( face ) = > {
// Flush the batch for our fillets/chamfers if there are any.
// If we do not do these for sketch on face, things will fail with face does not exist.
args . flush_batch_for_solid_set ( exec_state , face . solid . clone ( ) . into ( ) )
. await ? ;
}
SketchSurface ::Plane ( plane ) if ! plane . is_standard ( ) = > {
// Hide whatever plane we are sketching on.
// This is especially helpful for offset planes, which would be visible otherwise.
args . batch_end_cmd (
2024-12-17 09:38:32 +13:00
exec_state . next_uuid ( ) ,
2024-11-18 16:25:25 -05:00
ModelingCmd ::from ( mcmd ::ObjectVisible {
object_id : plane . id ,
hidden : true ,
} ) ,
)
2024-06-23 19:19:24 -07:00
. await ? ;
2024-11-18 16:25:25 -05:00
}
_ = > { }
2024-06-22 14:31:37 -07:00
}
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.
2024-12-17 09:38:32 +13:00
let id = exec_state . next_uuid ( ) ;
2024-06-21 19:54:18 -07:00
args . batch_modeling_cmd (
id ,
2024-09-18 17:04:04 -05:00
ModelingCmd ::from ( mcmd ::EnableSketchMode {
2024-06-21 19:54:18 -07:00
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.
2024-09-04 23:27:12 -05:00
Some ( plane . z_axis . into ( ) )
2024-06-21 19:54:18 -07:00
} else {
None
} ,
2024-09-18 17:04:04 -05:00
} ) ,
2024-06-21 19:54:18 -07:00
)
. await ? ;
2024-12-17 09:38:32 +13:00
let id = exec_state . next_uuid ( ) ;
let path_id = exec_state . next_uuid ( ) ;
2023-08-24 15:34:51 -07:00
2024-09-18 17:04:04 -05:00
args . batch_modeling_cmd ( path_id , ModelingCmd ::from ( mcmd ::StartPath { } ) )
. await ? ;
2024-06-19 13:57:50 -07:00
args . batch_modeling_cmd (
2023-08-24 15:34:51 -07:00
id ,
2024-09-18 17:04:04 -05:00
ModelingCmd ::from ( mcmd ::MovePathPen {
path : path_id . into ( ) ,
to : KPoint2d ::from ( to ) . with_z ( 0.0 ) . map ( LengthUnit ) ,
} ) ,
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-07-05 16:53:13 -07:00
tag : tag . clone ( ) ,
2023-08-24 15:34:51 -07:00
geo_meta : GeoMeta {
id ,
metadata : args . source_range . into ( ) ,
} ,
} ;
2024-09-27 15:44:44 -07:00
let sketch = Sketch {
2023-08-24 15:34:51 -07:00
id : path_id ,
2024-07-29 21:30:25 -07:00
original_id : path_id ,
2024-02-16 16:42:01 -08:00
on : sketch_surface . clone ( ) ,
2024-10-23 12:42:54 -05:00
paths : vec ! [ ] ,
2023-08-24 15:34:51 -07:00
meta : vec ! [ args . source_range . into ( ) ] ,
2024-07-05 16:53:13 -07:00
tags : if let Some ( tag ) = & tag {
2024-07-27 22:56:46 -07:00
let mut tag_identifier : TagIdentifier = tag . into ( ) ;
tag_identifier . info = Some ( TagEngineInfo {
id : current_path . geo_meta . id ,
2024-09-27 15:44:44 -07:00
sketch : path_id ,
2024-10-28 20:20:45 -04:00
path : Some ( Path ::Base {
2024-10-25 09:27:40 -05:00
base : current_path . clone ( ) ,
} ) ,
2024-10-28 20:20:45 -04:00
surface : None ,
2024-07-27 22:56:46 -07:00
} ) ;
2024-12-05 12:09:35 -05:00
IndexMap ::from ( [ ( tag . name . to_string ( ) , tag_identifier ) ] )
2024-07-05 16:53:13 -07:00
} else {
Default ::default ( )
} ,
2024-07-27 22:56:46 -07:00
start : current_path ,
2023-08-24 15:34:51 -07:00
} ;
2024-09-27 15:44:44 -07:00
Ok ( sketch )
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.
2024-09-16 15:10:33 -04:00
pub async fn profile_start_x ( _exec_state : & mut ExecState , args : Args ) -> Result < KclValue , KclError > {
2024-09-27 15:44:44 -07:00
let sketch : Sketch = args . get_sketch ( ) ? ;
let x = inner_profile_start_x ( sketch ) ? ;
2024-11-14 17:27:19 -06:00
Ok ( args . make_user_val_from_f64 ( x ) )
2024-05-21 03:44:02 -04:00
}
2024-09-27 15:44:44 -07:00
/// Extract the provided 2-dimensional sketch's profile's origin's 'x'
2024-08-06 20:27:26 -04:00
/// value.
///
2024-05-21 03:44:02 -04:00
/// ```no_run
2024-12-12 11:33:37 -05:00
/// sketch001 = startSketchOn('XY')
2024-05-21 03:44:02 -04:00
/// |> startProfileAt([5, 2], %)
/// |> angledLine([-26.6, 50], %)
/// |> angledLine([90, 50], %)
2024-11-25 09:21:55 +13:00
/// |> angledLineToX({ angle = 30, to = profileStartX(%) }, %)
2024-05-21 03:44:02 -04:00
/// ```
#[ stdlib {
name = " profileStartX "
} ]
2024-09-27 15:44:44 -07:00
pub ( crate ) fn inner_profile_start_x ( sketch : Sketch ) -> Result < f64 , KclError > {
Ok ( sketch . start . to [ 0 ] )
2024-05-21 03:44:02 -04:00
}
/// Returns the Y component of the sketch profile start point.
2024-09-16 15:10:33 -04:00
pub async fn profile_start_y ( _exec_state : & mut ExecState , args : Args ) -> Result < KclValue , KclError > {
2024-09-27 15:44:44 -07:00
let sketch : Sketch = args . get_sketch ( ) ? ;
let x = inner_profile_start_y ( sketch ) ? ;
2024-11-14 17:27:19 -06:00
Ok ( args . make_user_val_from_f64 ( x ) )
2024-05-21 03:44:02 -04:00
}
2024-09-27 15:44:44 -07:00
/// Extract the provided 2-dimensional sketch's profile's origin's 'y'
2024-08-06 20:27:26 -04:00
/// value.
///
2024-05-21 03:44:02 -04:00
/// ```no_run
2024-12-12 11:33:37 -05:00
/// sketch001 = startSketchOn('XY')
2024-05-21 03:44:02 -04:00
/// |> startProfileAt([5, 2], %)
2024-11-25 09:21:55 +13:00
/// |> angledLine({ angle = -60, length = 14 }, %)
/// |> angledLineToY({ angle = 30, to = profileStartY(%) }, %)
2024-05-21 03:44:02 -04:00
/// ```
#[ stdlib {
name = " profileStartY "
} ]
2024-09-27 15:44:44 -07:00
pub ( crate ) fn inner_profile_start_y ( sketch : Sketch ) -> Result < f64 , KclError > {
Ok ( sketch . start . to [ 1 ] )
2024-05-21 03:44:02 -04:00
}
/// Returns the sketch profile start point.
2024-09-16 15:10:33 -04:00
pub async fn profile_start ( _exec_state : & mut ExecState , args : Args ) -> Result < KclValue , KclError > {
2024-09-27 15:44:44 -07:00
let sketch : Sketch = args . get_sketch ( ) ? ;
let point = inner_profile_start ( sketch ) ? ;
2024-11-14 17:27:19 -06:00
Ok ( KclValue ::from_point2d ( point , args . into ( ) ) )
2024-05-21 03:44:02 -04:00
}
2024-09-27 15:44:44 -07:00
/// Extract the provided 2-dimensional sketch's profile's origin
2024-08-06 20:27:26 -04:00
/// value.
///
2024-05-21 03:44:02 -04:00
/// ```no_run
2024-12-12 11:33:37 -05:00
/// sketch001 = startSketchOn('XY')
2024-05-21 03:44:02 -04:00
/// |> startProfileAt([5, 2], %)
2024-11-25 09:21:55 +13:00
/// |> angledLine({ angle = 120, length = 50 }, %, $seg01)
/// |> angledLine({ angle = segAng(seg01) + 120, length = 50 }, %)
2024-05-21 03:44:02 -04:00
/// |> lineTo(profileStart(%), %)
/// |> close(%)
/// |> extrude(20, %)
/// ```
#[ stdlib {
name = " profileStart "
} ]
2024-09-27 15:44:44 -07:00
pub ( crate ) fn inner_profile_start ( sketch : Sketch ) -> Result < [ f64 ; 2 ] , KclError > {
Ok ( sketch . start . to )
2024-05-21 03:44:02 -04:00
}
2023-08-24 15:34:51 -07:00
/// Close the current sketch.
2024-10-09 19:38:40 -04:00
pub async fn close ( exec_state : & mut ExecState , args : Args ) -> Result < KclValue , KclError > {
2024-10-30 16:52:17 -04:00
let ( sketch , tag ) : ( Sketch , Option < TagNode > ) = args . get_sketch_and_optional_tag ( ) ? ;
2023-08-25 13:41:04 -07:00
2024-10-09 19:38:40 -04:00
let new_sketch = inner_close ( sketch , tag , exec_state , args ) . await ? ;
2023-08-25 13:41:04 -07:00
2024-11-14 17:27:19 -06:00
Ok ( KclValue ::Sketch {
value : Box ::new ( new_sketch ) ,
} )
2023-08-25 13:41:04 -07:00
}
2024-08-06 20:27:26 -04:00
/// Construct a line segment from the current origin back to the profile's
/// origin, ensuring the resulting 2-dimensional sketch is not open-ended.
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-12-12 11:33:37 -05:00
/// exampleSketch = startSketchOn('-XZ')
2024-05-14 17:10:47 -07:00
/// |> startProfileAt([0, 0], %)
/// |> line([10, 0], %)
/// |> line([0, 10], %)
/// |> close(%)
///
2024-12-12 11:33:37 -05:00
/// example = extrude(10, exampleSketch)
2024-03-13 12:56:46 -07:00
/// ```
2023-08-25 13:41:04 -07:00
#[ stdlib {
name = " close " ,
} ]
2024-10-09 19:38:40 -04:00
pub ( crate ) async fn inner_close (
sketch : Sketch ,
2024-10-30 16:52:17 -04:00
tag : Option < TagNode > ,
2024-10-09 19:38:40 -04:00
exec_state : & mut ExecState ,
args : Args ,
) -> Result < Sketch , KclError > {
2024-09-27 15:44:44 -07:00
let from = sketch . current_pen_position ( ) ? ;
let to : Point2d = sketch . start . from . into ( ) ;
2023-08-24 15:34:51 -07:00
2024-12-17 09:38:32 +13:00
let id = exec_state . next_uuid ( ) ;
2023-08-24 15:34:51 -07:00
2024-09-27 15:44:44 -07:00
args . batch_modeling_cmd ( id , ModelingCmd ::from ( mcmd ::ClosePath { path_id : sketch . id } ) )
. await ? ;
2023-08-24 15:34:51 -07:00
2024-09-27 15:44:44 -07:00
// If we are sketching on a plane we can close the sketch now.
if let SketchSurface ::Plane ( _ ) = sketch . on {
2023-10-05 14:27:48 -07:00
// We were on a plane, disable the sketch mode.
2024-09-18 17:04:04 -05:00
args . batch_modeling_cmd (
2024-12-17 09:38:32 +13:00
exec_state . next_uuid ( ) ,
2024-09-18 17:04:04 -05:00
ModelingCmd ::SketchModeDisable ( mcmd ::SketchModeDisable { } ) ,
)
. await ? ;
2023-10-05 14:27:48 -07:00
}
2024-07-27 22:56:46 -07:00
let current_path = Path ::ToPoint {
2023-08-24 15:34:51 -07:00
base : BasePath {
from : from . into ( ) ,
to : to . into ( ) ,
2024-07-27 22:56:46 -07:00
tag : tag . clone ( ) ,
2023-08-24 15:34:51 -07:00
geo_meta : GeoMeta {
id ,
metadata : args . source_range . into ( ) ,
} ,
} ,
2024-07-27 22:56:46 -07:00
} ;
2024-09-27 15:44:44 -07:00
let mut new_sketch = sketch . clone ( ) ;
2024-07-27 22:56:46 -07:00
if let Some ( tag ) = & tag {
2024-09-27 15:44:44 -07:00
new_sketch . add_tag ( tag , & current_path ) ;
2024-07-27 22:56:46 -07:00
}
2024-10-23 12:42:54 -05:00
new_sketch . paths . push ( current_path ) ;
2023-08-24 15:34:51 -07:00
2024-09-27 15:44:44 -07:00
Ok ( new_sketch )
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.
2025-01-05 17:49:16 -08:00
#[ serde(rename = " angleStart " ) ]
2024-09-26 16:33:49 -07:00
#[ schemars(range(min = -360.0, max = 360.0)) ]
2023-08-31 22:19:23 -07:00
angle_start : f64 ,
/// The end angle.
2025-01-05 17:49:16 -08:00
#[ serde(rename = " angleEnd " ) ]
2024-09-26 16:33:49 -07:00
#[ schemars(range(min = -360.0, max = 360.0)) ]
2023-08-31 22:19:23 -07:00
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 ,
} ,
}
2024-11-18 17:17:16 -05:00
/// Data to draw a three point arc (arcTo).
#[ derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema) ]
#[ ts(export) ]
#[ serde(rename_all = " camelCase " ) ]
pub struct ArcToData {
/// End point of the arc. A point in 3D space
pub end : [ f64 ; 2 ] ,
/// Interior point of the arc. A point in 3D space
pub interior : [ f64 ; 2 ] ,
}
2023-08-31 22:19:23 -07:00
/// Draw an arc.
2024-10-09 19:38:40 -04:00
pub async fn arc ( exec_state : & mut ExecState , args : Args ) -> Result < KclValue , KclError > {
2024-10-30 16:52:17 -04:00
let ( data , sketch , tag ) : ( ArcData , Sketch , Option < TagNode > ) = args . get_data_and_sketch_and_tag ( ) ? ;
2023-08-31 22:19:23 -07:00
2024-10-09 19:38:40 -04:00
let new_sketch = inner_arc ( data , sketch , tag , exec_state , args ) . await ? ;
2024-11-14 17:27:19 -06:00
Ok ( KclValue ::Sketch {
value : Box ::new ( new_sketch ) ,
} )
2023-08-31 22:19:23 -07:00
}
2024-09-30 13:43:16 -05:00
/// Draw a curved line segment along an imaginary circle.
2024-08-06 20:27:26 -04:00
/// The arc is constructed such that the current position of the sketch is
/// placed along an imaginary circle of the specified radius, at angleStart
/// degrees. The resulting arc is the segment of the imaginary circle from
/// that origin point to angleEnd, radius away from the center of the imaginary
/// circle.
///
/// Unless this makes a lot of sense and feels like what you're looking
/// for to construct your shape, you're likely looking for tangentialArc.
2024-03-13 12:56:46 -07:00
///
/// ```no_run
2024-12-12 11:33:37 -05:00
/// 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-11-25 09:21:55 +13:00
/// angleStart = 0,
/// angleEnd = 280,
/// radius = 16
2024-05-14 17:10:47 -07:00
/// }, %)
/// |> close(%)
2024-12-12 11:33:37 -05:00
/// 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 ,
2024-09-27 15:44:44 -07:00
sketch : Sketch ,
2024-10-30 16:52:17 -04:00
tag : Option < TagNode > ,
2024-10-09 19:38:40 -04:00
exec_state : & mut ExecState ,
2024-03-13 17:16:57 -07:00
args : Args ,
2024-09-27 15:44:44 -07:00
) -> Result < Sketch , KclError > {
let from : Point2d = sketch . 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 } = > {
2024-10-28 20:52:51 -04:00
let ( angle_start , angle_end ) = arc_angles ( from , to . into ( ) , center . into ( ) , * radius , args . source_range ) ? ;
2023-08-31 22:19:23 -07:00
( center . into ( ) , angle_start , angle_end , * radius , to . into ( ) )
}
} ;
2024-07-28 16:09:54 -07:00
if angle_start = = angle_end {
return Err ( KclError ::Type ( KclErrorDetails {
message : " Arc start and end angles must be different " . to_string ( ) ,
source_ranges : vec ! [ args . source_range ] ,
} ) ) ;
}
2024-11-25 18:17:47 -05:00
let ccw = angle_start < angle_end ;
2024-07-28 16:09:54 -07:00
2024-12-17 09:38:32 +13:00
let id = exec_state . next_uuid ( ) ;
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 ,
2024-09-18 17:04:04 -05:00
ModelingCmd ::from ( mcmd ::ExtendPath {
2024-09-27 15:44:44 -07:00
path : sketch . id . into ( ) ,
2024-09-18 17:04:04 -05:00
segment : PathSegment ::Arc {
2024-01-02 13:13:41 -06:00
start : angle_start ,
end : angle_end ,
2024-09-18 17:04:04 -05:00
center : KPoint2d ::from ( center ) . map ( LengthUnit ) ,
radius : LengthUnit ( radius ) ,
2023-09-20 17:36:26 -07:00
relative : false ,
2023-08-31 22:19:23 -07:00
} ,
2024-09-18 17:04:04 -05:00
} ) ,
2023-09-20 18:27:08 -07:00
)
. await ? ;
2023-09-20 17:36:26 -07:00
2024-10-25 17:49:30 -05:00
let current_path = Path ::Arc {
2023-08-31 22:19:23 -07:00
base : BasePath {
from : from . into ( ) ,
to : end . into ( ) ,
2024-07-27 22:56:46 -07:00
tag : tag . clone ( ) ,
2023-08-31 22:19:23 -07:00
geo_meta : GeoMeta {
id ,
metadata : args . source_range . into ( ) ,
} ,
} ,
2024-10-25 17:49:30 -05:00
center : center . into ( ) ,
radius ,
2024-11-25 13:07:03 -05:00
ccw ,
2023-08-31 22:19:23 -07:00
} ;
2024-09-27 15:44:44 -07:00
let mut new_sketch = sketch . clone ( ) ;
2024-07-27 22:56:46 -07:00
if let Some ( tag ) = & tag {
2024-09-27 15:44:44 -07:00
new_sketch . add_tag ( tag , & current_path ) ;
2024-07-27 22:56:46 -07:00
}
2024-10-23 12:42:54 -05:00
new_sketch . paths . push ( current_path ) ;
2023-08-31 22:19:23 -07:00
2024-09-27 15:44:44 -07:00
Ok ( new_sketch )
2023-08-31 22:19:23 -07:00
}
2024-11-18 17:17:16 -05:00
/// Draw a three point arc.
pub async fn arc_to ( exec_state : & mut ExecState , args : Args ) -> Result < KclValue , KclError > {
let ( data , sketch , tag ) : ( ArcToData , Sketch , Option < TagNode > ) = args . get_data_and_sketch_and_tag ( ) ? ;
let new_sketch = inner_arc_to ( data , sketch , tag , exec_state , args ) . await ? ;
Ok ( KclValue ::Sketch {
value : Box ::new ( new_sketch ) ,
} )
}
/// Draw a 3 point arc.
///
/// The arc is constructed such that the start point is the current position of the sketch and two more points defined as the end and interior point.
/// The interior point is placed between the start point and end point. The radius of the arc will be controlled by how far the interior point is placed from
/// the start and end.
///
/// ```no_run
2024-12-12 11:33:37 -05:00
/// exampleSketch = startSketchOn('XZ')
2024-11-18 17:17:16 -05:00
/// |> startProfileAt([0, 0], %)
/// |> arcTo({
2024-11-25 09:21:55 +13:00
/// end = [10,0],
/// interior = [5,5]
2024-11-18 17:17:16 -05:00
/// }, %)
/// |> close(%)
2024-12-12 11:33:37 -05:00
/// example = extrude(10, exampleSketch)
2024-11-18 17:17:16 -05:00
/// ```
#[ stdlib {
name = " arcTo " ,
} ]
pub ( crate ) async fn inner_arc_to (
data : ArcToData ,
sketch : Sketch ,
tag : Option < TagNode > ,
exec_state : & mut ExecState ,
args : Args ,
) -> Result < Sketch , KclError > {
let from : Point2d = sketch . current_pen_position ( ) ? ;
2024-12-17 09:38:32 +13:00
let id = exec_state . next_uuid ( ) ;
2024-11-18 17:17:16 -05:00
// The start point is taken from the path you are extending.
args . batch_modeling_cmd (
id ,
ModelingCmd ::from ( mcmd ::ExtendPath {
path : sketch . id . into ( ) ,
segment : PathSegment ::ArcTo {
end : kcmc ::shared ::Point3d {
x : LengthUnit ( data . end [ 0 ] ) ,
y : LengthUnit ( data . end [ 1 ] ) ,
z : LengthUnit ( 0.0 ) ,
} ,
interior : kcmc ::shared ::Point3d {
x : LengthUnit ( data . interior [ 0 ] ) ,
y : LengthUnit ( data . interior [ 1 ] ) ,
z : LengthUnit ( 0.0 ) ,
} ,
relative : false ,
} ,
} ) ,
)
. await ? ;
let start = [ from . x , from . y ] ;
2024-11-25 13:07:03 -05:00
let interior = data . interior ;
let end = data . end ;
2024-11-18 17:17:16 -05:00
// compute the center of the circle since we do not have the value returned from the engine
let center = calculate_circle_center ( start , interior , end ) ;
// compute the radius since we do not have the value returned from the engine
// Pick any of the 3 points since they all lie along the circle
let sum_of_square_differences =
( center [ 0 ] - start [ 0 ] * center [ 0 ] - start [ 0 ] ) + ( center [ 1 ] - start [ 1 ] * center [ 1 ] - start [ 1 ] ) ;
let radius = sum_of_square_differences . sqrt ( ) ;
2024-11-25 13:07:03 -05:00
let ccw = is_ccw ( start , interior , end ) ;
2024-11-18 17:17:16 -05:00
let current_path = Path ::Arc {
base : BasePath {
from : from . into ( ) ,
to : data . end ,
tag : tag . clone ( ) ,
geo_meta : GeoMeta {
id ,
metadata : args . source_range . into ( ) ,
} ,
} ,
center ,
radius ,
2024-11-25 13:07:03 -05:00
ccw ,
2024-11-18 17:17:16 -05:00
} ;
let mut new_sketch = sketch . clone ( ) ;
if let Some ( tag ) = & tag {
new_sketch . add_tag ( tag , & current_path ) ;
}
new_sketch . paths . push ( current_path ) ;
Ok ( new_sketch )
}
2024-11-25 13:07:03 -05:00
/// Returns true if the three-point arc is counterclockwise. The order of
/// parameters is critical.
///
/// | end
/// | /
/// | | / interior
/// | / /
/// | | /
/// |/_____________
/// start
///
/// If the slope of the line from start to interior is less than the slope of
/// the line from start to end, the arc is counterclockwise.
fn is_ccw ( start : [ f64 ; 2 ] , interior : [ f64 ; 2 ] , end : [ f64 ; 2 ] ) -> bool {
let t1 = ( interior [ 0 ] - start [ 0 ] ) * ( end [ 1 ] - start [ 1 ] ) ;
let t2 = ( end [ 0 ] - start [ 0 ] ) * ( interior [ 1 ] - start [ 1 ] ) ;
// If these terms are equal, the points are collinear.
t1 > t2
}
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 ,
} ,
}
2023-10-12 11:50:54 -05:00
/// Draw a tangential arc.
2024-10-09 19:38:40 -04:00
pub async fn tangential_arc ( exec_state : & mut ExecState , args : Args ) -> Result < KclValue , KclError > {
2024-10-30 16:52:17 -04:00
let ( data , sketch , tag ) : ( TangentialArcData , Sketch , Option < TagNode > ) = args . get_data_and_sketch_and_tag ( ) ? ;
2023-09-29 14:41:14 -07:00
2024-10-09 19:38:40 -04:00
let new_sketch = inner_tangential_arc ( data , sketch , tag , exec_state , args ) . await ? ;
2024-11-14 17:27:19 -06:00
Ok ( KclValue ::Sketch {
value : Box ::new ( new_sketch ) ,
} )
2023-09-29 14:41:14 -07:00
}
2024-09-30 13:43:16 -05:00
/// Draw a curved line segment along part of an imaginary circle.
2024-08-06 20:27:26 -04:00
///
/// The arc is constructed such that the last line segment is placed tangent
/// to the imaginary circle of the specified radius. The resulting arc is the
/// segment of the imaginary circle from that tangent point for 'offset'
/// degrees along the imaginary circle.
2024-03-13 12:56:46 -07:00
///
/// ```no_run
2024-12-12 11:33:37 -05:00
/// exampleSketch = startSketchOn('XZ')
2024-03-15 17:03:42 -04:00
/// |> startProfileAt([0, 0], %)
2024-05-14 17:10:47 -07:00
/// |> angledLine({
2024-11-25 09:21:55 +13:00
/// angle = 60,
/// length = 10,
2024-05-14 17:10:47 -07:00
/// }, %)
2024-11-25 09:21:55 +13:00
/// |> tangentialArc({ radius = 10, offset = -120 }, %)
2024-05-14 17:10:47 -07:00
/// |> angledLine({
2024-11-25 09:21:55 +13:00
/// angle = -60,
/// length = 10,
2024-05-14 17:10:47 -07:00
/// }, %)
2024-03-13 12:56:46 -07:00
/// |> close(%)
2024-05-14 17:10:47 -07:00
///
2024-12-12 11:33:37 -05:00
/// 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 ,
2024-09-27 15:44:44 -07:00
sketch : Sketch ,
2024-10-30 16:52:17 -04:00
tag : Option < TagNode > ,
2024-10-09 19:38:40 -04:00
exec_state : & mut ExecState ,
2023-09-29 14:41:14 -07:00
args : Args ,
2024-09-27 15:44:44 -07:00
) -> Result < Sketch , KclError > {
let from : Point2d = sketch . current_pen_position ( ) ? ;
2024-08-07 18:35:41 -05:00
// next set of lines is some undocumented voodoo from get_tangential_arc_to_info
2024-09-27 15:44:44 -07:00
let tangent_info = sketch . get_tangential_info_from_paths ( ) ; //this function desperately needs some documentation
2024-11-25 13:07:03 -05:00
let tan_previous_point = tangent_info . tan_previous_point ( from . into ( ) ) ;
2023-09-29 14:41:14 -07:00
2024-12-17 09:38:32 +13:00
let id = exec_state . next_uuid ( ) ;
2023-09-29 14:41:14 -07:00
2024-08-07 18:35:41 -05:00
let ( center , to , ccw ) = match data {
2023-10-12 11:50:54 -05:00
TangentialArcData ::RadiusAndOffset { radius , offset } = > {
2024-08-07 18:35:41 -05:00
// KCL stdlib types use degrees.
let offset = Angle ::from_degrees ( offset ) ;
2023-09-29 14:41:14 -07:00
// Calculate the end point from the angle and radius.
2024-08-07 18:35:41 -05:00
// atan2 outputs radians.
let previous_end_tangent = Angle ::from_radians ( f64 ::atan2 (
from . y - tan_previous_point [ 1 ] ,
from . x - tan_previous_point [ 0 ] ,
) ) ;
// make sure the arc center is on the correct side to guarantee deterministic behavior
// note the engine automatically rejects an offset of zero, if we want to flag that at KCL too to avoid engine errors
2024-09-18 17:04:04 -05:00
let ccw = offset . to_degrees ( ) > 0.0 ;
2024-08-07 18:35:41 -05:00
let tangent_to_arc_start_angle = if ccw {
// CCW turn
Angle ::from_degrees ( - 90.0 )
} else {
// CW turn
Angle ::from_degrees ( 90.0 )
} ;
// may need some logic and / or modulo on the various angle values to prevent them from going "backwards"
// but the above logic *should* capture that behavior
let start_angle = previous_end_tangent + tangent_to_arc_start_angle ;
let end_angle = start_angle + offset ;
let ( center , to ) = arc_center_and_end ( from , start_angle , end_angle , radius ) ;
2023-09-29 14:41:14 -07:00
2024-06-19 13:57:50 -07:00
args . batch_modeling_cmd (
2023-09-29 14:41:14 -07:00
id ,
2024-09-18 17:04:04 -05:00
ModelingCmd ::from ( mcmd ::ExtendPath {
2024-09-27 15:44:44 -07:00
path : sketch . id . into ( ) ,
2024-09-18 17:04:04 -05:00
segment : PathSegment ::TangentialArc {
radius : LengthUnit ( radius ) ,
offset ,
} ,
} ) ,
2023-09-29 14:41:14 -07:00
)
. await ? ;
2024-08-07 18:35:41 -05:00
( center , to . into ( ) , ccw )
2023-09-29 14:41:14 -07:00
}
} ;
2024-02-22 19:07:17 -08:00
let current_path = Path ::TangentialArc {
2024-08-07 18:35:41 -05:00
ccw ,
center : center . into ( ) ,
2023-09-29 14:41:14 -07:00
base : BasePath {
from : from . into ( ) ,
to ,
2024-07-27 22:56:46 -07:00
tag : tag . clone ( ) ,
2023-09-29 14:41:14 -07:00
geo_meta : GeoMeta {
id ,
metadata : args . source_range . into ( ) ,
} ,
} ,
} ;
2024-09-27 15:44:44 -07:00
let mut new_sketch = sketch . clone ( ) ;
2024-07-27 22:56:46 -07:00
if let Some ( tag ) = & tag {
2024-09-27 15:44:44 -07:00
new_sketch . add_tag ( tag , & current_path ) ;
2024-07-27 22:56:46 -07:00
}
2024-10-23 12:42:54 -05:00
new_sketch . paths . push ( current_path ) ;
2023-09-29 14:41:14 -07:00
2024-09-27 15:44:44 -07:00
Ok ( new_sketch )
2023-09-29 14:41:14 -07:00
}
2024-09-27 15:44:44 -07:00
fn tan_arc_to ( sketch : & Sketch , to : & [ f64 ; 2 ] ) -> ModelingCmd {
2024-09-18 17:04:04 -05:00
ModelingCmd ::from ( mcmd ::ExtendPath {
2024-09-27 15:44:44 -07:00
path : sketch . id . into ( ) ,
2024-09-18 17:04:04 -05:00
segment : PathSegment ::TangentialArcTo {
2023-10-24 10:30:14 -07:00
angle_snap_increment : None ,
2024-09-18 17:04:04 -05:00
to : KPoint2d ::from ( * to ) . with_z ( 0.0 ) . map ( LengthUnit ) ,
2023-10-24 10:30:14 -07:00
} ,
2024-09-18 17:04:04 -05:00
} )
2023-10-24 10:30:14 -07:00
}
2023-10-12 11:50:54 -05:00
/// Draw a tangential arc to a specific point.
2024-10-09 19:38:40 -04:00
pub async fn tangential_arc_to ( exec_state : & mut ExecState , args : Args ) -> Result < KclValue , KclError > {
2024-10-30 16:52:17 -04:00
let ( to , sketch , tag ) : ( [ f64 ; 2 ] , Sketch , Option < TagNode > ) = super ::args ::FromArgs ::from_args ( & args , 0 ) ? ;
2023-09-29 14:41:14 -07:00
2024-10-09 19:38:40 -04:00
let new_sketch = inner_tangential_arc_to ( to , sketch , tag , exec_state , args ) . await ? ;
2024-11-14 17:27:19 -06:00
Ok ( KclValue ::Sketch {
value : Box ::new ( new_sketch ) ,
} )
2023-09-29 14:41:14 -07:00
}
2024-08-30 13:44:20 -05:00
/// Draw a tangential arc to point some distance away..
2024-10-09 19:38:40 -04:00
pub async fn tangential_arc_to_relative ( exec_state : & mut ExecState , args : Args ) -> Result < KclValue , KclError > {
2024-10-30 16:52:17 -04:00
let ( delta , sketch , tag ) : ( [ f64 ; 2 ] , Sketch , Option < TagNode > ) = super ::args ::FromArgs ::from_args ( & args , 0 ) ? ;
2024-08-30 13:44:20 -05:00
2024-10-09 19:38:40 -04:00
let new_sketch = inner_tangential_arc_to_relative ( delta , sketch , tag , exec_state , args ) . await ? ;
2024-11-14 17:27:19 -06:00
Ok ( KclValue ::Sketch {
value : Box ::new ( new_sketch ) ,
} )
2024-08-30 13:44:20 -05:00
}
2024-08-06 20:27:26 -04:00
/// Starting at the current sketch's origin, draw a curved line segment along
/// some part of an imaginary circle until it reaches the desired (x, y)
/// coordinates.
2024-03-13 12:56:46 -07:00
///
/// ```no_run
2024-12-12 11:33:37 -05:00
/// exampleSketch = startSketchOn('XZ')
2024-05-14 17:10:47 -07:00
/// |> startProfileAt([0, 0], %)
/// |> angledLine({
2024-11-25 09:21:55 +13:00
/// angle = 60,
/// length = 10,
2024-05-14 17:10:47 -07:00
/// }, %)
/// |> tangentialArcTo([15, 15], %)
/// |> line([10, -15], %)
/// |> close(%)
///
2024-12-12 11:33:37 -05:00
/// 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 ] ,
2024-09-27 15:44:44 -07:00
sketch : Sketch ,
2024-10-30 16:52:17 -04:00
tag : Option < TagNode > ,
2024-10-09 19:38:40 -04:00
exec_state : & mut ExecState ,
2023-09-29 14:41:14 -07:00
args : Args ,
2024-09-27 15:44:44 -07:00
) -> Result < Sketch , KclError > {
let from : Point2d = sketch . current_pen_position ( ) ? ;
let tangent_info = sketch . get_tangential_info_from_paths ( ) ;
2024-11-25 13:07:03 -05:00
let tan_previous_point = tangent_info . tan_previous_point ( 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
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 ] ;
2024-12-17 09:38:32 +13:00
let id = exec_state . next_uuid ( ) ;
2024-09-27 15:44:44 -07:00
args . batch_modeling_cmd ( id , tan_arc_to ( & sketch , & 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 ,
2024-07-27 22:56:46 -07:00
tag : tag . clone ( ) ,
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
} ;
2024-09-27 15:44:44 -07:00
let mut new_sketch = sketch . clone ( ) ;
2024-07-27 22:56:46 -07:00
if let Some ( tag ) = & tag {
2024-09-27 15:44:44 -07:00
new_sketch . add_tag ( tag , & current_path ) ;
2024-07-27 22:56:46 -07:00
}
2024-10-23 12:42:54 -05:00
new_sketch . paths . push ( current_path ) ;
2023-09-29 14:41:14 -07:00
2024-09-27 15:44:44 -07:00
Ok ( new_sketch )
2023-09-29 14:41:14 -07:00
}
2024-08-30 13:44:20 -05:00
/// Starting at the current sketch's origin, draw a curved line segment along
/// some part of an imaginary circle until it reaches a point the given (x, y)
/// distance away.
///
/// ```no_run
2024-12-12 11:33:37 -05:00
/// exampleSketch = startSketchOn('XZ')
2024-08-30 13:44:20 -05:00
/// |> startProfileAt([0, 0], %)
/// |> angledLine({
2024-11-25 09:21:55 +13:00
/// angle = 45,
/// length = 10,
2024-08-30 13:44:20 -05:00
/// }, %)
/// |> tangentialArcToRelative([0, -10], %)
/// |> line([-10, 0], %)
/// |> close(%)
///
2024-12-12 11:33:37 -05:00
/// example = extrude(10, exampleSketch)
2024-08-30 13:44:20 -05:00
/// ```
#[ stdlib {
name = " tangentialArcToRelative " ,
} ]
async fn inner_tangential_arc_to_relative (
delta : [ f64 ; 2 ] ,
2024-09-27 15:44:44 -07:00
sketch : Sketch ,
2024-10-30 16:52:17 -04:00
tag : Option < TagNode > ,
2024-10-09 19:38:40 -04:00
exec_state : & mut ExecState ,
2024-08-30 13:44:20 -05:00
args : Args ,
2024-09-27 15:44:44 -07:00
) -> Result < Sketch , KclError > {
let from : Point2d = sketch . current_pen_position ( ) ? ;
2024-11-25 13:07:03 -05:00
let to = [ from . x + delta [ 0 ] , from . y + delta [ 1 ] ] ;
2024-09-27 15:44:44 -07:00
let tangent_info = sketch . get_tangential_info_from_paths ( ) ;
2024-11-25 13:07:03 -05:00
let tan_previous_point = tangent_info . tan_previous_point ( from . into ( ) ) ;
2024-08-30 13:44:20 -05:00
let [ dx , dy ] = delta ;
let result = get_tangential_arc_to_info ( TangentialArcInfoInput {
arc_start_point : [ from . x , from . y ] ,
arc_end_point : [ from . x + dx , from . y + dy ] ,
tan_previous_point ,
obtuse : true ,
} ) ;
if result . center [ 0 ] . is_infinite ( ) {
return Err ( KclError ::Semantic ( KclErrorDetails {
source_ranges : vec ! [ args . source_range ] ,
message :
" could not sketch tangential arc, because its center would be infinitely far away in the X direction "
. to_owned ( ) ,
} ) ) ;
} else if result . center [ 1 ] . is_infinite ( ) {
return Err ( KclError ::Semantic ( KclErrorDetails {
source_ranges : vec ! [ args . source_range ] ,
message :
" could not sketch tangential arc, because its center would be infinitely far away in the Y direction "
. to_owned ( ) ,
} ) ) ;
}
2024-12-17 09:38:32 +13:00
let id = exec_state . next_uuid ( ) ;
2024-09-27 15:44:44 -07:00
args . batch_modeling_cmd ( id , tan_arc_to ( & sketch , & delta ) ) . await ? ;
2024-08-30 13:44:20 -05:00
let current_path = Path ::TangentialArcTo {
base : BasePath {
from : from . into ( ) ,
2024-11-25 13:07:03 -05:00
to ,
2024-08-30 13:44:20 -05:00
tag : tag . clone ( ) ,
geo_meta : GeoMeta {
id ,
metadata : args . source_range . into ( ) ,
} ,
} ,
2024-09-18 17:04:04 -05:00
center : result . center ,
2024-08-30 13:44:20 -05:00
ccw : result . ccw > 0 ,
} ;
2024-09-27 15:44:44 -07:00
let mut new_sketch = sketch . clone ( ) ;
2024-08-30 13:44:20 -05:00
if let Some ( tag ) = & tag {
2024-09-27 15:44:44 -07:00
new_sketch . add_tag ( tag , & current_path ) ;
2024-08-30 13:44:20 -05:00
}
2024-10-23 12:42:54 -05:00
new_sketch . paths . push ( current_path ) ;
2024-08-30 13:44:20 -05:00
2024-09-27 15:44:44 -07:00
Ok ( new_sketch )
2024-08-30 13:44:20 -05:00
}
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.
2024-11-14 17:27:19 -06:00
pub to : [ f64 ; 2 ] ,
2024-03-07 15:35:26 -08:00
/// The first control point.
2024-11-14 17:27:19 -06:00
pub control1 : [ f64 ; 2 ] ,
2024-03-07 15:35:26 -08:00
/// The second control point.
2024-11-14 17:27:19 -06:00
pub control2 : [ f64 ; 2 ] ,
2023-08-31 22:19:23 -07:00
}
/// Draw a bezier curve.
2024-10-09 19:38:40 -04:00
pub async fn bezier_curve ( exec_state : & mut ExecState , args : Args ) -> Result < KclValue , KclError > {
2024-10-30 16:52:17 -04:00
let ( data , sketch , tag ) : ( BezierData , Sketch , Option < TagNode > ) = args . get_data_and_sketch_and_tag ( ) ? ;
2023-08-31 22:19:23 -07:00
2024-10-09 19:38:40 -04:00
let new_sketch = inner_bezier_curve ( data , sketch , tag , exec_state , args ) . await ? ;
2024-11-14 17:27:19 -06:00
Ok ( KclValue ::Sketch {
value : Box ::new ( new_sketch ) ,
} )
2023-08-31 22:19:23 -07:00
}
2024-08-06 20:27:26 -04:00
/// Draw a smooth, continuous, curved line segment from the current origin to
/// the desired (x, y), using a number of control points to shape the curve's
/// shape.
2024-03-13 12:56:46 -07:00
///
/// ```no_run
2024-12-12 11:33:37 -05:00
/// exampleSketch = startSketchOn('XZ')
2024-05-14 17:10:47 -07:00
/// |> startProfileAt([0, 0], %)
/// |> line([0, 10], %)
/// |> bezierCurve({
2024-11-25 09:21:55 +13:00
/// to = [10, 10],
/// control1 = [5, 0],
/// control2 = [5, 10]
2024-05-14 17:10:47 -07:00
/// }, %)
/// |> lineTo([10, 0], %)
/// |> close(%)
///
2024-12-12 11:33:37 -05:00
/// 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 ,
2024-09-27 15:44:44 -07:00
sketch : Sketch ,
2024-10-30 16:52:17 -04:00
tag : Option < TagNode > ,
2024-10-09 19:38:40 -04:00
exec_state : & mut ExecState ,
2023-09-20 18:27:08 -07:00
args : Args ,
2024-09-27 15:44:44 -07:00
) -> Result < Sketch , KclError > {
let from = sketch . 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
2024-12-17 09:38:32 +13:00
let id = exec_state . next_uuid ( ) ;
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 ,
2024-09-18 17:04:04 -05:00
ModelingCmd ::from ( mcmd ::ExtendPath {
2024-09-27 15:44:44 -07:00
path : sketch . id . into ( ) ,
2024-09-18 17:04:04 -05:00
segment : PathSegment ::Bezier {
control1 : KPoint2d ::from ( data . control1 ) . with_z ( 0.0 ) . map ( LengthUnit ) ,
control2 : KPoint2d ::from ( data . control2 ) . with_z ( 0.0 ) . map ( LengthUnit ) ,
end : KPoint2d ::from ( delta ) . with_z ( 0.0 ) . map ( LengthUnit ) ,
2023-09-20 17:36:26 -07:00
relative ,
2023-08-31 22:19:23 -07:00
} ,
2024-09-18 17:04:04 -05: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-07-27 22:56:46 -07:00
tag : tag . clone ( ) ,
2023-08-31 22:19:23 -07:00
geo_meta : GeoMeta {
id ,
metadata : args . source_range . into ( ) ,
} ,
} ,
} ;
2024-09-27 15:44:44 -07:00
let mut new_sketch = sketch . clone ( ) ;
2024-07-27 22:56:46 -07:00
if let Some ( tag ) = & tag {
2024-09-27 15:44:44 -07:00
new_sketch . add_tag ( tag , & current_path ) ;
2024-07-27 22:56:46 -07:00
}
2024-10-23 12:42:54 -05:00
new_sketch . paths . push ( current_path ) ;
2023-08-31 22:19:23 -07:00
2024-09-27 15:44:44 -07:00
Ok ( new_sketch )
2023-08-31 22:19:23 -07:00
}
2023-10-13 12:02:46 -07:00
/// Use a sketch to cut a hole in another sketch.
2024-10-09 19:38:40 -04:00
pub async fn hole ( exec_state : & mut ExecState , args : Args ) -> Result < KclValue , KclError > {
2024-09-27 15:44:44 -07:00
let ( hole_sketch , sketch ) : ( SketchSet , Sketch ) = args . get_sketches ( ) ? ;
2023-10-13 12:02:46 -07:00
2024-10-09 19:38:40 -04:00
let new_sketch = inner_hole ( hole_sketch , sketch , exec_state , args ) . await ? ;
2024-11-14 17:27:19 -06:00
Ok ( KclValue ::Sketch {
value : Box ::new ( new_sketch ) ,
} )
2023-10-13 12:02:46 -07:00
}
2024-08-06 20:27:26 -04:00
/// Use a 2-dimensional sketch to cut a hole in another 2-dimensional sketch.
2024-03-13 12:56:46 -07:00
///
/// ```no_run
2024-12-12 11:33:37 -05:00
/// exampleSketch = startSketchOn('XY')
2024-05-14 17:10:47 -07:00
/// |> startProfileAt([0, 0], %)
/// |> line([0, 5], %)
/// |> line([5, 0], %)
/// |> line([0, -5], %)
/// |> close(%)
2024-11-25 09:21:55 +13:00
/// |> hole(circle({ center = [1, 1], radius = .25 }, %), %)
/// |> hole(circle({ center = [1, 4], radius = .25 }, %), %)
2024-05-14 17:10:47 -07:00
///
2024-12-12 11:33:37 -05:00
/// example = extrude(1, exampleSketch)
2024-05-14 17:10:47 -07:00
/// ```
///
/// ```no_run
2024-12-12 11:33:37 -05:00
/// fn squareHoleSketch() {
/// squareSketch = startSketchOn('-XZ')
/// |> startProfileAt([-1, -1], %)
/// |> line([2, 0], %)
/// |> line([0, 2], %)
/// |> line([-2, 0], %)
/// |> close(%)
/// return squareSketch
/// }
///
/// exampleSketch = startSketchOn('-XZ')
2024-11-25 09:21:55 +13:00
/// |> circle({ center = [0, 0], radius = 3 }, %)
2024-05-14 17:10:47 -07:00
/// |> hole(squareHoleSketch(), %)
2024-12-12 11:33:37 -05:00
/// example = extrude(1, exampleSketch)
2024-03-13 12:56:46 -07:00
/// ```
2023-10-13 12:02:46 -07:00
#[ stdlib {
name = " hole " ,
2024-12-16 13:10:31 -05:00
feature_tree_operation = true ,
2023-10-13 12:02:46 -07:00
} ]
2024-10-09 19:38:40 -04:00
async fn inner_hole (
hole_sketch : SketchSet ,
sketch : Sketch ,
exec_state : & mut ExecState ,
args : Args ,
) -> Result < Sketch , KclError > {
2024-09-27 15:44:44 -07:00
let hole_sketches : Vec < Sketch > = hole_sketch . into ( ) ;
for hole_sketch in hole_sketches {
2024-06-23 23:04:32 -07:00
args . batch_modeling_cmd (
2024-12-17 09:38:32 +13:00
exec_state . next_uuid ( ) ,
2024-09-18 17:04:04 -05:00
ModelingCmd ::from ( mcmd ::Solid2dAddHole {
2024-09-27 15:44:44 -07:00
object_id : sketch . id ,
hole_id : hole_sketch . id ,
2024-09-18 17:04:04 -05:00
} ) ,
2024-06-23 23:04:32 -07:00
)
. await ? ;
// suggestion (mike)
// we also hide the source hole since its essentially "consumed" by this operation
args . batch_modeling_cmd (
2024-12-17 09:38:32 +13:00
exec_state . next_uuid ( ) ,
2024-09-18 17:04:04 -05:00
ModelingCmd ::from ( mcmd ::ObjectVisible {
2024-09-27 15:44:44 -07:00
object_id : hole_sketch . id ,
2024-06-23 23:04:32 -07:00
hidden : true ,
2024-09-18 17:04:04 -05:00
} ) ,
2024-06-23 23:04:32 -07:00
)
. await ? ;
2024-02-11 15:08:54 -08:00
}
2023-10-13 12:02:46 -07:00
2024-09-27 15:44:44 -07:00
Ok ( sketch )
2023-10-13 12:02:46 -07:00
}
2023-08-24 15:34:51 -07:00
#[ cfg(test) ]
mod tests {
use pretty_assertions ::assert_eq ;
2025-01-04 12:18:29 -05:00
use crate ::{ execution ::TagIdentifier , std ::sketch ::PlaneData , std ::utils ::calculate_circle_center } ;
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 ( ) ;
2024-06-23 23:04:32 -07:00
let data : crate ::std ::sketch ::FaceTag = serde_json ::from_str ( & str_json ) . unwrap ( ) ;
2024-02-13 10:26:09 -08:00
assert_eq! (
data ,
2024-06-23 23:04:32 -07:00
crate ::std ::sketch ::FaceTag ::StartOrEnd ( crate ::std ::sketch ::StartOrEnd ::End )
2024-02-13 10:26:09 -08:00
) ;
2024-07-27 17:59:41 -07:00
str_json = serde_json ::to_string ( & TagIdentifier {
value : " thing " . to_string ( ) ,
2024-07-27 22:56:46 -07:00
info : None ,
2024-07-27 17:59:41 -07:00
meta : Default ::default ( ) ,
} )
. unwrap ( ) ;
2024-06-23 23:04:32 -07:00
let data : crate ::std ::sketch ::FaceTag = serde_json ::from_str ( & str_json ) . unwrap ( ) ;
2024-06-24 14:45:07 -07:00
assert_eq! (
data ,
2024-07-27 22:56:46 -07:00
crate ::std ::sketch ::FaceTag ::Tag ( Box ::new ( TagIdentifier {
2024-06-24 14:45:07 -07:00
value : " thing " . to_string ( ) ,
2024-07-27 22:56:46 -07:00
info : None ,
2024-06-24 14:45:07 -07:00
meta : Default ::default ( )
2024-07-27 22:56:46 -07:00
} ) )
2024-06-24 14:45:07 -07:00
) ;
2024-02-13 10:26:09 -08:00
str_json = " \" END \" " . to_string ( ) ;
2024-06-23 23:04:32 -07:00
let data : crate ::std ::sketch ::FaceTag = serde_json ::from_str ( & str_json ) . unwrap ( ) ;
2024-02-13 10:26:09 -08:00
assert_eq! (
data ,
2024-06-23 23:04:32 -07:00
crate ::std ::sketch ::FaceTag ::StartOrEnd ( crate ::std ::sketch ::StartOrEnd ::End )
2024-02-13 10:26:09 -08:00
) ;
str_json = " \" start \" " . to_string ( ) ;
2024-06-23 23:04:32 -07:00
let data : crate ::std ::sketch ::FaceTag = serde_json ::from_str ( & str_json ) . unwrap ( ) ;
2024-02-13 10:26:09 -08:00
assert_eq! (
data ,
2024-06-23 23:04:32 -07:00
crate ::std ::sketch ::FaceTag ::StartOrEnd ( crate ::std ::sketch ::StartOrEnd ::Start )
2024-02-13 10:26:09 -08:00
) ;
str_json = " \" START \" " . to_string ( ) ;
2024-06-23 23:04:32 -07:00
let data : crate ::std ::sketch ::FaceTag = serde_json ::from_str ( & str_json ) . unwrap ( ) ;
2024-02-13 10:26:09 -08:00
assert_eq! (
data ,
2024-06-23 23:04:32 -07:00
crate ::std ::sketch ::FaceTag ::StartOrEnd ( crate ::std ::sketch ::StartOrEnd ::Start )
2024-02-13 10:26:09 -08:00
) ;
}
2024-11-18 17:17:16 -05:00
#[ test ]
fn test_circle_center ( ) {
let actual = calculate_circle_center ( [ 0.0 , 0.0 ] , [ 5.0 , 5.0 ] , [ 10.0 , 0.0 ] ) ;
assert_eq! ( actual [ 0 ] , 5.0 ) ;
assert_eq! ( actual [ 1 ] , 0.0 ) ;
}
2023-08-24 15:34:51 -07:00
}