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