1use std::ops::{Add, AddAssign, Mul, Sub, SubAssign};
2
3use anyhow::Result;
4use indexmap::IndexMap;
5use kittycad_modeling_cmds as kcmc;
6use kittycad_modeling_cmds::{ModelingCmd, each_cmd as mcmd, length_unit::LengthUnit, websocket::ModelingCmdReq};
7use parse_display::{Display, FromStr};
8use schemars::JsonSchema;
9use serde::{Deserialize, Serialize};
10
11use crate::{
12 engine::{DEFAULT_PLANE_INFO, PlaneName},
13 errors::{KclError, KclErrorDetails},
14 execution::{
15 ArtifactId, ExecState, ExecutorContext, Metadata, TagEngineInfo, TagIdentifier, UnitLen, types::NumericType,
16 },
17 parsing::ast::types::{Node, NodeRef, TagDeclarator, TagNode},
18 std::{args::TyF64, sketch::PlaneData},
19};
20
21type Point3D = kcmc::shared::Point3d<f64>;
22
23#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
25#[ts(export)]
26#[serde(tag = "type")]
27#[allow(clippy::large_enum_variant)]
28pub enum Geometry {
29 Sketch(Sketch),
30 Solid(Solid),
31}
32
33impl Geometry {
34 pub fn id(&self) -> uuid::Uuid {
35 match self {
36 Geometry::Sketch(s) => s.id,
37 Geometry::Solid(e) => e.id,
38 }
39 }
40
41 pub fn original_id(&self) -> uuid::Uuid {
45 match self {
46 Geometry::Sketch(s) => s.original_id,
47 Geometry::Solid(e) => e.sketch.original_id,
48 }
49 }
50}
51
52#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
54#[ts(export)]
55#[serde(tag = "type")]
56#[allow(clippy::large_enum_variant)]
57pub enum GeometryWithImportedGeometry {
58 Sketch(Sketch),
59 Solid(Solid),
60 ImportedGeometry(Box<ImportedGeometry>),
61}
62
63impl GeometryWithImportedGeometry {
64 pub async fn id(&mut self, ctx: &ExecutorContext) -> Result<uuid::Uuid, KclError> {
65 match self {
66 GeometryWithImportedGeometry::Sketch(s) => Ok(s.id),
67 GeometryWithImportedGeometry::Solid(e) => Ok(e.id),
68 GeometryWithImportedGeometry::ImportedGeometry(i) => {
69 let id = i.id(ctx).await?;
70 Ok(id)
71 }
72 }
73 }
74}
75
76#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
78#[ts(export)]
79#[serde(tag = "type")]
80#[allow(clippy::vec_box)]
81pub enum Geometries {
82 Sketches(Vec<Sketch>),
83 Solids(Vec<Solid>),
84}
85
86impl From<Geometry> for Geometries {
87 fn from(value: Geometry) -> Self {
88 match value {
89 Geometry::Sketch(x) => Self::Sketches(vec![x]),
90 Geometry::Solid(x) => Self::Solids(vec![x]),
91 }
92 }
93}
94
95#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
97#[ts(export)]
98#[serde(rename_all = "camelCase")]
99pub struct ImportedGeometry {
100 pub id: uuid::Uuid,
102 pub value: Vec<String>,
104 #[serde(skip)]
105 pub meta: Vec<Metadata>,
106 #[serde(skip)]
108 completed: bool,
109}
110
111impl ImportedGeometry {
112 pub fn new(id: uuid::Uuid, value: Vec<String>, meta: Vec<Metadata>) -> Self {
113 Self {
114 id,
115 value,
116 meta,
117 completed: false,
118 }
119 }
120
121 async fn wait_for_finish(&mut self, ctx: &ExecutorContext) -> Result<(), KclError> {
122 if self.completed {
123 return Ok(());
124 }
125
126 ctx.engine
127 .ensure_async_command_completed(self.id, self.meta.first().map(|m| m.source_range))
128 .await?;
129
130 self.completed = true;
131
132 Ok(())
133 }
134
135 pub async fn id(&mut self, ctx: &ExecutorContext) -> Result<uuid::Uuid, KclError> {
136 if !self.completed {
137 self.wait_for_finish(ctx).await?;
138 }
139
140 Ok(self.id)
141 }
142}
143
144#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
146#[ts(export)]
147#[serde(tag = "type", rename_all = "camelCase")]
148#[allow(clippy::vec_box)]
149pub enum SolidOrSketchOrImportedGeometry {
150 ImportedGeometry(Box<ImportedGeometry>),
151 SolidSet(Vec<Solid>),
152 SketchSet(Vec<Sketch>),
153}
154
155impl From<SolidOrSketchOrImportedGeometry> for crate::execution::KclValue {
156 fn from(value: SolidOrSketchOrImportedGeometry) -> Self {
157 match value {
158 SolidOrSketchOrImportedGeometry::ImportedGeometry(s) => crate::execution::KclValue::ImportedGeometry(*s),
159 SolidOrSketchOrImportedGeometry::SolidSet(mut s) => {
160 if s.len() == 1 {
161 crate::execution::KclValue::Solid {
162 value: Box::new(s.pop().unwrap()),
163 }
164 } else {
165 crate::execution::KclValue::HomArray {
166 value: s
167 .into_iter()
168 .map(|s| crate::execution::KclValue::Solid { value: Box::new(s) })
169 .collect(),
170 ty: crate::execution::types::RuntimeType::solid(),
171 }
172 }
173 }
174 SolidOrSketchOrImportedGeometry::SketchSet(mut s) => {
175 if s.len() == 1 {
176 crate::execution::KclValue::Sketch {
177 value: Box::new(s.pop().unwrap()),
178 }
179 } else {
180 crate::execution::KclValue::HomArray {
181 value: s
182 .into_iter()
183 .map(|s| crate::execution::KclValue::Sketch { value: Box::new(s) })
184 .collect(),
185 ty: crate::execution::types::RuntimeType::sketch(),
186 }
187 }
188 }
189 }
190 }
191}
192
193impl SolidOrSketchOrImportedGeometry {
194 pub(crate) async fn ids(&mut self, ctx: &ExecutorContext) -> Result<Vec<uuid::Uuid>, KclError> {
195 match self {
196 SolidOrSketchOrImportedGeometry::ImportedGeometry(s) => {
197 let id = s.id(ctx).await?;
198
199 Ok(vec![id])
200 }
201 SolidOrSketchOrImportedGeometry::SolidSet(s) => Ok(s.iter().map(|s| s.id).collect()),
202 SolidOrSketchOrImportedGeometry::SketchSet(s) => Ok(s.iter().map(|s| s.id).collect()),
203 }
204 }
205}
206
207#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
209#[ts(export)]
210#[serde(tag = "type", rename_all = "camelCase")]
211#[allow(clippy::vec_box)]
212pub enum SolidOrImportedGeometry {
213 ImportedGeometry(Box<ImportedGeometry>),
214 SolidSet(Vec<Solid>),
215}
216
217impl From<SolidOrImportedGeometry> for crate::execution::KclValue {
218 fn from(value: SolidOrImportedGeometry) -> Self {
219 match value {
220 SolidOrImportedGeometry::ImportedGeometry(s) => crate::execution::KclValue::ImportedGeometry(*s),
221 SolidOrImportedGeometry::SolidSet(mut s) => {
222 if s.len() == 1 {
223 crate::execution::KclValue::Solid {
224 value: Box::new(s.pop().unwrap()),
225 }
226 } else {
227 crate::execution::KclValue::HomArray {
228 value: s
229 .into_iter()
230 .map(|s| crate::execution::KclValue::Solid { value: Box::new(s) })
231 .collect(),
232 ty: crate::execution::types::RuntimeType::solid(),
233 }
234 }
235 }
236 }
237 }
238}
239
240impl SolidOrImportedGeometry {
241 pub(crate) async fn ids(&mut self, ctx: &ExecutorContext) -> Result<Vec<uuid::Uuid>, KclError> {
242 match self {
243 SolidOrImportedGeometry::ImportedGeometry(s) => {
244 let id = s.id(ctx).await?;
245
246 Ok(vec![id])
247 }
248 SolidOrImportedGeometry::SolidSet(s) => Ok(s.iter().map(|s| s.id).collect()),
249 }
250 }
251}
252
253#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
255#[ts(export)]
256#[serde(rename_all = "camelCase")]
257pub struct Helix {
258 pub value: uuid::Uuid,
260 pub artifact_id: ArtifactId,
262 pub revolutions: f64,
264 pub angle_start: f64,
266 pub ccw: bool,
268 pub cylinder_id: Option<uuid::Uuid>,
270 pub units: UnitLen,
271 #[serde(skip)]
272 pub meta: Vec<Metadata>,
273}
274
275#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
276#[ts(export)]
277#[serde(rename_all = "camelCase")]
278pub struct Plane {
279 pub id: uuid::Uuid,
281 pub artifact_id: ArtifactId,
283 pub value: PlaneType,
285 #[serde(flatten)]
287 pub info: PlaneInfo,
288 #[serde(skip)]
289 pub meta: Vec<Metadata>,
290}
291
292#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, ts_rs::TS, JsonSchema)]
293#[ts(export)]
294#[serde(rename_all = "camelCase")]
295pub struct PlaneInfo {
296 pub origin: Point3d,
298 pub x_axis: Point3d,
300 pub y_axis: Point3d,
302 pub z_axis: Point3d,
304}
305
306impl PlaneInfo {
307 pub(crate) fn into_plane_data(self) -> PlaneData {
308 if self.origin.is_zero() {
309 match self {
310 Self {
311 origin:
312 Point3d {
313 x: 0.0,
314 y: 0.0,
315 z: 0.0,
316 units: UnitLen::Mm,
317 },
318 x_axis:
319 Point3d {
320 x: 1.0,
321 y: 0.0,
322 z: 0.0,
323 units: _,
324 },
325 y_axis:
326 Point3d {
327 x: 0.0,
328 y: 1.0,
329 z: 0.0,
330 units: _,
331 },
332 z_axis: _,
333 } => return PlaneData::XY,
334 Self {
335 origin:
336 Point3d {
337 x: 0.0,
338 y: 0.0,
339 z: 0.0,
340 units: UnitLen::Mm,
341 },
342 x_axis:
343 Point3d {
344 x: -1.0,
345 y: 0.0,
346 z: 0.0,
347 units: _,
348 },
349 y_axis:
350 Point3d {
351 x: 0.0,
352 y: 1.0,
353 z: 0.0,
354 units: _,
355 },
356 z_axis: _,
357 } => return PlaneData::NegXY,
358 Self {
359 origin:
360 Point3d {
361 x: 0.0,
362 y: 0.0,
363 z: 0.0,
364 units: UnitLen::Mm,
365 },
366 x_axis:
367 Point3d {
368 x: 1.0,
369 y: 0.0,
370 z: 0.0,
371 units: _,
372 },
373 y_axis:
374 Point3d {
375 x: 0.0,
376 y: 0.0,
377 z: 1.0,
378 units: _,
379 },
380 z_axis: _,
381 } => return PlaneData::XZ,
382 Self {
383 origin:
384 Point3d {
385 x: 0.0,
386 y: 0.0,
387 z: 0.0,
388 units: UnitLen::Mm,
389 },
390 x_axis:
391 Point3d {
392 x: -1.0,
393 y: 0.0,
394 z: 0.0,
395 units: _,
396 },
397 y_axis:
398 Point3d {
399 x: 0.0,
400 y: 0.0,
401 z: 1.0,
402 units: _,
403 },
404 z_axis: _,
405 } => return PlaneData::NegXZ,
406 Self {
407 origin:
408 Point3d {
409 x: 0.0,
410 y: 0.0,
411 z: 0.0,
412 units: UnitLen::Mm,
413 },
414 x_axis:
415 Point3d {
416 x: 0.0,
417 y: 1.0,
418 z: 0.0,
419 units: _,
420 },
421 y_axis:
422 Point3d {
423 x: 0.0,
424 y: 0.0,
425 z: 1.0,
426 units: _,
427 },
428 z_axis: _,
429 } => return PlaneData::YZ,
430 Self {
431 origin:
432 Point3d {
433 x: 0.0,
434 y: 0.0,
435 z: 0.0,
436 units: UnitLen::Mm,
437 },
438 x_axis:
439 Point3d {
440 x: 0.0,
441 y: -1.0,
442 z: 0.0,
443 units: _,
444 },
445 y_axis:
446 Point3d {
447 x: 0.0,
448 y: 0.0,
449 z: 1.0,
450 units: _,
451 },
452 z_axis: _,
453 } => return PlaneData::NegYZ,
454 _ => {}
455 }
456 }
457
458 PlaneData::Plane(Self {
459 origin: self.origin,
460 x_axis: self.x_axis,
461 y_axis: self.y_axis,
462 z_axis: self.z_axis,
463 })
464 }
465
466 pub(crate) fn is_right_handed(&self) -> bool {
467 let lhs = self
470 .x_axis
471 .axes_cross_product(&self.y_axis)
472 .axes_dot_product(&self.z_axis);
473 let rhs_x = self.x_axis.axes_dot_product(&self.x_axis);
474 let rhs_y = self.y_axis.axes_dot_product(&self.y_axis);
475 let rhs_z = self.z_axis.axes_dot_product(&self.z_axis);
476 let rhs = (rhs_x * rhs_y * rhs_z).sqrt();
477 (lhs - rhs).abs() <= 0.0001
479 }
480
481 #[cfg(test)]
482 pub(crate) fn is_left_handed(&self) -> bool {
483 !self.is_right_handed()
484 }
485
486 pub(crate) fn make_right_handed(self) -> Self {
487 if self.is_right_handed() {
488 return self;
489 }
490 Self {
492 origin: self.origin,
493 x_axis: self.x_axis.negated(),
494 y_axis: self.y_axis,
495 z_axis: self.z_axis,
496 }
497 }
498}
499
500impl TryFrom<PlaneData> for PlaneInfo {
501 type Error = KclError;
502
503 fn try_from(value: PlaneData) -> Result<Self, Self::Error> {
504 if let PlaneData::Plane(info) = value {
505 return Ok(info);
506 }
507 let name = match value {
508 PlaneData::XY => PlaneName::Xy,
509 PlaneData::NegXY => PlaneName::NegXy,
510 PlaneData::XZ => PlaneName::Xz,
511 PlaneData::NegXZ => PlaneName::NegXz,
512 PlaneData::YZ => PlaneName::Yz,
513 PlaneData::NegYZ => PlaneName::NegYz,
514 PlaneData::Plane(_) => {
515 return Err(KclError::new_internal(KclErrorDetails::new(
517 format!("PlaneData {value:?} not found"),
518 Default::default(),
519 )));
520 }
521 };
522
523 let info = DEFAULT_PLANE_INFO.get(&name).ok_or_else(|| {
524 KclError::new_internal(KclErrorDetails::new(
525 format!("Plane {name} not found"),
526 Default::default(),
527 ))
528 })?;
529
530 Ok(info.clone())
531 }
532}
533
534impl From<PlaneData> for PlaneType {
535 fn from(value: PlaneData) -> Self {
536 match value {
537 PlaneData::XY => PlaneType::XY,
538 PlaneData::NegXY => PlaneType::XY,
539 PlaneData::XZ => PlaneType::XZ,
540 PlaneData::NegXZ => PlaneType::XZ,
541 PlaneData::YZ => PlaneType::YZ,
542 PlaneData::NegYZ => PlaneType::YZ,
543 PlaneData::Plane(_) => PlaneType::Custom,
544 }
545 }
546}
547
548impl Plane {
549 pub(crate) fn from_plane_data(value: PlaneData, exec_state: &mut ExecState) -> Result<Self, KclError> {
550 let id = exec_state.next_uuid();
551 Ok(Plane {
552 id,
553 artifact_id: id.into(),
554 info: PlaneInfo::try_from(value.clone())?,
555 value: value.into(),
556 meta: vec![],
557 })
558 }
559
560 pub fn is_standard(&self) -> bool {
562 !matches!(self.value, PlaneType::Custom | PlaneType::Uninit)
563 }
564
565 pub fn project(&self, point: Point3d) -> Point3d {
568 let v = point - self.info.origin;
569 let dot = v.axes_dot_product(&self.info.z_axis);
570
571 point - self.info.z_axis * dot
572 }
573}
574
575#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
577#[ts(export)]
578#[serde(rename_all = "camelCase")]
579pub struct Face {
580 pub id: uuid::Uuid,
582 pub artifact_id: ArtifactId,
584 pub value: String,
586 pub x_axis: Point3d,
588 pub y_axis: Point3d,
590 pub solid: Box<Solid>,
592 pub units: UnitLen,
593 #[serde(skip)]
594 pub meta: Vec<Metadata>,
595}
596
597#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema, FromStr, Display)]
599#[ts(export)]
600#[display(style = "camelCase")]
601pub enum PlaneType {
602 #[serde(rename = "XY", alias = "xy")]
603 #[display("XY")]
604 XY,
605 #[serde(rename = "XZ", alias = "xz")]
606 #[display("XZ")]
607 XZ,
608 #[serde(rename = "YZ", alias = "yz")]
609 #[display("YZ")]
610 YZ,
611 #[display("Custom")]
613 Custom,
614 #[display("Uninit")]
616 Uninit,
617}
618
619#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
620#[ts(export)]
621#[serde(tag = "type", rename_all = "camelCase")]
622pub struct Sketch {
623 pub id: uuid::Uuid,
625 pub paths: Vec<Path>,
629 #[serde(default, skip_serializing_if = "Vec::is_empty")]
631 pub inner_paths: Vec<Path>,
632 pub on: SketchSurface,
634 pub start: BasePath,
636 #[serde(default, skip_serializing_if = "IndexMap::is_empty")]
638 pub tags: IndexMap<String, TagIdentifier>,
639 pub artifact_id: ArtifactId,
642 #[ts(skip)]
643 pub original_id: uuid::Uuid,
644 #[serde(skip)]
646 pub mirror: Option<uuid::Uuid>,
647 pub units: UnitLen,
648 #[serde(skip)]
650 pub meta: Vec<Metadata>,
651 #[serde(default = "very_true", skip_serializing_if = "is_true")]
653 pub is_closed: bool,
654}
655
656fn very_true() -> bool {
657 true
658}
659
660fn is_true(b: &bool) -> bool {
661 *b
662}
663
664impl Sketch {
665 pub(crate) fn build_sketch_mode_cmds(
668 &self,
669 exec_state: &mut ExecState,
670 inner_cmd: ModelingCmdReq,
671 ) -> Vec<ModelingCmdReq> {
672 vec![
673 ModelingCmdReq {
676 cmd: ModelingCmd::from(mcmd::EnableSketchMode {
677 animated: false,
678 ortho: false,
679 entity_id: self.on.id(),
680 adjust_camera: false,
681 planar_normal: if let SketchSurface::Plane(plane) = &self.on {
682 let normal = plane.info.x_axis.axes_cross_product(&plane.info.y_axis);
684 Some(normal.into())
685 } else {
686 None
687 },
688 }),
689 cmd_id: exec_state.next_uuid().into(),
690 },
691 inner_cmd,
692 ModelingCmdReq {
693 cmd: ModelingCmd::SketchModeDisable(mcmd::SketchModeDisable::default()),
694 cmd_id: exec_state.next_uuid().into(),
695 },
696 ]
697 }
698}
699
700#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
702#[ts(export)]
703#[serde(tag = "type", rename_all = "camelCase")]
704pub enum SketchSurface {
705 Plane(Box<Plane>),
706 Face(Box<Face>),
707}
708
709impl SketchSurface {
710 pub(crate) fn id(&self) -> uuid::Uuid {
711 match self {
712 SketchSurface::Plane(plane) => plane.id,
713 SketchSurface::Face(face) => face.id,
714 }
715 }
716 pub(crate) fn x_axis(&self) -> Point3d {
717 match self {
718 SketchSurface::Plane(plane) => plane.info.x_axis,
719 SketchSurface::Face(face) => face.x_axis,
720 }
721 }
722 pub(crate) fn y_axis(&self) -> Point3d {
723 match self {
724 SketchSurface::Plane(plane) => plane.info.y_axis,
725 SketchSurface::Face(face) => face.y_axis,
726 }
727 }
728}
729
730#[derive(Debug, Clone)]
731pub(crate) enum GetTangentialInfoFromPathsResult {
732 PreviousPoint([f64; 2]),
733 Arc {
734 center: [f64; 2],
735 ccw: bool,
736 },
737 Circle {
738 center: [f64; 2],
739 ccw: bool,
740 radius: f64,
741 },
742 Ellipse {
743 center: [f64; 2],
744 ccw: bool,
745 major_radius: f64,
746 _minor_radius: f64,
747 },
748}
749
750impl GetTangentialInfoFromPathsResult {
751 pub(crate) fn tan_previous_point(&self, last_arc_end: [f64; 2]) -> [f64; 2] {
752 match self {
753 GetTangentialInfoFromPathsResult::PreviousPoint(p) => *p,
754 GetTangentialInfoFromPathsResult::Arc { center, ccw } => {
755 crate::std::utils::get_tangent_point_from_previous_arc(*center, *ccw, last_arc_end)
756 }
757 GetTangentialInfoFromPathsResult::Circle {
760 center, radius, ccw, ..
761 } => [center[0] + radius, center[1] + if *ccw { -1.0 } else { 1.0 }],
762 GetTangentialInfoFromPathsResult::Ellipse {
763 center,
764 major_radius,
765 ccw,
766 ..
767 } => [center[0] + major_radius, center[1] + if *ccw { -1.0 } else { 1.0 }],
768 }
769 }
770}
771
772impl Sketch {
773 pub(crate) fn add_tag(&mut self, tag: NodeRef<'_, TagDeclarator>, current_path: &Path, exec_state: &ExecState) {
774 let mut tag_identifier: TagIdentifier = tag.into();
775 let base = current_path.get_base();
776 tag_identifier.info.push((
777 exec_state.stack().current_epoch(),
778 TagEngineInfo {
779 id: base.geo_meta.id,
780 sketch: self.id,
781 path: Some(current_path.clone()),
782 surface: None,
783 },
784 ));
785
786 self.tags.insert(tag.name.to_string(), tag_identifier);
787 }
788
789 pub(crate) fn merge_tags<'a>(&mut self, tags: impl Iterator<Item = &'a TagIdentifier>) {
790 for t in tags {
791 match self.tags.get_mut(&t.value) {
792 Some(id) => {
793 id.merge_info(t);
794 }
795 None => {
796 self.tags.insert(t.value.clone(), t.clone());
797 }
798 }
799 }
800 }
801
802 pub(crate) fn latest_path(&self) -> Option<&Path> {
804 self.paths.last()
805 }
806
807 pub(crate) fn current_pen_position(&self) -> Result<Point2d, KclError> {
811 let Some(path) = self.latest_path() else {
812 return Ok(Point2d::new(self.start.to[0], self.start.to[1], self.start.units));
813 };
814
815 let to = path.get_base().to;
816 Ok(Point2d::new(to[0], to[1], path.get_base().units))
817 }
818
819 pub(crate) fn get_tangential_info_from_paths(&self) -> GetTangentialInfoFromPathsResult {
820 let Some(path) = self.latest_path() else {
821 return GetTangentialInfoFromPathsResult::PreviousPoint(self.start.to);
822 };
823 path.get_tangential_info()
824 }
825}
826
827#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
828#[ts(export)]
829#[serde(tag = "type", rename_all = "camelCase")]
830pub struct Solid {
831 pub id: uuid::Uuid,
833 pub artifact_id: ArtifactId,
835 pub value: Vec<ExtrudeSurface>,
837 pub sketch: Sketch,
839 pub height: f64,
841 pub start_cap_id: Option<uuid::Uuid>,
843 pub end_cap_id: Option<uuid::Uuid>,
845 #[serde(default, skip_serializing_if = "Vec::is_empty")]
847 pub edge_cuts: Vec<EdgeCut>,
848 pub units: UnitLen,
850 pub sectional: bool,
852 #[serde(skip)]
854 pub meta: Vec<Metadata>,
855}
856
857impl Solid {
858 pub(crate) fn get_all_edge_cut_ids(&self) -> impl Iterator<Item = uuid::Uuid> + '_ {
859 self.edge_cuts.iter().map(|foc| foc.id())
860 }
861
862 pub(crate) fn height_in_mm(&self) -> f64 {
863 self.units.adjust_to(self.height, UnitLen::Mm).0
864 }
865}
866
867#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
869#[ts(export)]
870#[serde(tag = "type", rename_all = "camelCase")]
871pub enum EdgeCut {
872 Fillet {
874 id: uuid::Uuid,
876 radius: TyF64,
877 #[serde(rename = "edgeId")]
879 edge_id: uuid::Uuid,
880 tag: Box<Option<TagNode>>,
881 },
882 Chamfer {
884 id: uuid::Uuid,
886 length: TyF64,
887 #[serde(rename = "edgeId")]
889 edge_id: uuid::Uuid,
890 tag: Box<Option<TagNode>>,
891 },
892}
893
894impl EdgeCut {
895 pub fn id(&self) -> uuid::Uuid {
896 match self {
897 EdgeCut::Fillet { id, .. } => *id,
898 EdgeCut::Chamfer { id, .. } => *id,
899 }
900 }
901
902 pub fn set_id(&mut self, id: uuid::Uuid) {
903 match self {
904 EdgeCut::Fillet { id: i, .. } => *i = id,
905 EdgeCut::Chamfer { id: i, .. } => *i = id,
906 }
907 }
908
909 pub fn edge_id(&self) -> uuid::Uuid {
910 match self {
911 EdgeCut::Fillet { edge_id, .. } => *edge_id,
912 EdgeCut::Chamfer { edge_id, .. } => *edge_id,
913 }
914 }
915
916 pub fn set_edge_id(&mut self, id: uuid::Uuid) {
917 match self {
918 EdgeCut::Fillet { edge_id: i, .. } => *i = id,
919 EdgeCut::Chamfer { edge_id: i, .. } => *i = id,
920 }
921 }
922
923 pub fn tag(&self) -> Option<TagNode> {
924 match self {
925 EdgeCut::Fillet { tag, .. } => *tag.clone(),
926 EdgeCut::Chamfer { tag, .. } => *tag.clone(),
927 }
928 }
929}
930
931#[derive(Debug, Deserialize, Serialize, PartialEq, Clone, Copy, ts_rs::TS, JsonSchema)]
932#[ts(export)]
933pub struct Point2d {
934 pub x: f64,
935 pub y: f64,
936 pub units: UnitLen,
937}
938
939impl Point2d {
940 pub const ZERO: Self = Self {
941 x: 0.0,
942 y: 0.0,
943 units: UnitLen::Mm,
944 };
945
946 pub fn new(x: f64, y: f64, units: UnitLen) -> Self {
947 Self { x, y, units }
948 }
949
950 pub fn into_x(self) -> TyF64 {
951 TyF64::new(self.x, self.units.into())
952 }
953
954 pub fn into_y(self) -> TyF64 {
955 TyF64::new(self.y, self.units.into())
956 }
957
958 pub fn ignore_units(self) -> [f64; 2] {
959 [self.x, self.y]
960 }
961}
962
963#[derive(Debug, Deserialize, Serialize, PartialEq, Clone, Copy, ts_rs::TS, JsonSchema, Default)]
964#[ts(export)]
965pub struct Point3d {
966 pub x: f64,
967 pub y: f64,
968 pub z: f64,
969 pub units: UnitLen,
970}
971
972impl Point3d {
973 pub const ZERO: Self = Self {
974 x: 0.0,
975 y: 0.0,
976 z: 0.0,
977 units: UnitLen::Mm,
978 };
979
980 pub fn new(x: f64, y: f64, z: f64, units: UnitLen) -> Self {
981 Self { x, y, z, units }
982 }
983
984 pub const fn is_zero(&self) -> bool {
985 self.x == 0.0 && self.y == 0.0 && self.z == 0.0
986 }
987
988 pub fn axes_cross_product(&self, other: &Self) -> Self {
993 Self {
994 x: self.y * other.z - self.z * other.y,
995 y: self.z * other.x - self.x * other.z,
996 z: self.x * other.y - self.y * other.x,
997 units: UnitLen::Unknown,
998 }
999 }
1000
1001 pub fn axes_dot_product(&self, other: &Self) -> f64 {
1006 let x = self.x * other.x;
1007 let y = self.y * other.y;
1008 let z = self.z * other.z;
1009 x + y + z
1010 }
1011
1012 pub fn normalize(&self) -> Self {
1013 let len = f64::sqrt(self.x * self.x + self.y * self.y + self.z * self.z);
1014 Point3d {
1015 x: self.x / len,
1016 y: self.y / len,
1017 z: self.z / len,
1018 units: UnitLen::Unknown,
1019 }
1020 }
1021
1022 pub fn as_3_dims(&self) -> ([f64; 3], UnitLen) {
1023 let p = [self.x, self.y, self.z];
1024 let u = self.units;
1025 (p, u)
1026 }
1027
1028 pub(crate) fn negated(self) -> Self {
1029 Self {
1030 x: -self.x,
1031 y: -self.y,
1032 z: -self.z,
1033 units: self.units,
1034 }
1035 }
1036}
1037
1038impl From<[TyF64; 3]> for Point3d {
1039 fn from(p: [TyF64; 3]) -> Self {
1040 Self {
1041 x: p[0].n,
1042 y: p[1].n,
1043 z: p[2].n,
1044 units: p[0].ty.expect_length(),
1045 }
1046 }
1047}
1048
1049impl From<Point3d> for Point3D {
1050 fn from(p: Point3d) -> Self {
1051 Self { x: p.x, y: p.y, z: p.z }
1052 }
1053}
1054
1055impl From<Point3d> for kittycad_modeling_cmds::shared::Point3d<LengthUnit> {
1056 fn from(p: Point3d) -> Self {
1057 Self {
1058 x: LengthUnit(p.units.adjust_to(p.x, UnitLen::Mm).0),
1059 y: LengthUnit(p.units.adjust_to(p.y, UnitLen::Mm).0),
1060 z: LengthUnit(p.units.adjust_to(p.z, UnitLen::Mm).0),
1061 }
1062 }
1063}
1064
1065impl Add for Point3d {
1066 type Output = Point3d;
1067
1068 fn add(self, rhs: Self) -> Self::Output {
1069 Point3d {
1071 x: self.x + rhs.x,
1072 y: self.y + rhs.y,
1073 z: self.z + rhs.z,
1074 units: self.units,
1075 }
1076 }
1077}
1078
1079impl AddAssign for Point3d {
1080 fn add_assign(&mut self, rhs: Self) {
1081 *self = *self + rhs
1082 }
1083}
1084
1085impl Sub for Point3d {
1086 type Output = Point3d;
1087
1088 fn sub(self, rhs: Self) -> Self::Output {
1089 let (x, y, z) = if rhs.units != self.units {
1090 (
1091 rhs.units.adjust_to(rhs.x, self.units).0,
1092 rhs.units.adjust_to(rhs.y, self.units).0,
1093 rhs.units.adjust_to(rhs.z, self.units).0,
1094 )
1095 } else {
1096 (rhs.x, rhs.y, rhs.z)
1097 };
1098 Point3d {
1099 x: self.x - x,
1100 y: self.y - y,
1101 z: self.z - z,
1102 units: self.units,
1103 }
1104 }
1105}
1106
1107impl SubAssign for Point3d {
1108 fn sub_assign(&mut self, rhs: Self) {
1109 *self = *self - rhs
1110 }
1111}
1112
1113impl Mul<f64> for Point3d {
1114 type Output = Point3d;
1115
1116 fn mul(self, rhs: f64) -> Self::Output {
1117 Point3d {
1118 x: self.x * rhs,
1119 y: self.y * rhs,
1120 z: self.z * rhs,
1121 units: self.units,
1122 }
1123 }
1124}
1125
1126#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
1128#[ts(export)]
1129#[serde(rename_all = "camelCase")]
1130pub struct BasePath {
1131 #[ts(type = "[number, number]")]
1133 pub from: [f64; 2],
1134 #[ts(type = "[number, number]")]
1136 pub to: [f64; 2],
1137 pub units: UnitLen,
1138 pub tag: Option<TagNode>,
1140 #[serde(rename = "__geoMeta")]
1142 pub geo_meta: GeoMeta,
1143}
1144
1145impl BasePath {
1146 pub fn get_to(&self) -> [TyF64; 2] {
1147 let ty: NumericType = self.units.into();
1148 [TyF64::new(self.to[0], ty), TyF64::new(self.to[1], ty)]
1149 }
1150
1151 pub fn get_from(&self) -> [TyF64; 2] {
1152 let ty: NumericType = self.units.into();
1153 [TyF64::new(self.from[0], ty), TyF64::new(self.from[1], ty)]
1154 }
1155}
1156
1157#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
1159#[ts(export)]
1160#[serde(rename_all = "camelCase")]
1161pub struct GeoMeta {
1162 pub id: uuid::Uuid,
1164 #[serde(flatten)]
1166 pub metadata: Metadata,
1167}
1168
1169#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
1171#[ts(export)]
1172#[serde(tag = "type")]
1173pub enum Path {
1174 ToPoint {
1176 #[serde(flatten)]
1177 base: BasePath,
1178 },
1179 TangentialArcTo {
1181 #[serde(flatten)]
1182 base: BasePath,
1183 #[ts(type = "[number, number]")]
1185 center: [f64; 2],
1186 ccw: bool,
1188 },
1189 TangentialArc {
1191 #[serde(flatten)]
1192 base: BasePath,
1193 #[ts(type = "[number, number]")]
1195 center: [f64; 2],
1196 ccw: bool,
1198 },
1199 Circle {
1202 #[serde(flatten)]
1203 base: BasePath,
1204 #[ts(type = "[number, number]")]
1206 center: [f64; 2],
1207 radius: f64,
1209 ccw: bool,
1212 },
1213 CircleThreePoint {
1214 #[serde(flatten)]
1215 base: BasePath,
1216 #[ts(type = "[number, number]")]
1218 p1: [f64; 2],
1219 #[ts(type = "[number, number]")]
1221 p2: [f64; 2],
1222 #[ts(type = "[number, number]")]
1224 p3: [f64; 2],
1225 },
1226 ArcThreePoint {
1227 #[serde(flatten)]
1228 base: BasePath,
1229 #[ts(type = "[number, number]")]
1231 p1: [f64; 2],
1232 #[ts(type = "[number, number]")]
1234 p2: [f64; 2],
1235 #[ts(type = "[number, number]")]
1237 p3: [f64; 2],
1238 },
1239 Horizontal {
1241 #[serde(flatten)]
1242 base: BasePath,
1243 x: f64,
1245 },
1246 AngledLineTo {
1248 #[serde(flatten)]
1249 base: BasePath,
1250 x: Option<f64>,
1252 y: Option<f64>,
1254 },
1255 Base {
1257 #[serde(flatten)]
1258 base: BasePath,
1259 },
1260 Arc {
1262 #[serde(flatten)]
1263 base: BasePath,
1264 center: [f64; 2],
1266 radius: f64,
1268 ccw: bool,
1270 },
1271 Ellipse {
1272 #[serde(flatten)]
1273 base: BasePath,
1274 center: [f64; 2],
1275 major_radius: f64,
1276 minor_radius: f64,
1277 ccw: bool,
1278 },
1279 Conic {
1281 #[serde(flatten)]
1282 base: BasePath,
1283 },
1284}
1285
1286#[derive(Display)]
1288enum PathType {
1289 ToPoint,
1290 Base,
1291 TangentialArc,
1292 TangentialArcTo,
1293 Circle,
1294 CircleThreePoint,
1295 Horizontal,
1296 AngledLineTo,
1297 Arc,
1298 Ellipse,
1299 Conic,
1300}
1301
1302impl From<&Path> for PathType {
1303 fn from(value: &Path) -> Self {
1304 match value {
1305 Path::ToPoint { .. } => Self::ToPoint,
1306 Path::TangentialArcTo { .. } => Self::TangentialArcTo,
1307 Path::TangentialArc { .. } => Self::TangentialArc,
1308 Path::Circle { .. } => Self::Circle,
1309 Path::CircleThreePoint { .. } => Self::CircleThreePoint,
1310 Path::Horizontal { .. } => Self::Horizontal,
1311 Path::AngledLineTo { .. } => Self::AngledLineTo,
1312 Path::Base { .. } => Self::Base,
1313 Path::Arc { .. } => Self::Arc,
1314 Path::ArcThreePoint { .. } => Self::Arc,
1315 Path::Ellipse { .. } => Self::Ellipse,
1316 Path::Conic { .. } => Self::Conic,
1317 }
1318 }
1319}
1320
1321impl Path {
1322 pub fn get_id(&self) -> uuid::Uuid {
1323 match self {
1324 Path::ToPoint { base } => base.geo_meta.id,
1325 Path::Horizontal { base, .. } => base.geo_meta.id,
1326 Path::AngledLineTo { base, .. } => base.geo_meta.id,
1327 Path::Base { base } => base.geo_meta.id,
1328 Path::TangentialArcTo { base, .. } => base.geo_meta.id,
1329 Path::TangentialArc { base, .. } => base.geo_meta.id,
1330 Path::Circle { base, .. } => base.geo_meta.id,
1331 Path::CircleThreePoint { base, .. } => base.geo_meta.id,
1332 Path::Arc { base, .. } => base.geo_meta.id,
1333 Path::ArcThreePoint { base, .. } => base.geo_meta.id,
1334 Path::Ellipse { base, .. } => base.geo_meta.id,
1335 Path::Conic { base, .. } => base.geo_meta.id,
1336 }
1337 }
1338
1339 pub fn set_id(&mut self, id: uuid::Uuid) {
1340 match self {
1341 Path::ToPoint { base } => base.geo_meta.id = id,
1342 Path::Horizontal { base, .. } => base.geo_meta.id = id,
1343 Path::AngledLineTo { base, .. } => base.geo_meta.id = id,
1344 Path::Base { base } => base.geo_meta.id = id,
1345 Path::TangentialArcTo { base, .. } => base.geo_meta.id = id,
1346 Path::TangentialArc { base, .. } => base.geo_meta.id = id,
1347 Path::Circle { base, .. } => base.geo_meta.id = id,
1348 Path::CircleThreePoint { base, .. } => base.geo_meta.id = id,
1349 Path::Arc { base, .. } => base.geo_meta.id = id,
1350 Path::ArcThreePoint { base, .. } => base.geo_meta.id = id,
1351 Path::Ellipse { base, .. } => base.geo_meta.id = id,
1352 Path::Conic { base, .. } => base.geo_meta.id = id,
1353 }
1354 }
1355
1356 pub fn get_tag(&self) -> Option<TagNode> {
1357 match self {
1358 Path::ToPoint { base } => base.tag.clone(),
1359 Path::Horizontal { base, .. } => base.tag.clone(),
1360 Path::AngledLineTo { base, .. } => base.tag.clone(),
1361 Path::Base { base } => base.tag.clone(),
1362 Path::TangentialArcTo { base, .. } => base.tag.clone(),
1363 Path::TangentialArc { base, .. } => base.tag.clone(),
1364 Path::Circle { base, .. } => base.tag.clone(),
1365 Path::CircleThreePoint { base, .. } => base.tag.clone(),
1366 Path::Arc { base, .. } => base.tag.clone(),
1367 Path::ArcThreePoint { base, .. } => base.tag.clone(),
1368 Path::Ellipse { base, .. } => base.tag.clone(),
1369 Path::Conic { base, .. } => base.tag.clone(),
1370 }
1371 }
1372
1373 pub fn get_base(&self) -> &BasePath {
1374 match self {
1375 Path::ToPoint { base } => base,
1376 Path::Horizontal { base, .. } => base,
1377 Path::AngledLineTo { base, .. } => base,
1378 Path::Base { base } => base,
1379 Path::TangentialArcTo { base, .. } => base,
1380 Path::TangentialArc { base, .. } => base,
1381 Path::Circle { base, .. } => base,
1382 Path::CircleThreePoint { base, .. } => base,
1383 Path::Arc { base, .. } => base,
1384 Path::ArcThreePoint { base, .. } => base,
1385 Path::Ellipse { base, .. } => base,
1386 Path::Conic { base, .. } => base,
1387 }
1388 }
1389
1390 pub fn get_from(&self) -> [TyF64; 2] {
1392 let p = &self.get_base().from;
1393 let ty: NumericType = self.get_base().units.into();
1394 [TyF64::new(p[0], ty), TyF64::new(p[1], ty)]
1395 }
1396
1397 pub fn get_to(&self) -> [TyF64; 2] {
1399 let p = &self.get_base().to;
1400 let ty: NumericType = self.get_base().units.into();
1401 [TyF64::new(p[0], ty), TyF64::new(p[1], ty)]
1402 }
1403
1404 pub fn start_point_components(&self) -> ([f64; 2], NumericType) {
1406 let p = &self.get_base().from;
1407 let ty: NumericType = self.get_base().units.into();
1408 (*p, ty)
1409 }
1410
1411 pub fn end_point_components(&self) -> ([f64; 2], NumericType) {
1413 let p = &self.get_base().to;
1414 let ty: NumericType = self.get_base().units.into();
1415 (*p, ty)
1416 }
1417
1418 pub fn length(&self) -> Option<TyF64> {
1421 let n = match self {
1422 Self::ToPoint { .. } | Self::Base { .. } | Self::Horizontal { .. } | Self::AngledLineTo { .. } => {
1423 Some(linear_distance(&self.get_base().from, &self.get_base().to))
1424 }
1425 Self::TangentialArc {
1426 base: _,
1427 center,
1428 ccw: _,
1429 }
1430 | Self::TangentialArcTo {
1431 base: _,
1432 center,
1433 ccw: _,
1434 } => {
1435 let radius = linear_distance(&self.get_base().from, center);
1438 debug_assert_eq!(radius, linear_distance(&self.get_base().to, center));
1439 Some(linear_distance(&self.get_base().from, &self.get_base().to))
1441 }
1442 Self::Circle { radius, .. } => Some(2.0 * std::f64::consts::PI * radius),
1443 Self::CircleThreePoint { .. } => {
1444 let circle_center = crate::std::utils::calculate_circle_from_3_points([
1445 self.get_base().from,
1446 self.get_base().to,
1447 self.get_base().to,
1448 ]);
1449 let radius = linear_distance(
1450 &[circle_center.center[0], circle_center.center[1]],
1451 &self.get_base().from,
1452 );
1453 Some(2.0 * std::f64::consts::PI * radius)
1454 }
1455 Self::Arc { .. } => {
1456 Some(linear_distance(&self.get_base().from, &self.get_base().to))
1458 }
1459 Self::ArcThreePoint { .. } => {
1460 Some(linear_distance(&self.get_base().from, &self.get_base().to))
1462 }
1463 Self::Ellipse { .. } => {
1464 None
1466 }
1467 Self::Conic { .. } => {
1468 None
1470 }
1471 };
1472 n.map(|n| TyF64::new(n, self.get_base().units.into()))
1473 }
1474
1475 pub fn get_base_mut(&mut self) -> Option<&mut BasePath> {
1476 match self {
1477 Path::ToPoint { base } => Some(base),
1478 Path::Horizontal { base, .. } => Some(base),
1479 Path::AngledLineTo { base, .. } => Some(base),
1480 Path::Base { base } => Some(base),
1481 Path::TangentialArcTo { base, .. } => Some(base),
1482 Path::TangentialArc { base, .. } => Some(base),
1483 Path::Circle { base, .. } => Some(base),
1484 Path::CircleThreePoint { base, .. } => Some(base),
1485 Path::Arc { base, .. } => Some(base),
1486 Path::ArcThreePoint { base, .. } => Some(base),
1487 Path::Ellipse { base, .. } => Some(base),
1488 Path::Conic { base, .. } => Some(base),
1489 }
1490 }
1491
1492 pub(crate) fn get_tangential_info(&self) -> GetTangentialInfoFromPathsResult {
1493 match self {
1494 Path::TangentialArc { center, ccw, .. }
1495 | Path::TangentialArcTo { center, ccw, .. }
1496 | Path::Arc { center, ccw, .. } => GetTangentialInfoFromPathsResult::Arc {
1497 center: *center,
1498 ccw: *ccw,
1499 },
1500 Path::ArcThreePoint { p1, p2, p3, .. } => {
1501 let circle = crate::std::utils::calculate_circle_from_3_points([*p1, *p2, *p3]);
1502 GetTangentialInfoFromPathsResult::Arc {
1503 center: circle.center,
1504 ccw: crate::std::utils::is_points_ccw(&[*p1, *p2, *p3]) > 0,
1505 }
1506 }
1507 Path::Circle {
1508 center, ccw, radius, ..
1509 } => GetTangentialInfoFromPathsResult::Circle {
1510 center: *center,
1511 ccw: *ccw,
1512 radius: *radius,
1513 },
1514 Path::CircleThreePoint { p1, p2, p3, .. } => {
1515 let circle = crate::std::utils::calculate_circle_from_3_points([*p1, *p2, *p3]);
1516 let center_point = [circle.center[0], circle.center[1]];
1517 GetTangentialInfoFromPathsResult::Circle {
1518 center: center_point,
1519 ccw: true,
1521 radius: circle.radius,
1522 }
1523 }
1524 Path::Ellipse {
1526 center,
1527 major_radius,
1528 minor_radius,
1529 ccw,
1530 ..
1531 } => GetTangentialInfoFromPathsResult::Ellipse {
1532 center: *center,
1533 major_radius: *major_radius,
1534 _minor_radius: *minor_radius,
1535 ccw: *ccw,
1536 },
1537 Path::Conic { .. }
1538 | Path::ToPoint { .. }
1539 | Path::Horizontal { .. }
1540 | Path::AngledLineTo { .. }
1541 | Path::Base { .. } => {
1542 let base = self.get_base();
1543 GetTangentialInfoFromPathsResult::PreviousPoint(base.from)
1544 }
1545 }
1546 }
1547
1548 pub(crate) fn is_straight_line(&self) -> bool {
1550 matches!(self, Path::AngledLineTo { .. } | Path::ToPoint { .. })
1551 }
1552}
1553
1554#[rustfmt::skip]
1556fn linear_distance(
1557 [x0, y0]: &[f64; 2],
1558 [x1, y1]: &[f64; 2]
1559) -> f64 {
1560 let y_sq = (y1 - y0).powi(2);
1561 let x_sq = (x1 - x0).powi(2);
1562 (y_sq + x_sq).sqrt()
1563}
1564
1565#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
1567#[ts(export)]
1568#[serde(tag = "type", rename_all = "camelCase")]
1569pub enum ExtrudeSurface {
1570 ExtrudePlane(ExtrudePlane),
1572 ExtrudeArc(ExtrudeArc),
1573 Chamfer(ChamferSurface),
1574 Fillet(FilletSurface),
1575}
1576
1577#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
1579#[ts(export)]
1580#[serde(rename_all = "camelCase")]
1581pub struct ChamferSurface {
1582 pub face_id: uuid::Uuid,
1584 pub tag: Option<Node<TagDeclarator>>,
1586 #[serde(flatten)]
1588 pub geo_meta: GeoMeta,
1589}
1590
1591#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
1593#[ts(export)]
1594#[serde(rename_all = "camelCase")]
1595pub struct FilletSurface {
1596 pub face_id: uuid::Uuid,
1598 pub tag: Option<Node<TagDeclarator>>,
1600 #[serde(flatten)]
1602 pub geo_meta: GeoMeta,
1603}
1604
1605#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
1607#[ts(export)]
1608#[serde(rename_all = "camelCase")]
1609pub struct ExtrudePlane {
1610 pub face_id: uuid::Uuid,
1612 pub tag: Option<Node<TagDeclarator>>,
1614 #[serde(flatten)]
1616 pub geo_meta: GeoMeta,
1617}
1618
1619#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
1621#[ts(export)]
1622#[serde(rename_all = "camelCase")]
1623pub struct ExtrudeArc {
1624 pub face_id: uuid::Uuid,
1626 pub tag: Option<Node<TagDeclarator>>,
1628 #[serde(flatten)]
1630 pub geo_meta: GeoMeta,
1631}
1632
1633impl ExtrudeSurface {
1634 pub fn get_id(&self) -> uuid::Uuid {
1635 match self {
1636 ExtrudeSurface::ExtrudePlane(ep) => ep.geo_meta.id,
1637 ExtrudeSurface::ExtrudeArc(ea) => ea.geo_meta.id,
1638 ExtrudeSurface::Fillet(f) => f.geo_meta.id,
1639 ExtrudeSurface::Chamfer(c) => c.geo_meta.id,
1640 }
1641 }
1642
1643 pub fn get_tag(&self) -> Option<Node<TagDeclarator>> {
1644 match self {
1645 ExtrudeSurface::ExtrudePlane(ep) => ep.tag.clone(),
1646 ExtrudeSurface::ExtrudeArc(ea) => ea.tag.clone(),
1647 ExtrudeSurface::Fillet(f) => f.tag.clone(),
1648 ExtrudeSurface::Chamfer(c) => c.tag.clone(),
1649 }
1650 }
1651}