Skip to main content

kcl_lib/execution/
geometry.rs

1use std::f64::consts::TAU;
2use std::ops::Add;
3use std::ops::AddAssign;
4use std::ops::Mul;
5use std::ops::Sub;
6use std::ops::SubAssign;
7use std::sync::Arc;
8
9use anyhow::Result;
10use indexmap::IndexMap;
11use kcl_api::ArcCtor;
12use kcl_api::ArcDirection;
13use kcl_api::CircleCtor;
14use kcl_api::ControlPointSplineCtor;
15use kcl_api::Freedom;
16use kcl_api::LineCtor;
17use kcl_api::Number;
18use kcl_api::ObjectId;
19use kcl_api::Point2d as ApiPoint2d;
20use kcl_api::PointCtor;
21use kcl_api::UnitLength;
22use kcl_error::SourceRange;
23use kittycad_modeling_cmds::ModelingCmd;
24use kittycad_modeling_cmds::each_cmd as mcmd;
25use kittycad_modeling_cmds::length_unit::LengthUnit;
26use kittycad_modeling_cmds::websocket::ModelingCmdReq;
27use kittycad_modeling_cmds::{self as kcmc};
28use parse_display::Display;
29use parse_display::FromStr;
30use serde::Deserialize;
31use serde::Serialize;
32use uuid::Uuid;
33
34use crate::NodePath;
35use crate::engine::DEFAULT_PLANE_INFO;
36use crate::engine::PlaneName;
37use crate::errors::KclError;
38use crate::errors::KclErrorDetails;
39use crate::exec::KclValue;
40use crate::execution::ArtifactId;
41use crate::execution::ExecState;
42use crate::execution::ExecutorContext;
43use crate::execution::Metadata;
44use crate::execution::TagEngineInfo;
45use crate::execution::TagIdentifier;
46use crate::execution::normalize_to_solver_distance_unit;
47use crate::execution::types::NumericType;
48use crate::execution::types::NumericTypeExt;
49use crate::execution::types::adjust_length;
50use crate::parsing::ast::types::Node;
51use crate::parsing::ast::types::NodeRef;
52use crate::parsing::ast::types::TagDeclarator;
53use crate::parsing::ast::types::TagNode;
54use crate::std::Args;
55use crate::std::args::TyF64;
56use crate::std::edge::UnresolvedEdgeSpecifier;
57use crate::std::sketch::FaceTag;
58use crate::std::sketch::PlaneData;
59use crate::util::MathExt;
60
61type Point3D = kcmc::shared::Point3d<f64>;
62
63/// A GD&T annotation.
64#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
65#[ts(export)]
66#[serde(tag = "type", rename_all = "camelCase")]
67pub struct GdtAnnotation {
68    /// The engine ID.
69    pub id: uuid::Uuid,
70    #[serde(skip)]
71    pub meta: Vec<Metadata>,
72}
73
74/// A geometry.
75#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
76#[ts(export)]
77#[serde(tag = "type")]
78#[allow(clippy::large_enum_variant)]
79pub enum Geometry {
80    Sketch(Sketch),
81    Solid(Solid),
82}
83
84impl Geometry {
85    pub fn id(&self) -> uuid::Uuid {
86        match self {
87            Geometry::Sketch(s) => s.id,
88            Geometry::Solid(e) => e.id,
89        }
90    }
91
92    /// Return the topology root to target when a pattern requests its
93    /// original geometry.
94    pub fn pattern_source_id(&self) -> uuid::Uuid {
95        match self {
96            Geometry::Sketch(s) => s.original_id,
97            Geometry::Solid(e) => e.topology_id(),
98        }
99    }
100}
101
102/// A geometry including an imported geometry.
103#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
104#[ts(export)]
105#[serde(tag = "type")]
106#[allow(clippy::large_enum_variant)]
107pub enum GeometryWithImportedGeometry {
108    Sketch(Sketch),
109    Solid(Solid),
110    ImportedGeometry(Box<ImportedGeometry>),
111}
112
113impl GeometryWithImportedGeometry {
114    pub async fn id(&mut self, ctx: &ExecutorContext) -> Result<uuid::Uuid, KclError> {
115        match self {
116            GeometryWithImportedGeometry::Sketch(s) => Ok(s.id),
117            GeometryWithImportedGeometry::Solid(e) => Ok(e.id),
118            GeometryWithImportedGeometry::ImportedGeometry(i) => {
119                let id = i.id(ctx).await?;
120                Ok(id)
121            }
122        }
123    }
124
125    pub fn into_solid(self) -> Option<Solid> {
126        match self {
127            GeometryWithImportedGeometry::Sketch(_) => None,
128            GeometryWithImportedGeometry::Solid(solid) => Some(solid),
129            GeometryWithImportedGeometry::ImportedGeometry(_) => None,
130        }
131    }
132}
133
134/// A set of geometry.
135#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
136#[ts(export)]
137#[serde(tag = "type")]
138#[allow(clippy::vec_box)]
139pub enum Geometries {
140    Sketches(Vec<Sketch>),
141    Solids(Vec<Solid>),
142}
143
144impl From<Geometry> for Geometries {
145    fn from(value: Geometry) -> Self {
146        match value {
147            Geometry::Sketch(x) => Self::Sketches(vec![x]),
148            Geometry::Solid(x) => Self::Solids(vec![x]),
149        }
150    }
151}
152
153/// Data for an imported geometry.
154#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
155#[ts(export)]
156#[serde(rename_all = "camelCase")]
157pub struct ImportedGeometry {
158    /// The ID of the imported geometry.
159    pub id: uuid::Uuid,
160    /// The original file paths.
161    pub value: Vec<String>,
162    #[serde(skip)]
163    pub meta: Vec<Metadata>,
164    /// If the imported geometry has completed.
165    #[serde(skip)]
166    completed: bool,
167}
168
169impl ImportedGeometry {
170    pub fn new(id: uuid::Uuid, value: Vec<String>, meta: Vec<Metadata>) -> Self {
171        Self {
172            id,
173            value,
174            meta,
175            completed: false,
176        }
177    }
178
179    async fn wait_for_finish(&mut self, ctx: &ExecutorContext) -> Result<(), KclError> {
180        if self.completed {
181            return Ok(());
182        }
183
184        ctx.engine
185            .ensure_async_command_completed(self.id, self.meta.first().map(|m| m.source_range))
186            .await?;
187
188        self.completed = true;
189
190        Ok(())
191    }
192
193    pub async fn id(&mut self, ctx: &ExecutorContext) -> Result<uuid::Uuid, KclError> {
194        if !self.completed {
195            self.wait_for_finish(ctx).await?;
196        }
197
198        Ok(self.id)
199    }
200}
201
202/// Data for geometry that can be hidden.
203#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
204#[ts(export)]
205#[serde(tag = "type", rename_all = "camelCase")]
206#[allow(clippy::vec_box)]
207pub enum HideableGeometry {
208    ImportedGeometry(Box<ImportedGeometry>),
209    SolidSet(Vec<Solid>),
210    PlaneSet(Vec<Plane>),
211    SketchSet(Vec<Sketch>),
212    HelixSet(Vec<Helix>),
213    GdtAnnotationSet(Vec<GdtAnnotation>),
214}
215
216impl From<HideableGeometry> for crate::execution::KclValue {
217    fn from(value: HideableGeometry) -> Self {
218        match value {
219            HideableGeometry::ImportedGeometry(s) => crate::execution::KclValue::ImportedGeometry(*s),
220            HideableGeometry::PlaneSet(mut s) => {
221                if s.len() == 1
222                    && let Some(s) = s.pop()
223                {
224                    crate::execution::KclValue::Plane { value: Box::new(s) }
225                } else {
226                    crate::execution::KclValue::HomArray {
227                        value: s
228                            .into_iter()
229                            .map(|s| crate::execution::KclValue::Plane { value: Box::new(s) })
230                            .collect(),
231                        ty: crate::execution::types::RuntimeType::plane(),
232                    }
233                }
234            }
235            HideableGeometry::SolidSet(mut s) => {
236                if s.len() == 1
237                    && let Some(s) = s.pop()
238                {
239                    crate::execution::KclValue::Solid { value: Box::new(s) }
240                } else {
241                    crate::execution::KclValue::HomArray {
242                        value: s
243                            .into_iter()
244                            .map(|s| crate::execution::KclValue::Solid { value: Box::new(s) })
245                            .collect(),
246                        ty: crate::execution::types::RuntimeType::solid(),
247                    }
248                }
249            }
250            HideableGeometry::GdtAnnotationSet(mut s) => {
251                if s.len() == 1
252                    && let Some(s) = s.pop()
253                {
254                    crate::execution::KclValue::GdtAnnotation { value: Box::new(s) }
255                } else {
256                    crate::execution::KclValue::HomArray {
257                        value: s
258                            .into_iter()
259                            .map(|s| crate::execution::KclValue::GdtAnnotation { value: Box::new(s) })
260                            .collect(),
261                        ty: crate::execution::types::RuntimeType::gdt(),
262                    }
263                }
264            }
265            HideableGeometry::SketchSet(mut s) => {
266                if s.len() == 1
267                    && let Some(s) = s.pop()
268                {
269                    crate::execution::KclValue::Sketch { value: Box::new(s) }
270                } else {
271                    crate::execution::KclValue::HomArray {
272                        value: s
273                            .into_iter()
274                            .map(|s| crate::execution::KclValue::Sketch { value: Box::new(s) })
275                            .collect(),
276                        ty: crate::execution::types::RuntimeType::sketch(),
277                    }
278                }
279            }
280            HideableGeometry::HelixSet(mut s) => {
281                if s.len() == 1
282                    && let Some(s) = s.pop()
283                {
284                    crate::execution::KclValue::Helix { value: Box::new(s) }
285                } else {
286                    crate::execution::KclValue::HomArray {
287                        value: s
288                            .into_iter()
289                            .map(|s| crate::execution::KclValue::Helix { value: Box::new(s) })
290                            .collect(),
291                        ty: crate::execution::types::RuntimeType::helices(),
292                    }
293                }
294            }
295        }
296    }
297}
298
299impl HideableGeometry {
300    pub(crate) async fn ids(&mut self, ctx: &ExecutorContext) -> Result<Vec<uuid::Uuid>, KclError> {
301        match self {
302            HideableGeometry::ImportedGeometry(s) => {
303                let id = s.id(ctx).await?;
304
305                Ok(vec![id])
306            }
307            HideableGeometry::PlaneSet(s) => Ok(s.iter().map(|s| s.id).collect()),
308            HideableGeometry::SolidSet(s) => Ok(s.iter().map(|s| s.id).collect()),
309            HideableGeometry::GdtAnnotationSet(s) => Ok(s.iter().map(|s| s.id).collect()),
310            HideableGeometry::SketchSet(s) => Ok(s.iter().map(|s| s.id).collect()),
311            HideableGeometry::HelixSet(s) => Ok(s.iter().map(|s| s.value).collect()),
312        }
313    }
314}
315
316/// Data for a solid, sketch, or an imported geometry.
317#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
318#[ts(export)]
319#[serde(tag = "type", rename_all = "camelCase")]
320#[allow(clippy::vec_box)]
321pub enum SolidOrSketchOrImportedGeometry {
322    ImportedGeometry(Box<ImportedGeometry>),
323    SolidSet(Vec<Solid>),
324    SketchSet(Vec<Sketch>),
325    HelixSet(Vec<Helix>),
326}
327
328impl From<SolidOrSketchOrImportedGeometry> for crate::execution::KclValue {
329    fn from(value: SolidOrSketchOrImportedGeometry) -> Self {
330        match value {
331            SolidOrSketchOrImportedGeometry::ImportedGeometry(s) => crate::execution::KclValue::ImportedGeometry(*s),
332            SolidOrSketchOrImportedGeometry::SolidSet(mut s) => {
333                if s.len() == 1
334                    && let Some(s) = s.pop()
335                {
336                    crate::execution::KclValue::Solid { value: Box::new(s) }
337                } else {
338                    crate::execution::KclValue::HomArray {
339                        value: s
340                            .into_iter()
341                            .map(|s| crate::execution::KclValue::Solid { value: Box::new(s) })
342                            .collect(),
343                        ty: crate::execution::types::RuntimeType::solid(),
344                    }
345                }
346            }
347            SolidOrSketchOrImportedGeometry::SketchSet(mut s) => {
348                if s.len() == 1
349                    && let Some(s) = s.pop()
350                {
351                    crate::execution::KclValue::Sketch { value: Box::new(s) }
352                } else {
353                    crate::execution::KclValue::HomArray {
354                        value: s
355                            .into_iter()
356                            .map(|s| crate::execution::KclValue::Sketch { value: Box::new(s) })
357                            .collect(),
358                        ty: crate::execution::types::RuntimeType::sketch(),
359                    }
360                }
361            }
362            SolidOrSketchOrImportedGeometry::HelixSet(mut s) => {
363                if s.len() == 1
364                    && let Some(s) = s.pop()
365                {
366                    crate::execution::KclValue::Helix { value: Box::new(s) }
367                } else {
368                    crate::execution::KclValue::HomArray {
369                        value: s
370                            .into_iter()
371                            .map(|s| crate::execution::KclValue::Helix { value: Box::new(s) })
372                            .collect(),
373                        ty: crate::execution::types::RuntimeType::helices(),
374                    }
375                }
376            }
377        }
378    }
379}
380
381impl SolidOrSketchOrImportedGeometry {
382    pub(crate) async fn ids(&mut self, ctx: &ExecutorContext) -> Result<Vec<uuid::Uuid>, KclError> {
383        match self {
384            SolidOrSketchOrImportedGeometry::ImportedGeometry(s) => {
385                let id = s.id(ctx).await?;
386
387                Ok(vec![id])
388            }
389            SolidOrSketchOrImportedGeometry::SolidSet(s) => Ok(s.iter().map(|s| s.id).collect()),
390            SolidOrSketchOrImportedGeometry::SketchSet(s) => Ok(s.iter().map(|s| s.id).collect()),
391            SolidOrSketchOrImportedGeometry::HelixSet(s) => Ok(s.iter().map(|s| s.value).collect()),
392        }
393    }
394}
395
396/// Data for a solid or an imported geometry.
397#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
398#[ts(export)]
399#[serde(tag = "type", rename_all = "camelCase")]
400#[allow(clippy::vec_box)]
401pub enum SolidOrImportedGeometry {
402    ImportedGeometry(Box<ImportedGeometry>),
403    SolidSet(Vec<Solid>),
404}
405
406impl From<SolidOrImportedGeometry> for crate::execution::KclValue {
407    fn from(value: SolidOrImportedGeometry) -> Self {
408        match value {
409            SolidOrImportedGeometry::ImportedGeometry(s) => crate::execution::KclValue::ImportedGeometry(*s),
410            SolidOrImportedGeometry::SolidSet(mut s) => {
411                if s.len() == 1
412                    && let Some(s) = s.pop()
413                {
414                    crate::execution::KclValue::Solid { value: Box::new(s) }
415                } else {
416                    crate::execution::KclValue::HomArray {
417                        value: s
418                            .into_iter()
419                            .map(|s| crate::execution::KclValue::Solid { value: Box::new(s) })
420                            .collect(),
421                        ty: crate::execution::types::RuntimeType::solid(),
422                    }
423                }
424            }
425        }
426    }
427}
428
429/// Something that you can change the color of.
430#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
431#[ts(export)]
432#[serde(tag = "type", rename_all = "camelCase")]
433#[allow(clippy::vec_box)]
434pub enum HasAppearance {
435    ImportedGeometry(Box<ImportedGeometry>),
436    SolidSet(Vec<Solid>),
437    Plane(Box<Plane>),
438}
439
440impl From<HasAppearance> for KclValue {
441    fn from(value: HasAppearance) -> Self {
442        match value {
443            HasAppearance::Plane(p) => KclValue::Plane { value: p },
444            HasAppearance::ImportedGeometry(s) => KclValue::ImportedGeometry(*s),
445            HasAppearance::SolidSet(mut s) => {
446                if s.len() == 1
447                    && let Some(s) = s.pop()
448                {
449                    KclValue::Solid { value: Box::new(s) }
450                } else {
451                    KclValue::HomArray {
452                        value: s.into_iter().map(|s| KclValue::Solid { value: Box::new(s) }).collect(),
453                        ty: crate::execution::types::RuntimeType::solid(),
454                    }
455                }
456            }
457        }
458    }
459}
460
461impl HasAppearance {
462    pub(crate) async fn ids(&mut self, ctx: &ExecutorContext) -> Result<Vec<uuid::Uuid>, KclError> {
463        match self {
464            HasAppearance::Plane(p) => Ok(vec![p.id]),
465            HasAppearance::ImportedGeometry(s) => {
466                let id = s.id(ctx).await?;
467
468                Ok(vec![id])
469            }
470            HasAppearance::SolidSet(s) => Ok(s.iter().map(|s| s.id).collect()),
471        }
472    }
473}
474
475/// A helix.
476#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
477#[ts(export)]
478#[serde(rename_all = "camelCase")]
479pub struct Helix {
480    /// The id of the helix.
481    pub value: uuid::Uuid,
482    /// The artifact ID.
483    pub artifact_id: ArtifactId,
484    /// Number of revolutions.
485    pub revolutions: f64,
486    /// Start angle (in degrees).
487    pub angle_start: f64,
488    /// Is the helix rotation counter clockwise?
489    pub ccw: bool,
490    /// The cylinder the helix was created on.
491    pub cylinder_id: Option<uuid::Uuid>,
492    pub units: UnitLength,
493    #[serde(skip)]
494    pub meta: Vec<Metadata>,
495}
496
497#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
498#[ts(export)]
499#[serde(rename_all = "camelCase")]
500pub struct Plane {
501    /// The id of the plane.
502    pub id: uuid::Uuid,
503    /// The artifact ID.
504    pub artifact_id: ArtifactId,
505    /// The scene object ID. If this is None, then the plane has not been
506    /// sent to the engine yet. It must be sent before it is used.
507    #[serde(skip_serializing_if = "Option::is_none")]
508    pub object_id: Option<ObjectId>,
509    /// The kind of plane or custom.
510    pub kind: PlaneKind,
511    /// The information for the plane.
512    #[serde(flatten)]
513    pub info: PlaneInfo,
514    #[serde(skip)]
515    pub meta: Vec<Metadata>,
516}
517
518#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, ts_rs::TS)]
519#[ts(export)]
520#[serde(rename_all = "camelCase")]
521pub struct PlaneInfo {
522    /// Origin of the plane.
523    pub origin: Point3d,
524    /// What should the plane's X axis be?
525    pub x_axis: Point3d,
526    /// What should the plane's Y axis be?
527    pub y_axis: Point3d,
528    /// What should the plane's Z axis be?
529    pub z_axis: Point3d,
530}
531
532impl PlaneInfo {
533    pub(crate) fn into_plane_data(self) -> PlaneData {
534        if self.origin.is_zero() {
535            match self {
536                Self {
537                    origin:
538                        Point3d {
539                            x: 0.0,
540                            y: 0.0,
541                            z: 0.0,
542                            units: Some(UnitLength::Millimeters),
543                        },
544                    x_axis:
545                        Point3d {
546                            x: 1.0,
547                            y: 0.0,
548                            z: 0.0,
549                            units: _,
550                        },
551                    y_axis:
552                        Point3d {
553                            x: 0.0,
554                            y: 1.0,
555                            z: 0.0,
556                            units: _,
557                        },
558                    z_axis: _,
559                } => return PlaneData::XY,
560                Self {
561                    origin:
562                        Point3d {
563                            x: 0.0,
564                            y: 0.0,
565                            z: 0.0,
566                            units: Some(UnitLength::Millimeters),
567                        },
568                    x_axis:
569                        Point3d {
570                            x: -1.0,
571                            y: 0.0,
572                            z: 0.0,
573                            units: _,
574                        },
575                    y_axis:
576                        Point3d {
577                            x: 0.0,
578                            y: 1.0,
579                            z: 0.0,
580                            units: _,
581                        },
582                    z_axis: _,
583                } => return PlaneData::NegXY,
584                Self {
585                    origin:
586                        Point3d {
587                            x: 0.0,
588                            y: 0.0,
589                            z: 0.0,
590                            units: Some(UnitLength::Millimeters),
591                        },
592                    x_axis:
593                        Point3d {
594                            x: 1.0,
595                            y: 0.0,
596                            z: 0.0,
597                            units: _,
598                        },
599                    y_axis:
600                        Point3d {
601                            x: 0.0,
602                            y: 0.0,
603                            z: 1.0,
604                            units: _,
605                        },
606                    z_axis: _,
607                } => return PlaneData::XZ,
608                Self {
609                    origin:
610                        Point3d {
611                            x: 0.0,
612                            y: 0.0,
613                            z: 0.0,
614                            units: Some(UnitLength::Millimeters),
615                        },
616                    x_axis:
617                        Point3d {
618                            x: -1.0,
619                            y: 0.0,
620                            z: 0.0,
621                            units: _,
622                        },
623                    y_axis:
624                        Point3d {
625                            x: 0.0,
626                            y: 0.0,
627                            z: 1.0,
628                            units: _,
629                        },
630                    z_axis: _,
631                } => return PlaneData::NegXZ,
632                Self {
633                    origin:
634                        Point3d {
635                            x: 0.0,
636                            y: 0.0,
637                            z: 0.0,
638                            units: Some(UnitLength::Millimeters),
639                        },
640                    x_axis:
641                        Point3d {
642                            x: 0.0,
643                            y: 1.0,
644                            z: 0.0,
645                            units: _,
646                        },
647                    y_axis:
648                        Point3d {
649                            x: 0.0,
650                            y: 0.0,
651                            z: 1.0,
652                            units: _,
653                        },
654                    z_axis: _,
655                } => return PlaneData::YZ,
656                Self {
657                    origin:
658                        Point3d {
659                            x: 0.0,
660                            y: 0.0,
661                            z: 0.0,
662                            units: Some(UnitLength::Millimeters),
663                        },
664                    x_axis:
665                        Point3d {
666                            x: 0.0,
667                            y: -1.0,
668                            z: 0.0,
669                            units: _,
670                        },
671                    y_axis:
672                        Point3d {
673                            x: 0.0,
674                            y: 0.0,
675                            z: 1.0,
676                            units: _,
677                        },
678                    z_axis: _,
679                } => return PlaneData::NegYZ,
680                _ => {}
681            }
682        }
683
684        PlaneData::Plane(Self {
685            origin: self.origin,
686            x_axis: self.x_axis,
687            y_axis: self.y_axis,
688            z_axis: self.z_axis,
689        })
690    }
691
692    pub(crate) fn is_right_handed(&self) -> bool {
693        // Katie's formula:
694        // dot(cross(x, y), z) ~= sqrt(dot(x, x) * dot(y, y) * dot(z, z))
695        let lhs = self
696            .x_axis
697            .axes_cross_product(&self.y_axis)
698            .axes_dot_product(&self.z_axis);
699        let rhs_x = self.x_axis.axes_dot_product(&self.x_axis);
700        let rhs_y = self.y_axis.axes_dot_product(&self.y_axis);
701        let rhs_z = self.z_axis.axes_dot_product(&self.z_axis);
702        let rhs = (rhs_x * rhs_y * rhs_z).sqrt();
703        // Check LHS ~= RHS
704        (lhs - rhs).abs() <= 0.0001
705    }
706
707    #[cfg(test)]
708    pub(crate) fn is_left_handed(&self) -> bool {
709        !self.is_right_handed()
710    }
711
712    pub(crate) fn make_right_handed(self) -> Self {
713        if self.is_right_handed() {
714            return self;
715        }
716        // To make it right-handed, negate X, i.e. rotate the plane 180 degrees.
717        Self {
718            origin: self.origin,
719            x_axis: self.x_axis.negated(),
720            y_axis: self.y_axis,
721            z_axis: self.z_axis,
722        }
723    }
724}
725
726impl TryFrom<PlaneData> for PlaneInfo {
727    type Error = KclError;
728
729    fn try_from(value: PlaneData) -> Result<Self, Self::Error> {
730        let name = match value {
731            PlaneData::XY => PlaneName::Xy,
732            PlaneData::NegXY => PlaneName::NegXy,
733            PlaneData::XZ => PlaneName::Xz,
734            PlaneData::NegXZ => PlaneName::NegXz,
735            PlaneData::YZ => PlaneName::Yz,
736            PlaneData::NegYZ => PlaneName::NegYz,
737            PlaneData::Plane(info) => {
738                return Ok(info);
739            }
740        };
741
742        let info = DEFAULT_PLANE_INFO.get(&name).ok_or_else(|| {
743            KclError::new_internal(KclErrorDetails::new(
744                format!("Plane {name} not found"),
745                Default::default(),
746            ))
747        })?;
748
749        Ok(info.clone())
750    }
751}
752
753impl From<&PlaneData> for PlaneKind {
754    fn from(value: &PlaneData) -> Self {
755        match value {
756            PlaneData::XY | PlaneData::NegXY => PlaneKind::XY,
757            PlaneData::XZ | PlaneData::NegXZ => PlaneKind::XZ,
758            PlaneData::YZ | PlaneData::NegYZ => PlaneKind::YZ,
759            PlaneData::Plane(_) => PlaneKind::Custom,
760        }
761    }
762}
763
764impl From<&PlaneInfo> for PlaneKind {
765    fn from(value: &PlaneInfo) -> Self {
766        PlaneKind::from(&PlaneData::Plane(value.clone()))
767    }
768}
769
770impl From<PlaneInfo> for PlaneKind {
771    fn from(value: PlaneInfo) -> Self {
772        PlaneKind::from(&PlaneData::Plane(value))
773    }
774}
775
776impl Plane {
777    #[cfg(test)]
778    pub(crate) fn from_plane_data_skipping_engine(
779        value: PlaneData,
780        exec_state: &mut ExecState,
781    ) -> Result<Self, KclError> {
782        let id = exec_state.next_uuid();
783        let kind = PlaneKind::from(&value);
784        Ok(Plane {
785            id,
786            artifact_id: id.into(),
787            info: PlaneInfo::try_from(value)?,
788            object_id: None,
789            kind,
790            meta: vec![],
791        })
792    }
793
794    /// Returns true if the plane has been sent to the engine.
795    pub fn is_initialized(&self) -> bool {
796        self.object_id.is_some()
797    }
798
799    /// Returns true if the plane has not been sent to the engine yet.
800    pub fn is_uninitialized(&self) -> bool {
801        !self.is_initialized()
802    }
803
804    /// The standard planes are XY, YZ and XZ (in both positive and negative)
805    pub fn is_standard(&self) -> bool {
806        match &self.kind {
807            PlaneKind::XY | PlaneKind::YZ | PlaneKind::XZ => true,
808            PlaneKind::Custom => false,
809        }
810    }
811
812    /// Project a point onto a plane by calculating how far away it is and moving it along the
813    /// normal of the plane so that it now lies on the plane.
814    pub fn project(&self, point: Point3d) -> Point3d {
815        let v = point - self.info.origin;
816        let dot = v.axes_dot_product(&self.info.z_axis);
817
818        point - self.info.z_axis * dot
819    }
820}
821
822/// A face.
823#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
824#[ts(export)]
825#[serde(rename_all = "camelCase")]
826pub struct Face {
827    /// The id of the face.
828    pub id: uuid::Uuid,
829    /// The artifact ID.
830    pub artifact_id: ArtifactId,
831    /// The scene object ID.
832    pub object_id: ObjectId,
833    /// The tag of the face.
834    pub value: String,
835    /// What should the face's X axis be?
836    pub x_axis: Point3d,
837    /// What should the face's Y axis be?
838    pub y_axis: Point3d,
839    /// The solid the face is on.
840    pub parent_solid: FaceParentSolid,
841    pub units: UnitLength,
842    #[serde(skip)]
843    pub meta: Vec<Metadata>,
844}
845
846/// The limited subset of a face's parent solid needed by face-backed sketches.
847#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
848#[ts(export)]
849#[serde(rename_all = "camelCase")]
850pub struct FaceParentSolid {
851    /// Which solid does this face belong to?
852    pub solid_id: Uuid,
853    /// ID of the sketch which created this solid, if any.
854    pub creator_sketch_id: Option<Uuid>,
855    /// Has the creator sketch been closed? This is only relevant if `creator_sketch_id` is Some, and we cannot infer the closed status otherwise.
856    pub creator_sketch_is_closed: Option<ProfileClosed>,
857    /// Pending edge cut IDs that may need to be flushed before referencing the face.
858    #[serde(default, skip_serializing_if = "Vec::is_empty")]
859    pub edge_cut_ids: Vec<Uuid>,
860}
861
862impl FaceParentSolid {
863    pub(crate) fn sketch_or_solid_id(&self) -> Uuid {
864        self.creator_sketch_id.unwrap_or(self.solid_id)
865    }
866}
867
868/// A bounded edge.
869/// Carries either `edge_id` (resolved) or `edge_specifier` (payload passed through for resolution in blend).
870#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
871#[ts(export)]
872#[serde(rename_all = "camelCase")]
873pub struct BoundedEdge {
874    /// The id of the face this edge belongs to.
875    pub face_id: uuid::Uuid,
876    /// The id of the edge (when resolved from a tag or UUID). Mutually exclusive with `edge_specifier`.
877    #[serde(skip_serializing_if = "Option::is_none")]
878    pub edge_id: Option<uuid::Uuid>,
879    /// Edge specifier payload (sideFaces, endFaces, index) when not resolved. Resolved in blend().
880    #[serde(skip_serializing_if = "Option::is_none")]
881    pub edge_specifier: Option<UnresolvedEdgeSpecifier>,
882    /// A percentage bound of the edge, used to restrict what portion of the edge will be used.
883    /// Range (0, 1)
884    pub lower_bound: f32,
885    /// A percentage bound of the edge, used to restrict what portion of the edge will be used.
886    /// Range (0, 1)
887    pub upper_bound: f32,
888}
889
890/// Kind of plane.
891#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq, ts_rs::TS, FromStr, Display)]
892#[ts(export)]
893#[display(style = "camelCase")]
894pub enum PlaneKind {
895    #[serde(rename = "XY", alias = "xy")]
896    #[display("XY")]
897    XY,
898    #[serde(rename = "XZ", alias = "xz")]
899    #[display("XZ")]
900    XZ,
901    #[serde(rename = "YZ", alias = "yz")]
902    #[display("YZ")]
903    YZ,
904    /// A custom plane.
905    #[display("Custom")]
906    Custom,
907}
908
909#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
910#[ts(export)]
911#[serde(tag = "type", rename_all = "camelCase")]
912pub struct Sketch {
913    /// The id of the sketch (this will change when the engine's reference to it changes).
914    pub id: uuid::Uuid,
915    /// The paths in the sketch.
916    /// Only paths on the "outside" i.e. the perimeter.
917    /// Does not include paths "inside" the profile (for example, edges made by subtracting a profile)
918    // Share accumulated paths when cloning a sketch; appending copies only the affected chunks.
919    #[ts(as = "Vec<Path>")]
920    pub paths: imbl::Vector<Path>,
921    /// Inner paths, resulting from subtract2d to carve profiles out of the sketch.
922    #[serde(default, skip_serializing_if = "Vec::is_empty")]
923    pub inner_paths: Vec<Path>,
924    /// What the sketch is on (can be a plane or a face).
925    pub on: SketchSurface,
926    /// The starting path.
927    pub start: BasePath,
928    /// Tag identifiers that have been declared in this sketch.
929    #[serde(default, skip_serializing_if = "IndexMap::is_empty")]
930    pub tags: IndexMap<String, TagIdentifier>,
931    /// The original id of the sketch. This stays the same even if the sketch is
932    /// is sketched on face etc.
933    pub artifact_id: ArtifactId,
934    #[ts(skip)]
935    pub original_id: uuid::Uuid,
936    /// If this sketch represents a region created from `region()`, the origin
937    /// sketch ID is the ID of the sketch block it was created from. None,
938    /// otherwise. This field corresponds to the `origin_path_id` of the `Path`
939    /// artifact.
940    #[serde(skip_serializing_if = "Option::is_none")]
941    #[ts(skip)]
942    pub origin_sketch_id: Option<uuid::Uuid>,
943    /// If the sketch includes a mirror.
944    #[serde(skip)]
945    pub mirror: Option<uuid::Uuid>,
946    /// If the sketch is a clone of another sketch.
947    #[serde(skip)]
948    pub clone: Option<uuid::Uuid>,
949    /// Synthetic pen-jump paths inserted to replay disconnected segment selections.
950    #[serde(skip)]
951    #[ts(skip)]
952    pub synthetic_jump_path_ids: Vec<uuid::Uuid>,
953    pub units: UnitLength,
954    /// Metadata.
955    #[serde(skip)]
956    pub meta: Vec<Metadata>,
957    /// Has the profile been closed?
958    /// If not given, defaults to yes, closed explicitly.
959    #[serde(
960        default = "ProfileClosed::explicitly",
961        skip_serializing_if = "ProfileClosed::is_explicitly"
962    )]
963    pub is_closed: ProfileClosed,
964}
965
966impl ProfileClosed {
967    #[expect(dead_code, reason = "it's not actually dead, it's called by serde")]
968    fn explicitly() -> Self {
969        Self::Explicitly
970    }
971
972    fn is_explicitly(&self) -> bool {
973        matches!(self, ProfileClosed::Explicitly)
974    }
975}
976
977/// Has the profile been closed?
978#[derive(Debug, Serialize, Eq, PartialEq, Clone, Copy, Hash, Ord, PartialOrd, ts_rs::TS)]
979#[serde(rename_all = "camelCase")]
980pub enum ProfileClosed {
981    /// It's definitely open.
982    No,
983    /// Unknown.
984    Maybe,
985    /// Yes, by adding a segment which loops back to the start.
986    Implicitly,
987    /// Yes, by calling `close()` or by making a closed shape (e.g. circle).
988    Explicitly,
989}
990
991impl Sketch {
992    // Tell the engine to enter sketch mode on the sketch.
993    // Run a specific command, then exit sketch mode.
994    pub(crate) fn build_sketch_mode_cmds(
995        &self,
996        exec_state: &mut ExecState,
997        inner_cmd: ModelingCmdReq,
998    ) -> Vec<ModelingCmdReq> {
999        vec![
1000            // Before we extrude, we need to enable the sketch mode.
1001            // We do this here in case extrude is called out of order.
1002            ModelingCmdReq {
1003                cmd: ModelingCmd::from(
1004                    mcmd::EnableSketchMode::builder()
1005                        .animated(false)
1006                        .ortho(false)
1007                        .entity_id(self.on.id())
1008                        .adjust_camera(false)
1009                        .maybe_planar_normal(if let SketchSurface::Plane(plane) = &self.on {
1010                            // We pass in the normal for the plane here.
1011                            let normal = plane.info.x_axis.axes_cross_product(&plane.info.y_axis);
1012                            Some(normal.into())
1013                        } else {
1014                            None
1015                        })
1016                        .build(),
1017                ),
1018                cmd_id: exec_state.next_uuid().into(),
1019            },
1020            inner_cmd,
1021            ModelingCmdReq {
1022                cmd: ModelingCmd::SketchModeDisable(mcmd::SketchModeDisable::builder().build()),
1023                cmd_id: exec_state.next_uuid().into(),
1024            },
1025        ]
1026    }
1027}
1028
1029/// A sketch type.
1030#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1031#[ts(export)]
1032#[serde(tag = "type", rename_all = "camelCase")]
1033pub enum SketchSurface {
1034    Plane(Box<Plane>),
1035    Face(Box<Face>),
1036}
1037
1038impl SketchSurface {
1039    pub(crate) fn id(&self) -> uuid::Uuid {
1040        match self {
1041            SketchSurface::Plane(plane) => plane.id,
1042            SketchSurface::Face(face) => face.id,
1043        }
1044    }
1045    pub(crate) fn x_axis(&self) -> Point3d {
1046        match self {
1047            SketchSurface::Plane(plane) => plane.info.x_axis,
1048            SketchSurface::Face(face) => face.x_axis,
1049        }
1050    }
1051    pub(crate) fn y_axis(&self) -> Point3d {
1052        match self {
1053            SketchSurface::Plane(plane) => plane.info.y_axis,
1054            SketchSurface::Face(face) => face.y_axis,
1055        }
1056    }
1057
1058    pub(crate) fn object_id(&self) -> Option<ObjectId> {
1059        match self {
1060            SketchSurface::Plane(plane) => plane.object_id,
1061            SketchSurface::Face(face) => Some(face.object_id),
1062        }
1063    }
1064
1065    pub(crate) fn set_object_id(&mut self, object_id: ObjectId) {
1066        match self {
1067            SketchSurface::Plane(plane) => plane.object_id = Some(object_id),
1068            SketchSurface::Face(face) => face.object_id = object_id,
1069        }
1070    }
1071}
1072
1073/// A Sketch, Face, or TaggedFace.
1074#[derive(Debug, Clone, PartialEq)]
1075pub enum Extrudable {
1076    /// Sketch.
1077    Sketch(Box<Sketch>),
1078    /// Tagged Face.
1079    FaceTag(FaceTag),
1080    /// Face.
1081    Face(Box<Face>),
1082    /// Tagged Edge.
1083    EdgeTag(Box<TagIdentifier>),
1084    /// Edge.
1085    Edge(Uuid),
1086    /// Edge specifier payload.
1087    EdgeSpecifier(UnresolvedEdgeSpecifier),
1088}
1089
1090impl Extrudable {
1091    /// Get the relevant id.
1092    pub async fn id_to_extrude(
1093        &self,
1094        exec_state: &mut ExecState,
1095        args: &Args,
1096        must_be_planar: bool,
1097    ) -> Result<uuid::Uuid, KclError> {
1098        match self {
1099            Extrudable::Sketch(sketch) => Ok(sketch.id),
1100            Extrudable::FaceTag(face_tag) => face_tag.get_face_id_from_tag(exec_state, args, must_be_planar).await,
1101            Extrudable::Face(face) => Ok(face.id),
1102            Extrudable::EdgeTag(edge_tag) => match edge_tag.get_cur_info() {
1103                Some(info) => Ok(info.id),
1104                None => Err(KclError::new_type(KclErrorDetails::new(
1105                    "Could not find a valid id to extrude".to_owned(),
1106                    vec![args.source_range],
1107                ))),
1108            },
1109            Extrudable::Edge(edge) => Ok(*edge),
1110            Extrudable::EdgeSpecifier(_) => Err(KclError::new_type(KclErrorDetails::new(
1111                "Could not find a legacy id for edge specifier".to_owned(),
1112                vec![args.source_range],
1113            ))),
1114        }
1115    }
1116
1117    pub fn as_sketch(&self) -> Option<Sketch> {
1118        match self {
1119            Extrudable::Sketch(sketch) => Some((**sketch).clone()),
1120            Extrudable::FaceTag(face) => match face.geometry() {
1121                Some(Geometry::Sketch(sketch)) => Some(sketch),
1122                Some(Geometry::Solid(solid)) => solid.sketch().cloned(),
1123                None => None,
1124            },
1125            Extrudable::Face(_) => None,
1126            Extrudable::EdgeTag(tag_identifier) => match tag_identifier.geometry() {
1127                Some(Geometry::Sketch(sketch)) => Some(sketch),
1128                Some(Geometry::Solid(solid)) => solid.sketch().cloned(),
1129                None => None,
1130            },
1131            Extrudable::Edge(_) => None,
1132            Extrudable::EdgeSpecifier(_) => None,
1133        }
1134    }
1135
1136    pub fn is_closed(&self) -> ProfileClosed {
1137        match self {
1138            Extrudable::Sketch(sketch) => sketch.is_closed,
1139            Extrudable::FaceTag(face_tag) => match face_tag.geometry() {
1140                Some(Geometry::Sketch(sketch)) => sketch.is_closed,
1141                Some(Geometry::Solid(solid)) => solid
1142                    .sketch()
1143                    .map(|sketch| sketch.is_closed)
1144                    .unwrap_or(ProfileClosed::Maybe),
1145                _ => ProfileClosed::Maybe,
1146            },
1147            Extrudable::Face(face) => match face.parent_solid.creator_sketch_is_closed {
1148                Some(is_closed) => is_closed,
1149                None => ProfileClosed::Maybe,
1150            },
1151            Extrudable::EdgeTag(edge_tag) => match edge_tag.geometry() {
1152                Some(Geometry::Sketch(sketch)) => sketch.is_closed,
1153                Some(Geometry::Solid(solid)) => solid
1154                    .sketch()
1155                    .map(|sketch| sketch.is_closed)
1156                    .unwrap_or(ProfileClosed::Maybe),
1157                _ => ProfileClosed::Maybe,
1158            },
1159            Extrudable::Edge(_) => ProfileClosed::Maybe,
1160            Extrudable::EdgeSpecifier(_) => ProfileClosed::Maybe,
1161        }
1162    }
1163}
1164
1165impl From<Sketch> for Extrudable {
1166    fn from(value: Sketch) -> Self {
1167        Extrudable::Sketch(Box::new(value))
1168    }
1169}
1170
1171#[derive(Debug, Clone)]
1172pub(crate) enum GetTangentialInfoFromPathsResult {
1173    PreviousPoint([f64; 2]),
1174    Arc {
1175        center: [f64; 2],
1176        ccw: bool,
1177    },
1178    Circle {
1179        center: [f64; 2],
1180        ccw: bool,
1181        radius: f64,
1182    },
1183    Ellipse {
1184        center: [f64; 2],
1185        ccw: bool,
1186        major_axis: [f64; 2],
1187        _minor_radius: f64,
1188    },
1189}
1190
1191impl GetTangentialInfoFromPathsResult {
1192    pub(crate) fn tan_previous_point(&self, last_arc_end: [f64; 2]) -> [f64; 2] {
1193        match self {
1194            GetTangentialInfoFromPathsResult::PreviousPoint(p) => *p,
1195            GetTangentialInfoFromPathsResult::Arc { center, ccw } => {
1196                crate::std::utils::get_tangent_point_from_previous_arc(*center, *ccw, last_arc_end)
1197            }
1198            // The circle always starts at 0 degrees, so a suitable tangent
1199            // point is either directly above or below.
1200            GetTangentialInfoFromPathsResult::Circle {
1201                center, radius, ccw, ..
1202            } => [center[0] + radius, center[1] + if *ccw { -1.0 } else { 1.0 }],
1203            GetTangentialInfoFromPathsResult::Ellipse {
1204                center,
1205                major_axis,
1206                ccw,
1207                ..
1208            } => [center[0] + major_axis[0], center[1] + if *ccw { -1.0 } else { 1.0 }],
1209        }
1210    }
1211}
1212
1213impl Sketch {
1214    pub(crate) fn add_tag(
1215        &mut self,
1216        tag: NodeRef<'_, TagDeclarator>,
1217        current_path: &Path,
1218        exec_state: &ExecState,
1219        surface: Option<&ExtrudeSurface>,
1220    ) {
1221        let mut tag_identifier: TagIdentifier = tag.into();
1222        let base = current_path.get_base();
1223        let mut sketch_copy = self.clone();
1224        sketch_copy.tags.clear();
1225        tag_identifier.info.push((
1226            exec_state.stack().current_epoch(),
1227            TagEngineInfo {
1228                id: base.geo_meta.id,
1229                geometry: Geometry::Sketch(sketch_copy),
1230                path: Some(current_path.clone()),
1231                surface: surface.cloned(),
1232            },
1233        ));
1234
1235        self.tags.insert(tag.name.to_string(), tag_identifier);
1236    }
1237
1238    pub(crate) fn merge_tags<'a>(&mut self, tags: impl Iterator<Item = &'a TagIdentifier>) {
1239        for t in tags {
1240            match self.tags.get_mut(&t.value) {
1241                Some(id) => {
1242                    id.merge_info(t);
1243                }
1244                None => {
1245                    self.tags.insert(t.value.clone(), t.clone());
1246                }
1247            }
1248        }
1249    }
1250
1251    /// Get the path most recently sketched.
1252    pub(crate) fn latest_path(&self) -> Option<&Path> {
1253        self.paths.last()
1254    }
1255
1256    /// The "pen" is an imaginary pen drawing the path.
1257    /// This gets the current point the pen is hovering over, i.e. the point
1258    /// where the last path segment ends, and the next path segment will begin.
1259    pub(crate) fn current_pen_position(&self) -> Result<Point2d, KclError> {
1260        let Some(path) = self.latest_path() else {
1261            return Ok(Point2d::new(self.start.to[0], self.start.to[1], self.start.units));
1262        };
1263
1264        let to = path.get_base().to;
1265        Ok(Point2d::new(to[0], to[1], path.get_base().units))
1266    }
1267
1268    pub(crate) fn get_tangential_info_from_paths(&self) -> GetTangentialInfoFromPathsResult {
1269        let Some(path) = self.latest_path() else {
1270            return GetTangentialInfoFromPathsResult::PreviousPoint(self.start.to);
1271        };
1272        path.get_tangential_info()
1273    }
1274}
1275
1276#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1277#[ts(export)]
1278#[serde(tag = "type", rename_all = "camelCase")]
1279pub struct Solid {
1280    /// The id of the solid.
1281    pub id: uuid::Uuid,
1282    /// Internal KCL value generation. The engine may reuse `id` for a new value.
1283    #[serde(skip)]
1284    #[ts(skip)]
1285    pub value_id: uuid::Uuid,
1286    /// The engine entity whose children correspond to the topology references
1287    /// stored on this solid. Pattern copies retain their source topology,
1288    /// while consuming operations and clones replace it with their output.
1289    #[serde(skip)]
1290    #[ts(skip)]
1291    pub(crate) topology_id: uuid::Uuid,
1292    /// Source sketch or region whose tags receive this body's face information
1293    /// in caller memory. Match this against the source's artifact ID, independently
1294    /// of the body's topology. Modifications preserve it; independent copies clear it.
1295    /// When unset, tag updates use the retained sketch's original topology ID.
1296    #[serde(skip)]
1297    #[ts(skip)]
1298    pub(crate) tag_update_source_artifact_id: Option<ArtifactId>,
1299    /// The semantic body artifact from which a pattern copy was created.
1300    /// Pattern commands replace `artifact_id` with the copy's engine entity
1301    /// ID, so retain this to distinguish Sweep-backed bodies from composites.
1302    #[serde(skip)]
1303    #[ts(skip)]
1304    pub(crate) pattern_source_artifact_id: Option<ArtifactId>,
1305    /// Body type known from the KCL operation that created this value.
1306    ///
1307    /// Mock execution cannot query the engine for this, so retain it when it
1308    /// is known locally. Procedural operations whose result depends on engine
1309    /// topology may leave it unset.
1310    #[serde(skip)]
1311    #[ts(skip)]
1312    pub(crate) best_guess_body_type: Option<kcmc::shared::BodyType>,
1313    /// The artifact ID of the solid.  Unlike `id`, this doesn't change.
1314    pub artifact_id: ArtifactId,
1315    /// The extrude surfaces.
1316    pub value: Vec<ExtrudeSurface>,
1317    /// Tag identifiers for the faces of this body, declared via tag arguments
1318    /// (e.g. `tag`, `tagStart`, `tagEnd`) on the call that created it.
1319    #[serde(default, skip_serializing_if = "IndexMap::is_empty")]
1320    pub faces: IndexMap<String, TagIdentifier>,
1321    /// How this solid was created.
1322    #[serde(rename = "sketch")]
1323    pub creator: SolidCreator,
1324    /// The id of the extrusion start cap
1325    pub start_cap_id: Option<uuid::Uuid>,
1326    /// The id of the extrusion end cap
1327    pub end_cap_id: Option<uuid::Uuid>,
1328    /// Chamfers or fillets on this solid.
1329    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1330    pub edge_cuts: Vec<EdgeCut>,
1331    /// Batch-end fillet/chamfer command ids that do not have concrete edge ids.
1332    #[serde(skip)]
1333    #[ts(skip)]
1334    pub pending_edge_cut_ids: Vec<uuid::Uuid>,
1335    /// The units of the solid.
1336    pub units: UnitLength,
1337    /// Is this a sectional solid?
1338    pub sectional: bool,
1339    /// Metadata.
1340    #[serde(skip)]
1341    pub meta: Vec<Metadata>,
1342}
1343
1344#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1345#[ts(export)]
1346pub struct CreatorFace {
1347    /// The face id that served as the base.
1348    pub face_id: uuid::Uuid,
1349    /// The solid id that owned the face.
1350    pub solid_id: uuid::Uuid,
1351    /// The sketch used for the operation.
1352    pub sketch: Sketch,
1353}
1354
1355#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1356#[ts(export)]
1357pub struct CreatorEdge {
1358    /// The edge id that served as the base.
1359    pub edge_id: uuid::Uuid,
1360    /// The solid id that owned the edge.
1361    pub body_id: uuid::Uuid,
1362}
1363
1364/// How a solid was created.
1365#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1366#[ts(export)]
1367#[serde(tag = "creatorType", rename_all = "camelCase")]
1368pub enum SolidCreator {
1369    /// Created from a sketch.
1370    Sketch(Sketch),
1371    /// Created by extruding or modifying a face.
1372    Face(CreatorFace),
1373    /// Created by extruding or modifying an edge.
1374    Edge(CreatorEdge),
1375    /// Created procedurally without a sketch.
1376    Procedural,
1377}
1378
1379/// Whether a new body replaces its source or is an independent copy.
1380#[derive(Debug, Clone, Copy)]
1381pub(crate) enum NewBodyKind {
1382    /// Preserve the source region's tag updates through a replacement operation.
1383    Replacement,
1384    /// Fork from the source region. Don't update the source region's tags when
1385    /// the new body is updated.
1386    IndependentCopy,
1387}
1388
1389impl Solid {
1390    pub fn sketch(&self) -> Option<&Sketch> {
1391        match &self.creator {
1392            SolidCreator::Sketch(sketch) => Some(sketch),
1393            SolidCreator::Face(CreatorFace { sketch, .. }) => Some(sketch),
1394            SolidCreator::Edge(_) => None,
1395            SolidCreator::Procedural => None,
1396        }
1397    }
1398
1399    pub fn sketch_mut(&mut self) -> Option<&mut Sketch> {
1400        match &mut self.creator {
1401            SolidCreator::Sketch(sketch) => Some(sketch),
1402            SolidCreator::Face(CreatorFace { sketch, .. }) => Some(sketch),
1403            SolidCreator::Edge(_) => None,
1404            SolidCreator::Procedural => None,
1405        }
1406    }
1407
1408    pub fn sketch_id(&self) -> Option<uuid::Uuid> {
1409        self.sketch().map(|sketch| sketch.id)
1410    }
1411
1412    pub fn original_id(&self) -> uuid::Uuid {
1413        self.sketch().map(|sketch| sketch.original_id).unwrap_or(self.id)
1414    }
1415
1416    pub(crate) fn topology_id(&self) -> uuid::Uuid {
1417        self.topology_id
1418    }
1419
1420    /// Make this solid a brand-new body produced by an operation. It now owns
1421    /// the topology of `engine_id`, and any retained pattern provenance no
1422    /// longer applies. Independent copies also stop updating their source
1423    /// region's tags, including when mock execution does not remap child IDs.
1424    pub(crate) fn become_new_body(&mut self, engine_id: uuid::Uuid, artifact_id: ArtifactId, kind: NewBodyKind) {
1425        self.topology_id = engine_id;
1426        self.pattern_source_artifact_id = None;
1427        self.artifact_id = artifact_id;
1428        match kind {
1429            NewBodyKind::Replacement => {}
1430            NewBodyKind::IndependentCopy => self.tag_update_source_artifact_id = None,
1431        }
1432    }
1433
1434    /// Make this solid a pattern copy. It gets a new top-level entity artifact
1435    /// while retaining the source body's topology and semantic artifact
1436    /// provenance.
1437    pub(crate) fn become_pattern_copy(&mut self, copy_engine_id: uuid::Uuid) {
1438        self.pattern_source_artifact_id.get_or_insert(self.artifact_id);
1439        self.artifact_id = ArtifactId::new(copy_engine_id);
1440        self.tag_update_source_artifact_id = None;
1441    }
1442
1443    pub(crate) fn get_all_edge_cut_ids(&self) -> impl Iterator<Item = uuid::Uuid> + '_ {
1444        self.edge_cuts
1445            .iter()
1446            .map(|foc| foc.id())
1447            .chain(self.pending_edge_cut_ids.iter().copied())
1448    }
1449}
1450
1451impl From<&Solid> for FaceParentSolid {
1452    fn from(solid: &Solid) -> Self {
1453        Self {
1454            solid_id: solid.id,
1455            creator_sketch_id: solid.sketch_id(),
1456            creator_sketch_is_closed: solid.sketch().map(|sketch| sketch.is_closed),
1457            edge_cut_ids: solid.get_all_edge_cut_ids().collect(),
1458        }
1459    }
1460}
1461
1462/// A fillet or a chamfer.
1463#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1464#[ts(export)]
1465#[serde(tag = "type", rename_all = "camelCase")]
1466pub enum EdgeCut {
1467    /// A fillet.
1468    Fillet {
1469        /// The id of the engine command that called this fillet.
1470        id: uuid::Uuid,
1471        radius: TyF64,
1472        /// The engine id of the edge to fillet.
1473        #[serde(rename = "edgeId")]
1474        edge_id: uuid::Uuid,
1475        tag: Box<Option<TagNode>>,
1476    },
1477    /// A chamfer.
1478    Chamfer {
1479        /// The id of the engine command that called this chamfer.
1480        id: uuid::Uuid,
1481        length: TyF64,
1482        /// The engine id of the edge to chamfer.
1483        #[serde(rename = "edgeId")]
1484        edge_id: uuid::Uuid,
1485        tag: Box<Option<TagNode>>,
1486    },
1487}
1488
1489impl EdgeCut {
1490    pub fn id(&self) -> uuid::Uuid {
1491        match self {
1492            EdgeCut::Fillet { id, .. } => *id,
1493            EdgeCut::Chamfer { id, .. } => *id,
1494        }
1495    }
1496
1497    pub fn set_id(&mut self, id: uuid::Uuid) {
1498        match self {
1499            EdgeCut::Fillet { id: i, .. } => *i = id,
1500            EdgeCut::Chamfer { id: i, .. } => *i = id,
1501        }
1502    }
1503
1504    pub fn edge_id(&self) -> uuid::Uuid {
1505        match self {
1506            EdgeCut::Fillet { edge_id, .. } => *edge_id,
1507            EdgeCut::Chamfer { edge_id, .. } => *edge_id,
1508        }
1509    }
1510
1511    pub fn set_edge_id(&mut self, id: uuid::Uuid) {
1512        match self {
1513            EdgeCut::Fillet { edge_id: i, .. } => *i = id,
1514            EdgeCut::Chamfer { edge_id: i, .. } => *i = id,
1515        }
1516    }
1517
1518    pub fn tag(&self) -> Option<TagNode> {
1519        match self {
1520            EdgeCut::Fillet { tag, .. } => *tag.clone(),
1521            EdgeCut::Chamfer { tag, .. } => *tag.clone(),
1522        }
1523    }
1524}
1525
1526#[derive(Debug, Serialize, PartialEq, Clone, Copy, ts_rs::TS)]
1527#[ts(export)]
1528pub struct Point2d {
1529    pub x: f64,
1530    pub y: f64,
1531    pub units: UnitLength,
1532}
1533
1534impl Point2d {
1535    pub const ZERO: Self = Self {
1536        x: 0.0,
1537        y: 0.0,
1538        units: UnitLength::Millimeters,
1539    };
1540
1541    pub fn new(x: f64, y: f64, units: UnitLength) -> Self {
1542        Self { x, y, units }
1543    }
1544
1545    pub fn into_x(self) -> TyF64 {
1546        TyF64::new(self.x, NumericType::length(self.units))
1547    }
1548
1549    pub fn into_y(self) -> TyF64 {
1550        TyF64::new(self.y, NumericType::length(self.units))
1551    }
1552
1553    pub fn ignore_units(self) -> [f64; 2] {
1554        [self.x, self.y]
1555    }
1556}
1557
1558#[derive(Debug, Deserialize, Serialize, PartialEq, Clone, Copy, ts_rs::TS, Default)]
1559#[ts(export)]
1560pub struct Point3d {
1561    pub x: f64,
1562    pub y: f64,
1563    pub z: f64,
1564    pub units: Option<UnitLength>,
1565}
1566
1567impl Point3d {
1568    pub const ZERO: Self = Self {
1569        x: 0.0,
1570        y: 0.0,
1571        z: 0.0,
1572        units: Some(UnitLength::Millimeters),
1573    };
1574
1575    pub fn new(x: f64, y: f64, z: f64, units: Option<UnitLength>) -> Self {
1576        Self { x, y, z, units }
1577    }
1578
1579    pub const fn is_zero(&self) -> bool {
1580        self.x == 0.0 && self.y == 0.0 && self.z == 0.0
1581    }
1582
1583    /// Calculate the cross product of this vector with another.
1584    ///
1585    /// This should only be applied to axes or other vectors which represent only a direction (and
1586    /// no magnitude) since units are ignored.
1587    pub fn axes_cross_product(&self, other: &Self) -> Self {
1588        Self {
1589            x: self.y * other.z - self.z * other.y,
1590            y: self.z * other.x - self.x * other.z,
1591            z: self.x * other.y - self.y * other.x,
1592            units: None,
1593        }
1594    }
1595
1596    /// Normalize `-0.0` to `0.0` for cleaner serialized axis data.
1597    pub fn canonicalize_signed_zero(&mut self) {
1598        if self.x == 0.0 {
1599            self.x = 0.0;
1600        }
1601        if self.y == 0.0 {
1602            self.y = 0.0;
1603        }
1604        if self.z == 0.0 {
1605            self.z = 0.0;
1606        }
1607    }
1608
1609    /// Calculate the dot product of this vector with another.
1610    ///
1611    /// This should only be applied to axes or other vectors which represent only a direction (and
1612    /// no magnitude) since units are ignored.
1613    pub fn axes_dot_product(&self, other: &Self) -> f64 {
1614        let x = self.x * other.x;
1615        let y = self.y * other.y;
1616        let z = self.z * other.z;
1617        x + y + z
1618    }
1619
1620    pub fn normalize(&self) -> Self {
1621        let len = f64::sqrt(self.x * self.x + self.y * self.y + self.z * self.z);
1622        Point3d {
1623            x: self.x / len,
1624            y: self.y / len,
1625            z: self.z / len,
1626            units: None,
1627        }
1628    }
1629
1630    pub fn as_3_dims(&self) -> ([f64; 3], Option<UnitLength>) {
1631        let p = [self.x, self.y, self.z];
1632        let u = self.units;
1633        (p, u)
1634    }
1635
1636    pub(crate) fn negated(self) -> Self {
1637        Self {
1638            x: -self.x,
1639            y: -self.y,
1640            z: -self.z,
1641            units: self.units,
1642        }
1643    }
1644}
1645
1646impl From<[TyF64; 3]> for Point3d {
1647    fn from(p: [TyF64; 3]) -> Self {
1648        Self {
1649            x: p[0].n,
1650            y: p[1].n,
1651            z: p[2].n,
1652            units: p[0].ty.as_length(),
1653        }
1654    }
1655}
1656
1657impl From<Point3d> for Point3D {
1658    fn from(p: Point3d) -> Self {
1659        Self { x: p.x, y: p.y, z: p.z }
1660    }
1661}
1662
1663impl From<Point3d> for kittycad_modeling_cmds::shared::Point3d<LengthUnit> {
1664    fn from(p: Point3d) -> Self {
1665        if let Some(units) = p.units {
1666            Self {
1667                x: LengthUnit(adjust_length(units, p.x, UnitLength::Millimeters).0),
1668                y: LengthUnit(adjust_length(units, p.y, UnitLength::Millimeters).0),
1669                z: LengthUnit(adjust_length(units, p.z, UnitLength::Millimeters).0),
1670            }
1671        } else {
1672            Self {
1673                x: LengthUnit(p.x),
1674                y: LengthUnit(p.y),
1675                z: LengthUnit(p.z),
1676            }
1677        }
1678    }
1679}
1680
1681impl Add for Point3d {
1682    type Output = Point3d;
1683
1684    fn add(self, rhs: Self) -> Self::Output {
1685        // TODO should assert that self and rhs the same units or coerce them
1686        Point3d {
1687            x: self.x + rhs.x,
1688            y: self.y + rhs.y,
1689            z: self.z + rhs.z,
1690            units: self.units,
1691        }
1692    }
1693}
1694
1695impl AddAssign for Point3d {
1696    fn add_assign(&mut self, rhs: Self) {
1697        *self = *self + rhs
1698    }
1699}
1700
1701impl Sub for Point3d {
1702    type Output = Point3d;
1703
1704    fn sub(self, rhs: Self) -> Self::Output {
1705        let (x, y, z) = if rhs.units != self.units
1706            && let Some(sunits) = self.units
1707            && let Some(runits) = rhs.units
1708        {
1709            (
1710                adjust_length(runits, rhs.x, sunits).0,
1711                adjust_length(runits, rhs.y, sunits).0,
1712                adjust_length(runits, rhs.z, sunits).0,
1713            )
1714        } else {
1715            (rhs.x, rhs.y, rhs.z)
1716        };
1717        Point3d {
1718            x: self.x - x,
1719            y: self.y - y,
1720            z: self.z - z,
1721            units: self.units,
1722        }
1723    }
1724}
1725
1726impl SubAssign for Point3d {
1727    fn sub_assign(&mut self, rhs: Self) {
1728        *self = *self - rhs
1729    }
1730}
1731
1732impl Mul<f64> for Point3d {
1733    type Output = Point3d;
1734
1735    fn mul(self, rhs: f64) -> Self::Output {
1736        Point3d {
1737            x: self.x * rhs,
1738            y: self.y * rhs,
1739            z: self.z * rhs,
1740            units: self.units,
1741        }
1742    }
1743}
1744
1745/// A base path.
1746#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1747#[ts(export)]
1748#[serde(rename_all = "camelCase")]
1749pub struct BasePath {
1750    /// The from point.
1751    #[ts(type = "[number, number]")]
1752    pub from: [f64; 2],
1753    /// The to point.
1754    #[ts(type = "[number, number]")]
1755    pub to: [f64; 2],
1756    pub units: UnitLength,
1757    /// The tag of the path.
1758    pub tag: Option<TagNode>,
1759    /// Metadata.
1760    #[serde(rename = "__geoMeta")]
1761    pub geo_meta: GeoMeta,
1762}
1763
1764impl BasePath {
1765    pub fn get_to(&self) -> [TyF64; 2] {
1766        let ty = NumericType::length(self.units);
1767        [TyF64::new(self.to[0], ty), TyF64::new(self.to[1], ty)]
1768    }
1769
1770    pub fn get_from(&self) -> [TyF64; 2] {
1771        let ty = NumericType::length(self.units);
1772        [TyF64::new(self.from[0], ty), TyF64::new(self.from[1], ty)]
1773    }
1774}
1775
1776/// Geometry metadata.
1777#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1778#[ts(export)]
1779#[serde(rename_all = "camelCase")]
1780pub struct GeoMeta {
1781    /// The id of the geometry.
1782    pub id: uuid::Uuid,
1783    /// Metadata.
1784    #[serde(flatten)]
1785    pub metadata: Metadata,
1786}
1787
1788/// A path.
1789#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1790#[ts(export)]
1791#[serde(tag = "type")]
1792pub enum Path {
1793    /// A straight line which ends at the given point.
1794    ToPoint {
1795        #[serde(flatten)]
1796        base: BasePath,
1797    },
1798    /// A arc that is tangential to the last path segment that goes to a point
1799    TangentialArcTo {
1800        #[serde(flatten)]
1801        base: BasePath,
1802        /// the arc's center
1803        #[ts(type = "[number, number]")]
1804        center: [f64; 2],
1805        /// arc's direction
1806        ccw: bool,
1807    },
1808    /// A arc that is tangential to the last path segment
1809    TangentialArc {
1810        #[serde(flatten)]
1811        base: BasePath,
1812        /// the arc's center
1813        #[ts(type = "[number, number]")]
1814        center: [f64; 2],
1815        /// arc's direction
1816        ccw: bool,
1817    },
1818    // TODO: consolidate segment enums, remove Circle. https://github.com/KittyCAD/modeling-app/issues/3940
1819    /// a complete arc
1820    Circle {
1821        #[serde(flatten)]
1822        base: BasePath,
1823        /// the arc's center
1824        #[ts(type = "[number, number]")]
1825        center: [f64; 2],
1826        /// the arc's radius
1827        radius: f64,
1828        /// arc's direction
1829        /// This is used to compute the tangential angle.
1830        ccw: bool,
1831    },
1832    CircleThreePoint {
1833        #[serde(flatten)]
1834        base: BasePath,
1835        /// Point 1 of the circle
1836        #[ts(type = "[number, number]")]
1837        p1: [f64; 2],
1838        /// Point 2 of the circle
1839        #[ts(type = "[number, number]")]
1840        p2: [f64; 2],
1841        /// Point 3 of the circle
1842        #[ts(type = "[number, number]")]
1843        p3: [f64; 2],
1844    },
1845    ArcThreePoint {
1846        #[serde(flatten)]
1847        base: BasePath,
1848        /// Point 1 of the arc (base on the end of previous segment)
1849        #[ts(type = "[number, number]")]
1850        p1: [f64; 2],
1851        /// Point 2 of the arc (interiorAbsolute kwarg)
1852        #[ts(type = "[number, number]")]
1853        p2: [f64; 2],
1854        /// Point 3 of the arc (endAbsolute kwarg)
1855        #[ts(type = "[number, number]")]
1856        p3: [f64; 2],
1857    },
1858    /// A path that is horizontal.
1859    Horizontal {
1860        #[serde(flatten)]
1861        base: BasePath,
1862        /// The x coordinate.
1863        x: f64,
1864    },
1865    /// An angled line to.
1866    AngledLineTo {
1867        #[serde(flatten)]
1868        base: BasePath,
1869        /// The x coordinate.
1870        x: Option<f64>,
1871        /// The y coordinate.
1872        y: Option<f64>,
1873    },
1874    /// A base path.
1875    Base {
1876        #[serde(flatten)]
1877        base: BasePath,
1878    },
1879    /// A circular arc, not necessarily tangential to the current point.
1880    Arc {
1881        #[serde(flatten)]
1882        base: BasePath,
1883        /// Center of the circle that this arc is drawn on.
1884        center: [f64; 2],
1885        /// Radius of the circle that this arc is drawn on.
1886        radius: f64,
1887        /// True if the arc is counterclockwise.
1888        ccw: bool,
1889    },
1890    Ellipse {
1891        #[serde(flatten)]
1892        base: BasePath,
1893        center: [f64; 2],
1894        major_axis: [f64; 2],
1895        minor_radius: f64,
1896        ccw: bool,
1897    },
1898    //TODO: (bc) figure this out
1899    Conic {
1900        #[serde(flatten)]
1901        base: BasePath,
1902    },
1903    /// A cubic Bezier curve.
1904    Bezier {
1905        #[serde(flatten)]
1906        base: BasePath,
1907        /// First control point (absolute coordinates).
1908        #[ts(type = "[number, number]")]
1909        control1: [f64; 2],
1910        /// Second control point (absolute coordinates).
1911        #[ts(type = "[number, number]")]
1912        control2: [f64; 2],
1913    },
1914}
1915
1916impl Path {
1917    pub fn get_id(&self) -> uuid::Uuid {
1918        match self {
1919            Path::ToPoint { base } => base.geo_meta.id,
1920            Path::Horizontal { base, .. } => base.geo_meta.id,
1921            Path::AngledLineTo { base, .. } => base.geo_meta.id,
1922            Path::Base { base } => base.geo_meta.id,
1923            Path::TangentialArcTo { base, .. } => base.geo_meta.id,
1924            Path::TangentialArc { base, .. } => base.geo_meta.id,
1925            Path::Circle { base, .. } => base.geo_meta.id,
1926            Path::CircleThreePoint { base, .. } => base.geo_meta.id,
1927            Path::Arc { base, .. } => base.geo_meta.id,
1928            Path::ArcThreePoint { base, .. } => base.geo_meta.id,
1929            Path::Ellipse { base, .. } => base.geo_meta.id,
1930            Path::Conic { base, .. } => base.geo_meta.id,
1931            Path::Bezier { base, .. } => base.geo_meta.id,
1932        }
1933    }
1934
1935    pub fn set_id(&mut self, id: uuid::Uuid) {
1936        match self {
1937            Path::ToPoint { base } => base.geo_meta.id = id,
1938            Path::Horizontal { base, .. } => base.geo_meta.id = id,
1939            Path::AngledLineTo { base, .. } => base.geo_meta.id = id,
1940            Path::Base { base } => base.geo_meta.id = id,
1941            Path::TangentialArcTo { base, .. } => base.geo_meta.id = id,
1942            Path::TangentialArc { base, .. } => base.geo_meta.id = id,
1943            Path::Circle { base, .. } => base.geo_meta.id = id,
1944            Path::CircleThreePoint { base, .. } => base.geo_meta.id = id,
1945            Path::Arc { base, .. } => base.geo_meta.id = id,
1946            Path::ArcThreePoint { base, .. } => base.geo_meta.id = id,
1947            Path::Ellipse { base, .. } => base.geo_meta.id = id,
1948            Path::Conic { base, .. } => base.geo_meta.id = id,
1949            Path::Bezier { base, .. } => base.geo_meta.id = id,
1950        }
1951    }
1952
1953    pub fn get_tag(&self) -> Option<TagNode> {
1954        match self {
1955            Path::ToPoint { base } => base.tag.clone(),
1956            Path::Horizontal { base, .. } => base.tag.clone(),
1957            Path::AngledLineTo { base, .. } => base.tag.clone(),
1958            Path::Base { base } => base.tag.clone(),
1959            Path::TangentialArcTo { base, .. } => base.tag.clone(),
1960            Path::TangentialArc { base, .. } => base.tag.clone(),
1961            Path::Circle { base, .. } => base.tag.clone(),
1962            Path::CircleThreePoint { base, .. } => base.tag.clone(),
1963            Path::Arc { base, .. } => base.tag.clone(),
1964            Path::ArcThreePoint { base, .. } => base.tag.clone(),
1965            Path::Ellipse { base, .. } => base.tag.clone(),
1966            Path::Conic { base, .. } => base.tag.clone(),
1967            Path::Bezier { base, .. } => base.tag.clone(),
1968        }
1969    }
1970
1971    pub fn get_base(&self) -> &BasePath {
1972        match self {
1973            Path::ToPoint { base } => base,
1974            Path::Horizontal { base, .. } => base,
1975            Path::AngledLineTo { base, .. } => base,
1976            Path::Base { base } => base,
1977            Path::TangentialArcTo { base, .. } => base,
1978            Path::TangentialArc { base, .. } => base,
1979            Path::Circle { base, .. } => base,
1980            Path::CircleThreePoint { base, .. } => base,
1981            Path::Arc { base, .. } => base,
1982            Path::ArcThreePoint { base, .. } => base,
1983            Path::Ellipse { base, .. } => base,
1984            Path::Conic { base, .. } => base,
1985            Path::Bezier { base, .. } => base,
1986        }
1987    }
1988
1989    /// Where does this path segment start?
1990    pub fn get_from(&self) -> [TyF64; 2] {
1991        let p = &self.get_base().from;
1992        let ty = NumericType::length(self.get_base().units);
1993        [TyF64::new(p[0], ty), TyF64::new(p[1], ty)]
1994    }
1995
1996    /// Where does this path segment end?
1997    pub fn get_to(&self) -> [TyF64; 2] {
1998        let p = &self.get_base().to;
1999        let ty = NumericType::length(self.get_base().units);
2000        [TyF64::new(p[0], ty), TyF64::new(p[1], ty)]
2001    }
2002
2003    /// The path segment start point and its type.
2004    pub fn start_point_components(&self) -> ([f64; 2], NumericType) {
2005        let p = &self.get_base().from;
2006        let ty = NumericType::length(self.get_base().units);
2007        (*p, ty)
2008    }
2009
2010    /// The path segment end point and its type.
2011    pub fn end_point_components(&self) -> ([f64; 2], NumericType) {
2012        let p = &self.get_base().to;
2013        let ty = NumericType::length(self.get_base().units);
2014        (*p, ty)
2015    }
2016
2017    /// Length of this path segment, in cartesian plane. Not all segment types
2018    /// are supported.
2019    pub fn length(&self) -> Option<TyF64> {
2020        let n = match self {
2021            Self::ToPoint { .. } | Self::Base { .. } | Self::Horizontal { .. } | Self::AngledLineTo { .. } => {
2022                Some(linear_distance(&self.get_base().from, &self.get_base().to))
2023            }
2024            Self::TangentialArc {
2025                base: _,
2026                center,
2027                ccw: _,
2028            }
2029            | Self::TangentialArcTo {
2030                base: _,
2031                center,
2032                ccw: _,
2033            } => {
2034                // The radius can be calculated as the linear distance between `to` and `center`,
2035                // or between `from` and `center`. They should be the same.
2036                let radius = linear_distance(&self.get_base().from, center);
2037                debug_assert_eq!(radius, linear_distance(&self.get_base().to, center));
2038                // TODO: Call engine utils to figure this out.
2039                Some(linear_distance(&self.get_base().from, &self.get_base().to))
2040            }
2041            Self::Circle { radius, .. } => Some(TAU * radius),
2042            Self::CircleThreePoint { .. } => {
2043                let circle_center = crate::std::utils::calculate_circle_from_3_points([
2044                    self.get_base().from,
2045                    self.get_base().to,
2046                    self.get_base().to,
2047                ]);
2048                let radius = linear_distance(
2049                    &[circle_center.center[0], circle_center.center[1]],
2050                    &self.get_base().from,
2051                );
2052                Some(TAU * radius)
2053            }
2054            Self::Arc { .. } => {
2055                // TODO: Call engine utils to figure this out.
2056                Some(linear_distance(&self.get_base().from, &self.get_base().to))
2057            }
2058            Self::ArcThreePoint { .. } => {
2059                // TODO: Call engine utils to figure this out.
2060                Some(linear_distance(&self.get_base().from, &self.get_base().to))
2061            }
2062            Self::Ellipse { .. } => {
2063                // Not supported.
2064                None
2065            }
2066            Self::Conic { .. } => {
2067                // Not supported.
2068                None
2069            }
2070            Self::Bezier { .. } => {
2071                // Not supported - Bezier curve length requires numerical integration.
2072                None
2073            }
2074        };
2075        n.map(|n| TyF64::new(n, NumericType::length(self.get_base().units)))
2076    }
2077
2078    pub fn get_base_mut(&mut self) -> &mut BasePath {
2079        match self {
2080            Path::ToPoint { base } => base,
2081            Path::Horizontal { base, .. } => base,
2082            Path::AngledLineTo { base, .. } => base,
2083            Path::Base { base } => base,
2084            Path::TangentialArcTo { base, .. } => base,
2085            Path::TangentialArc { base, .. } => base,
2086            Path::Circle { base, .. } => base,
2087            Path::CircleThreePoint { base, .. } => base,
2088            Path::Arc { base, .. } => base,
2089            Path::ArcThreePoint { base, .. } => base,
2090            Path::Ellipse { base, .. } => base,
2091            Path::Conic { base, .. } => base,
2092            Path::Bezier { base, .. } => base,
2093        }
2094    }
2095
2096    pub(crate) fn get_tangential_info(&self) -> GetTangentialInfoFromPathsResult {
2097        match self {
2098            Path::TangentialArc { center, ccw, .. }
2099            | Path::TangentialArcTo { center, ccw, .. }
2100            | Path::Arc { center, ccw, .. } => GetTangentialInfoFromPathsResult::Arc {
2101                center: *center,
2102                ccw: *ccw,
2103            },
2104            Path::ArcThreePoint { p1, p2, p3, .. } => {
2105                let circle = crate::std::utils::calculate_circle_from_3_points([*p1, *p2, *p3]);
2106                GetTangentialInfoFromPathsResult::Arc {
2107                    center: circle.center,
2108                    ccw: crate::std::utils::is_points_ccw(&[*p1, *p2, *p3]) > 0,
2109                }
2110            }
2111            Path::Circle {
2112                center, ccw, radius, ..
2113            } => GetTangentialInfoFromPathsResult::Circle {
2114                center: *center,
2115                ccw: *ccw,
2116                radius: *radius,
2117            },
2118            Path::CircleThreePoint { p1, p2, p3, .. } => {
2119                let circle = crate::std::utils::calculate_circle_from_3_points([*p1, *p2, *p3]);
2120                let center_point = [circle.center[0], circle.center[1]];
2121                GetTangentialInfoFromPathsResult::Circle {
2122                    center: center_point,
2123                    // Note: a circle is always ccw regardless of the order of points
2124                    ccw: true,
2125                    radius: circle.radius,
2126                }
2127            }
2128            // TODO: (bc) fix me
2129            Path::Ellipse {
2130                center,
2131                major_axis,
2132                minor_radius,
2133                ccw,
2134                ..
2135            } => GetTangentialInfoFromPathsResult::Ellipse {
2136                center: *center,
2137                major_axis: *major_axis,
2138                _minor_radius: *minor_radius,
2139                ccw: *ccw,
2140            },
2141            Path::Conic { .. }
2142            | Path::ToPoint { .. }
2143            | Path::Horizontal { .. }
2144            | Path::AngledLineTo { .. }
2145            | Path::Base { .. }
2146            | Path::Bezier { .. } => {
2147                let base = self.get_base();
2148                GetTangentialInfoFromPathsResult::PreviousPoint(base.from)
2149            }
2150        }
2151    }
2152
2153    /// i.e. not a curve
2154    pub(crate) fn is_straight_line(&self) -> bool {
2155        matches!(self, Path::AngledLineTo { .. } | Path::ToPoint { .. })
2156    }
2157}
2158
2159/// Compute the straight-line distance between a pair of (2D) points.
2160#[rustfmt::skip]
2161fn linear_distance(
2162    [x0, y0]: &[f64; 2],
2163    [x1, y1]: &[f64; 2]
2164) -> f64 {
2165    let y_sq = (y1 - y0).squared();
2166    let x_sq = (x1 - x0).squared();
2167    (y_sq + x_sq).sqrt()
2168}
2169
2170/// An extrude surface.
2171#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2172#[ts(export)]
2173#[serde(tag = "type", rename_all = "camelCase")]
2174pub enum ExtrudeSurface {
2175    /// An extrude plane.
2176    ExtrudePlane(ExtrudePlane),
2177    ExtrudeArc(ExtrudeArc),
2178    Chamfer(ChamferSurface),
2179    Fillet(FilletSurface),
2180}
2181
2182// Chamfer surface.
2183#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2184#[ts(export)]
2185#[serde(rename_all = "camelCase")]
2186pub struct ChamferSurface {
2187    /// The id for the chamfer surface.
2188    pub face_id: uuid::Uuid,
2189    /// The tag.
2190    pub tag: Option<Node<TagDeclarator>>,
2191    /// Metadata.
2192    #[serde(flatten)]
2193    pub geo_meta: GeoMeta,
2194}
2195
2196// Fillet surface.
2197#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2198#[ts(export)]
2199#[serde(rename_all = "camelCase")]
2200pub struct FilletSurface {
2201    /// The id for the fillet surface.
2202    pub face_id: uuid::Uuid,
2203    /// The tag.
2204    pub tag: Option<Node<TagDeclarator>>,
2205    /// Metadata.
2206    #[serde(flatten)]
2207    pub geo_meta: GeoMeta,
2208}
2209
2210/// An extruded plane.
2211#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2212#[ts(export)]
2213#[serde(rename_all = "camelCase")]
2214pub struct ExtrudePlane {
2215    /// The face id for the extrude plane.
2216    pub face_id: uuid::Uuid,
2217    /// The tag.
2218    pub tag: Option<Node<TagDeclarator>>,
2219    /// Metadata.
2220    #[serde(flatten)]
2221    pub geo_meta: GeoMeta,
2222}
2223
2224/// An extruded arc.
2225#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2226#[ts(export)]
2227#[serde(rename_all = "camelCase")]
2228pub struct ExtrudeArc {
2229    /// The face id for the extrude plane.
2230    pub face_id: uuid::Uuid,
2231    /// The tag.
2232    pub tag: Option<Node<TagDeclarator>>,
2233    /// Metadata.
2234    #[serde(flatten)]
2235    pub geo_meta: GeoMeta,
2236}
2237
2238impl ExtrudeSurface {
2239    pub fn get_id(&self) -> uuid::Uuid {
2240        match self {
2241            ExtrudeSurface::ExtrudePlane(ep) => ep.geo_meta.id,
2242            ExtrudeSurface::ExtrudeArc(ea) => ea.geo_meta.id,
2243            ExtrudeSurface::Fillet(f) => f.geo_meta.id,
2244            ExtrudeSurface::Chamfer(c) => c.geo_meta.id,
2245        }
2246    }
2247
2248    pub fn set_id(&mut self, id: uuid::Uuid) {
2249        match self {
2250            ExtrudeSurface::ExtrudePlane(ep) => ep.geo_meta.id = id,
2251            ExtrudeSurface::ExtrudeArc(ea) => ea.geo_meta.id = id,
2252            ExtrudeSurface::Fillet(f) => f.geo_meta.id = id,
2253            ExtrudeSurface::Chamfer(c) => c.geo_meta.id = id,
2254        }
2255    }
2256
2257    pub fn face_id(&self) -> uuid::Uuid {
2258        match self {
2259            ExtrudeSurface::ExtrudePlane(ep) => ep.face_id,
2260            ExtrudeSurface::ExtrudeArc(ea) => ea.face_id,
2261            ExtrudeSurface::Fillet(f) => f.face_id,
2262            ExtrudeSurface::Chamfer(c) => c.face_id,
2263        }
2264    }
2265
2266    pub fn set_face_id(&mut self, face_id: uuid::Uuid) {
2267        match self {
2268            ExtrudeSurface::ExtrudePlane(ep) => ep.face_id = face_id,
2269            ExtrudeSurface::ExtrudeArc(ea) => ea.face_id = face_id,
2270            ExtrudeSurface::Fillet(f) => f.face_id = face_id,
2271            ExtrudeSurface::Chamfer(c) => c.face_id = face_id,
2272        }
2273    }
2274
2275    pub fn set_surface_tag(&mut self, tag: &TagNode) {
2276        match self {
2277            ExtrudeSurface::ExtrudePlane(extrude_plane) => extrude_plane.tag = Some(tag.clone()),
2278            ExtrudeSurface::ExtrudeArc(extrude_arc) => extrude_arc.tag = Some(tag.clone()),
2279            ExtrudeSurface::Chamfer(chamfer) => chamfer.tag = Some(tag.clone()),
2280            ExtrudeSurface::Fillet(fillet) => fillet.tag = Some(tag.clone()),
2281        }
2282    }
2283
2284    pub fn get_tag(&self) -> Option<Node<TagDeclarator>> {
2285        match self {
2286            ExtrudeSurface::ExtrudePlane(ep) => ep.tag.clone(),
2287            ExtrudeSurface::ExtrudeArc(ea) => ea.tag.clone(),
2288            ExtrudeSurface::Fillet(f) => f.tag.clone(),
2289            ExtrudeSurface::Chamfer(c) => c.tag.clone(),
2290        }
2291    }
2292}
2293
2294#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, ts_rs::TS)]
2295pub struct SketchVarId(pub usize);
2296
2297impl SketchVarId {
2298    pub const INVALID: Self = Self(usize::MAX);
2299
2300    pub fn to_constraint_id(self, range: SourceRange) -> Result<ezpz::Id, KclError> {
2301        self.0.try_into().map_err(|_| {
2302            KclError::new_type(KclErrorDetails::new(
2303                "Cannot convert to constraint ID since the sketch variable ID is too large".to_owned(),
2304                vec![range],
2305            ))
2306        })
2307    }
2308}
2309
2310#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2311#[ts(export_to = "Geometry.ts")]
2312#[serde(rename_all = "camelCase")]
2313pub struct SketchVar {
2314    pub id: SketchVarId,
2315    pub initial_value: f64,
2316    pub ty: NumericType,
2317    /// Used for solver feedback to source.
2318    pub node_path: Option<NodePath>,
2319    #[serde(skip)]
2320    pub meta: Vec<Metadata>,
2321}
2322
2323impl SketchVar {
2324    pub fn initial_value_to_solver_units(
2325        &self,
2326        exec_state: &mut ExecState,
2327        source_range: SourceRange,
2328        description: &str,
2329    ) -> Result<TyF64, KclError> {
2330        let x_initial_value = KclValue::Number {
2331            value: self.initial_value,
2332            ty: self.ty,
2333            meta: vec![source_range.into()],
2334        };
2335        let normalized_value =
2336            normalize_to_solver_distance_unit(&x_initial_value, source_range, exec_state, description)?;
2337        normalized_value.as_ty_f64().ok_or_else(|| {
2338            let message = format!(
2339                "Expected number after coercion, but found {}",
2340                normalized_value.human_friendly_type()
2341            );
2342            debug_assert!(false, "{}", &message);
2343            KclError::new_internal(KclErrorDetails::new(message, vec![source_range]))
2344        })
2345    }
2346}
2347
2348#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2349#[ts(export_to = "Geometry.ts")]
2350#[serde(tag = "type")]
2351pub enum UnsolvedExpr {
2352    Known(TyF64),
2353    Unknown(SketchVarId),
2354}
2355
2356impl UnsolvedExpr {
2357    pub fn var(&self) -> Option<SketchVarId> {
2358        match self {
2359            UnsolvedExpr::Known(_) => None,
2360            UnsolvedExpr::Unknown(id) => Some(*id),
2361        }
2362    }
2363}
2364
2365pub type UnsolvedPoint2dExpr = [UnsolvedExpr; 2];
2366
2367#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2368#[ts(export_to = "Geometry.ts")]
2369#[serde(rename_all = "camelCase")]
2370pub struct ConstrainablePoint2d {
2371    pub vars: kcl_api::Point2d<SketchVarId>,
2372    pub object_id: ObjectId,
2373}
2374
2375#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2376#[ts(export_to = "Geometry.ts")]
2377pub enum ConstrainablePoint2dOrOrigin {
2378    Point(ConstrainablePoint2d),
2379    Origin,
2380}
2381
2382#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2383#[ts(export_to = "Geometry.ts")]
2384#[serde(rename_all = "camelCase")]
2385pub struct ConstrainableLine2d {
2386    pub vars: [kcl_api::Point2d<SketchVarId>; 2],
2387    pub object_id: ObjectId,
2388}
2389
2390#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2391#[ts(export_to = "Geometry.ts")]
2392#[serde(rename_all = "camelCase")]
2393pub struct UnsolvedSegment {
2394    /// The engine ID.
2395    pub id: Uuid,
2396    pub object_id: ObjectId,
2397    pub kind: UnsolvedSegmentKind,
2398    #[serde(skip_serializing_if = "Option::is_none")]
2399    pub tag: Option<TagIdentifier>,
2400    #[serde(skip)]
2401    pub node_path: Option<NodePath>,
2402    #[serde(skip)]
2403    pub meta: Vec<Metadata>,
2404}
2405
2406#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2407#[ts(export_to = "Geometry.ts")]
2408#[serde(rename_all = "camelCase")]
2409pub enum UnsolvedSegmentKind {
2410    Point {
2411        position: UnsolvedPoint2dExpr,
2412        ctor: Box<PointCtor>,
2413    },
2414    Line {
2415        start: UnsolvedPoint2dExpr,
2416        end: UnsolvedPoint2dExpr,
2417        ctor: Box<LineCtor>,
2418        start_object_id: ObjectId,
2419        end_object_id: ObjectId,
2420        construction: bool,
2421    },
2422    Arc {
2423        start: UnsolvedPoint2dExpr,
2424        end: UnsolvedPoint2dExpr,
2425        center: UnsolvedPoint2dExpr,
2426        ctor: Box<ArcCtor>,
2427        start_object_id: ObjectId,
2428        end_object_id: ObjectId,
2429        center_object_id: ObjectId,
2430        /// The direction that the arc sweeps from its declared start to its
2431        /// declared end. The solver and engine only understand
2432        /// counterclockwise arcs, so code sending them the arc must use
2433        /// [`ArcDirection::ccw_order`] to resolve which points to treat as the
2434        /// sweep's start and end.
2435        #[serde(default, skip_serializing_if = "ArcDirection::is_ccw")]
2436        #[ts(as = "Option<ArcDirection>")]
2437        #[ts(optional)]
2438        direction: ArcDirection,
2439        construction: bool,
2440    },
2441    Circle {
2442        start: UnsolvedPoint2dExpr,
2443        center: UnsolvedPoint2dExpr,
2444        ctor: Box<CircleCtor>,
2445        start_object_id: ObjectId,
2446        center_object_id: ObjectId,
2447        construction: bool,
2448    },
2449    ControlPointSpline {
2450        controls: Vec<UnsolvedPoint2dExpr>,
2451        ctor: Box<ControlPointSplineCtor>,
2452        control_object_ids: Vec<ObjectId>,
2453        control_polygon_edge_object_ids: Vec<ObjectId>,
2454        degree: u32,
2455        construction: bool,
2456    },
2457}
2458
2459impl UnsolvedSegmentKind {
2460    /// What kind of object is this (point, line, arc, etc)
2461    /// Suitable for use in user-facing messages.
2462    pub fn human_friendly_kind_with_article(&self) -> &'static str {
2463        match self {
2464            Self::Point { .. } => "a Point",
2465            Self::Line { .. } => "a Line",
2466            Self::Arc { .. } => "an Arc",
2467            Self::Circle { .. } => "a Circle",
2468            Self::ControlPointSpline { .. } => "a Control Point Spline",
2469        }
2470    }
2471}
2472
2473#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2474#[ts(export_to = "Geometry.ts")]
2475#[serde(rename_all = "camelCase")]
2476pub struct Segment {
2477    /// The engine ID.
2478    pub id: Uuid,
2479    pub object_id: ObjectId,
2480    pub kind: SegmentKind,
2481    pub surface: SketchSurface,
2482    /// The engine ID of the sketch that this is a part of.
2483    pub sketch_id: Uuid,
2484    #[serde(skip)]
2485    #[ts(skip)]
2486    pub sketch: Option<Arc<Sketch>>,
2487    #[serde(skip_serializing_if = "Option::is_none")]
2488    pub tag: Option<TagIdentifier>,
2489    #[serde(skip)]
2490    pub node_path: Option<NodePath>,
2491    #[serde(skip)]
2492    pub meta: Vec<Metadata>,
2493}
2494
2495impl Segment {
2496    pub fn is_construction(&self) -> bool {
2497        match &self.kind {
2498            SegmentKind::Point { .. } => true,
2499            SegmentKind::Line { construction, .. } => *construction,
2500            SegmentKind::Arc { construction, .. } => *construction,
2501            SegmentKind::Circle { construction, .. } => *construction,
2502            SegmentKind::ControlPointSpline { construction, .. } => *construction,
2503        }
2504    }
2505}
2506
2507#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2508#[ts(export_to = "Geometry.ts")]
2509#[serde(rename_all = "camelCase")]
2510pub enum SegmentKind {
2511    Point {
2512        position: [TyF64; 2],
2513        ctor: Box<PointCtor>,
2514        #[serde(skip_serializing_if = "Option::is_none")]
2515        freedom: Option<Freedom>,
2516    },
2517    Line {
2518        start: [TyF64; 2],
2519        end: [TyF64; 2],
2520        ctor: Box<LineCtor>,
2521        start_object_id: ObjectId,
2522        end_object_id: ObjectId,
2523        #[serde(skip_serializing_if = "Option::is_none")]
2524        start_freedom: Option<Freedom>,
2525        #[serde(skip_serializing_if = "Option::is_none")]
2526        end_freedom: Option<Freedom>,
2527        construction: bool,
2528    },
2529    Arc {
2530        start: [TyF64; 2],
2531        end: [TyF64; 2],
2532        center: [TyF64; 2],
2533        ctor: Box<ArcCtor>,
2534        start_object_id: ObjectId,
2535        end_object_id: ObjectId,
2536        center_object_id: ObjectId,
2537        #[serde(skip_serializing_if = "Option::is_none")]
2538        start_freedom: Option<Freedom>,
2539        #[serde(skip_serializing_if = "Option::is_none")]
2540        end_freedom: Option<Freedom>,
2541        #[serde(skip_serializing_if = "Option::is_none")]
2542        center_freedom: Option<Freedom>,
2543        /// The direction that the arc sweeps from its declared start to its
2544        /// declared end.
2545        #[serde(default, skip_serializing_if = "ArcDirection::is_ccw")]
2546        #[ts(as = "Option<ArcDirection>")]
2547        #[ts(optional)]
2548        direction: ArcDirection,
2549        construction: bool,
2550    },
2551    Circle {
2552        start: [TyF64; 2],
2553        center: [TyF64; 2],
2554        ctor: Box<CircleCtor>,
2555        start_object_id: ObjectId,
2556        center_object_id: ObjectId,
2557        #[serde(skip_serializing_if = "Option::is_none")]
2558        start_freedom: Option<Freedom>,
2559        #[serde(skip_serializing_if = "Option::is_none")]
2560        center_freedom: Option<Freedom>,
2561        construction: bool,
2562    },
2563    ControlPointSpline {
2564        controls: Vec<[TyF64; 2]>,
2565        ctor: Box<ControlPointSplineCtor>,
2566        control_object_ids: Vec<ObjectId>,
2567        control_polygon_edge_object_ids: Vec<ObjectId>,
2568        #[serde(skip_serializing_if = "Vec::is_empty")]
2569        control_freedoms: Vec<Option<Freedom>>,
2570        degree: u32,
2571        construction: bool,
2572    },
2573}
2574
2575#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2576#[ts(export_to = "Geometry.ts")]
2577#[serde(rename_all = "camelCase")]
2578pub struct AbstractSegment {
2579    pub repr: SegmentRepr,
2580    #[serde(skip)]
2581    pub meta: Vec<Metadata>,
2582}
2583
2584#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2585pub enum SegmentRepr {
2586    Unsolved { segment: Box<UnsolvedSegment> },
2587    Solved { segment: Box<Segment> },
2588}
2589
2590#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2591#[ts(export_to = "Geometry.ts")]
2592#[serde(rename_all = "camelCase")]
2593pub struct SketchConstraint {
2594    pub kind: SketchConstraintKind,
2595    #[serde(skip)]
2596    pub meta: Vec<Metadata>,
2597}
2598
2599#[derive(Debug, Clone, Copy, PartialEq)]
2600pub enum AngleRayDirection {
2601    Forward,
2602    Reverse,
2603}
2604
2605#[derive(Debug, Clone, Copy, PartialEq)]
2606pub enum AngleSector {
2607    One,
2608    Two,
2609    Three,
2610    Four,
2611}
2612
2613#[derive(Debug, Clone, Copy, PartialEq)]
2614pub enum AngleConstraintMode {
2615    LinesAtAngle,
2616    PointsAtAngle { sector: AngleSector, inverse: bool },
2617}
2618
2619#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2620#[ts(export_to = "Geometry.ts")]
2621#[serde(rename_all = "camelCase")]
2622pub enum SketchConstraintKind {
2623    Angle {
2624        line0: ConstrainableLine2d,
2625        line1: ConstrainableLine2d,
2626        #[serde(skip)]
2627        #[ts(skip)]
2628        mode: AngleConstraintMode,
2629        #[serde(rename = "labelPosition")]
2630        #[serde(skip_serializing_if = "Option::is_none")]
2631        #[ts(rename = "labelPosition")]
2632        #[ts(optional)]
2633        label_position: Option<ApiPoint2d<Number>>,
2634    },
2635    Distance {
2636        points: [ConstrainablePoint2dOrOrigin; 2],
2637        #[serde(rename = "labelPosition")]
2638        #[serde(skip_serializing_if = "Option::is_none")]
2639        #[ts(rename = "labelPosition")]
2640        #[ts(optional)]
2641        label_position: Option<ApiPoint2d<Number>>,
2642    },
2643    PointLineDistance {
2644        point: ConstrainablePoint2dOrOrigin,
2645        line: ConstrainableLine2d,
2646        input_object_ids: [Option<ObjectId>; 2],
2647        #[serde(rename = "labelPosition")]
2648        #[serde(skip_serializing_if = "Option::is_none")]
2649        #[ts(rename = "labelPosition")]
2650        #[ts(optional)]
2651        label_position: Option<ApiPoint2d<Number>>,
2652    },
2653    LineLineDistance {
2654        line0: ConstrainableLine2d,
2655        line1: ConstrainableLine2d,
2656        input_object_ids: [ObjectId; 2],
2657        #[serde(rename = "labelPosition")]
2658        #[serde(skip_serializing_if = "Option::is_none")]
2659        #[ts(rename = "labelPosition")]
2660        #[ts(optional)]
2661        label_position: Option<ApiPoint2d<Number>>,
2662    },
2663    PointCircularDistance {
2664        point: ConstrainablePoint2dOrOrigin,
2665        center: ConstrainablePoint2d,
2666        start: ConstrainablePoint2d,
2667        end: Option<ConstrainablePoint2d>,
2668        input_object_ids: [Option<ObjectId>; 2],
2669        #[serde(rename = "labelPosition")]
2670        #[serde(skip_serializing_if = "Option::is_none")]
2671        #[ts(rename = "labelPosition")]
2672        #[ts(optional)]
2673        label_position: Option<ApiPoint2d<Number>>,
2674    },
2675    LineCircularDistance {
2676        line: ConstrainableLine2d,
2677        center: ConstrainablePoint2d,
2678        start: ConstrainablePoint2d,
2679        end: Option<ConstrainablePoint2d>,
2680        input_object_ids: [ObjectId; 2],
2681        #[serde(rename = "labelPosition")]
2682        #[serde(skip_serializing_if = "Option::is_none")]
2683        #[ts(rename = "labelPosition")]
2684        #[ts(optional)]
2685        label_position: Option<ApiPoint2d<Number>>,
2686    },
2687    CircularCircularDistance {
2688        center0: ConstrainablePoint2d,
2689        start0: ConstrainablePoint2d,
2690        end0: Option<ConstrainablePoint2d>,
2691        center1: ConstrainablePoint2d,
2692        start1: ConstrainablePoint2d,
2693        end1: Option<ConstrainablePoint2d>,
2694        input_object_ids: [ObjectId; 2],
2695        #[serde(rename = "labelPosition")]
2696        #[serde(skip_serializing_if = "Option::is_none")]
2697        #[ts(rename = "labelPosition")]
2698        #[ts(optional)]
2699        label_position: Option<ApiPoint2d<Number>>,
2700    },
2701    Radius {
2702        points: [ConstrainablePoint2d; 2],
2703        #[serde(rename = "labelPosition")]
2704        #[serde(skip_serializing_if = "Option::is_none")]
2705        #[ts(rename = "labelPosition")]
2706        #[ts(optional)]
2707        label_position: Option<ApiPoint2d<Number>>,
2708    },
2709    Diameter {
2710        points: [ConstrainablePoint2d; 2],
2711        #[serde(rename = "labelPosition")]
2712        #[serde(skip_serializing_if = "Option::is_none")]
2713        #[ts(rename = "labelPosition")]
2714        #[ts(optional)]
2715        label_position: Option<ApiPoint2d<Number>>,
2716    },
2717    HorizontalDistance {
2718        points: [ConstrainablePoint2dOrOrigin; 2],
2719        #[serde(rename = "labelPosition")]
2720        #[serde(skip_serializing_if = "Option::is_none")]
2721        #[ts(rename = "labelPosition")]
2722        #[ts(optional)]
2723        label_position: Option<ApiPoint2d<Number>>,
2724    },
2725    VerticalDistance {
2726        points: [ConstrainablePoint2dOrOrigin; 2],
2727        #[serde(rename = "labelPosition")]
2728        #[serde(skip_serializing_if = "Option::is_none")]
2729        #[ts(rename = "labelPosition")]
2730        #[ts(optional)]
2731        label_position: Option<ApiPoint2d<Number>>,
2732    },
2733}
2734
2735impl SketchConstraintKind {
2736    pub fn name(&self) -> &'static str {
2737        match self {
2738            SketchConstraintKind::Angle { .. } => "angle",
2739            SketchConstraintKind::Distance { .. } => "distance",
2740            SketchConstraintKind::PointLineDistance { .. } => "distance",
2741            SketchConstraintKind::LineLineDistance { .. } => "distance",
2742            SketchConstraintKind::PointCircularDistance { .. } => "distance",
2743            SketchConstraintKind::LineCircularDistance { .. } => "distance",
2744            SketchConstraintKind::CircularCircularDistance { .. } => "distance",
2745            SketchConstraintKind::Radius { .. } => "radius",
2746            SketchConstraintKind::Diameter { .. } => "diameter",
2747            SketchConstraintKind::HorizontalDistance { .. } => "horizontalDistance",
2748            SketchConstraintKind::VerticalDistance { .. } => "verticalDistance",
2749        }
2750    }
2751}