kcl_lib/std/
segment.rs

1//! Functions related to line segments.
2
3use anyhow::Result;
4use kittycad_modeling_cmds::shared::Angle;
5
6use super::utils::untype_point;
7use crate::{
8    errors::{KclError, KclErrorDetails},
9    execution::{
10        ExecState, KclValue, Sketch, TagIdentifier,
11        types::{NumericType, PrimitiveType, RuntimeType},
12    },
13    std::{Args, args::TyF64, utils::between},
14};
15
16/// Returns the point at the end of the given segment.
17pub async fn segment_end(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
18    let tag: TagIdentifier = args.get_unlabeled_kw_arg("tag", &RuntimeType::tagged_edge(), exec_state)?;
19    let pt = inner_segment_end(&tag, exec_state, args.clone())?;
20
21    args.make_kcl_val_from_point([pt[0].n, pt[1].n], pt[0].ty)
22}
23
24fn inner_segment_end(tag: &TagIdentifier, exec_state: &mut ExecState, args: Args) -> Result<[TyF64; 2], KclError> {
25    let line = args.get_tag_engine_info(exec_state, tag)?;
26    let path = line.path.clone().ok_or_else(|| {
27        KclError::new_type(KclErrorDetails::new(
28            format!("Expected a line segment with a path, found `{line:?}`"),
29            vec![args.source_range],
30        ))
31    })?;
32    let (p, ty) = path.end_point_components();
33    // Docs generation isn't smart enough to handle ([f64; 2], NumericType).
34    let point = [TyF64::new(p[0], ty), TyF64::new(p[1], ty)];
35
36    Ok(point)
37}
38
39/// Returns the segment end of x.
40pub async fn segment_end_x(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
41    let tag: TagIdentifier = args.get_unlabeled_kw_arg("tag", &RuntimeType::tagged_edge(), exec_state)?;
42    let result = inner_segment_end_x(&tag, exec_state, args.clone())?;
43
44    Ok(args.make_user_val_from_f64_with_type(result))
45}
46
47fn inner_segment_end_x(tag: &TagIdentifier, exec_state: &mut ExecState, args: Args) -> Result<TyF64, KclError> {
48    let line = args.get_tag_engine_info(exec_state, tag)?;
49    let path = line.path.clone().ok_or_else(|| {
50        KclError::new_type(KclErrorDetails::new(
51            format!("Expected a line segment with a path, found `{line:?}`"),
52            vec![args.source_range],
53        ))
54    })?;
55
56    Ok(TyF64::new(path.get_base().to[0], path.get_base().units.into()))
57}
58
59/// Returns the segment end of y.
60pub async fn segment_end_y(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
61    let tag: TagIdentifier = args.get_unlabeled_kw_arg("tag", &RuntimeType::tagged_edge(), exec_state)?;
62    let result = inner_segment_end_y(&tag, exec_state, args.clone())?;
63
64    Ok(args.make_user_val_from_f64_with_type(result))
65}
66
67fn inner_segment_end_y(tag: &TagIdentifier, exec_state: &mut ExecState, args: Args) -> Result<TyF64, KclError> {
68    let line = args.get_tag_engine_info(exec_state, tag)?;
69    let path = line.path.clone().ok_or_else(|| {
70        KclError::new_type(KclErrorDetails::new(
71            format!("Expected a line segment with a path, found `{line:?}`"),
72            vec![args.source_range],
73        ))
74    })?;
75
76    Ok(path.get_to()[1].clone())
77}
78
79/// Returns the point at the start of the given segment.
80pub async fn segment_start(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
81    let tag: TagIdentifier = args.get_unlabeled_kw_arg("tag", &RuntimeType::tagged_edge(), exec_state)?;
82    let pt = inner_segment_start(&tag, exec_state, args.clone())?;
83
84    args.make_kcl_val_from_point([pt[0].n, pt[1].n], pt[0].ty)
85}
86
87fn inner_segment_start(tag: &TagIdentifier, exec_state: &mut ExecState, args: Args) -> Result<[TyF64; 2], KclError> {
88    let line = args.get_tag_engine_info(exec_state, tag)?;
89    let path = line.path.clone().ok_or_else(|| {
90        KclError::new_type(KclErrorDetails::new(
91            format!("Expected a line segment with a path, found `{line:?}`"),
92            vec![args.source_range],
93        ))
94    })?;
95    let (p, ty) = path.start_point_components();
96    // Docs generation isn't smart enough to handle ([f64; 2], NumericType).
97    let point = [TyF64::new(p[0], ty), TyF64::new(p[1], ty)];
98
99    Ok(point)
100}
101
102/// Returns the segment start of x.
103pub async fn segment_start_x(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
104    let tag: TagIdentifier = args.get_unlabeled_kw_arg("tag", &RuntimeType::tagged_edge(), exec_state)?;
105    let result = inner_segment_start_x(&tag, exec_state, args.clone())?;
106
107    Ok(args.make_user_val_from_f64_with_type(result))
108}
109
110fn inner_segment_start_x(tag: &TagIdentifier, exec_state: &mut ExecState, args: Args) -> Result<TyF64, KclError> {
111    let line = args.get_tag_engine_info(exec_state, tag)?;
112    let path = line.path.clone().ok_or_else(|| {
113        KclError::new_type(KclErrorDetails::new(
114            format!("Expected a line segment with a path, found `{line:?}`"),
115            vec![args.source_range],
116        ))
117    })?;
118
119    Ok(path.get_from()[0].clone())
120}
121
122/// Returns the segment start of y.
123pub async fn segment_start_y(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
124    let tag: TagIdentifier = args.get_unlabeled_kw_arg("tag", &RuntimeType::tagged_edge(), exec_state)?;
125    let result = inner_segment_start_y(&tag, exec_state, args.clone())?;
126
127    Ok(args.make_user_val_from_f64_with_type(result))
128}
129
130fn inner_segment_start_y(tag: &TagIdentifier, exec_state: &mut ExecState, args: Args) -> Result<TyF64, KclError> {
131    let line = args.get_tag_engine_info(exec_state, tag)?;
132    let path = line.path.clone().ok_or_else(|| {
133        KclError::new_type(KclErrorDetails::new(
134            format!("Expected a line segment with a path, found `{line:?}`"),
135            vec![args.source_range],
136        ))
137    })?;
138
139    Ok(path.get_from()[1].clone())
140}
141/// Returns the last segment of x.
142pub async fn last_segment_x(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
143    let sketch = args.get_unlabeled_kw_arg("sketch", &RuntimeType::Primitive(PrimitiveType::Sketch), exec_state)?;
144    let result = inner_last_segment_x(sketch, args.clone())?;
145
146    Ok(args.make_user_val_from_f64_with_type(result))
147}
148
149fn inner_last_segment_x(sketch: Sketch, args: Args) -> Result<TyF64, KclError> {
150    let last_line = sketch
151        .paths
152        .last()
153        .ok_or_else(|| {
154            KclError::new_type(KclErrorDetails::new(
155                format!("Expected a Sketch with at least one segment, found `{sketch:?}`"),
156                vec![args.source_range],
157            ))
158        })?
159        .get_base();
160
161    Ok(TyF64::new(last_line.to[0], last_line.units.into()))
162}
163
164/// Returns the last segment of y.
165pub async fn last_segment_y(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
166    let sketch = args.get_unlabeled_kw_arg("sketch", &RuntimeType::Primitive(PrimitiveType::Sketch), exec_state)?;
167    let result = inner_last_segment_y(sketch, args.clone())?;
168
169    Ok(args.make_user_val_from_f64_with_type(result))
170}
171
172fn inner_last_segment_y(sketch: Sketch, args: Args) -> Result<TyF64, KclError> {
173    let last_line = sketch
174        .paths
175        .last()
176        .ok_or_else(|| {
177            KclError::new_type(KclErrorDetails::new(
178                format!("Expected a Sketch with at least one segment, found `{sketch:?}`"),
179                vec![args.source_range],
180            ))
181        })?
182        .get_base();
183
184    Ok(TyF64::new(last_line.to[1], last_line.units.into()))
185}
186
187/// Returns the length of the segment.
188pub async fn segment_length(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
189    let tag: TagIdentifier = args.get_unlabeled_kw_arg("tag", &RuntimeType::tagged_edge(), exec_state)?;
190    let result = inner_segment_length(&tag, exec_state, args.clone())?;
191    Ok(args.make_user_val_from_f64_with_type(result))
192}
193
194fn inner_segment_length(tag: &TagIdentifier, exec_state: &mut ExecState, args: Args) -> Result<TyF64, KclError> {
195    let line = args.get_tag_engine_info(exec_state, tag)?;
196    let path = line.path.clone().ok_or_else(|| {
197        KclError::new_type(KclErrorDetails::new(
198            format!("Expected a line segment with a path, found `{line:?}`"),
199            vec![args.source_range],
200        ))
201    })?;
202
203    Ok(path.length())
204}
205
206/// Returns the angle of the segment.
207pub async fn segment_angle(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
208    let tag: TagIdentifier = args.get_unlabeled_kw_arg("tag", &RuntimeType::tagged_edge(), exec_state)?;
209
210    let result = inner_segment_angle(&tag, exec_state, args.clone())?;
211    Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, NumericType::degrees())))
212}
213
214fn inner_segment_angle(tag: &TagIdentifier, exec_state: &mut ExecState, args: Args) -> Result<f64, KclError> {
215    let line = args.get_tag_engine_info(exec_state, tag)?;
216    let path = line.path.clone().ok_or_else(|| {
217        KclError::new_type(KclErrorDetails::new(
218            format!("Expected a line segment with a path, found `{line:?}`"),
219            vec![args.source_range],
220        ))
221    })?;
222
223    let result = between(path.get_base().from, path.get_base().to);
224
225    Ok(result.to_degrees())
226}
227
228/// Returns the angle coming out of the end of the segment in degrees.
229pub async fn tangent_to_end(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
230    let tag: TagIdentifier = args.get_unlabeled_kw_arg("tag", &RuntimeType::tagged_edge(), exec_state)?;
231
232    let result = inner_tangent_to_end(&tag, exec_state, args.clone()).await?;
233    Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, NumericType::degrees())))
234}
235
236async fn inner_tangent_to_end(tag: &TagIdentifier, exec_state: &mut ExecState, args: Args) -> Result<f64, KclError> {
237    let line = args.get_tag_engine_info(exec_state, tag)?;
238    let path = line.path.clone().ok_or_else(|| {
239        KclError::new_type(KclErrorDetails::new(
240            format!("Expected a line segment with a path, found `{line:?}`"),
241            vec![args.source_range],
242        ))
243    })?;
244
245    let from = untype_point(path.get_to()).0;
246
247    // Undocumented voodoo from get_tangential_arc_to_info
248    let tangent_info = path.get_tangential_info();
249    let tan_previous_point = tangent_info.tan_previous_point(from);
250
251    // Calculate the end point from the angle and radius.
252    // atan2 outputs radians.
253    let previous_end_tangent = Angle::from_radians(libm::atan2(
254        from[1] - tan_previous_point[1],
255        from[0] - tan_previous_point[0],
256    ));
257
258    Ok(previous_end_tangent.to_degrees())
259}