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