Skip to main content

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