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)]
247#[ts(export)]
248#[serde(rename_all = "camelCase")]
249pub struct Plane {
250 pub id: uuid::Uuid,
252 pub artifact_id: ArtifactId,
254 pub value: PlaneType,
256 pub origin: Point3d,
258 pub x_axis: Point3d,
260 pub y_axis: Point3d,
262 pub z_axis: Point3d,
264 pub units: UnitLen,
265 #[serde(rename = "__meta")]
266 pub meta: Vec<Metadata>,
267}
268
269impl Plane {
270 pub(crate) fn into_plane_data(self) -> PlaneData {
271 if self.origin == Point3d::new(0.0, 0.0, 0.0) {
272 match self {
273 Self {
274 origin: Point3d { x: 0.0, y: 0.0, z: 0.0 },
275 x_axis: Point3d { x: 1.0, y: 0.0, z: 0.0 },
276 y_axis: Point3d { x: 0.0, y: 1.0, z: 0.0 },
277 z_axis: Point3d { x: 0.0, y: 0.0, z: 1.0 },
278 ..
279 } => return PlaneData::XY,
280 Self {
281 origin: Point3d { x: 0.0, y: 0.0, z: 0.0 },
282 x_axis: Point3d { x: 1.0, y: 0.0, z: 0.0 },
283 y_axis: Point3d { x: 0.0, y: 1.0, z: 0.0 },
284 z_axis:
285 Point3d {
286 x: 0.0,
287 y: 0.0,
288 z: -1.0,
289 },
290 ..
291 } => return PlaneData::NegXY,
292 Self {
293 origin: Point3d { x: 0.0, y: 0.0, z: 0.0 },
294 x_axis: Point3d { x: 1.0, y: 0.0, z: 0.0 },
295 y_axis: Point3d { x: 0.0, y: 0.0, z: 1.0 },
296 z_axis:
297 Point3d {
298 x: 0.0,
299 y: -1.0,
300 z: 0.0,
301 },
302 ..
303 } => return PlaneData::XZ,
304 Self {
305 origin: Point3d { x: 0.0, y: 0.0, z: 0.0 },
306 x_axis: Point3d { x: 1.0, y: 0.0, z: 0.0 },
307 y_axis: Point3d { x: 0.0, y: 0.0, z: 1.0 },
308 z_axis: Point3d { x: 0.0, y: 1.0, z: 0.0 },
309 ..
310 } => return PlaneData::NegXZ,
311 Self {
312 origin: Point3d { x: 0.0, y: 0.0, z: 0.0 },
313 x_axis: Point3d { x: 0.0, y: 1.0, z: 0.0 },
314 y_axis: Point3d { x: 0.0, y: 0.0, z: 1.0 },
315 z_axis: Point3d { x: 1.0, y: 0.0, z: 0.0 },
316 ..
317 } => return PlaneData::YZ,
318 Self {
319 origin: Point3d { x: 0.0, y: 0.0, z: 0.0 },
320 x_axis: Point3d { x: 0.0, y: 1.0, z: 0.0 },
321 y_axis: Point3d { x: 0.0, y: 0.0, z: 1.0 },
322 z_axis:
323 Point3d {
324 x: -1.0,
325 y: 0.0,
326 z: 0.0,
327 },
328 ..
329 } => return PlaneData::NegYZ,
330 _ => {}
331 }
332 }
333
334 PlaneData::Plane {
335 origin: self.origin,
336 x_axis: self.x_axis,
337 y_axis: self.y_axis,
338 z_axis: self.z_axis,
339 }
340 }
341
342 pub(crate) fn from_plane_data(value: PlaneData, exec_state: &mut ExecState) -> Self {
343 let id = exec_state.global.id_generator.next_uuid();
344 match value {
345 PlaneData::XY => Plane {
346 id,
347 artifact_id: id.into(),
348 origin: Point3d::new(0.0, 0.0, 0.0),
349 x_axis: Point3d::new(1.0, 0.0, 0.0),
350 y_axis: Point3d::new(0.0, 1.0, 0.0),
351 z_axis: Point3d::new(0.0, 0.0, 1.0),
352 value: PlaneType::XY,
353 units: exec_state.length_unit(),
354 meta: vec![],
355 },
356 PlaneData::NegXY => Plane {
357 id,
358 artifact_id: id.into(),
359 origin: Point3d::new(0.0, 0.0, 0.0),
360 x_axis: Point3d::new(1.0, 0.0, 0.0),
361 y_axis: Point3d::new(0.0, 1.0, 0.0),
362 z_axis: Point3d::new(0.0, 0.0, -1.0),
363 value: PlaneType::XY,
364 units: exec_state.length_unit(),
365 meta: vec![],
366 },
367 PlaneData::XZ => Plane {
368 id,
369 artifact_id: id.into(),
370 origin: Point3d::new(0.0, 0.0, 0.0),
371 x_axis: Point3d::new(1.0, 0.0, 0.0),
372 y_axis: Point3d::new(0.0, 0.0, 1.0),
373 z_axis: Point3d::new(0.0, -1.0, 0.0),
374 value: PlaneType::XZ,
375 units: exec_state.length_unit(),
376 meta: vec![],
377 },
378 PlaneData::NegXZ => Plane {
379 id,
380 artifact_id: id.into(),
381 origin: Point3d::new(0.0, 0.0, 0.0),
382 x_axis: Point3d::new(-1.0, 0.0, 0.0),
383 y_axis: Point3d::new(0.0, 0.0, 1.0),
384 z_axis: Point3d::new(0.0, 1.0, 0.0),
385 value: PlaneType::XZ,
386 units: exec_state.length_unit(),
387 meta: vec![],
388 },
389 PlaneData::YZ => Plane {
390 id,
391 artifact_id: id.into(),
392 origin: Point3d::new(0.0, 0.0, 0.0),
393 x_axis: Point3d::new(0.0, 1.0, 0.0),
394 y_axis: Point3d::new(0.0, 0.0, 1.0),
395 z_axis: Point3d::new(1.0, 0.0, 0.0),
396 value: PlaneType::YZ,
397 units: exec_state.length_unit(),
398 meta: vec![],
399 },
400 PlaneData::NegYZ => Plane {
401 id,
402 artifact_id: id.into(),
403 origin: Point3d::new(0.0, 0.0, 0.0),
404 x_axis: Point3d::new(0.0, 1.0, 0.0),
405 y_axis: Point3d::new(0.0, 0.0, 1.0),
406 z_axis: Point3d::new(-1.0, 0.0, 0.0),
407 value: PlaneType::YZ,
408 units: exec_state.length_unit(),
409 meta: vec![],
410 },
411 PlaneData::Plane {
412 origin,
413 x_axis,
414 y_axis,
415 z_axis,
416 } => Plane {
417 id,
418 artifact_id: id.into(),
419 origin,
420 x_axis,
421 y_axis,
422 z_axis,
423 value: PlaneType::Custom,
424 units: exec_state.length_unit(),
425 meta: vec![],
426 },
427 }
428 }
429
430 pub fn is_standard(&self) -> bool {
432 !matches!(self.value, PlaneType::Custom | PlaneType::Uninit)
433 }
434}
435
436#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
438#[ts(export)]
439#[serde(rename_all = "camelCase")]
440pub struct Face {
441 pub id: uuid::Uuid,
443 pub artifact_id: ArtifactId,
445 pub value: String,
447 pub x_axis: Point3d,
449 pub y_axis: Point3d,
451 pub z_axis: Point3d,
453 pub solid: Box<Solid>,
455 pub units: UnitLen,
456 #[serde(rename = "__meta")]
457 pub meta: Vec<Metadata>,
458}
459
460#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, FromStr, Display)]
462#[ts(export)]
463#[display(style = "camelCase")]
464pub enum PlaneType {
465 #[serde(rename = "XY", alias = "xy")]
466 #[display("XY")]
467 XY,
468 #[serde(rename = "XZ", alias = "xz")]
469 #[display("XZ")]
470 XZ,
471 #[serde(rename = "YZ", alias = "yz")]
472 #[display("YZ")]
473 YZ,
474 #[display("Custom")]
476 Custom,
477 #[display("Uninit")]
479 Uninit,
480}
481
482#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
483#[ts(export)]
484#[serde(tag = "type", rename_all = "camelCase")]
485pub struct Sketch {
486 pub id: uuid::Uuid,
488 pub paths: Vec<Path>,
490 pub on: SketchSurface,
492 pub start: BasePath,
494 #[serde(default, skip_serializing_if = "IndexMap::is_empty")]
496 pub tags: IndexMap<String, TagIdentifier>,
497 pub artifact_id: ArtifactId,
500 #[ts(skip)]
501 pub original_id: uuid::Uuid,
502 pub units: UnitLen,
503 #[serde(rename = "__meta")]
505 pub meta: Vec<Metadata>,
506}
507
508#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
510#[ts(export)]
511#[serde(tag = "type", rename_all = "camelCase")]
512pub enum SketchSurface {
513 Plane(Box<Plane>),
514 Face(Box<Face>),
515}
516
517impl SketchSurface {
518 pub(crate) fn id(&self) -> uuid::Uuid {
519 match self {
520 SketchSurface::Plane(plane) => plane.id,
521 SketchSurface::Face(face) => face.id,
522 }
523 }
524 pub(crate) fn x_axis(&self) -> Point3d {
525 match self {
526 SketchSurface::Plane(plane) => plane.x_axis,
527 SketchSurface::Face(face) => face.x_axis,
528 }
529 }
530 pub(crate) fn y_axis(&self) -> Point3d {
531 match self {
532 SketchSurface::Plane(plane) => plane.y_axis,
533 SketchSurface::Face(face) => face.y_axis,
534 }
535 }
536 pub(crate) fn z_axis(&self) -> Point3d {
537 match self {
538 SketchSurface::Plane(plane) => plane.z_axis,
539 SketchSurface::Face(face) => face.z_axis,
540 }
541 }
542 pub(crate) fn units(&self) -> UnitLen {
543 match self {
544 SketchSurface::Plane(plane) => plane.units,
545 SketchSurface::Face(face) => face.units,
546 }
547 }
548}
549
550#[derive(Debug, Clone)]
551pub(crate) enum GetTangentialInfoFromPathsResult {
552 PreviousPoint([f64; 2]),
553 Arc { center: [f64; 2], ccw: bool },
554 Circle { center: [f64; 2], ccw: bool, radius: f64 },
555}
556
557impl GetTangentialInfoFromPathsResult {
558 pub(crate) fn tan_previous_point(&self, last_arc_end: crate::std::utils::Coords2d) -> [f64; 2] {
559 match self {
560 GetTangentialInfoFromPathsResult::PreviousPoint(p) => *p,
561 GetTangentialInfoFromPathsResult::Arc { center, ccw, .. } => {
562 crate::std::utils::get_tangent_point_from_previous_arc(*center, *ccw, last_arc_end)
563 }
564 GetTangentialInfoFromPathsResult::Circle {
567 center, radius, ccw, ..
568 } => [center[0] + radius, center[1] + if *ccw { -1.0 } else { 1.0 }],
569 }
570 }
571}
572
573impl Sketch {
574 pub(crate) fn add_tag(&mut self, tag: NodeRef<'_, TagDeclarator>, current_path: &Path) {
575 let mut tag_identifier: TagIdentifier = tag.into();
576 let base = current_path.get_base();
577 tag_identifier.info = Some(TagEngineInfo {
578 id: base.geo_meta.id,
579 sketch: self.id,
580 path: Some(current_path.clone()),
581 surface: None,
582 });
583
584 self.tags.insert(tag.name.to_string(), tag_identifier);
585 }
586
587 pub(crate) fn latest_path(&self) -> Option<&Path> {
589 self.paths.last()
590 }
591
592 pub(crate) fn current_pen_position(&self) -> Result<Point2d, KclError> {
596 let Some(path) = self.latest_path() else {
597 return Ok(self.start.to.into());
598 };
599
600 let base = path.get_base();
601 Ok(base.to.into())
602 }
603
604 pub(crate) fn get_tangential_info_from_paths(&self) -> GetTangentialInfoFromPathsResult {
605 let Some(path) = self.latest_path() else {
606 return GetTangentialInfoFromPathsResult::PreviousPoint(self.start.to);
607 };
608 path.get_tangential_info()
609 }
610}
611
612#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
613#[ts(export)]
614#[serde(tag = "type", rename_all = "camelCase")]
615pub struct Solid {
616 pub id: uuid::Uuid,
618 pub artifact_id: ArtifactId,
620 pub value: Vec<ExtrudeSurface>,
622 pub sketch: Sketch,
624 pub height: f64,
626 pub start_cap_id: Option<uuid::Uuid>,
628 pub end_cap_id: Option<uuid::Uuid>,
630 #[serde(default, skip_serializing_if = "Vec::is_empty")]
632 pub edge_cuts: Vec<EdgeCut>,
633 pub units: UnitLen,
634 #[serde(rename = "__meta")]
636 pub meta: Vec<Metadata>,
637}
638
639impl Solid {
640 pub(crate) fn get_all_edge_cut_ids(&self) -> impl Iterator<Item = uuid::Uuid> + '_ {
641 self.edge_cuts.iter().map(|foc| foc.id())
642 }
643}
644
645#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
647#[ts(export)]
648#[serde(tag = "type", rename_all = "camelCase")]
649pub enum EdgeCut {
650 Fillet {
652 id: uuid::Uuid,
654 radius: f64,
655 #[serde(rename = "edgeId")]
657 edge_id: uuid::Uuid,
658 tag: Box<Option<TagNode>>,
659 },
660 Chamfer {
662 id: uuid::Uuid,
664 length: f64,
665 #[serde(rename = "edgeId")]
667 edge_id: uuid::Uuid,
668 tag: Box<Option<TagNode>>,
669 },
670}
671
672impl EdgeCut {
673 pub fn id(&self) -> uuid::Uuid {
674 match self {
675 EdgeCut::Fillet { id, .. } => *id,
676 EdgeCut::Chamfer { id, .. } => *id,
677 }
678 }
679
680 pub fn edge_id(&self) -> uuid::Uuid {
681 match self {
682 EdgeCut::Fillet { edge_id, .. } => *edge_id,
683 EdgeCut::Chamfer { edge_id, .. } => *edge_id,
684 }
685 }
686
687 pub fn tag(&self) -> Option<TagNode> {
688 match self {
689 EdgeCut::Fillet { tag, .. } => *tag.clone(),
690 EdgeCut::Chamfer { tag, .. } => *tag.clone(),
691 }
692 }
693}
694
695#[derive(Debug, Deserialize, Serialize, PartialEq, Clone, Copy, ts_rs::TS, JsonSchema)]
696#[ts(export)]
697pub struct Point2d {
698 pub x: f64,
699 pub y: f64,
700}
701
702impl From<[f64; 2]> for Point2d {
703 fn from(p: [f64; 2]) -> Self {
704 Self { x: p[0], y: p[1] }
705 }
706}
707
708impl From<&[f64; 2]> for Point2d {
709 fn from(p: &[f64; 2]) -> Self {
710 Self { x: p[0], y: p[1] }
711 }
712}
713
714impl From<Point2d> for [f64; 2] {
715 fn from(p: Point2d) -> Self {
716 [p.x, p.y]
717 }
718}
719
720impl From<Point2d> for Point2D {
721 fn from(p: Point2d) -> Self {
722 Self { x: p.x, y: p.y }
723 }
724}
725
726impl Point2d {
727 pub const ZERO: Self = Self { x: 0.0, y: 0.0 };
728 pub fn scale(self, scalar: f64) -> Self {
729 Self {
730 x: self.x * scalar,
731 y: self.y * scalar,
732 }
733 }
734}
735
736#[derive(Debug, Deserialize, Serialize, PartialEq, Clone, Copy, ts_rs::TS, JsonSchema, Default)]
737#[ts(export)]
738pub struct Point3d {
739 pub x: f64,
740 pub y: f64,
741 pub z: f64,
742}
743
744impl Point3d {
745 pub const ZERO: Self = Self { x: 0.0, y: 0.0, z: 0.0 };
746 pub fn new(x: f64, y: f64, z: f64) -> Self {
747 Self { x, y, z }
748 }
749}
750
751impl From<Point3d> for Point3D {
752 fn from(p: Point3d) -> Self {
753 Self { x: p.x, y: p.y, z: p.z }
754 }
755}
756impl From<Point3d> for kittycad_modeling_cmds::shared::Point3d<LengthUnit> {
757 fn from(p: Point3d) -> Self {
758 Self {
759 x: LengthUnit(p.x),
760 y: LengthUnit(p.y),
761 z: LengthUnit(p.z),
762 }
763 }
764}
765
766impl Add for Point3d {
767 type Output = Point3d;
768
769 fn add(self, rhs: Self) -> Self::Output {
770 Point3d {
771 x: self.x + rhs.x,
772 y: self.y + rhs.y,
773 z: self.z + rhs.z,
774 }
775 }
776}
777
778impl AddAssign for Point3d {
779 fn add_assign(&mut self, rhs: Self) {
780 *self = *self + rhs
781 }
782}
783
784impl Mul<f64> for Point3d {
785 type Output = Point3d;
786
787 fn mul(self, rhs: f64) -> Self::Output {
788 Point3d {
789 x: self.x * rhs,
790 y: self.y * rhs,
791 z: self.z * rhs,
792 }
793 }
794}
795
796#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
798#[ts(export)]
799#[serde(rename_all = "camelCase")]
800pub struct BasePath {
801 #[ts(type = "[number, number]")]
803 pub from: [f64; 2],
804 #[ts(type = "[number, number]")]
806 pub to: [f64; 2],
807 pub units: UnitLen,
808 pub tag: Option<TagNode>,
810 #[serde(rename = "__geoMeta")]
812 pub geo_meta: GeoMeta,
813}
814
815#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
817#[ts(export)]
818#[serde(rename_all = "camelCase")]
819pub struct GeoMeta {
820 pub id: uuid::Uuid,
822 #[serde(flatten)]
824 pub metadata: Metadata,
825}
826
827#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
829#[ts(export)]
830#[serde(tag = "type")]
831pub enum Path {
832 ToPoint {
834 #[serde(flatten)]
835 base: BasePath,
836 },
837 TangentialArcTo {
839 #[serde(flatten)]
840 base: BasePath,
841 #[ts(type = "[number, number]")]
843 center: [f64; 2],
844 ccw: bool,
846 },
847 TangentialArc {
849 #[serde(flatten)]
850 base: BasePath,
851 #[ts(type = "[number, number]")]
853 center: [f64; 2],
854 ccw: bool,
856 },
857 Circle {
860 #[serde(flatten)]
861 base: BasePath,
862 #[ts(type = "[number, number]")]
864 center: [f64; 2],
865 radius: f64,
867 ccw: bool,
870 },
871 CircleThreePoint {
872 #[serde(flatten)]
873 base: BasePath,
874 #[ts(type = "[number, number]")]
876 p1: [f64; 2],
877 #[ts(type = "[number, number]")]
879 p2: [f64; 2],
880 #[ts(type = "[number, number]")]
882 p3: [f64; 2],
883 },
884 Horizontal {
886 #[serde(flatten)]
887 base: BasePath,
888 x: f64,
890 },
891 AngledLineTo {
893 #[serde(flatten)]
894 base: BasePath,
895 x: Option<f64>,
897 y: Option<f64>,
899 },
900 Base {
902 #[serde(flatten)]
903 base: BasePath,
904 },
905 Arc {
907 #[serde(flatten)]
908 base: BasePath,
909 center: [f64; 2],
911 radius: f64,
913 ccw: bool,
915 },
916}
917
918#[derive(Display)]
920enum PathType {
921 ToPoint,
922 Base,
923 TangentialArc,
924 TangentialArcTo,
925 Circle,
926 CircleThreePoint,
927 Horizontal,
928 AngledLineTo,
929 Arc,
930}
931
932impl From<&Path> for PathType {
933 fn from(value: &Path) -> Self {
934 match value {
935 Path::ToPoint { .. } => Self::ToPoint,
936 Path::TangentialArcTo { .. } => Self::TangentialArcTo,
937 Path::TangentialArc { .. } => Self::TangentialArc,
938 Path::Circle { .. } => Self::Circle,
939 Path::CircleThreePoint { .. } => Self::CircleThreePoint,
940 Path::Horizontal { .. } => Self::Horizontal,
941 Path::AngledLineTo { .. } => Self::AngledLineTo,
942 Path::Base { .. } => Self::Base,
943 Path::Arc { .. } => Self::Arc,
944 }
945 }
946}
947
948impl Path {
949 pub fn get_id(&self) -> uuid::Uuid {
950 match self {
951 Path::ToPoint { base } => base.geo_meta.id,
952 Path::Horizontal { base, .. } => base.geo_meta.id,
953 Path::AngledLineTo { base, .. } => base.geo_meta.id,
954 Path::Base { base } => base.geo_meta.id,
955 Path::TangentialArcTo { base, .. } => base.geo_meta.id,
956 Path::TangentialArc { base, .. } => base.geo_meta.id,
957 Path::Circle { base, .. } => base.geo_meta.id,
958 Path::CircleThreePoint { base, .. } => base.geo_meta.id,
959 Path::Arc { base, .. } => base.geo_meta.id,
960 }
961 }
962
963 pub fn get_tag(&self) -> Option<TagNode> {
964 match self {
965 Path::ToPoint { base } => base.tag.clone(),
966 Path::Horizontal { base, .. } => base.tag.clone(),
967 Path::AngledLineTo { base, .. } => base.tag.clone(),
968 Path::Base { base } => base.tag.clone(),
969 Path::TangentialArcTo { base, .. } => base.tag.clone(),
970 Path::TangentialArc { base, .. } => base.tag.clone(),
971 Path::Circle { base, .. } => base.tag.clone(),
972 Path::CircleThreePoint { base, .. } => base.tag.clone(),
973 Path::Arc { base, .. } => base.tag.clone(),
974 }
975 }
976
977 pub fn get_base(&self) -> &BasePath {
978 match self {
979 Path::ToPoint { base } => base,
980 Path::Horizontal { base, .. } => base,
981 Path::AngledLineTo { base, .. } => base,
982 Path::Base { base } => base,
983 Path::TangentialArcTo { base, .. } => base,
984 Path::TangentialArc { base, .. } => base,
985 Path::Circle { base, .. } => base,
986 Path::CircleThreePoint { base, .. } => base,
987 Path::Arc { base, .. } => base,
988 }
989 }
990
991 pub fn get_from(&self) -> &[f64; 2] {
993 &self.get_base().from
994 }
995 pub fn get_to(&self) -> &[f64; 2] {
997 &self.get_base().to
998 }
999
1000 pub fn length(&self) -> f64 {
1002 match self {
1003 Self::ToPoint { .. } | Self::Base { .. } | Self::Horizontal { .. } | Self::AngledLineTo { .. } => {
1004 linear_distance(self.get_from(), self.get_to())
1005 }
1006 Self::TangentialArc {
1007 base: _,
1008 center,
1009 ccw: _,
1010 }
1011 | Self::TangentialArcTo {
1012 base: _,
1013 center,
1014 ccw: _,
1015 } => {
1016 let radius = linear_distance(self.get_from(), center);
1019 debug_assert_eq!(radius, linear_distance(self.get_to(), center));
1020 linear_distance(self.get_from(), self.get_to())
1022 }
1023 Self::Circle { radius, .. } => 2.0 * std::f64::consts::PI * radius,
1024 Self::CircleThreePoint { .. } => {
1025 let circle_center = crate::std::utils::calculate_circle_from_3_points([
1026 self.get_base().from.into(),
1027 self.get_base().to.into(),
1028 self.get_base().to.into(),
1029 ]);
1030 let radius = linear_distance(&[circle_center.center.x, circle_center.center.y], &self.get_base().from);
1031 2.0 * std::f64::consts::PI * radius
1032 }
1033 Self::Arc { .. } => {
1034 linear_distance(self.get_from(), self.get_to())
1036 }
1037 }
1038 }
1039
1040 pub fn get_base_mut(&mut self) -> Option<&mut BasePath> {
1041 match self {
1042 Path::ToPoint { base } => Some(base),
1043 Path::Horizontal { base, .. } => Some(base),
1044 Path::AngledLineTo { base, .. } => Some(base),
1045 Path::Base { base } => Some(base),
1046 Path::TangentialArcTo { base, .. } => Some(base),
1047 Path::TangentialArc { base, .. } => Some(base),
1048 Path::Circle { base, .. } => Some(base),
1049 Path::CircleThreePoint { base, .. } => Some(base),
1050 Path::Arc { base, .. } => Some(base),
1051 }
1052 }
1053
1054 pub(crate) fn get_tangential_info(&self) -> GetTangentialInfoFromPathsResult {
1055 match self {
1056 Path::TangentialArc { center, ccw, .. }
1057 | Path::TangentialArcTo { center, ccw, .. }
1058 | Path::Arc { center, ccw, .. } => GetTangentialInfoFromPathsResult::Arc {
1059 center: *center,
1060 ccw: *ccw,
1061 },
1062 Path::Circle {
1063 center, ccw, radius, ..
1064 } => GetTangentialInfoFromPathsResult::Circle {
1065 center: *center,
1066 ccw: *ccw,
1067 radius: *radius,
1068 },
1069 Path::CircleThreePoint { p1, p2, p3, .. } => {
1070 let circle_center =
1071 crate::std::utils::calculate_circle_from_3_points([(*p1).into(), (*p2).into(), (*p3).into()]);
1072 let radius = linear_distance(&[circle_center.center.x, circle_center.center.y], p1);
1073 let center_point = [circle_center.center.x, circle_center.center.y];
1074 GetTangentialInfoFromPathsResult::Circle {
1075 center: center_point,
1076 ccw: true,
1077 radius,
1078 }
1079 }
1080 Path::ToPoint { .. } | Path::Horizontal { .. } | Path::AngledLineTo { .. } | Path::Base { .. } => {
1081 let base = self.get_base();
1082 GetTangentialInfoFromPathsResult::PreviousPoint(base.from)
1083 }
1084 }
1085 }
1086}
1087
1088#[rustfmt::skip]
1090fn linear_distance(
1091 [x0, y0]: &[f64; 2],
1092 [x1, y1]: &[f64; 2]
1093) -> f64 {
1094 let y_sq = (y1 - y0).powi(2);
1095 let x_sq = (x1 - x0).powi(2);
1096 (y_sq + x_sq).sqrt()
1097}
1098
1099#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
1101#[ts(export)]
1102#[serde(tag = "type", rename_all = "camelCase")]
1103pub enum ExtrudeSurface {
1104 ExtrudePlane(ExtrudePlane),
1106 ExtrudeArc(ExtrudeArc),
1107 Chamfer(ChamferSurface),
1108 Fillet(FilletSurface),
1109}
1110
1111#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
1113#[ts(export)]
1114#[serde(rename_all = "camelCase")]
1115pub struct ChamferSurface {
1116 pub face_id: uuid::Uuid,
1118 pub tag: Option<Node<TagDeclarator>>,
1120 #[serde(flatten)]
1122 pub geo_meta: GeoMeta,
1123}
1124
1125#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
1127#[ts(export)]
1128#[serde(rename_all = "camelCase")]
1129pub struct FilletSurface {
1130 pub face_id: uuid::Uuid,
1132 pub tag: Option<Node<TagDeclarator>>,
1134 #[serde(flatten)]
1136 pub geo_meta: GeoMeta,
1137}
1138
1139#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
1141#[ts(export)]
1142#[serde(rename_all = "camelCase")]
1143pub struct ExtrudePlane {
1144 pub face_id: uuid::Uuid,
1146 pub tag: Option<Node<TagDeclarator>>,
1148 #[serde(flatten)]
1150 pub geo_meta: GeoMeta,
1151}
1152
1153#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
1155#[ts(export)]
1156#[serde(rename_all = "camelCase")]
1157pub struct ExtrudeArc {
1158 pub face_id: uuid::Uuid,
1160 pub tag: Option<Node<TagDeclarator>>,
1162 #[serde(flatten)]
1164 pub geo_meta: GeoMeta,
1165}
1166
1167impl ExtrudeSurface {
1168 pub fn get_id(&self) -> uuid::Uuid {
1169 match self {
1170 ExtrudeSurface::ExtrudePlane(ep) => ep.geo_meta.id,
1171 ExtrudeSurface::ExtrudeArc(ea) => ea.geo_meta.id,
1172 ExtrudeSurface::Fillet(f) => f.geo_meta.id,
1173 ExtrudeSurface::Chamfer(c) => c.geo_meta.id,
1174 }
1175 }
1176
1177 pub fn get_tag(&self) -> Option<Node<TagDeclarator>> {
1178 match self {
1179 ExtrudeSurface::ExtrudePlane(ep) => ep.tag.clone(),
1180 ExtrudeSurface::ExtrudeArc(ea) => ea.tag.clone(),
1181 ExtrudeSurface::Fillet(f) => f.tag.clone(),
1182 ExtrudeSurface::Chamfer(c) => c.tag.clone(),
1183 }
1184 }
1185}