1use std::ops::{Add, AddAssign, Mul};
2
3use anyhow::Result;
4use indexmap::IndexMap;
5use kittycad_modeling_cmds as kcmc;
6use kittycad_modeling_cmds::length_unit::LengthUnit;
7use parse_display::{Display, FromStr};
8use schemars::JsonSchema;
9use serde::{Deserialize, Serialize};
10
11use super::ArtifactId;
12use crate::{
13 errors::KclError,
14 execution::{ExecState, Metadata, TagEngineInfo, TagIdentifier, UnitLen},
15 parsing::ast::types::{Node, NodeRef, TagDeclarator, TagNode},
16 std::sketch::PlaneData,
17};
18
19type Point2D = kcmc::shared::Point2d<f64>;
20type Point3D = kcmc::shared::Point3d<f64>;
21
22#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
24#[ts(export)]
25#[serde(tag = "type")]
26pub enum Geometry {
27 Sketch(Box<Sketch>),
28 Solid(Box<Solid>),
29}
30
31impl Geometry {
32 pub fn id(&self) -> uuid::Uuid {
33 match self {
34 Geometry::Sketch(s) => s.id,
35 Geometry::Solid(e) => e.id,
36 }
37 }
38
39 pub fn original_id(&self) -> uuid::Uuid {
43 match self {
44 Geometry::Sketch(s) => s.original_id,
45 Geometry::Solid(e) => e.sketch.original_id,
46 }
47 }
48}
49
50#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
52#[ts(export)]
53#[serde(tag = "type")]
54#[allow(clippy::vec_box)]
55pub enum Geometries {
56 Sketches(Vec<Box<Sketch>>),
57 Solids(Vec<Box<Solid>>),
58}
59
60impl From<Geometry> for Geometries {
61 fn from(value: Geometry) -> Self {
62 match value {
63 Geometry::Sketch(x) => Self::Sketches(vec![x]),
64 Geometry::Solid(x) => Self::Solids(vec![x]),
65 }
66 }
67}
68
69#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
71#[ts(export)]
72#[serde(tag = "type", rename_all = "camelCase")]
73#[allow(clippy::vec_box)]
74pub enum SketchSet {
75 Sketch(Box<Sketch>),
76 Sketches(Vec<Box<Sketch>>),
77}
78
79impl SketchSet {
80 pub fn meta(&self) -> Vec<Metadata> {
81 match self {
82 SketchSet::Sketch(sg) => sg.meta.clone(),
83 SketchSet::Sketches(sg) => sg.iter().flat_map(|sg| sg.meta.clone()).collect(),
84 }
85 }
86}
87
88impl From<SketchSet> for Vec<Sketch> {
89 fn from(value: SketchSet) -> Self {
90 match value {
91 SketchSet::Sketch(sg) => vec![*sg],
92 SketchSet::Sketches(sgs) => sgs.into_iter().map(|sg| *sg).collect(),
93 }
94 }
95}
96
97impl From<Sketch> for SketchSet {
98 fn from(sg: Sketch) -> Self {
99 SketchSet::Sketch(Box::new(sg))
100 }
101}
102
103impl From<Box<Sketch>> for SketchSet {
104 fn from(sg: Box<Sketch>) -> Self {
105 SketchSet::Sketch(sg)
106 }
107}
108
109impl From<Vec<Sketch>> for SketchSet {
110 fn from(sg: Vec<Sketch>) -> Self {
111 if sg.len() == 1 {
112 SketchSet::Sketch(Box::new(sg[0].clone()))
113 } else {
114 SketchSet::Sketches(sg.into_iter().map(Box::new).collect())
115 }
116 }
117}
118
119impl From<Vec<Box<Sketch>>> for SketchSet {
120 fn from(sg: Vec<Box<Sketch>>) -> Self {
121 if sg.len() == 1 {
122 SketchSet::Sketch(sg[0].clone())
123 } else {
124 SketchSet::Sketches(sg)
125 }
126 }
127}
128
129impl From<SketchSet> for Vec<Box<Sketch>> {
130 fn from(sg: SketchSet) -> Self {
131 match sg {
132 SketchSet::Sketch(sg) => vec![sg],
133 SketchSet::Sketches(sgs) => sgs,
134 }
135 }
136}
137
138impl From<&Sketch> for Vec<Box<Sketch>> {
139 fn from(sg: &Sketch) -> Self {
140 vec![Box::new(sg.clone())]
141 }
142}
143
144impl From<Box<Sketch>> for Vec<Box<Sketch>> {
145 fn from(sg: Box<Sketch>) -> Self {
146 vec![sg]
147 }
148}
149
150#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
152#[ts(export)]
153#[serde(tag = "type", rename_all = "camelCase")]
154#[allow(clippy::vec_box)]
155pub enum SolidSet {
156 Solid(Box<Solid>),
157 Solids(Vec<Box<Solid>>),
158}
159
160impl From<Solid> for SolidSet {
161 fn from(eg: Solid) -> Self {
162 SolidSet::Solid(Box::new(eg))
163 }
164}
165
166impl From<Box<Solid>> for SolidSet {
167 fn from(eg: Box<Solid>) -> Self {
168 SolidSet::Solid(eg)
169 }
170}
171
172impl From<Vec<Solid>> for SolidSet {
173 fn from(eg: Vec<Solid>) -> Self {
174 if eg.len() == 1 {
175 SolidSet::Solid(Box::new(eg[0].clone()))
176 } else {
177 SolidSet::Solids(eg.into_iter().map(Box::new).collect())
178 }
179 }
180}
181
182impl From<Vec<Box<Solid>>> for SolidSet {
183 fn from(eg: Vec<Box<Solid>>) -> Self {
184 if eg.len() == 1 {
185 SolidSet::Solid(eg[0].clone())
186 } else {
187 SolidSet::Solids(eg)
188 }
189 }
190}
191
192impl From<SolidSet> for Vec<Box<Solid>> {
193 fn from(eg: SolidSet) -> Self {
194 match eg {
195 SolidSet::Solid(eg) => vec![eg],
196 SolidSet::Solids(egs) => egs,
197 }
198 }
199}
200
201impl From<&Solid> for Vec<Box<Solid>> {
202 fn from(eg: &Solid) -> Self {
203 vec![Box::new(eg.clone())]
204 }
205}
206
207impl From<Box<Solid>> for Vec<Box<Solid>> {
208 fn from(eg: Box<Solid>) -> Self {
209 vec![eg]
210 }
211}
212
213#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
215#[ts(export)]
216#[serde(rename_all = "camelCase")]
217pub struct ImportedGeometry {
218 pub id: uuid::Uuid,
220 pub value: Vec<String>,
222 #[serde(rename = "__meta")]
223 pub meta: Vec<Metadata>,
224}
225
226#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
228#[ts(export)]
229#[serde(rename_all = "camelCase")]
230pub struct Helix {
231 pub value: uuid::Uuid,
233 pub artifact_id: ArtifactId,
235 pub revolutions: f64,
237 pub angle_start: f64,
239 pub ccw: bool,
241 pub units: UnitLen,
242 #[serde(rename = "__meta")]
243 pub meta: Vec<Metadata>,
244}
245
246#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
248#[ts(export)]
249#[serde(rename_all = "camelCase")]
250pub struct Plane {
251 pub id: uuid::Uuid,
253 pub artifact_id: ArtifactId,
255 pub value: PlaneType,
257 pub origin: Point3d,
259 pub x_axis: Point3d,
261 pub y_axis: Point3d,
263 pub z_axis: Point3d,
265 pub units: UnitLen,
266 #[serde(rename = "__meta")]
267 pub meta: Vec<Metadata>,
268}
269
270impl Plane {
271 pub(crate) fn into_plane_data(self) -> PlaneData {
272 if self.origin == Point3d::new(0.0, 0.0, 0.0) {
273 match self {
274 Self {
275 origin: Point3d { x: 0.0, y: 0.0, z: 0.0 },
276 x_axis: Point3d { x: 1.0, y: 0.0, z: 0.0 },
277 y_axis: Point3d { x: 0.0, y: 1.0, z: 0.0 },
278 z_axis: Point3d { x: 0.0, y: 0.0, z: 1.0 },
279 ..
280 } => return PlaneData::XY,
281 Self {
282 origin: Point3d { x: 0.0, y: 0.0, z: 0.0 },
283 x_axis: Point3d { x: 1.0, y: 0.0, z: 0.0 },
284 y_axis: Point3d { x: 0.0, y: 1.0, z: 0.0 },
285 z_axis:
286 Point3d {
287 x: 0.0,
288 y: 0.0,
289 z: -1.0,
290 },
291 ..
292 } => return PlaneData::NegXY,
293 Self {
294 origin: Point3d { x: 0.0, y: 0.0, z: 0.0 },
295 x_axis: Point3d { x: 1.0, y: 0.0, z: 0.0 },
296 y_axis: Point3d { x: 0.0, y: 0.0, z: 1.0 },
297 z_axis:
298 Point3d {
299 x: 0.0,
300 y: -1.0,
301 z: 0.0,
302 },
303 ..
304 } => return PlaneData::XZ,
305 Self {
306 origin: Point3d { x: 0.0, y: 0.0, z: 0.0 },
307 x_axis: Point3d { x: 1.0, y: 0.0, z: 0.0 },
308 y_axis: Point3d { x: 0.0, y: 0.0, z: 1.0 },
309 z_axis: Point3d { x: 0.0, y: 1.0, z: 0.0 },
310 ..
311 } => return PlaneData::NegXZ,
312 Self {
313 origin: Point3d { x: 0.0, y: 0.0, z: 0.0 },
314 x_axis: Point3d { x: 0.0, y: 1.0, z: 0.0 },
315 y_axis: Point3d { x: 0.0, y: 0.0, z: 1.0 },
316 z_axis: Point3d { x: 1.0, y: 0.0, z: 0.0 },
317 ..
318 } => return PlaneData::YZ,
319 Self {
320 origin: Point3d { x: 0.0, y: 0.0, z: 0.0 },
321 x_axis: Point3d { x: 0.0, y: 1.0, z: 0.0 },
322 y_axis: Point3d { x: 0.0, y: 0.0, z: 1.0 },
323 z_axis:
324 Point3d {
325 x: -1.0,
326 y: 0.0,
327 z: 0.0,
328 },
329 ..
330 } => return PlaneData::NegYZ,
331 _ => {}
332 }
333 }
334
335 PlaneData::Plane {
336 origin: self.origin,
337 x_axis: self.x_axis,
338 y_axis: self.y_axis,
339 z_axis: self.z_axis,
340 }
341 }
342
343 pub(crate) fn from_plane_data(value: PlaneData, exec_state: &mut ExecState) -> Self {
344 let id = exec_state.global.id_generator.next_uuid();
345 match value {
346 PlaneData::XY => Plane {
347 id,
348 artifact_id: id.into(),
349 origin: Point3d::new(0.0, 0.0, 0.0),
350 x_axis: Point3d::new(1.0, 0.0, 0.0),
351 y_axis: Point3d::new(0.0, 1.0, 0.0),
352 z_axis: Point3d::new(0.0, 0.0, 1.0),
353 value: PlaneType::XY,
354 units: exec_state.length_unit(),
355 meta: vec![],
356 },
357 PlaneData::NegXY => Plane {
358 id,
359 artifact_id: id.into(),
360 origin: Point3d::new(0.0, 0.0, 0.0),
361 x_axis: Point3d::new(1.0, 0.0, 0.0),
362 y_axis: Point3d::new(0.0, 1.0, 0.0),
363 z_axis: Point3d::new(0.0, 0.0, -1.0),
364 value: PlaneType::XY,
365 units: exec_state.length_unit(),
366 meta: vec![],
367 },
368 PlaneData::XZ => Plane {
369 id,
370 artifact_id: id.into(),
371 origin: Point3d::new(0.0, 0.0, 0.0),
372 x_axis: Point3d::new(1.0, 0.0, 0.0),
373 y_axis: Point3d::new(0.0, 0.0, 1.0),
374 z_axis: Point3d::new(0.0, -1.0, 0.0),
375 value: PlaneType::XZ,
376 units: exec_state.length_unit(),
377 meta: vec![],
378 },
379 PlaneData::NegXZ => Plane {
380 id,
381 artifact_id: id.into(),
382 origin: Point3d::new(0.0, 0.0, 0.0),
383 x_axis: Point3d::new(-1.0, 0.0, 0.0),
384 y_axis: Point3d::new(0.0, 0.0, 1.0),
385 z_axis: Point3d::new(0.0, 1.0, 0.0),
386 value: PlaneType::XZ,
387 units: exec_state.length_unit(),
388 meta: vec![],
389 },
390 PlaneData::YZ => Plane {
391 id,
392 artifact_id: id.into(),
393 origin: Point3d::new(0.0, 0.0, 0.0),
394 x_axis: Point3d::new(0.0, 1.0, 0.0),
395 y_axis: Point3d::new(0.0, 0.0, 1.0),
396 z_axis: Point3d::new(1.0, 0.0, 0.0),
397 value: PlaneType::YZ,
398 units: exec_state.length_unit(),
399 meta: vec![],
400 },
401 PlaneData::NegYZ => Plane {
402 id,
403 artifact_id: id.into(),
404 origin: Point3d::new(0.0, 0.0, 0.0),
405 x_axis: Point3d::new(0.0, 1.0, 0.0),
406 y_axis: Point3d::new(0.0, 0.0, 1.0),
407 z_axis: Point3d::new(-1.0, 0.0, 0.0),
408 value: PlaneType::YZ,
409 units: exec_state.length_unit(),
410 meta: vec![],
411 },
412 PlaneData::Plane {
413 origin,
414 x_axis,
415 y_axis,
416 z_axis,
417 } => Plane {
418 id,
419 artifact_id: id.into(),
420 origin,
421 x_axis,
422 y_axis,
423 z_axis,
424 value: PlaneType::Custom,
425 units: exec_state.length_unit(),
426 meta: vec![],
427 },
428 }
429 }
430
431 pub fn is_standard(&self) -> bool {
433 !matches!(self.value, PlaneType::Custom | PlaneType::Uninit)
434 }
435}
436
437#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
439#[ts(export)]
440#[serde(rename_all = "camelCase")]
441pub struct Face {
442 pub id: uuid::Uuid,
444 pub artifact_id: ArtifactId,
446 pub value: String,
448 pub x_axis: Point3d,
450 pub y_axis: Point3d,
452 pub z_axis: Point3d,
454 pub solid: Box<Solid>,
456 pub units: UnitLen,
457 #[serde(rename = "__meta")]
458 pub meta: Vec<Metadata>,
459}
460
461#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, FromStr, Display)]
463#[ts(export)]
464#[display(style = "camelCase")]
465pub enum PlaneType {
466 #[serde(rename = "XY", alias = "xy")]
467 #[display("XY")]
468 XY,
469 #[serde(rename = "XZ", alias = "xz")]
470 #[display("XZ")]
471 XZ,
472 #[serde(rename = "YZ", alias = "yz")]
473 #[display("YZ")]
474 YZ,
475 #[display("Custom")]
477 Custom,
478 #[display("Uninit")]
480 Uninit,
481}
482
483#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
525#[ts(export)]
526#[serde(tag = "type", rename_all = "camelCase")]
527pub struct Sketch {
528 pub id: uuid::Uuid,
530 pub paths: Vec<Path>,
532 pub on: SketchSurface,
534 pub start: BasePath,
536 #[serde(default, skip_serializing_if = "IndexMap::is_empty")]
538 pub tags: IndexMap<String, TagIdentifier>,
539 pub artifact_id: ArtifactId,
542 #[ts(skip)]
543 pub original_id: uuid::Uuid,
544 pub units: UnitLen,
545 #[serde(rename = "__meta")]
547 pub meta: Vec<Metadata>,
548}
549
550#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
552#[ts(export)]
553#[serde(tag = "type", rename_all = "camelCase")]
554pub enum SketchSurface {
555 Plane(Box<Plane>),
556 Face(Box<Face>),
557}
558
559impl SketchSurface {
560 pub(crate) fn id(&self) -> uuid::Uuid {
561 match self {
562 SketchSurface::Plane(plane) => plane.id,
563 SketchSurface::Face(face) => face.id,
564 }
565 }
566 pub(crate) fn x_axis(&self) -> Point3d {
567 match self {
568 SketchSurface::Plane(plane) => plane.x_axis,
569 SketchSurface::Face(face) => face.x_axis,
570 }
571 }
572 pub(crate) fn y_axis(&self) -> Point3d {
573 match self {
574 SketchSurface::Plane(plane) => plane.y_axis,
575 SketchSurface::Face(face) => face.y_axis,
576 }
577 }
578 pub(crate) fn z_axis(&self) -> Point3d {
579 match self {
580 SketchSurface::Plane(plane) => plane.z_axis,
581 SketchSurface::Face(face) => face.z_axis,
582 }
583 }
584 pub(crate) fn units(&self) -> UnitLen {
585 match self {
586 SketchSurface::Plane(plane) => plane.units,
587 SketchSurface::Face(face) => face.units,
588 }
589 }
590}
591
592#[derive(Debug, Clone)]
593pub(crate) enum GetTangentialInfoFromPathsResult {
594 PreviousPoint([f64; 2]),
595 Arc { center: [f64; 2], ccw: bool },
596 Circle { center: [f64; 2], ccw: bool, radius: f64 },
597}
598
599impl GetTangentialInfoFromPathsResult {
600 pub(crate) fn tan_previous_point(&self, last_arc_end: crate::std::utils::Coords2d) -> [f64; 2] {
601 match self {
602 GetTangentialInfoFromPathsResult::PreviousPoint(p) => *p,
603 GetTangentialInfoFromPathsResult::Arc { center, ccw, .. } => {
604 crate::std::utils::get_tangent_point_from_previous_arc(*center, *ccw, last_arc_end)
605 }
606 GetTangentialInfoFromPathsResult::Circle {
609 center, radius, ccw, ..
610 } => [center[0] + radius, center[1] + if *ccw { -1.0 } else { 1.0 }],
611 }
612 }
613}
614
615impl Sketch {
616 pub(crate) fn add_tag(&mut self, tag: NodeRef<'_, TagDeclarator>, current_path: &Path) {
617 let mut tag_identifier: TagIdentifier = tag.into();
618 let base = current_path.get_base();
619 tag_identifier.info = Some(TagEngineInfo {
620 id: base.geo_meta.id,
621 sketch: self.id,
622 path: Some(current_path.clone()),
623 surface: None,
624 });
625
626 self.tags.insert(tag.name.to_string(), tag_identifier);
627 }
628
629 pub(crate) fn latest_path(&self) -> Option<&Path> {
631 self.paths.last()
632 }
633
634 pub(crate) fn current_pen_position(&self) -> Result<Point2d, KclError> {
638 let Some(path) = self.latest_path() else {
639 return Ok(self.start.to.into());
640 };
641
642 let base = path.get_base();
643 Ok(base.to.into())
644 }
645
646 pub(crate) fn get_tangential_info_from_paths(&self) -> GetTangentialInfoFromPathsResult {
647 let Some(path) = self.latest_path() else {
648 return GetTangentialInfoFromPathsResult::PreviousPoint(self.start.to);
649 };
650 path.get_tangential_info()
651 }
652}
653
654#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
698#[ts(export)]
699#[serde(tag = "type", rename_all = "camelCase")]
700pub struct Solid {
701 pub id: uuid::Uuid,
703 pub artifact_id: ArtifactId,
705 pub value: Vec<ExtrudeSurface>,
707 pub sketch: Sketch,
709 pub height: f64,
711 pub start_cap_id: Option<uuid::Uuid>,
713 pub end_cap_id: Option<uuid::Uuid>,
715 #[serde(default, skip_serializing_if = "Vec::is_empty")]
717 pub edge_cuts: Vec<EdgeCut>,
718 pub units: UnitLen,
719 #[serde(rename = "__meta")]
721 pub meta: Vec<Metadata>,
722}
723
724impl Solid {
725 pub(crate) fn get_all_edge_cut_ids(&self) -> impl Iterator<Item = uuid::Uuid> + '_ {
726 self.edge_cuts.iter().map(|foc| foc.id())
727 }
728}
729
730#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
732#[ts(export)]
733#[serde(tag = "type", rename_all = "camelCase")]
734pub enum EdgeCut {
735 Fillet {
737 id: uuid::Uuid,
739 radius: f64,
740 #[serde(rename = "edgeId")]
742 edge_id: uuid::Uuid,
743 tag: Box<Option<TagNode>>,
744 },
745 Chamfer {
747 id: uuid::Uuid,
749 length: f64,
750 #[serde(rename = "edgeId")]
752 edge_id: uuid::Uuid,
753 tag: Box<Option<TagNode>>,
754 },
755}
756
757impl EdgeCut {
758 pub fn id(&self) -> uuid::Uuid {
759 match self {
760 EdgeCut::Fillet { id, .. } => *id,
761 EdgeCut::Chamfer { id, .. } => *id,
762 }
763 }
764
765 pub fn edge_id(&self) -> uuid::Uuid {
766 match self {
767 EdgeCut::Fillet { edge_id, .. } => *edge_id,
768 EdgeCut::Chamfer { edge_id, .. } => *edge_id,
769 }
770 }
771
772 pub fn tag(&self) -> Option<TagNode> {
773 match self {
774 EdgeCut::Fillet { tag, .. } => *tag.clone(),
775 EdgeCut::Chamfer { tag, .. } => *tag.clone(),
776 }
777 }
778}
779
780#[derive(Debug, Deserialize, Serialize, PartialEq, Clone, Copy, ts_rs::TS, JsonSchema)]
781#[ts(export)]
782pub struct Point2d {
783 pub x: f64,
784 pub y: f64,
785}
786
787impl From<[f64; 2]> for Point2d {
788 fn from(p: [f64; 2]) -> Self {
789 Self { x: p[0], y: p[1] }
790 }
791}
792
793impl From<&[f64; 2]> for Point2d {
794 fn from(p: &[f64; 2]) -> Self {
795 Self { x: p[0], y: p[1] }
796 }
797}
798
799impl From<Point2d> for [f64; 2] {
800 fn from(p: Point2d) -> Self {
801 [p.x, p.y]
802 }
803}
804
805impl From<Point2d> for Point2D {
806 fn from(p: Point2d) -> Self {
807 Self { x: p.x, y: p.y }
808 }
809}
810
811impl Point2d {
812 pub const ZERO: Self = Self { x: 0.0, y: 0.0 };
813 pub fn scale(self, scalar: f64) -> Self {
814 Self {
815 x: self.x * scalar,
816 y: self.y * scalar,
817 }
818 }
819}
820
821#[derive(Debug, Deserialize, Serialize, PartialEq, Clone, Copy, ts_rs::TS, JsonSchema, Default)]
822#[ts(export)]
823pub struct Point3d {
824 pub x: f64,
825 pub y: f64,
826 pub z: f64,
827}
828
829impl Point3d {
830 pub const ZERO: Self = Self { x: 0.0, y: 0.0, z: 0.0 };
831 pub fn new(x: f64, y: f64, z: f64) -> Self {
832 Self { x, y, z }
833 }
834}
835
836impl From<Point3d> for Point3D {
837 fn from(p: Point3d) -> Self {
838 Self { x: p.x, y: p.y, z: p.z }
839 }
840}
841impl From<Point3d> for kittycad_modeling_cmds::shared::Point3d<LengthUnit> {
842 fn from(p: Point3d) -> Self {
843 Self {
844 x: LengthUnit(p.x),
845 y: LengthUnit(p.y),
846 z: LengthUnit(p.z),
847 }
848 }
849}
850
851impl Add for Point3d {
852 type Output = Point3d;
853
854 fn add(self, rhs: Self) -> Self::Output {
855 Point3d {
856 x: self.x + rhs.x,
857 y: self.y + rhs.y,
858 z: self.z + rhs.z,
859 }
860 }
861}
862
863impl AddAssign for Point3d {
864 fn add_assign(&mut self, rhs: Self) {
865 *self = *self + rhs
866 }
867}
868
869impl Mul<f64> for Point3d {
870 type Output = Point3d;
871
872 fn mul(self, rhs: f64) -> Self::Output {
873 Point3d {
874 x: self.x * rhs,
875 y: self.y * rhs,
876 z: self.z * rhs,
877 }
878 }
879}
880
881#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
883#[ts(export)]
884#[serde(rename_all = "camelCase")]
885pub struct BasePath {
886 #[ts(type = "[number, number]")]
888 pub from: [f64; 2],
889 #[ts(type = "[number, number]")]
891 pub to: [f64; 2],
892 pub units: UnitLen,
893 pub tag: Option<TagNode>,
895 #[serde(rename = "__geoMeta")]
897 pub geo_meta: GeoMeta,
898}
899
900#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
902#[ts(export)]
903#[serde(rename_all = "camelCase")]
904pub struct GeoMeta {
905 pub id: uuid::Uuid,
907 #[serde(flatten)]
909 pub metadata: Metadata,
910}
911
912#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
914#[ts(export)]
915#[serde(tag = "type")]
916pub enum Path {
917 ToPoint {
919 #[serde(flatten)]
920 base: BasePath,
921 },
922 TangentialArcTo {
924 #[serde(flatten)]
925 base: BasePath,
926 #[ts(type = "[number, number]")]
928 center: [f64; 2],
929 ccw: bool,
931 },
932 TangentialArc {
934 #[serde(flatten)]
935 base: BasePath,
936 #[ts(type = "[number, number]")]
938 center: [f64; 2],
939 ccw: bool,
941 },
942 Circle {
945 #[serde(flatten)]
946 base: BasePath,
947 #[ts(type = "[number, number]")]
949 center: [f64; 2],
950 radius: f64,
952 ccw: bool,
955 },
956 CircleThreePoint {
957 #[serde(flatten)]
958 base: BasePath,
959 #[ts(type = "[number, number]")]
961 p1: [f64; 2],
962 #[ts(type = "[number, number]")]
964 p2: [f64; 2],
965 #[ts(type = "[number, number]")]
967 p3: [f64; 2],
968 },
969 Horizontal {
971 #[serde(flatten)]
972 base: BasePath,
973 x: f64,
975 },
976 AngledLineTo {
978 #[serde(flatten)]
979 base: BasePath,
980 x: Option<f64>,
982 y: Option<f64>,
984 },
985 Base {
987 #[serde(flatten)]
988 base: BasePath,
989 },
990 Arc {
992 #[serde(flatten)]
993 base: BasePath,
994 center: [f64; 2],
996 radius: f64,
998 ccw: bool,
1000 },
1001}
1002
1003#[derive(Display)]
1005enum PathType {
1006 ToPoint,
1007 Base,
1008 TangentialArc,
1009 TangentialArcTo,
1010 Circle,
1011 CircleThreePoint,
1012 Horizontal,
1013 AngledLineTo,
1014 Arc,
1015}
1016
1017impl From<&Path> for PathType {
1018 fn from(value: &Path) -> Self {
1019 match value {
1020 Path::ToPoint { .. } => Self::ToPoint,
1021 Path::TangentialArcTo { .. } => Self::TangentialArcTo,
1022 Path::TangentialArc { .. } => Self::TangentialArc,
1023 Path::Circle { .. } => Self::Circle,
1024 Path::CircleThreePoint { .. } => Self::CircleThreePoint,
1025 Path::Horizontal { .. } => Self::Horizontal,
1026 Path::AngledLineTo { .. } => Self::AngledLineTo,
1027 Path::Base { .. } => Self::Base,
1028 Path::Arc { .. } => Self::Arc,
1029 }
1030 }
1031}
1032
1033impl Path {
1034 pub fn get_id(&self) -> uuid::Uuid {
1035 match self {
1036 Path::ToPoint { base } => base.geo_meta.id,
1037 Path::Horizontal { base, .. } => base.geo_meta.id,
1038 Path::AngledLineTo { base, .. } => base.geo_meta.id,
1039 Path::Base { base } => base.geo_meta.id,
1040 Path::TangentialArcTo { base, .. } => base.geo_meta.id,
1041 Path::TangentialArc { base, .. } => base.geo_meta.id,
1042 Path::Circle { base, .. } => base.geo_meta.id,
1043 Path::CircleThreePoint { base, .. } => base.geo_meta.id,
1044 Path::Arc { base, .. } => base.geo_meta.id,
1045 }
1046 }
1047
1048 pub fn get_tag(&self) -> Option<TagNode> {
1049 match self {
1050 Path::ToPoint { base } => base.tag.clone(),
1051 Path::Horizontal { base, .. } => base.tag.clone(),
1052 Path::AngledLineTo { base, .. } => base.tag.clone(),
1053 Path::Base { base } => base.tag.clone(),
1054 Path::TangentialArcTo { base, .. } => base.tag.clone(),
1055 Path::TangentialArc { base, .. } => base.tag.clone(),
1056 Path::Circle { base, .. } => base.tag.clone(),
1057 Path::CircleThreePoint { base, .. } => base.tag.clone(),
1058 Path::Arc { base, .. } => base.tag.clone(),
1059 }
1060 }
1061
1062 pub fn get_base(&self) -> &BasePath {
1063 match self {
1064 Path::ToPoint { base } => base,
1065 Path::Horizontal { base, .. } => base,
1066 Path::AngledLineTo { base, .. } => base,
1067 Path::Base { base } => base,
1068 Path::TangentialArcTo { base, .. } => base,
1069 Path::TangentialArc { base, .. } => base,
1070 Path::Circle { base, .. } => base,
1071 Path::CircleThreePoint { base, .. } => base,
1072 Path::Arc { base, .. } => base,
1073 }
1074 }
1075
1076 pub fn get_from(&self) -> &[f64; 2] {
1078 &self.get_base().from
1079 }
1080 pub fn get_to(&self) -> &[f64; 2] {
1082 &self.get_base().to
1083 }
1084
1085 pub fn length(&self) -> f64 {
1087 match self {
1088 Self::ToPoint { .. } | Self::Base { .. } | Self::Horizontal { .. } | Self::AngledLineTo { .. } => {
1089 linear_distance(self.get_from(), self.get_to())
1090 }
1091 Self::TangentialArc {
1092 base: _,
1093 center,
1094 ccw: _,
1095 }
1096 | Self::TangentialArcTo {
1097 base: _,
1098 center,
1099 ccw: _,
1100 } => {
1101 let radius = linear_distance(self.get_from(), center);
1104 debug_assert_eq!(radius, linear_distance(self.get_to(), center));
1105 linear_distance(self.get_from(), self.get_to())
1107 }
1108 Self::Circle { radius, .. } => 2.0 * std::f64::consts::PI * radius,
1109 Self::CircleThreePoint { .. } => {
1110 let circle_center = crate::std::utils::calculate_circle_from_3_points([
1111 self.get_base().from.into(),
1112 self.get_base().to.into(),
1113 self.get_base().to.into(),
1114 ]);
1115 let radius = linear_distance(&[circle_center.center.x, circle_center.center.y], &self.get_base().from);
1116 2.0 * std::f64::consts::PI * radius
1117 }
1118 Self::Arc { .. } => {
1119 linear_distance(self.get_from(), self.get_to())
1121 }
1122 }
1123 }
1124
1125 pub fn get_base_mut(&mut self) -> Option<&mut BasePath> {
1126 match self {
1127 Path::ToPoint { base } => Some(base),
1128 Path::Horizontal { base, .. } => Some(base),
1129 Path::AngledLineTo { base, .. } => Some(base),
1130 Path::Base { base } => Some(base),
1131 Path::TangentialArcTo { base, .. } => Some(base),
1132 Path::TangentialArc { base, .. } => Some(base),
1133 Path::Circle { base, .. } => Some(base),
1134 Path::CircleThreePoint { base, .. } => Some(base),
1135 Path::Arc { base, .. } => Some(base),
1136 }
1137 }
1138
1139 pub(crate) fn get_tangential_info(&self) -> GetTangentialInfoFromPathsResult {
1140 match self {
1141 Path::TangentialArc { center, ccw, .. }
1142 | Path::TangentialArcTo { center, ccw, .. }
1143 | Path::Arc { center, ccw, .. } => GetTangentialInfoFromPathsResult::Arc {
1144 center: *center,
1145 ccw: *ccw,
1146 },
1147 Path::Circle {
1148 center, ccw, radius, ..
1149 } => GetTangentialInfoFromPathsResult::Circle {
1150 center: *center,
1151 ccw: *ccw,
1152 radius: *radius,
1153 },
1154 Path::CircleThreePoint { p1, p2, p3, .. } => {
1155 let circle_center =
1156 crate::std::utils::calculate_circle_from_3_points([(*p1).into(), (*p2).into(), (*p3).into()]);
1157 let radius = linear_distance(&[circle_center.center.x, circle_center.center.y], p1);
1158 let center_point = [circle_center.center.x, circle_center.center.y];
1159 GetTangentialInfoFromPathsResult::Circle {
1160 center: center_point,
1161 ccw: true,
1162 radius,
1163 }
1164 }
1165 Path::ToPoint { .. } | Path::Horizontal { .. } | Path::AngledLineTo { .. } | Path::Base { .. } => {
1166 let base = self.get_base();
1167 GetTangentialInfoFromPathsResult::PreviousPoint(base.from)
1168 }
1169 }
1170 }
1171}
1172
1173#[rustfmt::skip]
1175fn linear_distance(
1176 [x0, y0]: &[f64; 2],
1177 [x1, y1]: &[f64; 2]
1178) -> f64 {
1179 let y_sq = (y1 - y0).powi(2);
1180 let x_sq = (x1 - x0).powi(2);
1181 (y_sq + x_sq).sqrt()
1182}
1183
1184#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
1186#[ts(export)]
1187#[serde(tag = "type", rename_all = "camelCase")]
1188pub enum ExtrudeSurface {
1189 ExtrudePlane(ExtrudePlane),
1191 ExtrudeArc(ExtrudeArc),
1192 Chamfer(ChamferSurface),
1193 Fillet(FilletSurface),
1194}
1195
1196#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
1198#[ts(export)]
1199#[serde(rename_all = "camelCase")]
1200pub struct ChamferSurface {
1201 pub face_id: uuid::Uuid,
1203 pub tag: Option<Node<TagDeclarator>>,
1205 #[serde(flatten)]
1207 pub geo_meta: GeoMeta,
1208}
1209
1210#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
1212#[ts(export)]
1213#[serde(rename_all = "camelCase")]
1214pub struct FilletSurface {
1215 pub face_id: uuid::Uuid,
1217 pub tag: Option<Node<TagDeclarator>>,
1219 #[serde(flatten)]
1221 pub geo_meta: GeoMeta,
1222}
1223
1224#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
1226#[ts(export)]
1227#[serde(rename_all = "camelCase")]
1228pub struct ExtrudePlane {
1229 pub face_id: uuid::Uuid,
1231 pub tag: Option<Node<TagDeclarator>>,
1233 #[serde(flatten)]
1235 pub geo_meta: GeoMeta,
1236}
1237
1238#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
1240#[ts(export)]
1241#[serde(rename_all = "camelCase")]
1242pub struct ExtrudeArc {
1243 pub face_id: uuid::Uuid,
1245 pub tag: Option<Node<TagDeclarator>>,
1247 #[serde(flatten)]
1249 pub geo_meta: GeoMeta,
1250}
1251
1252impl ExtrudeSurface {
1253 pub fn get_id(&self) -> uuid::Uuid {
1254 match self {
1255 ExtrudeSurface::ExtrudePlane(ep) => ep.geo_meta.id,
1256 ExtrudeSurface::ExtrudeArc(ea) => ea.geo_meta.id,
1257 ExtrudeSurface::Fillet(f) => f.geo_meta.id,
1258 ExtrudeSurface::Chamfer(c) => c.geo_meta.id,
1259 }
1260 }
1261
1262 pub fn get_tag(&self) -> Option<Node<TagDeclarator>> {
1263 match self {
1264 ExtrudeSurface::ExtrudePlane(ep) => ep.tag.clone(),
1265 ExtrudeSurface::ExtrudeArc(ea) => ea.tag.clone(),
1266 ExtrudeSurface::Fillet(f) => f.tag.clone(),
1267 ExtrudeSurface::Chamfer(c) => c.tag.clone(),
1268 }
1269 }
1270}