kcl_lib/std/
segment.rs

1//! Functions related to line segments.
2
3use anyhow::Result;
4use kcl_derive_docs::stdlib;
5use kittycad_modeling_cmds::shared::Angle;
6
7use crate::{
8    errors::{KclError, KclErrorDetails},
9    execution::{
10        types::{PrimitiveType, RuntimeType},
11        ExecState, KclValue, Point2d, Sketch, TagIdentifier,
12    },
13    std::{utils::between, Args},
14};
15
16/// Returns the point at the end of the given segment.
17pub async fn segment_end(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
18    let tag: TagIdentifier = args.get_unlabeled_kw_arg("tag")?;
19    let result = inner_segment_end(&tag, exec_state, args.clone())?;
20
21    args.make_user_val_from_point(result)
22}
23
24/// Compute the ending point of the provided line segment.
25///
26/// ```no_run
27/// w = 15
28/// cube = startSketchOn('XY')
29///   |> startProfileAt([0, 0], %)
30///   |> line(end = [w, 0], tag = $line1)
31///   |> line(end = [0, w], tag = $line2)
32///   |> line(end = [-w, 0], tag = $line3)
33///   |> line(end = [0, -w], tag = $line4)
34///   |> close()
35///   |> extrude(length = 5)
36///
37/// fn cylinder(radius, tag) {
38///   return startSketchOn('XY')
39///   |> startProfileAt([0, 0], %)
40///   |> circle(radius = radius, center = segEnd(tag) )
41///   |> extrude(length = radius)
42/// }
43///
44/// cylinder(1, line1)
45/// cylinder(2, line2)
46/// cylinder(3, line3)
47/// cylinder(4, line4)
48/// ```
49#[stdlib {
50    name = "segEnd",
51    keywords = true,
52    unlabeled_first = true,
53    args = {
54        tag = { docs = "The line segment being queried by its tag"},
55    }
56}]
57fn inner_segment_end(tag: &TagIdentifier, exec_state: &mut ExecState, args: Args) -> Result<[f64; 2], KclError> {
58    let line = args.get_tag_engine_info(exec_state, tag)?;
59    let path = line.path.clone().ok_or_else(|| {
60        KclError::Type(KclErrorDetails {
61            message: format!("Expected a line segment with a path, found `{:?}`", line),
62            source_ranges: vec![args.source_range],
63        })
64    })?;
65
66    Ok(path.get_base().to)
67}
68
69/// Returns the segment end of x.
70pub async fn segment_end_x(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
71    let tag: TagIdentifier = args.get_unlabeled_kw_arg("tag")?;
72    let result = inner_segment_end_x(&tag, exec_state, args.clone())?;
73
74    Ok(args.make_user_val_from_f64(result))
75}
76
77/// Compute the ending point of the provided line segment along the 'x' axis.
78///
79/// ```no_run
80/// exampleSketch = startSketchOn('XZ')
81///   |> startProfileAt([0, 0], %)
82///   |> line(end = [20, 0], tag = $thing)
83///   |> line(end = [0, 5])
84///   |> line(end = [segEndX(thing), 0])
85///   |> line(end = [-20, 10])
86///   |> close()
87///  
88/// example = extrude(exampleSketch, length = 5)
89/// ```
90#[stdlib {
91    name = "segEndX",
92    keywords = true,
93    unlabeled_first = true,
94    args = {
95        tag = { docs = "The line segment being queried by its tag"},
96    }
97}]
98fn inner_segment_end_x(tag: &TagIdentifier, exec_state: &mut ExecState, args: Args) -> Result<f64, KclError> {
99    let line = args.get_tag_engine_info(exec_state, tag)?;
100    let path = line.path.clone().ok_or_else(|| {
101        KclError::Type(KclErrorDetails {
102            message: format!("Expected a line segment with a path, found `{:?}`", line),
103            source_ranges: vec![args.source_range],
104        })
105    })?;
106
107    Ok(path.get_base().to[0])
108}
109
110/// Returns the segment end of y.
111pub async fn segment_end_y(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
112    let tag: TagIdentifier = args.get_unlabeled_kw_arg("tag")?;
113    let result = inner_segment_end_y(&tag, exec_state, args.clone())?;
114
115    Ok(args.make_user_val_from_f64(result))
116}
117
118/// Compute the ending point of the provided line segment along the 'y' axis.
119///
120/// ```no_run
121/// exampleSketch = startSketchOn('XZ')
122///   |> startProfileAt([0, 0], %)
123///   |> line(end = [20, 0])
124///   |> line(end = [0, 3], tag = $thing)
125///   |> line(end = [-10, 0])
126///   |> line(end = [0, segEndY(thing)])
127///   |> line(end = [-10, 0])
128///   |> close()
129///  
130/// example = extrude(exampleSketch, length = 5)
131/// ```
132#[stdlib {
133    name = "segEndY",
134    keywords = true,
135    unlabeled_first = true,
136    args = {
137        tag = { docs = "The line segment being queried by its tag"},
138    }
139}]
140fn inner_segment_end_y(tag: &TagIdentifier, exec_state: &mut ExecState, args: Args) -> Result<f64, KclError> {
141    let line = args.get_tag_engine_info(exec_state, tag)?;
142    let path = line.path.clone().ok_or_else(|| {
143        KclError::Type(KclErrorDetails {
144            message: format!("Expected a line segment with a path, found `{:?}`", line),
145            source_ranges: vec![args.source_range],
146        })
147    })?;
148
149    Ok(path.get_to()[1])
150}
151
152/// Returns the point at the start of the given segment.
153pub async fn segment_start(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
154    let tag: TagIdentifier = args.get_unlabeled_kw_arg("tag")?;
155    let result = inner_segment_start(&tag, exec_state, args.clone())?;
156
157    args.make_user_val_from_point(result)
158}
159
160/// Compute the starting point of the provided line segment.
161///
162/// ```no_run
163/// w = 15
164/// cube = startSketchOn('XY')
165///   |> startProfileAt([0, 0], %)
166///   |> line(end = [w, 0], tag = $line1)
167///   |> line(end = [0, w], tag = $line2)
168///   |> line(end = [-w, 0], tag = $line3)
169///   |> line(end = [0, -w], tag = $line4)
170///   |> close()
171///   |> extrude(length = 5)
172///
173/// fn cylinder(radius, tag) {
174///   return startSketchOn('XY')
175///   |> startProfileAt([0, 0], %)
176///   |> circle( radius = radius, center = segStart(tag) )
177///   |> extrude(length = radius)
178/// }
179///
180/// cylinder(1, line1)
181/// cylinder(2, line2)
182/// cylinder(3, line3)
183/// cylinder(4, line4)
184/// ```
185#[stdlib {
186    name = "segStart",
187    keywords = true,
188    unlabeled_first = true,
189    args = {
190        tag = { docs = "The line segment being queried by its tag"},
191    }
192}]
193fn inner_segment_start(tag: &TagIdentifier, exec_state: &mut ExecState, args: Args) -> Result<[f64; 2], KclError> {
194    let line = args.get_tag_engine_info(exec_state, tag)?;
195    let path = line.path.clone().ok_or_else(|| {
196        KclError::Type(KclErrorDetails {
197            message: format!("Expected a line segment with a path, found `{:?}`", line),
198            source_ranges: vec![args.source_range],
199        })
200    })?;
201
202    Ok(path.get_from().to_owned())
203}
204
205/// Returns the segment start of x.
206pub async fn segment_start_x(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
207    let tag: TagIdentifier = args.get_unlabeled_kw_arg("tag")?;
208    let result = inner_segment_start_x(&tag, exec_state, args.clone())?;
209
210    Ok(args.make_user_val_from_f64(result))
211}
212
213/// Compute the starting point of the provided line segment along the 'x' axis.
214///
215/// ```no_run
216/// exampleSketch = startSketchOn('XZ')
217///   |> startProfileAt([0, 0], %)
218///   |> line(end = [20, 0], tag = $thing)
219///   |> line(end = [0, 5])
220///   |> line(end = [20 - segStartX(thing), 0])
221///   |> line(end = [-20, 10])
222///   |> close()
223///  
224/// example = extrude(exampleSketch, length = 5)
225/// ```
226#[stdlib {
227    name = "segStartX",
228    keywords = true,
229    unlabeled_first = true,
230    args = {
231        tag = { docs = "The line segment being queried by its tag"},
232    }
233}]
234fn inner_segment_start_x(tag: &TagIdentifier, exec_state: &mut ExecState, args: Args) -> Result<f64, KclError> {
235    let line = args.get_tag_engine_info(exec_state, tag)?;
236    let path = line.path.clone().ok_or_else(|| {
237        KclError::Type(KclErrorDetails {
238            message: format!("Expected a line segment with a path, found `{:?}`", line),
239            source_ranges: vec![args.source_range],
240        })
241    })?;
242
243    Ok(path.get_from()[0])
244}
245
246/// Returns the segment start of y.
247pub async fn segment_start_y(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
248    let tag: TagIdentifier = args.get_unlabeled_kw_arg("tag")?;
249    let result = inner_segment_start_y(&tag, exec_state, args.clone())?;
250
251    Ok(args.make_user_val_from_f64(result))
252}
253
254/// Compute the starting point of the provided line segment along the 'y' axis.
255///
256/// ```no_run
257/// exampleSketch = startSketchOn('XZ')
258///   |> startProfileAt([0, 0], %)
259///   |> line(end = [20, 0])
260///   |> line(end = [0, 3], tag = $thing)
261///   |> line(end = [-10, 0])
262///   |> line(end = [0, 20-segStartY(thing)])
263///   |> line(end = [-10, 0])
264///   |> close()
265///  
266/// example = extrude(exampleSketch, length = 5)
267/// ```
268#[stdlib {
269    name = "segStartY",
270    keywords = true,
271    unlabeled_first = true,
272    args = {
273        tag = { docs = "The line segment being queried by its tag"},
274    }
275}]
276fn inner_segment_start_y(tag: &TagIdentifier, exec_state: &mut ExecState, args: Args) -> Result<f64, KclError> {
277    let line = args.get_tag_engine_info(exec_state, tag)?;
278    let path = line.path.clone().ok_or_else(|| {
279        KclError::Type(KclErrorDetails {
280            message: format!("Expected a line segment with a path, found `{:?}`", line),
281            source_ranges: vec![args.source_range],
282        })
283    })?;
284
285    Ok(path.get_from()[1])
286}
287/// Returns the last segment of x.
288pub async fn last_segment_x(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
289    let sketch =
290        args.get_unlabeled_kw_arg_typed("sketch", &RuntimeType::Primitive(PrimitiveType::Sketch), exec_state)?;
291    let result = inner_last_segment_x(sketch, args.clone())?;
292
293    Ok(args.make_user_val_from_f64(result))
294}
295
296/// Extract the 'x' axis value of the last line segment in the provided 2-d
297/// sketch.
298///
299/// ```no_run
300/// exampleSketch = startSketchOn("XZ")
301///   |> startProfileAt([0, 0], %)
302///   |> line(end = [5, 0])
303///   |> line(end = [20, 5])
304///   |> line(end = [lastSegX(%), 0])
305///   |> line(end = [-15, 0])
306///   |> close()
307///
308/// example = extrude(exampleSketch, length = 5)
309/// ```
310#[stdlib {
311    name = "lastSegX",
312    keywords = true,
313    unlabeled_first = true,
314    args = {
315        sketch = { docs = "The sketch whose line segment is being queried"},
316    }
317}]
318fn inner_last_segment_x(sketch: Sketch, args: Args) -> Result<f64, KclError> {
319    let last_line = sketch
320        .paths
321        .last()
322        .ok_or_else(|| {
323            KclError::Type(KclErrorDetails {
324                message: format!("Expected a Sketch with at least one segment, found `{:?}`", sketch),
325                source_ranges: vec![args.source_range],
326            })
327        })?
328        .get_base();
329
330    Ok(last_line.to[0])
331}
332
333/// Returns the last segment of y.
334pub async fn last_segment_y(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
335    let sketch =
336        args.get_unlabeled_kw_arg_typed("sketch", &RuntimeType::Primitive(PrimitiveType::Sketch), exec_state)?;
337    let result = inner_last_segment_y(sketch, args.clone())?;
338
339    Ok(args.make_user_val_from_f64(result))
340}
341
342/// Extract the 'y' axis value of the last line segment in the provided 2-d
343/// sketch.
344///
345/// ```no_run
346/// exampleSketch = startSketchOn("XZ")
347///   |> startProfileAt([0, 0], %)
348///   |> line(end = [5, 0])
349///   |> line(end = [20, 5])
350///   |> line(end = [0, lastSegY(%)])
351///   |> line(end = [-15, 0])
352///   |> close()
353///
354/// example = extrude(exampleSketch, length = 5)
355/// ```
356#[stdlib {
357    name = "lastSegY",
358    keywords = true,
359    unlabeled_first = true,
360    args = {
361        sketch = { docs = "The sketch whose line segment is being queried"},
362    }
363}]
364fn inner_last_segment_y(sketch: Sketch, args: Args) -> Result<f64, KclError> {
365    let last_line = sketch
366        .paths
367        .last()
368        .ok_or_else(|| {
369            KclError::Type(KclErrorDetails {
370                message: format!("Expected a Sketch with at least one segment, found `{:?}`", sketch),
371                source_ranges: vec![args.source_range],
372            })
373        })?
374        .get_base();
375
376    Ok(last_line.to[1])
377}
378
379/// Returns the length of the segment.
380pub async fn segment_length(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
381    let tag: TagIdentifier = args.get_unlabeled_kw_arg("tag")?;
382    let result = inner_segment_length(&tag, exec_state, args.clone())?;
383    Ok(args.make_user_val_from_f64(result))
384}
385
386/// Compute the length of the provided line segment.
387///
388/// ```no_run
389/// exampleSketch = startSketchOn("XZ")
390///   |> startProfileAt([0, 0], %)
391///   |> angledLine({
392///     angle = 60,
393///     length = 10,
394///   }, %, $thing)
395///   |> tangentialArc({
396///     offset = -120,
397///     radius = 5,
398///   }, %)
399///   |> angledLine({
400///     angle = -60,
401///     length = segLen(thing),
402///   }, %)
403///   |> close()
404///
405/// example = extrude(exampleSketch, length = 5)
406/// ```
407#[stdlib {
408    name = "segLen",
409    keywords = true,
410    unlabeled_first = true,
411    args = {
412        tag = { docs = "The line segment being queried by its tag"},
413    }
414}]
415fn inner_segment_length(tag: &TagIdentifier, exec_state: &mut ExecState, args: Args) -> Result<f64, KclError> {
416    let line = args.get_tag_engine_info(exec_state, tag)?;
417    let path = line.path.clone().ok_or_else(|| {
418        KclError::Type(KclErrorDetails {
419            message: format!("Expected a line segment with a path, found `{:?}`", line),
420            source_ranges: vec![args.source_range],
421        })
422    })?;
423
424    let result = path.length();
425
426    Ok(result)
427}
428
429/// Returns the angle of the segment.
430pub async fn segment_angle(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
431    let tag: TagIdentifier = args.get_unlabeled_kw_arg("tag")?;
432
433    let result = inner_segment_angle(&tag, exec_state, args.clone())?;
434    Ok(args.make_user_val_from_f64(result))
435}
436
437/// Compute the angle (in degrees) of the provided line segment.
438///
439/// ```no_run
440/// exampleSketch = startSketchOn('XZ')
441///   |> startProfileAt([0, 0], %)
442///   |> line(end = [10, 0])
443///   |> line(end = [5, 10], tag = $seg01)
444///   |> line(end = [-10, 0])
445///   |> angledLine([segAng(seg01), 10], %)
446///   |> line(end = [-10, 0])
447///   |> angledLine([segAng(seg01), -15], %)
448///   |> close()
449///
450/// example = extrude(exampleSketch, length = 4)
451/// ```
452#[stdlib {
453    name = "segAng",
454    keywords = true,
455    unlabeled_first = true,
456    args = {
457        tag = { docs = "The line segment being queried by its tag"},
458    }
459}]
460fn inner_segment_angle(tag: &TagIdentifier, exec_state: &mut ExecState, args: Args) -> Result<f64, KclError> {
461    let line = args.get_tag_engine_info(exec_state, tag)?;
462    let path = line.path.clone().ok_or_else(|| {
463        KclError::Type(KclErrorDetails {
464            message: format!("Expected a line segment with a path, found `{:?}`", line),
465            source_ranges: vec![args.source_range],
466        })
467    })?;
468
469    let result = between(path.get_from().into(), path.get_to().into());
470
471    Ok(result.to_degrees())
472}
473
474/// Returns the angle coming out of the end of the segment in degrees.
475pub async fn tangent_to_end(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
476    let tag: TagIdentifier = args.get_unlabeled_kw_arg("tag")?;
477
478    let result = inner_tangent_to_end(&tag, exec_state, args.clone()).await?;
479    Ok(args.make_user_val_from_f64(result))
480}
481
482/// Returns the angle coming out of the end of the segment in degrees.
483///
484/// ```no_run
485/// // Horizontal pill.
486/// pillSketch = startSketchOn('XZ')
487///   |> startProfileAt([0, 0], %)
488///   |> line(end = [20, 0])
489///   |> tangentialArcToRelative([0, 10], %, $arc1)
490///   |> angledLine({
491///     angle: tangentToEnd(arc1),
492///     length: 20,
493///   }, %)
494///   |> tangentialArcToRelative([0, -10], %)
495///   |> close()
496///
497/// pillExtrude = extrude(pillSketch, length = 10)
498/// ```
499///
500/// ```no_run
501/// // Vertical pill.  Use absolute coordinate for arc.
502/// pillSketch = startSketchOn('XZ')
503///   |> startProfileAt([0, 0], %)
504///   |> line(end = [0, 20])
505///   |> tangentialArcTo([10, 20], %, $arc1)
506///   |> angledLine({
507///     angle: tangentToEnd(arc1),
508///     length: 20,
509///   }, %)
510///   |> tangentialArcToRelative([-10, 0], %)
511///   |> close()
512///
513/// pillExtrude = extrude(pillSketch, length = 10)
514/// ```
515///
516/// ```no_run
517/// rectangleSketch = startSketchOn('XZ')
518///   |> startProfileAt([0, 0], %)
519///   |> line(end = [10, 0], tag = $seg1)
520///   |> angledLine({
521///     angle: tangentToEnd(seg1),
522///     length: 10,
523///   }, %)
524///   |> line(end = [0, 10])
525///   |> line(end = [-20, 0])
526///   |> close()
527///
528/// rectangleExtrude = extrude(rectangleSketch, length = 10)
529/// ```
530///
531/// ```no_run
532/// bottom = startSketchOn("XY")
533///   |> startProfileAt([0, 0], %)
534///   |> arcTo({
535///        end: [10, 10],
536///        interior: [5, 1]
537///      }, %, $arc1)
538///   |> angledLine([tangentToEnd(arc1), 20], %)
539///   |> close()
540/// ```
541///
542/// ```no_run
543/// circSketch = startSketchOn("XY")
544///   |> circle( center= [0, 0], radius= 3 , tag= $circ)
545///
546/// triangleSketch = startSketchOn("XY")
547///   |> startProfileAt([-5, 0], %)
548///   |> angledLine([tangentToEnd(circ), 10], %)
549///   |> line(end = [-15, 0])
550///   |> close()
551/// ```
552#[stdlib {
553    name = "tangentToEnd",
554    keywords = true,
555    unlabeled_first = true,
556    args = {
557        tag = { docs = "The line segment being queried by its tag"},
558    }
559}]
560async fn inner_tangent_to_end(tag: &TagIdentifier, exec_state: &mut ExecState, args: Args) -> Result<f64, KclError> {
561    let line = args.get_tag_engine_info(exec_state, tag)?;
562    let path = line.path.clone().ok_or_else(|| {
563        KclError::Type(KclErrorDetails {
564            message: format!("Expected a line segment with a path, found `{:?}`", line),
565            source_ranges: vec![args.source_range],
566        })
567    })?;
568
569    let from = Point2d::from(path.get_to());
570
571    // Undocumented voodoo from get_tangential_arc_to_info
572    let tangent_info = path.get_tangential_info();
573    let tan_previous_point = tangent_info.tan_previous_point(from.into());
574
575    // Calculate the end point from the angle and radius.
576    // atan2 outputs radians.
577    let previous_end_tangent = Angle::from_radians(f64::atan2(
578        from.y - tan_previous_point[1],
579        from.x - tan_previous_point[0],
580    ));
581
582    Ok(previous_end_tangent.to_degrees())
583}
584
585/// Returns the angle to match the given length for x.
586pub async fn angle_to_match_length_x(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
587    let (tag, to, sketch) = args.get_tag_to_number_sketch()?;
588    let result = inner_angle_to_match_length_x(&tag, to, sketch, exec_state, args.clone())?;
589    Ok(args.make_user_val_from_f64(result))
590}
591
592/// Returns the angle to match the given length for x.
593///
594/// ```no_run
595/// sketch001 = startSketchOn('XZ')
596///   |> startProfileAt([0, 0], %)
597///   |> line(end = [2, 5], tag = $seg01)
598///   |> angledLineToX([
599///        -angleToMatchLengthX(seg01, 7, %),
600///        10
601///      ], %)
602///   |> close()
603///
604/// extrusion = extrude(sketch001, length = 5)
605/// ```
606#[stdlib {
607    name = "angleToMatchLengthX",
608}]
609fn inner_angle_to_match_length_x(
610    tag: &TagIdentifier,
611    to: f64,
612    sketch: Sketch,
613    exec_state: &mut ExecState,
614    args: Args,
615) -> Result<f64, KclError> {
616    let line = args.get_tag_engine_info(exec_state, tag)?;
617    let path = line.path.clone().ok_or_else(|| {
618        KclError::Type(KclErrorDetails {
619            message: format!("Expected a line segment with a path, found `{:?}`", line),
620            source_ranges: vec![args.source_range],
621        })
622    })?;
623
624    let length = path.length();
625
626    let last_line = sketch
627        .paths
628        .last()
629        .ok_or_else(|| {
630            KclError::Type(KclErrorDetails {
631                message: format!("Expected a Sketch with at least one segment, found `{:?}`", sketch),
632                source_ranges: vec![args.source_range],
633            })
634        })?
635        .get_base();
636
637    let diff = (to - last_line.to[0]).abs();
638
639    let angle_r = (diff / length).acos();
640
641    if diff > length {
642        Ok(0.0)
643    } else {
644        Ok(angle_r.to_degrees())
645    }
646}
647
648/// Returns the angle to match the given length for y.
649pub async fn angle_to_match_length_y(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
650    let (tag, to, sketch) = args.get_tag_to_number_sketch()?;
651    let result = inner_angle_to_match_length_y(&tag, to, sketch, exec_state, args.clone())?;
652    Ok(args.make_user_val_from_f64(result))
653}
654
655/// Returns the angle to match the given length for y.
656///
657/// ```no_run
658/// sketch001 = startSketchOn('XZ')
659///   |> startProfileAt([0, 0], %)
660///   |> line(end = [1, 2], tag = $seg01)
661///   |> angledLine({
662///     angle = angleToMatchLengthY(seg01, 15, %),
663///     length = 5,
664///     }, %)
665///   |> yLine(endAbsolute = 0)
666///   |> close()
667///  
668/// extrusion = extrude(sketch001, length = 5)
669/// ```
670#[stdlib {
671    name = "angleToMatchLengthY",
672}]
673fn inner_angle_to_match_length_y(
674    tag: &TagIdentifier,
675    to: f64,
676    sketch: Sketch,
677    exec_state: &mut ExecState,
678    args: Args,
679) -> Result<f64, KclError> {
680    let line = args.get_tag_engine_info(exec_state, tag)?;
681    let path = line.path.clone().ok_or_else(|| {
682        KclError::Type(KclErrorDetails {
683            message: format!("Expected a line segment with a path, found `{:?}`", line),
684            source_ranges: vec![args.source_range],
685        })
686    })?;
687
688    let length = path.length();
689
690    let last_line = sketch
691        .paths
692        .last()
693        .ok_or_else(|| {
694            KclError::Type(KclErrorDetails {
695                message: format!("Expected a Sketch with at least one segment, found `{:?}`", sketch),
696                source_ranges: vec![args.source_range],
697            })
698        })?
699        .get_base();
700
701    let diff = (to - last_line.to[1]).abs();
702
703    let angle_r = (diff / length).asin();
704
705    if diff > length {
706        Ok(0.0)
707    } else {
708        Ok(angle_r.to_degrees())
709    }
710}