1use anyhow::Result;
4use kcl_api::UnitLength;
5use kcmc::ModelingCmd;
6use kcmc::each_cmd as mcmd;
7use kcmc::length_unit::LengthUnit;
8use kcmc::shared::Angle;
9use kcmc::shared::Point2d as KPoint2d;
10use kittycad_modeling_cmds::shared::PathSegment;
11use kittycad_modeling_cmds::{self as kcmc};
12use serde::Serialize;
13
14use super::args::TyF64;
15use super::utils::point_to_len_unit;
16use super::utils::point_to_mm;
17use super::utils::point_to_typed;
18use super::utils::untype_point;
19use super::utils::untyped_point_to_mm;
20use crate::SourceRange;
21use crate::errors::KclError;
22use crate::errors::KclErrorDetails;
23use crate::execution::BasePath;
24use crate::execution::ExecState;
25use crate::execution::GeoMeta;
26use crate::execution::KclValue;
27use crate::execution::ModelingCmdMeta;
28use crate::execution::Path;
29use crate::execution::ProfileClosed;
30use crate::execution::Sketch;
31use crate::execution::SketchSurface;
32use crate::execution::types::NumericTypeExt;
33use crate::execution::types::RuntimeType;
34use crate::execution::types::adjust_length;
35use crate::parsing::ast::types::TagNode;
36use crate::std::Args;
37use crate::std::utils::calculate_circle_center;
38use crate::std::utils::distance;
39
40#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
42#[ts(export)]
43#[serde(untagged)]
44pub enum SketchOrSurface {
45 SketchSurface(SketchSurface),
46 Sketch(Box<Sketch>),
47}
48
49impl SketchOrSurface {
50 pub fn into_sketch_surface(self) -> SketchSurface {
51 match self {
52 SketchOrSurface::SketchSurface(surface) => surface,
53 SketchOrSurface::Sketch(sketch) => sketch.on,
54 }
55 }
56}
57
58pub async fn rectangle(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
60 let sketch_or_surface =
61 args.get_unlabeled_kw_arg("sketchOrSurface", &RuntimeType::sketch_or_surface(), exec_state)?;
62 let center = args.get_kw_arg_opt("center", &RuntimeType::point2d(), exec_state)?;
63 let corner = args.get_kw_arg_opt("corner", &RuntimeType::point2d(), exec_state)?;
64 let width: TyF64 = args.get_kw_arg("width", &RuntimeType::length(), exec_state)?;
65 let height: TyF64 = args.get_kw_arg("height", &RuntimeType::length(), exec_state)?;
66
67 inner_rectangle(sketch_or_surface, center, corner, width, height, exec_state, args)
68 .await
69 .map(Box::new)
70 .map(|value| KclValue::Sketch { value })
71}
72
73async fn inner_rectangle(
74 sketch_or_surface: SketchOrSurface,
75 center: Option<[TyF64; 2]>,
76 corner: Option<[TyF64; 2]>,
77 width: TyF64,
78 height: TyF64,
79 exec_state: &mut ExecState,
80 args: Args,
81) -> Result<Sketch, KclError> {
82 let sketch_surface = sketch_or_surface.into_sketch_surface();
83
84 let (ty, corner) = match (center, corner) {
86 (Some(center), None) => (
87 center[0].ty,
88 [center[0].n - width.n / 2.0, center[1].n - height.n / 2.0],
89 ),
90 (None, Some(corner)) => (corner[0].ty, [corner[0].n, corner[1].n]),
91 (None, None) => {
92 return Err(KclError::new_semantic(KclErrorDetails::new(
93 "You must supply either `corner` or `center` arguments, but not both".to_string(),
94 vec![args.source_range],
95 )));
96 }
97 (Some(_), Some(_)) => {
98 return Err(KclError::new_semantic(KclErrorDetails::new(
99 "You must supply either `corner` or `center` arguments, but not both".to_string(),
100 vec![args.source_range],
101 )));
102 }
103 };
104 let units = ty.as_length().unwrap_or(UnitLength::Millimeters);
105 let corner_t = [TyF64::new(corner[0], ty), TyF64::new(corner[1], ty)];
106
107 let sketch = crate::std::sketch::inner_start_profile(
109 sketch_surface,
110 corner_t,
111 None,
112 exec_state,
113 &args.ctx,
114 args.source_range,
115 )
116 .await?;
117 let sketch_id = sketch.id;
118 let deltas = [[width.n, 0.0], [0.0, height.n], [-width.n, 0.0], [0.0, -height.n]];
119 let ids = [
120 exec_state.next_uuid(),
121 exec_state.next_uuid(),
122 exec_state.next_uuid(),
123 exec_state.next_uuid(),
124 ];
125 for (id, delta) in ids.iter().copied().zip(deltas) {
126 exec_state
127 .batch_modeling_cmd(
128 ModelingCmdMeta::from_args_id(exec_state, &args, id),
129 ModelingCmd::from(
130 mcmd::ExtendPath::builder()
131 .path(sketch.id.into())
132 .segment(PathSegment::Line {
133 end: KPoint2d::from(untyped_point_to_mm(delta, units))
134 .with_z(0.0)
135 .map(LengthUnit),
136 relative: true,
137 })
138 .build(),
139 ),
140 )
141 .await?;
142 }
143 exec_state
144 .batch_modeling_cmd(
145 ModelingCmdMeta::from_args_id(exec_state, &args, sketch_id),
146 ModelingCmd::from(mcmd::ClosePath::builder().path_id(sketch.id).build()),
147 )
148 .await?;
149
150 let mut new_sketch = sketch;
152 new_sketch.is_closed = ProfileClosed::Explicitly;
153 fn add(a: [f64; 2], b: [f64; 2]) -> [f64; 2] {
154 [a[0] + b[0], a[1] + b[1]]
155 }
156 let a = (corner, add(corner, deltas[0]));
157 let b = (a.1, add(a.1, deltas[1]));
158 let c = (b.1, add(b.1, deltas[2]));
159 let d = (c.1, add(c.1, deltas[3]));
160 for (id, (from, to)) in ids.into_iter().zip([a, b, c, d]) {
161 let current_path = Path::ToPoint {
162 base: BasePath {
163 from,
164 to,
165 tag: None,
166 units,
167 geo_meta: GeoMeta {
168 id,
169 metadata: args.source_range.into(),
170 },
171 },
172 };
173 new_sketch.paths.push(current_path);
174 }
175 Ok(new_sketch)
176}
177
178pub async fn circle(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
180 let sketch_or_surface =
181 args.get_unlabeled_kw_arg("sketchOrSurface", &RuntimeType::sketch_or_surface(), exec_state)?;
182 let center = args.get_kw_arg_opt("center", &RuntimeType::point2d(), exec_state)?;
183 let radius: Option<TyF64> = args.get_kw_arg_opt("radius", &RuntimeType::length(), exec_state)?;
184 let diameter: Option<TyF64> = args.get_kw_arg_opt("diameter", &RuntimeType::length(), exec_state)?;
185 let tag = args.get_kw_arg_opt("tag", &RuntimeType::tag_decl(), exec_state)?;
186
187 let sketch = inner_circle(sketch_or_surface, center, radius, diameter, tag, exec_state, args).await?;
188 Ok(KclValue::Sketch {
189 value: Box::new(sketch),
190 })
191}
192
193pub const POINT_ZERO_ZERO: [TyF64; 2] = [
194 TyF64::new(
195 0.0,
196 crate::exec::NumericType::Known(crate::exec::UnitType::Length(crate::exec::UnitLength::Millimeters)),
197 ),
198 TyF64::new(
199 0.0,
200 crate::exec::NumericType::Known(crate::exec::UnitType::Length(crate::exec::UnitLength::Millimeters)),
201 ),
202];
203
204pub(super) async fn inner_circle(
205 sketch_or_surface: SketchOrSurface,
206 center: Option<[TyF64; 2]>,
207 radius: Option<TyF64>,
208 diameter: Option<TyF64>,
209 tag: Option<TagNode>,
210 exec_state: &mut ExecState,
211 args: Args,
212) -> Result<Sketch, KclError> {
213 let sketch_surface = sketch_or_surface.into_sketch_surface();
214 let center = center.unwrap_or(POINT_ZERO_ZERO);
215 let (center_u, ty) = untype_point(center.clone());
216 let units = ty.as_length().unwrap_or(UnitLength::Millimeters);
217
218 let radius = get_radius(radius, diameter, args.source_range)?;
219 let from = [center_u[0] + radius.to_length_units(units), center_u[1]];
220 let from_t = [TyF64::new(from[0], ty), TyF64::new(from[1], ty)];
221
222 let sketch =
223 crate::std::sketch::inner_start_profile(sketch_surface, from_t, None, exec_state, &args.ctx, args.source_range)
224 .await?;
225
226 let angle_start = Angle::zero();
227 let angle_end = Angle::turn();
228
229 let id = exec_state.next_uuid();
230
231 exec_state
232 .batch_modeling_cmd(
233 ModelingCmdMeta::from_args_id(exec_state, &args, id),
234 ModelingCmd::from(
235 mcmd::ExtendPath::builder()
236 .path(sketch.id.into())
237 .segment(PathSegment::Arc {
238 start: angle_start,
239 end: angle_end,
240 center: KPoint2d::from(point_to_mm(center)).map(LengthUnit),
241 radius: LengthUnit(radius.to_mm()),
242 relative: false,
243 })
244 .build(),
245 ),
246 )
247 .await?;
248
249 let current_path = Path::Circle {
250 base: BasePath {
251 from,
252 to: from,
253 tag: tag.clone(),
254 units,
255 geo_meta: GeoMeta {
256 id,
257 metadata: args.source_range.into(),
258 },
259 },
260 radius: radius.to_length_units(units),
261 center: center_u,
262 ccw: angle_start < angle_end,
263 };
264
265 let mut new_sketch = sketch;
266 new_sketch.is_closed = ProfileClosed::Explicitly;
267 if let Some(tag) = &tag {
268 new_sketch.add_tag(tag, ¤t_path, exec_state, None);
269 }
270
271 new_sketch.paths.push(current_path);
272
273 exec_state
274 .batch_modeling_cmd(
275 ModelingCmdMeta::from_args_id(exec_state, &args, id),
276 ModelingCmd::from(mcmd::ClosePath::builder().path_id(new_sketch.id).build()),
277 )
278 .await?;
279
280 Ok(new_sketch)
281}
282
283pub async fn circle_three_point(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
285 let sketch_or_surface =
286 args.get_unlabeled_kw_arg("sketchOrSurface", &RuntimeType::sketch_or_surface(), exec_state)?;
287 let p1 = args.get_kw_arg("p1", &RuntimeType::point2d(), exec_state)?;
288 let p2 = args.get_kw_arg("p2", &RuntimeType::point2d(), exec_state)?;
289 let p3 = args.get_kw_arg("p3", &RuntimeType::point2d(), exec_state)?;
290 let tag = args.get_kw_arg_opt("tag", &RuntimeType::tag_decl(), exec_state)?;
291
292 let sketch = inner_circle_three_point(sketch_or_surface, p1, p2, p3, tag, exec_state, args).await?;
293 Ok(KclValue::Sketch {
294 value: Box::new(sketch),
295 })
296}
297
298async fn inner_circle_three_point(
301 sketch_surface_or_group: SketchOrSurface,
302 p1: [TyF64; 2],
303 p2: [TyF64; 2],
304 p3: [TyF64; 2],
305 tag: Option<TagNode>,
306 exec_state: &mut ExecState,
307 args: Args,
308) -> Result<Sketch, KclError> {
309 let ty = p1[0].ty;
310 let units = ty.as_length().unwrap_or(UnitLength::Millimeters);
311
312 let p1 = point_to_len_unit(p1, units);
313 let p2 = point_to_len_unit(p2, units);
314 let p3 = point_to_len_unit(p3, units);
315
316 let center = calculate_circle_center(p1, p2, p3);
317 let radius = distance(center, p2);
319
320 let sketch_surface = sketch_surface_or_group.into_sketch_surface();
321
322 let from = [TyF64::new(center[0] + radius, ty), TyF64::new(center[1], ty)];
323 let sketch = crate::std::sketch::inner_start_profile(
324 sketch_surface,
325 from.clone(),
326 None,
327 exec_state,
328 &args.ctx,
329 args.source_range,
330 )
331 .await?;
332
333 let angle_start = Angle::zero();
334 let angle_end = Angle::turn();
335
336 let id = exec_state.next_uuid();
337
338 exec_state
339 .batch_modeling_cmd(
340 ModelingCmdMeta::from_args_id(exec_state, &args, id),
341 ModelingCmd::from(
342 mcmd::ExtendPath::builder()
343 .path(sketch.id.into())
344 .segment(PathSegment::Arc {
345 start: angle_start,
346 end: angle_end,
347 center: KPoint2d::from(untyped_point_to_mm(center, units)).map(LengthUnit),
348 radius: adjust_length(units, radius, UnitLength::Millimeters).0.into(),
349 relative: false,
350 })
351 .build(),
352 ),
353 )
354 .await?;
355
356 let current_path = Path::CircleThreePoint {
357 base: BasePath {
358 from: untype_point(from.clone()).0,
360 to: untype_point(from).0,
361 tag: tag.clone(),
362 units,
363 geo_meta: GeoMeta {
364 id,
365 metadata: args.source_range.into(),
366 },
367 },
368 p1,
369 p2,
370 p3,
371 };
372
373 let mut new_sketch = sketch;
374 new_sketch.is_closed = ProfileClosed::Explicitly;
375 if let Some(tag) = &tag {
376 new_sketch.add_tag(tag, ¤t_path, exec_state, None);
377 }
378
379 new_sketch.paths.push(current_path);
380
381 exec_state
382 .batch_modeling_cmd(
383 ModelingCmdMeta::from_args_id(exec_state, &args, id),
384 ModelingCmd::from(mcmd::ClosePath::builder().path_id(new_sketch.id).build()),
385 )
386 .await?;
387
388 Ok(new_sketch)
389}
390
391#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, Default)]
393#[ts(export)]
394#[serde(rename_all = "lowercase")]
395pub enum PolygonType {
396 #[default]
397 Inscribed,
398 Circumscribed,
399}
400
401pub async fn polygon(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
403 let sketch_or_surface =
404 args.get_unlabeled_kw_arg("sketchOrSurface", &RuntimeType::sketch_or_surface(), exec_state)?;
405 let radius: TyF64 = args.get_kw_arg("radius", &RuntimeType::length(), exec_state)?;
406 let num_sides: TyF64 = args.get_kw_arg("numSides", &RuntimeType::count(), exec_state)?;
407 let center = args.get_kw_arg_opt("center", &RuntimeType::point2d(), exec_state)?;
408 let inscribed = args.get_kw_arg_opt("inscribed", &RuntimeType::bool(), exec_state)?;
409
410 let sketch = inner_polygon(
411 sketch_or_surface,
412 radius,
413 num_sides.n as u64,
414 center,
415 inscribed,
416 exec_state,
417 args,
418 )
419 .await?;
420 Ok(KclValue::Sketch {
421 value: Box::new(sketch),
422 })
423}
424
425#[allow(clippy::too_many_arguments)]
426async fn inner_polygon(
427 sketch_surface_or_group: SketchOrSurface,
428 radius: TyF64,
429 num_sides: u64,
430 center: Option<[TyF64; 2]>,
431 inscribed: Option<bool>,
432 exec_state: &mut ExecState,
433 args: Args,
434) -> Result<Sketch, KclError> {
435 let center = center.unwrap_or(POINT_ZERO_ZERO);
436 if num_sides < 3 {
437 return Err(KclError::new_type(KclErrorDetails::new(
438 "Polygon must have at least 3 sides".to_string(),
439 vec![args.source_range],
440 )));
441 }
442
443 if radius.n <= 0.0 {
444 return Err(KclError::new_type(KclErrorDetails::new(
445 "Radius must be greater than 0".to_string(),
446 vec![args.source_range],
447 )));
448 }
449
450 let (sketch_surface, units) = match sketch_surface_or_group {
451 SketchOrSurface::SketchSurface(surface) => (surface, radius.ty.as_length().unwrap_or(UnitLength::Millimeters)),
452 SketchOrSurface::Sketch(group) => (group.on, group.units),
453 };
454
455 let half_angle = std::f64::consts::PI / num_sides as f64;
456
457 let radius_to_vertices = if inscribed.unwrap_or(true) {
458 radius.n
460 } else {
461 radius.n / libm::cos(half_angle)
463 };
464
465 let angle_step = std::f64::consts::TAU / num_sides as f64;
466
467 let center_u = point_to_len_unit(center, units);
468
469 let vertices: Vec<[f64; 2]> = (0..num_sides)
470 .map(|i| {
471 let angle = angle_step * i as f64;
472 [
473 center_u[0] + radius_to_vertices * libm::cos(angle),
474 center_u[1] + radius_to_vertices * libm::sin(angle),
475 ]
476 })
477 .collect();
478
479 let mut sketch = crate::std::sketch::inner_start_profile(
480 sketch_surface,
481 point_to_typed(vertices[0], units),
482 None,
483 exec_state,
484 &args.ctx,
485 args.source_range,
486 )
487 .await?;
488
489 for vertex in vertices.iter().skip(1) {
491 let from = sketch.current_pen_position()?;
492 let id = exec_state.next_uuid();
493
494 exec_state
495 .batch_modeling_cmd(
496 ModelingCmdMeta::from_args_id(exec_state, &args, id),
497 ModelingCmd::from(
498 mcmd::ExtendPath::builder()
499 .path(sketch.id.into())
500 .segment(PathSegment::Line {
501 end: KPoint2d::from(untyped_point_to_mm(*vertex, units))
502 .with_z(0.0)
503 .map(LengthUnit),
504 relative: false,
505 })
506 .build(),
507 ),
508 )
509 .await?;
510
511 let current_path = Path::ToPoint {
512 base: BasePath {
513 from: from.ignore_units(),
514 to: *vertex,
515 tag: None,
516 units: sketch.units,
517 geo_meta: GeoMeta {
518 id,
519 metadata: args.source_range.into(),
520 },
521 },
522 };
523
524 sketch.paths.push(current_path);
525 }
526
527 let from = sketch.current_pen_position()?;
529 let close_id = exec_state.next_uuid();
530
531 exec_state
532 .batch_modeling_cmd(
533 ModelingCmdMeta::from_args_id(exec_state, &args, close_id),
534 ModelingCmd::from(
535 mcmd::ExtendPath::builder()
536 .path(sketch.id.into())
537 .segment(PathSegment::Line {
538 end: KPoint2d::from(untyped_point_to_mm(vertices[0], units))
539 .with_z(0.0)
540 .map(LengthUnit),
541 relative: false,
542 })
543 .build(),
544 ),
545 )
546 .await?;
547
548 let current_path = Path::ToPoint {
549 base: BasePath {
550 from: from.ignore_units(),
551 to: vertices[0],
552 tag: None,
553 units: sketch.units,
554 geo_meta: GeoMeta {
555 id: close_id,
556 metadata: args.source_range.into(),
557 },
558 },
559 };
560
561 sketch.paths.push(current_path);
562 sketch.is_closed = ProfileClosed::Explicitly;
563
564 exec_state
565 .batch_modeling_cmd(
566 ModelingCmdMeta::from_args(exec_state, &args),
567 ModelingCmd::from(mcmd::ClosePath::builder().path_id(sketch.id).build()),
568 )
569 .await?;
570
571 Ok(sketch)
572}
573
574pub async fn ellipse(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
576 let sketch_or_surface =
577 args.get_unlabeled_kw_arg("sketchOrSurface", &RuntimeType::sketch_or_surface(), exec_state)?;
578 let center = args.get_kw_arg_opt("center", &RuntimeType::point2d(), exec_state)?;
579 let major_radius = args.get_kw_arg_opt("majorRadius", &RuntimeType::length(), exec_state)?;
580 let major_axis = args.get_kw_arg_opt("majorAxis", &RuntimeType::point2d(), exec_state)?;
581 let minor_radius = args.get_kw_arg("minorRadius", &RuntimeType::length(), exec_state)?;
582 let tag = args.get_kw_arg_opt("tag", &RuntimeType::tag_decl(), exec_state)?;
583
584 let sketch = inner_ellipse(
585 sketch_or_surface,
586 center,
587 major_radius,
588 major_axis,
589 minor_radius,
590 tag,
591 exec_state,
592 args,
593 )
594 .await?;
595 Ok(KclValue::Sketch {
596 value: Box::new(sketch),
597 })
598}
599
600#[allow(clippy::too_many_arguments)]
601async fn inner_ellipse(
602 sketch_surface_or_group: SketchOrSurface,
603 center: Option<[TyF64; 2]>,
604 major_radius: Option<TyF64>,
605 major_axis: Option<[TyF64; 2]>,
606 minor_radius: TyF64,
607 tag: Option<TagNode>,
608 exec_state: &mut ExecState,
609 args: Args,
610) -> Result<Sketch, KclError> {
611 let sketch_surface = sketch_surface_or_group.into_sketch_surface();
612 let center = center.unwrap_or(POINT_ZERO_ZERO);
613 let (center_u, ty) = untype_point(center.clone());
614 let units = ty.as_length().unwrap_or(UnitLength::Millimeters);
615
616 let major_axis = match (major_axis, major_radius) {
617 (Some(_), Some(_)) | (None, None) => {
618 return Err(KclError::new_type(KclErrorDetails::new(
619 "Provide either `majorAxis` or `majorRadius`.".to_string(),
620 vec![args.source_range],
621 )));
622 }
623 (Some(major_axis), None) => major_axis,
624 (None, Some(major_radius)) => [
625 major_radius.clone(),
626 TyF64 {
627 n: 0.0,
628 ty: major_radius.ty,
629 },
630 ],
631 };
632
633 let from = [
634 center_u[0] + major_axis[0].to_length_units(units),
635 center_u[1] + major_axis[1].to_length_units(units),
636 ];
637 let from_t = [TyF64::new(from[0], ty), TyF64::new(from[1], ty)];
638
639 let sketch =
640 crate::std::sketch::inner_start_profile(sketch_surface, from_t, None, exec_state, &args.ctx, args.source_range)
641 .await?;
642
643 let angle_start = Angle::zero();
644 let angle_end = Angle::turn();
645
646 let id = exec_state.next_uuid();
647
648 let axis = KPoint2d::from(untyped_point_to_mm([major_axis[0].n, major_axis[1].n], units)).map(LengthUnit);
649 exec_state
650 .batch_modeling_cmd(
651 ModelingCmdMeta::from_args_id(exec_state, &args, id),
652 ModelingCmd::from(
653 mcmd::ExtendPath::builder()
654 .path(sketch.id.into())
655 .segment(PathSegment::Ellipse {
656 center: KPoint2d::from(point_to_mm(center)).map(LengthUnit),
657 major_axis: axis,
658 minor_radius: LengthUnit(minor_radius.to_mm()),
659 start_angle: Angle::from_degrees(angle_start.to_degrees()),
660 end_angle: Angle::from_degrees(angle_end.to_degrees()),
661 })
662 .build(),
663 ),
664 )
665 .await?;
666
667 let current_path = Path::Ellipse {
668 base: BasePath {
669 from,
670 to: from,
671 tag: tag.clone(),
672 units,
673 geo_meta: GeoMeta {
674 id,
675 metadata: args.source_range.into(),
676 },
677 },
678 major_axis: major_axis.map(|x| x.to_length_units(units)),
679 minor_radius: minor_radius.to_length_units(units),
680 center: center_u,
681 ccw: angle_start < angle_end,
682 };
683
684 let mut new_sketch = sketch;
685 new_sketch.is_closed = ProfileClosed::Explicitly;
686 if let Some(tag) = &tag {
687 new_sketch.add_tag(tag, ¤t_path, exec_state, None);
688 }
689
690 new_sketch.paths.push(current_path);
691
692 exec_state
693 .batch_modeling_cmd(
694 ModelingCmdMeta::from_args_id(exec_state, &args, id),
695 ModelingCmd::from(mcmd::ClosePath::builder().path_id(new_sketch.id).build()),
696 )
697 .await?;
698
699 Ok(new_sketch)
700}
701
702pub(crate) fn get_radius(
703 radius: Option<TyF64>,
704 diameter: Option<TyF64>,
705 source_range: SourceRange,
706) -> Result<TyF64, KclError> {
707 get_radius_labelled(radius, diameter, source_range, "radius", "diameter")
708}
709
710pub(crate) fn get_radius_labelled(
711 radius: Option<TyF64>,
712 diameter: Option<TyF64>,
713 source_range: SourceRange,
714 label_radius: &'static str,
715 label_diameter: &'static str,
716) -> Result<TyF64, KclError> {
717 match (radius, diameter) {
718 (Some(radius), None) => Ok(radius),
719 (None, Some(diameter)) => Ok(TyF64::new(diameter.n / 2.0, diameter.ty)),
720 (None, None) => Err(KclError::new_type(KclErrorDetails::new(
721 format!("This function needs either `{label_diameter}` or `{label_radius}`"),
722 vec![source_range],
723 ))),
724 (Some(_), Some(_)) => Err(KclError::new_type(KclErrorDetails::new(
725 format!("You cannot specify both `{label_diameter}` and `{label_radius}`, please remove one"),
726 vec![source_range],
727 ))),
728 }
729}