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