Skip to main content

kcl_lib/execution/
geometry.rs

1use std::{
2    f64::consts::TAU,
3    ops::{Add, AddAssign, Mul, Sub, SubAssign},
4};
5
6use anyhow::Result;
7use indexmap::IndexMap;
8use kcl_error::SourceRange;
9use kittycad_modeling_cmds::{
10    self as kcmc, ModelingCmd, each_cmd as mcmd, length_unit::LengthUnit, units::UnitLength, websocket::ModelingCmdReq,
11};
12use parse_display::{Display, FromStr};
13use serde::{Deserialize, Serialize};
14use uuid::Uuid;
15
16use crate::{
17    engine::{DEFAULT_PLANE_INFO, PlaneName},
18    errors::{KclError, KclErrorDetails},
19    exec::KclValue,
20    execution::{
21        ArtifactId, ExecState, ExecutorContext, Metadata, TagEngineInfo, TagIdentifier, normalize_to_solver_unit,
22        types::{NumericType, adjust_length},
23    },
24    front::{ArcCtor, Freedom, LineCtor, ObjectId, PointCtor},
25    parsing::ast::types::{Node, NodeRef, TagDeclarator, TagNode},
26    std::{
27        Args,
28        args::TyF64,
29        sketch::{FaceTag, PlaneData},
30    },
31};
32
33type Point3D = kcmc::shared::Point3d<f64>;
34
35/// A GD&T annotation.
36#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
37#[ts(export)]
38#[serde(tag = "type", rename_all = "camelCase")]
39pub struct GdtAnnotation {
40    /// The engine ID.
41    pub id: uuid::Uuid,
42    #[serde(skip)]
43    pub meta: Vec<Metadata>,
44}
45
46/// A geometry.
47#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
48#[ts(export)]
49#[serde(tag = "type")]
50#[allow(clippy::large_enum_variant)]
51pub enum Geometry {
52    Sketch(Sketch),
53    Solid(Solid),
54}
55
56impl Geometry {
57    pub fn id(&self) -> uuid::Uuid {
58        match self {
59            Geometry::Sketch(s) => s.id,
60            Geometry::Solid(e) => e.id,
61        }
62    }
63
64    /// If this geometry is the result of a pattern, then return the ID of
65    /// the original sketch which was patterned.
66    /// Equivalent to the `id()` method if this isn't a pattern.
67    pub fn original_id(&self) -> uuid::Uuid {
68        match self {
69            Geometry::Sketch(s) => s.original_id,
70            Geometry::Solid(e) => e.original_id(),
71        }
72    }
73}
74
75/// A geometry including an imported geometry.
76#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
77#[ts(export)]
78#[serde(tag = "type")]
79#[allow(clippy::large_enum_variant)]
80pub enum GeometryWithImportedGeometry {
81    Sketch(Sketch),
82    Solid(Solid),
83    ImportedGeometry(Box<ImportedGeometry>),
84}
85
86impl GeometryWithImportedGeometry {
87    pub async fn id(&mut self, ctx: &ExecutorContext) -> Result<uuid::Uuid, KclError> {
88        match self {
89            GeometryWithImportedGeometry::Sketch(s) => Ok(s.id),
90            GeometryWithImportedGeometry::Solid(e) => Ok(e.id),
91            GeometryWithImportedGeometry::ImportedGeometry(i) => {
92                let id = i.id(ctx).await?;
93                Ok(id)
94            }
95        }
96    }
97}
98
99/// A set of geometry.
100#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
101#[ts(export)]
102#[serde(tag = "type")]
103#[allow(clippy::vec_box)]
104pub enum Geometries {
105    Sketches(Vec<Sketch>),
106    Solids(Vec<Solid>),
107}
108
109impl From<Geometry> for Geometries {
110    fn from(value: Geometry) -> Self {
111        match value {
112            Geometry::Sketch(x) => Self::Sketches(vec![x]),
113            Geometry::Solid(x) => Self::Solids(vec![x]),
114        }
115    }
116}
117
118/// Data for an imported geometry.
119#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
120#[ts(export)]
121#[serde(rename_all = "camelCase")]
122pub struct ImportedGeometry {
123    /// The ID of the imported geometry.
124    pub id: uuid::Uuid,
125    /// The original file paths.
126    pub value: Vec<String>,
127    #[serde(skip)]
128    pub meta: Vec<Metadata>,
129    /// If the imported geometry has completed.
130    #[serde(skip)]
131    completed: bool,
132}
133
134impl ImportedGeometry {
135    pub fn new(id: uuid::Uuid, value: Vec<String>, meta: Vec<Metadata>) -> Self {
136        Self {
137            id,
138            value,
139            meta,
140            completed: false,
141        }
142    }
143
144    async fn wait_for_finish(&mut self, ctx: &ExecutorContext) -> Result<(), KclError> {
145        if self.completed {
146            return Ok(());
147        }
148
149        ctx.engine
150            .ensure_async_command_completed(self.id, self.meta.first().map(|m| m.source_range))
151            .await?;
152
153        self.completed = true;
154
155        Ok(())
156    }
157
158    pub async fn id(&mut self, ctx: &ExecutorContext) -> Result<uuid::Uuid, KclError> {
159        if !self.completed {
160            self.wait_for_finish(ctx).await?;
161        }
162
163        Ok(self.id)
164    }
165}
166
167/// Data for a solid, sketch, or an imported geometry.
168#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
169#[ts(export)]
170#[serde(tag = "type", rename_all = "camelCase")]
171#[allow(clippy::vec_box)]
172pub enum HideableGeometry {
173    ImportedGeometry(Box<ImportedGeometry>),
174    SolidSet(Vec<Solid>),
175    HelixSet(Vec<Helix>),
176    // TODO: Sketches should be groups of profiles that relate to a plane,
177    // Not the plane itself. Until then, sketches nor profiles ("Sketches" in Rust)
178    // are not hideable.
179    // SketchSet(Vec<Sketch>),
180}
181
182impl From<HideableGeometry> for crate::execution::KclValue {
183    fn from(value: HideableGeometry) -> Self {
184        match value {
185            HideableGeometry::ImportedGeometry(s) => crate::execution::KclValue::ImportedGeometry(*s),
186            HideableGeometry::SolidSet(mut s) => {
187                if s.len() == 1
188                    && let Some(s) = s.pop()
189                {
190                    crate::execution::KclValue::Solid { value: Box::new(s) }
191                } else {
192                    crate::execution::KclValue::HomArray {
193                        value: s
194                            .into_iter()
195                            .map(|s| crate::execution::KclValue::Solid { value: Box::new(s) })
196                            .collect(),
197                        ty: crate::execution::types::RuntimeType::solid(),
198                    }
199                }
200            }
201            HideableGeometry::HelixSet(mut s) => {
202                if s.len() == 1
203                    && let Some(s) = s.pop()
204                {
205                    crate::execution::KclValue::Helix { value: Box::new(s) }
206                } else {
207                    crate::execution::KclValue::HomArray {
208                        value: s
209                            .into_iter()
210                            .map(|s| crate::execution::KclValue::Helix { value: Box::new(s) })
211                            .collect(),
212                        ty: crate::execution::types::RuntimeType::helices(),
213                    }
214                }
215            }
216        }
217    }
218}
219
220impl HideableGeometry {
221    pub(crate) async fn ids(&mut self, ctx: &ExecutorContext) -> Result<Vec<uuid::Uuid>, KclError> {
222        match self {
223            HideableGeometry::ImportedGeometry(s) => {
224                let id = s.id(ctx).await?;
225
226                Ok(vec![id])
227            }
228            HideableGeometry::SolidSet(s) => Ok(s.iter().map(|s| s.id).collect()),
229            HideableGeometry::HelixSet(s) => Ok(s.iter().map(|s| s.value).collect()),
230        }
231    }
232}
233
234/// Data for a solid, sketch, or an imported geometry.
235#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
236#[ts(export)]
237#[serde(tag = "type", rename_all = "camelCase")]
238#[allow(clippy::vec_box)]
239pub enum SolidOrSketchOrImportedGeometry {
240    ImportedGeometry(Box<ImportedGeometry>),
241    SolidSet(Vec<Solid>),
242    SketchSet(Vec<Sketch>),
243}
244
245impl From<SolidOrSketchOrImportedGeometry> for crate::execution::KclValue {
246    fn from(value: SolidOrSketchOrImportedGeometry) -> Self {
247        match value {
248            SolidOrSketchOrImportedGeometry::ImportedGeometry(s) => crate::execution::KclValue::ImportedGeometry(*s),
249            SolidOrSketchOrImportedGeometry::SolidSet(mut s) => {
250                if s.len() == 1
251                    && let Some(s) = s.pop()
252                {
253                    crate::execution::KclValue::Solid { value: Box::new(s) }
254                } else {
255                    crate::execution::KclValue::HomArray {
256                        value: s
257                            .into_iter()
258                            .map(|s| crate::execution::KclValue::Solid { value: Box::new(s) })
259                            .collect(),
260                        ty: crate::execution::types::RuntimeType::solid(),
261                    }
262                }
263            }
264            SolidOrSketchOrImportedGeometry::SketchSet(mut s) => {
265                if s.len() == 1
266                    && let Some(s) = s.pop()
267                {
268                    crate::execution::KclValue::Sketch { value: Box::new(s) }
269                } else {
270                    crate::execution::KclValue::HomArray {
271                        value: s
272                            .into_iter()
273                            .map(|s| crate::execution::KclValue::Sketch { value: Box::new(s) })
274                            .collect(),
275                        ty: crate::execution::types::RuntimeType::sketch(),
276                    }
277                }
278            }
279        }
280    }
281}
282
283impl SolidOrSketchOrImportedGeometry {
284    pub(crate) async fn ids(&mut self, ctx: &ExecutorContext) -> Result<Vec<uuid::Uuid>, KclError> {
285        match self {
286            SolidOrSketchOrImportedGeometry::ImportedGeometry(s) => {
287                let id = s.id(ctx).await?;
288
289                Ok(vec![id])
290            }
291            SolidOrSketchOrImportedGeometry::SolidSet(s) => Ok(s.iter().map(|s| s.id).collect()),
292            SolidOrSketchOrImportedGeometry::SketchSet(s) => Ok(s.iter().map(|s| s.id).collect()),
293        }
294    }
295}
296
297/// Data for a solid or an imported geometry.
298#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
299#[ts(export)]
300#[serde(tag = "type", rename_all = "camelCase")]
301#[allow(clippy::vec_box)]
302pub enum SolidOrImportedGeometry {
303    ImportedGeometry(Box<ImportedGeometry>),
304    SolidSet(Vec<Solid>),
305}
306
307impl From<SolidOrImportedGeometry> for crate::execution::KclValue {
308    fn from(value: SolidOrImportedGeometry) -> Self {
309        match value {
310            SolidOrImportedGeometry::ImportedGeometry(s) => crate::execution::KclValue::ImportedGeometry(*s),
311            SolidOrImportedGeometry::SolidSet(mut s) => {
312                if s.len() == 1
313                    && let Some(s) = s.pop()
314                {
315                    crate::execution::KclValue::Solid { value: Box::new(s) }
316                } else {
317                    crate::execution::KclValue::HomArray {
318                        value: s
319                            .into_iter()
320                            .map(|s| crate::execution::KclValue::Solid { value: Box::new(s) })
321                            .collect(),
322                        ty: crate::execution::types::RuntimeType::solid(),
323                    }
324                }
325            }
326        }
327    }
328}
329
330impl SolidOrImportedGeometry {
331    pub(crate) async fn ids(&mut self, ctx: &ExecutorContext) -> Result<Vec<uuid::Uuid>, KclError> {
332        match self {
333            SolidOrImportedGeometry::ImportedGeometry(s) => {
334                let id = s.id(ctx).await?;
335
336                Ok(vec![id])
337            }
338            SolidOrImportedGeometry::SolidSet(s) => Ok(s.iter().map(|s| s.id).collect()),
339        }
340    }
341}
342
343/// A helix.
344#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
345#[ts(export)]
346#[serde(rename_all = "camelCase")]
347pub struct Helix {
348    /// The id of the helix.
349    pub value: uuid::Uuid,
350    /// The artifact ID.
351    pub artifact_id: ArtifactId,
352    /// Number of revolutions.
353    pub revolutions: f64,
354    /// Start angle (in degrees).
355    pub angle_start: f64,
356    /// Is the helix rotation counter clockwise?
357    pub ccw: bool,
358    /// The cylinder the helix was created on.
359    pub cylinder_id: Option<uuid::Uuid>,
360    pub units: UnitLength,
361    #[serde(skip)]
362    pub meta: Vec<Metadata>,
363}
364
365#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
366#[ts(export)]
367#[serde(rename_all = "camelCase")]
368pub struct Plane {
369    /// The id of the plane.
370    pub id: uuid::Uuid,
371    /// The artifact ID.
372    pub artifact_id: ArtifactId,
373    /// The scene object ID. If this is None, then the plane has not been
374    /// sent to the engine yet. It must be sent before it is used.
375    #[serde(skip_serializing_if = "Option::is_none")]
376    pub object_id: Option<ObjectId>,
377    /// The kind of plane or custom.
378    pub kind: PlaneKind,
379    /// The information for the plane.
380    #[serde(flatten)]
381    pub info: PlaneInfo,
382    #[serde(skip)]
383    pub meta: Vec<Metadata>,
384}
385
386#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, ts_rs::TS)]
387#[ts(export)]
388#[serde(rename_all = "camelCase")]
389pub struct PlaneInfo {
390    /// Origin of the plane.
391    pub origin: Point3d,
392    /// What should the plane's X axis be?
393    pub x_axis: Point3d,
394    /// What should the plane's Y axis be?
395    pub y_axis: Point3d,
396    /// What should the plane's Z axis be?
397    pub z_axis: Point3d,
398}
399
400impl PlaneInfo {
401    pub(crate) fn into_plane_data(self) -> PlaneData {
402        if self.origin.is_zero() {
403            match self {
404                Self {
405                    origin:
406                        Point3d {
407                            x: 0.0,
408                            y: 0.0,
409                            z: 0.0,
410                            units: Some(UnitLength::Millimeters),
411                        },
412                    x_axis:
413                        Point3d {
414                            x: 1.0,
415                            y: 0.0,
416                            z: 0.0,
417                            units: _,
418                        },
419                    y_axis:
420                        Point3d {
421                            x: 0.0,
422                            y: 1.0,
423                            z: 0.0,
424                            units: _,
425                        },
426                    z_axis: _,
427                } => return PlaneData::XY,
428                Self {
429                    origin:
430                        Point3d {
431                            x: 0.0,
432                            y: 0.0,
433                            z: 0.0,
434                            units: Some(UnitLength::Millimeters),
435                        },
436                    x_axis:
437                        Point3d {
438                            x: -1.0,
439                            y: 0.0,
440                            z: 0.0,
441                            units: _,
442                        },
443                    y_axis:
444                        Point3d {
445                            x: 0.0,
446                            y: 1.0,
447                            z: 0.0,
448                            units: _,
449                        },
450                    z_axis: _,
451                } => return PlaneData::NegXY,
452                Self {
453                    origin:
454                        Point3d {
455                            x: 0.0,
456                            y: 0.0,
457                            z: 0.0,
458                            units: Some(UnitLength::Millimeters),
459                        },
460                    x_axis:
461                        Point3d {
462                            x: 1.0,
463                            y: 0.0,
464                            z: 0.0,
465                            units: _,
466                        },
467                    y_axis:
468                        Point3d {
469                            x: 0.0,
470                            y: 0.0,
471                            z: 1.0,
472                            units: _,
473                        },
474                    z_axis: _,
475                } => return PlaneData::XZ,
476                Self {
477                    origin:
478                        Point3d {
479                            x: 0.0,
480                            y: 0.0,
481                            z: 0.0,
482                            units: Some(UnitLength::Millimeters),
483                        },
484                    x_axis:
485                        Point3d {
486                            x: -1.0,
487                            y: 0.0,
488                            z: 0.0,
489                            units: _,
490                        },
491                    y_axis:
492                        Point3d {
493                            x: 0.0,
494                            y: 0.0,
495                            z: 1.0,
496                            units: _,
497                        },
498                    z_axis: _,
499                } => return PlaneData::NegXZ,
500                Self {
501                    origin:
502                        Point3d {
503                            x: 0.0,
504                            y: 0.0,
505                            z: 0.0,
506                            units: Some(UnitLength::Millimeters),
507                        },
508                    x_axis:
509                        Point3d {
510                            x: 0.0,
511                            y: 1.0,
512                            z: 0.0,
513                            units: _,
514                        },
515                    y_axis:
516                        Point3d {
517                            x: 0.0,
518                            y: 0.0,
519                            z: 1.0,
520                            units: _,
521                        },
522                    z_axis: _,
523                } => return PlaneData::YZ,
524                Self {
525                    origin:
526                        Point3d {
527                            x: 0.0,
528                            y: 0.0,
529                            z: 0.0,
530                            units: Some(UnitLength::Millimeters),
531                        },
532                    x_axis:
533                        Point3d {
534                            x: 0.0,
535                            y: -1.0,
536                            z: 0.0,
537                            units: _,
538                        },
539                    y_axis:
540                        Point3d {
541                            x: 0.0,
542                            y: 0.0,
543                            z: 1.0,
544                            units: _,
545                        },
546                    z_axis: _,
547                } => return PlaneData::NegYZ,
548                _ => {}
549            }
550        }
551
552        PlaneData::Plane(Self {
553            origin: self.origin,
554            x_axis: self.x_axis,
555            y_axis: self.y_axis,
556            z_axis: self.z_axis,
557        })
558    }
559
560    pub(crate) fn is_right_handed(&self) -> bool {
561        // Katie's formula:
562        // dot(cross(x, y), z) ~= sqrt(dot(x, x) * dot(y, y) * dot(z, z))
563        let lhs = self
564            .x_axis
565            .axes_cross_product(&self.y_axis)
566            .axes_dot_product(&self.z_axis);
567        let rhs_x = self.x_axis.axes_dot_product(&self.x_axis);
568        let rhs_y = self.y_axis.axes_dot_product(&self.y_axis);
569        let rhs_z = self.z_axis.axes_dot_product(&self.z_axis);
570        let rhs = (rhs_x * rhs_y * rhs_z).sqrt();
571        // Check LHS ~= RHS
572        (lhs - rhs).abs() <= 0.0001
573    }
574
575    #[cfg(test)]
576    pub(crate) fn is_left_handed(&self) -> bool {
577        !self.is_right_handed()
578    }
579
580    pub(crate) fn make_right_handed(self) -> Self {
581        if self.is_right_handed() {
582            return self;
583        }
584        // To make it right-handed, negate X, i.e. rotate the plane 180 degrees.
585        Self {
586            origin: self.origin,
587            x_axis: self.x_axis.negated(),
588            y_axis: self.y_axis,
589            z_axis: self.z_axis,
590        }
591    }
592}
593
594impl TryFrom<PlaneData> for PlaneInfo {
595    type Error = KclError;
596
597    fn try_from(value: PlaneData) -> Result<Self, Self::Error> {
598        let name = match value {
599            PlaneData::XY => PlaneName::Xy,
600            PlaneData::NegXY => PlaneName::NegXy,
601            PlaneData::XZ => PlaneName::Xz,
602            PlaneData::NegXZ => PlaneName::NegXz,
603            PlaneData::YZ => PlaneName::Yz,
604            PlaneData::NegYZ => PlaneName::NegYz,
605            PlaneData::Plane(info) => {
606                return Ok(info);
607            }
608        };
609
610        let info = DEFAULT_PLANE_INFO.get(&name).ok_or_else(|| {
611            KclError::new_internal(KclErrorDetails::new(
612                format!("Plane {name} not found"),
613                Default::default(),
614            ))
615        })?;
616
617        Ok(info.clone())
618    }
619}
620
621impl From<&PlaneData> for PlaneKind {
622    fn from(value: &PlaneData) -> Self {
623        match value {
624            PlaneData::XY => PlaneKind::XY,
625            PlaneData::NegXY => PlaneKind::XY,
626            PlaneData::XZ => PlaneKind::XZ,
627            PlaneData::NegXZ => PlaneKind::XZ,
628            PlaneData::YZ => PlaneKind::YZ,
629            PlaneData::NegYZ => PlaneKind::YZ,
630            PlaneData::Plane(_) => PlaneKind::Custom,
631        }
632    }
633}
634
635impl From<&PlaneInfo> for PlaneKind {
636    fn from(value: &PlaneInfo) -> Self {
637        let data = PlaneData::Plane(value.clone());
638        PlaneKind::from(&data)
639    }
640}
641
642impl From<PlaneInfo> for PlaneKind {
643    fn from(value: PlaneInfo) -> Self {
644        let data = PlaneData::Plane(value);
645        PlaneKind::from(&data)
646    }
647}
648
649impl Plane {
650    #[cfg(test)]
651    pub(crate) fn from_plane_data_skipping_engine(
652        value: PlaneData,
653        exec_state: &mut ExecState,
654    ) -> Result<Self, KclError> {
655        let id = exec_state.next_uuid();
656        let kind = PlaneKind::from(&value);
657        Ok(Plane {
658            id,
659            artifact_id: id.into(),
660            info: PlaneInfo::try_from(value)?,
661            object_id: None,
662            kind,
663            meta: vec![],
664        })
665    }
666
667    /// Returns true if the plane has been sent to the engine.
668    pub fn is_initialized(&self) -> bool {
669        self.object_id.is_some()
670    }
671
672    /// Returns true if the plane has not been sent to the engine yet.
673    pub fn is_uninitialized(&self) -> bool {
674        !self.is_initialized()
675    }
676
677    /// The standard planes are XY, YZ and XZ (in both positive and negative)
678    pub fn is_standard(&self) -> bool {
679        match &self.kind {
680            PlaneKind::XY | PlaneKind::YZ | PlaneKind::XZ => true,
681            PlaneKind::Custom => false,
682        }
683    }
684
685    /// Project a point onto a plane by calculating how far away it is and moving it along the
686    /// normal of the plane so that it now lies on the plane.
687    pub fn project(&self, point: Point3d) -> Point3d {
688        let v = point - self.info.origin;
689        let dot = v.axes_dot_product(&self.info.z_axis);
690
691        point - self.info.z_axis * dot
692    }
693}
694
695/// A face.
696#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
697#[ts(export)]
698#[serde(rename_all = "camelCase")]
699pub struct Face {
700    /// The id of the face.
701    pub id: uuid::Uuid,
702    /// The artifact ID.
703    pub artifact_id: ArtifactId,
704    /// The scene object ID.
705    pub object_id: ObjectId,
706    /// The tag of the face.
707    pub value: String,
708    /// What should the face's X axis be?
709    pub x_axis: Point3d,
710    /// What should the face's Y axis be?
711    pub y_axis: Point3d,
712    /// The solid the face is on.
713    pub solid: Box<Solid>,
714    pub units: UnitLength,
715    #[serde(skip)]
716    pub meta: Vec<Metadata>,
717}
718
719/// A bounded edge.
720#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
721#[ts(export)]
722#[serde(rename_all = "camelCase")]
723pub struct BoundedEdge {
724    /// The id of the face this edge belongs to.
725    pub face_id: uuid::Uuid,
726    /// The id of the edge.
727    pub edge_id: uuid::Uuid,
728    /// A percentage bound of the edge, used to restrict what portion of the edge will be used.
729    /// Range (0, 1)
730    pub lower_bound: f32,
731    /// A percentage bound of the edge, used to restrict what portion of the edge will be used.
732    /// Range (0, 1)
733    pub upper_bound: f32,
734}
735
736/// Kind of plane.
737#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq, ts_rs::TS, FromStr, Display)]
738#[ts(export)]
739#[display(style = "camelCase")]
740pub enum PlaneKind {
741    #[serde(rename = "XY", alias = "xy")]
742    #[display("XY")]
743    XY,
744    #[serde(rename = "XZ", alias = "xz")]
745    #[display("XZ")]
746    XZ,
747    #[serde(rename = "YZ", alias = "yz")]
748    #[display("YZ")]
749    YZ,
750    /// A custom plane.
751    #[display("Custom")]
752    Custom,
753}
754
755#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
756#[ts(export)]
757#[serde(tag = "type", rename_all = "camelCase")]
758pub struct Sketch {
759    /// The id of the sketch (this will change when the engine's reference to it changes).
760    pub id: uuid::Uuid,
761    /// The paths in the sketch.
762    /// Only paths on the "outside" i.e. the perimeter.
763    /// Does not include paths "inside" the profile (for example, edges made by subtracting a profile)
764    pub paths: Vec<Path>,
765    /// Inner paths, resulting from subtract2d to carve profiles out of the sketch.
766    #[serde(default, skip_serializing_if = "Vec::is_empty")]
767    pub inner_paths: Vec<Path>,
768    /// What the sketch is on (can be a plane or a face).
769    pub on: SketchSurface,
770    /// The starting path.
771    pub start: BasePath,
772    /// Tag identifiers that have been declared in this sketch.
773    #[serde(default, skip_serializing_if = "IndexMap::is_empty")]
774    pub tags: IndexMap<String, TagIdentifier>,
775    /// The original id of the sketch. This stays the same even if the sketch is
776    /// is sketched on face etc.
777    pub artifact_id: ArtifactId,
778    #[ts(skip)]
779    pub original_id: uuid::Uuid,
780    /// If the sketch includes a mirror.
781    #[serde(skip)]
782    pub mirror: Option<uuid::Uuid>,
783    /// If the sketch is a clone of another sketch.
784    #[serde(skip)]
785    pub clone: Option<uuid::Uuid>,
786    pub units: UnitLength,
787    /// Metadata.
788    #[serde(skip)]
789    pub meta: Vec<Metadata>,
790    /// Has the profile been closed?
791    /// If not given, defaults to yes, closed explicitly.
792    #[serde(
793        default = "ProfileClosed::explicitly",
794        skip_serializing_if = "ProfileClosed::is_explicitly"
795    )]
796    pub is_closed: ProfileClosed,
797}
798
799impl ProfileClosed {
800    #[expect(dead_code, reason = "it's not actually dead, it's called by serde")]
801    fn explicitly() -> Self {
802        Self::Explicitly
803    }
804
805    fn is_explicitly(&self) -> bool {
806        matches!(self, ProfileClosed::Explicitly)
807    }
808}
809
810/// Has the profile been closed?
811#[derive(Debug, Serialize, Eq, PartialEq, Clone, Copy, Hash, Ord, PartialOrd, ts_rs::TS)]
812#[serde(rename_all = "camelCase")]
813pub enum ProfileClosed {
814    /// It's definitely open.
815    No,
816    /// Unknown.
817    Maybe,
818    /// Yes, by adding a segment which loops back to the start.
819    Implicitly,
820    /// Yes, by calling `close()` or by making a closed shape (e.g. circle).
821    Explicitly,
822}
823
824impl Sketch {
825    // Tell the engine to enter sketch mode on the sketch.
826    // Run a specific command, then exit sketch mode.
827    pub(crate) fn build_sketch_mode_cmds(
828        &self,
829        exec_state: &mut ExecState,
830        inner_cmd: ModelingCmdReq,
831    ) -> Vec<ModelingCmdReq> {
832        vec![
833            // Before we extrude, we need to enable the sketch mode.
834            // We do this here in case extrude is called out of order.
835            ModelingCmdReq {
836                cmd: ModelingCmd::from(
837                    mcmd::EnableSketchMode::builder()
838                        .animated(false)
839                        .ortho(false)
840                        .entity_id(self.on.id())
841                        .adjust_camera(false)
842                        .maybe_planar_normal(if let SketchSurface::Plane(plane) = &self.on {
843                            // We pass in the normal for the plane here.
844                            let normal = plane.info.x_axis.axes_cross_product(&plane.info.y_axis);
845                            Some(normal.into())
846                        } else {
847                            None
848                        })
849                        .build(),
850                ),
851                cmd_id: exec_state.next_uuid().into(),
852            },
853            inner_cmd,
854            ModelingCmdReq {
855                cmd: ModelingCmd::SketchModeDisable(mcmd::SketchModeDisable::builder().build()),
856                cmd_id: exec_state.next_uuid().into(),
857            },
858        ]
859    }
860}
861
862/// A sketch type.
863#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
864#[ts(export)]
865#[serde(tag = "type", rename_all = "camelCase")]
866pub enum SketchSurface {
867    Plane(Box<Plane>),
868    Face(Box<Face>),
869}
870
871impl SketchSurface {
872    pub(crate) fn id(&self) -> uuid::Uuid {
873        match self {
874            SketchSurface::Plane(plane) => plane.id,
875            SketchSurface::Face(face) => face.id,
876        }
877    }
878    pub(crate) fn x_axis(&self) -> Point3d {
879        match self {
880            SketchSurface::Plane(plane) => plane.info.x_axis,
881            SketchSurface::Face(face) => face.x_axis,
882        }
883    }
884    pub(crate) fn y_axis(&self) -> Point3d {
885        match self {
886            SketchSurface::Plane(plane) => plane.info.y_axis,
887            SketchSurface::Face(face) => face.y_axis,
888        }
889    }
890
891    pub(crate) fn object_id(&self) -> Option<ObjectId> {
892        match self {
893            SketchSurface::Plane(plane) => plane.object_id,
894            SketchSurface::Face(face) => Some(face.object_id),
895        }
896    }
897
898    pub(crate) fn set_object_id(&mut self, object_id: ObjectId) {
899        match self {
900            SketchSurface::Plane(plane) => plane.object_id = Some(object_id),
901            SketchSurface::Face(face) => face.object_id = object_id,
902        }
903    }
904}
905
906/// A Sketch, Face, or TaggedFace.
907#[derive(Debug, Clone, PartialEq)]
908pub enum Extrudable {
909    /// Sketch.
910    Sketch(Box<Sketch>),
911    /// Face.
912    Face(FaceTag),
913}
914
915impl Extrudable {
916    /// Get the relevant id.
917    pub async fn id_to_extrude(
918        &self,
919        exec_state: &mut ExecState,
920        args: &Args,
921        must_be_planar: bool,
922    ) -> Result<uuid::Uuid, KclError> {
923        match self {
924            Extrudable::Sketch(sketch) => Ok(sketch.id),
925            Extrudable::Face(face_tag) => face_tag.get_face_id_from_tag(exec_state, args, must_be_planar).await,
926        }
927    }
928
929    pub fn as_sketch(&self) -> Option<Sketch> {
930        match self {
931            Extrudable::Sketch(sketch) => Some((**sketch).clone()),
932            Extrudable::Face(face_tag) => match face_tag.geometry() {
933                Some(Geometry::Sketch(sketch)) => Some(sketch),
934                Some(Geometry::Solid(solid)) => solid.sketch().cloned(),
935                None => None,
936            },
937        }
938    }
939
940    pub fn is_closed(&self) -> ProfileClosed {
941        match self {
942            Extrudable::Sketch(sketch) => sketch.is_closed,
943            Extrudable::Face(face_tag) => match face_tag.geometry() {
944                Some(Geometry::Sketch(sketch)) => sketch.is_closed,
945                Some(Geometry::Solid(solid)) => solid
946                    .sketch()
947                    .map(|sketch| sketch.is_closed)
948                    .unwrap_or(ProfileClosed::Maybe),
949                _ => ProfileClosed::Maybe,
950            },
951        }
952    }
953}
954
955impl From<Sketch> for Extrudable {
956    fn from(value: Sketch) -> Self {
957        Extrudable::Sketch(Box::new(value))
958    }
959}
960
961#[derive(Debug, Clone)]
962pub(crate) enum GetTangentialInfoFromPathsResult {
963    PreviousPoint([f64; 2]),
964    Arc {
965        center: [f64; 2],
966        ccw: bool,
967    },
968    Circle {
969        center: [f64; 2],
970        ccw: bool,
971        radius: f64,
972    },
973    Ellipse {
974        center: [f64; 2],
975        ccw: bool,
976        major_axis: [f64; 2],
977        _minor_radius: f64,
978    },
979}
980
981impl GetTangentialInfoFromPathsResult {
982    pub(crate) fn tan_previous_point(&self, last_arc_end: [f64; 2]) -> [f64; 2] {
983        match self {
984            GetTangentialInfoFromPathsResult::PreviousPoint(p) => *p,
985            GetTangentialInfoFromPathsResult::Arc { center, ccw } => {
986                crate::std::utils::get_tangent_point_from_previous_arc(*center, *ccw, last_arc_end)
987            }
988            // The circle always starts at 0 degrees, so a suitable tangent
989            // point is either directly above or below.
990            GetTangentialInfoFromPathsResult::Circle {
991                center, radius, ccw, ..
992            } => [center[0] + radius, center[1] + if *ccw { -1.0 } else { 1.0 }],
993            GetTangentialInfoFromPathsResult::Ellipse {
994                center,
995                major_axis,
996                ccw,
997                ..
998            } => [center[0] + major_axis[0], center[1] + if *ccw { -1.0 } else { 1.0 }],
999        }
1000    }
1001}
1002
1003impl Sketch {
1004    pub(crate) fn add_tag(
1005        &mut self,
1006        tag: NodeRef<'_, TagDeclarator>,
1007        current_path: &Path,
1008        exec_state: &ExecState,
1009        surface: Option<&ExtrudeSurface>,
1010    ) {
1011        let mut tag_identifier: TagIdentifier = tag.into();
1012        let base = current_path.get_base();
1013        let mut sketch_copy = self.clone();
1014        sketch_copy.tags.clear();
1015        tag_identifier.info.push((
1016            exec_state.stack().current_epoch(),
1017            TagEngineInfo {
1018                id: base.geo_meta.id,
1019                geometry: Geometry::Sketch(sketch_copy),
1020                path: Some(current_path.clone()),
1021                surface: surface.cloned(),
1022            },
1023        ));
1024
1025        self.tags.insert(tag.name.to_string(), tag_identifier);
1026    }
1027
1028    pub(crate) fn merge_tags<'a>(&mut self, tags: impl Iterator<Item = &'a TagIdentifier>) {
1029        for t in tags {
1030            match self.tags.get_mut(&t.value) {
1031                Some(id) => {
1032                    id.merge_info(t);
1033                }
1034                None => {
1035                    self.tags.insert(t.value.clone(), t.clone());
1036                }
1037            }
1038        }
1039    }
1040
1041    /// Get the path most recently sketched.
1042    pub(crate) fn latest_path(&self) -> Option<&Path> {
1043        self.paths.last()
1044    }
1045
1046    /// The "pen" is an imaginary pen drawing the path.
1047    /// This gets the current point the pen is hovering over, i.e. the point
1048    /// where the last path segment ends, and the next path segment will begin.
1049    pub(crate) fn current_pen_position(&self) -> Result<Point2d, KclError> {
1050        let Some(path) = self.latest_path() else {
1051            return Ok(Point2d::new(self.start.to[0], self.start.to[1], self.start.units));
1052        };
1053
1054        let to = path.get_base().to;
1055        Ok(Point2d::new(to[0], to[1], path.get_base().units))
1056    }
1057
1058    pub(crate) fn get_tangential_info_from_paths(&self) -> GetTangentialInfoFromPathsResult {
1059        let Some(path) = self.latest_path() else {
1060            return GetTangentialInfoFromPathsResult::PreviousPoint(self.start.to);
1061        };
1062        path.get_tangential_info()
1063    }
1064}
1065
1066#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1067#[ts(export)]
1068#[serde(tag = "type", rename_all = "camelCase")]
1069pub struct Solid {
1070    /// The id of the solid.
1071    pub id: uuid::Uuid,
1072    /// The artifact ID of the solid.  Unlike `id`, this doesn't change.
1073    pub artifact_id: ArtifactId,
1074    /// The extrude surfaces.
1075    pub value: Vec<ExtrudeSurface>,
1076    /// How this solid was created.
1077    #[serde(rename = "sketch")]
1078    pub creator: SolidCreator,
1079    /// The id of the extrusion start cap
1080    pub start_cap_id: Option<uuid::Uuid>,
1081    /// The id of the extrusion end cap
1082    pub end_cap_id: Option<uuid::Uuid>,
1083    /// Chamfers or fillets on this solid.
1084    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1085    pub edge_cuts: Vec<EdgeCut>,
1086    /// The units of the solid.
1087    pub units: UnitLength,
1088    /// Is this a sectional solid?
1089    pub sectional: bool,
1090    /// Metadata.
1091    #[serde(skip)]
1092    pub meta: Vec<Metadata>,
1093}
1094
1095#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1096#[ts(export)]
1097pub struct CreatorFace {
1098    /// The face id that served as the base.
1099    pub face_id: uuid::Uuid,
1100    /// The solid id that owned the face.
1101    pub solid_id: uuid::Uuid,
1102    /// The sketch used for the operation.
1103    pub sketch: Sketch,
1104}
1105
1106/// How a solid was created.
1107#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1108#[ts(export)]
1109#[serde(tag = "creatorType", rename_all = "camelCase")]
1110pub enum SolidCreator {
1111    /// Created from a sketch.
1112    Sketch(Sketch),
1113    /// Created by extruding or modifying a face.
1114    Face(CreatorFace),
1115    /// Created procedurally without a sketch.
1116    Procedural,
1117}
1118
1119impl Solid {
1120    pub fn sketch(&self) -> Option<&Sketch> {
1121        match &self.creator {
1122            SolidCreator::Sketch(sketch) => Some(sketch),
1123            SolidCreator::Face(CreatorFace { sketch, .. }) => Some(sketch),
1124            SolidCreator::Procedural => None,
1125        }
1126    }
1127
1128    pub fn sketch_mut(&mut self) -> Option<&mut Sketch> {
1129        match &mut self.creator {
1130            SolidCreator::Sketch(sketch) => Some(sketch),
1131            SolidCreator::Face(CreatorFace { sketch, .. }) => Some(sketch),
1132            SolidCreator::Procedural => None,
1133        }
1134    }
1135
1136    pub fn sketch_id(&self) -> Option<uuid::Uuid> {
1137        self.sketch().map(|sketch| sketch.id)
1138    }
1139
1140    pub fn original_id(&self) -> uuid::Uuid {
1141        self.sketch().map(|sketch| sketch.original_id).unwrap_or(self.id)
1142    }
1143
1144    pub(crate) fn get_all_edge_cut_ids(&self) -> impl Iterator<Item = uuid::Uuid> + '_ {
1145        self.edge_cuts.iter().map(|foc| foc.id())
1146    }
1147}
1148
1149/// A fillet or a chamfer.
1150#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1151#[ts(export)]
1152#[serde(tag = "type", rename_all = "camelCase")]
1153pub enum EdgeCut {
1154    /// A fillet.
1155    Fillet {
1156        /// The id of the engine command that called this fillet.
1157        id: uuid::Uuid,
1158        radius: TyF64,
1159        /// The engine id of the edge to fillet.
1160        #[serde(rename = "edgeId")]
1161        edge_id: uuid::Uuid,
1162        tag: Box<Option<TagNode>>,
1163    },
1164    /// A chamfer.
1165    Chamfer {
1166        /// The id of the engine command that called this chamfer.
1167        id: uuid::Uuid,
1168        length: TyF64,
1169        /// The engine id of the edge to chamfer.
1170        #[serde(rename = "edgeId")]
1171        edge_id: uuid::Uuid,
1172        tag: Box<Option<TagNode>>,
1173    },
1174}
1175
1176impl EdgeCut {
1177    pub fn id(&self) -> uuid::Uuid {
1178        match self {
1179            EdgeCut::Fillet { id, .. } => *id,
1180            EdgeCut::Chamfer { id, .. } => *id,
1181        }
1182    }
1183
1184    pub fn set_id(&mut self, id: uuid::Uuid) {
1185        match self {
1186            EdgeCut::Fillet { id: i, .. } => *i = id,
1187            EdgeCut::Chamfer { id: i, .. } => *i = id,
1188        }
1189    }
1190
1191    pub fn edge_id(&self) -> uuid::Uuid {
1192        match self {
1193            EdgeCut::Fillet { edge_id, .. } => *edge_id,
1194            EdgeCut::Chamfer { edge_id, .. } => *edge_id,
1195        }
1196    }
1197
1198    pub fn set_edge_id(&mut self, id: uuid::Uuid) {
1199        match self {
1200            EdgeCut::Fillet { edge_id: i, .. } => *i = id,
1201            EdgeCut::Chamfer { edge_id: i, .. } => *i = id,
1202        }
1203    }
1204
1205    pub fn tag(&self) -> Option<TagNode> {
1206        match self {
1207            EdgeCut::Fillet { tag, .. } => *tag.clone(),
1208            EdgeCut::Chamfer { tag, .. } => *tag.clone(),
1209        }
1210    }
1211}
1212
1213#[derive(Debug, Serialize, PartialEq, Clone, Copy, ts_rs::TS)]
1214#[ts(export)]
1215pub struct Point2d {
1216    pub x: f64,
1217    pub y: f64,
1218    pub units: UnitLength,
1219}
1220
1221impl Point2d {
1222    pub const ZERO: Self = Self {
1223        x: 0.0,
1224        y: 0.0,
1225        units: UnitLength::Millimeters,
1226    };
1227
1228    pub fn new(x: f64, y: f64, units: UnitLength) -> Self {
1229        Self { x, y, units }
1230    }
1231
1232    pub fn into_x(self) -> TyF64 {
1233        TyF64::new(self.x, self.units.into())
1234    }
1235
1236    pub fn into_y(self) -> TyF64 {
1237        TyF64::new(self.y, self.units.into())
1238    }
1239
1240    pub fn ignore_units(self) -> [f64; 2] {
1241        [self.x, self.y]
1242    }
1243}
1244
1245#[derive(Debug, Deserialize, Serialize, PartialEq, Clone, Copy, ts_rs::TS, Default)]
1246#[ts(export)]
1247pub struct Point3d {
1248    pub x: f64,
1249    pub y: f64,
1250    pub z: f64,
1251    pub units: Option<UnitLength>,
1252}
1253
1254impl Point3d {
1255    pub const ZERO: Self = Self {
1256        x: 0.0,
1257        y: 0.0,
1258        z: 0.0,
1259        units: Some(UnitLength::Millimeters),
1260    };
1261
1262    pub fn new(x: f64, y: f64, z: f64, units: Option<UnitLength>) -> Self {
1263        Self { x, y, z, units }
1264    }
1265
1266    pub const fn is_zero(&self) -> bool {
1267        self.x == 0.0 && self.y == 0.0 && self.z == 0.0
1268    }
1269
1270    /// Calculate the cross product of this vector with another.
1271    ///
1272    /// This should only be applied to axes or other vectors which represent only a direction (and
1273    /// no magnitude) since units are ignored.
1274    pub fn axes_cross_product(&self, other: &Self) -> Self {
1275        Self {
1276            x: self.y * other.z - self.z * other.y,
1277            y: self.z * other.x - self.x * other.z,
1278            z: self.x * other.y - self.y * other.x,
1279            units: None,
1280        }
1281    }
1282
1283    /// Calculate the dot product of this vector with another.
1284    ///
1285    /// This should only be applied to axes or other vectors which represent only a direction (and
1286    /// no magnitude) since units are ignored.
1287    pub fn axes_dot_product(&self, other: &Self) -> f64 {
1288        let x = self.x * other.x;
1289        let y = self.y * other.y;
1290        let z = self.z * other.z;
1291        x + y + z
1292    }
1293
1294    pub fn normalize(&self) -> Self {
1295        let len = f64::sqrt(self.x * self.x + self.y * self.y + self.z * self.z);
1296        Point3d {
1297            x: self.x / len,
1298            y: self.y / len,
1299            z: self.z / len,
1300            units: None,
1301        }
1302    }
1303
1304    pub fn as_3_dims(&self) -> ([f64; 3], Option<UnitLength>) {
1305        let p = [self.x, self.y, self.z];
1306        let u = self.units;
1307        (p, u)
1308    }
1309
1310    pub(crate) fn negated(self) -> Self {
1311        Self {
1312            x: -self.x,
1313            y: -self.y,
1314            z: -self.z,
1315            units: self.units,
1316        }
1317    }
1318}
1319
1320impl From<[TyF64; 3]> for Point3d {
1321    fn from(p: [TyF64; 3]) -> Self {
1322        Self {
1323            x: p[0].n,
1324            y: p[1].n,
1325            z: p[2].n,
1326            units: p[0].ty.as_length(),
1327        }
1328    }
1329}
1330
1331impl From<Point3d> for Point3D {
1332    fn from(p: Point3d) -> Self {
1333        Self { x: p.x, y: p.y, z: p.z }
1334    }
1335}
1336
1337impl From<Point3d> for kittycad_modeling_cmds::shared::Point3d<LengthUnit> {
1338    fn from(p: Point3d) -> Self {
1339        if let Some(units) = p.units {
1340            Self {
1341                x: LengthUnit(adjust_length(units, p.x, UnitLength::Millimeters).0),
1342                y: LengthUnit(adjust_length(units, p.y, UnitLength::Millimeters).0),
1343                z: LengthUnit(adjust_length(units, p.z, UnitLength::Millimeters).0),
1344            }
1345        } else {
1346            Self {
1347                x: LengthUnit(p.x),
1348                y: LengthUnit(p.y),
1349                z: LengthUnit(p.z),
1350            }
1351        }
1352    }
1353}
1354
1355impl Add for Point3d {
1356    type Output = Point3d;
1357
1358    fn add(self, rhs: Self) -> Self::Output {
1359        // TODO should assert that self and rhs the same units or coerce them
1360        Point3d {
1361            x: self.x + rhs.x,
1362            y: self.y + rhs.y,
1363            z: self.z + rhs.z,
1364            units: self.units,
1365        }
1366    }
1367}
1368
1369impl AddAssign for Point3d {
1370    fn add_assign(&mut self, rhs: Self) {
1371        *self = *self + rhs
1372    }
1373}
1374
1375impl Sub for Point3d {
1376    type Output = Point3d;
1377
1378    fn sub(self, rhs: Self) -> Self::Output {
1379        let (x, y, z) = if rhs.units != self.units
1380            && let Some(sunits) = self.units
1381            && let Some(runits) = rhs.units
1382        {
1383            (
1384                adjust_length(runits, rhs.x, sunits).0,
1385                adjust_length(runits, rhs.y, sunits).0,
1386                adjust_length(runits, rhs.z, sunits).0,
1387            )
1388        } else {
1389            (rhs.x, rhs.y, rhs.z)
1390        };
1391        Point3d {
1392            x: self.x - x,
1393            y: self.y - y,
1394            z: self.z - z,
1395            units: self.units,
1396        }
1397    }
1398}
1399
1400impl SubAssign for Point3d {
1401    fn sub_assign(&mut self, rhs: Self) {
1402        *self = *self - rhs
1403    }
1404}
1405
1406impl Mul<f64> for Point3d {
1407    type Output = Point3d;
1408
1409    fn mul(self, rhs: f64) -> Self::Output {
1410        Point3d {
1411            x: self.x * rhs,
1412            y: self.y * rhs,
1413            z: self.z * rhs,
1414            units: self.units,
1415        }
1416    }
1417}
1418
1419/// A base path.
1420#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1421#[ts(export)]
1422#[serde(rename_all = "camelCase")]
1423pub struct BasePath {
1424    /// The from point.
1425    #[ts(type = "[number, number]")]
1426    pub from: [f64; 2],
1427    /// The to point.
1428    #[ts(type = "[number, number]")]
1429    pub to: [f64; 2],
1430    pub units: UnitLength,
1431    /// The tag of the path.
1432    pub tag: Option<TagNode>,
1433    /// Metadata.
1434    #[serde(rename = "__geoMeta")]
1435    pub geo_meta: GeoMeta,
1436}
1437
1438impl BasePath {
1439    pub fn get_to(&self) -> [TyF64; 2] {
1440        let ty: NumericType = self.units.into();
1441        [TyF64::new(self.to[0], ty), TyF64::new(self.to[1], ty)]
1442    }
1443
1444    pub fn get_from(&self) -> [TyF64; 2] {
1445        let ty: NumericType = self.units.into();
1446        [TyF64::new(self.from[0], ty), TyF64::new(self.from[1], ty)]
1447    }
1448}
1449
1450/// Geometry metadata.
1451#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1452#[ts(export)]
1453#[serde(rename_all = "camelCase")]
1454pub struct GeoMeta {
1455    /// The id of the geometry.
1456    pub id: uuid::Uuid,
1457    /// Metadata.
1458    #[serde(flatten)]
1459    pub metadata: Metadata,
1460}
1461
1462/// A path.
1463#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1464#[ts(export)]
1465#[serde(tag = "type")]
1466pub enum Path {
1467    /// A straight line which ends at the given point.
1468    ToPoint {
1469        #[serde(flatten)]
1470        base: BasePath,
1471    },
1472    /// A arc that is tangential to the last path segment that goes to a point
1473    TangentialArcTo {
1474        #[serde(flatten)]
1475        base: BasePath,
1476        /// the arc's center
1477        #[ts(type = "[number, number]")]
1478        center: [f64; 2],
1479        /// arc's direction
1480        ccw: bool,
1481    },
1482    /// A arc that is tangential to the last path segment
1483    TangentialArc {
1484        #[serde(flatten)]
1485        base: BasePath,
1486        /// the arc's center
1487        #[ts(type = "[number, number]")]
1488        center: [f64; 2],
1489        /// arc's direction
1490        ccw: bool,
1491    },
1492    // TODO: consolidate segment enums, remove Circle. https://github.com/KittyCAD/modeling-app/issues/3940
1493    /// a complete arc
1494    Circle {
1495        #[serde(flatten)]
1496        base: BasePath,
1497        /// the arc's center
1498        #[ts(type = "[number, number]")]
1499        center: [f64; 2],
1500        /// the arc's radius
1501        radius: f64,
1502        /// arc's direction
1503        /// This is used to compute the tangential angle.
1504        ccw: bool,
1505    },
1506    CircleThreePoint {
1507        #[serde(flatten)]
1508        base: BasePath,
1509        /// Point 1 of the circle
1510        #[ts(type = "[number, number]")]
1511        p1: [f64; 2],
1512        /// Point 2 of the circle
1513        #[ts(type = "[number, number]")]
1514        p2: [f64; 2],
1515        /// Point 3 of the circle
1516        #[ts(type = "[number, number]")]
1517        p3: [f64; 2],
1518    },
1519    ArcThreePoint {
1520        #[serde(flatten)]
1521        base: BasePath,
1522        /// Point 1 of the arc (base on the end of previous segment)
1523        #[ts(type = "[number, number]")]
1524        p1: [f64; 2],
1525        /// Point 2 of the arc (interiorAbsolute kwarg)
1526        #[ts(type = "[number, number]")]
1527        p2: [f64; 2],
1528        /// Point 3 of the arc (endAbsolute kwarg)
1529        #[ts(type = "[number, number]")]
1530        p3: [f64; 2],
1531    },
1532    /// A path that is horizontal.
1533    Horizontal {
1534        #[serde(flatten)]
1535        base: BasePath,
1536        /// The x coordinate.
1537        x: f64,
1538    },
1539    /// An angled line to.
1540    AngledLineTo {
1541        #[serde(flatten)]
1542        base: BasePath,
1543        /// The x coordinate.
1544        x: Option<f64>,
1545        /// The y coordinate.
1546        y: Option<f64>,
1547    },
1548    /// A base path.
1549    Base {
1550        #[serde(flatten)]
1551        base: BasePath,
1552    },
1553    /// A circular arc, not necessarily tangential to the current point.
1554    Arc {
1555        #[serde(flatten)]
1556        base: BasePath,
1557        /// Center of the circle that this arc is drawn on.
1558        center: [f64; 2],
1559        /// Radius of the circle that this arc is drawn on.
1560        radius: f64,
1561        /// True if the arc is counterclockwise.
1562        ccw: bool,
1563    },
1564    Ellipse {
1565        #[serde(flatten)]
1566        base: BasePath,
1567        center: [f64; 2],
1568        major_axis: [f64; 2],
1569        minor_radius: f64,
1570        ccw: bool,
1571    },
1572    //TODO: (bc) figure this out
1573    Conic {
1574        #[serde(flatten)]
1575        base: BasePath,
1576    },
1577    /// A cubic Bezier curve.
1578    Bezier {
1579        #[serde(flatten)]
1580        base: BasePath,
1581        /// First control point (absolute coordinates).
1582        #[ts(type = "[number, number]")]
1583        control1: [f64; 2],
1584        /// Second control point (absolute coordinates).
1585        #[ts(type = "[number, number]")]
1586        control2: [f64; 2],
1587    },
1588}
1589
1590impl Path {
1591    pub fn get_id(&self) -> uuid::Uuid {
1592        match self {
1593            Path::ToPoint { base } => base.geo_meta.id,
1594            Path::Horizontal { base, .. } => base.geo_meta.id,
1595            Path::AngledLineTo { base, .. } => base.geo_meta.id,
1596            Path::Base { base } => base.geo_meta.id,
1597            Path::TangentialArcTo { base, .. } => base.geo_meta.id,
1598            Path::TangentialArc { base, .. } => base.geo_meta.id,
1599            Path::Circle { base, .. } => base.geo_meta.id,
1600            Path::CircleThreePoint { base, .. } => base.geo_meta.id,
1601            Path::Arc { base, .. } => base.geo_meta.id,
1602            Path::ArcThreePoint { base, .. } => base.geo_meta.id,
1603            Path::Ellipse { base, .. } => base.geo_meta.id,
1604            Path::Conic { base, .. } => base.geo_meta.id,
1605            Path::Bezier { base, .. } => base.geo_meta.id,
1606        }
1607    }
1608
1609    pub fn set_id(&mut self, id: uuid::Uuid) {
1610        match self {
1611            Path::ToPoint { base } => base.geo_meta.id = id,
1612            Path::Horizontal { base, .. } => base.geo_meta.id = id,
1613            Path::AngledLineTo { base, .. } => base.geo_meta.id = id,
1614            Path::Base { base } => base.geo_meta.id = id,
1615            Path::TangentialArcTo { base, .. } => base.geo_meta.id = id,
1616            Path::TangentialArc { base, .. } => base.geo_meta.id = id,
1617            Path::Circle { base, .. } => base.geo_meta.id = id,
1618            Path::CircleThreePoint { base, .. } => base.geo_meta.id = id,
1619            Path::Arc { base, .. } => base.geo_meta.id = id,
1620            Path::ArcThreePoint { base, .. } => base.geo_meta.id = id,
1621            Path::Ellipse { base, .. } => base.geo_meta.id = id,
1622            Path::Conic { base, .. } => base.geo_meta.id = id,
1623            Path::Bezier { base, .. } => base.geo_meta.id = id,
1624        }
1625    }
1626
1627    pub fn get_tag(&self) -> Option<TagNode> {
1628        match self {
1629            Path::ToPoint { base } => base.tag.clone(),
1630            Path::Horizontal { base, .. } => base.tag.clone(),
1631            Path::AngledLineTo { base, .. } => base.tag.clone(),
1632            Path::Base { base } => base.tag.clone(),
1633            Path::TangentialArcTo { base, .. } => base.tag.clone(),
1634            Path::TangentialArc { base, .. } => base.tag.clone(),
1635            Path::Circle { base, .. } => base.tag.clone(),
1636            Path::CircleThreePoint { base, .. } => base.tag.clone(),
1637            Path::Arc { base, .. } => base.tag.clone(),
1638            Path::ArcThreePoint { base, .. } => base.tag.clone(),
1639            Path::Ellipse { base, .. } => base.tag.clone(),
1640            Path::Conic { base, .. } => base.tag.clone(),
1641            Path::Bezier { base, .. } => base.tag.clone(),
1642        }
1643    }
1644
1645    pub fn get_base(&self) -> &BasePath {
1646        match self {
1647            Path::ToPoint { base } => base,
1648            Path::Horizontal { base, .. } => base,
1649            Path::AngledLineTo { base, .. } => base,
1650            Path::Base { base } => base,
1651            Path::TangentialArcTo { base, .. } => base,
1652            Path::TangentialArc { base, .. } => base,
1653            Path::Circle { base, .. } => base,
1654            Path::CircleThreePoint { base, .. } => base,
1655            Path::Arc { base, .. } => base,
1656            Path::ArcThreePoint { base, .. } => base,
1657            Path::Ellipse { base, .. } => base,
1658            Path::Conic { base, .. } => base,
1659            Path::Bezier { base, .. } => base,
1660        }
1661    }
1662
1663    /// Where does this path segment start?
1664    pub fn get_from(&self) -> [TyF64; 2] {
1665        let p = &self.get_base().from;
1666        let ty: NumericType = self.get_base().units.into();
1667        [TyF64::new(p[0], ty), TyF64::new(p[1], ty)]
1668    }
1669
1670    /// Where does this path segment end?
1671    pub fn get_to(&self) -> [TyF64; 2] {
1672        let p = &self.get_base().to;
1673        let ty: NumericType = self.get_base().units.into();
1674        [TyF64::new(p[0], ty), TyF64::new(p[1], ty)]
1675    }
1676
1677    /// The path segment start point and its type.
1678    pub fn start_point_components(&self) -> ([f64; 2], NumericType) {
1679        let p = &self.get_base().from;
1680        let ty: NumericType = self.get_base().units.into();
1681        (*p, ty)
1682    }
1683
1684    /// The path segment end point and its type.
1685    pub fn end_point_components(&self) -> ([f64; 2], NumericType) {
1686        let p = &self.get_base().to;
1687        let ty: NumericType = self.get_base().units.into();
1688        (*p, ty)
1689    }
1690
1691    /// Length of this path segment, in cartesian plane. Not all segment types
1692    /// are supported.
1693    pub fn length(&self) -> Option<TyF64> {
1694        let n = match self {
1695            Self::ToPoint { .. } | Self::Base { .. } | Self::Horizontal { .. } | Self::AngledLineTo { .. } => {
1696                Some(linear_distance(&self.get_base().from, &self.get_base().to))
1697            }
1698            Self::TangentialArc {
1699                base: _,
1700                center,
1701                ccw: _,
1702            }
1703            | Self::TangentialArcTo {
1704                base: _,
1705                center,
1706                ccw: _,
1707            } => {
1708                // The radius can be calculated as the linear distance between `to` and `center`,
1709                // or between `from` and `center`. They should be the same.
1710                let radius = linear_distance(&self.get_base().from, center);
1711                debug_assert_eq!(radius, linear_distance(&self.get_base().to, center));
1712                // TODO: Call engine utils to figure this out.
1713                Some(linear_distance(&self.get_base().from, &self.get_base().to))
1714            }
1715            Self::Circle { radius, .. } => Some(TAU * radius),
1716            Self::CircleThreePoint { .. } => {
1717                let circle_center = crate::std::utils::calculate_circle_from_3_points([
1718                    self.get_base().from,
1719                    self.get_base().to,
1720                    self.get_base().to,
1721                ]);
1722                let radius = linear_distance(
1723                    &[circle_center.center[0], circle_center.center[1]],
1724                    &self.get_base().from,
1725                );
1726                Some(TAU * radius)
1727            }
1728            Self::Arc { .. } => {
1729                // TODO: Call engine utils to figure this out.
1730                Some(linear_distance(&self.get_base().from, &self.get_base().to))
1731            }
1732            Self::ArcThreePoint { .. } => {
1733                // TODO: Call engine utils to figure this out.
1734                Some(linear_distance(&self.get_base().from, &self.get_base().to))
1735            }
1736            Self::Ellipse { .. } => {
1737                // Not supported.
1738                None
1739            }
1740            Self::Conic { .. } => {
1741                // Not supported.
1742                None
1743            }
1744            Self::Bezier { .. } => {
1745                // Not supported - Bezier curve length requires numerical integration.
1746                None
1747            }
1748        };
1749        n.map(|n| TyF64::new(n, self.get_base().units.into()))
1750    }
1751
1752    pub fn get_base_mut(&mut self) -> Option<&mut BasePath> {
1753        match self {
1754            Path::ToPoint { base } => Some(base),
1755            Path::Horizontal { base, .. } => Some(base),
1756            Path::AngledLineTo { base, .. } => Some(base),
1757            Path::Base { base } => Some(base),
1758            Path::TangentialArcTo { base, .. } => Some(base),
1759            Path::TangentialArc { base, .. } => Some(base),
1760            Path::Circle { base, .. } => Some(base),
1761            Path::CircleThreePoint { base, .. } => Some(base),
1762            Path::Arc { base, .. } => Some(base),
1763            Path::ArcThreePoint { base, .. } => Some(base),
1764            Path::Ellipse { base, .. } => Some(base),
1765            Path::Conic { base, .. } => Some(base),
1766            Path::Bezier { base, .. } => Some(base),
1767        }
1768    }
1769
1770    pub(crate) fn get_tangential_info(&self) -> GetTangentialInfoFromPathsResult {
1771        match self {
1772            Path::TangentialArc { center, ccw, .. }
1773            | Path::TangentialArcTo { center, ccw, .. }
1774            | Path::Arc { center, ccw, .. } => GetTangentialInfoFromPathsResult::Arc {
1775                center: *center,
1776                ccw: *ccw,
1777            },
1778            Path::ArcThreePoint { p1, p2, p3, .. } => {
1779                let circle = crate::std::utils::calculate_circle_from_3_points([*p1, *p2, *p3]);
1780                GetTangentialInfoFromPathsResult::Arc {
1781                    center: circle.center,
1782                    ccw: crate::std::utils::is_points_ccw(&[*p1, *p2, *p3]) > 0,
1783                }
1784            }
1785            Path::Circle {
1786                center, ccw, radius, ..
1787            } => GetTangentialInfoFromPathsResult::Circle {
1788                center: *center,
1789                ccw: *ccw,
1790                radius: *radius,
1791            },
1792            Path::CircleThreePoint { p1, p2, p3, .. } => {
1793                let circle = crate::std::utils::calculate_circle_from_3_points([*p1, *p2, *p3]);
1794                let center_point = [circle.center[0], circle.center[1]];
1795                GetTangentialInfoFromPathsResult::Circle {
1796                    center: center_point,
1797                    // Note: a circle is always ccw regardless of the order of points
1798                    ccw: true,
1799                    radius: circle.radius,
1800                }
1801            }
1802            // TODO: (bc) fix me
1803            Path::Ellipse {
1804                center,
1805                major_axis,
1806                minor_radius,
1807                ccw,
1808                ..
1809            } => GetTangentialInfoFromPathsResult::Ellipse {
1810                center: *center,
1811                major_axis: *major_axis,
1812                _minor_radius: *minor_radius,
1813                ccw: *ccw,
1814            },
1815            Path::Conic { .. }
1816            | Path::ToPoint { .. }
1817            | Path::Horizontal { .. }
1818            | Path::AngledLineTo { .. }
1819            | Path::Base { .. }
1820            | Path::Bezier { .. } => {
1821                let base = self.get_base();
1822                GetTangentialInfoFromPathsResult::PreviousPoint(base.from)
1823            }
1824        }
1825    }
1826
1827    /// i.e. not a curve
1828    pub(crate) fn is_straight_line(&self) -> bool {
1829        matches!(self, Path::AngledLineTo { .. } | Path::ToPoint { .. })
1830    }
1831}
1832
1833/// Compute the straight-line distance between a pair of (2D) points.
1834#[rustfmt::skip]
1835fn linear_distance(
1836    [x0, y0]: &[f64; 2],
1837    [x1, y1]: &[f64; 2]
1838) -> f64 {
1839    let y_sq = (y1 - y0).powi(2);
1840    let x_sq = (x1 - x0).powi(2);
1841    (y_sq + x_sq).sqrt()
1842}
1843
1844/// An extrude surface.
1845#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1846#[ts(export)]
1847#[serde(tag = "type", rename_all = "camelCase")]
1848pub enum ExtrudeSurface {
1849    /// An extrude plane.
1850    ExtrudePlane(ExtrudePlane),
1851    ExtrudeArc(ExtrudeArc),
1852    Chamfer(ChamferSurface),
1853    Fillet(FilletSurface),
1854}
1855
1856// Chamfer surface.
1857#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1858#[ts(export)]
1859#[serde(rename_all = "camelCase")]
1860pub struct ChamferSurface {
1861    /// The id for the chamfer surface.
1862    pub face_id: uuid::Uuid,
1863    /// The tag.
1864    pub tag: Option<Node<TagDeclarator>>,
1865    /// Metadata.
1866    #[serde(flatten)]
1867    pub geo_meta: GeoMeta,
1868}
1869
1870// Fillet surface.
1871#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1872#[ts(export)]
1873#[serde(rename_all = "camelCase")]
1874pub struct FilletSurface {
1875    /// The id for the fillet surface.
1876    pub face_id: uuid::Uuid,
1877    /// The tag.
1878    pub tag: Option<Node<TagDeclarator>>,
1879    /// Metadata.
1880    #[serde(flatten)]
1881    pub geo_meta: GeoMeta,
1882}
1883
1884/// An extruded plane.
1885#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1886#[ts(export)]
1887#[serde(rename_all = "camelCase")]
1888pub struct ExtrudePlane {
1889    /// The face id for the extrude plane.
1890    pub face_id: uuid::Uuid,
1891    /// The tag.
1892    pub tag: Option<Node<TagDeclarator>>,
1893    /// Metadata.
1894    #[serde(flatten)]
1895    pub geo_meta: GeoMeta,
1896}
1897
1898/// An extruded arc.
1899#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1900#[ts(export)]
1901#[serde(rename_all = "camelCase")]
1902pub struct ExtrudeArc {
1903    /// The face id for the extrude plane.
1904    pub face_id: uuid::Uuid,
1905    /// The tag.
1906    pub tag: Option<Node<TagDeclarator>>,
1907    /// Metadata.
1908    #[serde(flatten)]
1909    pub geo_meta: GeoMeta,
1910}
1911
1912impl ExtrudeSurface {
1913    pub fn get_id(&self) -> uuid::Uuid {
1914        match self {
1915            ExtrudeSurface::ExtrudePlane(ep) => ep.geo_meta.id,
1916            ExtrudeSurface::ExtrudeArc(ea) => ea.geo_meta.id,
1917            ExtrudeSurface::Fillet(f) => f.geo_meta.id,
1918            ExtrudeSurface::Chamfer(c) => c.geo_meta.id,
1919        }
1920    }
1921
1922    pub fn face_id(&self) -> uuid::Uuid {
1923        match self {
1924            ExtrudeSurface::ExtrudePlane(ep) => ep.face_id,
1925            ExtrudeSurface::ExtrudeArc(ea) => ea.face_id,
1926            ExtrudeSurface::Fillet(f) => f.face_id,
1927            ExtrudeSurface::Chamfer(c) => c.face_id,
1928        }
1929    }
1930
1931    pub fn set_face_id(&mut self, face_id: uuid::Uuid) {
1932        match self {
1933            ExtrudeSurface::ExtrudePlane(ep) => ep.face_id = face_id,
1934            ExtrudeSurface::ExtrudeArc(ea) => ea.face_id = face_id,
1935            ExtrudeSurface::Fillet(f) => f.face_id = face_id,
1936            ExtrudeSurface::Chamfer(c) => c.face_id = face_id,
1937        }
1938    }
1939
1940    pub fn set_surface_tag(&mut self, tag: &TagNode) {
1941        match self {
1942            ExtrudeSurface::ExtrudePlane(extrude_plane) => extrude_plane.tag = Some(tag.clone()),
1943            ExtrudeSurface::ExtrudeArc(extrude_arc) => extrude_arc.tag = Some(tag.clone()),
1944            ExtrudeSurface::Chamfer(chamfer) => chamfer.tag = Some(tag.clone()),
1945            ExtrudeSurface::Fillet(fillet) => fillet.tag = Some(tag.clone()),
1946        }
1947    }
1948
1949    pub fn get_tag(&self) -> Option<Node<TagDeclarator>> {
1950        match self {
1951            ExtrudeSurface::ExtrudePlane(ep) => ep.tag.clone(),
1952            ExtrudeSurface::ExtrudeArc(ea) => ea.tag.clone(),
1953            ExtrudeSurface::Fillet(f) => f.tag.clone(),
1954            ExtrudeSurface::Chamfer(c) => c.tag.clone(),
1955        }
1956    }
1957}
1958
1959#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, ts_rs::TS)]
1960pub struct SketchVarId(pub usize);
1961
1962impl SketchVarId {
1963    pub fn to_constraint_id(self, range: SourceRange) -> Result<kcl_ezpz::Id, KclError> {
1964        self.0.try_into().map_err(|_| {
1965            KclError::new_type(KclErrorDetails::new(
1966                "Cannot convert to constraint ID since the sketch variable ID is too large".to_owned(),
1967                vec![range],
1968            ))
1969        })
1970    }
1971}
1972
1973#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1974#[ts(export_to = "Geometry.ts")]
1975#[serde(rename_all = "camelCase")]
1976pub struct SketchVar {
1977    pub id: SketchVarId,
1978    pub initial_value: f64,
1979    pub ty: NumericType,
1980    #[serde(skip)]
1981    pub meta: Vec<Metadata>,
1982}
1983
1984impl SketchVar {
1985    pub fn initial_value_to_solver_units(
1986        &self,
1987        exec_state: &mut ExecState,
1988        source_range: SourceRange,
1989        description: &str,
1990    ) -> Result<TyF64, KclError> {
1991        let x_initial_value = KclValue::Number {
1992            value: self.initial_value,
1993            ty: self.ty,
1994            meta: vec![source_range.into()],
1995        };
1996        let normalized_value = normalize_to_solver_unit(&x_initial_value, source_range, exec_state, description)?;
1997        normalized_value.as_ty_f64().ok_or_else(|| {
1998            let message = format!(
1999                "Expected number after coercion, but found {}",
2000                normalized_value.human_friendly_type()
2001            );
2002            debug_assert!(false, "{}", &message);
2003            KclError::new_internal(KclErrorDetails::new(message, vec![source_range]))
2004        })
2005    }
2006}
2007
2008#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2009#[ts(export_to = "Geometry.ts")]
2010#[serde(tag = "type")]
2011pub enum UnsolvedExpr {
2012    Known(TyF64),
2013    Unknown(SketchVarId),
2014}
2015
2016impl UnsolvedExpr {
2017    pub fn var(&self) -> Option<SketchVarId> {
2018        match self {
2019            UnsolvedExpr::Known(_) => None,
2020            UnsolvedExpr::Unknown(id) => Some(*id),
2021        }
2022    }
2023}
2024
2025pub type UnsolvedPoint2dExpr = [UnsolvedExpr; 2];
2026
2027#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2028#[ts(export_to = "Geometry.ts")]
2029#[serde(rename_all = "camelCase")]
2030pub struct ConstrainablePoint2d {
2031    pub vars: crate::front::Point2d<SketchVarId>,
2032    pub object_id: ObjectId,
2033}
2034
2035#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2036#[ts(export_to = "Geometry.ts")]
2037#[serde(rename_all = "camelCase")]
2038pub struct UnsolvedSegment {
2039    /// The engine ID.
2040    pub id: Uuid,
2041    pub object_id: ObjectId,
2042    pub kind: UnsolvedSegmentKind,
2043    #[serde(skip_serializing_if = "Option::is_none")]
2044    pub tag: Option<TagIdentifier>,
2045    #[serde(skip)]
2046    pub meta: Vec<Metadata>,
2047}
2048
2049#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2050#[ts(export_to = "Geometry.ts")]
2051#[serde(rename_all = "camelCase")]
2052pub enum UnsolvedSegmentKind {
2053    Point {
2054        position: UnsolvedPoint2dExpr,
2055        ctor: Box<PointCtor>,
2056    },
2057    Line {
2058        start: UnsolvedPoint2dExpr,
2059        end: UnsolvedPoint2dExpr,
2060        ctor: Box<LineCtor>,
2061        start_object_id: ObjectId,
2062        end_object_id: ObjectId,
2063        construction: bool,
2064    },
2065    Arc {
2066        start: UnsolvedPoint2dExpr,
2067        end: UnsolvedPoint2dExpr,
2068        center: UnsolvedPoint2dExpr,
2069        ctor: Box<ArcCtor>,
2070        start_object_id: ObjectId,
2071        end_object_id: ObjectId,
2072        center_object_id: ObjectId,
2073        construction: bool,
2074    },
2075}
2076
2077#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2078#[ts(export_to = "Geometry.ts")]
2079#[serde(rename_all = "camelCase")]
2080pub struct Segment {
2081    /// The engine ID.
2082    pub id: Uuid,
2083    pub object_id: ObjectId,
2084    pub kind: SegmentKind,
2085    pub surface: SketchSurface,
2086    /// The engine ID of the sketch that this is a part of.
2087    pub sketch_id: Uuid,
2088    #[serde(skip)]
2089    pub sketch: Option<Sketch>,
2090    #[serde(skip_serializing_if = "Option::is_none")]
2091    pub tag: Option<TagIdentifier>,
2092    #[serde(skip)]
2093    pub meta: Vec<Metadata>,
2094}
2095
2096impl Segment {
2097    pub fn is_construction(&self) -> bool {
2098        match &self.kind {
2099            SegmentKind::Point { .. } => true,
2100            SegmentKind::Line { construction, .. } => *construction,
2101            SegmentKind::Arc { construction, .. } => *construction,
2102        }
2103    }
2104}
2105
2106#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2107#[ts(export_to = "Geometry.ts")]
2108#[serde(rename_all = "camelCase")]
2109pub enum SegmentKind {
2110    Point {
2111        position: [TyF64; 2],
2112        ctor: Box<PointCtor>,
2113        #[serde(skip_serializing_if = "Option::is_none")]
2114        freedom: Option<Freedom>,
2115    },
2116    Line {
2117        start: [TyF64; 2],
2118        end: [TyF64; 2],
2119        ctor: Box<LineCtor>,
2120        start_object_id: ObjectId,
2121        end_object_id: ObjectId,
2122        #[serde(skip_serializing_if = "Option::is_none")]
2123        start_freedom: Option<Freedom>,
2124        #[serde(skip_serializing_if = "Option::is_none")]
2125        end_freedom: Option<Freedom>,
2126        construction: bool,
2127    },
2128    Arc {
2129        start: [TyF64; 2],
2130        end: [TyF64; 2],
2131        center: [TyF64; 2],
2132        ctor: Box<ArcCtor>,
2133        start_object_id: ObjectId,
2134        end_object_id: ObjectId,
2135        center_object_id: ObjectId,
2136        #[serde(skip_serializing_if = "Option::is_none")]
2137        start_freedom: Option<Freedom>,
2138        #[serde(skip_serializing_if = "Option::is_none")]
2139        end_freedom: Option<Freedom>,
2140        #[serde(skip_serializing_if = "Option::is_none")]
2141        center_freedom: Option<Freedom>,
2142        construction: bool,
2143    },
2144}
2145
2146#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2147#[ts(export_to = "Geometry.ts")]
2148#[serde(rename_all = "camelCase")]
2149pub struct AbstractSegment {
2150    pub repr: SegmentRepr,
2151    #[serde(skip)]
2152    pub meta: Vec<Metadata>,
2153}
2154
2155#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2156pub enum SegmentRepr {
2157    Unsolved { segment: Box<UnsolvedSegment> },
2158    Solved { segment: Box<Segment> },
2159}
2160
2161#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2162#[ts(export_to = "Geometry.ts")]
2163#[serde(rename_all = "camelCase")]
2164pub struct SketchConstraint {
2165    pub kind: SketchConstraintKind,
2166    #[serde(skip)]
2167    pub meta: Vec<Metadata>,
2168}
2169
2170#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2171#[ts(export_to = "Geometry.ts")]
2172#[serde(rename_all = "camelCase")]
2173pub enum SketchConstraintKind {
2174    Distance { points: [ConstrainablePoint2d; 2] },
2175    Radius { points: [ConstrainablePoint2d; 2] },
2176    Diameter { points: [ConstrainablePoint2d; 2] },
2177    HorizontalDistance { points: [ConstrainablePoint2d; 2] },
2178    VerticalDistance { points: [ConstrainablePoint2d; 2] },
2179}
2180
2181impl SketchConstraintKind {
2182    pub fn name(&self) -> &'static str {
2183        match self {
2184            SketchConstraintKind::Distance { .. } => "distance",
2185            SketchConstraintKind::Radius { .. } => "radius",
2186            SketchConstraintKind::Diameter { .. } => "diameter",
2187            SketchConstraintKind::HorizontalDistance { .. } => "horizontalDistance",
2188            SketchConstraintKind::VerticalDistance { .. } => "verticalDistance",
2189        }
2190    }
2191}