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
11use crate::{
12 errors::KclError,
13 execution::{types::NumericType, ArtifactId, ExecState, Metadata, TagEngineInfo, TagIdentifier, UnitLen},
14 parsing::ast::types::{Node, NodeRef, TagDeclarator, TagNode},
15 std::{args::TyF64, sketch::PlaneData},
16};
17
18type Point2D = kcmc::shared::Point2d<f64>;
19type Point3D = kcmc::shared::Point3d<f64>;
20
21#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
23#[ts(export)]
24#[serde(tag = "type")]
25pub enum Geometry {
26 Sketch(Sketch),
27 Solid(Solid),
28}
29
30impl Geometry {
31 pub fn id(&self) -> uuid::Uuid {
32 match self {
33 Geometry::Sketch(s) => s.id,
34 Geometry::Solid(e) => e.id,
35 }
36 }
37
38 pub fn original_id(&self) -> uuid::Uuid {
42 match self {
43 Geometry::Sketch(s) => s.original_id,
44 Geometry::Solid(e) => e.sketch.original_id,
45 }
46 }
47}
48
49#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
51#[ts(export)]
52#[serde(tag = "type")]
53#[allow(clippy::vec_box)]
54pub enum Geometries {
55 Sketches(Vec<Sketch>),
56 Solids(Vec<Solid>),
57}
58
59impl From<Geometry> for Geometries {
60 fn from(value: Geometry) -> Self {
61 match value {
62 Geometry::Sketch(x) => Self::Sketches(vec![x]),
63 Geometry::Solid(x) => Self::Solids(vec![x]),
64 }
65 }
66}
67
68#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
70#[ts(export)]
71#[serde(rename_all = "camelCase")]
72pub struct ImportedGeometry {
73 pub id: uuid::Uuid,
75 pub value: Vec<String>,
77 #[serde(skip)]
78 pub meta: Vec<Metadata>,
79}
80
81#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
83#[ts(export)]
84#[serde(tag = "type", rename_all = "camelCase")]
85#[allow(clippy::vec_box)]
86pub enum SolidOrSketchOrImportedGeometry {
87 ImportedGeometry(Box<ImportedGeometry>),
88 SolidSet(Vec<Solid>),
89 SketchSet(Vec<Sketch>),
90}
91
92impl From<SolidOrSketchOrImportedGeometry> for crate::execution::KclValue {
93 fn from(value: SolidOrSketchOrImportedGeometry) -> Self {
94 match value {
95 SolidOrSketchOrImportedGeometry::ImportedGeometry(s) => crate::execution::KclValue::ImportedGeometry(*s),
96 SolidOrSketchOrImportedGeometry::SolidSet(mut s) => {
97 if s.len() == 1 {
98 crate::execution::KclValue::Solid {
99 value: Box::new(s.pop().unwrap()),
100 }
101 } else {
102 crate::execution::KclValue::HomArray {
103 value: s
104 .into_iter()
105 .map(|s| crate::execution::KclValue::Solid { value: Box::new(s) })
106 .collect(),
107 ty: crate::execution::types::RuntimeType::solid(),
108 }
109 }
110 }
111 SolidOrSketchOrImportedGeometry::SketchSet(mut s) => {
112 if s.len() == 1 {
113 crate::execution::KclValue::Sketch {
114 value: Box::new(s.pop().unwrap()),
115 }
116 } else {
117 crate::execution::KclValue::HomArray {
118 value: s
119 .into_iter()
120 .map(|s| crate::execution::KclValue::Sketch { value: Box::new(s) })
121 .collect(),
122 ty: crate::execution::types::RuntimeType::sketch(),
123 }
124 }
125 }
126 }
127 }
128}
129
130impl SolidOrSketchOrImportedGeometry {
131 pub(crate) fn ids(&self) -> Vec<uuid::Uuid> {
132 match self {
133 SolidOrSketchOrImportedGeometry::ImportedGeometry(s) => vec![s.id],
134 SolidOrSketchOrImportedGeometry::SolidSet(s) => s.iter().map(|s| s.id).collect(),
135 SolidOrSketchOrImportedGeometry::SketchSet(s) => s.iter().map(|s| s.id).collect(),
136 }
137 }
138}
139
140#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
142#[ts(export)]
143#[serde(rename_all = "camelCase")]
144pub struct Helix {
145 pub value: uuid::Uuid,
147 pub artifact_id: ArtifactId,
149 pub revolutions: f64,
151 pub angle_start: f64,
153 pub ccw: bool,
155 pub cylinder_id: Option<uuid::Uuid>,
157 pub units: UnitLen,
158 #[serde(skip)]
159 pub meta: Vec<Metadata>,
160}
161
162#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
163#[ts(export)]
164#[serde(rename_all = "camelCase")]
165pub struct Plane {
166 pub id: uuid::Uuid,
168 pub artifact_id: ArtifactId,
170 pub value: PlaneType,
172 pub origin: Point3d,
174 pub x_axis: Point3d,
176 pub y_axis: Point3d,
178 pub z_axis: Point3d,
180 pub units: UnitLen,
181 #[serde(skip)]
182 pub meta: Vec<Metadata>,
183}
184
185impl Plane {
186 pub(crate) fn into_plane_data(self) -> PlaneData {
187 if self.origin.is_zero() {
188 match self {
189 Self {
190 origin:
191 Point3d {
192 x: 0.0,
193 y: 0.0,
194 z: 0.0,
195 units: UnitLen::Mm,
196 },
197 x_axis:
198 Point3d {
199 x: 1.0,
200 y: 0.0,
201 z: 0.0,
202 units: UnitLen::Mm,
203 },
204 y_axis:
205 Point3d {
206 x: 0.0,
207 y: 1.0,
208 z: 0.0,
209 units: UnitLen::Mm,
210 },
211 z_axis:
212 Point3d {
213 x: 0.0,
214 y: 0.0,
215 z: 1.0,
216 units: UnitLen::Mm,
217 },
218 ..
219 } => return PlaneData::XY,
220 Self {
221 origin:
222 Point3d {
223 x: 0.0,
224 y: 0.0,
225 z: 0.0,
226 units: UnitLen::Mm,
227 },
228 x_axis:
229 Point3d {
230 x: 1.0,
231 y: 0.0,
232 z: 0.0,
233 units: UnitLen::Mm,
234 },
235 y_axis:
236 Point3d {
237 x: 0.0,
238 y: 1.0,
239 z: 0.0,
240 units: UnitLen::Mm,
241 },
242 z_axis:
243 Point3d {
244 x: 0.0,
245 y: 0.0,
246 z: -1.0,
247 units: UnitLen::Mm,
248 },
249 ..
250 } => return PlaneData::NegXY,
251 Self {
252 origin:
253 Point3d {
254 x: 0.0,
255 y: 0.0,
256 z: 0.0,
257 units: UnitLen::Mm,
258 },
259 x_axis:
260 Point3d {
261 x: 1.0,
262 y: 0.0,
263 z: 0.0,
264 units: UnitLen::Mm,
265 },
266 y_axis:
267 Point3d {
268 x: 0.0,
269 y: 0.0,
270 z: 1.0,
271 units: UnitLen::Mm,
272 },
273 z_axis:
274 Point3d {
275 x: 0.0,
276 y: -1.0,
277 z: 0.0,
278 units: UnitLen::Mm,
279 },
280 ..
281 } => return PlaneData::XZ,
282 Self {
283 origin:
284 Point3d {
285 x: 0.0,
286 y: 0.0,
287 z: 0.0,
288 units: UnitLen::Mm,
289 },
290 x_axis:
291 Point3d {
292 x: 1.0,
293 y: 0.0,
294 z: 0.0,
295 units: UnitLen::Mm,
296 },
297 y_axis:
298 Point3d {
299 x: 0.0,
300 y: 0.0,
301 z: 1.0,
302 units: UnitLen::Mm,
303 },
304 z_axis:
305 Point3d {
306 x: 0.0,
307 y: 1.0,
308 z: 0.0,
309 units: UnitLen::Mm,
310 },
311 ..
312 } => return PlaneData::NegXZ,
313 Self {
314 origin:
315 Point3d {
316 x: 0.0,
317 y: 0.0,
318 z: 0.0,
319 units: UnitLen::Mm,
320 },
321 x_axis:
322 Point3d {
323 x: 0.0,
324 y: 1.0,
325 z: 0.0,
326 units: UnitLen::Mm,
327 },
328 y_axis:
329 Point3d {
330 x: 0.0,
331 y: 0.0,
332 z: 1.0,
333 units: UnitLen::Mm,
334 },
335 z_axis:
336 Point3d {
337 x: 1.0,
338 y: 0.0,
339 z: 0.0,
340 units: UnitLen::Mm,
341 },
342 ..
343 } => return PlaneData::YZ,
344 Self {
345 origin:
346 Point3d {
347 x: 0.0,
348 y: 0.0,
349 z: 0.0,
350 units: UnitLen::Mm,
351 },
352 x_axis:
353 Point3d {
354 x: 0.0,
355 y: 1.0,
356 z: 0.0,
357 units: UnitLen::Mm,
358 },
359 y_axis:
360 Point3d {
361 x: 0.0,
362 y: 0.0,
363 z: 1.0,
364 units: UnitLen::Mm,
365 },
366 z_axis:
367 Point3d {
368 x: -1.0,
369 y: 0.0,
370 z: 0.0,
371 units: UnitLen::Mm,
372 },
373 ..
374 } => return PlaneData::NegYZ,
375 _ => {}
376 }
377 }
378
379 PlaneData::Plane {
380 origin: self.origin,
381 x_axis: self.x_axis,
382 y_axis: self.y_axis,
383 z_axis: self.z_axis,
384 }
385 }
386
387 pub(crate) fn from_plane_data(value: PlaneData, exec_state: &mut ExecState) -> Self {
388 let id = exec_state.next_uuid();
389 match value {
390 PlaneData::XY => Plane {
391 id,
392 artifact_id: id.into(),
393 origin: Point3d::new(0.0, 0.0, 0.0, UnitLen::Mm),
394 x_axis: Point3d::new(1.0, 0.0, 0.0, UnitLen::Mm),
395 y_axis: Point3d::new(0.0, 1.0, 0.0, UnitLen::Mm),
396 z_axis: Point3d::new(0.0, 0.0, 1.0, UnitLen::Mm),
397 value: PlaneType::XY,
398 units: exec_state.length_unit(),
399 meta: vec![],
400 },
401 PlaneData::NegXY => Plane {
402 id,
403 artifact_id: id.into(),
404 origin: Point3d::new(0.0, 0.0, 0.0, UnitLen::Mm),
405 x_axis: Point3d::new(1.0, 0.0, 0.0, UnitLen::Mm),
406 y_axis: Point3d::new(0.0, 1.0, 0.0, UnitLen::Mm),
407 z_axis: Point3d::new(0.0, 0.0, -1.0, UnitLen::Mm),
408 value: PlaneType::XY,
409 units: exec_state.length_unit(),
410 meta: vec![],
411 },
412 PlaneData::XZ => Plane {
413 id,
414 artifact_id: id.into(),
415 origin: Point3d::new(0.0, 0.0, 0.0, UnitLen::Mm),
416 x_axis: Point3d::new(1.0, 0.0, 0.0, UnitLen::Mm),
417 y_axis: Point3d::new(0.0, 0.0, 1.0, UnitLen::Mm),
418 z_axis: Point3d::new(0.0, -1.0, 0.0, UnitLen::Mm),
419 value: PlaneType::XZ,
420 units: exec_state.length_unit(),
421 meta: vec![],
422 },
423 PlaneData::NegXZ => Plane {
424 id,
425 artifact_id: id.into(),
426 origin: Point3d::new(0.0, 0.0, 0.0, UnitLen::Mm),
427 x_axis: Point3d::new(-1.0, 0.0, 0.0, UnitLen::Mm),
428 y_axis: Point3d::new(0.0, 0.0, 1.0, UnitLen::Mm),
429 z_axis: Point3d::new(0.0, 1.0, 0.0, UnitLen::Mm),
430 value: PlaneType::XZ,
431 units: exec_state.length_unit(),
432 meta: vec![],
433 },
434 PlaneData::YZ => Plane {
435 id,
436 artifact_id: id.into(),
437 origin: Point3d::new(0.0, 0.0, 0.0, UnitLen::Mm),
438 x_axis: Point3d::new(0.0, 1.0, 0.0, UnitLen::Mm),
439 y_axis: Point3d::new(0.0, 0.0, 1.0, UnitLen::Mm),
440 z_axis: Point3d::new(1.0, 0.0, 0.0, UnitLen::Mm),
441 value: PlaneType::YZ,
442 units: exec_state.length_unit(),
443 meta: vec![],
444 },
445 PlaneData::NegYZ => Plane {
446 id,
447 artifact_id: id.into(),
448 origin: Point3d::new(0.0, 0.0, 0.0, UnitLen::Mm),
449 x_axis: Point3d::new(0.0, 1.0, 0.0, UnitLen::Mm),
450 y_axis: Point3d::new(0.0, 0.0, 1.0, UnitLen::Mm),
451 z_axis: Point3d::new(-1.0, 0.0, 0.0, UnitLen::Mm),
452 value: PlaneType::YZ,
453 units: exec_state.length_unit(),
454 meta: vec![],
455 },
456 PlaneData::Plane {
457 origin,
458 x_axis,
459 y_axis,
460 z_axis,
461 } => {
462 let id = exec_state.next_uuid();
463 Plane {
464 id,
465 artifact_id: id.into(),
466 origin,
467 x_axis,
468 y_axis,
469 z_axis,
470 value: PlaneType::Custom,
471 units: exec_state.length_unit(),
472 meta: vec![],
473 }
474 }
475 }
476 }
477
478 pub fn is_standard(&self) -> bool {
480 !matches!(self.value, PlaneType::Custom | PlaneType::Uninit)
481 }
482}
483
484#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
486#[ts(export)]
487#[serde(rename_all = "camelCase")]
488pub struct Face {
489 pub id: uuid::Uuid,
491 pub artifact_id: ArtifactId,
493 pub value: String,
495 pub x_axis: Point3d,
497 pub y_axis: Point3d,
499 pub z_axis: Point3d,
501 pub solid: Box<Solid>,
503 pub units: UnitLen,
504 #[serde(skip)]
505 pub meta: Vec<Metadata>,
506}
507
508#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema, FromStr, Display)]
510#[ts(export)]
511#[display(style = "camelCase")]
512pub enum PlaneType {
513 #[serde(rename = "XY", alias = "xy")]
514 #[display("XY")]
515 XY,
516 #[serde(rename = "XZ", alias = "xz")]
517 #[display("XZ")]
518 XZ,
519 #[serde(rename = "YZ", alias = "yz")]
520 #[display("YZ")]
521 YZ,
522 #[display("Custom")]
524 Custom,
525 #[display("Uninit")]
527 Uninit,
528}
529
530#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
531#[ts(export)]
532#[serde(tag = "type", rename_all = "camelCase")]
533pub struct Sketch {
534 pub id: uuid::Uuid,
536 pub paths: Vec<Path>,
538 pub on: SketchSurface,
540 pub start: BasePath,
542 #[serde(default, skip_serializing_if = "IndexMap::is_empty")]
544 pub tags: IndexMap<String, TagIdentifier>,
545 pub artifact_id: ArtifactId,
548 #[ts(skip)]
549 pub original_id: uuid::Uuid,
550 #[serde(skip)]
552 pub mirror: Option<uuid::Uuid>,
553 pub units: UnitLen,
554 #[serde(skip)]
556 pub meta: Vec<Metadata>,
557}
558
559impl Sketch {
560 pub(crate) fn build_sketch_mode_cmds(
563 &self,
564 exec_state: &mut ExecState,
565 inner_cmd: ModelingCmdReq,
566 ) -> Vec<ModelingCmdReq> {
567 vec![
568 ModelingCmdReq {
571 cmd: ModelingCmd::from(mcmd::EnableSketchMode {
572 animated: false,
573 ortho: false,
574 entity_id: self.on.id(),
575 adjust_camera: false,
576 planar_normal: if let SketchSurface::Plane(plane) = &self.on {
577 Some(plane.z_axis.into())
579 } else {
580 None
581 },
582 }),
583 cmd_id: exec_state.next_uuid().into(),
584 },
585 inner_cmd,
586 ModelingCmdReq {
587 cmd: ModelingCmd::SketchModeDisable(mcmd::SketchModeDisable::default()),
588 cmd_id: exec_state.next_uuid().into(),
589 },
590 ]
591 }
592}
593
594#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
596#[ts(export)]
597#[serde(tag = "type", rename_all = "camelCase")]
598pub enum SketchSurface {
599 Plane(Box<Plane>),
600 Face(Box<Face>),
601}
602
603impl SketchSurface {
604 pub(crate) fn id(&self) -> uuid::Uuid {
605 match self {
606 SketchSurface::Plane(plane) => plane.id,
607 SketchSurface::Face(face) => face.id,
608 }
609 }
610 pub(crate) fn x_axis(&self) -> Point3d {
611 match self {
612 SketchSurface::Plane(plane) => plane.x_axis,
613 SketchSurface::Face(face) => face.x_axis,
614 }
615 }
616 pub(crate) fn y_axis(&self) -> Point3d {
617 match self {
618 SketchSurface::Plane(plane) => plane.y_axis,
619 SketchSurface::Face(face) => face.y_axis,
620 }
621 }
622 pub(crate) fn z_axis(&self) -> Point3d {
623 match self {
624 SketchSurface::Plane(plane) => plane.z_axis,
625 SketchSurface::Face(face) => face.z_axis,
626 }
627 }
628 pub(crate) fn units(&self) -> UnitLen {
629 match self {
630 SketchSurface::Plane(plane) => plane.units,
631 SketchSurface::Face(face) => face.units,
632 }
633 }
634}
635
636#[derive(Debug, Clone)]
637pub(crate) enum GetTangentialInfoFromPathsResult {
638 PreviousPoint([f64; 2]),
639 Arc { center: [f64; 2], ccw: bool },
640 Circle { center: [f64; 2], ccw: bool, radius: f64 },
641}
642
643impl GetTangentialInfoFromPathsResult {
644 pub(crate) fn tan_previous_point(&self, last_arc_end: [f64; 2]) -> [f64; 2] {
645 match self {
646 GetTangentialInfoFromPathsResult::PreviousPoint(p) => *p,
647 GetTangentialInfoFromPathsResult::Arc { center, ccw } => {
648 crate::std::utils::get_tangent_point_from_previous_arc(*center, *ccw, last_arc_end)
649 }
650 GetTangentialInfoFromPathsResult::Circle {
653 center, radius, ccw, ..
654 } => [center[0] + radius, center[1] + if *ccw { -1.0 } else { 1.0 }],
655 }
656 }
657}
658
659impl Sketch {
660 pub(crate) fn add_tag(&mut self, tag: NodeRef<'_, TagDeclarator>, current_path: &Path, exec_state: &ExecState) {
661 let mut tag_identifier: TagIdentifier = tag.into();
662 let base = current_path.get_base();
663 tag_identifier.info.push((
664 exec_state.stack().current_epoch(),
665 TagEngineInfo {
666 id: base.geo_meta.id,
667 sketch: self.id,
668 path: Some(current_path.clone()),
669 surface: None,
670 },
671 ));
672
673 self.tags.insert(tag.name.to_string(), tag_identifier);
674 }
675
676 pub(crate) fn merge_tags<'a>(&mut self, tags: impl Iterator<Item = &'a TagIdentifier>) {
677 for t in tags {
678 match self.tags.get_mut(&t.value) {
679 Some(id) => {
680 id.merge_info(t);
681 }
682 None => {
683 self.tags.insert(t.value.clone(), t.clone());
684 }
685 }
686 }
687 }
688
689 pub(crate) fn latest_path(&self) -> Option<&Path> {
691 self.paths.last()
692 }
693
694 pub(crate) fn current_pen_position(&self) -> Result<Point2d, KclError> {
698 let Some(path) = self.latest_path() else {
699 return Ok(Point2d::new(self.start.to[0], self.start.to[1], self.start.units));
700 };
701
702 Ok(path.get_to().into())
703 }
704
705 pub(crate) fn get_tangential_info_from_paths(&self) -> GetTangentialInfoFromPathsResult {
706 let Some(path) = self.latest_path() else {
707 return GetTangentialInfoFromPathsResult::PreviousPoint(self.start.to);
708 };
709 path.get_tangential_info()
710 }
711}
712
713#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
714#[ts(export)]
715#[serde(tag = "type", rename_all = "camelCase")]
716pub struct Solid {
717 pub id: uuid::Uuid,
719 pub artifact_id: ArtifactId,
721 pub value: Vec<ExtrudeSurface>,
723 pub sketch: Sketch,
725 pub height: f64,
727 pub start_cap_id: Option<uuid::Uuid>,
729 pub end_cap_id: Option<uuid::Uuid>,
731 #[serde(default, skip_serializing_if = "Vec::is_empty")]
733 pub edge_cuts: Vec<EdgeCut>,
734 pub units: UnitLen,
735 #[serde(skip)]
737 pub meta: Vec<Metadata>,
738}
739
740impl Solid {
741 pub(crate) fn get_all_edge_cut_ids(&self) -> impl Iterator<Item = uuid::Uuid> + '_ {
742 self.edge_cuts.iter().map(|foc| foc.id())
743 }
744}
745
746#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
748#[ts(export)]
749#[serde(tag = "type", rename_all = "camelCase")]
750pub enum EdgeCut {
751 Fillet {
753 id: uuid::Uuid,
755 radius: TyF64,
756 #[serde(rename = "edgeId")]
758 edge_id: uuid::Uuid,
759 tag: Box<Option<TagNode>>,
760 },
761 Chamfer {
763 id: uuid::Uuid,
765 length: TyF64,
766 #[serde(rename = "edgeId")]
768 edge_id: uuid::Uuid,
769 tag: Box<Option<TagNode>>,
770 },
771}
772
773impl EdgeCut {
774 pub fn id(&self) -> uuid::Uuid {
775 match self {
776 EdgeCut::Fillet { id, .. } => *id,
777 EdgeCut::Chamfer { id, .. } => *id,
778 }
779 }
780
781 pub fn edge_id(&self) -> uuid::Uuid {
782 match self {
783 EdgeCut::Fillet { edge_id, .. } => *edge_id,
784 EdgeCut::Chamfer { edge_id, .. } => *edge_id,
785 }
786 }
787
788 pub fn tag(&self) -> Option<TagNode> {
789 match self {
790 EdgeCut::Fillet { tag, .. } => *tag.clone(),
791 EdgeCut::Chamfer { tag, .. } => *tag.clone(),
792 }
793 }
794}
795
796#[derive(Debug, Deserialize, Serialize, PartialEq, Clone, Copy, ts_rs::TS, JsonSchema)]
797#[ts(export)]
798pub struct Point2d {
799 pub x: f64,
800 pub y: f64,
801 pub units: UnitLen,
802}
803
804impl From<[TyF64; 2]> for Point2d {
805 fn from(p: [TyF64; 2]) -> Self {
806 Self {
807 x: p[0].n,
808 y: p[1].n,
809 units: p[0].ty.expect_length(),
810 }
811 }
812}
813
814impl From<Point2d> for [f64; 2] {
815 fn from(p: Point2d) -> Self {
816 [p.x, p.y]
817 }
818}
819
820impl From<Point2d> for Point2D {
821 fn from(p: Point2d) -> Self {
822 Self { x: p.x, y: p.y }
823 }
824}
825
826impl Point2d {
827 pub const ZERO: Self = Self {
828 x: 0.0,
829 y: 0.0,
830 units: UnitLen::Mm,
831 };
832
833 pub fn new(x: f64, y: f64, units: UnitLen) -> Self {
834 Self { x, y, units }
835 }
836}
837
838#[derive(Debug, Deserialize, Serialize, PartialEq, Clone, Copy, ts_rs::TS, JsonSchema, Default)]
839#[ts(export)]
840pub struct Point3d {
841 pub x: f64,
842 pub y: f64,
843 pub z: f64,
844 pub units: UnitLen,
845}
846
847impl Point3d {
848 pub const ZERO: Self = Self {
849 x: 0.0,
850 y: 0.0,
851 z: 0.0,
852 units: UnitLen::Mm,
853 };
854
855 pub fn new(x: f64, y: f64, z: f64, units: UnitLen) -> Self {
856 Self { x, y, z, units }
857 }
858
859 pub const fn is_zero(&self) -> bool {
860 self.x == 0.0 && self.y == 0.0 && self.z == 0.0
861 }
862}
863
864impl From<[TyF64; 3]> for Point3d {
865 fn from(p: [TyF64; 3]) -> Self {
866 Self {
867 x: p[0].n,
868 y: p[1].n,
869 z: p[2].n,
870 units: p[0].ty.expect_length(),
871 }
872 }
873}
874
875impl From<Point3d> for Point3D {
876 fn from(p: Point3d) -> Self {
877 Self { x: p.x, y: p.y, z: p.z }
878 }
879}
880impl From<Point3d> for kittycad_modeling_cmds::shared::Point3d<LengthUnit> {
881 fn from(p: Point3d) -> Self {
882 Self {
883 x: LengthUnit(p.x),
884 y: LengthUnit(p.y),
885 z: LengthUnit(p.z),
886 }
887 }
888}
889
890impl Add for Point3d {
891 type Output = Point3d;
892
893 fn add(self, rhs: Self) -> Self::Output {
894 Point3d {
896 x: self.x + rhs.x,
897 y: self.y + rhs.y,
898 z: self.z + rhs.z,
899 units: self.units,
900 }
901 }
902}
903
904impl AddAssign for Point3d {
905 fn add_assign(&mut self, rhs: Self) {
906 *self = *self + rhs
907 }
908}
909
910impl Mul<f64> for Point3d {
911 type Output = Point3d;
912
913 fn mul(self, rhs: f64) -> Self::Output {
914 Point3d {
915 x: self.x * rhs,
916 y: self.y * rhs,
917 z: self.z * rhs,
918 units: self.units,
919 }
920 }
921}
922
923#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
925#[ts(export)]
926#[serde(rename_all = "camelCase")]
927pub struct BasePath {
928 #[ts(type = "[number, number]")]
930 pub from: [f64; 2],
931 #[ts(type = "[number, number]")]
933 pub to: [f64; 2],
934 pub units: UnitLen,
935 pub tag: Option<TagNode>,
937 #[serde(rename = "__geoMeta")]
939 pub geo_meta: GeoMeta,
940}
941
942impl BasePath {
943 pub fn get_to(&self) -> [TyF64; 2] {
944 let ty: NumericType = self.units.into();
945 [TyF64::new(self.to[0], ty.clone()), TyF64::new(self.to[1], ty)]
946 }
947
948 pub fn get_from(&self) -> [TyF64; 2] {
949 let ty: NumericType = self.units.into();
950 [TyF64::new(self.from[0], ty.clone()), TyF64::new(self.from[1], ty)]
951 }
952}
953
954#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
956#[ts(export)]
957#[serde(rename_all = "camelCase")]
958pub struct GeoMeta {
959 pub id: uuid::Uuid,
961 #[serde(flatten)]
963 pub metadata: Metadata,
964}
965
966#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
968#[ts(export)]
969#[serde(tag = "type")]
970pub enum Path {
971 ToPoint {
973 #[serde(flatten)]
974 base: BasePath,
975 },
976 TangentialArcTo {
978 #[serde(flatten)]
979 base: BasePath,
980 #[ts(type = "[number, number]")]
982 center: [f64; 2],
983 ccw: bool,
985 },
986 TangentialArc {
988 #[serde(flatten)]
989 base: BasePath,
990 #[ts(type = "[number, number]")]
992 center: [f64; 2],
993 ccw: bool,
995 },
996 Circle {
999 #[serde(flatten)]
1000 base: BasePath,
1001 #[ts(type = "[number, number]")]
1003 center: [f64; 2],
1004 radius: f64,
1006 ccw: bool,
1009 },
1010 CircleThreePoint {
1011 #[serde(flatten)]
1012 base: BasePath,
1013 #[ts(type = "[number, number]")]
1015 p1: [f64; 2],
1016 #[ts(type = "[number, number]")]
1018 p2: [f64; 2],
1019 #[ts(type = "[number, number]")]
1021 p3: [f64; 2],
1022 },
1023 ArcThreePoint {
1024 #[serde(flatten)]
1025 base: BasePath,
1026 #[ts(type = "[number, number]")]
1028 p1: [f64; 2],
1029 #[ts(type = "[number, number]")]
1031 p2: [f64; 2],
1032 #[ts(type = "[number, number]")]
1034 p3: [f64; 2],
1035 },
1036 Horizontal {
1038 #[serde(flatten)]
1039 base: BasePath,
1040 x: f64,
1042 },
1043 AngledLineTo {
1045 #[serde(flatten)]
1046 base: BasePath,
1047 x: Option<f64>,
1049 y: Option<f64>,
1051 },
1052 Base {
1054 #[serde(flatten)]
1055 base: BasePath,
1056 },
1057 Arc {
1059 #[serde(flatten)]
1060 base: BasePath,
1061 center: [f64; 2],
1063 radius: f64,
1065 ccw: bool,
1067 },
1068}
1069
1070#[derive(Display)]
1072enum PathType {
1073 ToPoint,
1074 Base,
1075 TangentialArc,
1076 TangentialArcTo,
1077 Circle,
1078 CircleThreePoint,
1079 Horizontal,
1080 AngledLineTo,
1081 Arc,
1082}
1083
1084impl From<&Path> for PathType {
1085 fn from(value: &Path) -> Self {
1086 match value {
1087 Path::ToPoint { .. } => Self::ToPoint,
1088 Path::TangentialArcTo { .. } => Self::TangentialArcTo,
1089 Path::TangentialArc { .. } => Self::TangentialArc,
1090 Path::Circle { .. } => Self::Circle,
1091 Path::CircleThreePoint { .. } => Self::CircleThreePoint,
1092 Path::Horizontal { .. } => Self::Horizontal,
1093 Path::AngledLineTo { .. } => Self::AngledLineTo,
1094 Path::Base { .. } => Self::Base,
1095 Path::Arc { .. } => Self::Arc,
1096 Path::ArcThreePoint { .. } => Self::Arc,
1097 }
1098 }
1099}
1100
1101impl Path {
1102 pub fn get_id(&self) -> uuid::Uuid {
1103 match self {
1104 Path::ToPoint { base } => base.geo_meta.id,
1105 Path::Horizontal { base, .. } => base.geo_meta.id,
1106 Path::AngledLineTo { base, .. } => base.geo_meta.id,
1107 Path::Base { base } => base.geo_meta.id,
1108 Path::TangentialArcTo { base, .. } => base.geo_meta.id,
1109 Path::TangentialArc { base, .. } => base.geo_meta.id,
1110 Path::Circle { base, .. } => base.geo_meta.id,
1111 Path::CircleThreePoint { base, .. } => base.geo_meta.id,
1112 Path::Arc { base, .. } => base.geo_meta.id,
1113 Path::ArcThreePoint { base, .. } => base.geo_meta.id,
1114 }
1115 }
1116
1117 pub fn get_tag(&self) -> Option<TagNode> {
1118 match self {
1119 Path::ToPoint { base } => base.tag.clone(),
1120 Path::Horizontal { base, .. } => base.tag.clone(),
1121 Path::AngledLineTo { base, .. } => base.tag.clone(),
1122 Path::Base { base } => base.tag.clone(),
1123 Path::TangentialArcTo { base, .. } => base.tag.clone(),
1124 Path::TangentialArc { base, .. } => base.tag.clone(),
1125 Path::Circle { base, .. } => base.tag.clone(),
1126 Path::CircleThreePoint { base, .. } => base.tag.clone(),
1127 Path::Arc { base, .. } => base.tag.clone(),
1128 Path::ArcThreePoint { base, .. } => base.tag.clone(),
1129 }
1130 }
1131
1132 pub fn get_base(&self) -> &BasePath {
1133 match self {
1134 Path::ToPoint { base } => base,
1135 Path::Horizontal { base, .. } => base,
1136 Path::AngledLineTo { base, .. } => base,
1137 Path::Base { base } => base,
1138 Path::TangentialArcTo { base, .. } => base,
1139 Path::TangentialArc { base, .. } => base,
1140 Path::Circle { base, .. } => base,
1141 Path::CircleThreePoint { base, .. } => base,
1142 Path::Arc { base, .. } => base,
1143 Path::ArcThreePoint { base, .. } => base,
1144 }
1145 }
1146
1147 pub fn get_from(&self) -> [TyF64; 2] {
1149 let p = &self.get_base().from;
1150 let ty: NumericType = self.get_base().units.into();
1151 [TyF64::new(p[0], ty.clone()), TyF64::new(p[1], ty)]
1152 }
1153
1154 pub fn get_to(&self) -> [TyF64; 2] {
1156 let p = &self.get_base().to;
1157 let ty: NumericType = self.get_base().units.into();
1158 [TyF64::new(p[0], ty.clone()), TyF64::new(p[1], ty)]
1159 }
1160
1161 pub fn length(&self) -> TyF64 {
1163 let n = match self {
1164 Self::ToPoint { .. } | Self::Base { .. } | Self::Horizontal { .. } | Self::AngledLineTo { .. } => {
1165 linear_distance(&self.get_base().from, &self.get_base().to)
1166 }
1167 Self::TangentialArc {
1168 base: _,
1169 center,
1170 ccw: _,
1171 }
1172 | Self::TangentialArcTo {
1173 base: _,
1174 center,
1175 ccw: _,
1176 } => {
1177 let radius = linear_distance(&self.get_base().from, center);
1180 debug_assert_eq!(radius, linear_distance(&self.get_base().to, center));
1181 linear_distance(&self.get_base().from, &self.get_base().to)
1183 }
1184 Self::Circle { radius, .. } => 2.0 * std::f64::consts::PI * radius,
1185 Self::CircleThreePoint { .. } => {
1186 let circle_center = crate::std::utils::calculate_circle_from_3_points([
1187 self.get_base().from,
1188 self.get_base().to,
1189 self.get_base().to,
1190 ]);
1191 let radius = linear_distance(
1192 &[circle_center.center[0], circle_center.center[1]],
1193 &self.get_base().from,
1194 );
1195 2.0 * std::f64::consts::PI * radius
1196 }
1197 Self::Arc { .. } => {
1198 linear_distance(&self.get_base().from, &self.get_base().to)
1200 }
1201 Self::ArcThreePoint { .. } => {
1202 linear_distance(&self.get_base().from, &self.get_base().to)
1204 }
1205 };
1206 TyF64::new(n, self.get_base().units.into())
1207 }
1208
1209 pub fn get_base_mut(&mut self) -> Option<&mut BasePath> {
1210 match self {
1211 Path::ToPoint { base } => Some(base),
1212 Path::Horizontal { base, .. } => Some(base),
1213 Path::AngledLineTo { base, .. } => Some(base),
1214 Path::Base { base } => Some(base),
1215 Path::TangentialArcTo { base, .. } => Some(base),
1216 Path::TangentialArc { base, .. } => Some(base),
1217 Path::Circle { base, .. } => Some(base),
1218 Path::CircleThreePoint { base, .. } => Some(base),
1219 Path::Arc { base, .. } => Some(base),
1220 Path::ArcThreePoint { base, .. } => Some(base),
1221 }
1222 }
1223
1224 pub(crate) fn get_tangential_info(&self) -> GetTangentialInfoFromPathsResult {
1225 match self {
1226 Path::TangentialArc { center, ccw, .. }
1227 | Path::TangentialArcTo { center, ccw, .. }
1228 | Path::Arc { center, ccw, .. } => GetTangentialInfoFromPathsResult::Arc {
1229 center: *center,
1230 ccw: *ccw,
1231 },
1232 Path::ArcThreePoint { p1, p2, p3, .. } => {
1233 let circle_center = crate::std::utils::calculate_circle_from_3_points([*p1, *p2, *p3]);
1234 GetTangentialInfoFromPathsResult::Arc {
1235 center: circle_center.center,
1236 ccw: crate::std::utils::is_points_ccw(&[*p1, *p2, *p3]) > 0,
1237 }
1238 }
1239 Path::Circle {
1240 center, ccw, radius, ..
1241 } => GetTangentialInfoFromPathsResult::Circle {
1242 center: *center,
1243 ccw: *ccw,
1244 radius: *radius,
1245 },
1246 Path::CircleThreePoint { p1, p2, p3, .. } => {
1247 let circle_center = crate::std::utils::calculate_circle_from_3_points([*p1, *p2, *p3]);
1248 let radius = linear_distance(&[circle_center.center[0], circle_center.center[1]], p1);
1249 let center_point = [circle_center.center[0], circle_center.center[1]];
1250 GetTangentialInfoFromPathsResult::Circle {
1251 center: center_point,
1252 ccw: true,
1254 radius,
1255 }
1256 }
1257 Path::ToPoint { .. } | Path::Horizontal { .. } | Path::AngledLineTo { .. } | Path::Base { .. } => {
1258 let base = self.get_base();
1259 GetTangentialInfoFromPathsResult::PreviousPoint(base.from)
1260 }
1261 }
1262 }
1263}
1264
1265#[rustfmt::skip]
1267fn linear_distance(
1268 [x0, y0]: &[f64; 2],
1269 [x1, y1]: &[f64; 2]
1270) -> f64 {
1271 let y_sq = (y1 - y0).powi(2);
1272 let x_sq = (x1 - x0).powi(2);
1273 (y_sq + x_sq).sqrt()
1274}
1275
1276#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
1278#[ts(export)]
1279#[serde(tag = "type", rename_all = "camelCase")]
1280pub enum ExtrudeSurface {
1281 ExtrudePlane(ExtrudePlane),
1283 ExtrudeArc(ExtrudeArc),
1284 Chamfer(ChamferSurface),
1285 Fillet(FilletSurface),
1286}
1287
1288#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
1290#[ts(export)]
1291#[serde(rename_all = "camelCase")]
1292pub struct ChamferSurface {
1293 pub face_id: uuid::Uuid,
1295 pub tag: Option<Node<TagDeclarator>>,
1297 #[serde(flatten)]
1299 pub geo_meta: GeoMeta,
1300}
1301
1302#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
1304#[ts(export)]
1305#[serde(rename_all = "camelCase")]
1306pub struct FilletSurface {
1307 pub face_id: uuid::Uuid,
1309 pub tag: Option<Node<TagDeclarator>>,
1311 #[serde(flatten)]
1313 pub geo_meta: GeoMeta,
1314}
1315
1316#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
1318#[ts(export)]
1319#[serde(rename_all = "camelCase")]
1320pub struct ExtrudePlane {
1321 pub face_id: uuid::Uuid,
1323 pub tag: Option<Node<TagDeclarator>>,
1325 #[serde(flatten)]
1327 pub geo_meta: GeoMeta,
1328}
1329
1330#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
1332#[ts(export)]
1333#[serde(rename_all = "camelCase")]
1334pub struct ExtrudeArc {
1335 pub face_id: uuid::Uuid,
1337 pub tag: Option<Node<TagDeclarator>>,
1339 #[serde(flatten)]
1341 pub geo_meta: GeoMeta,
1342}
1343
1344impl ExtrudeSurface {
1345 pub fn get_id(&self) -> uuid::Uuid {
1346 match self {
1347 ExtrudeSurface::ExtrudePlane(ep) => ep.geo_meta.id,
1348 ExtrudeSurface::ExtrudeArc(ea) => ea.geo_meta.id,
1349 ExtrudeSurface::Fillet(f) => f.geo_meta.id,
1350 ExtrudeSurface::Chamfer(c) => c.geo_meta.id,
1351 }
1352 }
1353
1354 pub fn get_tag(&self) -> Option<Node<TagDeclarator>> {
1355 match self {
1356 ExtrudeSurface::ExtrudePlane(ep) => ep.tag.clone(),
1357 ExtrudeSurface::ExtrudeArc(ea) => ea.tag.clone(),
1358 ExtrudeSurface::Fillet(f) => f.tag.clone(),
1359 ExtrudeSurface::Chamfer(c) => c.tag.clone(),
1360 }
1361 }
1362}