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