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