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::{ArtifactId, ExecState, Metadata, TagEngineInfo, TagIdentifier, UnitLen},
14 parsing::ast::types::{Node, NodeRef, TagDeclarator, TagNode},
15 std::sketch::PlaneData,
16};
17
18type Point2D = kcmc::shared::Point2d<f64>;
19type Point3D = kcmc::shared::Point3d<f64>;
20
21#[derive(Debug, Clone, Deserialize, 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, Deserialize, 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, Deserialize, 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, Deserialize, 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, Deserialize, 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 == Point3d::new(0.0, 0.0, 0.0) {
188 match self {
189 Self {
190 origin: Point3d { x: 0.0, y: 0.0, z: 0.0 },
191 x_axis: Point3d { x: 1.0, y: 0.0, z: 0.0 },
192 y_axis: Point3d { x: 0.0, y: 1.0, z: 0.0 },
193 z_axis: Point3d { x: 0.0, y: 0.0, z: 1.0 },
194 ..
195 } => return PlaneData::XY,
196 Self {
197 origin: Point3d { x: 0.0, y: 0.0, z: 0.0 },
198 x_axis: Point3d { x: 1.0, y: 0.0, z: 0.0 },
199 y_axis: Point3d { x: 0.0, y: 1.0, z: 0.0 },
200 z_axis:
201 Point3d {
202 x: 0.0,
203 y: 0.0,
204 z: -1.0,
205 },
206 ..
207 } => return PlaneData::NegXY,
208 Self {
209 origin: Point3d { x: 0.0, y: 0.0, z: 0.0 },
210 x_axis: Point3d { x: 1.0, y: 0.0, z: 0.0 },
211 y_axis: Point3d { x: 0.0, y: 0.0, z: 1.0 },
212 z_axis:
213 Point3d {
214 x: 0.0,
215 y: -1.0,
216 z: 0.0,
217 },
218 ..
219 } => return PlaneData::XZ,
220 Self {
221 origin: Point3d { x: 0.0, y: 0.0, z: 0.0 },
222 x_axis: Point3d { x: 1.0, y: 0.0, z: 0.0 },
223 y_axis: Point3d { x: 0.0, y: 0.0, z: 1.0 },
224 z_axis: Point3d { x: 0.0, y: 1.0, z: 0.0 },
225 ..
226 } => return PlaneData::NegXZ,
227 Self {
228 origin: Point3d { x: 0.0, y: 0.0, z: 0.0 },
229 x_axis: Point3d { x: 0.0, y: 1.0, z: 0.0 },
230 y_axis: Point3d { x: 0.0, y: 0.0, z: 1.0 },
231 z_axis: Point3d { x: 1.0, y: 0.0, z: 0.0 },
232 ..
233 } => return PlaneData::YZ,
234 Self {
235 origin: Point3d { x: 0.0, y: 0.0, z: 0.0 },
236 x_axis: Point3d { x: 0.0, y: 1.0, z: 0.0 },
237 y_axis: Point3d { x: 0.0, y: 0.0, z: 1.0 },
238 z_axis:
239 Point3d {
240 x: -1.0,
241 y: 0.0,
242 z: 0.0,
243 },
244 ..
245 } => return PlaneData::NegYZ,
246 _ => {}
247 }
248 }
249
250 PlaneData::Plane {
251 origin: self.origin,
252 x_axis: self.x_axis,
253 y_axis: self.y_axis,
254 z_axis: self.z_axis,
255 }
256 }
257
258 pub(crate) fn from_plane_data(value: PlaneData, exec_state: &mut ExecState) -> Self {
259 let id = exec_state.next_uuid();
260 match value {
261 PlaneData::XY => Plane {
262 id,
263 artifact_id: id.into(),
264 origin: Point3d::new(0.0, 0.0, 0.0),
265 x_axis: Point3d::new(1.0, 0.0, 0.0),
266 y_axis: Point3d::new(0.0, 1.0, 0.0),
267 z_axis: Point3d::new(0.0, 0.0, 1.0),
268 value: PlaneType::XY,
269 units: exec_state.length_unit(),
270 meta: vec![],
271 },
272 PlaneData::NegXY => Plane {
273 id,
274 artifact_id: id.into(),
275 origin: Point3d::new(0.0, 0.0, 0.0),
276 x_axis: Point3d::new(1.0, 0.0, 0.0),
277 y_axis: Point3d::new(0.0, 1.0, 0.0),
278 z_axis: Point3d::new(0.0, 0.0, -1.0),
279 value: PlaneType::XY,
280 units: exec_state.length_unit(),
281 meta: vec![],
282 },
283 PlaneData::XZ => Plane {
284 id,
285 artifact_id: id.into(),
286 origin: Point3d::new(0.0, 0.0, 0.0),
287 x_axis: Point3d::new(1.0, 0.0, 0.0),
288 y_axis: Point3d::new(0.0, 0.0, 1.0),
289 z_axis: Point3d::new(0.0, -1.0, 0.0),
290 value: PlaneType::XZ,
291 units: exec_state.length_unit(),
292 meta: vec![],
293 },
294 PlaneData::NegXZ => Plane {
295 id,
296 artifact_id: id.into(),
297 origin: Point3d::new(0.0, 0.0, 0.0),
298 x_axis: Point3d::new(-1.0, 0.0, 0.0),
299 y_axis: Point3d::new(0.0, 0.0, 1.0),
300 z_axis: Point3d::new(0.0, 1.0, 0.0),
301 value: PlaneType::XZ,
302 units: exec_state.length_unit(),
303 meta: vec![],
304 },
305 PlaneData::YZ => Plane {
306 id,
307 artifact_id: id.into(),
308 origin: Point3d::new(0.0, 0.0, 0.0),
309 x_axis: Point3d::new(0.0, 1.0, 0.0),
310 y_axis: Point3d::new(0.0, 0.0, 1.0),
311 z_axis: Point3d::new(1.0, 0.0, 0.0),
312 value: PlaneType::YZ,
313 units: exec_state.length_unit(),
314 meta: vec![],
315 },
316 PlaneData::NegYZ => Plane {
317 id,
318 artifact_id: id.into(),
319 origin: Point3d::new(0.0, 0.0, 0.0),
320 x_axis: Point3d::new(0.0, 1.0, 0.0),
321 y_axis: Point3d::new(0.0, 0.0, 1.0),
322 z_axis: Point3d::new(-1.0, 0.0, 0.0),
323 value: PlaneType::YZ,
324 units: exec_state.length_unit(),
325 meta: vec![],
326 },
327 PlaneData::Plane {
328 origin,
329 x_axis,
330 y_axis,
331 z_axis,
332 } => {
333 let id = exec_state.next_uuid();
334 Plane {
335 id,
336 artifact_id: id.into(),
337 origin,
338 x_axis,
339 y_axis,
340 z_axis,
341 value: PlaneType::Custom,
342 units: exec_state.length_unit(),
343 meta: vec![],
344 }
345 }
346 }
347 }
348
349 pub fn is_standard(&self) -> bool {
351 !matches!(self.value, PlaneType::Custom | PlaneType::Uninit)
352 }
353}
354
355#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
357#[ts(export)]
358#[serde(rename_all = "camelCase")]
359pub struct Face {
360 pub id: uuid::Uuid,
362 pub artifact_id: ArtifactId,
364 pub value: String,
366 pub x_axis: Point3d,
368 pub y_axis: Point3d,
370 pub z_axis: Point3d,
372 pub solid: Box<Solid>,
374 pub units: UnitLen,
375 #[serde(skip)]
376 pub meta: Vec<Metadata>,
377}
378
379#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, FromStr, Display)]
381#[ts(export)]
382#[display(style = "camelCase")]
383pub enum PlaneType {
384 #[serde(rename = "XY", alias = "xy")]
385 #[display("XY")]
386 XY,
387 #[serde(rename = "XZ", alias = "xz")]
388 #[display("XZ")]
389 XZ,
390 #[serde(rename = "YZ", alias = "yz")]
391 #[display("YZ")]
392 YZ,
393 #[display("Custom")]
395 Custom,
396 #[display("Uninit")]
398 Uninit,
399}
400
401#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
402#[ts(export)]
403#[serde(tag = "type", rename_all = "camelCase")]
404pub struct Sketch {
405 pub id: uuid::Uuid,
407 pub paths: Vec<Path>,
409 pub on: SketchSurface,
411 pub start: BasePath,
413 #[serde(default, skip_serializing_if = "IndexMap::is_empty")]
415 pub tags: IndexMap<String, TagIdentifier>,
416 pub artifact_id: ArtifactId,
419 #[ts(skip)]
420 pub original_id: uuid::Uuid,
421 pub units: UnitLen,
422 #[serde(skip)]
424 pub meta: Vec<Metadata>,
425}
426
427impl Sketch {
428 pub(crate) fn build_sketch_mode_cmds(
431 &self,
432 exec_state: &mut ExecState,
433 inner_cmd: ModelingCmdReq,
434 ) -> Vec<ModelingCmdReq> {
435 vec![
436 ModelingCmdReq {
439 cmd: ModelingCmd::from(mcmd::EnableSketchMode {
440 animated: false,
441 ortho: false,
442 entity_id: self.on.id(),
443 adjust_camera: false,
444 planar_normal: if let SketchSurface::Plane(plane) = &self.on {
445 Some(plane.z_axis.into())
447 } else {
448 None
449 },
450 }),
451 cmd_id: exec_state.next_uuid().into(),
452 },
453 inner_cmd,
454 ModelingCmdReq {
455 cmd: ModelingCmd::SketchModeDisable(mcmd::SketchModeDisable::default()),
456 cmd_id: exec_state.next_uuid().into(),
457 },
458 ]
459 }
460}
461
462#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
464#[ts(export)]
465#[serde(tag = "type", rename_all = "camelCase")]
466pub enum SketchSurface {
467 Plane(Box<Plane>),
468 Face(Box<Face>),
469}
470
471impl SketchSurface {
472 pub(crate) fn id(&self) -> uuid::Uuid {
473 match self {
474 SketchSurface::Plane(plane) => plane.id,
475 SketchSurface::Face(face) => face.id,
476 }
477 }
478 pub(crate) fn x_axis(&self) -> Point3d {
479 match self {
480 SketchSurface::Plane(plane) => plane.x_axis,
481 SketchSurface::Face(face) => face.x_axis,
482 }
483 }
484 pub(crate) fn y_axis(&self) -> Point3d {
485 match self {
486 SketchSurface::Plane(plane) => plane.y_axis,
487 SketchSurface::Face(face) => face.y_axis,
488 }
489 }
490 pub(crate) fn z_axis(&self) -> Point3d {
491 match self {
492 SketchSurface::Plane(plane) => plane.z_axis,
493 SketchSurface::Face(face) => face.z_axis,
494 }
495 }
496 pub(crate) fn units(&self) -> UnitLen {
497 match self {
498 SketchSurface::Plane(plane) => plane.units,
499 SketchSurface::Face(face) => face.units,
500 }
501 }
502}
503
504#[derive(Debug, Clone)]
505pub(crate) enum GetTangentialInfoFromPathsResult {
506 PreviousPoint([f64; 2]),
507 Arc { center: [f64; 2], ccw: bool },
508 Circle { center: [f64; 2], ccw: bool, radius: f64 },
509}
510
511impl GetTangentialInfoFromPathsResult {
512 pub(crate) fn tan_previous_point(&self, last_arc_end: crate::std::utils::Coords2d) -> [f64; 2] {
513 match self {
514 GetTangentialInfoFromPathsResult::PreviousPoint(p) => *p,
515 GetTangentialInfoFromPathsResult::Arc { center, ccw, .. } => {
516 crate::std::utils::get_tangent_point_from_previous_arc(*center, *ccw, last_arc_end)
517 }
518 GetTangentialInfoFromPathsResult::Circle {
521 center, radius, ccw, ..
522 } => [center[0] + radius, center[1] + if *ccw { -1.0 } else { 1.0 }],
523 }
524 }
525}
526
527impl Sketch {
528 pub(crate) fn add_tag(&mut self, tag: NodeRef<'_, TagDeclarator>, current_path: &Path, exec_state: &ExecState) {
529 let mut tag_identifier: TagIdentifier = tag.into();
530 let base = current_path.get_base();
531 tag_identifier.info.push((
532 exec_state.stack().current_epoch(),
533 TagEngineInfo {
534 id: base.geo_meta.id,
535 sketch: self.id,
536 path: Some(current_path.clone()),
537 surface: None,
538 },
539 ));
540
541 self.tags.insert(tag.name.to_string(), tag_identifier);
542 }
543
544 pub(crate) fn merge_tags<'a>(&mut self, tags: impl Iterator<Item = &'a TagIdentifier>) {
545 for t in tags {
546 match self.tags.get_mut(&t.value) {
547 Some(id) => {
548 id.merge_info(t);
549 }
550 None => {
551 self.tags.insert(t.value.clone(), t.clone());
552 }
553 }
554 }
555 }
556
557 pub(crate) fn latest_path(&self) -> Option<&Path> {
559 self.paths.last()
560 }
561
562 pub(crate) fn current_pen_position(&self) -> Result<Point2d, KclError> {
566 let Some(path) = self.latest_path() else {
567 return Ok(self.start.to.into());
568 };
569
570 let base = path.get_base();
571 Ok(base.to.into())
572 }
573
574 pub(crate) fn get_tangential_info_from_paths(&self) -> GetTangentialInfoFromPathsResult {
575 let Some(path) = self.latest_path() else {
576 return GetTangentialInfoFromPathsResult::PreviousPoint(self.start.to);
577 };
578 path.get_tangential_info()
579 }
580}
581
582#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
583#[ts(export)]
584#[serde(tag = "type", rename_all = "camelCase")]
585pub struct Solid {
586 pub id: uuid::Uuid,
588 pub artifact_id: ArtifactId,
590 pub value: Vec<ExtrudeSurface>,
592 pub sketch: Sketch,
594 pub height: f64,
596 pub start_cap_id: Option<uuid::Uuid>,
598 pub end_cap_id: Option<uuid::Uuid>,
600 #[serde(default, skip_serializing_if = "Vec::is_empty")]
602 pub edge_cuts: Vec<EdgeCut>,
603 pub units: UnitLen,
604 #[serde(skip)]
606 pub meta: Vec<Metadata>,
607}
608
609impl Solid {
610 pub(crate) fn get_all_edge_cut_ids(&self) -> impl Iterator<Item = uuid::Uuid> + '_ {
611 self.edge_cuts.iter().map(|foc| foc.id())
612 }
613}
614
615#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
617#[ts(export)]
618#[serde(tag = "type", rename_all = "camelCase")]
619pub enum EdgeCut {
620 Fillet {
622 id: uuid::Uuid,
624 radius: f64,
625 #[serde(rename = "edgeId")]
627 edge_id: uuid::Uuid,
628 tag: Box<Option<TagNode>>,
629 },
630 Chamfer {
632 id: uuid::Uuid,
634 length: f64,
635 #[serde(rename = "edgeId")]
637 edge_id: uuid::Uuid,
638 tag: Box<Option<TagNode>>,
639 },
640}
641
642impl EdgeCut {
643 pub fn id(&self) -> uuid::Uuid {
644 match self {
645 EdgeCut::Fillet { id, .. } => *id,
646 EdgeCut::Chamfer { id, .. } => *id,
647 }
648 }
649
650 pub fn edge_id(&self) -> uuid::Uuid {
651 match self {
652 EdgeCut::Fillet { edge_id, .. } => *edge_id,
653 EdgeCut::Chamfer { edge_id, .. } => *edge_id,
654 }
655 }
656
657 pub fn tag(&self) -> Option<TagNode> {
658 match self {
659 EdgeCut::Fillet { tag, .. } => *tag.clone(),
660 EdgeCut::Chamfer { tag, .. } => *tag.clone(),
661 }
662 }
663}
664
665#[derive(Debug, Deserialize, Serialize, PartialEq, Clone, Copy, ts_rs::TS, JsonSchema)]
666#[ts(export)]
667pub struct Point2d {
668 pub x: f64,
669 pub y: f64,
670}
671
672impl From<[f64; 2]> for Point2d {
673 fn from(p: [f64; 2]) -> Self {
674 Self { x: p[0], y: p[1] }
675 }
676}
677
678impl From<&[f64; 2]> for Point2d {
679 fn from(p: &[f64; 2]) -> Self {
680 Self { x: p[0], y: p[1] }
681 }
682}
683
684impl From<Point2d> for [f64; 2] {
685 fn from(p: Point2d) -> Self {
686 [p.x, p.y]
687 }
688}
689
690impl From<Point2d> for Point2D {
691 fn from(p: Point2d) -> Self {
692 Self { x: p.x, y: p.y }
693 }
694}
695
696impl Point2d {
697 pub const ZERO: Self = Self { x: 0.0, y: 0.0 };
698 pub fn scale(self, scalar: f64) -> Self {
699 Self {
700 x: self.x * scalar,
701 y: self.y * scalar,
702 }
703 }
704}
705
706#[derive(Debug, Deserialize, Serialize, PartialEq, Clone, Copy, ts_rs::TS, JsonSchema, Default)]
707#[ts(export)]
708pub struct Point3d {
709 pub x: f64,
710 pub y: f64,
711 pub z: f64,
712}
713
714impl Point3d {
715 pub const ZERO: Self = Self { x: 0.0, y: 0.0, z: 0.0 };
716 pub fn new(x: f64, y: f64, z: f64) -> Self {
717 Self { x, y, z }
718 }
719}
720
721impl From<Point3d> for Point3D {
722 fn from(p: Point3d) -> Self {
723 Self { x: p.x, y: p.y, z: p.z }
724 }
725}
726impl From<Point3d> for kittycad_modeling_cmds::shared::Point3d<LengthUnit> {
727 fn from(p: Point3d) -> Self {
728 Self {
729 x: LengthUnit(p.x),
730 y: LengthUnit(p.y),
731 z: LengthUnit(p.z),
732 }
733 }
734}
735
736impl Add for Point3d {
737 type Output = Point3d;
738
739 fn add(self, rhs: Self) -> Self::Output {
740 Point3d {
741 x: self.x + rhs.x,
742 y: self.y + rhs.y,
743 z: self.z + rhs.z,
744 }
745 }
746}
747
748impl AddAssign for Point3d {
749 fn add_assign(&mut self, rhs: Self) {
750 *self = *self + rhs
751 }
752}
753
754impl Mul<f64> for Point3d {
755 type Output = Point3d;
756
757 fn mul(self, rhs: f64) -> Self::Output {
758 Point3d {
759 x: self.x * rhs,
760 y: self.y * rhs,
761 z: self.z * rhs,
762 }
763 }
764}
765
766#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
768#[ts(export)]
769#[serde(rename_all = "camelCase")]
770pub struct BasePath {
771 #[ts(type = "[number, number]")]
773 pub from: [f64; 2],
774 #[ts(type = "[number, number]")]
776 pub to: [f64; 2],
777 pub units: UnitLen,
778 pub tag: Option<TagNode>,
780 #[serde(rename = "__geoMeta")]
782 pub geo_meta: GeoMeta,
783}
784
785#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
787#[ts(export)]
788#[serde(rename_all = "camelCase")]
789pub struct GeoMeta {
790 pub id: uuid::Uuid,
792 #[serde(flatten)]
794 pub metadata: Metadata,
795}
796
797#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
799#[ts(export)]
800#[serde(tag = "type")]
801pub enum Path {
802 ToPoint {
804 #[serde(flatten)]
805 base: BasePath,
806 },
807 TangentialArcTo {
809 #[serde(flatten)]
810 base: BasePath,
811 #[ts(type = "[number, number]")]
813 center: [f64; 2],
814 ccw: bool,
816 },
817 TangentialArc {
819 #[serde(flatten)]
820 base: BasePath,
821 #[ts(type = "[number, number]")]
823 center: [f64; 2],
824 ccw: bool,
826 },
827 Circle {
830 #[serde(flatten)]
831 base: BasePath,
832 #[ts(type = "[number, number]")]
834 center: [f64; 2],
835 radius: f64,
837 ccw: bool,
840 },
841 CircleThreePoint {
842 #[serde(flatten)]
843 base: BasePath,
844 #[ts(type = "[number, number]")]
846 p1: [f64; 2],
847 #[ts(type = "[number, number]")]
849 p2: [f64; 2],
850 #[ts(type = "[number, number]")]
852 p3: [f64; 2],
853 },
854 ArcThreePoint {
855 #[serde(flatten)]
856 base: BasePath,
857 #[ts(type = "[number, number]")]
859 p1: [f64; 2],
860 #[ts(type = "[number, number]")]
862 p2: [f64; 2],
863 #[ts(type = "[number, number]")]
865 p3: [f64; 2],
866 },
867 Horizontal {
869 #[serde(flatten)]
870 base: BasePath,
871 x: f64,
873 },
874 AngledLineTo {
876 #[serde(flatten)]
877 base: BasePath,
878 x: Option<f64>,
880 y: Option<f64>,
882 },
883 Base {
885 #[serde(flatten)]
886 base: BasePath,
887 },
888 Arc {
890 #[serde(flatten)]
891 base: BasePath,
892 center: [f64; 2],
894 radius: f64,
896 ccw: bool,
898 },
899}
900
901#[derive(Display)]
903enum PathType {
904 ToPoint,
905 Base,
906 TangentialArc,
907 TangentialArcTo,
908 Circle,
909 CircleThreePoint,
910 Horizontal,
911 AngledLineTo,
912 Arc,
913}
914
915impl From<&Path> for PathType {
916 fn from(value: &Path) -> Self {
917 match value {
918 Path::ToPoint { .. } => Self::ToPoint,
919 Path::TangentialArcTo { .. } => Self::TangentialArcTo,
920 Path::TangentialArc { .. } => Self::TangentialArc,
921 Path::Circle { .. } => Self::Circle,
922 Path::CircleThreePoint { .. } => Self::CircleThreePoint,
923 Path::Horizontal { .. } => Self::Horizontal,
924 Path::AngledLineTo { .. } => Self::AngledLineTo,
925 Path::Base { .. } => Self::Base,
926 Path::Arc { .. } => Self::Arc,
927 Path::ArcThreePoint { .. } => Self::Arc,
928 }
929 }
930}
931
932impl Path {
933 pub fn get_id(&self) -> uuid::Uuid {
934 match self {
935 Path::ToPoint { base } => base.geo_meta.id,
936 Path::Horizontal { base, .. } => base.geo_meta.id,
937 Path::AngledLineTo { base, .. } => base.geo_meta.id,
938 Path::Base { base } => base.geo_meta.id,
939 Path::TangentialArcTo { base, .. } => base.geo_meta.id,
940 Path::TangentialArc { base, .. } => base.geo_meta.id,
941 Path::Circle { base, .. } => base.geo_meta.id,
942 Path::CircleThreePoint { base, .. } => base.geo_meta.id,
943 Path::Arc { base, .. } => base.geo_meta.id,
944 Path::ArcThreePoint { base, .. } => base.geo_meta.id,
945 }
946 }
947
948 pub fn get_tag(&self) -> Option<TagNode> {
949 match self {
950 Path::ToPoint { base } => base.tag.clone(),
951 Path::Horizontal { base, .. } => base.tag.clone(),
952 Path::AngledLineTo { base, .. } => base.tag.clone(),
953 Path::Base { base } => base.tag.clone(),
954 Path::TangentialArcTo { base, .. } => base.tag.clone(),
955 Path::TangentialArc { base, .. } => base.tag.clone(),
956 Path::Circle { base, .. } => base.tag.clone(),
957 Path::CircleThreePoint { base, .. } => base.tag.clone(),
958 Path::Arc { base, .. } => base.tag.clone(),
959 Path::ArcThreePoint { base, .. } => base.tag.clone(),
960 }
961 }
962
963 pub fn get_base(&self) -> &BasePath {
964 match self {
965 Path::ToPoint { base } => base,
966 Path::Horizontal { base, .. } => base,
967 Path::AngledLineTo { base, .. } => base,
968 Path::Base { base } => base,
969 Path::TangentialArcTo { base, .. } => base,
970 Path::TangentialArc { base, .. } => base,
971 Path::Circle { base, .. } => base,
972 Path::CircleThreePoint { base, .. } => base,
973 Path::Arc { base, .. } => base,
974 Path::ArcThreePoint { base, .. } => base,
975 }
976 }
977
978 pub fn get_from(&self) -> &[f64; 2] {
980 &self.get_base().from
981 }
982 pub fn get_to(&self) -> &[f64; 2] {
984 &self.get_base().to
985 }
986
987 pub fn length(&self) -> f64 {
989 match self {
990 Self::ToPoint { .. } | Self::Base { .. } | Self::Horizontal { .. } | Self::AngledLineTo { .. } => {
991 linear_distance(self.get_from(), self.get_to())
992 }
993 Self::TangentialArc {
994 base: _,
995 center,
996 ccw: _,
997 }
998 | Self::TangentialArcTo {
999 base: _,
1000 center,
1001 ccw: _,
1002 } => {
1003 let radius = linear_distance(self.get_from(), center);
1006 debug_assert_eq!(radius, linear_distance(self.get_to(), center));
1007 linear_distance(self.get_from(), self.get_to())
1009 }
1010 Self::Circle { radius, .. } => 2.0 * std::f64::consts::PI * radius,
1011 Self::CircleThreePoint { .. } => {
1012 let circle_center = crate::std::utils::calculate_circle_from_3_points([
1013 self.get_base().from.into(),
1014 self.get_base().to.into(),
1015 self.get_base().to.into(),
1016 ]);
1017 let radius = linear_distance(&[circle_center.center.x, circle_center.center.y], &self.get_base().from);
1018 2.0 * std::f64::consts::PI * radius
1019 }
1020 Self::Arc { .. } => {
1021 linear_distance(self.get_from(), self.get_to())
1023 }
1024 Self::ArcThreePoint { .. } => {
1025 linear_distance(self.get_from(), self.get_to())
1027 }
1028 }
1029 }
1030
1031 pub fn get_base_mut(&mut self) -> Option<&mut BasePath> {
1032 match self {
1033 Path::ToPoint { base } => Some(base),
1034 Path::Horizontal { base, .. } => Some(base),
1035 Path::AngledLineTo { base, .. } => Some(base),
1036 Path::Base { base } => Some(base),
1037 Path::TangentialArcTo { base, .. } => Some(base),
1038 Path::TangentialArc { base, .. } => Some(base),
1039 Path::Circle { base, .. } => Some(base),
1040 Path::CircleThreePoint { base, .. } => Some(base),
1041 Path::Arc { base, .. } => Some(base),
1042 Path::ArcThreePoint { base, .. } => Some(base),
1043 }
1044 }
1045
1046 pub(crate) fn get_tangential_info(&self) -> GetTangentialInfoFromPathsResult {
1047 match self {
1048 Path::TangentialArc { center, ccw, .. }
1049 | Path::TangentialArcTo { center, ccw, .. }
1050 | Path::Arc { center, ccw, .. } => GetTangentialInfoFromPathsResult::Arc {
1051 center: *center,
1052 ccw: *ccw,
1053 },
1054 Path::ArcThreePoint { p1, p2, p3, .. } => {
1055 let circle_center =
1056 crate::std::utils::calculate_circle_from_3_points([(*p1).into(), (*p2).into(), (*p3).into()]);
1057 let radius = linear_distance(&[circle_center.center.x, circle_center.center.y], p1);
1058 let center_point = [circle_center.center.x, circle_center.center.y];
1059 GetTangentialInfoFromPathsResult::Circle {
1060 center: center_point,
1061 ccw: true,
1062 radius,
1063 }
1064 }
1065 Path::Circle {
1066 center, ccw, radius, ..
1067 } => GetTangentialInfoFromPathsResult::Circle {
1068 center: *center,
1069 ccw: *ccw,
1070 radius: *radius,
1071 },
1072 Path::CircleThreePoint { p1, p2, p3, .. } => {
1073 let circle_center =
1074 crate::std::utils::calculate_circle_from_3_points([(*p1).into(), (*p2).into(), (*p3).into()]);
1075 let radius = linear_distance(&[circle_center.center.x, circle_center.center.y], p1);
1076 let center_point = [circle_center.center.x, circle_center.center.y];
1077 GetTangentialInfoFromPathsResult::Circle {
1078 center: center_point,
1079 ccw: true,
1080 radius,
1081 }
1082 }
1083 Path::ToPoint { .. } | Path::Horizontal { .. } | Path::AngledLineTo { .. } | Path::Base { .. } => {
1084 let base = self.get_base();
1085 GetTangentialInfoFromPathsResult::PreviousPoint(base.from)
1086 }
1087 }
1088 }
1089}
1090
1091#[rustfmt::skip]
1093fn linear_distance(
1094 [x0, y0]: &[f64; 2],
1095 [x1, y1]: &[f64; 2]
1096) -> f64 {
1097 let y_sq = (y1 - y0).powi(2);
1098 let x_sq = (x1 - x0).powi(2);
1099 (y_sq + x_sq).sqrt()
1100}
1101
1102#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
1104#[ts(export)]
1105#[serde(tag = "type", rename_all = "camelCase")]
1106pub enum ExtrudeSurface {
1107 ExtrudePlane(ExtrudePlane),
1109 ExtrudeArc(ExtrudeArc),
1110 Chamfer(ChamferSurface),
1111 Fillet(FilletSurface),
1112}
1113
1114#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
1116#[ts(export)]
1117#[serde(rename_all = "camelCase")]
1118pub struct ChamferSurface {
1119 pub face_id: uuid::Uuid,
1121 pub tag: Option<Node<TagDeclarator>>,
1123 #[serde(flatten)]
1125 pub geo_meta: GeoMeta,
1126}
1127
1128#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
1130#[ts(export)]
1131#[serde(rename_all = "camelCase")]
1132pub struct FilletSurface {
1133 pub face_id: uuid::Uuid,
1135 pub tag: Option<Node<TagDeclarator>>,
1137 #[serde(flatten)]
1139 pub geo_meta: GeoMeta,
1140}
1141
1142#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
1144#[ts(export)]
1145#[serde(rename_all = "camelCase")]
1146pub struct ExtrudePlane {
1147 pub face_id: uuid::Uuid,
1149 pub tag: Option<Node<TagDeclarator>>,
1151 #[serde(flatten)]
1153 pub geo_meta: GeoMeta,
1154}
1155
1156#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
1158#[ts(export)]
1159#[serde(rename_all = "camelCase")]
1160pub struct ExtrudeArc {
1161 pub face_id: uuid::Uuid,
1163 pub tag: Option<Node<TagDeclarator>>,
1165 #[serde(flatten)]
1167 pub geo_meta: GeoMeta,
1168}
1169
1170impl ExtrudeSurface {
1171 pub fn get_id(&self) -> uuid::Uuid {
1172 match self {
1173 ExtrudeSurface::ExtrudePlane(ep) => ep.geo_meta.id,
1174 ExtrudeSurface::ExtrudeArc(ea) => ea.geo_meta.id,
1175 ExtrudeSurface::Fillet(f) => f.geo_meta.id,
1176 ExtrudeSurface::Chamfer(c) => c.geo_meta.id,
1177 }
1178 }
1179
1180 pub fn get_tag(&self) -> Option<Node<TagDeclarator>> {
1181 match self {
1182 ExtrudeSurface::ExtrudePlane(ep) => ep.tag.clone(),
1183 ExtrudeSurface::ExtrudeArc(ea) => ea.tag.clone(),
1184 ExtrudeSurface::Fillet(f) => f.tag.clone(),
1185 ExtrudeSurface::Chamfer(c) => c.tag.clone(),
1186 }
1187 }
1188}