Skip to main content

kcl_lib/execution/
geometry.rs

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