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 result = inner_segment_end(&tag, exec_state, args.clone())?;
21
22    args.make_user_val_from_point(result)
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    keywords = true,
53    unlabeled_first = true,
54    args = {
55        tag = { docs = "The line segment being queried by its tag"},
56    },
57    tags = ["sketch"]
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///   |> startProfile(at = [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    tags = ["sketch"]
100}]
101fn inner_segment_end_x(tag: &TagIdentifier, exec_state: &mut ExecState, args: Args) -> Result<TyF64, KclError> {
102    let line = args.get_tag_engine_info(exec_state, tag)?;
103    let path = line.path.clone().ok_or_else(|| {
104        KclError::Type(KclErrorDetails {
105            message: format!("Expected a line segment with a path, found `{:?}`", line),
106            source_ranges: vec![args.source_range],
107        })
108    })?;
109
110    Ok(TyF64::new(path.get_base().to[0], path.get_base().units.into()))
111}
112
113/// Returns the segment end of y.
114pub async fn segment_end_y(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
115    let tag: TagIdentifier = args.get_unlabeled_kw_arg("tag")?;
116    let result = inner_segment_end_y(&tag, exec_state, args.clone())?;
117
118    Ok(args.make_user_val_from_f64_with_type(result))
119}
120
121/// Compute the ending point of the provided line segment along the 'y' axis.
122///
123/// ```no_run
124/// exampleSketch = startSketchOn(XZ)
125///   |> startProfile(at = [0, 0])
126///   |> line(end = [20, 0])
127///   |> line(end = [0, 3], tag = $thing)
128///   |> line(end = [-10, 0])
129///   |> line(end = [0, segEndY(thing)])
130///   |> line(end = [-10, 0])
131///   |> close()
132///  
133/// example = extrude(exampleSketch, length = 5)
134/// ```
135#[stdlib {
136    name = "segEndY",
137    keywords = true,
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 {
148            message: format!("Expected a line segment with a path, found `{:?}`", line),
149            source_ranges: 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 result = inner_segment_start(&tag, exec_state, args.clone())?;
160
161    args.make_user_val_from_point(result)
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    keywords = true,
192    unlabeled_first = true,
193    args = {
194        tag = { docs = "The line segment being queried by its tag"},
195    },
196    tags = ["sketch"]
197}]
198fn inner_segment_start(tag: &TagIdentifier, exec_state: &mut ExecState, args: Args) -> Result<[TyF64; 2], KclError> {
199    let line = args.get_tag_engine_info(exec_state, tag)?;
200    let path = line.path.clone().ok_or_else(|| {
201        KclError::Type(KclErrorDetails {
202            message: format!("Expected a line segment with a path, found `{:?}`", line),
203            source_ranges: vec![args.source_range],
204        })
205    })?;
206
207    Ok(path.get_from().to_owned())
208}
209
210/// Returns the segment start of x.
211pub async fn segment_start_x(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
212    let tag: TagIdentifier = args.get_unlabeled_kw_arg("tag")?;
213    let result = inner_segment_start_x(&tag, exec_state, args.clone())?;
214
215    Ok(args.make_user_val_from_f64_with_type(result))
216}
217
218/// Compute the starting point of the provided line segment along the 'x' axis.
219///
220/// ```no_run
221/// exampleSketch = startSketchOn(XZ)
222///   |> startProfile(at = [0, 0])
223///   |> line(end = [20, 0], tag = $thing)
224///   |> line(end = [0, 5])
225///   |> line(end = [20 - segStartX(thing), 0])
226///   |> line(end = [-20, 10])
227///   |> close()
228///  
229/// example = extrude(exampleSketch, length = 5)
230/// ```
231#[stdlib {
232    name = "segStartX",
233    keywords = true,
234    unlabeled_first = true,
235    args = {
236        tag = { docs = "The line segment being queried by its tag"},
237    },
238    tags = ["sketch"]
239}]
240fn inner_segment_start_x(tag: &TagIdentifier, exec_state: &mut ExecState, args: Args) -> Result<TyF64, KclError> {
241    let line = args.get_tag_engine_info(exec_state, tag)?;
242    let path = line.path.clone().ok_or_else(|| {
243        KclError::Type(KclErrorDetails {
244            message: format!("Expected a line segment with a path, found `{:?}`", line),
245            source_ranges: vec![args.source_range],
246        })
247    })?;
248
249    Ok(path.get_from()[0].clone())
250}
251
252/// Returns the segment start of y.
253pub async fn segment_start_y(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
254    let tag: TagIdentifier = args.get_unlabeled_kw_arg("tag")?;
255    let result = inner_segment_start_y(&tag, exec_state, args.clone())?;
256
257    Ok(args.make_user_val_from_f64_with_type(result))
258}
259
260/// Compute the starting point of the provided line segment along the 'y' axis.
261///
262/// ```no_run
263/// exampleSketch = startSketchOn(XZ)
264///   |> startProfile(at = [0, 0])
265///   |> line(end = [20, 0])
266///   |> line(end = [0, 3], tag = $thing)
267///   |> line(end = [-10, 0])
268///   |> line(end = [0, 20-segStartY(thing)])
269///   |> line(end = [-10, 0])
270///   |> close()
271///  
272/// example = extrude(exampleSketch, length = 5)
273/// ```
274#[stdlib {
275    name = "segStartY",
276    keywords = true,
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 {
287            message: format!("Expected a line segment with a path, found `{:?}`", line),
288            source_ranges: 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    keywords = true,
320    unlabeled_first = true,
321    args = {
322        sketch = { docs = "The sketch whose line segment is being queried"},
323    },
324    tags = ["sketch"]
325}]
326fn inner_last_segment_x(sketch: Sketch, args: Args) -> Result<TyF64, KclError> {
327    let last_line = sketch
328        .paths
329        .last()
330        .ok_or_else(|| {
331            KclError::Type(KclErrorDetails {
332                message: format!("Expected a Sketch with at least one segment, found `{:?}`", sketch),
333                source_ranges: vec![args.source_range],
334            })
335        })?
336        .get_base();
337
338    Ok(TyF64::new(last_line.to[0], last_line.units.into()))
339}
340
341/// Returns the last segment of y.
342pub async fn last_segment_y(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
343    let sketch =
344        args.get_unlabeled_kw_arg_typed("sketch", &RuntimeType::Primitive(PrimitiveType::Sketch), exec_state)?;
345    let result = inner_last_segment_y(sketch, args.clone())?;
346
347    Ok(args.make_user_val_from_f64_with_type(result))
348}
349
350/// Extract the 'y' axis value of the last line segment in the provided 2-d
351/// sketch.
352///
353/// ```no_run
354/// exampleSketch = startSketchOn(XZ)
355///   |> startProfile(at = [0, 0])
356///   |> line(end = [5, 0])
357///   |> line(end = [20, 5])
358///   |> line(end = [0, lastSegY(%)])
359///   |> line(end = [-15, 0])
360///   |> close()
361///
362/// example = extrude(exampleSketch, length = 5)
363/// ```
364#[stdlib {
365    name = "lastSegY",
366    keywords = true,
367    unlabeled_first = true,
368    args = {
369        sketch = { docs = "The sketch whose line segment is being queried"},
370    },
371    tags = ["sketch"]
372}]
373fn inner_last_segment_y(sketch: Sketch, args: Args) -> Result<TyF64, KclError> {
374    let last_line = sketch
375        .paths
376        .last()
377        .ok_or_else(|| {
378            KclError::Type(KclErrorDetails {
379                message: format!("Expected a Sketch with at least one segment, found `{:?}`", sketch),
380                source_ranges: vec![args.source_range],
381            })
382        })?
383        .get_base();
384
385    Ok(TyF64::new(last_line.to[1], last_line.units.into()))
386}
387
388/// Returns the length of the segment.
389pub async fn segment_length(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
390    let tag: TagIdentifier = args.get_unlabeled_kw_arg("tag")?;
391    let result = inner_segment_length(&tag, exec_state, args.clone())?;
392    Ok(args.make_user_val_from_f64_with_type(result))
393}
394
395/// Compute the length of the provided line segment.
396///
397/// ```no_run
398/// exampleSketch = startSketchOn(XZ)
399///   |> startProfile(at = [0, 0])
400///   |> angledLine(
401///     angle = 60,
402///     length = 10,
403///     tag = $thing,
404///   )
405///   |> tangentialArc(angle = -120, radius = 5)
406///   |> angledLine(
407///     angle = -60,
408///     length = segLen(thing),
409///   )
410///   |> close()
411///
412/// example = extrude(exampleSketch, length = 5)
413/// ```
414#[stdlib {
415    name = "segLen",
416    keywords = true,
417    unlabeled_first = true,
418    args = {
419        tag = { docs = "The line segment being queried by its tag"},
420    },
421    tags = ["sketch"]
422}]
423fn inner_segment_length(tag: &TagIdentifier, exec_state: &mut ExecState, args: Args) -> Result<TyF64, KclError> {
424    let line = args.get_tag_engine_info(exec_state, tag)?;
425    let path = line.path.clone().ok_or_else(|| {
426        KclError::Type(KclErrorDetails {
427            message: format!("Expected a line segment with a path, found `{:?}`", line),
428            source_ranges: vec![args.source_range],
429        })
430    })?;
431
432    Ok(path.length())
433}
434
435/// Returns the angle of the segment.
436pub async fn segment_angle(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
437    let tag: TagIdentifier = args.get_unlabeled_kw_arg("tag")?;
438
439    let result = inner_segment_angle(&tag, exec_state, args.clone())?;
440    Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, NumericType::degrees())))
441}
442
443/// Compute the angle (in degrees) of the provided line segment.
444///
445/// ```no_run
446/// exampleSketch = startSketchOn(XZ)
447///   |> startProfile(at = [0, 0])
448///   |> line(end = [10, 0])
449///   |> line(end = [5, 10], tag = $seg01)
450///   |> line(end = [-10, 0])
451///   |> angledLine(angle = segAng(seg01), length = 10)
452///   |> line(end = [-10, 0])
453///   |> angledLine(angle = segAng(seg01), length = -15)
454///   |> close()
455///
456/// example = extrude(exampleSketch, length = 4)
457/// ```
458#[stdlib {
459    name = "segAng",
460    keywords = true,
461    unlabeled_first = true,
462    args = {
463        tag = { docs = "The line segment being queried by its tag"},
464    },
465    tags = ["sketch"]
466}]
467fn inner_segment_angle(tag: &TagIdentifier, exec_state: &mut ExecState, args: Args) -> Result<f64, KclError> {
468    let line = args.get_tag_engine_info(exec_state, tag)?;
469    let path = line.path.clone().ok_or_else(|| {
470        KclError::Type(KclErrorDetails {
471            message: format!("Expected a line segment with a path, found `{:?}`", line),
472            source_ranges: vec![args.source_range],
473        })
474    })?;
475
476    let result = between(path.get_base().from, path.get_base().to);
477
478    Ok(result.to_degrees())
479}
480
481/// Returns the angle coming out of the end of the segment in degrees.
482pub async fn tangent_to_end(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
483    let tag: TagIdentifier = args.get_unlabeled_kw_arg("tag")?;
484
485    let result = inner_tangent_to_end(&tag, exec_state, args.clone()).await?;
486    Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, NumericType::degrees())))
487}
488
489/// Returns the angle coming out of the end of the segment in degrees.
490///
491/// ```no_run
492/// // Horizontal pill.
493/// pillSketch = startSketchOn(XZ)
494///   |> startProfile(at = [0, 0])
495///   |> line(end = [20, 0])
496///   |> tangentialArc(end = [0, 10], tag = $arc1)
497///   |> angledLine(
498///     angle = tangentToEnd(arc1),
499///     length = 20,
500///   )
501///   |> tangentialArc(end = [0, -10])
502///   |> close()
503///
504/// pillExtrude = extrude(pillSketch, length = 10)
505/// ```
506///
507/// ```no_run
508/// // Vertical pill.  Use absolute coordinate for arc.
509/// pillSketch = startSketchOn(XZ)
510///   |> startProfile(at = [0, 0])
511///   |> line(end = [0, 20])
512///   |> tangentialArc(endAbsolute = [10, 20], tag = $arc1)
513///   |> angledLine(
514///     angle = tangentToEnd(arc1),
515///     length = 20,
516///   )
517///   |> tangentialArc(end = [-10, 0])
518///   |> close()
519///
520/// pillExtrude = extrude(pillSketch, length = 10)
521/// ```
522///
523/// ```no_run
524/// rectangleSketch = startSketchOn(XZ)
525///   |> startProfile(at = [0, 0])
526///   |> line(end = [10, 0], tag = $seg1)
527///   |> angledLine(
528///     angle = tangentToEnd(seg1),
529///     length = 10,
530///   )
531///   |> line(end = [0, 10])
532///   |> line(end = [-20, 0])
533///   |> close()
534///
535/// rectangleExtrude = extrude(rectangleSketch, length = 10)
536/// ```
537///
538/// ```no_run
539/// bottom = startSketchOn(XY)
540///   |> startProfile(at = [0, 0])
541///   |> arc(
542///        endAbsolute = [10, 10],
543///        interiorAbsolute = [5, 1],
544///        tag = $arc1,
545///      )
546///   |> angledLine(angle = tangentToEnd(arc1), length = 20)
547///   |> close()
548/// ```
549///
550/// ```no_run
551/// circSketch = startSketchOn(XY)
552///   |> circle( center= [0, 0], radius= 3 , tag= $circ)
553///
554/// triangleSketch = startSketchOn(XY)
555///   |> startProfile(at = [-5, 0])
556///   |> angledLine(angle = tangentToEnd(circ), length = 10)
557///   |> line(end = [-15, 0])
558///   |> close()
559/// ```
560#[stdlib {
561    name = "tangentToEnd",
562    keywords = true,
563    unlabeled_first = true,
564    args = {
565        tag = { docs = "The line segment being queried by its tag"},
566    },
567    tags = ["sketch"]
568}]
569async fn inner_tangent_to_end(tag: &TagIdentifier, exec_state: &mut ExecState, args: Args) -> Result<f64, KclError> {
570    let line = args.get_tag_engine_info(exec_state, tag)?;
571    let path = line.path.clone().ok_or_else(|| {
572        KclError::Type(KclErrorDetails {
573            message: format!("Expected a line segment with a path, found `{:?}`", line),
574            source_ranges: vec![args.source_range],
575        })
576    })?;
577
578    let from = untype_point(path.get_to()).0;
579
580    // Undocumented voodoo from get_tangential_arc_to_info
581    let tangent_info = path.get_tangential_info();
582    let tan_previous_point = tangent_info.tan_previous_point(from);
583
584    // Calculate the end point from the angle and radius.
585    // atan2 outputs radians.
586    let previous_end_tangent = Angle::from_radians(f64::atan2(
587        from[1] - tan_previous_point[1],
588        from[0] - tan_previous_point[0],
589    ));
590
591    Ok(previous_end_tangent.to_degrees())
592}