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