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