1use std::ops::{Add, AddAssign, Mul};
2
3use anyhow::Result;
4use indexmap::IndexMap;
5use kittycad_modeling_cmds as kcmc;
6use kittycad_modeling_cmds::{each_cmd as mcmd, length_unit::LengthUnit, websocket::ModelingCmdReq, ModelingCmd};
7use parse_display::{Display, FromStr};
8use schemars::JsonSchema;
9use serde::{Deserialize, Serialize};
10
11#[cfg(feature = "artifact-graph")]
12use crate::execution::ArtifactId;
13use crate::{
14    engine::{PlaneName, DEFAULT_PLANE_INFO},
15    errors::{KclError, KclErrorDetails},
16    execution::{types::NumericType, ExecState, ExecutorContext, Metadata, TagEngineInfo, TagIdentifier, UnitLen},
17    parsing::ast::types::{Node, NodeRef, TagDeclarator, TagNode},
18    std::{args::TyF64, sketch::PlaneData},
19};
20
21type Point3D = kcmc::shared::Point3d<f64>;
22
23#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
25#[ts(export)]
26#[serde(tag = "type")]
27pub enum Geometry {
28    Sketch(Sketch),
29    Solid(Solid),
30}
31
32impl Geometry {
33    pub fn id(&self) -> uuid::Uuid {
34        match self {
35            Geometry::Sketch(s) => s.id,
36            Geometry::Solid(e) => e.id,
37        }
38    }
39
40    pub fn original_id(&self) -> uuid::Uuid {
44        match self {
45            Geometry::Sketch(s) => s.original_id,
46            Geometry::Solid(e) => e.sketch.original_id,
47        }
48    }
49}
50
51#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
53#[ts(export)]
54#[serde(tag = "type")]
55pub enum GeometryWithImportedGeometry {
56    Sketch(Sketch),
57    Solid(Solid),
58    ImportedGeometry(Box<ImportedGeometry>),
59}
60
61impl GeometryWithImportedGeometry {
62    pub async fn id(&mut self, ctx: &ExecutorContext) -> Result<uuid::Uuid, KclError> {
63        match self {
64            GeometryWithImportedGeometry::Sketch(s) => Ok(s.id),
65            GeometryWithImportedGeometry::Solid(e) => Ok(e.id),
66            GeometryWithImportedGeometry::ImportedGeometry(i) => {
67                let id = i.id(ctx).await?;
68                Ok(id)
69            }
70        }
71    }
72}
73
74#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
76#[ts(export)]
77#[serde(tag = "type")]
78#[allow(clippy::vec_box)]
79pub enum Geometries {
80    Sketches(Vec<Sketch>),
81    Solids(Vec<Solid>),
82}
83
84impl From<Geometry> for Geometries {
85    fn from(value: Geometry) -> Self {
86        match value {
87            Geometry::Sketch(x) => Self::Sketches(vec![x]),
88            Geometry::Solid(x) => Self::Solids(vec![x]),
89        }
90    }
91}
92
93#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
95#[ts(export)]
96#[serde(rename_all = "camelCase")]
97pub struct ImportedGeometry {
98    pub id: uuid::Uuid,
100    pub value: Vec<String>,
102    #[serde(skip)]
103    pub meta: Vec<Metadata>,
104    #[serde(skip)]
106    completed: bool,
107}
108
109impl ImportedGeometry {
110    pub fn new(id: uuid::Uuid, value: Vec<String>, meta: Vec<Metadata>) -> Self {
111        Self {
112            id,
113            value,
114            meta,
115            completed: false,
116        }
117    }
118
119    async fn wait_for_finish(&mut self, ctx: &ExecutorContext) -> Result<(), KclError> {
120        if self.completed {
121            return Ok(());
122        }
123
124        ctx.engine
125            .ensure_async_command_completed(self.id, self.meta.first().map(|m| m.source_range))
126            .await?;
127
128        self.completed = true;
129
130        Ok(())
131    }
132
133    pub async fn id(&mut self, ctx: &ExecutorContext) -> Result<uuid::Uuid, KclError> {
134        if !self.completed {
135            self.wait_for_finish(ctx).await?;
136        }
137
138        Ok(self.id)
139    }
140}
141
142#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
144#[ts(export)]
145#[serde(tag = "type", rename_all = "camelCase")]
146#[allow(clippy::vec_box)]
147pub enum SolidOrSketchOrImportedGeometry {
148    ImportedGeometry(Box<ImportedGeometry>),
149    SolidSet(Vec<Solid>),
150    SketchSet(Vec<Sketch>),
151}
152
153impl From<SolidOrSketchOrImportedGeometry> for crate::execution::KclValue {
154    fn from(value: SolidOrSketchOrImportedGeometry) -> Self {
155        match value {
156            SolidOrSketchOrImportedGeometry::ImportedGeometry(s) => crate::execution::KclValue::ImportedGeometry(*s),
157            SolidOrSketchOrImportedGeometry::SolidSet(mut s) => {
158                if s.len() == 1 {
159                    crate::execution::KclValue::Solid {
160                        value: Box::new(s.pop().unwrap()),
161                    }
162                } else {
163                    crate::execution::KclValue::HomArray {
164                        value: s
165                            .into_iter()
166                            .map(|s| crate::execution::KclValue::Solid { value: Box::new(s) })
167                            .collect(),
168                        ty: crate::execution::types::RuntimeType::solid(),
169                    }
170                }
171            }
172            SolidOrSketchOrImportedGeometry::SketchSet(mut s) => {
173                if s.len() == 1 {
174                    crate::execution::KclValue::Sketch {
175                        value: Box::new(s.pop().unwrap()),
176                    }
177                } else {
178                    crate::execution::KclValue::HomArray {
179                        value: s
180                            .into_iter()
181                            .map(|s| crate::execution::KclValue::Sketch { value: Box::new(s) })
182                            .collect(),
183                        ty: crate::execution::types::RuntimeType::sketch(),
184                    }
185                }
186            }
187        }
188    }
189}
190
191impl SolidOrSketchOrImportedGeometry {
192    pub(crate) async fn ids(&mut self, ctx: &ExecutorContext) -> Result<Vec<uuid::Uuid>, KclError> {
193        match self {
194            SolidOrSketchOrImportedGeometry::ImportedGeometry(s) => {
195                let id = s.id(ctx).await?;
196
197                Ok(vec![id])
198            }
199            SolidOrSketchOrImportedGeometry::SolidSet(s) => Ok(s.iter().map(|s| s.id).collect()),
200            SolidOrSketchOrImportedGeometry::SketchSet(s) => Ok(s.iter().map(|s| s.id).collect()),
201        }
202    }
203}
204
205#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
207#[ts(export)]
208#[serde(tag = "type", rename_all = "camelCase")]
209#[allow(clippy::vec_box)]
210pub enum SolidOrImportedGeometry {
211    ImportedGeometry(Box<ImportedGeometry>),
212    SolidSet(Vec<Solid>),
213}
214
215impl From<SolidOrImportedGeometry> for crate::execution::KclValue {
216    fn from(value: SolidOrImportedGeometry) -> Self {
217        match value {
218            SolidOrImportedGeometry::ImportedGeometry(s) => crate::execution::KclValue::ImportedGeometry(*s),
219            SolidOrImportedGeometry::SolidSet(mut s) => {
220                if s.len() == 1 {
221                    crate::execution::KclValue::Solid {
222                        value: Box::new(s.pop().unwrap()),
223                    }
224                } else {
225                    crate::execution::KclValue::HomArray {
226                        value: s
227                            .into_iter()
228                            .map(|s| crate::execution::KclValue::Solid { value: Box::new(s) })
229                            .collect(),
230                        ty: crate::execution::types::RuntimeType::solid(),
231                    }
232                }
233            }
234        }
235    }
236}
237
238impl SolidOrImportedGeometry {
239    pub(crate) async fn ids(&mut self, ctx: &ExecutorContext) -> Result<Vec<uuid::Uuid>, KclError> {
240        match self {
241            SolidOrImportedGeometry::ImportedGeometry(s) => {
242                let id = s.id(ctx).await?;
243
244                Ok(vec![id])
245            }
246            SolidOrImportedGeometry::SolidSet(s) => Ok(s.iter().map(|s| s.id).collect()),
247        }
248    }
249}
250
251#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
253#[ts(export)]
254#[serde(rename_all = "camelCase")]
255pub struct Helix {
256    pub value: uuid::Uuid,
258    #[cfg(feature = "artifact-graph")]
260    pub artifact_id: ArtifactId,
261    pub revolutions: f64,
263    pub angle_start: f64,
265    pub ccw: bool,
267    pub cylinder_id: Option<uuid::Uuid>,
269    pub units: UnitLen,
270    #[serde(skip)]
271    pub meta: Vec<Metadata>,
272}
273
274#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
275#[ts(export)]
276#[serde(rename_all = "camelCase")]
277pub struct Plane {
278    pub id: uuid::Uuid,
280    #[cfg(feature = "artifact-graph")]
282    pub artifact_id: ArtifactId,
283    pub value: PlaneType,
285    #[serde(flatten)]
287    pub info: PlaneInfo,
288    #[serde(skip)]
289    pub meta: Vec<Metadata>,
290}
291
292#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, ts_rs::TS, JsonSchema)]
293#[ts(export)]
294#[serde(rename_all = "camelCase")]
295pub struct PlaneInfo {
296    pub origin: Point3d,
298    pub x_axis: Point3d,
300    pub y_axis: Point3d,
302}
303
304impl PlaneInfo {
305    pub(crate) fn into_plane_data(self) -> PlaneData {
306        if self.origin.is_zero() {
307            match self {
308                Self {
309                    origin:
310                        Point3d {
311                            x: 0.0,
312                            y: 0.0,
313                            z: 0.0,
314                            units: UnitLen::Mm,
315                        },
316                    x_axis:
317                        Point3d {
318                            x: 1.0,
319                            y: 0.0,
320                            z: 0.0,
321                            units: _,
322                        },
323                    y_axis:
324                        Point3d {
325                            x: 0.0,
326                            y: 1.0,
327                            z: 0.0,
328                            units: _,
329                        },
330                } => return PlaneData::XY,
331                Self {
332                    origin:
333                        Point3d {
334                            x: 0.0,
335                            y: 0.0,
336                            z: 0.0,
337                            units: UnitLen::Mm,
338                        },
339                    x_axis:
340                        Point3d {
341                            x: -1.0,
342                            y: 0.0,
343                            z: 0.0,
344                            units: _,
345                        },
346                    y_axis:
347                        Point3d {
348                            x: 0.0,
349                            y: 1.0,
350                            z: 0.0,
351                            units: _,
352                        },
353                } => return PlaneData::NegXY,
354                Self {
355                    origin:
356                        Point3d {
357                            x: 0.0,
358                            y: 0.0,
359                            z: 0.0,
360                            units: UnitLen::Mm,
361                        },
362                    x_axis:
363                        Point3d {
364                            x: 1.0,
365                            y: 0.0,
366                            z: 0.0,
367                            units: _,
368                        },
369                    y_axis:
370                        Point3d {
371                            x: 0.0,
372                            y: 0.0,
373                            z: 1.0,
374                            units: _,
375                        },
376                } => return PlaneData::XZ,
377                Self {
378                    origin:
379                        Point3d {
380                            x: 0.0,
381                            y: 0.0,
382                            z: 0.0,
383                            units: UnitLen::Mm,
384                        },
385                    x_axis:
386                        Point3d {
387                            x: -1.0,
388                            y: 0.0,
389                            z: 0.0,
390                            units: _,
391                        },
392                    y_axis:
393                        Point3d {
394                            x: 0.0,
395                            y: 0.0,
396                            z: 1.0,
397                            units: _,
398                        },
399                } => return PlaneData::NegXZ,
400                Self {
401                    origin:
402                        Point3d {
403                            x: 0.0,
404                            y: 0.0,
405                            z: 0.0,
406                            units: UnitLen::Mm,
407                        },
408                    x_axis:
409                        Point3d {
410                            x: 0.0,
411                            y: 1.0,
412                            z: 0.0,
413                            units: _,
414                        },
415                    y_axis:
416                        Point3d {
417                            x: 0.0,
418                            y: 0.0,
419                            z: 1.0,
420                            units: _,
421                        },
422                } => return PlaneData::YZ,
423                Self {
424                    origin:
425                        Point3d {
426                            x: 0.0,
427                            y: 0.0,
428                            z: 0.0,
429                            units: UnitLen::Mm,
430                        },
431                    x_axis:
432                        Point3d {
433                            x: 0.0,
434                            y: -1.0,
435                            z: 0.0,
436                            units: _,
437                        },
438                    y_axis:
439                        Point3d {
440                            x: 0.0,
441                            y: 0.0,
442                            z: 1.0,
443                            units: _,
444                        },
445                } => return PlaneData::NegYZ,
446                _ => {}
447            }
448        }
449
450        PlaneData::Plane(Self {
451            origin: self.origin,
452            x_axis: self.x_axis,
453            y_axis: self.y_axis,
454        })
455    }
456}
457
458impl TryFrom<PlaneData> for PlaneInfo {
459    type Error = KclError;
460
461    fn try_from(value: PlaneData) -> Result<Self, Self::Error> {
462        if let PlaneData::Plane(info) = value {
463            return Ok(info);
464        }
465        let name = match value {
466            PlaneData::XY => PlaneName::Xy,
467            PlaneData::NegXY => PlaneName::NegXy,
468            PlaneData::XZ => PlaneName::Xz,
469            PlaneData::NegXZ => PlaneName::NegXz,
470            PlaneData::YZ => PlaneName::Yz,
471            PlaneData::NegYZ => PlaneName::NegYz,
472            PlaneData::Plane(_) => {
473                return Err(KclError::Internal(KclErrorDetails {
475                    message: format!("PlaneData {:?} not found", value),
476                    source_ranges: Default::default(),
477                }));
478            }
479        };
480
481        let info = DEFAULT_PLANE_INFO.get(&name).ok_or_else(|| {
482            KclError::Internal(KclErrorDetails {
483                message: format!("Plane {} not found", name),
484                source_ranges: Default::default(),
485            })
486        })?;
487
488        Ok(info.clone())
489    }
490}
491
492impl From<PlaneData> for PlaneType {
493    fn from(value: PlaneData) -> Self {
494        match value {
495            PlaneData::XY => PlaneType::XY,
496            PlaneData::NegXY => PlaneType::XY,
497            PlaneData::XZ => PlaneType::XZ,
498            PlaneData::NegXZ => PlaneType::XZ,
499            PlaneData::YZ => PlaneType::YZ,
500            PlaneData::NegYZ => PlaneType::YZ,
501            PlaneData::Plane(_) => PlaneType::Custom,
502        }
503    }
504}
505
506impl Plane {
507    pub(crate) fn from_plane_data(value: PlaneData, exec_state: &mut ExecState) -> Result<Self, KclError> {
508        let id = exec_state.next_uuid();
509        Ok(Plane {
510            id,
511            #[cfg(feature = "artifact-graph")]
512            artifact_id: id.into(),
513            info: PlaneInfo::try_from(value.clone())?,
514            value: value.into(),
515            meta: vec![],
516        })
517    }
518
519    pub fn is_standard(&self) -> bool {
521        !matches!(self.value, PlaneType::Custom | PlaneType::Uninit)
522    }
523}
524
525#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
527#[ts(export)]
528#[serde(rename_all = "camelCase")]
529pub struct Face {
530    pub id: uuid::Uuid,
532    #[cfg(feature = "artifact-graph")]
534    pub artifact_id: ArtifactId,
535    pub value: String,
537    pub x_axis: Point3d,
539    pub y_axis: Point3d,
541    pub solid: Box<Solid>,
543    pub units: UnitLen,
544    #[serde(skip)]
545    pub meta: Vec<Metadata>,
546}
547
548#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema, FromStr, Display)]
550#[ts(export)]
551#[display(style = "camelCase")]
552pub enum PlaneType {
553    #[serde(rename = "XY", alias = "xy")]
554    #[display("XY")]
555    XY,
556    #[serde(rename = "XZ", alias = "xz")]
557    #[display("XZ")]
558    XZ,
559    #[serde(rename = "YZ", alias = "yz")]
560    #[display("YZ")]
561    YZ,
562    #[display("Custom")]
564    Custom,
565    #[display("Uninit")]
567    Uninit,
568}
569
570#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
571#[ts(export)]
572#[serde(tag = "type", rename_all = "camelCase")]
573pub struct Sketch {
574    pub id: uuid::Uuid,
576    pub paths: Vec<Path>,
578    pub on: SketchSurface,
580    pub start: BasePath,
582    #[serde(default, skip_serializing_if = "IndexMap::is_empty")]
584    pub tags: IndexMap<String, TagIdentifier>,
585    #[cfg(feature = "artifact-graph")]
588    pub artifact_id: ArtifactId,
589    #[ts(skip)]
590    pub original_id: uuid::Uuid,
591    #[serde(skip)]
593    pub mirror: Option<uuid::Uuid>,
594    pub units: UnitLen,
595    #[serde(skip)]
597    pub meta: Vec<Metadata>,
598}
599
600impl Sketch {
601    pub(crate) fn build_sketch_mode_cmds(
604        &self,
605        exec_state: &mut ExecState,
606        inner_cmd: ModelingCmdReq,
607    ) -> Vec<ModelingCmdReq> {
608        vec![
609            ModelingCmdReq {
612                cmd: ModelingCmd::from(mcmd::EnableSketchMode {
613                    animated: false,
614                    ortho: false,
615                    entity_id: self.on.id(),
616                    adjust_camera: false,
617                    planar_normal: if let SketchSurface::Plane(plane) = &self.on {
618                        let normal = plane.info.x_axis.axes_cross_product(&plane.info.y_axis);
620                        Some(normal.into())
621                    } else {
622                        None
623                    },
624                }),
625                cmd_id: exec_state.next_uuid().into(),
626            },
627            inner_cmd,
628            ModelingCmdReq {
629                cmd: ModelingCmd::SketchModeDisable(mcmd::SketchModeDisable::default()),
630                cmd_id: exec_state.next_uuid().into(),
631            },
632        ]
633    }
634}
635
636#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
638#[ts(export)]
639#[serde(tag = "type", rename_all = "camelCase")]
640pub enum SketchSurface {
641    Plane(Box<Plane>),
642    Face(Box<Face>),
643}
644
645impl SketchSurface {
646    pub(crate) fn id(&self) -> uuid::Uuid {
647        match self {
648            SketchSurface::Plane(plane) => plane.id,
649            SketchSurface::Face(face) => face.id,
650        }
651    }
652    pub(crate) fn x_axis(&self) -> Point3d {
653        match self {
654            SketchSurface::Plane(plane) => plane.info.x_axis,
655            SketchSurface::Face(face) => face.x_axis,
656        }
657    }
658    pub(crate) fn y_axis(&self) -> Point3d {
659        match self {
660            SketchSurface::Plane(plane) => plane.info.y_axis,
661            SketchSurface::Face(face) => face.y_axis,
662        }
663    }
664}
665
666#[derive(Debug, Clone)]
667pub(crate) enum GetTangentialInfoFromPathsResult {
668    PreviousPoint([f64; 2]),
669    Arc { center: [f64; 2], ccw: bool },
670    Circle { center: [f64; 2], ccw: bool, radius: f64 },
671}
672
673impl GetTangentialInfoFromPathsResult {
674    pub(crate) fn tan_previous_point(&self, last_arc_end: [f64; 2]) -> [f64; 2] {
675        match self {
676            GetTangentialInfoFromPathsResult::PreviousPoint(p) => *p,
677            GetTangentialInfoFromPathsResult::Arc { center, ccw } => {
678                crate::std::utils::get_tangent_point_from_previous_arc(*center, *ccw, last_arc_end)
679            }
680            GetTangentialInfoFromPathsResult::Circle {
683                center, radius, ccw, ..
684            } => [center[0] + radius, center[1] + if *ccw { -1.0 } else { 1.0 }],
685        }
686    }
687}
688
689impl Sketch {
690    pub(crate) fn add_tag(&mut self, tag: NodeRef<'_, TagDeclarator>, current_path: &Path, exec_state: &ExecState) {
691        let mut tag_identifier: TagIdentifier = tag.into();
692        let base = current_path.get_base();
693        tag_identifier.info.push((
694            exec_state.stack().current_epoch(),
695            TagEngineInfo {
696                id: base.geo_meta.id,
697                sketch: self.id,
698                path: Some(current_path.clone()),
699                surface: None,
700            },
701        ));
702
703        self.tags.insert(tag.name.to_string(), tag_identifier);
704    }
705
706    pub(crate) fn merge_tags<'a>(&mut self, tags: impl Iterator<Item = &'a TagIdentifier>) {
707        for t in tags {
708            match self.tags.get_mut(&t.value) {
709                Some(id) => {
710                    id.merge_info(t);
711                }
712                None => {
713                    self.tags.insert(t.value.clone(), t.clone());
714                }
715            }
716        }
717    }
718
719    pub(crate) fn latest_path(&self) -> Option<&Path> {
721        self.paths.last()
722    }
723
724    pub(crate) fn current_pen_position(&self) -> Result<Point2d, KclError> {
728        let Some(path) = self.latest_path() else {
729            return Ok(Point2d::new(self.start.to[0], self.start.to[1], self.start.units));
730        };
731
732        let to = path.get_base().to;
733        Ok(Point2d::new(to[0], to[1], path.get_base().units))
734    }
735
736    pub(crate) fn get_tangential_info_from_paths(&self) -> GetTangentialInfoFromPathsResult {
737        let Some(path) = self.latest_path() else {
738            return GetTangentialInfoFromPathsResult::PreviousPoint(self.start.to);
739        };
740        path.get_tangential_info()
741    }
742}
743
744#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
745#[ts(export)]
746#[serde(tag = "type", rename_all = "camelCase")]
747pub struct Solid {
748    pub id: uuid::Uuid,
750    #[cfg(feature = "artifact-graph")]
752    pub artifact_id: ArtifactId,
753    pub value: Vec<ExtrudeSurface>,
755    pub sketch: Sketch,
757    pub height: f64,
759    pub start_cap_id: Option<uuid::Uuid>,
761    pub end_cap_id: Option<uuid::Uuid>,
763    #[serde(default, skip_serializing_if = "Vec::is_empty")]
765    pub edge_cuts: Vec<EdgeCut>,
766    pub units: UnitLen,
768    pub sectional: bool,
770    #[serde(skip)]
772    pub meta: Vec<Metadata>,
773}
774
775impl Solid {
776    pub(crate) fn get_all_edge_cut_ids(&self) -> impl Iterator<Item = uuid::Uuid> + '_ {
777        self.edge_cuts.iter().map(|foc| foc.id())
778    }
779
780    pub(crate) fn height_in_mm(&self) -> f64 {
781        self.units.adjust_to(self.height, UnitLen::Mm).0
782    }
783}
784
785#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
787#[ts(export)]
788#[serde(tag = "type", rename_all = "camelCase")]
789pub enum EdgeCut {
790    Fillet {
792        id: uuid::Uuid,
794        radius: TyF64,
795        #[serde(rename = "edgeId")]
797        edge_id: uuid::Uuid,
798        tag: Box<Option<TagNode>>,
799    },
800    Chamfer {
802        id: uuid::Uuid,
804        length: TyF64,
805        #[serde(rename = "edgeId")]
807        edge_id: uuid::Uuid,
808        tag: Box<Option<TagNode>>,
809    },
810}
811
812impl EdgeCut {
813    pub fn id(&self) -> uuid::Uuid {
814        match self {
815            EdgeCut::Fillet { id, .. } => *id,
816            EdgeCut::Chamfer { id, .. } => *id,
817        }
818    }
819
820    pub fn set_id(&mut self, id: uuid::Uuid) {
821        match self {
822            EdgeCut::Fillet { id: ref mut i, .. } => *i = id,
823            EdgeCut::Chamfer { id: ref mut i, .. } => *i = id,
824        }
825    }
826
827    pub fn edge_id(&self) -> uuid::Uuid {
828        match self {
829            EdgeCut::Fillet { edge_id, .. } => *edge_id,
830            EdgeCut::Chamfer { edge_id, .. } => *edge_id,
831        }
832    }
833
834    pub fn set_edge_id(&mut self, id: uuid::Uuid) {
835        match self {
836            EdgeCut::Fillet { edge_id: ref mut i, .. } => *i = id,
837            EdgeCut::Chamfer { edge_id: ref mut i, .. } => *i = id,
838        }
839    }
840
841    pub fn tag(&self) -> Option<TagNode> {
842        match self {
843            EdgeCut::Fillet { tag, .. } => *tag.clone(),
844            EdgeCut::Chamfer { tag, .. } => *tag.clone(),
845        }
846    }
847}
848
849#[derive(Debug, Deserialize, Serialize, PartialEq, Clone, Copy, ts_rs::TS, JsonSchema)]
850#[ts(export)]
851pub struct Point2d {
852    pub x: f64,
853    pub y: f64,
854    pub units: UnitLen,
855}
856
857impl Point2d {
858    pub const ZERO: Self = Self {
859        x: 0.0,
860        y: 0.0,
861        units: UnitLen::Mm,
862    };
863
864    pub fn new(x: f64, y: f64, units: UnitLen) -> Self {
865        Self { x, y, units }
866    }
867
868    pub fn into_x(self) -> TyF64 {
869        TyF64::new(self.x, self.units.into())
870    }
871
872    pub fn into_y(self) -> TyF64 {
873        TyF64::new(self.y, self.units.into())
874    }
875
876    pub fn ignore_units(self) -> [f64; 2] {
877        [self.x, self.y]
878    }
879}
880
881#[derive(Debug, Deserialize, Serialize, PartialEq, Clone, Copy, ts_rs::TS, JsonSchema, Default)]
882#[ts(export)]
883pub struct Point3d {
884    pub x: f64,
885    pub y: f64,
886    pub z: f64,
887    pub units: UnitLen,
888}
889
890impl Point3d {
891    pub const ZERO: Self = Self {
892        x: 0.0,
893        y: 0.0,
894        z: 0.0,
895        units: UnitLen::Mm,
896    };
897
898    pub fn new(x: f64, y: f64, z: f64, units: UnitLen) -> Self {
899        Self { x, y, z, units }
900    }
901
902    pub const fn is_zero(&self) -> bool {
903        self.x == 0.0 && self.y == 0.0 && self.z == 0.0
904    }
905
906    pub fn axes_cross_product(&self, other: &Self) -> Self {
911        Self {
912            x: self.y * other.z - self.z * other.y,
913            y: self.z * other.x - self.x * other.z,
914            z: self.x * other.y - self.y * other.x,
915            units: UnitLen::Unknown,
916        }
917    }
918
919    pub fn normalize(&self) -> Self {
920        let len = f64::sqrt(self.x * self.x + self.y * self.y + self.z * self.z);
921        Point3d {
922            x: self.x / len,
923            y: self.y / len,
924            z: self.z / len,
925            units: UnitLen::Unknown,
926        }
927    }
928}
929
930impl From<[TyF64; 3]> for Point3d {
931    fn from(p: [TyF64; 3]) -> Self {
932        Self {
933            x: p[0].n,
934            y: p[1].n,
935            z: p[2].n,
936            units: p[0].ty.expect_length(),
937        }
938    }
939}
940
941impl From<Point3d> for Point3D {
942    fn from(p: Point3d) -> Self {
943        Self { x: p.x, y: p.y, z: p.z }
944    }
945}
946impl From<Point3d> for kittycad_modeling_cmds::shared::Point3d<LengthUnit> {
947    fn from(p: Point3d) -> Self {
948        Self {
949            x: LengthUnit(p.units.adjust_to(p.x, UnitLen::Mm).0),
950            y: LengthUnit(p.units.adjust_to(p.y, UnitLen::Mm).0),
951            z: LengthUnit(p.units.adjust_to(p.z, UnitLen::Mm).0),
952        }
953    }
954}
955
956impl Add for Point3d {
957    type Output = Point3d;
958
959    fn add(self, rhs: Self) -> Self::Output {
960        Point3d {
962            x: self.x + rhs.x,
963            y: self.y + rhs.y,
964            z: self.z + rhs.z,
965            units: self.units,
966        }
967    }
968}
969
970impl AddAssign for Point3d {
971    fn add_assign(&mut self, rhs: Self) {
972        *self = *self + rhs
973    }
974}
975
976impl Mul<f64> for Point3d {
977    type Output = Point3d;
978
979    fn mul(self, rhs: f64) -> Self::Output {
980        Point3d {
981            x: self.x * rhs,
982            y: self.y * rhs,
983            z: self.z * rhs,
984            units: self.units,
985        }
986    }
987}
988
989#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
991#[ts(export)]
992#[serde(rename_all = "camelCase")]
993pub struct BasePath {
994    #[ts(type = "[number, number]")]
996    pub from: [f64; 2],
997    #[ts(type = "[number, number]")]
999    pub to: [f64; 2],
1000    pub units: UnitLen,
1001    pub tag: Option<TagNode>,
1003    #[serde(rename = "__geoMeta")]
1005    pub geo_meta: GeoMeta,
1006}
1007
1008impl BasePath {
1009    pub fn get_to(&self) -> [TyF64; 2] {
1010        let ty: NumericType = self.units.into();
1011        [TyF64::new(self.to[0], ty.clone()), TyF64::new(self.to[1], ty)]
1012    }
1013
1014    pub fn get_from(&self) -> [TyF64; 2] {
1015        let ty: NumericType = self.units.into();
1016        [TyF64::new(self.from[0], ty.clone()), TyF64::new(self.from[1], ty)]
1017    }
1018}
1019
1020#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
1022#[ts(export)]
1023#[serde(rename_all = "camelCase")]
1024pub struct GeoMeta {
1025    pub id: uuid::Uuid,
1027    #[serde(flatten)]
1029    pub metadata: Metadata,
1030}
1031
1032#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
1034#[ts(export)]
1035#[serde(tag = "type")]
1036pub enum Path {
1037    ToPoint {
1039        #[serde(flatten)]
1040        base: BasePath,
1041    },
1042    TangentialArcTo {
1044        #[serde(flatten)]
1045        base: BasePath,
1046        #[ts(type = "[number, number]")]
1048        center: [f64; 2],
1049        ccw: bool,
1051    },
1052    TangentialArc {
1054        #[serde(flatten)]
1055        base: BasePath,
1056        #[ts(type = "[number, number]")]
1058        center: [f64; 2],
1059        ccw: bool,
1061    },
1062    Circle {
1065        #[serde(flatten)]
1066        base: BasePath,
1067        #[ts(type = "[number, number]")]
1069        center: [f64; 2],
1070        radius: f64,
1072        ccw: bool,
1075    },
1076    CircleThreePoint {
1077        #[serde(flatten)]
1078        base: BasePath,
1079        #[ts(type = "[number, number]")]
1081        p1: [f64; 2],
1082        #[ts(type = "[number, number]")]
1084        p2: [f64; 2],
1085        #[ts(type = "[number, number]")]
1087        p3: [f64; 2],
1088    },
1089    ArcThreePoint {
1090        #[serde(flatten)]
1091        base: BasePath,
1092        #[ts(type = "[number, number]")]
1094        p1: [f64; 2],
1095        #[ts(type = "[number, number]")]
1097        p2: [f64; 2],
1098        #[ts(type = "[number, number]")]
1100        p3: [f64; 2],
1101    },
1102    Horizontal {
1104        #[serde(flatten)]
1105        base: BasePath,
1106        x: f64,
1108    },
1109    AngledLineTo {
1111        #[serde(flatten)]
1112        base: BasePath,
1113        x: Option<f64>,
1115        y: Option<f64>,
1117    },
1118    Base {
1120        #[serde(flatten)]
1121        base: BasePath,
1122    },
1123    Arc {
1125        #[serde(flatten)]
1126        base: BasePath,
1127        center: [f64; 2],
1129        radius: f64,
1131        ccw: bool,
1133    },
1134}
1135
1136#[derive(Display)]
1138enum PathType {
1139    ToPoint,
1140    Base,
1141    TangentialArc,
1142    TangentialArcTo,
1143    Circle,
1144    CircleThreePoint,
1145    Horizontal,
1146    AngledLineTo,
1147    Arc,
1148}
1149
1150impl From<&Path> for PathType {
1151    fn from(value: &Path) -> Self {
1152        match value {
1153            Path::ToPoint { .. } => Self::ToPoint,
1154            Path::TangentialArcTo { .. } => Self::TangentialArcTo,
1155            Path::TangentialArc { .. } => Self::TangentialArc,
1156            Path::Circle { .. } => Self::Circle,
1157            Path::CircleThreePoint { .. } => Self::CircleThreePoint,
1158            Path::Horizontal { .. } => Self::Horizontal,
1159            Path::AngledLineTo { .. } => Self::AngledLineTo,
1160            Path::Base { .. } => Self::Base,
1161            Path::Arc { .. } => Self::Arc,
1162            Path::ArcThreePoint { .. } => Self::Arc,
1163        }
1164    }
1165}
1166
1167impl Path {
1168    pub fn get_id(&self) -> uuid::Uuid {
1169        match self {
1170            Path::ToPoint { base } => base.geo_meta.id,
1171            Path::Horizontal { base, .. } => base.geo_meta.id,
1172            Path::AngledLineTo { base, .. } => base.geo_meta.id,
1173            Path::Base { base } => base.geo_meta.id,
1174            Path::TangentialArcTo { base, .. } => base.geo_meta.id,
1175            Path::TangentialArc { base, .. } => base.geo_meta.id,
1176            Path::Circle { base, .. } => base.geo_meta.id,
1177            Path::CircleThreePoint { base, .. } => base.geo_meta.id,
1178            Path::Arc { base, .. } => base.geo_meta.id,
1179            Path::ArcThreePoint { base, .. } => base.geo_meta.id,
1180        }
1181    }
1182
1183    pub fn set_id(&mut self, id: uuid::Uuid) {
1184        match self {
1185            Path::ToPoint { base } => base.geo_meta.id = id,
1186            Path::Horizontal { base, .. } => base.geo_meta.id = id,
1187            Path::AngledLineTo { base, .. } => base.geo_meta.id = id,
1188            Path::Base { base } => base.geo_meta.id = id,
1189            Path::TangentialArcTo { base, .. } => base.geo_meta.id = id,
1190            Path::TangentialArc { base, .. } => base.geo_meta.id = id,
1191            Path::Circle { base, .. } => base.geo_meta.id = id,
1192            Path::CircleThreePoint { base, .. } => base.geo_meta.id = id,
1193            Path::Arc { base, .. } => base.geo_meta.id = id,
1194            Path::ArcThreePoint { base, .. } => base.geo_meta.id = id,
1195        }
1196    }
1197
1198    pub fn get_tag(&self) -> Option<TagNode> {
1199        match self {
1200            Path::ToPoint { base } => base.tag.clone(),
1201            Path::Horizontal { base, .. } => base.tag.clone(),
1202            Path::AngledLineTo { base, .. } => base.tag.clone(),
1203            Path::Base { base } => base.tag.clone(),
1204            Path::TangentialArcTo { base, .. } => base.tag.clone(),
1205            Path::TangentialArc { base, .. } => base.tag.clone(),
1206            Path::Circle { base, .. } => base.tag.clone(),
1207            Path::CircleThreePoint { base, .. } => base.tag.clone(),
1208            Path::Arc { base, .. } => base.tag.clone(),
1209            Path::ArcThreePoint { base, .. } => base.tag.clone(),
1210        }
1211    }
1212
1213    pub fn get_base(&self) -> &BasePath {
1214        match self {
1215            Path::ToPoint { base } => base,
1216            Path::Horizontal { base, .. } => base,
1217            Path::AngledLineTo { base, .. } => base,
1218            Path::Base { base } => base,
1219            Path::TangentialArcTo { base, .. } => base,
1220            Path::TangentialArc { base, .. } => base,
1221            Path::Circle { base, .. } => base,
1222            Path::CircleThreePoint { base, .. } => base,
1223            Path::Arc { base, .. } => base,
1224            Path::ArcThreePoint { base, .. } => base,
1225        }
1226    }
1227
1228    pub fn get_from(&self) -> [TyF64; 2] {
1230        let p = &self.get_base().from;
1231        let ty: NumericType = self.get_base().units.into();
1232        [TyF64::new(p[0], ty.clone()), TyF64::new(p[1], ty)]
1233    }
1234
1235    pub fn get_to(&self) -> [TyF64; 2] {
1237        let p = &self.get_base().to;
1238        let ty: NumericType = self.get_base().units.into();
1239        [TyF64::new(p[0], ty.clone()), TyF64::new(p[1], ty)]
1240    }
1241
1242    pub fn start_point_components(&self) -> ([f64; 2], NumericType) {
1244        let p = &self.get_base().from;
1245        let ty: NumericType = self.get_base().units.into();
1246        (*p, ty)
1247    }
1248
1249    pub fn end_point_components(&self) -> ([f64; 2], NumericType) {
1251        let p = &self.get_base().to;
1252        let ty: NumericType = self.get_base().units.into();
1253        (*p, ty)
1254    }
1255
1256    pub fn length(&self) -> TyF64 {
1258        let n = match self {
1259            Self::ToPoint { .. } | Self::Base { .. } | Self::Horizontal { .. } | Self::AngledLineTo { .. } => {
1260                linear_distance(&self.get_base().from, &self.get_base().to)
1261            }
1262            Self::TangentialArc {
1263                base: _,
1264                center,
1265                ccw: _,
1266            }
1267            | Self::TangentialArcTo {
1268                base: _,
1269                center,
1270                ccw: _,
1271            } => {
1272                let radius = linear_distance(&self.get_base().from, center);
1275                debug_assert_eq!(radius, linear_distance(&self.get_base().to, center));
1276                linear_distance(&self.get_base().from, &self.get_base().to)
1278            }
1279            Self::Circle { radius, .. } => 2.0 * std::f64::consts::PI * radius,
1280            Self::CircleThreePoint { .. } => {
1281                let circle_center = crate::std::utils::calculate_circle_from_3_points([
1282                    self.get_base().from,
1283                    self.get_base().to,
1284                    self.get_base().to,
1285                ]);
1286                let radius = linear_distance(
1287                    &[circle_center.center[0], circle_center.center[1]],
1288                    &self.get_base().from,
1289                );
1290                2.0 * std::f64::consts::PI * radius
1291            }
1292            Self::Arc { .. } => {
1293                linear_distance(&self.get_base().from, &self.get_base().to)
1295            }
1296            Self::ArcThreePoint { .. } => {
1297                linear_distance(&self.get_base().from, &self.get_base().to)
1299            }
1300        };
1301        TyF64::new(n, self.get_base().units.into())
1302    }
1303
1304    pub fn get_base_mut(&mut self) -> Option<&mut BasePath> {
1305        match self {
1306            Path::ToPoint { base } => Some(base),
1307            Path::Horizontal { base, .. } => Some(base),
1308            Path::AngledLineTo { base, .. } => Some(base),
1309            Path::Base { base } => Some(base),
1310            Path::TangentialArcTo { base, .. } => Some(base),
1311            Path::TangentialArc { base, .. } => Some(base),
1312            Path::Circle { base, .. } => Some(base),
1313            Path::CircleThreePoint { base, .. } => Some(base),
1314            Path::Arc { base, .. } => Some(base),
1315            Path::ArcThreePoint { base, .. } => Some(base),
1316        }
1317    }
1318
1319    pub(crate) fn get_tangential_info(&self) -> GetTangentialInfoFromPathsResult {
1320        match self {
1321            Path::TangentialArc { center, ccw, .. }
1322            | Path::TangentialArcTo { center, ccw, .. }
1323            | Path::Arc { center, ccw, .. } => GetTangentialInfoFromPathsResult::Arc {
1324                center: *center,
1325                ccw: *ccw,
1326            },
1327            Path::ArcThreePoint { p1, p2, p3, .. } => {
1328                let circle = crate::std::utils::calculate_circle_from_3_points([*p1, *p2, *p3]);
1329                GetTangentialInfoFromPathsResult::Arc {
1330                    center: circle.center,
1331                    ccw: crate::std::utils::is_points_ccw(&[*p1, *p2, *p3]) > 0,
1332                }
1333            }
1334            Path::Circle {
1335                center, ccw, radius, ..
1336            } => GetTangentialInfoFromPathsResult::Circle {
1337                center: *center,
1338                ccw: *ccw,
1339                radius: *radius,
1340            },
1341            Path::CircleThreePoint { p1, p2, p3, .. } => {
1342                let circle = crate::std::utils::calculate_circle_from_3_points([*p1, *p2, *p3]);
1343                let center_point = [circle.center[0], circle.center[1]];
1344                GetTangentialInfoFromPathsResult::Circle {
1345                    center: center_point,
1346                    ccw: true,
1348                    radius: circle.radius,
1349                }
1350            }
1351            Path::ToPoint { .. } | Path::Horizontal { .. } | Path::AngledLineTo { .. } | Path::Base { .. } => {
1352                let base = self.get_base();
1353                GetTangentialInfoFromPathsResult::PreviousPoint(base.from)
1354            }
1355        }
1356    }
1357}
1358
1359#[rustfmt::skip]
1361fn linear_distance(
1362    [x0, y0]: &[f64; 2],
1363    [x1, y1]: &[f64; 2]
1364) -> f64 {
1365    let y_sq = (y1 - y0).powi(2);
1366    let x_sq = (x1 - x0).powi(2);
1367    (y_sq + x_sq).sqrt()
1368}
1369
1370#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
1372#[ts(export)]
1373#[serde(tag = "type", rename_all = "camelCase")]
1374pub enum ExtrudeSurface {
1375    ExtrudePlane(ExtrudePlane),
1377    ExtrudeArc(ExtrudeArc),
1378    Chamfer(ChamferSurface),
1379    Fillet(FilletSurface),
1380}
1381
1382#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
1384#[ts(export)]
1385#[serde(rename_all = "camelCase")]
1386pub struct ChamferSurface {
1387    pub face_id: uuid::Uuid,
1389    pub tag: Option<Node<TagDeclarator>>,
1391    #[serde(flatten)]
1393    pub geo_meta: GeoMeta,
1394}
1395
1396#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
1398#[ts(export)]
1399#[serde(rename_all = "camelCase")]
1400pub struct FilletSurface {
1401    pub face_id: uuid::Uuid,
1403    pub tag: Option<Node<TagDeclarator>>,
1405    #[serde(flatten)]
1407    pub geo_meta: GeoMeta,
1408}
1409
1410#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
1412#[ts(export)]
1413#[serde(rename_all = "camelCase")]
1414pub struct ExtrudePlane {
1415    pub face_id: uuid::Uuid,
1417    pub tag: Option<Node<TagDeclarator>>,
1419    #[serde(flatten)]
1421    pub geo_meta: GeoMeta,
1422}
1423
1424#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
1426#[ts(export)]
1427#[serde(rename_all = "camelCase")]
1428pub struct ExtrudeArc {
1429    pub face_id: uuid::Uuid,
1431    pub tag: Option<Node<TagDeclarator>>,
1433    #[serde(flatten)]
1435    pub geo_meta: GeoMeta,
1436}
1437
1438impl ExtrudeSurface {
1439    pub fn get_id(&self) -> uuid::Uuid {
1440        match self {
1441            ExtrudeSurface::ExtrudePlane(ep) => ep.geo_meta.id,
1442            ExtrudeSurface::ExtrudeArc(ea) => ea.geo_meta.id,
1443            ExtrudeSurface::Fillet(f) => f.geo_meta.id,
1444            ExtrudeSurface::Chamfer(c) => c.geo_meta.id,
1445        }
1446    }
1447
1448    pub fn get_tag(&self) -> Option<Node<TagDeclarator>> {
1449        match self {
1450            ExtrudeSurface::ExtrudePlane(ep) => ep.tag.clone(),
1451            ExtrudeSurface::ExtrudeArc(ea) => ea.tag.clone(),
1452            ExtrudeSurface::Fillet(f) => f.tag.clone(),
1453            ExtrudeSurface::Chamfer(c) => c.tag.clone(),
1454        }
1455    }
1456}