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