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