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