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